From ecb368ddb602812048576f3e0a013958d1fdfcd7 Mon Sep 17 00:00:00 2001 From: Evennia docbuilder action Date: Sat, 5 Feb 2022 15:09:22 +0000 Subject: [PATCH] Updated HTML docs --- docs/0.9.5/.buildinfo | 4 - docs/0.9.5/.nojekyll | 0 ...-voice-operated-elevator-using-events.html | 541 - docs/0.9.5/API-refactoring.html | 167 - docs/0.9.5/Accounts.html | 217 - docs/0.9.5/Add-a-simple-new-web-page.html | 206 - docs/0.9.5/Add-a-wiki-on-your-website.html | 354 - docs/0.9.5/Adding-Command-Tutorial.html | 279 - .../Adding-Object-Typeclass-Tutorial.html | 227 - docs/0.9.5/Administrative-Docs.html | 206 - docs/0.9.5/Apache-Config.html | 311 - docs/0.9.5/Arxcode-installing-help.html | 367 - docs/0.9.5/Async-Process.html | 328 - docs/0.9.5/Attributes.html | 485 - docs/0.9.5/Banning.html | 270 - docs/0.9.5/Batch-Code-Processor.html | 354 - docs/0.9.5/Batch-Command-Processor.html | 297 - docs/0.9.5/Batch-Processors.html | 190 - docs/0.9.5/Bootstrap-&-Evennia.html | 219 - .../Bootstrap-Components-and-Utilities.html | 226 - docs/0.9.5/Builder-Docs.html | 160 - docs/0.9.5/Building-Permissions.html | 190 - docs/0.9.5/Building-Quickstart.html | 383 - docs/0.9.5/Building-a-mech-tutorial.html | 350 - docs/0.9.5/Building-menus.html | 1320 -- docs/0.9.5/Choosing-An-SQL-Server.html | 358 - docs/0.9.5/Client-Support-Grid.html | 279 - docs/0.9.5/Coding-FAQ.html | 494 - docs/0.9.5/Coding-Introduction.html | 220 - docs/0.9.5/Coding-Utils.html | 426 - docs/0.9.5/Command-Cooldown.html | 211 - docs/0.9.5/Command-Duration.html | 512 - docs/0.9.5/Command-Prompt.html | 240 - docs/0.9.5/Command-Sets.html | 491 - docs/0.9.5/Command-System.html | 122 - docs/0.9.5/Commands.html | 776 - docs/0.9.5/Communications.html | 234 - docs/0.9.5/Connection-Screen.html | 152 - docs/0.9.5/Continuous-Integration.html | 379 - docs/0.9.5/Contributing-Docs.html | 820 - docs/0.9.5/Contributing.html | 233 - docs/0.9.5/Coordinates.html | 456 - docs/0.9.5/Custom-Protocols.html | 343 - docs/0.9.5/Customize-channels.html | 586 - docs/0.9.5/Debugging.html | 408 - docs/0.9.5/Default-Commands.html | 215 - docs/0.9.5/Default-Exit-Errors.html | 232 - docs/0.9.5/Developer-Central.html | 295 - docs/0.9.5/Dialogues-in-events.html | 348 - docs/0.9.5/Directory-Overview.html | 197 - docs/0.9.5/Docs-refactoring.html | 230 - docs/0.9.5/Dynamic-In-Game-Map.html | 582 - docs/0.9.5/EvEditor.html | 297 - docs/0.9.5/EvMenu.html | 1368 -- docs/0.9.5/EvMore.html | 151 - docs/0.9.5/Evennia-API.html | 244 - docs/0.9.5/Evennia-Game-Index.html | 189 - docs/0.9.5/Evennia-Introduction.html | 310 - docs/0.9.5/Evennia-for-Diku-Users.html | 298 - docs/0.9.5/Evennia-for-MUSH-Users.html | 317 - .../Evennia-for-roleplaying-sessions.html | 841 - docs/0.9.5/Execute-Python-Code.html | 240 - docs/0.9.5/First-Steps-Coding.html | 397 - docs/0.9.5/Game-Planning.html | 335 - docs/0.9.5/Gametime-Tutorial.html | 400 - docs/0.9.5/Getting-Started.html | 638 - docs/0.9.5/Glossary.html | 513 - docs/0.9.5/Grapevine.html | 180 - docs/0.9.5/Guest-Logins.html | 137 - docs/0.9.5/HAProxy-Config.html | 270 - docs/0.9.5/Help-System-Tutorial.html | 577 - docs/0.9.5/Help-System.html | 230 - docs/0.9.5/How-To-Get-And-Give-Help.html | 179 - .../How-to-connect-Evennia-to-Twitter.html | 220 - docs/0.9.5/IRC.html | 199 - .../Implementing-a-game-rule-system.html | 426 - docs/0.9.5/Inputfuncs.html | 313 - docs/0.9.5/Installing-on-Android.html | 250 - docs/0.9.5/Internationalization.html | 203 - ...Learn-Python-for-Evennia-The-Hard-Way.html | 190 - docs/0.9.5/Licensing.html | 132 - docs/0.9.5/Links.html | 323 - docs/0.9.5/Locks.html | 607 - docs/0.9.5/Manually-Configuring-Color.html | 276 - docs/0.9.5/Mass-and-weight-for-objects.html | 205 - docs/0.9.5/Messagepath.html | 403 - docs/0.9.5/MonitorHandler.html | 189 - docs/0.9.5/NPC-shop-Tutorial.html | 442 - docs/0.9.5/New-Models.html | 363 - docs/0.9.5/Nicks.html | 231 - docs/0.9.5/OOB.html | 297 - docs/0.9.5/Objects.html | 305 - docs/0.9.5/Online-Setup.html | 602 - ...-arguments,-theory-and-best-practices.html | 831 - docs/0.9.5/Portal-And-Server.html | 120 - docs/0.9.5/Profiling.html | 235 - docs/0.9.5/Python-3.html | 113 - docs/0.9.5/Python-basic-introduction.html | 363 - .../0.9.5/Python-basic-tutorial-part-two.html | 572 - docs/0.9.5/Quirks.html | 232 - docs/0.9.5/RSS.html | 166 - docs/0.9.5/Roadmap.html | 114 - docs/0.9.5/Running-Evennia-in-Docker.html | 409 - docs/0.9.5/Screenshot.html | 124 - docs/0.9.5/Scripts.html | 486 - docs/0.9.5/Security.html | 278 - docs/0.9.5/Server-Conf.html | 219 - docs/0.9.5/Sessions.html | 303 - docs/0.9.5/Setting-up-PyCharm.html | 240 - docs/0.9.5/Signals.html | 230 - docs/0.9.5/Soft-Code.html | 204 - docs/0.9.5/Spawner-and-Prototypes.html | 441 - docs/0.9.5/Start-Stop-Reload.html | 315 - docs/0.9.5/Static-In-Game-Map.html | 507 - docs/0.9.5/Tags.html | 278 - docs/0.9.5/Text-Encodings.html | 177 - docs/0.9.5/TextTags.html | 449 - docs/0.9.5/TickerHandler.html | 233 - docs/0.9.5/Turn-based-Combat-System.html | 621 - docs/0.9.5/Tutorial-Aggressive-NPCs.html | 220 - docs/0.9.5/Tutorial-NPCs-listening.html | 220 - .../0.9.5/Tutorial-Searching-For-Objects.html | 528 - docs/0.9.5/Tutorial-Tweeting-Game-Stats.html | 202 - docs/0.9.5/Tutorial-Vehicles.html | 504 - docs/0.9.5/Tutorial-World-Introduction.html | 222 - .../Tutorial-for-basic-MUSH-like-game.html | 739 - docs/0.9.5/Tutorials.html | 301 - docs/0.9.5/Typeclasses.html | 441 - docs/0.9.5/Understanding-Color-Tags.html | 279 - docs/0.9.5/Unit-Testing.html | 537 - docs/0.9.5/Updating-Your-Game.html | 247 - docs/0.9.5/Using-MUX-as-a-Standard.html | 196 - docs/0.9.5/Using-Travis.html | 143 - docs/0.9.5/Version-Control.html | 569 - docs/0.9.5/Weather-Tutorial.html | 156 - docs/0.9.5/Web-Character-Generation.html | 751 - docs/0.9.5/Web-Character-View-Tutorial.html | 313 - docs/0.9.5/Web-Features.html | 243 - docs/0.9.5/Web-Tutorial.html | 229 - docs/0.9.5/Webclient-brainstorm.html | 450 - docs/0.9.5/Webclient.html | 389 - docs/0.9.5/Wiki-Index.html | 327 - docs/0.9.5/Zones.html | 166 - docs/0.9.5/_modules/django/conf.html | 377 - .../db/models/fields/related_descriptors.html | 1309 -- .../_modules/django/db/models/manager.html | 307 - .../django/db/models/query_utils.html | 450 - .../_modules/django/utils/functional.html | 527 - docs/0.9.5/_modules/evennia.html | 543 - .../_modules/evennia/accounts/accounts.html | 1790 -- .../_modules/evennia/accounts/admin.html | 471 - .../0.9.5/_modules/evennia/accounts/bots.html | 685 - .../_modules/evennia/accounts/manager.html | 295 - .../_modules/evennia/accounts/models.html | 287 - .../_modules/evennia/commands/cmdhandler.html | 933 - .../_modules/evennia/commands/cmdparser.html | 329 - .../_modules/evennia/commands/cmdset.html | 784 - .../evennia/commands/cmdsethandler.html | 760 - .../_modules/evennia/commands/command.html | 786 - .../evennia/commands/default/account.html | 1160 -- .../evennia/commands/default/admin.html | 701 - .../commands/default/batchprocess.html | 927 - .../evennia/commands/default/building.html | 3939 ----- .../commands/default/cmdset_account.html | 183 - .../commands/default/cmdset_character.html | 198 - .../commands/default/cmdset_session.html | 124 - .../commands/default/cmdset_unloggedin.html | 133 - .../evennia/commands/default/comms.html | 1463 -- .../evennia/commands/default/general.html | 837 - .../evennia/commands/default/help.html | 606 - .../evennia/commands/default/muxcommand.html | 376 - .../evennia/commands/default/syscommands.html | 259 - .../evennia/commands/default/system.html | 1288 -- .../evennia/commands/default/tests.html | 1694 -- .../evennia/commands/default/unloggedin.html | 605 - docs/0.9.5/_modules/evennia/comms/admin.html | 229 - .../evennia/comms/channelhandler.html | 429 - docs/0.9.5/_modules/evennia/comms/comms.html | 928 - .../_modules/evennia/comms/managers.html | 521 - docs/0.9.5/_modules/evennia/comms/models.html | 847 - .../_modules/evennia/contrib/barter.html | 1003 -- .../evennia/contrib/building_menu.html | 1373 -- .../_modules/evennia/contrib/chargen.html | 300 - .../_modules/evennia/contrib/clothing.html | 850 - .../evennia/contrib/custom_gametime.html | 415 - docs/0.9.5/_modules/evennia/contrib/dice.html | 368 - .../_modules/evennia/contrib/email_login.html | 469 - .../evennia/contrib/extended_room.html | 699 - .../_modules/evennia/contrib/fieldfill.html | 822 - .../_modules/evennia/contrib/gendersub.html | 263 - .../_modules/evennia/contrib/health_bar.html | 226 - .../ingame_python/callbackhandler.html | 331 - .../contrib/ingame_python/commands.html | 689 - .../contrib/ingame_python/eventfuncs.html | 197 - .../contrib/ingame_python/scripts.html | 774 - .../evennia/contrib/ingame_python/tests.html | 649 - .../evennia/contrib/ingame_python/utils.html | 369 - docs/0.9.5/_modules/evennia/contrib/mail.html | 465 - .../_modules/evennia/contrib/multidescer.html | 376 - .../_modules/evennia/contrib/puzzles.html | 920 - .../contrib/random_string_generator.html | 460 - .../_modules/evennia/contrib/rplanguage.html | 651 - .../_modules/evennia/contrib/rpsystem.html | 1732 -- .../contrib/security/auditing/outputs.html | 166 - .../contrib/security/auditing/server.html | 355 - .../contrib/security/auditing/tests.html | 220 - .../_modules/evennia/contrib/simpledoor.html | 278 - .../_modules/evennia/contrib/slow_exit.html | 250 - .../_modules/evennia/contrib/talking_npc.html | 239 - .../_modules/evennia/contrib/tree_select.html | 683 - .../evennia/contrib/turnbattle/tb_basic.html | 886 - .../evennia/contrib/turnbattle/tb_equip.html | 1244 -- .../evennia/contrib/turnbattle/tb_items.html | 1563 -- .../evennia/contrib/turnbattle/tb_magic.html | 1485 -- .../evennia/contrib/turnbattle/tb_range.html | 1542 -- .../tutorial_examples/bodyfunctions.html | 172 - .../tutorial_examples/cmdset_red_button.html | 441 - .../contrib/tutorial_examples/red_button.html | 273 - .../tutorial_examples/red_button_scripts.html | 391 - .../contrib/tutorial_examples/tests.html | 177 - .../contrib/tutorial_world/intro_menu.html | 887 - .../evennia/contrib/tutorial_world/mob.html | 547 - .../contrib/tutorial_world/objects.html | 1291 -- .../evennia/contrib/tutorial_world/rooms.html | 1282 -- .../_modules/evennia/contrib/unixcommand.html | 401 - .../_modules/evennia/contrib/wilderness.html | 883 - docs/0.9.5/_modules/evennia/help/admin.html | 162 - docs/0.9.5/_modules/evennia/help/manager.html | 258 - docs/0.9.5/_modules/evennia/help/models.html | 385 - .../_modules/evennia/locks/lockfuncs.html | 809 - .../_modules/evennia/locks/lockhandler.html | 863 - .../0.9.5/_modules/evennia/objects/admin.html | 303 - .../_modules/evennia/objects/manager.html | 705 - .../_modules/evennia/objects/models.html | 470 - .../_modules/evennia/objects/objects.html | 2738 --- .../_modules/evennia/prototypes/menus.html | 2865 ---- .../evennia/prototypes/protfuncs.html | 452 - .../evennia/prototypes/prototypes.html | 1078 -- .../_modules/evennia/prototypes/spawner.html | 1098 -- .../0.9.5/_modules/evennia/scripts/admin.html | 197 - .../_modules/evennia/scripts/manager.html | 392 - .../_modules/evennia/scripts/models.html | 287 - .../evennia/scripts/monitorhandler.html | 311 - .../evennia/scripts/scripthandler.html | 278 - .../_modules/evennia/scripts/scripts.html | 822 - .../_modules/evennia/scripts/taskhandler.html | 694 - .../evennia/scripts/tickerhandler.html | 749 - docs/0.9.5/_modules/evennia/server/admin.html | 131 - .../_modules/evennia/server/amp_client.html | 356 - .../evennia/server/connection_wizard.html | 627 - .../_modules/evennia/server/deprecations.html | 230 - .../evennia/server/evennia_launcher.html | 2400 --- .../server/game_index_client/client.html | 285 - .../server/game_index_client/service.html | 164 - .../evennia/server/initial_setup.html | 345 - .../_modules/evennia/server/inputfuncs.html | 733 - .../_modules/evennia/server/manager.html | 159 - .../0.9.5/_modules/evennia/server/models.html | 241 - .../_modules/evennia/server/portal/amp.html | 646 - .../evennia/server/portal/amp_server.html | 587 - .../evennia/server/portal/grapevine.html | 465 - .../_modules/evennia/server/portal/irc.html | 584 - .../_modules/evennia/server/portal/mccp.html | 195 - .../_modules/evennia/server/portal/mssp.html | 240 - .../_modules/evennia/server/portal/mxp.html | 189 - .../_modules/evennia/server/portal/naws.html | 189 - .../evennia/server/portal/portal.html | 541 - .../server/portal/portalsessionhandler.html | 572 - .../_modules/evennia/server/portal/rss.html | 269 - .../_modules/evennia/server/portal/ssh.html | 632 - .../_modules/evennia/server/portal/ssl.html | 221 - .../evennia/server/portal/suppress_ga.html | 173 - .../evennia/server/portal/telnet.html | 597 - .../evennia/server/portal/telnet_oob.html | 551 - .../evennia/server/portal/telnet_ssl.html | 261 - .../_modules/evennia/server/portal/tests.html | 427 - .../_modules/evennia/server/portal/ttype.html | 291 - .../evennia/server/portal/webclient.html | 417 - .../evennia/server/portal/webclient_ajax.html | 573 - .../evennia/server/profiling/dummyrunner.html | 540 - .../profiling/dummyrunner_settings.html | 398 - .../evennia/server/profiling/memplot.html | 222 - .../server/profiling/test_queries.html | 149 - .../evennia/server/profiling/tests.html | 268 - .../evennia/server/profiling/timetrace.html | 146 - .../0.9.5/_modules/evennia/server/server.html | 848 - .../evennia/server/serversession.html | 644 - .../_modules/evennia/server/session.html | 300 - .../evennia/server/sessionhandler.html | 969 -- .../_modules/evennia/server/throttle.html | 224 - .../_modules/evennia/server/validators.html | 196 - .../_modules/evennia/server/webserver.html | 406 - .../_modules/evennia/typeclasses/admin.html | 450 - .../evennia/typeclasses/attributes.html | 1237 -- .../evennia/typeclasses/managers.html | 956 -- .../_modules/evennia/typeclasses/models.html | 1185 -- .../_modules/evennia/typeclasses/tags.html | 602 - docs/0.9.5/_modules/evennia/utils/ansi.html | 1524 -- .../evennia/utils/batchprocessors.html | 533 - .../_modules/evennia/utils/containers.html | 348 - docs/0.9.5/_modules/evennia/utils/create.html | 700 - .../_modules/evennia/utils/dbserialize.html | 866 - .../_modules/evennia/utils/eveditor.html | 1225 -- docs/0.9.5/_modules/evennia/utils/evform.html | 571 - docs/0.9.5/_modules/evennia/utils/evmenu.html | 2049 --- docs/0.9.5/_modules/evennia/utils/evmore.html | 702 - .../0.9.5/_modules/evennia/utils/evtable.html | 1860 -- .../_modules/evennia/utils/gametime.html | 379 - .../evennia/utils/idmapper/manager.html | 139 - .../evennia/utils/idmapper/models.html | 779 - .../evennia/utils/idmapper/tests.html | 184 - .../_modules/evennia/utils/inlinefuncs.html | 728 - docs/0.9.5/_modules/evennia/utils/logger.html | 649 - .../_modules/evennia/utils/optionclasses.html | 435 - .../_modules/evennia/utils/optionhandler.html | 286 - .../_modules/evennia/utils/picklefield.html | 408 - docs/0.9.5/_modules/evennia/utils/search.html | 470 - .../evennia/utils/test_resources.html | 281 - .../_modules/evennia/utils/text2html.html | 474 - docs/0.9.5/_modules/evennia/utils/utils.html | 2241 --- .../evennia/utils/validatorfuncs.html | 342 - .../_modules/evennia/web/utils/backends.html | 149 - .../evennia/web/utils/general_context.html | 204 - .../evennia/web/utils/middleware.html | 178 - .../_modules/evennia/web/utils/tests.html | 177 - .../_modules/evennia/web/webclient/views.html | 136 - .../_modules/evennia/web/website/forms.html | 281 - .../web/website/templatetags/addclass.html | 123 - .../_modules/evennia/web/website/tests.html | 396 - .../_modules/evennia/web/website/views.html | 1241 -- docs/0.9.5/_modules/functools.html | 953 -- docs/0.9.5/_modules/index.html | 290 - ...oice-operated-elevator-using-events.md.txt | 436 - docs/0.9.5/_sources/API-refactoring.md.txt | 46 - docs/0.9.5/_sources/Accounts.md.txt | 108 - .../_sources/Add-a-simple-new-web-page.md.txt | 100 - .../Add-a-wiki-on-your-website.md.txt | 232 - .../_sources/Adding-Command-Tutorial.md.txt | 171 - .../Adding-Object-Typeclass-Tutorial.md.txt | 109 - .../0.9.5/_sources/Administrative-Docs.md.txt | 76 - docs/0.9.5/_sources/Apache-Config.md.txt | 171 - .../_sources/Arxcode-installing-help.md.txt | 272 - docs/0.9.5/_sources/Async-Process.md.txt | 233 - docs/0.9.5/_sources/Attributes.md.txt | 393 - docs/0.9.5/_sources/Banning.md.txt | 148 - .../_sources/Batch-Code-Processor.md.txt | 229 - .../_sources/Batch-Command-Processor.md.txt | 182 - docs/0.9.5/_sources/Batch-Processors.md.txt | 82 - .../0.9.5/_sources/Bootstrap-&-Evennia.md.txt | 100 - .../Bootstrap-Components-and-Utilities.md.txt | 82 - docs/0.9.5/_sources/Builder-Docs.md.txt | 43 - .../_sources/Building-Permissions.md.txt | 72 - .../0.9.5/_sources/Building-Quickstart.md.txt | 274 - .../_sources/Building-a-mech-tutorial.md.txt | 236 - docs/0.9.5/_sources/Building-menus.md.txt | 1233 -- .../_sources/Choosing-An-SQL-Server.md.txt | 249 - .../0.9.5/_sources/Client-Support-Grid.md.txt | 84 - docs/0.9.5/_sources/Coding-FAQ.md.txt | 389 - .../0.9.5/_sources/Coding-Introduction.md.txt | 99 - docs/0.9.5/_sources/Coding-Utils.md.txt | 297 - docs/0.9.5/_sources/Command-Cooldown.md.txt | 98 - docs/0.9.5/_sources/Command-Duration.md.txt | 403 - docs/0.9.5/_sources/Command-Prompt.md.txt | 129 - docs/0.9.5/_sources/Command-Sets.md.txt | 376 - docs/0.9.5/_sources/Command-System.md.txt | 9 - docs/0.9.5/_sources/Commands.md.txt | 663 - docs/0.9.5/_sources/Communications.md.txt | 113 - docs/0.9.5/_sources/Connection-Screen.md.txt | 36 - .../_sources/Continuous-Integration.md.txt | 222 - docs/0.9.5/_sources/Contributing-Docs.md.txt | 681 - docs/0.9.5/_sources/Contributing.md.txt | 118 - docs/0.9.5/_sources/Coordinates.md.txt | 348 - docs/0.9.5/_sources/Custom-Protocols.md.txt | 239 - docs/0.9.5/_sources/Customize-channels.md.txt | 483 - docs/0.9.5/_sources/Debugging.md.txt | 296 - docs/0.9.5/_sources/Default-Commands.md.txt | 107 - .../0.9.5/_sources/Default-Exit-Errors.md.txt | 122 - docs/0.9.5/_sources/Developer-Central.md.txt | 170 - .../0.9.5/_sources/Dialogues-in-events.md.txt | 247 - docs/0.9.5/_sources/Directory-Overview.md.txt | 68 - docs/0.9.5/_sources/Docs-refactoring.md.txt | 117 - .../0.9.5/_sources/Dynamic-In-Game-Map.md.txt | 495 - docs/0.9.5/_sources/EvEditor.md.txt | 181 - docs/0.9.5/_sources/EvMenu.md.txt | 1312 -- docs/0.9.5/_sources/EvMore.md.txt | 38 - docs/0.9.5/_sources/Evennia-API.md.txt | 97 - docs/0.9.5/_sources/Evennia-Game-Index.md.txt | 71 - .../_sources/Evennia-Introduction.md.txt | 178 - .../_sources/Evennia-for-Diku-Users.md.txt | 200 - .../_sources/Evennia-for-MUSH-Users.md.txt | 218 - .../Evennia-for-roleplaying-sessions.md.txt | 732 - .../0.9.5/_sources/Execute-Python-Code.md.txt | 120 - docs/0.9.5/_sources/First-Steps-Coding.md.txt | 292 - docs/0.9.5/_sources/Game-Planning.md.txt | 214 - docs/0.9.5/_sources/Gametime-Tutorial.md.txt | 302 - docs/0.9.5/_sources/Getting-Started.md.txt | 542 - docs/0.9.5/_sources/Glossary.md.txt | 381 - docs/0.9.5/_sources/Grapevine.md.txt | 71 - docs/0.9.5/_sources/Guest-Logins.md.txt | 29 - docs/0.9.5/_sources/HAProxy-Config.md.txt | 176 - .../_sources/Help-System-Tutorial.md.txt | 465 - docs/0.9.5/_sources/Help-System.md.txt | 122 - .../_sources/How-To-Get-And-Give-Help.md.txt | 71 - .../How-to-connect-Evennia-to-Twitter.md.txt | 110 - docs/0.9.5/_sources/IRC.md.txt | 90 - .../Implementing-a-game-rule-system.md.txt | 302 - docs/0.9.5/_sources/Inputfuncs.md.txt | 174 - .../_sources/Installing-on-Android.md.txt | 143 - .../_sources/Internationalization.md.txt | 88 - ...arn-Python-for-Evennia-The-Hard-Way.md.txt | 70 - docs/0.9.5/_sources/Licensing.md.txt | 32 - docs/0.9.5/_sources/Links.md.txt | 176 - docs/0.9.5/_sources/Locks.md.txt | 495 - .../Manually-Configuring-Color.md.txt | 169 - .../Mass-and-weight-for-objects.md.txt | 95 - docs/0.9.5/_sources/Messagepath.md.txt | 292 - docs/0.9.5/_sources/MonitorHandler.md.txt | 79 - docs/0.9.5/_sources/NPC-shop-Tutorial.md.txt | 334 - docs/0.9.5/_sources/New-Models.md.txt | 264 - docs/0.9.5/_sources/Nicks.md.txt | 124 - docs/0.9.5/_sources/OOB.md.txt | 170 - docs/0.9.5/_sources/Objects.md.txt | 185 - docs/0.9.5/_sources/Online-Setup.md.txt | 426 - ...rguments,-theory-and-best-practices.md.txt | 748 - docs/0.9.5/_sources/Portal-And-Server.md.txt | 14 - docs/0.9.5/_sources/Profiling.md.txt | 125 - docs/0.9.5/_sources/Python-3.md.txt | 4 - .../_sources/Python-basic-introduction.md.txt | 266 - .../Python-basic-tutorial-part-two.md.txt | 492 - docs/0.9.5/_sources/Quirks.md.txt | 120 - docs/0.9.5/_sources/RSS.md.txt | 47 - docs/0.9.5/_sources/Roadmap.md.txt | 5 - .../_sources/Running-Evennia-in-Docker.md.txt | 299 - docs/0.9.5/_sources/Screenshot.md.txt | 19 - docs/0.9.5/_sources/Scripts.md.txt | 384 - docs/0.9.5/_sources/Security.md.txt | 152 - docs/0.9.5/_sources/Server-Conf.md.txt | 104 - docs/0.9.5/_sources/Sessions.md.txt | 188 - docs/0.9.5/_sources/Setting-up-PyCharm.md.txt | 116 - docs/0.9.5/_sources/Signals.md.txt | 122 - docs/0.9.5/_sources/Soft-Code.md.txt | 94 - .../_sources/Spawner-and-Prototypes.md.txt | 332 - docs/0.9.5/_sources/Start-Stop-Reload.md.txt | 196 - docs/0.9.5/_sources/Static-In-Game-Map.md.txt | 412 - docs/0.9.5/_sources/Tags.md.txt | 170 - docs/0.9.5/_sources/Text-Encodings.md.txt | 66 - docs/0.9.5/_sources/TextTags.md.txt | 345 - docs/0.9.5/_sources/TickerHandler.md.txt | 136 - .../_sources/Turn-based-Combat-System.md.txt | 516 - .../_sources/Tutorial-Aggressive-NPCs.md.txt | 127 - .../_sources/Tutorial-NPCs-listening.md.txt | 112 - .../Tutorial-Searching-For-Objects.md.txt | 430 - .../Tutorial-Tweeting-Game-Stats.md.txt | 96 - docs/0.9.5/_sources/Tutorial-Vehicles.md.txt | 422 - .../Tutorial-World-Introduction.md.txt | 109 - .../Tutorial-for-basic-MUSH-like-game.md.txt | 651 - docs/0.9.5/_sources/Tutorials.md.txt | 207 - docs/0.9.5/_sources/Typeclasses.md.txt | 354 - .../_sources/Understanding-Color-Tags.md.txt | 198 - docs/0.9.5/_sources/Unit-Testing.md.txt | 435 - docs/0.9.5/_sources/Updating-Your-Game.md.txt | 132 - .../_sources/Using-MUX-as-a-Standard.md.txt | 85 - docs/0.9.5/_sources/Using-Travis.md.txt | 37 - docs/0.9.5/_sources/Version-Control.md.txt | 475 - docs/0.9.5/_sources/Weather-Tutorial.md.txt | 54 - .../_sources/Web-Character-Generation.md.txt | 637 - .../Web-Character-View-Tutorial.md.txt | 228 - docs/0.9.5/_sources/Web-Features.md.txt | 131 - docs/0.9.5/_sources/Web-Tutorial.md.txt | 127 - .../_sources/Webclient-brainstorm.md.txt | 354 - docs/0.9.5/_sources/Webclient.md.txt | 265 - docs/0.9.5/_sources/Wiki-Index.md.txt | 208 - docs/0.9.5/_sources/Zones.md.txt | 55 - docs/0.9.5/_sources/api/evennia-api.md.txt | 10 - .../api/evennia.accounts.accounts.md.txt | 10 - .../api/evennia.accounts.admin.md.txt | 10 - .../_sources/api/evennia.accounts.bots.md.txt | 10 - .../api/evennia.accounts.manager.md.txt | 10 - .../_sources/api/evennia.accounts.md.txt | 21 - .../api/evennia.accounts.models.md.txt | 10 - .../api/evennia.commands.cmdhandler.md.txt | 10 - .../api/evennia.commands.cmdparser.md.txt | 10 - .../api/evennia.commands.cmdset.md.txt | 10 - .../api/evennia.commands.cmdsethandler.md.txt | 10 - .../api/evennia.commands.command.md.txt | 10 - .../evennia.commands.default.account.md.txt | 10 - .../api/evennia.commands.default.admin.md.txt | 10 - ...ennia.commands.default.batchprocess.md.txt | 10 - .../evennia.commands.default.building.md.txt | 10 - ...nia.commands.default.cmdset_account.md.txt | 10 - ...a.commands.default.cmdset_character.md.txt | 10 - ...nia.commands.default.cmdset_session.md.txt | 10 - ....commands.default.cmdset_unloggedin.md.txt | 10 - .../api/evennia.commands.default.comms.md.txt | 10 - .../evennia.commands.default.general.md.txt | 10 - .../api/evennia.commands.default.help.md.txt | 10 - .../api/evennia.commands.default.md.txt | 32 - ...evennia.commands.default.muxcommand.md.txt | 10 - ...vennia.commands.default.syscommands.md.txt | 10 - .../evennia.commands.default.system.md.txt | 10 - .../api/evennia.commands.default.tests.md.txt | 10 - ...evennia.commands.default.unloggedin.md.txt | 10 - .../_sources/api/evennia.commands.md.txt | 27 - .../_sources/api/evennia.comms.admin.md.txt | 10 - .../api/evennia.comms.channelhandler.md.txt | 10 - .../_sources/api/evennia.comms.comms.md.txt | 10 - .../api/evennia.comms.managers.md.txt | 10 - docs/0.9.5/_sources/api/evennia.comms.md.txt | 21 - .../_sources/api/evennia.comms.models.md.txt | 10 - .../api/evennia.contrib.barter.md.txt | 10 - .../api/evennia.contrib.building_menu.md.txt | 10 - .../api/evennia.contrib.chargen.md.txt | 10 - .../api/evennia.contrib.clothing.md.txt | 10 - .../api/evennia.contrib.color_markups.md.txt | 10 - .../evennia.contrib.custom_gametime.md.txt | 10 - .../_sources/api/evennia.contrib.dice.md.txt | 10 - .../api/evennia.contrib.email_login.md.txt | 10 - .../api/evennia.contrib.extended_room.md.txt | 10 - .../api/evennia.contrib.fieldfill.md.txt | 10 - .../api/evennia.contrib.gendersub.md.txt | 10 - .../api/evennia.contrib.health_bar.md.txt | 10 - ...ntrib.ingame_python.callbackhandler.md.txt | 10 - ...nnia.contrib.ingame_python.commands.md.txt | 10 - ...ia.contrib.ingame_python.eventfuncs.md.txt | 10 - .../api/evennia.contrib.ingame_python.md.txt | 23 - ...ennia.contrib.ingame_python.scripts.md.txt | 10 - ...evennia.contrib.ingame_python.tests.md.txt | 10 - ...a.contrib.ingame_python.typeclasses.md.txt | 10 - ...evennia.contrib.ingame_python.utils.md.txt | 10 - .../_sources/api/evennia.contrib.mail.md.txt | 10 - .../api/evennia.contrib.mapbuilder.md.txt | 10 - .../0.9.5/_sources/api/evennia.contrib.md.txt | 52 - .../api/evennia.contrib.menu_login.md.txt | 10 - .../api/evennia.contrib.multidescer.md.txt | 10 - .../api/evennia.contrib.puzzles.md.txt | 10 - ...nia.contrib.random_string_generator.md.txt | 10 - .../api/evennia.contrib.rplanguage.md.txt | 10 - .../api/evennia.contrib.rpsystem.md.txt | 10 - .../evennia.contrib.security.auditing.md.txt | 19 - ...a.contrib.security.auditing.outputs.md.txt | 10 - ...ia.contrib.security.auditing.server.md.txt | 10 - ...nia.contrib.security.auditing.tests.md.txt | 10 - .../api/evennia.contrib.security.md.txt | 16 - .../api/evennia.contrib.simpledoor.md.txt | 10 - .../api/evennia.contrib.slow_exit.md.txt | 10 - .../api/evennia.contrib.talking_npc.md.txt | 10 - .../api/evennia.contrib.tree_select.md.txt | 10 - .../api/evennia.contrib.turnbattle.md.txt | 21 - ...evennia.contrib.turnbattle.tb_basic.md.txt | 10 - ...evennia.contrib.turnbattle.tb_equip.md.txt | 10 - ...evennia.contrib.turnbattle.tb_items.md.txt | 10 - ...evennia.contrib.turnbattle.tb_magic.md.txt | 10 - ...evennia.contrib.turnbattle.tb_range.md.txt | 10 - ...rib.tutorial_examples.bodyfunctions.md.txt | 10 - ...tutorial_examples.cmdset_red_button.md.txt | 10 - ...utorial_examples.example_batch_code.md.txt | 10 - .../evennia.contrib.tutorial_examples.md.txt | 22 - ...ontrib.tutorial_examples.red_button.md.txt | 10 - ...utorial_examples.red_button_scripts.md.txt | 10 - ...nia.contrib.tutorial_examples.tests.md.txt | 10 - ...a.contrib.tutorial_world.intro_menu.md.txt | 10 - .../api/evennia.contrib.tutorial_world.md.txt | 20 - .../evennia.contrib.tutorial_world.mob.md.txt | 10 - ...nnia.contrib.tutorial_world.objects.md.txt | 10 - ...vennia.contrib.tutorial_world.rooms.md.txt | 10 - .../api/evennia.contrib.unixcommand.md.txt | 10 - .../api/evennia.contrib.wilderness.md.txt | 10 - .../_sources/api/evennia.help.admin.md.txt | 10 - .../_sources/api/evennia.help.manager.md.txt | 10 - docs/0.9.5/_sources/api/evennia.help.md.txt | 19 - .../_sources/api/evennia.help.models.md.txt | 10 - .../api/evennia.locks.lockfuncs.md.txt | 10 - .../api/evennia.locks.lockhandler.md.txt | 10 - docs/0.9.5/_sources/api/evennia.locks.md.txt | 18 - docs/0.9.5/_sources/api/evennia.md.txt | 35 - .../_sources/api/evennia.objects.admin.md.txt | 10 - .../api/evennia.objects.manager.md.txt | 10 - .../0.9.5/_sources/api/evennia.objects.md.txt | 20 - .../api/evennia.objects.models.md.txt | 10 - .../api/evennia.objects.objects.md.txt | 10 - .../_sources/api/evennia.prototypes.md.txt | 20 - .../api/evennia.prototypes.menus.md.txt | 10 - .../api/evennia.prototypes.protfuncs.md.txt | 10 - .../api/evennia.prototypes.prototypes.md.txt | 10 - .../api/evennia.prototypes.spawner.md.txt | 10 - .../_sources/api/evennia.scripts.admin.md.txt | 10 - .../api/evennia.scripts.manager.md.txt | 10 - .../0.9.5/_sources/api/evennia.scripts.md.txt | 24 - .../api/evennia.scripts.models.md.txt | 10 - .../api/evennia.scripts.monitorhandler.md.txt | 10 - .../api/evennia.scripts.scripthandler.md.txt | 10 - .../api/evennia.scripts.scripts.md.txt | 10 - .../api/evennia.scripts.taskhandler.md.txt | 10 - .../api/evennia.scripts.tickerhandler.md.txt | 10 - .../_sources/api/evennia.server.admin.md.txt | 10 - .../api/evennia.server.amp_client.md.txt | 10 - .../evennia.server.connection_wizard.md.txt | 10 - .../api/evennia.server.deprecations.md.txt | 10 - .../evennia.server.evennia_launcher.md.txt | 10 - ...nia.server.game_index_client.client.md.txt | 10 - .../evennia.server.game_index_client.md.txt | 18 - ...ia.server.game_index_client.service.md.txt | 10 - .../api/evennia.server.initial_setup.md.txt | 10 - .../api/evennia.server.inputfuncs.md.txt | 10 - .../api/evennia.server.manager.md.txt | 10 - docs/0.9.5/_sources/api/evennia.server.md.txt | 41 - .../_sources/api/evennia.server.models.md.txt | 10 - .../api/evennia.server.portal.amp.md.txt | 10 - .../evennia.server.portal.amp_server.md.txt | 10 - .../evennia.server.portal.grapevine.md.txt | 10 - .../api/evennia.server.portal.irc.md.txt | 10 - .../api/evennia.server.portal.mccp.md.txt | 10 - .../_sources/api/evennia.server.portal.md.txt | 37 - .../api/evennia.server.portal.mssp.md.txt | 10 - .../api/evennia.server.portal.mxp.md.txt | 10 - .../api/evennia.server.portal.naws.md.txt | 10 - .../api/evennia.server.portal.portal.md.txt | 10 - ....server.portal.portalsessionhandler.md.txt | 10 - .../api/evennia.server.portal.rss.md.txt | 10 - .../api/evennia.server.portal.ssh.md.txt | 10 - .../api/evennia.server.portal.ssl.md.txt | 10 - .../evennia.server.portal.suppress_ga.md.txt | 10 - .../api/evennia.server.portal.telnet.md.txt | 10 - .../evennia.server.portal.telnet_oob.md.txt | 10 - .../evennia.server.portal.telnet_ssl.md.txt | 10 - .../api/evennia.server.portal.tests.md.txt | 10 - .../api/evennia.server.portal.ttype.md.txt | 10 - .../evennia.server.portal.webclient.md.txt | 10 - ...vennia.server.portal.webclient_ajax.md.txt | 10 - ...vennia.server.profiling.dummyrunner.md.txt | 10 - ...rver.profiling.dummyrunner_settings.md.txt | 10 - .../api/evennia.server.profiling.md.txt | 23 - .../evennia.server.profiling.memplot.md.txt | 10 - ...nia.server.profiling.settings_mixin.md.txt | 10 - ...ennia.server.profiling.test_queries.md.txt | 10 - .../api/evennia.server.profiling.tests.md.txt | 10 - .../evennia.server.profiling.timetrace.md.txt | 10 - .../_sources/api/evennia.server.server.md.txt | 10 - .../api/evennia.server.serversession.md.txt | 10 - .../api/evennia.server.session.md.txt | 10 - .../api/evennia.server.sessionhandler.md.txt | 10 - .../api/evennia.server.signals.md.txt | 10 - .../api/evennia.server.throttle.md.txt | 10 - .../api/evennia.server.validators.md.txt | 10 - .../api/evennia.server.webserver.md.txt | 10 - .../api/evennia.settings_default.md.txt | 10 - .../api/evennia.typeclasses.admin.md.txt | 10 - .../api/evennia.typeclasses.attributes.md.txt | 10 - .../api/evennia.typeclasses.managers.md.txt | 10 - .../_sources/api/evennia.typeclasses.md.txt | 21 - .../api/evennia.typeclasses.models.md.txt | 10 - .../api/evennia.typeclasses.tags.md.txt | 10 - .../_sources/api/evennia.utils.ansi.md.txt | 10 - .../api/evennia.utils.batchprocessors.md.txt | 10 - .../api/evennia.utils.containers.md.txt | 10 - .../_sources/api/evennia.utils.create.md.txt | 10 - .../api/evennia.utils.dbserialize.md.txt | 10 - .../api/evennia.utils.eveditor.md.txt | 10 - .../_sources/api/evennia.utils.evform.md.txt | 10 - .../_sources/api/evennia.utils.evmenu.md.txt | 10 - .../_sources/api/evennia.utils.evmore.md.txt | 10 - .../_sources/api/evennia.utils.evtable.md.txt | 10 - .../api/evennia.utils.gametime.md.txt | 10 - .../api/evennia.utils.idmapper.manager.md.txt | 10 - .../api/evennia.utils.idmapper.md.txt | 19 - .../api/evennia.utils.idmapper.models.md.txt | 10 - .../api/evennia.utils.idmapper.tests.md.txt | 10 - .../api/evennia.utils.inlinefuncs.md.txt | 10 - .../_sources/api/evennia.utils.logger.md.txt | 10 - docs/0.9.5/_sources/api/evennia.utils.md.txt | 43 - .../api/evennia.utils.optionclasses.md.txt | 10 - .../api/evennia.utils.optionhandler.md.txt | 10 - .../api/evennia.utils.picklefield.md.txt | 10 - .../_sources/api/evennia.utils.search.md.txt | 10 - .../api/evennia.utils.test_resources.md.txt | 10 - .../api/evennia.utils.text2html.md.txt | 10 - .../_sources/api/evennia.utils.utils.md.txt | 10 - .../api/evennia.utils.validatorfuncs.md.txt | 10 - docs/0.9.5/_sources/api/evennia.web.md.txt | 25 - .../_sources/api/evennia.web.urls.md.txt | 10 - .../api/evennia.web.utils.backends.md.txt | 10 - .../evennia.web.utils.general_context.md.txt | 10 - .../_sources/api/evennia.web.utils.md.txt | 20 - .../api/evennia.web.utils.middleware.md.txt | 10 - .../api/evennia.web.utils.tests.md.txt | 10 - .../_sources/api/evennia.web.webclient.md.txt | 18 - .../api/evennia.web.webclient.urls.md.txt | 10 - .../api/evennia.web.webclient.views.md.txt | 10 - .../api/evennia.web.website.forms.md.txt | 10 - .../_sources/api/evennia.web.website.md.txt | 26 - ...a.web.website.templatetags.addclass.md.txt | 10 - .../evennia.web.website.templatetags.md.txt | 17 - .../api/evennia.web.website.tests.md.txt | 10 - .../api/evennia.web.website.urls.md.txt | 10 - .../api/evennia.web.website.views.md.txt | 10 - docs/0.9.5/_sources/index.md.txt | 47 - docs/0.9.5/_sources/toc.md.txt | 373 - docs/0.9.5/_static/basic.css | 792 - docs/0.9.5/_static/doctools.js | 315 - docs/0.9.5/_static/documentation_options.js | 12 - docs/0.9.5/_static/evennia_logo.png | Bin 17938 -> 0 bytes docs/0.9.5/_static/favicon.ico | Bin 1406 -> 0 bytes docs/0.9.5/_static/file.png | Bin 286 -> 0 bytes docs/0.9.5/_static/images/evennia_logo.png | Bin 17938 -> 0 bytes docs/0.9.5/_static/images/favicon.ico | Bin 1406 -> 0 bytes docs/0.9.5/_static/jquery-3.5.1.js | 10872 ------------ docs/0.9.5/_static/jquery.js | 2 - docs/0.9.5/_static/language_data.js | 297 - docs/0.9.5/_static/minus.png | Bin 90 -> 0 bytes docs/0.9.5/_static/nature.css | 460 - docs/0.9.5/_static/plus.png | Bin 90 -> 0 bytes docs/0.9.5/_static/pygments.css | 74 - docs/0.9.5/_static/searchtools.js | 514 - docs/0.9.5/_static/underscore-1.3.1.js | 999 -- docs/0.9.5/_static/underscore.js | 31 - docs/0.9.5/api/evennia-api.html | 405 - docs/0.9.5/api/evennia.accounts.accounts.html | 1101 -- docs/0.9.5/api/evennia.accounts.admin.html | 412 - docs/0.9.5/api/evennia.accounts.bots.html | 522 - docs/0.9.5/api/evennia.accounts.html | 124 - docs/0.9.5/api/evennia.accounts.manager.html | 286 - docs/0.9.5/api/evennia.accounts.models.html | 440 - .../api/evennia.commands.cmdhandler.html | 211 - .../0.9.5/api/evennia.commands.cmdparser.html | 234 - docs/0.9.5/api/evennia.commands.cmdset.html | 451 - .../api/evennia.commands.cmdsethandler.html | 419 - docs/0.9.5/api/evennia.commands.command.html | 520 - .../api/evennia.commands.default.account.html | 822 - .../api/evennia.commands.default.admin.html | 532 - ...evennia.commands.default.batchprocess.html | 237 - .../evennia.commands.default.building.html | 1885 -- ...ennia.commands.default.cmdset_account.html | 146 - ...nia.commands.default.cmdset_character.html | 144 - ...ennia.commands.default.cmdset_session.html | 141 - ...ia.commands.default.cmdset_unloggedin.html | 143 - .../api/evennia.commands.default.comms.html | 1010 -- .../api/evennia.commands.default.general.html | 749 - .../api/evennia.commands.default.help.html | 337 - docs/0.9.5/api/evennia.commands.default.html | 133 - .../evennia.commands.default.muxcommand.html | 299 - .../evennia.commands.default.syscommands.html | 364 - .../api/evennia.commands.default.system.html | 760 - .../api/evennia.commands.default.tests.html | 718 - .../evennia.commands.default.unloggedin.html | 486 - docs/0.9.5/api/evennia.commands.html | 149 - docs/0.9.5/api/evennia.comms.admin.html | 301 - .../api/evennia.comms.channelhandler.html | 332 - docs/0.9.5/api/evennia.comms.comms.html | 805 - docs/0.9.5/api/evennia.comms.html | 124 - docs/0.9.5/api/evennia.comms.managers.html | 402 - docs/0.9.5/api/evennia.comms.models.html | 754 - docs/0.9.5/api/evennia.contrib.barter.html | 868 - .../api/evennia.contrib.building_menu.html | 936 - docs/0.9.5/api/evennia.contrib.chargen.html | 245 - docs/0.9.5/api/evennia.contrib.clothing.html | 728 - .../api/evennia.contrib.color_markups.html | 154 - .../api/evennia.contrib.custom_gametime.html | 341 - docs/0.9.5/api/evennia.contrib.dice.html | 267 - .../api/evennia.contrib.email_login.html | 349 - .../api/evennia.contrib.extended_room.html | 553 - docs/0.9.5/api/evennia.contrib.fieldfill.html | 467 - docs/0.9.5/api/evennia.contrib.gendersub.html | 246 - .../0.9.5/api/evennia.contrib.health_bar.html | 169 - docs/0.9.5/api/evennia.contrib.html | 192 - ...contrib.ingame_python.callbackhandler.html | 347 - ...vennia.contrib.ingame_python.commands.html | 206 - ...nnia.contrib.ingame_python.eventfuncs.html | 179 - .../api/evennia.contrib.ingame_python.html | 123 - ...evennia.contrib.ingame_python.scripts.html | 468 - .../evennia.contrib.ingame_python.tests.html | 254 - ...nia.contrib.ingame_python.typeclasses.html | 112 - .../evennia.contrib.ingame_python.utils.html | 232 - docs/0.9.5/api/evennia.contrib.mail.html | 361 - .../0.9.5/api/evennia.contrib.mapbuilder.html | 112 - .../0.9.5/api/evennia.contrib.menu_login.html | 112 - .../api/evennia.contrib.multidescer.html | 191 - docs/0.9.5/api/evennia.contrib.puzzles.html | 547 - ...ennia.contrib.random_string_generator.html | 327 - .../0.9.5/api/evennia.contrib.rplanguage.html | 407 - docs/0.9.5/api/evennia.contrib.rpsystem.html | 1278 -- .../evennia.contrib.security.auditing.html | 119 - ...nia.contrib.security.auditing.outputs.html | 154 - ...nnia.contrib.security.auditing.server.html | 193 - ...ennia.contrib.security.auditing.tests.html | 133 - docs/0.9.5/api/evennia.contrib.security.html | 122 - .../0.9.5/api/evennia.contrib.simpledoor.html | 298 - docs/0.9.5/api/evennia.contrib.slow_exit.html | 248 - .../api/evennia.contrib.talking_npc.html | 255 - .../api/evennia.contrib.tree_select.html | 462 - .../0.9.5/api/evennia.contrib.turnbattle.html | 121 - .../evennia.contrib.turnbattle.tb_basic.html | 797 - .../evennia.contrib.turnbattle.tb_equip.html | 1076 -- .../evennia.contrib.turnbattle.tb_items.html | 1071 -- .../evennia.contrib.turnbattle.tb_magic.html | 1010 -- .../evennia.contrib.turnbattle.tb_range.html | 1271 -- ...ntrib.tutorial_examples.bodyfunctions.html | 167 - ...b.tutorial_examples.cmdset_red_button.html | 555 - ....tutorial_examples.example_batch_code.html | 112 - .../evennia.contrib.tutorial_examples.html | 122 - ....contrib.tutorial_examples.red_button.html | 211 - ....tutorial_examples.red_button_scripts.html | 456 - ...ennia.contrib.tutorial_examples.tests.html | 148 - .../api/evennia.contrib.tutorial_world.html | 121 - ...nia.contrib.tutorial_world.intro_menu.html | 303 - .../evennia.contrib.tutorial_world.mob.html | 347 - ...vennia.contrib.tutorial_world.objects.html | 983 -- .../evennia.contrib.tutorial_world.rooms.html | 1225 -- .../api/evennia.contrib.unixcommand.html | 393 - .../0.9.5/api/evennia.contrib.wilderness.html | 739 - docs/0.9.5/api/evennia.help.admin.html | 238 - docs/0.9.5/api/evennia.help.html | 123 - docs/0.9.5/api/evennia.help.manager.html | 250 - docs/0.9.5/api/evennia.help.models.html | 412 - docs/0.9.5/api/evennia.html | 500 - docs/0.9.5/api/evennia.locks.html | 124 - docs/0.9.5/api/evennia.locks.lockfuncs.html | 576 - docs/0.9.5/api/evennia.locks.lockhandler.html | 484 - docs/0.9.5/api/evennia.objects.admin.html | 371 - docs/0.9.5/api/evennia.objects.html | 122 - docs/0.9.5/api/evennia.objects.manager.html | 474 - docs/0.9.5/api/evennia.objects.models.html | 578 - docs/0.9.5/api/evennia.objects.objects.html | 1959 --- docs/0.9.5/api/evennia.prototypes.html | 120 - docs/0.9.5/api/evennia.prototypes.menus.html | 225 - .../api/evennia.prototypes.protfuncs.html | 294 - .../api/evennia.prototypes.prototypes.html | 528 - .../0.9.5/api/evennia.prototypes.spawner.html | 546 - docs/0.9.5/api/evennia.scripts.admin.html | 236 - docs/0.9.5/api/evennia.scripts.html | 128 - docs/0.9.5/api/evennia.scripts.manager.html | 306 - docs/0.9.5/api/evennia.scripts.models.html | 406 - .../api/evennia.scripts.monitorhandler.html | 228 - .../api/evennia.scripts.scripthandler.html | 232 - docs/0.9.5/api/evennia.scripts.scripts.html | 463 - .../api/evennia.scripts.taskhandler.html | 592 - .../api/evennia.scripts.tickerhandler.html | 456 - docs/0.9.5/api/evennia.server.admin.html | 159 - docs/0.9.5/api/evennia.server.amp_client.html | 294 - .../api/evennia.server.connection_wizard.html | 233 - .../api/evennia.server.deprecations.html | 137 - .../api/evennia.server.evennia_launcher.html | 611 - ...ennia.server.game_index_client.client.html | 223 - .../api/evennia.server.game_index_client.html | 118 - ...nnia.server.game_index_client.service.html | 142 - docs/0.9.5/api/evennia.server.html | 180 - .../api/evennia.server.initial_setup.html | 172 - docs/0.9.5/api/evennia.server.inputfuncs.html | 418 - docs/0.9.5/api/evennia.server.manager.html | 150 - docs/0.9.5/api/evennia.server.models.html | 204 - docs/0.9.5/api/evennia.server.portal.amp.html | 587 - .../api/evennia.server.portal.amp_server.html | 335 - .../api/evennia.server.portal.grapevine.html | 332 - docs/0.9.5/api/evennia.server.portal.html | 137 - docs/0.9.5/api/evennia.server.portal.irc.html | 448 - .../0.9.5/api/evennia.server.portal.mccp.html | 182 - .../0.9.5/api/evennia.server.portal.mssp.html | 183 - docs/0.9.5/api/evennia.server.portal.mxp.html | 175 - .../0.9.5/api/evennia.server.portal.naws.html | 171 - .../api/evennia.server.portal.portal.html | 170 - ...ia.server.portal.portalsessionhandler.html | 360 - docs/0.9.5/api/evennia.server.portal.rss.html | 212 - docs/0.9.5/api/evennia.server.portal.ssh.html | 433 - docs/0.9.5/api/evennia.server.portal.ssl.html | 153 - .../evennia.server.portal.suppress_ga.html | 161 - .../api/evennia.server.portal.telnet.html | 348 - .../api/evennia.server.portal.telnet_oob.html | 329 - .../api/evennia.server.portal.telnet_ssl.html | 164 - .../api/evennia.server.portal.tests.html | 220 - .../api/evennia.server.portal.ttype.html | 170 - .../api/evennia.server.portal.webclient.html | 287 - .../evennia.server.portal.webclient_ajax.html | 412 - .../evennia.server.profiling.dummyrunner.html | 279 - ...server.profiling.dummyrunner_settings.html | 245 - docs/0.9.5/api/evennia.server.profiling.html | 123 - .../api/evennia.server.profiling.memplot.html | 157 - ...ennia.server.profiling.settings_mixin.html | 119 - ...evennia.server.profiling.test_queries.html | 121 - .../api/evennia.server.profiling.tests.html | 215 - .../evennia.server.profiling.timetrace.html | 130 - docs/0.9.5/api/evennia.server.server.html | 249 - .../api/evennia.server.serversession.html | 535 - docs/0.9.5/api/evennia.server.session.html | 225 - .../api/evennia.server.sessionhandler.html | 645 - docs/0.9.5/api/evennia.server.signals.html | 128 - docs/0.9.5/api/evennia.server.throttle.html | 209 - docs/0.9.5/api/evennia.server.validators.html | 174 - docs/0.9.5/api/evennia.server.webserver.html | 332 - docs/0.9.5/api/evennia.settings_default.html | 120 - docs/0.9.5/api/evennia.typeclasses.admin.html | 403 - .../api/evennia.typeclasses.attributes.html | 896 - docs/0.9.5/api/evennia.typeclasses.html | 127 - .../api/evennia.typeclasses.managers.html | 521 - .../0.9.5/api/evennia.typeclasses.models.html | 795 - docs/0.9.5/api/evennia.typeclasses.tags.html | 464 - docs/0.9.5/api/evennia.utils.ansi.html | 915 - .../api/evennia.utils.batchprocessors.html | 403 - docs/0.9.5/api/evennia.utils.containers.html | 259 - docs/0.9.5/api/evennia.utils.create.html | 343 - docs/0.9.5/api/evennia.utils.dbserialize.html | 194 - docs/0.9.5/api/evennia.utils.eveditor.html | 547 - docs/0.9.5/api/evennia.utils.evform.html | 289 - docs/0.9.5/api/evennia.utils.evmenu.html | 1060 -- docs/0.9.5/api/evennia.utils.evmore.html | 554 - docs/0.9.5/api/evennia.utils.evtable.html | 677 - docs/0.9.5/api/evennia.utils.gametime.html | 301 - docs/0.9.5/api/evennia.utils.html | 157 - docs/0.9.5/api/evennia.utils.idmapper.html | 120 - .../api/evennia.utils.idmapper.manager.html | 125 - .../api/evennia.utils.idmapper.models.html | 337 - .../api/evennia.utils.idmapper.tests.html | 434 - docs/0.9.5/api/evennia.utils.inlinefuncs.html | 403 - docs/0.9.5/api/evennia.utils.logger.html | 468 - .../api/evennia.utils.optionclasses.html | 929 - .../api/evennia.utils.optionhandler.html | 235 - docs/0.9.5/api/evennia.utils.picklefield.html | 279 - docs/0.9.5/api/evennia.utils.search.html | 386 - .../api/evennia.utils.test_resources.html | 242 - docs/0.9.5/api/evennia.utils.text2html.html | 469 - docs/0.9.5/api/evennia.utils.utils.html | 1468 -- .../api/evennia.utils.validatorfuncs.html | 232 - docs/0.9.5/api/evennia.web.html | 148 - docs/0.9.5/api/evennia.web.urls.html | 112 - .../0.9.5/api/evennia.web.utils.backends.html | 138 - .../evennia.web.utils.general_context.html | 137 - docs/0.9.5/api/evennia.web.utils.html | 120 - .../api/evennia.web.utils.middleware.html | 130 - docs/0.9.5/api/evennia.web.utils.tests.html | 138 - docs/0.9.5/api/evennia.web.webclient.html | 118 - .../0.9.5/api/evennia.web.webclient.urls.html | 114 - .../api/evennia.web.webclient.views.html | 120 - docs/0.9.5/api/evennia.web.website.forms.html | 350 - docs/0.9.5/api/evennia.web.website.html | 128 - ...nia.web.website.templatetags.addclass.html | 117 - .../api/evennia.web.website.templatetags.html | 117 - docs/0.9.5/api/evennia.web.website.tests.html | 483 - docs/0.9.5/api/evennia.web.website.urls.html | 113 - docs/0.9.5/api/evennia.web.website.views.html | 899 - docs/0.9.5/genindex.html | 14243 ---------------- docs/0.9.5/index.html | 149 - docs/0.9.5/objects.inv | Bin 95213 -> 0 bytes docs/0.9.5/py-modindex.html | 1197 -- docs/0.9.5/search.html | 116 - docs/0.9.5/searchindex.js | 1 - docs/0.9.5/toc.html | 2888 ---- docs/1.0-dev/.buildinfo | 2 +- docs/1.0-dev/Coding/Coding-Introduction.html | 1 - docs/1.0-dev/Coding/Coding-Overview.html | 1 - .../Coding/Continuous-Integration.html | 1 - docs/1.0-dev/Coding/Debugging.html | 1 - docs/1.0-dev/Coding/Flat-API.html | 1 - docs/1.0-dev/Coding/Profiling.html | 1 - docs/1.0-dev/Coding/Quirks.html | 3 +- docs/1.0-dev/Coding/Setting-up-PyCharm.html | 1 - docs/1.0-dev/Coding/Unit-Testing.html | 1 - docs/1.0-dev/Coding/Updating-Your-Game.html | 3 +- docs/1.0-dev/Coding/Using-Travis.html | 1 - docs/1.0-dev/Coding/Version-Control.html | 1 - docs/1.0-dev/Components/Accounts.html | 1 - docs/1.0-dev/Components/Attributes.html | 1 - .../Components/Batch-Code-Processor.html | 1 - .../Components/Batch-Command-Processor.html | 1 - docs/1.0-dev/Components/Batch-Processors.html | 1 - .../Bootstrap-Components-and-Utilities.html | 1 - docs/1.0-dev/Components/Channels.html | 1 - docs/1.0-dev/Components/Coding-Utils.html | 1 - docs/1.0-dev/Components/Command-Sets.html | 1 - docs/1.0-dev/Components/Command-System.html | 1 - docs/1.0-dev/Components/Commands.html | 1 - docs/1.0-dev/Components/Communications.html | 1 - .../Components/Components-Overview.html | 1 - .../1.0-dev/Components/Connection-Screen.html | 1 - docs/1.0-dev/Components/Default-Commands.html | 1 - docs/1.0-dev/Components/EvEditor.html | 1 - docs/1.0-dev/Components/EvMenu.html | 1 - docs/1.0-dev/Components/EvMore.html | 1 - docs/1.0-dev/Components/FuncParser.html | 1 - docs/1.0-dev/Components/Help-System.html | 1 - docs/1.0-dev/Components/Inputfuncs.html | 1 - docs/1.0-dev/Components/Locks.html | 1 - docs/1.0-dev/Components/MonitorHandler.html | 1 - docs/1.0-dev/Components/Msg.html | 1 - docs/1.0-dev/Components/Nicks.html | 1 - docs/1.0-dev/Components/Objects.html | 1 - docs/1.0-dev/Components/Outputfuncs.html | 1 - docs/1.0-dev/Components/Permissions.html | 1 - .../1.0-dev/Components/Portal-And-Server.html | 1 - docs/1.0-dev/Components/Prototypes.html | 1 - docs/1.0-dev/Components/Scripts.html | 1 - docs/1.0-dev/Components/Server.html | 1 - docs/1.0-dev/Components/Sessions.html | 1 - docs/1.0-dev/Components/Signals.html | 1 - docs/1.0-dev/Components/Tags.html | 1 - docs/1.0-dev/Components/TickerHandler.html | 1 - docs/1.0-dev/Components/Typeclasses.html | 1 - docs/1.0-dev/Components/Web-API.html | 1 - docs/1.0-dev/Components/Web-Admin.html | 1 - docs/1.0-dev/Components/Webclient.html | 1 - docs/1.0-dev/Components/Webserver.html | 1 - docs/1.0-dev/Components/Website.html | 1 - docs/1.0-dev/Concepts/Async-Process.html | 1 - docs/1.0-dev/Concepts/Banning.html | 1 - .../1.0-dev/Concepts/Bootstrap-&-Evennia.html | 1 - .../Concepts/Building-Permissions.html | 1 - .../Change-Messages-Per-Receiver.html | 1 - docs/1.0-dev/Concepts/Clickable-Links.html | 1 - docs/1.0-dev/Concepts/Colors.html | 1 - docs/1.0-dev/Concepts/Concepts-Overview.html | 1 - docs/1.0-dev/Concepts/Custom-Protocols.html | 1 - docs/1.0-dev/Concepts/Guest-Logins.html | 1 - .../Concepts/Internationalization.html | 3 +- docs/1.0-dev/Concepts/Messagepath.html | 1 - docs/1.0-dev/Concepts/Multisession-modes.html | 1 - docs/1.0-dev/Concepts/New-Models.html | 1 - docs/1.0-dev/Concepts/OOB.html | 1 - docs/1.0-dev/Concepts/Soft-Code.html | 1 - docs/1.0-dev/Concepts/Text-Encodings.html | 1 - docs/1.0-dev/Concepts/TextTags.html | 1 - .../Concepts/Using-MUX-as-a-Standard.html | 1 - docs/1.0-dev/Concepts/Web-Features.html | 1 - docs/1.0-dev/Concepts/Zones.html | 1 - .../Contribs/Arxcode-installing-help.html | 5 +- docs/1.0-dev/Contribs/Building-menus.html | 1 - docs/1.0-dev/Contribs/Contrib-AWSStorage.html | 1 - docs/1.0-dev/Contribs/Contrib-Auditing.html | 1 - docs/1.0-dev/Contribs/Contrib-Barter.html | 1 - .../Contribs/Contrib-Batchprocessor.html | 1 - .../Contribs/Contrib-Bodyfunctions.html | 1 - .../Contribs/Contrib-Building-Menu.html | 1 - docs/1.0-dev/Contribs/Contrib-Clothing.html | 1 - .../Contribs/Contrib-Color-Markups.html | 1 - docs/1.0-dev/Contribs/Contrib-Cooldowns.html | 1 - docs/1.0-dev/Contribs/Contrib-Crafting.html | 1 - .../Contribs/Contrib-Custom-Gametime.html | 1 - docs/1.0-dev/Contribs/Contrib-Dice.html | 1 - .../1.0-dev/Contribs/Contrib-Email-Login.html | 1 - .../1.0-dev/Contribs/Contrib-Evscaperoom.html | 1 - .../Contribs/Contrib-Extended-Room.html | 1 - docs/1.0-dev/Contribs/Contrib-Fieldfill.html | 1 - docs/1.0-dev/Contribs/Contrib-Gendersub.html | 1 - docs/1.0-dev/Contribs/Contrib-Health-Bar.html | 1 - ...ntrib-Ingame-Python-Tutorial-Dialogue.html | 1 - ...ntrib-Ingame-Python-Tutorial-Elevator.html | 1 - .../Contribs/Contrib-Ingame-Python.html | 1 - docs/1.0-dev/Contribs/Contrib-Mail.html | 1 - docs/1.0-dev/Contribs/Contrib-Mapbuilder.html | 1 - docs/1.0-dev/Contribs/Contrib-Menu-Login.html | 1 - docs/1.0-dev/Contribs/Contrib-Mirror.html | 1 - .../1.0-dev/Contribs/Contrib-Multidescer.html | 1 - .../Contribs/Contrib-Mux-Comms-Cmds.html | 1 - docs/1.0-dev/Contribs/Contrib-Overview.html | 1 - docs/1.0-dev/Contribs/Contrib-Puzzles.html | 1 - docs/1.0-dev/Contribs/Contrib-RPSystem.html | 1 - .../Contrib-Random-String-Generator.html | 1 - docs/1.0-dev/Contribs/Contrib-Red-Button.html | 1 - docs/1.0-dev/Contribs/Contrib-Simpledoor.html | 1 - docs/1.0-dev/Contribs/Contrib-Slow-Exit.html | 1 - .../1.0-dev/Contribs/Contrib-Talking-Npc.html | 1 - docs/1.0-dev/Contribs/Contrib-Traits.html | 1 - .../1.0-dev/Contribs/Contrib-Tree-Select.html | 1 - docs/1.0-dev/Contribs/Contrib-Turnbattle.html | 1 - .../Contribs/Contrib-Tutorial-World.html | 1 - .../1.0-dev/Contribs/Contrib-Unixcommand.html | 1 - docs/1.0-dev/Contribs/Contrib-Wilderness.html | 1 - docs/1.0-dev/Contribs/Contrib-XYZGrid.html | 3 +- docs/1.0-dev/Contributing-Docs.html | 1 - docs/1.0-dev/Contributing.html | 1 - docs/1.0-dev/Evennia-API.html | 1 - docs/1.0-dev/Evennia-Introduction.html | 15 +- docs/1.0-dev/Glossary.html | 14 +- .../Howto/Add-a-wiki-on-your-website.html | 1 - .../Howto/Building-a-mech-tutorial.html | 1 - docs/1.0-dev/Howto/Coding-FAQ.html | 1 - docs/1.0-dev/Howto/Command-Cooldown.html | 1 - docs/1.0-dev/Howto/Command-Duration.html | 1 - docs/1.0-dev/Howto/Command-Prompt.html | 1 - docs/1.0-dev/Howto/Coordinates.html | 1 - docs/1.0-dev/Howto/Default-Exit-Errors.html | 1 - docs/1.0-dev/Howto/Dynamic-In-Game-Map.html | 1 - .../1.0-dev/Howto/Evennia-for-Diku-Users.html | 1 - .../1.0-dev/Howto/Evennia-for-MUSH-Users.html | 5 +- .../Evennia-for-roleplaying-sessions.html | 3 +- docs/1.0-dev/Howto/Gametime-Tutorial.html | 1 - docs/1.0-dev/Howto/Help-System-Tutorial.html | 1 - docs/1.0-dev/Howto/Howto-Overview.html | 3 +- .../Howto/Manually-Configuring-Color.html | 1 - .../Howto/Mass-and-weight-for-objects.html | 1 - docs/1.0-dev/Howto/NPC-shop-Tutorial.html | 1 - .../Howto/Parsing-commands-tutorial.html | 1 - .../Howto/Starting/Part1/Adding-Commands.html | 1 - .../Starting/Part1/Building-Quickstart.html | 11 +- .../Howto/Starting/Part1/Creating-Things.html | 1 - .../Howto/Starting/Part1/Django-queries.html | 1 - .../Part1/Evennia-Library-Overview.html | 3 +- .../Starting/Part1/Gamedir-Overview.html | 3 +- .../Starting/Part1/Learning-Typeclasses.html | 1 - .../Starting/Part1/More-on-Commands.html | 3 +- .../Part1/Python-basic-introduction.html | 11 +- .../Part1/Python-classes-and-objects.html | 1 - .../Starting/Part1/Searching-Things.html | 1 - .../Howto/Starting/Part1/Starting-Part1.html | 15 +- ...-Introduction.html => Tutorial-World.html} | 29 +- .../Howto/Starting/Part2/Game-Planning.html | 1 - .../Part2/Planning-Some-Useful-Contribs.html | 1 - .../Part2/Planning-The-Tutorial-Game.html | 1 - .../Part2/Planning-Where-Do-I-Begin.html | 1 - .../Howto/Starting/Part2/Starting-Part2.html | 1 - .../Starting/Part3/A-Sittable-Object.html | 1 - .../Implementing-a-game-rule-system.html | 1 - .../Howto/Starting/Part3/Starting-Part3.html | 1 - .../Part3/Turn-based-Combat-System.html | 1 - .../Tutorial-for-basic-MUSH-like-game.html | 3 +- .../Howto/Starting/Part4/Starting-Part4.html | 1 - .../Part5/Add-a-simple-new-web-page.html | 1 - .../Howto/Starting/Part5/Starting-Part5.html | 1 - .../Howto/Starting/Part5/Web-Tutorial.html | 1 - docs/1.0-dev/Howto/Static-In-Game-Map.html | 1 - .../Howto/Tutorial-Aggressive-NPCs.html | 1 - .../Howto/Tutorial-NPCs-listening.html | 1 - .../Howto/Tutorial-Tweeting-Game-Stats.html | 1 - docs/1.0-dev/Howto/Tutorial-Vehicles.html | 1 - .../Howto/Understanding-Color-Tags.html | 1 - docs/1.0-dev/Howto/Weather-Tutorial.html | 1 - .../Howto/Web-Character-Generation.html | 1 - .../Howto/Web-Character-View-Tutorial.html | 1 - docs/1.0-dev/Licensing.html | 1 - docs/1.0-dev/Links.html | 1 - docs/1.0-dev/Setup/Apache-Config.html | 1 - docs/1.0-dev/Setup/Changelog.html | 1003 ++ .../1.0-dev/Setup/Choosing-An-SQL-Server.html | 1 - docs/1.0-dev/Setup/Client-Support-Grid.html | 1 - docs/1.0-dev/Setup/Evennia-Game-Index.html | 1 - docs/1.0-dev/Setup/Extended-Installation.html | 634 - docs/1.0-dev/Setup/Grapevine.html | 1 - docs/1.0-dev/Setup/HAProxy-Config.html | 1 - .../How-to-connect-Evennia-to-Twitter.html | 1 - docs/1.0-dev/Setup/IRC.html | 1 - ...Android.html => Installation-Android.html} | 13 +- ...n-Docker.html => Installation-Docker.html} | 7 +- docs/1.0-dev/Setup/Installation-Git.html | 390 + ...html => Installation-Non-Interactive.html} | 31 +- .../Setup/Installation-Troubleshooting.html | 244 + docs/1.0-dev/Setup/Installation-Upgrade.html | 159 + ...etup-Quickstart.html => Installation.html} | 147 +- docs/1.0-dev/Setup/Online-Setup.html | 5 +- docs/1.0-dev/Setup/RSS.html | 1 - docs/1.0-dev/Setup/Security.html | 1 - docs/1.0-dev/Setup/Server-Conf.html | 3 +- docs/1.0-dev/Setup/Settings-Default.html | 1307 ++ docs/1.0-dev/Setup/Setup-Overview.html | 11 +- docs/1.0-dev/Setup/Start-Stop-Reload.html | 3 +- docs/1.0-dev/Unimplemented.html | 1 - docs/1.0-dev/_modules/django/conf.html | 68 +- .../db/models/fields/related_descriptors.html | 10 +- .../_modules/django/db/models/manager.html | 1 - .../_modules/django/db/models/query.html | 127 +- .../django/db/models/query_utils.html | 31 +- .../_modules/django/utils/deconstruct.html | 160 - .../_modules/django/utils/functional.html | 7 +- docs/1.0-dev/_modules/evennia.html | 1 - .../_modules/evennia/accounts/accounts.html | 1 - .../_modules/evennia/accounts/bots.html | 1 - .../_modules/evennia/accounts/manager.html | 1 - .../_modules/evennia/accounts/models.html | 1 - .../_modules/evennia/commands/cmdhandler.html | 1 - .../_modules/evennia/commands/cmdparser.html | 1 - .../_modules/evennia/commands/cmdset.html | 1 - .../evennia/commands/cmdsethandler.html | 1 - .../_modules/evennia/commands/command.html | 1 - .../evennia/commands/default/account.html | 1 - .../evennia/commands/default/admin.html | 1 - .../commands/default/batchprocess.html | 1 - .../evennia/commands/default/building.html | 8 +- .../commands/default/cmdset_account.html | 1 - .../commands/default/cmdset_character.html | 1 - .../commands/default/cmdset_session.html | 1 - .../commands/default/cmdset_unloggedin.html | 1 - .../evennia/commands/default/comms.html | 1 - .../evennia/commands/default/general.html | 1 - .../evennia/commands/default/help.html | 1 - .../evennia/commands/default/muxcommand.html | 1 - .../evennia/commands/default/syscommands.html | 1 - .../evennia/commands/default/system.html | 1 - .../evennia/commands/default/tests.html | 1 - .../evennia/commands/default/unloggedin.html | 1 - .../1.0-dev/_modules/evennia/comms/comms.html | 1 - .../_modules/evennia/comms/managers.html | 1 - .../_modules/evennia/comms/models.html | 1 - .../base_systems/awsstorage/aws_s3_cdn.html | 970 -- .../base_systems/awsstorage/tests.html | 1 - .../building_menu/building_menu.html | 1 - .../base_systems/building_menu/tests.html | 1 - .../base_systems/color_markups/tests.html | 1 - .../custom_gametime/custom_gametime.html | 1 - .../base_systems/custom_gametime/tests.html | 1 - .../base_systems/email_login/email_login.html | 1 - .../base_systems/email_login/tests.html | 1 - .../ingame_python/callbackhandler.html | 1 - .../base_systems/ingame_python/commands.html | 1 - .../ingame_python/eventfuncs.html | 1 - .../base_systems/ingame_python/scripts.html | 1 - .../base_systems/ingame_python/tests.html | 1 - .../base_systems/ingame_python/utils.html | 1 - .../mux_comms_cmds/mux_comms_cmds.html | 1 - .../base_systems/mux_comms_cmds/tests.html | 1 - .../base_systems/unixcommand/tests.html | 1 - .../base_systems/unixcommand/unixcommand.html | 1 - .../full_systems/evscaperoom/commands.html | 1 - .../full_systems/evscaperoom/menu.html | 1 - .../full_systems/evscaperoom/objects.html | 1 - .../full_systems/evscaperoom/room.html | 1 - .../full_systems/evscaperoom/scripts.html | 1 - .../full_systems/evscaperoom/state.html | 1 - .../full_systems/evscaperoom/tests.html | 1 - .../full_systems/evscaperoom/utils.html | 1 - .../contrib/game_systems/barter/barter.html | 1 - .../contrib/game_systems/barter/tests.html | 1 - .../game_systems/clothing/clothing.html | 1 - .../contrib/game_systems/clothing/tests.html | 1 - .../game_systems/cooldowns/cooldowns.html | 1 - .../contrib/game_systems/cooldowns/tests.html | 1 - .../game_systems/crafting/crafting.html | 1 - .../crafting/example_recipes.html | 1 - .../contrib/game_systems/crafting/tests.html | 1 - .../game_systems/gendersub/gendersub.html | 1 - .../contrib/game_systems/gendersub/tests.html | 1 - .../contrib/game_systems/mail/mail.html | 1 - .../contrib/game_systems/mail/tests.html | 1 - .../game_systems/multidescer/multidescer.html | 1 - .../game_systems/multidescer/tests.html | 1 - .../contrib/game_systems/puzzles/puzzles.html | 1 - .../contrib/game_systems/puzzles/tests.html | 1 - .../game_systems/turnbattle/tb_basic.html | 482 +- .../game_systems/turnbattle/tb_equip.html | 753 +- .../game_systems/turnbattle/tb_items.html | 1913 +-- .../game_systems/turnbattle/tb_magic.html | 1143 +- .../game_systems/turnbattle/tb_range.html | 975 +- .../game_systems/turnbattle/tests.html | 156 +- .../grid/extended_room/extended_room.html | 1 - .../contrib/grid/extended_room/tests.html | 1 - .../contrib/grid/simpledoor/simpledoor.html | 1 - .../contrib/grid/simpledoor/tests.html | 1 - .../contrib/grid/slow_exit/slow_exit.html | 1 - .../evennia/contrib/grid/slow_exit/tests.html | 1 - .../contrib/grid/wilderness/tests.html | 1 - .../contrib/grid/wilderness/wilderness.html | 1 - .../contrib/grid/xyzgrid/commands.html | 1 - .../evennia/contrib/grid/xyzgrid/example.html | 1 - .../contrib/grid/xyzgrid/launchcmd.html | 1 - .../evennia/contrib/grid/xyzgrid/tests.html | 1 - .../evennia/contrib/grid/xyzgrid/utils.html | 1 - .../evennia/contrib/grid/xyzgrid/xymap.html | 1 - .../contrib/grid/xyzgrid/xymap_legend.html | 1 - .../evennia/contrib/grid/xyzgrid/xyzgrid.html | 1 - .../evennia/contrib/grid/xyzgrid/xyzroom.html | 1 - .../evennia/contrib/rpg/dice/dice.html | 1 - .../evennia/contrib/rpg/dice/tests.html | 1 - .../contrib/rpg/health_bar/health_bar.html | 1 - .../evennia/contrib/rpg/health_bar/tests.html | 1 - .../contrib/rpg/rpsystem/rplanguage.html | 1 - .../contrib/rpg/rpsystem/rpsystem.html | 1 - .../evennia/contrib/rpg/rpsystem/tests.html | 1 - .../evennia/contrib/rpg/traits/tests.html | 1 - .../evennia/contrib/rpg/traits/traits.html | 1 - .../bodyfunctions/bodyfunctions.html | 1 - .../tutorials/bodyfunctions/tests.html | 1 - .../contrib/tutorials/mirror/mirror.html | 1 - .../tutorials/red_button/red_button.html | 1 - .../tutorials/talking_npc/talking_npc.html | 1 - .../contrib/tutorials/talking_npc/tests.html | 1 - .../tutorials/tutorial_world/intro_menu.html | 47 +- .../contrib/tutorials/tutorial_world/mob.html | 1 - .../tutorials/tutorial_world/objects.html | 1 - .../tutorials/tutorial_world/rooms.html | 20 +- .../tutorials/tutorial_world/tests.html | 1 - .../contrib/utils/auditing/outputs.html | 1 - .../contrib/utils/auditing/server.html | 1 - .../evennia/contrib/utils/auditing/tests.html | 1 - .../contrib/utils/fieldfill/fieldfill.html | 1 - .../random_string_generator.html | 1 - .../utils/random_string_generator/tests.html | 1 - .../contrib/utils/tree_select/tests.html | 1 - .../utils/tree_select/tree_select.html | 1 - .../_modules/evennia/help/filehelp.html | 1 - .../_modules/evennia/help/manager.html | 1 - .../1.0-dev/_modules/evennia/help/models.html | 1 - docs/1.0-dev/_modules/evennia/help/utils.html | 1 - .../_modules/evennia/locks/lockfuncs.html | 1 - .../_modules/evennia/locks/lockhandler.html | 1 - .../_modules/evennia/objects/manager.html | 1 - .../_modules/evennia/objects/models.html | 1 - .../_modules/evennia/objects/objects.html | 1 - .../_modules/evennia/prototypes/menus.html | 1 - .../evennia/prototypes/protfuncs.html | 1 - .../evennia/prototypes/prototypes.html | 1 - .../_modules/evennia/prototypes/spawner.html | 1 - .../_modules/evennia/scripts/manager.html | 1 - .../_modules/evennia/scripts/models.html | 1 - .../evennia/scripts/monitorhandler.html | 1 - .../evennia/scripts/scripthandler.html | 1 - .../_modules/evennia/scripts/scripts.html | 1 - .../_modules/evennia/scripts/taskhandler.html | 1 - .../evennia/scripts/tickerhandler.html | 1 - .../_modules/evennia/server/amp_client.html | 1 - .../evennia/server/connection_wizard.html | 1 - .../_modules/evennia/server/deprecations.html | 1 - .../evennia/server/evennia_launcher.html | 24 +- .../server/game_index_client/client.html | 1 - .../server/game_index_client/service.html | 1 - .../evennia/server/initial_setup.html | 6 +- .../_modules/evennia/server/inputfuncs.html | 1 - .../_modules/evennia/server/manager.html | 1 - .../_modules/evennia/server/models.html | 1 - .../_modules/evennia/server/portal/amp.html | 1 - .../evennia/server/portal/amp_server.html | 1 - .../evennia/server/portal/grapevine.html | 1 - .../_modules/evennia/server/portal/irc.html | 1 - .../_modules/evennia/server/portal/mccp.html | 1 - .../_modules/evennia/server/portal/mssp.html | 1 - .../_modules/evennia/server/portal/mxp.html | 1 - .../_modules/evennia/server/portal/naws.html | 1 - .../evennia/server/portal/portal.html | 1 - .../server/portal/portalsessionhandler.html | 1 - .../_modules/evennia/server/portal/rss.html | 1 - .../_modules/evennia/server/portal/ssh.html | 1 - .../_modules/evennia/server/portal/ssl.html | 1 - .../evennia/server/portal/suppress_ga.html | 1 - .../evennia/server/portal/telnet.html | 1 - .../evennia/server/portal/telnet_oob.html | 1 - .../evennia/server/portal/telnet_ssl.html | 1 - .../_modules/evennia/server/portal/tests.html | 1 - .../_modules/evennia/server/portal/ttype.html | 1 - .../evennia/server/portal/webclient.html | 1 - .../evennia/server/portal/webclient_ajax.html | 1 - .../evennia/server/profiling/dummyrunner.html | 1 - .../profiling/dummyrunner_settings.html | 1 - .../evennia/server/profiling/memplot.html | 1 - .../server/profiling/test_queries.html | 1 - .../evennia/server/profiling/tests.html | 1 - .../evennia/server/profiling/timetrace.html | 1 - .../_modules/evennia/server/server.html | 1 - .../evennia/server/serversession.html | 1 - .../_modules/evennia/server/session.html | 1 - .../evennia/server/sessionhandler.html | 1 - .../_modules/evennia/server/throttle.html | 1 - .../_modules/evennia/server/validators.html | 1 - .../_modules/evennia/server/webserver.html | 1 - .../evennia/typeclasses/attributes.html | 1 - .../evennia/typeclasses/managers.html | 1 - .../_modules/evennia/typeclasses/models.html | 1 - .../_modules/evennia/typeclasses/tags.html | 1 - docs/1.0-dev/_modules/evennia/utils/ansi.html | 1 - .../evennia/utils/batchprocessors.html | 1 - .../_modules/evennia/utils/containers.html | 1 - .../_modules/evennia/utils/dbserialize.html | 1 - .../_modules/evennia/utils/eveditor.html | 1 - .../_modules/evennia/utils/evform.html | 1 - .../_modules/evennia/utils/evmenu.html | 1 - .../_modules/evennia/utils/evmore.html | 1 - .../_modules/evennia/utils/evtable.html | 1 - .../_modules/evennia/utils/funcparser.html | 1 - .../_modules/evennia/utils/gametime.html | 1 - .../evennia/utils/idmapper/manager.html | 1 - .../evennia/utils/idmapper/models.html | 1 - .../evennia/utils/idmapper/tests.html | 1 - .../_modules/evennia/utils/logger.html | 1 - .../_modules/evennia/utils/optionclasses.html | 1 - .../_modules/evennia/utils/optionhandler.html | 1 - .../_modules/evennia/utils/picklefield.html | 1 - .../_modules/evennia/utils/search.html | 1 - .../evennia/utils/test_resources.html | 1 - .../_modules/evennia/utils/text2html.html | 1 - .../1.0-dev/_modules/evennia/utils/utils.html | 1 - .../evennia/utils/validatorfuncs.html | 1 - .../utils/verb_conjugation/conjugate.html | 1 - .../utils/verb_conjugation/pronouns.html | 1 - .../evennia/utils/verb_conjugation/tests.html | 1 - .../_modules/evennia/web/admin/accounts.html | 3 +- .../evennia/web/admin/attributes.html | 1 - .../_modules/evennia/web/admin/comms.html | 6 +- .../_modules/evennia/web/admin/frontpage.html | 1 - .../_modules/evennia/web/admin/help.html | 1 - .../_modules/evennia/web/admin/objects.html | 10 +- .../_modules/evennia/web/admin/scripts.html | 3 +- .../_modules/evennia/web/admin/server.html | 1 - .../_modules/evennia/web/admin/tags.html | 1 - .../_modules/evennia/web/admin/utils.html | 1 - .../_modules/evennia/web/api/filters.html | 1 - .../_modules/evennia/web/api/permissions.html | 1 - .../_modules/evennia/web/api/root.html | 1 - .../_modules/evennia/web/api/serializers.html | 1 - .../_modules/evennia/web/api/tests.html | 8 +- .../_modules/evennia/web/api/views.html | 1 - .../evennia/web/templatetags/addclass.html | 1 - .../_modules/evennia/web/utils/adminsite.html | 1 - .../_modules/evennia/web/utils/backends.html | 1 - .../evennia/web/utils/general_context.html | 1 - .../evennia/web/utils/middleware.html | 1 - .../_modules/evennia/web/utils/tests.html | 1 - .../_modules/evennia/web/webclient/views.html | 1 - .../_modules/evennia/web/website/forms.html | 1 - .../_modules/evennia/web/website/tests.html | 1 - .../evennia/web/website/views/accounts.html | 1 - .../evennia/web/website/views/channels.html | 1 - .../evennia/web/website/views/characters.html | 5 +- .../evennia/web/website/views/errors.html | 1 - .../evennia/web/website/views/help.html | 1 - .../evennia/web/website/views/index.html | 1 - .../evennia/web/website/views/mixins.html | 3 - .../evennia/web/website/views/objects.html | 24 +- docs/1.0-dev/_modules/functools.html | 284 +- docs/1.0-dev/_modules/index.html | 4 +- docs/1.0-dev/_modules/re.html | 487 + .../1.0-dev/_modules/rest_framework/test.html | 43 +- docs/1.0-dev/_sources/Coding/Quirks.md.txt | 2 +- .../_sources/Coding/Updating-Your-Game.md.txt | 2 +- .../Concepts/Internationalization.md.txt | 2 +- .../Contribs/Arxcode-installing-help.md.txt | 4 +- .../1.0-dev/_sources/Contributing-Docs.md.txt | 2 +- .../_sources/Evennia-Introduction.md.txt | 4 +- docs/1.0-dev/_sources/Glossary.md.txt | 14 +- .../Howto/Evennia-for-MUSH-Users.md.txt | 4 +- .../Evennia-for-roleplaying-sessions.md.txt | 2 +- .../_sources/Howto/Howto-Overview.md.txt | 2 +- .../Part1/Evennia-Library-Overview.md.txt | 2 +- .../Starting/Part1/Gamedir-Overview.md.txt | 2 +- .../Starting/Part1/More-on-Commands.md.txt | 2 +- .../Starting/Part1/Starting-Part1.md.txt | 4 +- ...roduction.md.txt => Tutorial-World.md.txt} | 0 .../Tutorial-for-basic-MUSH-like-game.md.txt | 2 +- docs/1.0-dev/_sources/Setup/Changelog.md.txt | 701 + .../Setup/Extended-Installation.md.txt | 557 - ...oid.md.txt => Installation-Android.md.txt} | 11 +- ...cker.md.txt => Installation-Docker.md.txt} | 2 +- .../_sources/Setup/Installation-Git.md.txt | 302 + .../Setup/Installation-Non-Interactive.md.txt | 19 + .../Setup/Installation-Troubleshooting.md.txt | 115 + .../Setup/Installation-Upgrade.md.txt | 44 + .../_sources/Setup/Installation.md.txt | 110 + .../_sources/Setup/Online-Setup.md.txt | 4 +- .../1.0-dev/_sources/Setup/Server-Conf.md.txt | 2 +- .../_sources/Setup/Settings-Default.md.txt | 1202 ++ .../_sources/Setup/Settings-File.md.txt | 3 - .../_sources/Setup/Setup-Overview.md.txt | 10 +- .../_sources/Setup/Setup-Quickstart.md.txt | 99 - .../_sources/Setup/Start-Stop-Reload.md.txt | 2 +- docs/1.0-dev/_sources/index.md.txt | 2 +- docs/1.0-dev/_sources/toc.md.txt | 16 +- docs/1.0-dev/api/evennia-api.html | 1 - .../api/evennia.accounts.accounts.html | 1 - docs/1.0-dev/api/evennia.accounts.bots.html | 1 - docs/1.0-dev/api/evennia.accounts.html | 1 - .../1.0-dev/api/evennia.accounts.manager.html | 1 - docs/1.0-dev/api/evennia.accounts.models.html | 99 +- .../api/evennia.commands.cmdhandler.html | 1 - .../api/evennia.commands.cmdparser.html | 1 - docs/1.0-dev/api/evennia.commands.cmdset.html | 1 - .../api/evennia.commands.cmdsethandler.html | 1 - .../1.0-dev/api/evennia.commands.command.html | 3 +- .../api/evennia.commands.default.account.html | 1 - .../api/evennia.commands.default.admin.html | 5 +- ...evennia.commands.default.batchprocess.html | 5 +- .../evennia.commands.default.building.html | 9 +- ...ennia.commands.default.cmdset_account.html | 1 - ...nia.commands.default.cmdset_character.html | 1 - ...ennia.commands.default.cmdset_session.html | 1 - ...ia.commands.default.cmdset_unloggedin.html | 1 - .../api/evennia.commands.default.comms.html | 9 +- .../api/evennia.commands.default.general.html | 5 +- .../api/evennia.commands.default.help.html | 1 - .../1.0-dev/api/evennia.commands.default.html | 1 - .../evennia.commands.default.muxcommand.html | 1 - .../evennia.commands.default.syscommands.html | 1 - .../api/evennia.commands.default.system.html | 5 +- .../api/evennia.commands.default.tests.html | 3 +- .../evennia.commands.default.unloggedin.html | 17 +- docs/1.0-dev/api/evennia.commands.html | 1 - docs/1.0-dev/api/evennia.comms.comms.html | 1 - docs/1.0-dev/api/evennia.comms.html | 1 - docs/1.0-dev/api/evennia.comms.managers.html | 1 - docs/1.0-dev/api/evennia.comms.models.html | 29 +- ...ib.base_systems.awsstorage.aws_s3_cdn.html | 468 +- ...ennia.contrib.base_systems.awsstorage.html | 1 - ...contrib.base_systems.awsstorage.tests.html | 1 - ...e_systems.building_menu.building_menu.html | 1 - ...ia.contrib.base_systems.building_menu.html | 1 - ...trib.base_systems.building_menu.tests.html | 1 - ...e_systems.color_markups.color_markups.html | 1 - ...ia.contrib.base_systems.color_markups.html | 1 - ...trib.base_systems.color_markups.tests.html | 1 - ...stems.custom_gametime.custom_gametime.html | 1 - ....contrib.base_systems.custom_gametime.html | 1 - ...ib.base_systems.custom_gametime.tests.html | 1 - ...ystems.email_login.connection_screens.html | 1 - ....base_systems.email_login.email_login.html | 17 +- ...nnia.contrib.base_systems.email_login.html | 1 - ...ontrib.base_systems.email_login.tests.html | 1 - .../api/evennia.contrib.base_systems.html | 1 - ...systems.ingame_python.callbackhandler.html | 41 +- ...b.base_systems.ingame_python.commands.html | 5 +- ...base_systems.ingame_python.eventfuncs.html | 1 - ...ia.contrib.base_systems.ingame_python.html | 1 - ...ib.base_systems.ingame_python.scripts.html | 1 - ...trib.base_systems.ingame_python.tests.html | 1 - ...ase_systems.ingame_python.typeclasses.html | 1 - ...trib.base_systems.ingame_python.utils.html | 1 - ...systems.menu_login.connection_screens.html | 1 - ...ennia.contrib.base_systems.menu_login.html | 1 - ...ib.base_systems.menu_login.menu_login.html | 1 - ...contrib.base_systems.menu_login.tests.html | 1 - ...a.contrib.base_systems.mux_comms_cmds.html | 1 - ...systems.mux_comms_cmds.mux_comms_cmds.html | 1 - ...rib.base_systems.mux_comms_cmds.tests.html | 1 - ...nnia.contrib.base_systems.unixcommand.html | 1 - ...ontrib.base_systems.unixcommand.tests.html | 1 - ....base_systems.unixcommand.unixcommand.html | 1 - ...rib.full_systems.evscaperoom.commands.html | 25 +- ...nnia.contrib.full_systems.evscaperoom.html | 1 - ...contrib.full_systems.evscaperoom.menu.html | 1 - ...trib.full_systems.evscaperoom.objects.html | 1 - ...contrib.full_systems.evscaperoom.room.html | 1 - ...trib.full_systems.evscaperoom.scripts.html | 1 - ...ontrib.full_systems.evscaperoom.state.html | 1 - ...ontrib.full_systems.evscaperoom.tests.html | 1 - ...ontrib.full_systems.evscaperoom.utils.html | 1 - .../api/evennia.contrib.full_systems.html | 1 - ...ia.contrib.game_systems.barter.barter.html | 1 - .../evennia.contrib.game_systems.barter.html | 1 - ...nia.contrib.game_systems.barter.tests.html | 1 - ...ontrib.game_systems.clothing.clothing.html | 1 - ...evennia.contrib.game_systems.clothing.html | 1 - ...a.contrib.game_systems.clothing.tests.html | 1 - ...trib.game_systems.cooldowns.cooldowns.html | 1 - ...vennia.contrib.game_systems.cooldowns.html | 1 - ....contrib.game_systems.cooldowns.tests.html | 1 - ...ontrib.game_systems.crafting.crafting.html | 1 - ...game_systems.crafting.example_recipes.html | 1 - ...evennia.contrib.game_systems.crafting.html | 1 - ...a.contrib.game_systems.crafting.tests.html | 1 - ...trib.game_systems.gendersub.gendersub.html | 1 - ...vennia.contrib.game_systems.gendersub.html | 1 - ....contrib.game_systems.gendersub.tests.html | 1 - .../api/evennia.contrib.game_systems.html | 1 - .../evennia.contrib.game_systems.mail.html | 1 - ...vennia.contrib.game_systems.mail.mail.html | 1 - ...ennia.contrib.game_systems.mail.tests.html | 1 - ...nnia.contrib.game_systems.multidescer.html | 1 - ....game_systems.multidescer.multidescer.html | 1 - ...ontrib.game_systems.multidescer.tests.html | 1 - .../evennia.contrib.game_systems.puzzles.html | 1 - ....contrib.game_systems.puzzles.puzzles.html | 1 - ...ia.contrib.game_systems.puzzles.tests.html | 1 - ...ennia.contrib.game_systems.turnbattle.html | 1 - ...trib.game_systems.turnbattle.tb_basic.html | 146 +- ...trib.game_systems.turnbattle.tb_equip.html | 357 +- ...trib.game_systems.turnbattle.tb_items.html | 553 +- ...trib.game_systems.turnbattle.tb_magic.html | 586 +- ...trib.game_systems.turnbattle.tb_range.html | 472 +- ...contrib.game_systems.turnbattle.tests.html | 1 - ...trib.grid.extended_room.extended_room.html | 1 - .../evennia.contrib.grid.extended_room.html | 1 - ...nnia.contrib.grid.extended_room.tests.html | 1 - docs/1.0-dev/api/evennia.contrib.grid.html | 1 - .../api/evennia.contrib.grid.mapbuilder.html | 1 - ...ia.contrib.grid.mapbuilder.mapbuilder.html | 1 - ...evennia.contrib.grid.mapbuilder.tests.html | 1 - .../api/evennia.contrib.grid.simpledoor.html | 1 - ...ia.contrib.grid.simpledoor.simpledoor.html | 1 - ...evennia.contrib.grid.simpledoor.tests.html | 1 - .../api/evennia.contrib.grid.slow_exit.html | 1 - ...nnia.contrib.grid.slow_exit.slow_exit.html | 1 - .../evennia.contrib.grid.slow_exit.tests.html | 1 - .../api/evennia.contrib.grid.wilderness.html | 1 - ...evennia.contrib.grid.wilderness.tests.html | 1 - ...ia.contrib.grid.wilderness.wilderness.html | 1 - ...evennia.contrib.grid.xyzgrid.commands.html | 21 +- .../evennia.contrib.grid.xyzgrid.example.html | 1 - .../api/evennia.contrib.grid.xyzgrid.html | 1 - ...vennia.contrib.grid.xyzgrid.launchcmd.html | 1 - ...ennia.contrib.grid.xyzgrid.prototypes.html | 1 - .../evennia.contrib.grid.xyzgrid.tests.html | 1 - .../evennia.contrib.grid.xyzgrid.utils.html | 1 - .../evennia.contrib.grid.xyzgrid.xymap.html | 1 - ...nia.contrib.grid.xyzgrid.xymap_legend.html | 1 - .../evennia.contrib.grid.xyzgrid.xyzgrid.html | 1 - .../evennia.contrib.grid.xyzgrid.xyzroom.html | 1 - docs/1.0-dev/api/evennia.contrib.html | 1 - .../api/evennia.contrib.rpg.dice.dice.html | 5 +- .../1.0-dev/api/evennia.contrib.rpg.dice.html | 1 - .../api/evennia.contrib.rpg.dice.tests.html | 1 - ...nia.contrib.rpg.health_bar.health_bar.html | 1 - .../api/evennia.contrib.rpg.health_bar.html | 1 - .../evennia.contrib.rpg.health_bar.tests.html | 1 - docs/1.0-dev/api/evennia.contrib.rpg.html | 1 - .../api/evennia.contrib.rpg.rpsystem.html | 1 - ...ennia.contrib.rpg.rpsystem.rplanguage.html | 1 - ...evennia.contrib.rpg.rpsystem.rpsystem.html | 1 - .../evennia.contrib.rpg.rpsystem.tests.html | 1 - .../api/evennia.contrib.rpg.traits.html | 1 - .../api/evennia.contrib.rpg.traits.tests.html | 1 - .../evennia.contrib.rpg.traits.traits.html | 1 - ...als.batchprocessor.example_batch_code.html | 1 - ...nnia.contrib.tutorials.batchprocessor.html | 1 - ...tutorials.bodyfunctions.bodyfunctions.html | 1 - ...ennia.contrib.tutorials.bodyfunctions.html | 1 - ...contrib.tutorials.bodyfunctions.tests.html | 1 - .../api/evennia.contrib.tutorials.html | 1 - .../api/evennia.contrib.tutorials.mirror.html | 1 - ...ennia.contrib.tutorials.mirror.mirror.html | 1 - .../evennia.contrib.tutorials.red_button.html | 1 - ...ntrib.tutorials.red_button.red_button.html | 17 +- ...evennia.contrib.tutorials.talking_npc.html | 1 - ...rib.tutorials.talking_npc.talking_npc.html | 1 - ...a.contrib.tutorials.talking_npc.tests.html | 1 - ...nnia.contrib.tutorials.tutorial_world.html | 1 - ...b.tutorials.tutorial_world.intro_menu.html | 1 - ....contrib.tutorials.tutorial_world.mob.html | 1 - ...trib.tutorials.tutorial_world.objects.html | 17 +- ...ontrib.tutorials.tutorial_world.rooms.html | 48 +- ...ontrib.tutorials.tutorial_world.tests.html | 1 - .../api/evennia.contrib.utils.auditing.html | 1 - ...vennia.contrib.utils.auditing.outputs.html | 1 - ...evennia.contrib.utils.auditing.server.html | 1 - .../evennia.contrib.utils.auditing.tests.html | 1 - ...nia.contrib.utils.fieldfill.fieldfill.html | 1 - .../api/evennia.contrib.utils.fieldfill.html | 1 - docs/1.0-dev/api/evennia.contrib.utils.html | 1 - ...contrib.utils.random_string_generator.html | 1 - ...ing_generator.random_string_generator.html | 1 - ...b.utils.random_string_generator.tests.html | 1 - .../evennia.contrib.utils.tree_select.html | 1 - ...ennia.contrib.utils.tree_select.tests.html | 1 - ...contrib.utils.tree_select.tree_select.html | 1 - docs/1.0-dev/api/evennia.help.filehelp.html | 1 - docs/1.0-dev/api/evennia.help.html | 1 - docs/1.0-dev/api/evennia.help.manager.html | 1 - docs/1.0-dev/api/evennia.help.models.html | 1 - docs/1.0-dev/api/evennia.help.utils.html | 1 - docs/1.0-dev/api/evennia.html | 1 - docs/1.0-dev/api/evennia.locks.html | 1 - docs/1.0-dev/api/evennia.locks.lockfuncs.html | 1 - .../api/evennia.locks.lockhandler.html | 1 - docs/1.0-dev/api/evennia.objects.html | 1 - docs/1.0-dev/api/evennia.objects.manager.html | 1 - docs/1.0-dev/api/evennia.objects.models.html | 29 +- docs/1.0-dev/api/evennia.objects.objects.html | 1 - docs/1.0-dev/api/evennia.prototypes.html | 1 - .../1.0-dev/api/evennia.prototypes.menus.html | 1 - .../api/evennia.prototypes.protfuncs.html | 1 - .../api/evennia.prototypes.prototypes.html | 1 - .../api/evennia.prototypes.spawner.html | 1 - docs/1.0-dev/api/evennia.scripts.html | 1 - docs/1.0-dev/api/evennia.scripts.manager.html | 1 - docs/1.0-dev/api/evennia.scripts.models.html | 29 +- .../api/evennia.scripts.monitorhandler.html | 1 - .../api/evennia.scripts.scripthandler.html | 1 - docs/1.0-dev/api/evennia.scripts.scripts.html | 1 - .../api/evennia.scripts.taskhandler.html | 1 - .../api/evennia.scripts.tickerhandler.html | 1 - .../api/evennia.server.amp_client.html | 1 - .../api/evennia.server.connection_wizard.html | 1 - .../api/evennia.server.deprecations.html | 1 - .../api/evennia.server.evennia_launcher.html | 1 - ...ennia.server.game_index_client.client.html | 1 - .../api/evennia.server.game_index_client.html | 1 - ...nnia.server.game_index_client.service.html | 1 - docs/1.0-dev/api/evennia.server.html | 1 - .../api/evennia.server.initial_setup.html | 1 - .../api/evennia.server.inputfuncs.html | 1 - docs/1.0-dev/api/evennia.server.manager.html | 1 - docs/1.0-dev/api/evennia.server.models.html | 1 - .../api/evennia.server.portal.amp.html | 1 - .../api/evennia.server.portal.amp_server.html | 1 - .../api/evennia.server.portal.grapevine.html | 1 - docs/1.0-dev/api/evennia.server.portal.html | 1 - .../api/evennia.server.portal.irc.html | 3 +- .../api/evennia.server.portal.mccp.html | 1 - .../api/evennia.server.portal.mssp.html | 1 - .../api/evennia.server.portal.mxp.html | 1 - .../api/evennia.server.portal.naws.html | 1 - .../api/evennia.server.portal.portal.html | 1 - ...ia.server.portal.portalsessionhandler.html | 1 - .../api/evennia.server.portal.rss.html | 1 - .../api/evennia.server.portal.ssh.html | 1 - .../api/evennia.server.portal.ssl.html | 1 - .../evennia.server.portal.suppress_ga.html | 1 - .../api/evennia.server.portal.telnet.html | 1 - .../api/evennia.server.portal.telnet_oob.html | 1 - .../api/evennia.server.portal.telnet_ssl.html | 1 - .../api/evennia.server.portal.tests.html | 1 - .../api/evennia.server.portal.ttype.html | 1 - .../api/evennia.server.portal.webclient.html | 1 - .../evennia.server.portal.webclient_ajax.html | 1 - .../evennia.server.profiling.dummyrunner.html | 1 - ...server.profiling.dummyrunner_settings.html | 1 - .../1.0-dev/api/evennia.server.profiling.html | 1 - .../api/evennia.server.profiling.memplot.html | 1 - ...ennia.server.profiling.settings_mixin.html | 1 - ...evennia.server.profiling.test_queries.html | 1 - .../api/evennia.server.profiling.tests.html | 1 - .../evennia.server.profiling.timetrace.html | 1 - docs/1.0-dev/api/evennia.server.server.html | 1 - .../api/evennia.server.serversession.html | 1 - docs/1.0-dev/api/evennia.server.session.html | 1 - .../api/evennia.server.sessionhandler.html | 1 - docs/1.0-dev/api/evennia.server.signals.html | 1 - docs/1.0-dev/api/evennia.server.throttle.html | 1 - .../api/evennia.server.validators.html | 1 - .../1.0-dev/api/evennia.server.webserver.html | 1 - .../1.0-dev/api/evennia.settings_default.html | 1 - .../api/evennia.typeclasses.attributes.html | 1 - docs/1.0-dev/api/evennia.typeclasses.html | 1 - .../api/evennia.typeclasses.managers.html | 1 - .../api/evennia.typeclasses.models.html | 1 - .../1.0-dev/api/evennia.typeclasses.tags.html | 1 - docs/1.0-dev/api/evennia.utils.ansi.html | 1 - .../api/evennia.utils.batchprocessors.html | 1 - .../1.0-dev/api/evennia.utils.containers.html | 1 - docs/1.0-dev/api/evennia.utils.create.html | 1 - .../api/evennia.utils.dbserialize.html | 1 - docs/1.0-dev/api/evennia.utils.eveditor.html | 5 +- docs/1.0-dev/api/evennia.utils.evform.html | 1 - docs/1.0-dev/api/evennia.utils.evmenu.html | 5 +- docs/1.0-dev/api/evennia.utils.evmore.html | 5 +- docs/1.0-dev/api/evennia.utils.evtable.html | 1 - .../1.0-dev/api/evennia.utils.funcparser.html | 1 - docs/1.0-dev/api/evennia.utils.gametime.html | 1 - docs/1.0-dev/api/evennia.utils.html | 1 - docs/1.0-dev/api/evennia.utils.idmapper.html | 1 - .../api/evennia.utils.idmapper.manager.html | 1 - .../api/evennia.utils.idmapper.models.html | 1 - .../api/evennia.utils.idmapper.tests.html | 1 - docs/1.0-dev/api/evennia.utils.logger.html | 3 +- .../api/evennia.utils.optionclasses.html | 1 - .../api/evennia.utils.optionhandler.html | 1 - .../api/evennia.utils.picklefield.html | 1 - docs/1.0-dev/api/evennia.utils.search.html | 1 - .../api/evennia.utils.test_resources.html | 1 - docs/1.0-dev/api/evennia.utils.text2html.html | 1 - docs/1.0-dev/api/evennia.utils.utils.html | 1 - .../api/evennia.utils.validatorfuncs.html | 1 - ...nnia.utils.verb_conjugation.conjugate.html | 1 - .../api/evennia.utils.verb_conjugation.html | 1 - ...ennia.utils.verb_conjugation.pronouns.html | 1 - .../evennia.utils.verb_conjugation.tests.html | 1 - .../api/evennia.web.admin.accounts.html | 1 - .../api/evennia.web.admin.attributes.html | 1 - docs/1.0-dev/api/evennia.web.admin.comms.html | 1 - .../api/evennia.web.admin.frontpage.html | 1 - docs/1.0-dev/api/evennia.web.admin.help.html | 1 - docs/1.0-dev/api/evennia.web.admin.html | 1 - .../api/evennia.web.admin.objects.html | 1 - .../api/evennia.web.admin.scripts.html | 1 - .../1.0-dev/api/evennia.web.admin.server.html | 1 - docs/1.0-dev/api/evennia.web.admin.tags.html | 1 - docs/1.0-dev/api/evennia.web.admin.urls.html | 1 - docs/1.0-dev/api/evennia.web.admin.utils.html | 1 - docs/1.0-dev/api/evennia.web.api.filters.html | 3 +- docs/1.0-dev/api/evennia.web.api.html | 1 - .../api/evennia.web.api.permissions.html | 1 - docs/1.0-dev/api/evennia.web.api.root.html | 1 - .../api/evennia.web.api.serializers.html | 1 - docs/1.0-dev/api/evennia.web.api.tests.html | 1 - docs/1.0-dev/api/evennia.web.api.urls.html | 1 - docs/1.0-dev/api/evennia.web.api.views.html | 1 - docs/1.0-dev/api/evennia.web.html | 1 - .../evennia.web.templatetags.addclass.html | 1 - .../1.0-dev/api/evennia.web.templatetags.html | 1 - docs/1.0-dev/api/evennia.web.urls.html | 1 - .../api/evennia.web.utils.adminsite.html | 1 - .../api/evennia.web.utils.backends.html | 1 - .../evennia.web.utils.general_context.html | 1 - docs/1.0-dev/api/evennia.web.utils.html | 1 - .../api/evennia.web.utils.middleware.html | 1 - docs/1.0-dev/api/evennia.web.utils.tests.html | 1 - docs/1.0-dev/api/evennia.web.webclient.html | 1 - .../api/evennia.web.webclient.urls.html | 1 - .../api/evennia.web.webclient.views.html | 1 - .../api/evennia.web.website.forms.html | 1 - docs/1.0-dev/api/evennia.web.website.html | 1 - .../api/evennia.web.website.tests.html | 1 - .../1.0-dev/api/evennia.web.website.urls.html | 1 - .../evennia.web.website.views.accounts.html | 1 - .../evennia.web.website.views.channels.html | 1 - .../evennia.web.website.views.characters.html | 9 +- .../api/evennia.web.website.views.errors.html | 1 - .../api/evennia.web.website.views.help.html | 1 - .../api/evennia.web.website.views.html | 1 - .../api/evennia.web.website.views.index.html | 1 - .../api/evennia.web.website.views.mixins.html | 3 +- .../evennia.web.website.views.objects.html | 11 +- docs/1.0-dev/genindex.html | 724 +- docs/1.0-dev/index.html | 3 +- docs/1.0-dev/objects.inv | Bin 137778 -> 139110 bytes docs/1.0-dev/py-modindex.html | 6 - docs/1.0-dev/search.html | 1 - docs/1.0-dev/searchindex.js | 2 +- docs/1.0-dev/toc.html | 183 +- 1797 files changed, 10075 insertions(+), 330676 deletions(-) delete mode 100644 docs/0.9.5/.buildinfo delete mode 100644 docs/0.9.5/.nojekyll delete mode 100644 docs/0.9.5/A-voice-operated-elevator-using-events.html delete mode 100644 docs/0.9.5/API-refactoring.html delete mode 100644 docs/0.9.5/Accounts.html delete mode 100644 docs/0.9.5/Add-a-simple-new-web-page.html delete mode 100644 docs/0.9.5/Add-a-wiki-on-your-website.html delete mode 100644 docs/0.9.5/Adding-Command-Tutorial.html delete mode 100644 docs/0.9.5/Adding-Object-Typeclass-Tutorial.html delete mode 100644 docs/0.9.5/Administrative-Docs.html delete mode 100644 docs/0.9.5/Apache-Config.html delete mode 100644 docs/0.9.5/Arxcode-installing-help.html delete mode 100644 docs/0.9.5/Async-Process.html delete mode 100644 docs/0.9.5/Attributes.html delete mode 100644 docs/0.9.5/Banning.html delete mode 100644 docs/0.9.5/Batch-Code-Processor.html delete mode 100644 docs/0.9.5/Batch-Command-Processor.html delete mode 100644 docs/0.9.5/Batch-Processors.html delete mode 100644 docs/0.9.5/Bootstrap-&-Evennia.html delete mode 100644 docs/0.9.5/Bootstrap-Components-and-Utilities.html delete mode 100644 docs/0.9.5/Builder-Docs.html delete mode 100644 docs/0.9.5/Building-Permissions.html delete mode 100644 docs/0.9.5/Building-Quickstart.html delete mode 100644 docs/0.9.5/Building-a-mech-tutorial.html delete mode 100644 docs/0.9.5/Building-menus.html delete mode 100644 docs/0.9.5/Choosing-An-SQL-Server.html delete mode 100644 docs/0.9.5/Client-Support-Grid.html delete mode 100644 docs/0.9.5/Coding-FAQ.html delete mode 100644 docs/0.9.5/Coding-Introduction.html delete mode 100644 docs/0.9.5/Coding-Utils.html delete mode 100644 docs/0.9.5/Command-Cooldown.html delete mode 100644 docs/0.9.5/Command-Duration.html delete mode 100644 docs/0.9.5/Command-Prompt.html delete mode 100644 docs/0.9.5/Command-Sets.html delete mode 100644 docs/0.9.5/Command-System.html delete mode 100644 docs/0.9.5/Commands.html delete mode 100644 docs/0.9.5/Communications.html delete mode 100644 docs/0.9.5/Connection-Screen.html delete mode 100644 docs/0.9.5/Continuous-Integration.html delete mode 100644 docs/0.9.5/Contributing-Docs.html delete mode 100644 docs/0.9.5/Contributing.html delete mode 100644 docs/0.9.5/Coordinates.html delete mode 100644 docs/0.9.5/Custom-Protocols.html delete mode 100644 docs/0.9.5/Customize-channels.html delete mode 100644 docs/0.9.5/Debugging.html delete mode 100644 docs/0.9.5/Default-Commands.html delete mode 100644 docs/0.9.5/Default-Exit-Errors.html delete mode 100644 docs/0.9.5/Developer-Central.html delete mode 100644 docs/0.9.5/Dialogues-in-events.html delete mode 100644 docs/0.9.5/Directory-Overview.html delete mode 100644 docs/0.9.5/Docs-refactoring.html delete mode 100644 docs/0.9.5/Dynamic-In-Game-Map.html delete mode 100644 docs/0.9.5/EvEditor.html delete mode 100644 docs/0.9.5/EvMenu.html delete mode 100644 docs/0.9.5/EvMore.html delete mode 100644 docs/0.9.5/Evennia-API.html delete mode 100644 docs/0.9.5/Evennia-Game-Index.html delete mode 100644 docs/0.9.5/Evennia-Introduction.html delete mode 100644 docs/0.9.5/Evennia-for-Diku-Users.html delete mode 100644 docs/0.9.5/Evennia-for-MUSH-Users.html delete mode 100644 docs/0.9.5/Evennia-for-roleplaying-sessions.html delete mode 100644 docs/0.9.5/Execute-Python-Code.html delete mode 100644 docs/0.9.5/First-Steps-Coding.html delete mode 100644 docs/0.9.5/Game-Planning.html delete mode 100644 docs/0.9.5/Gametime-Tutorial.html delete mode 100644 docs/0.9.5/Getting-Started.html delete mode 100644 docs/0.9.5/Glossary.html delete mode 100644 docs/0.9.5/Grapevine.html delete mode 100644 docs/0.9.5/Guest-Logins.html delete mode 100644 docs/0.9.5/HAProxy-Config.html delete mode 100644 docs/0.9.5/Help-System-Tutorial.html delete mode 100644 docs/0.9.5/Help-System.html delete mode 100644 docs/0.9.5/How-To-Get-And-Give-Help.html delete mode 100644 docs/0.9.5/How-to-connect-Evennia-to-Twitter.html delete mode 100644 docs/0.9.5/IRC.html delete mode 100644 docs/0.9.5/Implementing-a-game-rule-system.html delete mode 100644 docs/0.9.5/Inputfuncs.html delete mode 100644 docs/0.9.5/Installing-on-Android.html delete mode 100644 docs/0.9.5/Internationalization.html delete mode 100644 docs/0.9.5/Learn-Python-for-Evennia-The-Hard-Way.html delete mode 100644 docs/0.9.5/Licensing.html delete mode 100644 docs/0.9.5/Links.html delete mode 100644 docs/0.9.5/Locks.html delete mode 100644 docs/0.9.5/Manually-Configuring-Color.html delete mode 100644 docs/0.9.5/Mass-and-weight-for-objects.html delete mode 100644 docs/0.9.5/Messagepath.html delete mode 100644 docs/0.9.5/MonitorHandler.html delete mode 100644 docs/0.9.5/NPC-shop-Tutorial.html delete mode 100644 docs/0.9.5/New-Models.html delete mode 100644 docs/0.9.5/Nicks.html delete mode 100644 docs/0.9.5/OOB.html delete mode 100644 docs/0.9.5/Objects.html delete mode 100644 docs/0.9.5/Online-Setup.html delete mode 100644 docs/0.9.5/Parsing-command-arguments,-theory-and-best-practices.html delete mode 100644 docs/0.9.5/Portal-And-Server.html delete mode 100644 docs/0.9.5/Profiling.html delete mode 100644 docs/0.9.5/Python-3.html delete mode 100644 docs/0.9.5/Python-basic-introduction.html delete mode 100644 docs/0.9.5/Python-basic-tutorial-part-two.html delete mode 100644 docs/0.9.5/Quirks.html delete mode 100644 docs/0.9.5/RSS.html delete mode 100644 docs/0.9.5/Roadmap.html delete mode 100644 docs/0.9.5/Running-Evennia-in-Docker.html delete mode 100644 docs/0.9.5/Screenshot.html delete mode 100644 docs/0.9.5/Scripts.html delete mode 100644 docs/0.9.5/Security.html delete mode 100644 docs/0.9.5/Server-Conf.html delete mode 100644 docs/0.9.5/Sessions.html delete mode 100644 docs/0.9.5/Setting-up-PyCharm.html delete mode 100644 docs/0.9.5/Signals.html delete mode 100644 docs/0.9.5/Soft-Code.html delete mode 100644 docs/0.9.5/Spawner-and-Prototypes.html delete mode 100644 docs/0.9.5/Start-Stop-Reload.html delete mode 100644 docs/0.9.5/Static-In-Game-Map.html delete mode 100644 docs/0.9.5/Tags.html delete mode 100644 docs/0.9.5/Text-Encodings.html delete mode 100644 docs/0.9.5/TextTags.html delete mode 100644 docs/0.9.5/TickerHandler.html delete mode 100644 docs/0.9.5/Turn-based-Combat-System.html delete mode 100644 docs/0.9.5/Tutorial-Aggressive-NPCs.html delete mode 100644 docs/0.9.5/Tutorial-NPCs-listening.html delete mode 100644 docs/0.9.5/Tutorial-Searching-For-Objects.html delete mode 100644 docs/0.9.5/Tutorial-Tweeting-Game-Stats.html delete mode 100644 docs/0.9.5/Tutorial-Vehicles.html delete mode 100644 docs/0.9.5/Tutorial-World-Introduction.html delete mode 100644 docs/0.9.5/Tutorial-for-basic-MUSH-like-game.html delete mode 100644 docs/0.9.5/Tutorials.html delete mode 100644 docs/0.9.5/Typeclasses.html delete mode 100644 docs/0.9.5/Understanding-Color-Tags.html delete mode 100644 docs/0.9.5/Unit-Testing.html delete mode 100644 docs/0.9.5/Updating-Your-Game.html delete mode 100644 docs/0.9.5/Using-MUX-as-a-Standard.html delete mode 100644 docs/0.9.5/Using-Travis.html delete mode 100644 docs/0.9.5/Version-Control.html delete mode 100644 docs/0.9.5/Weather-Tutorial.html delete mode 100644 docs/0.9.5/Web-Character-Generation.html delete mode 100644 docs/0.9.5/Web-Character-View-Tutorial.html delete mode 100644 docs/0.9.5/Web-Features.html delete mode 100644 docs/0.9.5/Web-Tutorial.html delete mode 100644 docs/0.9.5/Webclient-brainstorm.html delete mode 100644 docs/0.9.5/Webclient.html delete mode 100644 docs/0.9.5/Wiki-Index.html delete mode 100644 docs/0.9.5/Zones.html delete mode 100644 docs/0.9.5/_modules/django/conf.html delete mode 100644 docs/0.9.5/_modules/django/db/models/fields/related_descriptors.html delete mode 100644 docs/0.9.5/_modules/django/db/models/manager.html delete mode 100644 docs/0.9.5/_modules/django/db/models/query_utils.html delete mode 100644 docs/0.9.5/_modules/django/utils/functional.html delete mode 100644 docs/0.9.5/_modules/evennia.html delete mode 100644 docs/0.9.5/_modules/evennia/accounts/accounts.html delete mode 100644 docs/0.9.5/_modules/evennia/accounts/admin.html delete mode 100644 docs/0.9.5/_modules/evennia/accounts/bots.html delete mode 100644 docs/0.9.5/_modules/evennia/accounts/manager.html delete mode 100644 docs/0.9.5/_modules/evennia/accounts/models.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/cmdhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/cmdparser.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/cmdset.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/cmdsethandler.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/command.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/account.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/admin.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/batchprocess.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/building.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/cmdset_account.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/cmdset_character.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/cmdset_session.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/cmdset_unloggedin.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/comms.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/general.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/help.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/muxcommand.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/syscommands.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/system.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/commands/default/unloggedin.html delete mode 100644 docs/0.9.5/_modules/evennia/comms/admin.html delete mode 100644 docs/0.9.5/_modules/evennia/comms/channelhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/comms/comms.html delete mode 100644 docs/0.9.5/_modules/evennia/comms/managers.html delete mode 100644 docs/0.9.5/_modules/evennia/comms/models.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/barter.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/building_menu.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/chargen.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/clothing.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/custom_gametime.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/dice.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/email_login.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/extended_room.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/fieldfill.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/gendersub.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/health_bar.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/ingame_python/callbackhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/ingame_python/commands.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/ingame_python/eventfuncs.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/ingame_python/scripts.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/ingame_python/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/ingame_python/utils.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/mail.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/multidescer.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/puzzles.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/random_string_generator.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/rplanguage.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/rpsystem.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/security/auditing/outputs.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/security/auditing/server.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/security/auditing/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/simpledoor.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/slow_exit.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/talking_npc.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tree_select.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_basic.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_equip.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_items.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_magic.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_range.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_examples/bodyfunctions.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_examples/cmdset_red_button.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_examples/red_button.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_examples/red_button_scripts.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_examples/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_world/intro_menu.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_world/mob.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_world/objects.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/tutorial_world/rooms.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/unixcommand.html delete mode 100644 docs/0.9.5/_modules/evennia/contrib/wilderness.html delete mode 100644 docs/0.9.5/_modules/evennia/help/admin.html delete mode 100644 docs/0.9.5/_modules/evennia/help/manager.html delete mode 100644 docs/0.9.5/_modules/evennia/help/models.html delete mode 100644 docs/0.9.5/_modules/evennia/locks/lockfuncs.html delete mode 100644 docs/0.9.5/_modules/evennia/locks/lockhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/objects/admin.html delete mode 100644 docs/0.9.5/_modules/evennia/objects/manager.html delete mode 100644 docs/0.9.5/_modules/evennia/objects/models.html delete mode 100644 docs/0.9.5/_modules/evennia/objects/objects.html delete mode 100644 docs/0.9.5/_modules/evennia/prototypes/menus.html delete mode 100644 docs/0.9.5/_modules/evennia/prototypes/protfuncs.html delete mode 100644 docs/0.9.5/_modules/evennia/prototypes/prototypes.html delete mode 100644 docs/0.9.5/_modules/evennia/prototypes/spawner.html delete mode 100644 docs/0.9.5/_modules/evennia/scripts/admin.html delete mode 100644 docs/0.9.5/_modules/evennia/scripts/manager.html delete mode 100644 docs/0.9.5/_modules/evennia/scripts/models.html delete mode 100644 docs/0.9.5/_modules/evennia/scripts/monitorhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/scripts/scripthandler.html delete mode 100644 docs/0.9.5/_modules/evennia/scripts/scripts.html delete mode 100644 docs/0.9.5/_modules/evennia/scripts/taskhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/scripts/tickerhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/server/admin.html delete mode 100644 docs/0.9.5/_modules/evennia/server/amp_client.html delete mode 100644 docs/0.9.5/_modules/evennia/server/connection_wizard.html delete mode 100644 docs/0.9.5/_modules/evennia/server/deprecations.html delete mode 100644 docs/0.9.5/_modules/evennia/server/evennia_launcher.html delete mode 100644 docs/0.9.5/_modules/evennia/server/game_index_client/client.html delete mode 100644 docs/0.9.5/_modules/evennia/server/game_index_client/service.html delete mode 100644 docs/0.9.5/_modules/evennia/server/initial_setup.html delete mode 100644 docs/0.9.5/_modules/evennia/server/inputfuncs.html delete mode 100644 docs/0.9.5/_modules/evennia/server/manager.html delete mode 100644 docs/0.9.5/_modules/evennia/server/models.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/amp.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/amp_server.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/grapevine.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/irc.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/mccp.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/mssp.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/mxp.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/naws.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/portal.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/portalsessionhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/rss.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/ssh.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/ssl.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/suppress_ga.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/telnet.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/telnet_oob.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/telnet_ssl.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/ttype.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/webclient.html delete mode 100644 docs/0.9.5/_modules/evennia/server/portal/webclient_ajax.html delete mode 100644 docs/0.9.5/_modules/evennia/server/profiling/dummyrunner.html delete mode 100644 docs/0.9.5/_modules/evennia/server/profiling/dummyrunner_settings.html delete mode 100644 docs/0.9.5/_modules/evennia/server/profiling/memplot.html delete mode 100644 docs/0.9.5/_modules/evennia/server/profiling/test_queries.html delete mode 100644 docs/0.9.5/_modules/evennia/server/profiling/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/server/profiling/timetrace.html delete mode 100644 docs/0.9.5/_modules/evennia/server/server.html delete mode 100644 docs/0.9.5/_modules/evennia/server/serversession.html delete mode 100644 docs/0.9.5/_modules/evennia/server/session.html delete mode 100644 docs/0.9.5/_modules/evennia/server/sessionhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/server/throttle.html delete mode 100644 docs/0.9.5/_modules/evennia/server/validators.html delete mode 100644 docs/0.9.5/_modules/evennia/server/webserver.html delete mode 100644 docs/0.9.5/_modules/evennia/typeclasses/admin.html delete mode 100644 docs/0.9.5/_modules/evennia/typeclasses/attributes.html delete mode 100644 docs/0.9.5/_modules/evennia/typeclasses/managers.html delete mode 100644 docs/0.9.5/_modules/evennia/typeclasses/models.html delete mode 100644 docs/0.9.5/_modules/evennia/typeclasses/tags.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/ansi.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/batchprocessors.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/containers.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/create.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/dbserialize.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/eveditor.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/evform.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/evmenu.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/evmore.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/evtable.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/gametime.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/idmapper/manager.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/idmapper/models.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/idmapper/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/inlinefuncs.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/logger.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/optionclasses.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/optionhandler.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/picklefield.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/search.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/test_resources.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/text2html.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/utils.html delete mode 100644 docs/0.9.5/_modules/evennia/utils/validatorfuncs.html delete mode 100644 docs/0.9.5/_modules/evennia/web/utils/backends.html delete mode 100644 docs/0.9.5/_modules/evennia/web/utils/general_context.html delete mode 100644 docs/0.9.5/_modules/evennia/web/utils/middleware.html delete mode 100644 docs/0.9.5/_modules/evennia/web/utils/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/web/webclient/views.html delete mode 100644 docs/0.9.5/_modules/evennia/web/website/forms.html delete mode 100644 docs/0.9.5/_modules/evennia/web/website/templatetags/addclass.html delete mode 100644 docs/0.9.5/_modules/evennia/web/website/tests.html delete mode 100644 docs/0.9.5/_modules/evennia/web/website/views.html delete mode 100644 docs/0.9.5/_modules/functools.html delete mode 100644 docs/0.9.5/_modules/index.html delete mode 100644 docs/0.9.5/_sources/A-voice-operated-elevator-using-events.md.txt delete mode 100644 docs/0.9.5/_sources/API-refactoring.md.txt delete mode 100644 docs/0.9.5/_sources/Accounts.md.txt delete mode 100644 docs/0.9.5/_sources/Add-a-simple-new-web-page.md.txt delete mode 100644 docs/0.9.5/_sources/Add-a-wiki-on-your-website.md.txt delete mode 100644 docs/0.9.5/_sources/Adding-Command-Tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/Adding-Object-Typeclass-Tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/Administrative-Docs.md.txt delete mode 100644 docs/0.9.5/_sources/Apache-Config.md.txt delete mode 100644 docs/0.9.5/_sources/Arxcode-installing-help.md.txt delete mode 100644 docs/0.9.5/_sources/Async-Process.md.txt delete mode 100644 docs/0.9.5/_sources/Attributes.md.txt delete mode 100644 docs/0.9.5/_sources/Banning.md.txt delete mode 100644 docs/0.9.5/_sources/Batch-Code-Processor.md.txt delete mode 100644 docs/0.9.5/_sources/Batch-Command-Processor.md.txt delete mode 100644 docs/0.9.5/_sources/Batch-Processors.md.txt delete mode 100644 docs/0.9.5/_sources/Bootstrap-&-Evennia.md.txt delete mode 100644 docs/0.9.5/_sources/Bootstrap-Components-and-Utilities.md.txt delete mode 100644 docs/0.9.5/_sources/Builder-Docs.md.txt delete mode 100644 docs/0.9.5/_sources/Building-Permissions.md.txt delete mode 100644 docs/0.9.5/_sources/Building-Quickstart.md.txt delete mode 100644 docs/0.9.5/_sources/Building-a-mech-tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/Building-menus.md.txt delete mode 100644 docs/0.9.5/_sources/Choosing-An-SQL-Server.md.txt delete mode 100644 docs/0.9.5/_sources/Client-Support-Grid.md.txt delete mode 100644 docs/0.9.5/_sources/Coding-FAQ.md.txt delete mode 100644 docs/0.9.5/_sources/Coding-Introduction.md.txt delete mode 100644 docs/0.9.5/_sources/Coding-Utils.md.txt delete mode 100644 docs/0.9.5/_sources/Command-Cooldown.md.txt delete mode 100644 docs/0.9.5/_sources/Command-Duration.md.txt delete mode 100644 docs/0.9.5/_sources/Command-Prompt.md.txt delete mode 100644 docs/0.9.5/_sources/Command-Sets.md.txt delete mode 100644 docs/0.9.5/_sources/Command-System.md.txt delete mode 100644 docs/0.9.5/_sources/Commands.md.txt delete mode 100644 docs/0.9.5/_sources/Communications.md.txt delete mode 100644 docs/0.9.5/_sources/Connection-Screen.md.txt delete mode 100644 docs/0.9.5/_sources/Continuous-Integration.md.txt delete mode 100644 docs/0.9.5/_sources/Contributing-Docs.md.txt delete mode 100644 docs/0.9.5/_sources/Contributing.md.txt delete mode 100644 docs/0.9.5/_sources/Coordinates.md.txt delete mode 100644 docs/0.9.5/_sources/Custom-Protocols.md.txt delete mode 100644 docs/0.9.5/_sources/Customize-channels.md.txt delete mode 100644 docs/0.9.5/_sources/Debugging.md.txt delete mode 100644 docs/0.9.5/_sources/Default-Commands.md.txt delete mode 100644 docs/0.9.5/_sources/Default-Exit-Errors.md.txt delete mode 100644 docs/0.9.5/_sources/Developer-Central.md.txt delete mode 100644 docs/0.9.5/_sources/Dialogues-in-events.md.txt delete mode 100644 docs/0.9.5/_sources/Directory-Overview.md.txt delete mode 100644 docs/0.9.5/_sources/Docs-refactoring.md.txt delete mode 100644 docs/0.9.5/_sources/Dynamic-In-Game-Map.md.txt delete mode 100644 docs/0.9.5/_sources/EvEditor.md.txt delete mode 100644 docs/0.9.5/_sources/EvMenu.md.txt delete mode 100644 docs/0.9.5/_sources/EvMore.md.txt delete mode 100644 docs/0.9.5/_sources/Evennia-API.md.txt delete mode 100644 docs/0.9.5/_sources/Evennia-Game-Index.md.txt delete mode 100644 docs/0.9.5/_sources/Evennia-Introduction.md.txt delete mode 100644 docs/0.9.5/_sources/Evennia-for-Diku-Users.md.txt delete mode 100644 docs/0.9.5/_sources/Evennia-for-MUSH-Users.md.txt delete mode 100644 docs/0.9.5/_sources/Evennia-for-roleplaying-sessions.md.txt delete mode 100644 docs/0.9.5/_sources/Execute-Python-Code.md.txt delete mode 100644 docs/0.9.5/_sources/First-Steps-Coding.md.txt delete mode 100644 docs/0.9.5/_sources/Game-Planning.md.txt delete mode 100644 docs/0.9.5/_sources/Gametime-Tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/Getting-Started.md.txt delete mode 100644 docs/0.9.5/_sources/Glossary.md.txt delete mode 100644 docs/0.9.5/_sources/Grapevine.md.txt delete mode 100644 docs/0.9.5/_sources/Guest-Logins.md.txt delete mode 100644 docs/0.9.5/_sources/HAProxy-Config.md.txt delete mode 100644 docs/0.9.5/_sources/Help-System-Tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/Help-System.md.txt delete mode 100644 docs/0.9.5/_sources/How-To-Get-And-Give-Help.md.txt delete mode 100644 docs/0.9.5/_sources/How-to-connect-Evennia-to-Twitter.md.txt delete mode 100644 docs/0.9.5/_sources/IRC.md.txt delete mode 100644 docs/0.9.5/_sources/Implementing-a-game-rule-system.md.txt delete mode 100644 docs/0.9.5/_sources/Inputfuncs.md.txt delete mode 100644 docs/0.9.5/_sources/Installing-on-Android.md.txt delete mode 100644 docs/0.9.5/_sources/Internationalization.md.txt delete mode 100644 docs/0.9.5/_sources/Learn-Python-for-Evennia-The-Hard-Way.md.txt delete mode 100644 docs/0.9.5/_sources/Licensing.md.txt delete mode 100644 docs/0.9.5/_sources/Links.md.txt delete mode 100644 docs/0.9.5/_sources/Locks.md.txt delete mode 100644 docs/0.9.5/_sources/Manually-Configuring-Color.md.txt delete mode 100644 docs/0.9.5/_sources/Mass-and-weight-for-objects.md.txt delete mode 100644 docs/0.9.5/_sources/Messagepath.md.txt delete mode 100644 docs/0.9.5/_sources/MonitorHandler.md.txt delete mode 100644 docs/0.9.5/_sources/NPC-shop-Tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/New-Models.md.txt delete mode 100644 docs/0.9.5/_sources/Nicks.md.txt delete mode 100644 docs/0.9.5/_sources/OOB.md.txt delete mode 100644 docs/0.9.5/_sources/Objects.md.txt delete mode 100644 docs/0.9.5/_sources/Online-Setup.md.txt delete mode 100644 docs/0.9.5/_sources/Parsing-command-arguments,-theory-and-best-practices.md.txt delete mode 100644 docs/0.9.5/_sources/Portal-And-Server.md.txt delete mode 100644 docs/0.9.5/_sources/Profiling.md.txt delete mode 100644 docs/0.9.5/_sources/Python-3.md.txt delete mode 100644 docs/0.9.5/_sources/Python-basic-introduction.md.txt delete mode 100644 docs/0.9.5/_sources/Python-basic-tutorial-part-two.md.txt delete mode 100644 docs/0.9.5/_sources/Quirks.md.txt delete mode 100644 docs/0.9.5/_sources/RSS.md.txt delete mode 100644 docs/0.9.5/_sources/Roadmap.md.txt delete mode 100644 docs/0.9.5/_sources/Running-Evennia-in-Docker.md.txt delete mode 100644 docs/0.9.5/_sources/Screenshot.md.txt delete mode 100644 docs/0.9.5/_sources/Scripts.md.txt delete mode 100644 docs/0.9.5/_sources/Security.md.txt delete mode 100644 docs/0.9.5/_sources/Server-Conf.md.txt delete mode 100644 docs/0.9.5/_sources/Sessions.md.txt delete mode 100644 docs/0.9.5/_sources/Setting-up-PyCharm.md.txt delete mode 100644 docs/0.9.5/_sources/Signals.md.txt delete mode 100644 docs/0.9.5/_sources/Soft-Code.md.txt delete mode 100644 docs/0.9.5/_sources/Spawner-and-Prototypes.md.txt delete mode 100644 docs/0.9.5/_sources/Start-Stop-Reload.md.txt delete mode 100644 docs/0.9.5/_sources/Static-In-Game-Map.md.txt delete mode 100644 docs/0.9.5/_sources/Tags.md.txt delete mode 100644 docs/0.9.5/_sources/Text-Encodings.md.txt delete mode 100644 docs/0.9.5/_sources/TextTags.md.txt delete mode 100644 docs/0.9.5/_sources/TickerHandler.md.txt delete mode 100644 docs/0.9.5/_sources/Turn-based-Combat-System.md.txt delete mode 100644 docs/0.9.5/_sources/Tutorial-Aggressive-NPCs.md.txt delete mode 100644 docs/0.9.5/_sources/Tutorial-NPCs-listening.md.txt delete mode 100644 docs/0.9.5/_sources/Tutorial-Searching-For-Objects.md.txt delete mode 100644 docs/0.9.5/_sources/Tutorial-Tweeting-Game-Stats.md.txt delete mode 100644 docs/0.9.5/_sources/Tutorial-Vehicles.md.txt delete mode 100644 docs/0.9.5/_sources/Tutorial-World-Introduction.md.txt delete mode 100644 docs/0.9.5/_sources/Tutorial-for-basic-MUSH-like-game.md.txt delete mode 100644 docs/0.9.5/_sources/Tutorials.md.txt delete mode 100644 docs/0.9.5/_sources/Typeclasses.md.txt delete mode 100644 docs/0.9.5/_sources/Understanding-Color-Tags.md.txt delete mode 100644 docs/0.9.5/_sources/Unit-Testing.md.txt delete mode 100644 docs/0.9.5/_sources/Updating-Your-Game.md.txt delete mode 100644 docs/0.9.5/_sources/Using-MUX-as-a-Standard.md.txt delete mode 100644 docs/0.9.5/_sources/Using-Travis.md.txt delete mode 100644 docs/0.9.5/_sources/Version-Control.md.txt delete mode 100644 docs/0.9.5/_sources/Weather-Tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/Web-Character-Generation.md.txt delete mode 100644 docs/0.9.5/_sources/Web-Character-View-Tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/Web-Features.md.txt delete mode 100644 docs/0.9.5/_sources/Web-Tutorial.md.txt delete mode 100644 docs/0.9.5/_sources/Webclient-brainstorm.md.txt delete mode 100644 docs/0.9.5/_sources/Webclient.md.txt delete mode 100644 docs/0.9.5/_sources/Wiki-Index.md.txt delete mode 100644 docs/0.9.5/_sources/Zones.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia-api.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.accounts.accounts.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.accounts.admin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.accounts.bots.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.accounts.manager.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.accounts.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.accounts.models.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.cmdhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.cmdparser.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.cmdset.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.cmdsethandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.command.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.account.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.admin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.batchprocess.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.building.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.cmdset_account.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.cmdset_character.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.cmdset_session.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.cmdset_unloggedin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.comms.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.general.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.help.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.muxcommand.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.syscommands.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.system.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.default.unloggedin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.commands.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.comms.admin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.comms.channelhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.comms.comms.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.comms.managers.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.comms.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.comms.models.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.barter.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.building_menu.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.chargen.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.clothing.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.color_markups.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.custom_gametime.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.dice.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.email_login.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.extended_room.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.fieldfill.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.gendersub.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.health_bar.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.ingame_python.callbackhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.ingame_python.commands.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.ingame_python.eventfuncs.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.ingame_python.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.ingame_python.scripts.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.ingame_python.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.ingame_python.typeclasses.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.ingame_python.utils.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.mail.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.mapbuilder.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.menu_login.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.multidescer.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.puzzles.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.random_string_generator.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.rplanguage.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.rpsystem.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.security.auditing.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.security.auditing.outputs.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.security.auditing.server.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.security.auditing.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.security.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.simpledoor.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.slow_exit.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.talking_npc.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tree_select.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.turnbattle.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.turnbattle.tb_basic.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.turnbattle.tb_equip.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.turnbattle.tb_items.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.turnbattle.tb_magic.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.turnbattle.tb_range.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_examples.bodyfunctions.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_examples.cmdset_red_button.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_examples.example_batch_code.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_examples.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_examples.red_button.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_examples.red_button_scripts.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_examples.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_world.intro_menu.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_world.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_world.mob.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_world.objects.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.tutorial_world.rooms.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.unixcommand.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.contrib.wilderness.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.help.admin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.help.manager.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.help.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.help.models.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.locks.lockfuncs.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.locks.lockhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.locks.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.objects.admin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.objects.manager.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.objects.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.objects.models.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.objects.objects.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.prototypes.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.prototypes.menus.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.prototypes.protfuncs.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.prototypes.prototypes.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.prototypes.spawner.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.admin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.manager.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.models.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.monitorhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.scripthandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.scripts.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.taskhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.scripts.tickerhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.admin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.amp_client.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.connection_wizard.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.deprecations.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.evennia_launcher.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.game_index_client.client.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.game_index_client.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.game_index_client.service.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.initial_setup.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.inputfuncs.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.manager.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.models.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.amp.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.amp_server.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.grapevine.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.irc.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.mccp.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.mssp.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.mxp.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.naws.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.portal.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.portalsessionhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.rss.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.ssh.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.ssl.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.suppress_ga.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.telnet.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.telnet_oob.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.telnet_ssl.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.ttype.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.webclient.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.portal.webclient_ajax.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.profiling.dummyrunner.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.profiling.dummyrunner_settings.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.profiling.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.profiling.memplot.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.profiling.settings_mixin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.profiling.test_queries.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.profiling.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.profiling.timetrace.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.server.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.serversession.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.session.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.sessionhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.signals.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.throttle.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.validators.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.server.webserver.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.settings_default.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.typeclasses.admin.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.typeclasses.attributes.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.typeclasses.managers.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.typeclasses.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.typeclasses.models.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.typeclasses.tags.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.ansi.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.batchprocessors.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.containers.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.create.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.dbserialize.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.eveditor.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.evform.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.evmenu.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.evmore.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.evtable.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.gametime.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.idmapper.manager.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.idmapper.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.idmapper.models.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.idmapper.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.inlinefuncs.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.logger.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.optionclasses.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.optionhandler.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.picklefield.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.search.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.test_resources.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.text2html.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.utils.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.utils.validatorfuncs.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.urls.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.utils.backends.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.utils.general_context.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.utils.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.utils.middleware.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.utils.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.webclient.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.webclient.urls.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.webclient.views.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.website.forms.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.website.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.website.templatetags.addclass.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.website.templatetags.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.website.tests.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.website.urls.md.txt delete mode 100644 docs/0.9.5/_sources/api/evennia.web.website.views.md.txt delete mode 100644 docs/0.9.5/_sources/index.md.txt delete mode 100644 docs/0.9.5/_sources/toc.md.txt delete mode 100644 docs/0.9.5/_static/basic.css delete mode 100644 docs/0.9.5/_static/doctools.js delete mode 100644 docs/0.9.5/_static/documentation_options.js delete mode 100644 docs/0.9.5/_static/evennia_logo.png delete mode 100644 docs/0.9.5/_static/favicon.ico delete mode 100644 docs/0.9.5/_static/file.png delete mode 100644 docs/0.9.5/_static/images/evennia_logo.png delete mode 100644 docs/0.9.5/_static/images/favicon.ico delete mode 100644 docs/0.9.5/_static/jquery-3.5.1.js delete mode 100644 docs/0.9.5/_static/jquery.js delete mode 100644 docs/0.9.5/_static/language_data.js delete mode 100644 docs/0.9.5/_static/minus.png delete mode 100644 docs/0.9.5/_static/nature.css delete mode 100644 docs/0.9.5/_static/plus.png delete mode 100644 docs/0.9.5/_static/pygments.css delete mode 100644 docs/0.9.5/_static/searchtools.js delete mode 100644 docs/0.9.5/_static/underscore-1.3.1.js delete mode 100644 docs/0.9.5/_static/underscore.js delete mode 100644 docs/0.9.5/api/evennia-api.html delete mode 100644 docs/0.9.5/api/evennia.accounts.accounts.html delete mode 100644 docs/0.9.5/api/evennia.accounts.admin.html delete mode 100644 docs/0.9.5/api/evennia.accounts.bots.html delete mode 100644 docs/0.9.5/api/evennia.accounts.html delete mode 100644 docs/0.9.5/api/evennia.accounts.manager.html delete mode 100644 docs/0.9.5/api/evennia.accounts.models.html delete mode 100644 docs/0.9.5/api/evennia.commands.cmdhandler.html delete mode 100644 docs/0.9.5/api/evennia.commands.cmdparser.html delete mode 100644 docs/0.9.5/api/evennia.commands.cmdset.html delete mode 100644 docs/0.9.5/api/evennia.commands.cmdsethandler.html delete mode 100644 docs/0.9.5/api/evennia.commands.command.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.account.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.admin.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.batchprocess.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.building.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.cmdset_account.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.cmdset_character.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.cmdset_session.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.cmdset_unloggedin.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.comms.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.general.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.help.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.muxcommand.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.syscommands.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.system.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.tests.html delete mode 100644 docs/0.9.5/api/evennia.commands.default.unloggedin.html delete mode 100644 docs/0.9.5/api/evennia.commands.html delete mode 100644 docs/0.9.5/api/evennia.comms.admin.html delete mode 100644 docs/0.9.5/api/evennia.comms.channelhandler.html delete mode 100644 docs/0.9.5/api/evennia.comms.comms.html delete mode 100644 docs/0.9.5/api/evennia.comms.html delete mode 100644 docs/0.9.5/api/evennia.comms.managers.html delete mode 100644 docs/0.9.5/api/evennia.comms.models.html delete mode 100644 docs/0.9.5/api/evennia.contrib.barter.html delete mode 100644 docs/0.9.5/api/evennia.contrib.building_menu.html delete mode 100644 docs/0.9.5/api/evennia.contrib.chargen.html delete mode 100644 docs/0.9.5/api/evennia.contrib.clothing.html delete mode 100644 docs/0.9.5/api/evennia.contrib.color_markups.html delete mode 100644 docs/0.9.5/api/evennia.contrib.custom_gametime.html delete mode 100644 docs/0.9.5/api/evennia.contrib.dice.html delete mode 100644 docs/0.9.5/api/evennia.contrib.email_login.html delete mode 100644 docs/0.9.5/api/evennia.contrib.extended_room.html delete mode 100644 docs/0.9.5/api/evennia.contrib.fieldfill.html delete mode 100644 docs/0.9.5/api/evennia.contrib.gendersub.html delete mode 100644 docs/0.9.5/api/evennia.contrib.health_bar.html delete mode 100644 docs/0.9.5/api/evennia.contrib.html delete mode 100644 docs/0.9.5/api/evennia.contrib.ingame_python.callbackhandler.html delete mode 100644 docs/0.9.5/api/evennia.contrib.ingame_python.commands.html delete mode 100644 docs/0.9.5/api/evennia.contrib.ingame_python.eventfuncs.html delete mode 100644 docs/0.9.5/api/evennia.contrib.ingame_python.html delete mode 100644 docs/0.9.5/api/evennia.contrib.ingame_python.scripts.html delete mode 100644 docs/0.9.5/api/evennia.contrib.ingame_python.tests.html delete mode 100644 docs/0.9.5/api/evennia.contrib.ingame_python.typeclasses.html delete mode 100644 docs/0.9.5/api/evennia.contrib.ingame_python.utils.html delete mode 100644 docs/0.9.5/api/evennia.contrib.mail.html delete mode 100644 docs/0.9.5/api/evennia.contrib.mapbuilder.html delete mode 100644 docs/0.9.5/api/evennia.contrib.menu_login.html delete mode 100644 docs/0.9.5/api/evennia.contrib.multidescer.html delete mode 100644 docs/0.9.5/api/evennia.contrib.puzzles.html delete mode 100644 docs/0.9.5/api/evennia.contrib.random_string_generator.html delete mode 100644 docs/0.9.5/api/evennia.contrib.rplanguage.html delete mode 100644 docs/0.9.5/api/evennia.contrib.rpsystem.html delete mode 100644 docs/0.9.5/api/evennia.contrib.security.auditing.html delete mode 100644 docs/0.9.5/api/evennia.contrib.security.auditing.outputs.html delete mode 100644 docs/0.9.5/api/evennia.contrib.security.auditing.server.html delete mode 100644 docs/0.9.5/api/evennia.contrib.security.auditing.tests.html delete mode 100644 docs/0.9.5/api/evennia.contrib.security.html delete mode 100644 docs/0.9.5/api/evennia.contrib.simpledoor.html delete mode 100644 docs/0.9.5/api/evennia.contrib.slow_exit.html delete mode 100644 docs/0.9.5/api/evennia.contrib.talking_npc.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tree_select.html delete mode 100644 docs/0.9.5/api/evennia.contrib.turnbattle.html delete mode 100644 docs/0.9.5/api/evennia.contrib.turnbattle.tb_basic.html delete mode 100644 docs/0.9.5/api/evennia.contrib.turnbattle.tb_equip.html delete mode 100644 docs/0.9.5/api/evennia.contrib.turnbattle.tb_items.html delete mode 100644 docs/0.9.5/api/evennia.contrib.turnbattle.tb_magic.html delete mode 100644 docs/0.9.5/api/evennia.contrib.turnbattle.tb_range.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_examples.bodyfunctions.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_examples.cmdset_red_button.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_examples.example_batch_code.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_examples.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button_scripts.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_examples.tests.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_world.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_world.intro_menu.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_world.mob.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html delete mode 100644 docs/0.9.5/api/evennia.contrib.tutorial_world.rooms.html delete mode 100644 docs/0.9.5/api/evennia.contrib.unixcommand.html delete mode 100644 docs/0.9.5/api/evennia.contrib.wilderness.html delete mode 100644 docs/0.9.5/api/evennia.help.admin.html delete mode 100644 docs/0.9.5/api/evennia.help.html delete mode 100644 docs/0.9.5/api/evennia.help.manager.html delete mode 100644 docs/0.9.5/api/evennia.help.models.html delete mode 100644 docs/0.9.5/api/evennia.html delete mode 100644 docs/0.9.5/api/evennia.locks.html delete mode 100644 docs/0.9.5/api/evennia.locks.lockfuncs.html delete mode 100644 docs/0.9.5/api/evennia.locks.lockhandler.html delete mode 100644 docs/0.9.5/api/evennia.objects.admin.html delete mode 100644 docs/0.9.5/api/evennia.objects.html delete mode 100644 docs/0.9.5/api/evennia.objects.manager.html delete mode 100644 docs/0.9.5/api/evennia.objects.models.html delete mode 100644 docs/0.9.5/api/evennia.objects.objects.html delete mode 100644 docs/0.9.5/api/evennia.prototypes.html delete mode 100644 docs/0.9.5/api/evennia.prototypes.menus.html delete mode 100644 docs/0.9.5/api/evennia.prototypes.protfuncs.html delete mode 100644 docs/0.9.5/api/evennia.prototypes.prototypes.html delete mode 100644 docs/0.9.5/api/evennia.prototypes.spawner.html delete mode 100644 docs/0.9.5/api/evennia.scripts.admin.html delete mode 100644 docs/0.9.5/api/evennia.scripts.html delete mode 100644 docs/0.9.5/api/evennia.scripts.manager.html delete mode 100644 docs/0.9.5/api/evennia.scripts.models.html delete mode 100644 docs/0.9.5/api/evennia.scripts.monitorhandler.html delete mode 100644 docs/0.9.5/api/evennia.scripts.scripthandler.html delete mode 100644 docs/0.9.5/api/evennia.scripts.scripts.html delete mode 100644 docs/0.9.5/api/evennia.scripts.taskhandler.html delete mode 100644 docs/0.9.5/api/evennia.scripts.tickerhandler.html delete mode 100644 docs/0.9.5/api/evennia.server.admin.html delete mode 100644 docs/0.9.5/api/evennia.server.amp_client.html delete mode 100644 docs/0.9.5/api/evennia.server.connection_wizard.html delete mode 100644 docs/0.9.5/api/evennia.server.deprecations.html delete mode 100644 docs/0.9.5/api/evennia.server.evennia_launcher.html delete mode 100644 docs/0.9.5/api/evennia.server.game_index_client.client.html delete mode 100644 docs/0.9.5/api/evennia.server.game_index_client.html delete mode 100644 docs/0.9.5/api/evennia.server.game_index_client.service.html delete mode 100644 docs/0.9.5/api/evennia.server.html delete mode 100644 docs/0.9.5/api/evennia.server.initial_setup.html delete mode 100644 docs/0.9.5/api/evennia.server.inputfuncs.html delete mode 100644 docs/0.9.5/api/evennia.server.manager.html delete mode 100644 docs/0.9.5/api/evennia.server.models.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.amp.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.amp_server.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.grapevine.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.irc.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.mccp.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.mssp.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.mxp.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.naws.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.portal.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.portalsessionhandler.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.rss.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.ssh.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.ssl.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.suppress_ga.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.telnet.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.telnet_oob.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.telnet_ssl.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.tests.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.ttype.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.webclient.html delete mode 100644 docs/0.9.5/api/evennia.server.portal.webclient_ajax.html delete mode 100644 docs/0.9.5/api/evennia.server.profiling.dummyrunner.html delete mode 100644 docs/0.9.5/api/evennia.server.profiling.dummyrunner_settings.html delete mode 100644 docs/0.9.5/api/evennia.server.profiling.html delete mode 100644 docs/0.9.5/api/evennia.server.profiling.memplot.html delete mode 100644 docs/0.9.5/api/evennia.server.profiling.settings_mixin.html delete mode 100644 docs/0.9.5/api/evennia.server.profiling.test_queries.html delete mode 100644 docs/0.9.5/api/evennia.server.profiling.tests.html delete mode 100644 docs/0.9.5/api/evennia.server.profiling.timetrace.html delete mode 100644 docs/0.9.5/api/evennia.server.server.html delete mode 100644 docs/0.9.5/api/evennia.server.serversession.html delete mode 100644 docs/0.9.5/api/evennia.server.session.html delete mode 100644 docs/0.9.5/api/evennia.server.sessionhandler.html delete mode 100644 docs/0.9.5/api/evennia.server.signals.html delete mode 100644 docs/0.9.5/api/evennia.server.throttle.html delete mode 100644 docs/0.9.5/api/evennia.server.validators.html delete mode 100644 docs/0.9.5/api/evennia.server.webserver.html delete mode 100644 docs/0.9.5/api/evennia.settings_default.html delete mode 100644 docs/0.9.5/api/evennia.typeclasses.admin.html delete mode 100644 docs/0.9.5/api/evennia.typeclasses.attributes.html delete mode 100644 docs/0.9.5/api/evennia.typeclasses.html delete mode 100644 docs/0.9.5/api/evennia.typeclasses.managers.html delete mode 100644 docs/0.9.5/api/evennia.typeclasses.models.html delete mode 100644 docs/0.9.5/api/evennia.typeclasses.tags.html delete mode 100644 docs/0.9.5/api/evennia.utils.ansi.html delete mode 100644 docs/0.9.5/api/evennia.utils.batchprocessors.html delete mode 100644 docs/0.9.5/api/evennia.utils.containers.html delete mode 100644 docs/0.9.5/api/evennia.utils.create.html delete mode 100644 docs/0.9.5/api/evennia.utils.dbserialize.html delete mode 100644 docs/0.9.5/api/evennia.utils.eveditor.html delete mode 100644 docs/0.9.5/api/evennia.utils.evform.html delete mode 100644 docs/0.9.5/api/evennia.utils.evmenu.html delete mode 100644 docs/0.9.5/api/evennia.utils.evmore.html delete mode 100644 docs/0.9.5/api/evennia.utils.evtable.html delete mode 100644 docs/0.9.5/api/evennia.utils.gametime.html delete mode 100644 docs/0.9.5/api/evennia.utils.html delete mode 100644 docs/0.9.5/api/evennia.utils.idmapper.html delete mode 100644 docs/0.9.5/api/evennia.utils.idmapper.manager.html delete mode 100644 docs/0.9.5/api/evennia.utils.idmapper.models.html delete mode 100644 docs/0.9.5/api/evennia.utils.idmapper.tests.html delete mode 100644 docs/0.9.5/api/evennia.utils.inlinefuncs.html delete mode 100644 docs/0.9.5/api/evennia.utils.logger.html delete mode 100644 docs/0.9.5/api/evennia.utils.optionclasses.html delete mode 100644 docs/0.9.5/api/evennia.utils.optionhandler.html delete mode 100644 docs/0.9.5/api/evennia.utils.picklefield.html delete mode 100644 docs/0.9.5/api/evennia.utils.search.html delete mode 100644 docs/0.9.5/api/evennia.utils.test_resources.html delete mode 100644 docs/0.9.5/api/evennia.utils.text2html.html delete mode 100644 docs/0.9.5/api/evennia.utils.utils.html delete mode 100644 docs/0.9.5/api/evennia.utils.validatorfuncs.html delete mode 100644 docs/0.9.5/api/evennia.web.html delete mode 100644 docs/0.9.5/api/evennia.web.urls.html delete mode 100644 docs/0.9.5/api/evennia.web.utils.backends.html delete mode 100644 docs/0.9.5/api/evennia.web.utils.general_context.html delete mode 100644 docs/0.9.5/api/evennia.web.utils.html delete mode 100644 docs/0.9.5/api/evennia.web.utils.middleware.html delete mode 100644 docs/0.9.5/api/evennia.web.utils.tests.html delete mode 100644 docs/0.9.5/api/evennia.web.webclient.html delete mode 100644 docs/0.9.5/api/evennia.web.webclient.urls.html delete mode 100644 docs/0.9.5/api/evennia.web.webclient.views.html delete mode 100644 docs/0.9.5/api/evennia.web.website.forms.html delete mode 100644 docs/0.9.5/api/evennia.web.website.html delete mode 100644 docs/0.9.5/api/evennia.web.website.templatetags.addclass.html delete mode 100644 docs/0.9.5/api/evennia.web.website.templatetags.html delete mode 100644 docs/0.9.5/api/evennia.web.website.tests.html delete mode 100644 docs/0.9.5/api/evennia.web.website.urls.html delete mode 100644 docs/0.9.5/api/evennia.web.website.views.html delete mode 100644 docs/0.9.5/genindex.html delete mode 100644 docs/0.9.5/index.html delete mode 100644 docs/0.9.5/objects.inv delete mode 100644 docs/0.9.5/py-modindex.html delete mode 100644 docs/0.9.5/search.html delete mode 100644 docs/0.9.5/searchindex.js delete mode 100644 docs/0.9.5/toc.html rename docs/1.0-dev/Howto/Starting/Part1/{Tutorial-World-Introduction.html => Tutorial-World.html} (89%) create mode 100644 docs/1.0-dev/Setup/Changelog.html delete mode 100644 docs/1.0-dev/Setup/Extended-Installation.html rename docs/1.0-dev/Setup/{Installing-on-Android.html => Installation-Android.html} (95%) rename docs/1.0-dev/Setup/{Running-Evennia-in-Docker.html => Installation-Docker.html} (98%) create mode 100644 docs/1.0-dev/Setup/Installation-Git.html rename docs/1.0-dev/Setup/{Settings-File.html => Installation-Non-Interactive.html} (67%) create mode 100644 docs/1.0-dev/Setup/Installation-Troubleshooting.html create mode 100644 docs/1.0-dev/Setup/Installation-Upgrade.html rename docs/1.0-dev/Setup/{Setup-Quickstart.html => Installation.html} (59%) create mode 100644 docs/1.0-dev/Setup/Settings-Default.html delete mode 100644 docs/1.0-dev/_modules/django/utils/deconstruct.html delete mode 100644 docs/1.0-dev/_modules/evennia/contrib/base_systems/awsstorage/aws_s3_cdn.html create mode 100644 docs/1.0-dev/_modules/re.html rename docs/1.0-dev/_sources/Howto/Starting/Part1/{Tutorial-World-Introduction.md.txt => Tutorial-World.md.txt} (100%) create mode 100644 docs/1.0-dev/_sources/Setup/Changelog.md.txt delete mode 100644 docs/1.0-dev/_sources/Setup/Extended-Installation.md.txt rename docs/1.0-dev/_sources/Setup/{Installing-on-Android.md.txt => Installation-Android.md.txt} (92%) rename docs/1.0-dev/_sources/Setup/{Running-Evennia-in-Docker.md.txt => Installation-Docker.md.txt} (99%) create mode 100644 docs/1.0-dev/_sources/Setup/Installation-Git.md.txt create mode 100644 docs/1.0-dev/_sources/Setup/Installation-Non-Interactive.md.txt create mode 100644 docs/1.0-dev/_sources/Setup/Installation-Troubleshooting.md.txt create mode 100644 docs/1.0-dev/_sources/Setup/Installation-Upgrade.md.txt create mode 100644 docs/1.0-dev/_sources/Setup/Installation.md.txt create mode 100644 docs/1.0-dev/_sources/Setup/Settings-Default.md.txt delete mode 100644 docs/1.0-dev/_sources/Setup/Settings-File.md.txt delete mode 100644 docs/1.0-dev/_sources/Setup/Setup-Quickstart.md.txt diff --git a/docs/0.9.5/.buildinfo b/docs/0.9.5/.buildinfo deleted file mode 100644 index 357056f083..0000000000 --- a/docs/0.9.5/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: adfe33567de484c5d80bc9ea3fdbc24a -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/0.9.5/.nojekyll b/docs/0.9.5/.nojekyll deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/0.9.5/A-voice-operated-elevator-using-events.html b/docs/0.9.5/A-voice-operated-elevator-using-events.html deleted file mode 100644 index b30b5e4b7e..0000000000 --- a/docs/0.9.5/A-voice-operated-elevator-using-events.html +++ /dev/null @@ -1,541 +0,0 @@ - - - - - - - - - A voice operated elevator using events — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

A voice operated elevator using events

- -

This tutorial will walk you through the steps to create a voice-operated elevator, using the in- -game Python -system. -This tutorial assumes the in-game Python system is installed in your game. If it isn’t, you can -follow the installation steps given in the documentation on in-game -Python, and -come back on this tutorial once the system is installed. You do not need to read the entire -documentation, it’s a good reference, but not the easiest way to learn about it. Hence these -tutorials.

-

The in-game Python system allows to run code on individual objects in some situations. You don’t -have to modify the source code to add these features, past the installation. The entire system -makes it easy to add specific features to some objects, but not all.

-
-

What will we try to do?

-
-

In this tutorial, we are going to create a simple voice-operated elevator. In terms of features, we -will:

-
    -
  • Explore events with parameters.

  • -
  • Work on more interesting callbacks.

  • -
  • Learn about chained events.

  • -
  • Play with variable modification in callbacks.

  • -
-
-

Our study case

-

Let’s summarize what we want to achieve first. We would like to create a room that will represent -the inside of our elevator. In this room, a character could just say “1”, “2” or “3”, and the -elevator will start moving. The doors will close and open on the new floor (the exits leading in -and out of the elevator will be modified).

-

We will work on basic features first, and then will adjust some, showing you how easy and powerfully -independent actions can be configured through the in-game Python system.

-
-
-

Creating the rooms and exits we need

-

We’ll create an elevator right in our room (generally called “Limbo”, of ID 2). You could easily -adapt the following instructions if you already have some rooms and exits, of course, just remember -to check the IDs.

-
-

Note: the in-game Python system uses IDs for a lot of things. While it is not mandatory, it is -good practice to know the IDs you have for your callbacks, because it will make manipulation much -quicker. There are other ways to identify objects, but as they depend on many factors, IDs are -usually the safest path in our callbacks.

-
-

Let’s go into limbo (#2) to add our elevator. We’ll add it to the north. To create this room, -in-game you could type:

-
tunnel n = Inside of an elevator
-
-
-

The game should respond by telling you:

-
Created room Inside of an elevator(#3) of type typeclasses.rooms.Room.
-Created Exit from Limbo to Inside of an elevator: north(#4) (n).
-Created Exit back from Inside of an elevator to Limbo: south(#5) (s).
-
-
-

Note the given IDs:

-
    -
  • #2 is limbo, the first room the system created.

  • -
  • #3 is our room inside of an elevator.

  • -
  • #4 is the north exit from Limbo to our elevator.

  • -
  • #5 is the south exit from an elevator to Limbo.

  • -
-

Keep these IDs somewhere for the demonstration. You will shortly see why they are important.

-
-

Why have we created exits to our elevator and back to Limbo? Isn’t the elevator supposed to move?

-
-

It is. But we need to have exits that will represent the way inside the elevator and out. What we -will do, at every floor, will be to change these exits so they become connected to the right room. -You’ll see this process a bit later.

-

We have two more rooms to create: our floor 2 and 3. This time, we’ll use dig, because we don’t -need exits leading there, not yet anyway.

-
dig The second floor
-dig The third floor
-
-
-

Evennia should answer with:

-
Created room The second floor(#6) of type typeclasses.rooms.Room.
-Created room The third floor(#7) of type typeclasses.rooms.Room.
-
-
-

Add these IDs to your list, we will use them too.

-
-
-

Our first callback in the elevator

-

Let’s go to the elevator (you could use tel #3 if you have the same IDs I have).

-

This is our elevator room. It looks a bit empty, feel free to add a prettier description or other -things to decorate it a bit.

-

But what we want now is to be able to say “1”, “2” or “3” and have the elevator move in that -direction.

-

If you have read the previous tutorial about adding dialogues in events, you -may remember what we need to do. If not, here’s a summary: we need to run some code when somebody -speaks in the room. So we need to create a callback (the callback will contain our lines of code). -We just need to know on which event this should be set. You can enter call here to see the -possible events in this room.

-

In the table, you should see the “say” event, which is called when somebody says something in the -room. So we’ll need to add a callback to this event. Don’t worry if you’re a bit lost, just follow -the following steps, the way they connect together will become more obvious.

-
call/add here = say 1, 2, 3
-
-
-
    -
  1. We need to add a callback. A callback contains the code that will be executed at a given time. -So we use the call/add command and switch.

  2. -
  3. here is our object, the room in which we are.

  4. -
  5. An equal sign.

  6. -
  7. The name of the event to which the callback should be connected. Here, the event is “say”. -Meaning this callback will be executed every time somebody says something in the room.

  8. -
  9. But we add an event parameter to indicate the keywords said in the room that should execute our -callback. Otherwise, our callback would be called every time somebody speaks, no matter what. Here -we limit, indicating our callback should be executed only if the spoken message contains “1”, “2” or -“3”.

  10. -
-

An editor should open, inviting you to enter the Python code that should be executed. The first -thing to remember is to read the text provided (it can contain important information) and, most of -all, the list of variables that are available in this callback:

-
Variables you can use in this event:
-
-    character: the character having spoken in this room.
-    room: the room connected to this event.
-    message: the text having been spoken by the character.
-
-----------Line Editor [Callback say of Inside of an elevator]---------------------
-01|
-----------[l:01 w:000 c:0000]------------(:h for help)----------------------------
-
-
-

This is important, in order to know what variables we can use in our callback out-of-the-box. Let’s -write a single line to be sure our callback is called when we expect it to:

-
character.msg("You just said {}.".format(message))
-
-
-

You can paste this line in-game, then type the :wq command to exit the editor and save your -modifications.

-

Let’s check. Try to say “hello” in the room. You should see the standard message, but nothing -more. Now try to say “1”. Below the standard message, you should see:

-
You just said 1.
-
-
-

You can try it. Our callback is only called when we say “1”, “2” or “3”. Which is just what we -want.

-

Let’s go back in our code editor and add something more useful.

-
call/edit here = say
-
-
-
-

Notice that we used the “edit” switch this time, since the callback exists, we just want to edit -it.

-
-

The editor opens again. Let’s empty it first:

-
:DD
-
-
-

And turn off automatic indentation, which will help us:

-
:=
-
-
-
-

Auto-indentation is an interesting feature of the code editor, but we’d better not use it at this -point, it will make copy/pasting more complicated.

-
-
-
-

Our entire callback in the elevator

-

So here’s the time to truly code our callback in-game. Here’s a little reminder:

-
    -
  1. We have all the IDs of our three rooms and two exits.

  2. -
  3. When we say “1”, “2” or “3”, the elevator should move to the right room, that is change the -exits. Remember, we already have the exits, we just need to change their location and destination.

  4. -
-

It’s a good idea to try to write this callback yourself, but don’t feel bad about checking the -solution right now. Here’s a possible code that you could paste in the code editor:

-
# First let's have some constants
-ELEVATOR = get(id=3)
-FLOORS = {
-    "1": get(id=2),
-    "2": get(id=6),
-    "3": get(id=7),
-}
-TO_EXIT = get(id=4)
-BACK_EXIT = get(id=5)
-
-# Now we check that the elevator isn't already at this floor
-floor = FLOORS.get(message)
-if floor is None:
-    character.msg("Which floor do you want?")
-elif TO_EXIT.location is floor:
-    character.msg("The elevator already is at this floor.")
-else:
-    # 'floor' contains the new room where the elevator should be
-    room.msg_contents("The doors of the elevator close with a clank.")
-    TO_EXIT.location = floor
-    BACK_EXIT.destination = floor
-    room.msg_contents("The doors of the elevator open to {floor}.",
-            mapping=dict(floor=floor))
-
-
-

Let’s review this longer callback:

-
    -
  1. We first obtain the objects of both exits and our three floors. We use the get() eventfunc, -which is a shortcut to obtaining objects. We usually use it to retrieve specific objects with an -ID. We put the floors in a dictionary. The keys of the dictionary are the floor number (as str), -the values are room objects.

  2. -
  3. Remember, the message variable contains the message spoken in the room. So either “1”, “2”, or -“3”. We still need to check it, however, because if the character says something like “1 2” in the -room, our callback will be executed. Let’s be sure what she says is a floor number.

  4. -
  5. We then check if the elevator is already at this floor. Notice that we use TO_EXIT.location. -TO_EXIT contains our “north” exit, leading inside of our elevator. Therefore, its location will -be the room where the elevator currently is.

  6. -
  7. If the floor is a different one, have the elevator “move”, changing just the location and -destination of both exits.

    -
      -
    • The BACK_EXIT (that is “north”) should change its location. The elevator shouldn’t be -accessible through our old floor.

    • -
    • The TO_EXIT (that is “south”, the exit leading out of the elevator) should have a different -destination. When we go out of the elevator, we should find ourselves in the new floor, not the old -one.

    • -
    -
  8. -
-

Feel free to expand on this example, changing messages, making further checks. Usage and practice -are keys.

-

You can quit the editor as usual with :wq and test it out.

-
-
-

Adding a pause in our callback

-

Let’s improve our callback. One thing that’s worth adding would be a pause: for the time being, -when we say the floor number in the elevator, the doors close and open right away. It would be -better to have a pause of several seconds. More logical.

-

This is a great opportunity to learn about chained events. Chained events are very useful to create -pauses. Contrary to the events we have seen so far, chained events aren’t called automatically. -They must be called by you, and can be called after some time.

-
    -
  • Chained events always have the name “chain_X”. Usually, X is a number, but you can give the -chained event a more explicit name.

  • -
  • In our original callback, we will call our chained events in, say, 15 seconds.

  • -
  • We’ll also have to make sure the elevator isn’t already moving.

  • -
-

Other than that, a chained event can be connected to a callback as usual. We’ll create a chained -event in our elevator, that will only contain the code necessary to open the doors to the new floor.

-
call/add here = chain_1
-
-
-

The callback is added to the “chain_1” event, an event that will not be automatically called by the -system when something happens. Inside this event, you can paste the code to open the doors at the -new floor. You can notice a few differences:

-
TO_EXIT.location = floor
-TO_EXIT.destination = ELEVATOR
-BACK_EXIT.location = ELEVATOR
-BACK_EXIT.destination = floor
-room.msg_contents("The doors of the elevator open to {floor}.",
-        mapping=dict(floor=floor))
-
-
-

Paste this code into the editor, then use :wq to save and quit the editor.

-

Now let’s edit our callback in the “say” event. We’ll have to change it a bit:

-
    -
  • The callback will have to check the elevator isn’t already moving.

  • -
  • It must change the exits when the elevator move.

  • -
  • It has to call the “chain_1” event we have defined. It should call it 15 seconds later.

  • -
-

Let’s see the code in our callback.

-
call/edit here = say
-
-
-

Remove the current code and disable auto-indentation again:

-
:DD
-:=
-
-
-

And you can paste instead the following code. Notice the differences with our first attempt:

-
# First let's have some constants
-ELEVATOR = get(id=3)
-FLOORS = {
-    "1": get(id=2),
-    "2": get(id=6),
-    "3": get(id=7),
-}
-TO_EXIT = get(id=4)
-BACK_EXIT = get(id=5)
-
-# Now we check that the elevator isn't already at this floor
-floor = FLOORS.get(message)
-if floor is None:
-    character.msg("Which floor do you want?")
-elif BACK_EXIT.location is None:
-    character.msg("The elevator is between floors.")
-elif TO_EXIT.location is floor:
-    character.msg("The elevator already is at this floor.")
-else:
-    # 'floor' contains the new room where the elevator should be
-    room.msg_contents("The doors of the elevator close with a clank.")
-    TO_EXIT.location = None
-    BACK_EXIT.location = None
-    call_event(room, "chain_1", 15)
-
-
-

What changed?

-
    -
  1. We added a little test to make sure the elevator wasn’t already moving. If it is, the -BACK_EXIT.location (the “south” exit leading out of the elevator) should be None. We’ll remove -the exit while the elevator is moving.

  2. -
  3. When the doors close, we set both exits’ location to None. Which “removes” them from their -room but doesn’t destroy them. The exits still exist but they don’t connect anything. If you say -“2” in the elevator and look around while the elevator is moving, you won’t see any exits.

  4. -
  5. Instead of opening the doors immediately, we call call_event. We give it the object containing -the event to be called (here, our elevator), the name of the event to be called (here, “chain_1”) -and the number of seconds from now when the event should be called (here, 15).

  6. -
  7. The chain_1 callback we have created contains the code to “re-open” the elevator doors. That -is, besides displaying a message, it reset the exits’ location and destination.

  8. -
-

If you try to say “3” in the elevator, you should see the doors closing. Look around you and you -won’t see any exit. Then, 15 seconds later, the doors should open, and you can leave the elevator -to go to the third floor. While the elevator is moving, the exit leading to it will be -inaccessible.

-
-

Note: we don’t define the variables again in our chained event, we just call them. When we -execute call_event, a copy of our current variables is placed in the database. These variables -will be restored and accessible again when the chained event is called.

-
-

You can use the call/tasks command to see the tasks waiting to be executed. For instance, say “2” -in the room, notice the doors closing, and then type the call/tasks command. You will see a task -in the elevator, waiting to call the chain_1 event.

-
-
-

Changing exit messages

-

Here’s another nice little feature of events: you can modify the message of a single exit without -altering the others. In this case, when someone goes north into our elevator, we’d like to see -something like: “someone walks into the elevator.” Something similar for the back exit would be -great too.

-

Inside of the elevator, you can look at the available events on the exit leading outside (south).

-
call south
-
-
-

You should see two interesting rows in this table:

-
| msg_arrive       |   0 (0) | Customize the message when a character        |
-|                  |         | arrives through this exit.                    |
-| msg_leave        |   0 (0) | Customize the message when a character leaves |
-|                  |         | through this exit.                            |
-
-
-

So we can change the message others see when a character leaves, by editing the “msg_leave” event. -Let’s do that:

-
call/add south = msg_leave
-
-
-

Take the time to read the help. It gives you all the information you should need. We’ll need to -change the “message” variable, and use custom mapping (between braces) to alter the message. We’re -given an example, let’s use it. In the code editor, you can paste the following line:

-
message = "{character} walks out of the elevator."
-
-
-

Again, save and quit the editor by entering :wq. You can create a new character to see it leave.

-
charcreate A beggar
-tel #8 = here
-
-
-

(Obviously, adapt the ID if necessary.)

-
py self.search("beggar").move_to(self.search("south"))
-
-
-

This is a crude way to force our beggar out of the elevator, but it allows us to test. You should -see:

-
A beggar(#8) walks out of the elevator.
-
-
-

Great! Let’s do the same thing for the exit leading inside of the elevator. Follow the beggar, -then edit “msg_leave” of “north”:

-
call/add north = msg_leave
-
-
-
message = "{character} walks into the elevator."
-
-
-

Again, you can force our beggar to move and see the message we have just set. This modification -applies to these two exits, obviously: the custom message won’t be used for other exits. Since we -use the same exits for every floor, this will be available no matter at what floor the elevator is, -which is pretty neat!

-
-
-

Tutorial F.A.Q.

-
    -
  • Q: what happens if the game reloads or shuts down while a task is waiting to happen?

  • -
  • A: if your game reloads while a task is in pause (like our elevator between floors), when the -game is accessible again, the task will be called (if necessary, with a new time difference to take -into account the reload). If the server shuts down, obviously, the task will not be called, but -will be stored and executed when the server is up again.

  • -
  • Q: can I use all kinds of variables in my callback? Whether chained or not?

  • -
  • A: you can use every variable type you like in your original callback. However, if you -execute call_event, since your variables are stored in the database, they will need to respect the -constraints on persistent attributes. A callback will not be stored in this way, for instance. -This variable will not be available in your chained event.

  • -
  • Q: when you say I can call my chained events something else than “chain_1”, “chain_2” and -such, what is the naming convention?

  • -
  • A: chained events have names beginning by “chain_”. This is useful for you and for the -system. But after the underscore, you can give a more useful name, like “chain_open_doors” in our -case.

  • -
  • Q: do I have to pause several seconds to call a chained event?

  • -
  • A: no, you can call it right away. Just leave the third parameter of call_event out (it -will default to 0, meaning the chained event will be called right away). This will not create a -task.

  • -
  • Q: can I have chained events calling themselves?

  • -
  • A: you can. There’s no limitation. Just be careful, a callback that calls itself, -particularly without delay, might be a good recipe for an infinite loop. However, in some cases, it -is useful to have chained events calling themselves, to do the same repeated action every X seconds -for instance.

  • -
  • Q: what if I need several elevators, do I need to copy/paste these callbacks each time?

  • -
  • A: not advisable. There are definitely better ways to handle this situation. One of them is -to consider adding the code in the source itself. Another possibility is to call chained events -with the expected behavior, which makes porting code very easy. This side of chained events will be -shown in the next tutorial.

  • -
  • Previous tutorial: Adding dialogues in events

  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/API-refactoring.html b/docs/0.9.5/API-refactoring.html deleted file mode 100644 index bd123c9fce..0000000000 --- a/docs/0.9.5/API-refactoring.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - API refactoring — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

API refactoring

-

Building up to Evennia 1.0 and beyond, it’s time to comb through the Evennia API for old cruft. This -whitepage is for anyone interested to contribute with their views on what part of the API needs -refactoring, cleanup or clarification (or extension!)

-

Note that this is not a forum. To keep things clean, each opinion text should ideally present a -clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be -held in chat or forum.

-
-
-

Griatch (Aug 13, 2019)

-

This is how to enter an opinion. Use any markdown needed but stay within your section. Also remember -to copy your text to the clipboard before saving since if someone else edited the wiki in the -meantime you’ll have to start over.

-
-
-

Griatch (Sept 2, 2019)

-

I don’t agree with removing explicit keywords as suggested by [Johnny on Aug 29 below](API- -refactoring#reduce-usage-of-optionalpositional-arguments-aug-29-2019). Overriding such a method can -still be done by get(self, **kwargs) if so desired, making the kwargs explicit helps IMO -readability of the API. If just giving a generic **kwargs, one must read the docstring or even the -code to see which keywords are valid.

-

On the other hand, I think it makes sense to as a standard offer an extra **kwargs at the end of -arg-lists for common methods that are expected to be over-ridden. This make the API more flexible by -hinting to the dev that they could expand their own over-ridden implementation with their own -keyword arguments if so desired.

-
-
-
-

Johnny

-
-

Reduce usage of optional/positional arguments (Aug 29, 2019)

-
# AttributeHandler
-def get(self, key=None, default=None, category=None, return_obj=False,
-            strattr=False, raise_exception=False, accessing_obj=None,
-            default_access=True, return_list=False):
-
-
-

Many classes have methods requiring lengthy positional argument lists, which are tedious and error- -prone to extend and override especially in cases where not all arguments are even required. It would -be useful if arguments were reserved for required inputs and anything else relegated to kwargs for -easier passthrough on extension.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Accounts.html b/docs/0.9.5/Accounts.html deleted file mode 100644 index 7a24808e9d..0000000000 --- a/docs/0.9.5/Accounts.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - - - - Accounts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Accounts

-

All users (real people) that starts a game Session on Evennia are doing so through an -object called Account. The Account object has no in-game representation, it represents a unique -game account. In order to actually get on the game the Account must puppet an Object -(normally a Character).

-

Exactly how many Sessions can interact with an Account and its Puppets at once is determined by -Evennia’s MULTISESSION_MODE setting.

-

Apart from storing login information and other account-specific data, the Account object is what is -chatting on Channels. It is also a good place to store Permissions to be -consistent between different in-game characters as well as configuration options. The Account -object also has its own CmdSet, the AccountCmdSet.

-

Logged into default evennia, you can use the ooc command to leave your current -character and go into OOC mode. You are quite limited in this mode, basically it works -like a simple chat program. It acts as a staging area for switching between Characters (if your -game supports that) or as a safety mode if your Character gets deleted. Use ic to attempt to -(re)puppet a Character.

-

Note that the Account object can have, and often does have, a different set of -Permissions from the Character they control. Normally you should put your -permissions on the Account level - this will overrule permissions set on the Character level. For -the permissions of the Character to come into play the default quell command can be used. This -allows for exploring the game using a different permission set (but you can’t escalate your -permissions this way - for hierarchical permissions like Builder, Admin etc, the lower of the -permissions on the Character/Account will always be used).

-
-

How to create your own Account types

-

You will usually not want more than one Account typeclass for all new accounts (but you could in -principle create a system that changes an account’s typeclass dynamically).

-

An Evennia Account is, per definition, a Python class that includes evennia.DefaultAccount among -its parents. In mygame/typeclasses/accounts.py there is an empty class ready for you to modify. -Evennia defaults to using this (it inherits directly from DefaultAccount).

-

Here’s an example of modifying the default Account class in code:

-
    # in mygame/typeclasses/accounts.py
-
-    from evennia import DefaultAccount
-
-    class Account(DefaultAccount): # [...]
-
-        at_account_creation(self): "this is called only once, when account is first created"
-            self.db.real_name = None      # this is set later self.db.real_address = None   #
-"
-            self.db.config_1 = True       # default config self.db.config_2 = False      #       "
-            self.db.config_3 = 1          #       "
-
-            # ... whatever else our game needs to know ``` Reload the server with `reload`.
-
-
-
-

… However, if you use examine *self (the asterisk makes you examine your Account object rather -than your Character), you won’t see your new Attributes yet. This is because at_account_creation -is only called the very first time the Account is called and your Account object already exists -(any new Accounts that connect will see them though). To update yourself you need to make sure to -re-fire the hook on all the Accounts you have already created. Here is an example of how to do this -using py:

-

py [account.at_account_creation() for account in evennia.managers.accounts.all()]

-

You should now see the Attributes on yourself.

-
-

If you wanted Evennia to default to a completely different Account class located elsewhere, you -must point Evennia to it. Add BASE_ACCOUNT_TYPECLASS to your settings file, and give the python -path to your custom class as its value. By default this points to typeclasses.accounts.Account, -the empty template we used above.

-
-
-
-

Properties on Accounts

-

Beyond those properties assigned to all typeclassed objects (see Typeclasses), the -Account also has the following custom properties:

-
    -
  • user - a unique link to a User Django object, representing the logged-in user.

  • -
  • obj - an alias for character.

  • -
  • name - an alias for user.username

  • -
  • sessions - an instance of -ObjectSessionHandler -managing all connected Sessions (physical connections) this object listens to (Note: In older -versions of Evennia, this was a list). The so-called session-id (used in many places) is found -as -a property sessid on each Session instance.

  • -
  • is_superuser (bool: True/False) - if this account is a superuser.

  • -
-

Special handlers:

-
    -
  • cmdset - This holds all the current Commands of this Account. By default these are -the commands found in the cmdset defined by settings.CMDSET_ACCOUNT.

  • -
  • nicks - This stores and handles Nicks, in the same way as nicks it works on Objects. -For Accounts, nicks are primarily used to store custom aliases for -Channels.

  • -
-

Selection of special methods (see evennia.DefaultAccount for details):

-
    -
  • get_puppet - get a currently puppeted object connected to the Account and a given session id, if -any.

  • -
  • puppet_object - connect a session to a puppetable Object.

  • -
  • unpuppet_object - disconnect a session from a puppetable Object.

  • -
  • msg - send text to the Account

  • -
  • execute_cmd - runs a command as if this Account did it.

  • -
  • search - search for Accounts.

  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Add-a-simple-new-web-page.html b/docs/0.9.5/Add-a-simple-new-web-page.html deleted file mode 100644 index 04933fafbd..0000000000 --- a/docs/0.9.5/Add-a-simple-new-web-page.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - Add a simple new web page — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Add a simple new web page

-

Evennia leverages Django which is a web development framework. -Huge professional websites are made in Django and there is extensive documentation (and books) on it -. You are encouraged to at least look at the Django basic tutorials. Here we will just give a brief -introduction for how things hang together, to get you started.

-

We assume you have installed and set up Evennia to run. A webserver and website comes out of the -box. You can get to that by entering http://localhost:4001 in your web browser - you should see a -welcome page with some game statistics and a link to the web client. Let us add a new page that you -can get to by going to http://localhost:4001/story.

-
-

Create the view

-

A django “view” is a normal Python function that django calls to render the HTML page you will see -in the web browser. Here we will just have it spit back the raw html, but Django can do all sorts of -cool stuff with the page in the view, like adding dynamic content or change it on the fly. Open -mygame/web folder and add a new module there named story.py (you could also put it in its own -folder if you wanted to be neat. Don’t forget to add an empty __init__.py file if you do, to tell -Python you can import from the new folder). Here’s how it looks:

-
# in mygame/web/story.py
-
-from django.shortcuts import render
-
-def storypage(request):
-    return render(request, "story.html")
-
-
-

This view takes advantage of a shortcut provided to use by Django, render. This shortcut gives the -template some information from the request, for instance, the game name, and then renders it.

-
-
-

The HTML page

-

We need to find a place where Evennia (and Django) looks for html files (called templates in -Django parlance). You can specify such places in your settings (see the TEMPLATES variable in -default_settings.py for more info), but here we’ll use an existing one. Go to -mygame/template/overrides/website/ and create a page story.html there.

-

This is not a HTML tutorial, so we’ll go simple:

-
{% extends "base.html" %}
-{% block content %}
-<div class="row">
-  <div class="col">
-    <h1>A story about a tree</h1>
-    <p>
-        This is a story about a tree, a classic tale ...
-    </p>
-  </div>
-</div>
-{% endblock %}
-
-
-

Since we’ve used the render shortcut, Django will allow us to extend our base styles easily.

-

If you’d rather not take advantage of Evennia’s base styles, you can do something like this instead:

-
<html>
-  <body>
-    <h1>A story about a tree</h1>
-    <p>
-    This is a story about a tree, a classic tale ...
-  </body>
-</html>
-
-
-
-
-

The URL

-

When you enter the address http://localhost:4001/story in your web browser, Django will parse that -field to figure out which page you want to go to. You tell it which patterns are relevant in the -file -mygame/web/urls.py. -Open it now.

-

Django looks for the variable urlpatterns in this file. You want to add your new pattern to the -custom_patterns list we have prepared - that is then merged with the default urlpatterns. Here’s -how it could look:

-
from web import story
-
-# ...
-
-custom_patterns = [
-    url(r'story', story.storypage, name='Story'),
-]
-
-
-

That is, we import our story view module from where we created it earlier and then create an url -instance. The first argument to url is the pattern of the url we want to find ("story") (this is -a regular expression if you are familiar with those) and then our view function we want to direct -to.

-

That should be it. Reload Evennia and you should be able to browse to your new story page!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Add-a-wiki-on-your-website.html b/docs/0.9.5/Add-a-wiki-on-your-website.html deleted file mode 100644 index 646565ce08..0000000000 --- a/docs/0.9.5/Add-a-wiki-on-your-website.html +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - - - - Add a wiki on your website — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Add a wiki on your website

-

Before doing this tutorial you will probably want to read the intro in -Basic Web tutorial. Reading the three first parts of the -Django tutorial might help as well.

-

This tutorial will provide a step-by-step process to installing a wiki on your website. -Fortunately, you don’t have to create the features manually, since it has been done by others, and -we can integrate their work quite easily with Django. I have decided to focus on -the Django-wiki.

-
-

Note: this article has been updated for Evennia 0.9. If you’re not yet using this version, be -careful, as the django wiki doesn’t support Python 2 anymore. (Remove this note when enough time -has passed.)

-
-

The Django-wiki offers a lot of features associated with -wikis, is -actively maintained (at this time, anyway), and isn’t too difficult to install in Evennia. You can -see a demonstration of Django-wiki here.

-
-

Basic installation

-

You should begin by shutting down the Evennia server if it is running. We will run migrations and -alter the virtual environment just a bit. Open a terminal and activate your Python environment, the -one you use to run the evennia command.

-
    -
  • On Linux:

    -
    source evenv/bin/activate
    -
    -
    -
  • -
  • Or Windows:

    -
    evenv\bin\activate
    -
    -
    -
  • -
-
-

Installing with pip

-

Install the wiki using pip:

-
pip install wiki
-
-
-
-

Note: this will install the last version of Django wiki. Version >0.4 doesn’t support Python 2, so -install wiki 0.3 if you haven’t updated to Python 3 yet.

-
-

It might take some time, the Django-wiki having some dependencies.

-
-
-

Adding the wiki in the settings

-

You will need to add a few settings to have the wiki app on your website. Open your -server/conf/settings.py file and add the following at the bottom (but before importing -secret_settings). Here’s what you’ll find in my own setting file (add the whole Django-wiki -section):

-
r"""
-Evennia settings file.
-
-...
-
-"""
-
-# Use the defaults from Evennia unless explicitly overridden
-from evennia.settings_default import *
-
-######################################################################
-# Evennia base server config
-######################################################################
-
-# This is the name of your game. Make it catchy!
-SERVERNAME = "demowiki"
-
-######################################################################
-# Django-wiki settings
-######################################################################
-INSTALLED_APPS += (
-    'django.contrib.humanize.apps.HumanizeConfig',
-    'django_nyt.apps.DjangoNytConfig',
-    'mptt',
-    'sorl.thumbnail',
-    'wiki.apps.WikiConfig',
-    'wiki.plugins.attachments.apps.AttachmentsConfig',
-    'wiki.plugins.notifications.apps.NotificationsConfig',
-    'wiki.plugins.images.apps.ImagesConfig',
-    'wiki.plugins.macros.apps.MacrosConfig',
-)
-
-# Disable wiki handling of login/signup
-WIKI_ACCOUNT_HANDLING = False
-WIKI_ACCOUNT_SIGNUP_ALLOWED = False
-
-######################################################################
-# Settings given in secret_settings.py override those in this file.
-######################################################################
-try:
-    from server.conf.secret_settings import *
-except ImportError:
-    print("secret_settings.py file not found or failed to import.")
-
-
-
-
-

Adding the new URLs

-

Next we need to add two URLs in our web/urls.py file. Open it and compare the following output: -you will need to add two URLs in custom_patterns and add one import line:

-
from django.conf.urls import url, include
-from django.urls import path # NEW!
-
-# default evenni        a patterns
-from evennia.web.urls import urlpatterns
-
-# eventual custom patterns
-custom_patterns = [
-    # url(r'/desired/url/', view, name='example'),
-    url('notifications/', include('django_nyt.urls')), # NEW!
-    url('wiki/', include('wiki.urls')), # NEW!
-]
-
-# this is required by Django.
-urlpatterns = custom_patterns + urlpatterns
-
-
-

You will probably need to copy line 2, 10, and 11. Be sure to place them correctly, as shown in -the example above.

-
-
-

Running migrations

-

It’s time to run the new migrations. The wiki app adds a few tables in our database. We’ll need to -run:

-
evennia migrate
-
-
-

And that’s it, you can start the server. If you go to http://localhost:4001/wiki , you should see -the wiki. Use your account’s username and password to connect to it. That’s how simple it is.

-
-
-
-

Customizing privileges

-

A wiki can be a great collaborative tool, but who can see it? Who can modify it? Django-wiki comes -with a privilege system centered around four values per wiki page. The owner of an article can -always read and write in it (which is somewhat logical). The group of the article defines who can -read and who can write, if the user seeing the page belongs to this group. The topic of groups in -wiki pages will not be discussed here. A last setting determines which other user (that is, these -who aren’t in the groups, and aren’t the article’s owner) can read and write. Each article has -these four settings (group read, group write, other read, other write). Depending on your purpose, -it might not be a good default choice, particularly if you have to remind every builder to keep the -pages private. Fortunately, Django-wiki gives us additional settings to customize who can read, and -who can write, a specific article.

-

These settings must be placed, as usual, in your server/conf/settings.py file. They take a -function as argument, said function (or callback) will be called with the article and the user. -Remember, a Django user, for us, is an account. So we could check lockstrings on them if needed. -Here is a default setting to restrict the wiki: only builders can write in it, but anyone (including -non-logged in users) can read it. The superuser has some additional privileges.

-
# In server/conf/settings.py
-# ...
-
-def is_superuser(article, user):
-    """Return True if user is a superuser, False otherwise."""
-    return not user.is_anonymous() and user.is_superuser
-
-def is_builder(article, user):
-    """Return True if user is a builder, False otherwise."""
-    return not user.is_anonymous() and user.locks.check_lockstring(user, "perm(Builders)")
-
-def is_anyone(article, user):
-    """Return True even if the user is anonymous."""
-    return True
-
-# Who can create new groups and users from the wiki?
-WIKI_CAN_ADMIN = is_superuser
-# Who can change owner and group membership?
-WIKI_CAN_ASSIGN = is_superuser
-# Who can change group membership?
-WIKI_CAN_ASSIGN_OWNER = is_superuser
-# Who can change read/write access to groups or others?
-WIKI_CAN_CHANGE_PERMISSIONS = is_superuser
-# Who can soft-delete an article?
-WIKI_CAN_DELETE = is_builder
-# Who can lock an article and permanently delete it?
-WIKI_CAN_MODERATE = is_superuser
-# Who can edit articles?
-WIKI_CAN_WRITE = is_builder
-# Who can read articles?
-WIKI_CAN_READ = is_anyone
-
-
-

Here, we have created three functions: one to return True if the user is the superuser, one to -return True if the user is a builder, one to return True no matter what (this includes if the -user is anonymous, E.G. if it’s not logged-in). We then change settings to allow either the -superuser or -each builder to moderate, read, write, delete, and more. You can, of course, add more functions, -adapting them to your need. This is just a demonstration.

-

Providing the WIKI_CAN*... settings will bypass the original permission system. The superuser -could change permissions of an article, but still, only builders would be able to write it. If you -need something more custom, you will have to expand on the functions you use.

-
-

Managing wiki pages from Evennia

-

Unfortunately, Django wiki doesn’t provide a clear and clean entry point to read and write articles -from Evennia and it doesn’t seem to be a very high priority. If you really need to keep Django wiki -and to create and manage wiki pages from your code, you can do so, but this article won’t elaborate, -as this is somewhat more technical.

-

However, it is a good opportunity to present a small project that has been created more recently: -evennia-wiki has been created to provide a simple -wiki, more tailored to Evennia and easier to connect. It doesn’t, as yet, provide as many options -as does Django wiki, but it’s perfectly usable:

-
    -
  • Pages have an inherent and much-easier to understand hierarchy based on URLs.

  • -
  • Article permissions are connected to Evennia groups and are much easier to accommodate specific -requirements.

  • -
  • Articles can easily be created, read or updated from the Evennia code itself.

  • -
  • Markdown is fully-supported with a default integration to Bootstrap to look good on an Evennia -website. Tables and table of contents are supported as well as wiki links.

  • -
  • The process to override wiki templates makes full use of the template_overrides directory.

  • -
-

However evennia-wiki doesn’t yet support:

-
    -
  • Images in markdown and the uploading schema. If images are important to you, please consider -contributing to this new project.

  • -
  • Modifying permissions on a per page/setting basis.

  • -
  • Moving pages to new locations.

  • -
  • Viewing page history.

  • -
-

Considering the list of features in Django wiki, obviously other things could be added to the list. -However, these features may be the most important and useful. Additional ones might not be that -necessary. If you’re interested in supporting this little project, you are more than welcome to -contribute to it. Thanks!

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Adding-Command-Tutorial.html b/docs/0.9.5/Adding-Command-Tutorial.html deleted file mode 100644 index 07d23a7472..0000000000 --- a/docs/0.9.5/Adding-Command-Tutorial.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - Adding Command Tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Adding Command Tutorial

-

This is a quick first-time tutorial expanding on the Commands documentation.

-

Let’s assume you have just downloaded Evennia, installed it and created your game folder (let’s call -it just mygame here). Now you want to try to add a new command. This is the fastest way to do it.

-
-

Step 1: Creating a custom command

-
    -
  1. Open mygame/commands/command.py in a text editor. This is just one place commands could be -placed but you get it setup from the onset as an easy place to start. It also already contains some -example code.

  2. -
  3. Create a new class in command.py inheriting from default_cmds.MuxCommand. Let’s call it -CmdEcho in this example.

  4. -
  5. Set the class variable key to a good command name, like echo.

  6. -
  7. Give your class a useful docstring. A docstring is the string at the very top of a class or -function/method. The docstring at the top of the command class is read by Evennia to become the help -entry for the Command (see -Command Auto-help).

  8. -
  9. Define a class method func(self) that echoes your input back to you.

  10. -
-

Below is an example how this all could look for the echo command:

-
        # file mygame/commands/command.py
-        #[...]
-        from evennia import default_cmds
-        class CmdEcho(default_cmds.MuxCommand):
-            """
-            Simple command example
-    
-            Usage:
-              echo [text]
-    
-            This command simply echoes text back to the caller.
-            """
-    
-            key = "echo"
-    
-            def func(self):
-                "This actually does things"
-                if not self.args:
-                    self.caller.msg("You didn't enter anything!")
-                else:
-                    self.caller.msg("You gave the string: '%s'" % self.args)
-
-
-
-
-

Step 2: Adding the Command to a default Cmdset

-

The command is not available to use until it is part of a Command Set. In this -example we will go the easiest route and add it to the default Character commandset that already -exists.

-
    -
  1. Edit mygame/commands/default_cmdsets.py

  2. -
  3. Import your new command with from commands.command import CmdEcho.

  4. -
  5. Add a line self.add(CmdEcho()) to CharacterCmdSet, in the at_cmdset_creation method (the -template tells you where).

  6. -
-

This is approximately how it should look at this point:

-
        # file mygame/commands/default_cmdsets.py
-        #[...]
-        from commands.command import CmdEcho
-        #[...]
-        class CharacterCmdSet(default_cmds.CharacterCmdSet):
-        
-            key = "DefaultCharacter"
-    
-            def at_cmdset_creation(self):
-    
-                # this first adds all default commands
-                super().at_cmdset_creation()
-    
-                # all commands added after this point will extend or
-                # overwrite the default commands.
-                self.add(CmdEcho())
-
-
-

Next, run the @reload command. You should now be able to use your new echo command from inside -the game. Use help echo to see the documentation for the command.

-

If you have trouble, make sure to check the log for error messages (probably due to syntax errors in -your command definition).

-
-

Note: Typing echotest will also work. It will be handled as the command echo directly followed -by -its argument test (which will end up in self.args). To change this behavior, you can add the arg_regexproperty alongsidekey, help_category` etc. See the arg_regex -documentation for more info.

-
-

If you want to overload existing default commands (such as look or get), just add your new -command with the same key as the old one - it will then replace it. Just remember that you must use -@reload to see any changes.

-

See Commands for many more details and possibilities when defining Commands and using -Cmdsets in various ways.

-
-
-

Adding the command to specific object types

-

Adding your Command to the CharacterCmdSet is just one easy exapmple. The cmdset system is very -generic. You can create your own cmdsets (let’s say in a module mycmdsets.py) and add them to -objects as you please (how to control their merging is described in detail in the Command Set -documentation).

-
    # file mygame/commands/mycmdsets.py
-    #[...]
-    from commands.command import CmdEcho
-    from evennia import CmdSet
-    #[...]
-    class MyCmdSet(CmdSet):
-        
-        key = "MyCmdSet"
-    
-        def at_cmdset_creation(self):
-            self.add(CmdEcho())
-
-
-

Now you just need to add this to an object. To test things (as superuser) you can do

-
 @py self.cmdset.add("mycmdsets.MyCmdSet")
-
-
-

This will add this cmdset (along with its echo command) to yourself so you can test it. Note that -you cannot add a single Command to an object on its own, it must be part of a CommandSet in order to -do so.

-

The Command you added is not there permanently at this point. If you do a @reload the merger will -be gone. You could add the permanent=True keyword to the cmdset.add call. This will however -only make the new merged cmdset permanent on that single object. Often you want all objects of -this particular class to have this cmdset.

-

To make sure all new created objects get your new merged set, put the cmdset.add call in your -custom Typeclassesat_object_creation method:

-
    # e.g. in mygame/typeclasses/objects.py
-
-    from evennia import DefaultObject
-    class MyObject(DefaultObject):
-        
-        def at_object_creation(self):
-            "called when the object is first created"
-            self.cmdset.add("mycmdset.MyCmdSet", permanent=True)
-
-
-

All new objects of this typeclass will now start with this cmdset and it will survive a @reload.

-

Note: An important caveat with this is that at_object_creation is only called once, when the -object is first created. This means that if you already have existing objects in your databases -using that typeclass, they will not have been initiated the same way. There are many ways to update -them; since it’s a one-time update you can usually just simply loop through them. As superuser, try -the following:

-
 @py from typeclasses.objects import MyObject; [o.cmdset.add("mycmdset.MyCmdSet") for o in
-
-
-

MyObject.objects.all()]

-

This goes through all objects in your database having the right typeclass, adding the new cmdset to -each. The good news is that you only have to do this if you want to post-add cmdsets. If you just -want to add a new command, you can simply add that command to the cmdset’s at_cmdset_creation -and @reload to make the Command immediately available.

-
-
-

Change where Evennia looks for command sets

-

Evennia uses settings variables to know where to look for its default command sets. These are -normally not changed unless you want to re-organize your game folder in some way. For example, the -default character cmdset defaults to being defined as

-
CMDSET_CHARACTER="commands.default_cmdset.CharacterCmdSet"
-
-
-

See evennia/settings_default.py for the other settings.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Adding-Object-Typeclass-Tutorial.html b/docs/0.9.5/Adding-Object-Typeclass-Tutorial.html deleted file mode 100644 index 68da967c9d..0000000000 --- a/docs/0.9.5/Adding-Object-Typeclass-Tutorial.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - Adding Object Typeclass Tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Adding Object Typeclass Tutorial

-

Evennia comes with a few very basic classes of in-game entities:

-
DefaultObject
-   |
-   DefaultCharacter
-   DefaultRoom
-   DefaultExit
-   DefaultChannel
-
-
-

When you create a new Evennia game (with for example evennia --init mygame) Evennia will -automatically create empty child classes Object, Character, Room and Exit respectively. They -are found mygame/typeclasses/objects.py, mygame/typeclasses/rooms.py etc.

-
-

Technically these are all Typeclassed, which can be ignored for now. In -mygame/typeclasses are also base typeclasses for out-of-character things, notably -Channels, Accounts and Scripts. We don’t cover those in -this tutorial.

-
-

For your own game you will most likely want to expand on these very simple beginnings. It’s normal -to want your Characters to have various attributes, for example. Maybe Rooms should hold extra -information or even all Objects in your game should have properties not included in basic Evennia.

-
-

Change Default Rooms, Exits, Character Typeclass

-

This is the simplest case.

-

The default build commands of a new Evennia game is set up to use the Room, Exit and Character -classes found in the same-named modules under mygame/typeclasses/. By default these are empty and -just implements the default parents from the Evennia library (DefaultRoometc). Just add the -changes you want to these classes and run @reload to add your new functionality.

-
-
-

Create a new type of object

-

Say you want to create a new “Heavy” object-type that characters should not have the ability to pick -up.

-
    -
  1. Edit mygame/typeclasses/objects.py (you could also create a new module there, named something -like heavy.py, that’s up to how you want to organize things).

  2. -
  3. Create a new class inheriting at any distance from DefaultObject. It could look something like -this:

  4. -
-
    # end of file mygame/typeclasses/objects.py
-    from evennia import DefaultObject
-    
-    class Heavy(DefaultObject):
-       "Heavy object"
-       def at_object_creation(self):
-           "Called whenever a new object is created"
-           # lock the object down by default
-           self.locks.add("get:false()")
-           # the default "get" command looks for this Attribute in order
-           # to return a customized error message (we just happen to know
-           # this, you'd have to look at the code of the 'get' command to
-           # find out).
-           self.db.get_err_msg = "This is too heavy to pick up."
-
-
-
    -
  1. Once you are done, log into the game with a build-capable account and do @create/drop rock:objects.Heavy to drop a new heavy “rock” object in your location. Next try to pick it up -(@quell yourself first if you are a superuser). If you get errors, look at your log files where -you will find the traceback. The most common error is that you have some sort of syntax error in -your class.

  2. -
-

Note that the Locks and Attribute which are set in the typeclass could just -as well have been set using commands in-game, so this is a very simple example.

-
-
-

Storing data on initialization

-

The at_object_creation is only called once, when the object is first created. This makes it ideal -for database-bound things like Attributes. But sometimes you want to create temporary -properties (things that are not to be stored in the database but still always exist every time the -object is created). Such properties can be initialized in the at_init method on the object. -at_init is called every time the object is loaded into memory.

-
-

Note: It’s usually pointless and wasteful to assign database data in at_init, since this will -hit the database with the same value over and over. Put those in at_object_creation instead.

-
-

You are wise to use ndb (non-database Attributes) to store these non-persistent properties, since -ndb-properties are protected against being cached out in various ways and also allows you to list -them using various in-game tools:

-
def at_init(self):
-    self.ndb.counter = 0
-    self.ndb.mylist = []
-
-
-
-

Note: As mentioned in the Typeclasses documentation, at_init replaces the use of -the standard __init__ method of typeclasses due to how the latter may be called in situations -other than you’d expect. So use at_init where you would normally use __init__.

-
-
-
-

Updating existing objects

-

If you already have some Heavy objects created and you add a new Attribute in -at_object_creation, you will find that those existing objects will not have this Attribute. This -is not so strange, since at_object_creation is only called once, it will not be called again just -because you update it. You need to update existing objects manually.

-

If the number of objects is limited, you can use @typeclass/force/reload objectname to force a -re-load of the at_object_creation method (only) on the object. This case is common enough that -there is an alias @update objectname you can use to get the same effect. If there are multiple -objects you can use @py to loop over the objects you need:

-
@py from typeclasses.objects import Heavy; [obj.at_object_creation() for obj in Heavy.objects.all()]
-
-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Administrative-Docs.html b/docs/0.9.5/Administrative-Docs.html deleted file mode 100644 index c01eda590f..0000000000 --- a/docs/0.9.5/Administrative-Docs.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - Administrative Docs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - -
- - -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Apache-Config.html b/docs/0.9.5/Apache-Config.html deleted file mode 100644 index 166c1e99a8..0000000000 --- a/docs/0.9.5/Apache-Config.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - - - - Apache Config — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Apache Config

-

Warning: This information is presented as a convenience, using another webserver than Evennia’s -own is not directly supported and you are on your own if you want to do so. Evennia’s webserver -works out of the box without any extra configuration and also runs in-process making sure to avoid -caching race conditions. The browser web client will most likely not work (at least not without -tweaking) on a third-party web server.

-

One reason for wanting to use an external webserver like Apache would be to act as a proxy in -front of the Evennia webserver. Getting this working with TLS (encryption) requires some extra work -covered at the end of this page.

-

Note that the Apache instructions below might be outdated. If something is not working right, or you -use Evennia with a different server, please let us know. Also, if there is a particular Linux distro -you would like covered, please let us know.

-
-

mod_wsgi Setup

-
-

Install mod_wsgi

-
    -
  • Fedora/RHEL - Apache HTTP Server and mod_wsgi are available in the standard package -repositories for Fedora and RHEL:

    -
    $ dnf install httpd mod_wsgi
    -or
    -$ yum install httpd mod_wsgi
    -
    -
    -
  • -
  • Ubuntu/Debian - Apache HTTP Server and mod_wsgi are available in the standard package -repositories for Ubuntu and Debian:

    -
    $ apt-get update
    -$ apt-get install apache2 libapache2-mod-wsgi
    -
    -
    -
  • -
-
-
-

Copy and modify the VHOST

-

After mod_wsgi is installed, copy the evennia/web/utils/evennia_wsgi_apache.conf file to your -apache2 vhosts/sites folder. On Debian/Ubuntu, this is /etc/apache2/sites-enabled/. Make your -modifications after copying the file there.

-

Read the comments and change the paths to point to the appropriate locations within your setup.

-
-
-

Restart/Reload Apache

-

You’ll then want to reload or restart apache2 after changing the configurations.

-
    -
  • Fedora/RHEL/Ubuntu

    -
    $ systemctl restart httpd
    -
    -
    -
  • -
  • Ubuntu/Debian

    -
    $ systemctl restart apache2
    -
    -
    -
  • -
-
-
-

Enjoy

-

With any luck, you’ll be able to point your browser at your domain or subdomain that you set up in -your vhost and see the nifty default Evennia webpage. If not, read the hopefully informative error -message and work from there. Questions may be directed to our Evennia Community -site.

-
-
-

A note on code reloading

-

If your mod_wsgi is set up to run on daemon mode (as will be the case by default on Debian and -Ubuntu), you may tell mod_wsgi to reload by using the touch command on -evennia/game/web/utils/apache_wsgi.conf. When mod_wsgi sees that the file modification time has -changed, it will force a code reload. Any modifications to the code will not be propagated to the -live instance of your site until reloaded.

-

If you are not running in daemon mode or want to force the issue, simply restart or reload apache2 -to apply your changes.

-
-
-

Further notes and hints:

-

If you get strange (and usually uninformative) Permission denied errors from Apache, make sure -that your evennia directory is located in a place the webserver may actually access. For example, -some Linux distributions may default to very restrictive access permissions on a user’s /home -directory.

-

One user commented that they had to add the following to their Apache config to get things to work. -Not confirmed, but worth trying if there are trouble.

-
<Directory "/home/<yourname>/evennia/game/web">
-                Options +ExecCGI
-                Allow from all
-</Directory>
-
-
-
-
-
-

mod_proxy and mod_ssl setup

-

Below are steps on running Evennia using a front-end proxy (Apache HTTP), mod_proxy_http, -mod_proxy_wstunnel, and mod_ssl. mod_proxy_http and mod_proxy_wstunnel will simply be -referred to as -mod_proxy below.

-
-

Install mod_ssl

-
    -
  • Fedora/RHEL - Apache HTTP Server and mod_ssl are available in the standard package -repositories for Fedora and RHEL:

    -
    $ dnf install httpd mod_ssl
    -or
    -$ yum install httpd mod_ssl
    -
    -
    -
    -
  • -
  • Ubuntu/Debian - Apache HTTP Server and mod_ssljkl are installed together in the apache2 -package and available in the -standard package repositories for Ubuntu and Debian. mod_ssl needs to be enabled after -installation:

    -
    $ apt-get update
    -$ apt-get install apache2
    -$ a2enmod ssl
    -
    -
    -
    -
  • -
-
-
-

TLS proxy+websocket configuration

-

Below is a sample configuration for Evennia with a TLS-enabled http and websocket proxy.

-
-

Apache HTTP Server Configuration

-
<VirtualHost *:80>
-  # Always redirect to https/443
-  ServerName mud.example.com
-  Redirect / https://mud.example.com
-</VirtualHost>
-
-<VirtualHost *:443>
-  ServerName mud.example.com
-  
-  SSLEngine On
-  
-  # Location of certificate and key
-  SSLCertificateFile /etc/pki/tls/certs/mud.example.com.crt
-  SSLCertificateKeyFile /etc/pki/tls/private/mud.example.com.key
-  
-  # Use a tool https://www.ssllabs.com/ssltest/ to scan your set after setting up.
-  SSLProtocol TLSv1.2
-  SSLCipherSuite HIGH:!eNULL:!NULL:!aNULL
-  
-  # Proxy all websocket traffic to port 4002 in Evennia
-  ProxyPass /ws ws://127.0.0.1:4002/
-  ProxyPassReverse /ws ws://127.0.0.1:4002/
-  
-  # Proxy all HTTP traffic to port 4001 in Evennia
-  ProxyPass / http://127.0.0.1:4001/
-  ProxyPassReverse / http://127.0.0.1:4001/
-  
-  # Configure separate logging for this Evennia proxy
-  ErrorLog logs/evennia_error.log
-  CustomLog logs/evennia_access.log combined
-</VirtualHost>
-
-
-
-
-

Evennia secure websocket configuration

-

There is a slight trick in setting up Evennia so websocket traffic is handled correctly by the -proxy. You must set the WEBSOCKET_CLIENT_URL setting in your mymud/server/conf/settings.py file:

-
WEBSOCKET_CLIENT_URL = "wss://external.example.com/ws"
-
-
-

The setting above is what the client’s browser will actually use. Note the use of wss:// is -because our client will be communicating over an encrypted connection (“wss” indicates websocket -over SSL/TLS). Also, especially note the additional path /ws at the end of the URL. This is how -Apache HTTP Server identifies that a particular request should be proxied to Evennia’s websocket -port but this should be applicable also to other types of proxies (like nginx).

-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Arxcode-installing-help.html b/docs/0.9.5/Arxcode-installing-help.html deleted file mode 100644 index f1ee0ff541..0000000000 --- a/docs/0.9.5/Arxcode-installing-help.html +++ /dev/null @@ -1,367 +0,0 @@ - - - - - - - - - Arxcode installing help — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Arxcode installing help

-
-

Introduction

-

Arx - After the Reckoning is a big and very popular -Evennia-based game. Arx is heavily roleplaying-centric, relying on game -masters to drive the story. Technically it’s maybe best described as “a MUSH, but with more coded -systems”. In August of 2018, the game’s developer, Tehom, generously released the source code of -Arx on github. This is a treasure-trove for developers wanting -to pick ideas or even get a starting game to build on. These instructions are based on the Arx-code -released as of Aug 12, 2018.

-

If you are not familiar with what Evennia is, you can read -an introduction here.

-

It’s not too hard to run Arx from the sources (of course you’ll start with an empty database) but -since part of Arx has grown organically, it doesn’t follow standard Evennia paradigms everywhere. -This page covers one take on installing and setting things up while making your new Arx-based game -better match with the vanilla Evennia install.

-
-
-

Installing Evennia

-

Firstly, set aside a folder/directory on your drive for everything to follow.

-

You need to start by installing Evennia by following most of the Getting -Started -Instructions for your OS. The difference is that you need to git clone https://github.com/TehomCD/evennia.git instead of Evennia’s repo because Arx uses TehomCD’s older -Evennia 0.8 fork, notably still using Python2. This detail is -important if referring to newer Evennia documentation.

-

If you are new to Evennia it’s highly recommended that you run through the -instructions in full - including initializing and starting a new empty game and connecting to it. -That way you can be sure Evennia works correctly as a base line. If you have trouble, make sure to -read the Troubleshooting instructions for your -operating system. You can also drop into our -forums, join #evennia on irc.freenode.net -or chat from the linked Discord Server.

-

After installing you should have a virtualenv running and you should have the following file -structure in your set-aside folder:

-
vienv/
-evennia/
-mygame/
-
-
-
-

Here mygame is the empty game you created during the Evennia install, with evennia --init. Go to -that and run evennia stop to make sure your empty game is not running. We’ll instead let Evenna -run Arx, so in principle you could erase mygame - but it could also be good to have a clean game -to compare to.

-
-
-

Installing Arxcode

-
-

Clone the arxcode repo

-

Cd to the root of your directory and clone the released source code from github:

-
git clone https://github.com/Arx-Game/arxcode.git myarx
-
-
-

A new folder myarx should appear next to the ones you already had. You could rename this to -something else if you want.

-

Cd into myarx. If you wonder about the structure of the game dir, you can read more about it -here.

-
-
-

Clean up settings

-

Arx has split evennia’s normal settings into base_settings.py and production_settings.py. It -also has its own solution for managing ‘secret’ parts of the settings file. We’ll keep most of Arx -way but remove the secret-handling and replace it with the normal Evennia method.

-

Cd into myarx/server/conf/ and open the file settings.py in a text editor. The top part (within -"""...""") is just help text. Wipe everything underneath that and make it look like this instead -(don’t forget to save):

-
from base_settings import *
-    
-TELNET_PORTS = [4000]
-SERVERNAME = "MyArx"
-GAME_SLOGAN = "The cool game"
-
-try:
-    from server.conf.secret_settings import *
-except ImportError:
-    print("secret_settings.py file not found or failed to import.")
-
-
-
-

Note: Indents and capitalization matter in Python. Make indents 4 spaces (not tabs) for your own -sanity. If you want a starter on Python in Evennia, [you can look here](Python-basic- -introduction).

-
-

This will import Arx’ base settings and override them with the Evennia-default telnet port and give -the game a name. The slogan changes the sub-text shown under the name of your game in the website -header. You can tweak these to your own liking later.

-

Next, create a new, empty file secret_settings.py in the same location as the settings.py file. -This can just contain the following:

-
SECRET_KEY = "sefsefiwwj3 jnwidufhjw4545_oifej whewiu hwejfpoiwjrpw09&4er43233fwefwfw"
-
-
-
-

Replace the long random string with random ASCII characters of your own. The secret key should not -be shared.

-

Next, open myarx/server/conf/base_settings.py in your text editor. We want to remove/comment out -all mentions of the decouple package, which Evennia doesn’t use (we use private_settings.py to -hide away settings that should not be shared).

-

Comment out from decouple import config by adding a # to the start of the line: # from decouple import config. Then search for config( in the file and comment out all lines where this is used. -Many of these are specific to the server environment where the original Arx runs, so is not that -relevant to us.

-
-
-

Install Arx dependencies

-

Arx has some further dependencies beyond vanilla Evennia. Start by cd:ing to the root of your -myarx folder.

-
-

If you run Linux or Mac: Edit myarx/requirements.txt and comment out the line -pypiwin32==219 - it’s only needed on Windows and will give an error on other platforms.

-
-

Make sure your virtualenv is active, then run

-
pip install -r requirements.txt
-
-
-

The needed Python packages will be installed for you.

-
-
-

Adding logs/ folder

-

The Arx repo does not contain the myarx/server/logs/ folder Evennia expects for storing server -logs. This is simple to add:

-
# linux/mac
-mkdir server/logs
-# windows
-mkdir server\logs
-
-
-
-
-

Setting up the database and starting

-

From the myarx folder, run

-
evennia migrate
-
-
-

This creates the database and will step through all database migrations needed.

-
evennia start
-
-
-

If all goes well Evennia will now start up, running Arx! You can connect to it on localhost (or -127.0.0.1 if your platform doesn’t alias localhost), port 4000 using a Telnet client. -Alternatively, you can use your web browser to browse to http://localhost:4001 to see the game’s -website and get to the web client.

-

When you log in you’ll get the standard Evennia greeting (since the database is empty), but you can -try help to see that it’s indeed Arx that is running.

-
-
-

Additional Setup Steps

-

The first time you start Evennia after creating the database with the evennia migrate step above, -it should create a few starting objects for you - your superuser account, which it will prompt you -to enter, a starting room (Limbo), and a character object for you. If for some reason this does not -occur, you may have to follow the steps below. For the first time Superuser login you may have to -run steps 7-8 and 10 to create and connect to your in-came Character.

-
    -
  1. Login to the game website with your Superuser account.

  2. -
  3. Press the Admin button to get into the (Django-) Admin Interface.

  4. -
  5. Navigate to the Accounts section.

  6. -
  7. Add a new Account named for the new staffer. Use a place holder password and dummy e-mail -address.

  8. -
  9. Flag account as Staff and apply the Admin permission group (This assumes you have already set -up an Admin Group in Django).

  10. -
  11. Add Tags named player and developer.

  12. -
  13. Log into the game using the web client (or a third-party telnet client) using your superuser -account. Move to where you want the new staffer character to appear.

  14. -
  15. In the game client, run @create/drop <staffername>:typeclasses.characters.Character, where -<staffername> is usually the same name you used for the Staffer account you created in the -Admin earlier (if you are creating a Character for your superuser, use your superuser account -name). -This creates a new in-game Character and places it in your current location.

  16. -
  17. Have the new Admin player log into the game.

  18. -
  19. Have the new Admin puppet the character with @ic StafferName.

  20. -
  21. Have the new Admin change their password - @password <old password> = <new password>.

  22. -
-

Now that you have a Character and an Account object, there’s a few additional things you may need to -do in order for some commands to function properly. You can either execute these as in-game commands -while @ic (controlling your character object).

-
    -
  1. @py from web.character.models import RosterEntry;RosterEntry.objects.create(player=self.player, character=self)

  2. -
  3. @py from world.dominion.models import PlayerOrNpc, AssetOwner;dompc = PlayerOrNpc.objects.create(player = self.player);AssetOwner.objects.create(player=dompc)

  4. -
-

Those steps will give you a ‘RosterEntry’, ‘PlayerOrNpc’, and ‘AssetOwner’ objects. RosterEntry -explicitly connects a character and account object together, even while offline, and contains -additional information about a character’s current presence in game (such as which ‘roster’ they’re -in, if you choose to use an active roster of characters). PlayerOrNpc are more character extensions, -as well as support for npcs with no in-game presence and just represented by a name which can be -offscreen members of a character’s family. It also allows for membership in Organizations. -AssetOwner holds information about a character or organization’s money and resources.

-
-
-
-

Alternate guide by Pax for installing on Windows

-

If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions -identical to the ones above), it’s possible to get Evennia running under Anaconda for Windows. The -process is a little bit trickier.

-

Make sure you have:

- -

conda update conda -conda create -n arx python=2.7 -source activate arx

-

Set up a convenient repository place for things.

-

cd ~ -mkdir Source -cd Source -mkdir Arx -cd Arx

-

Replace the SSH git clone links below with your own github forks. -If you don’t plan to change Evennia at all, you can use the -evennia/evennia.git repo instead of a forked one.

-

git clone git@github.com:/evennia.git -git clone git@github.com:/arxcode.git

-

Evennia is a package itself, so we want to install it and all of its -prerequisites, after switching to the appropriately-tagged branch for -Arxcode.

-

cd evennia -git checkout tags/v0.7 -b arx-master -pip install -e .

-

Arx has some dependencies of its own, so now we’ll go install them -As it is not a package, we’ll use the normal requirements file.

-

cd …/arxcode -pip install -r requirements.txt

-

The git repo doesn’t include the empty log directory and Evennia is unhappy if you -don’t have it, so while still in the arxcode directory…

-

mkdir server/logs

-

Now hit https://github.com/evennia/evennia/wiki/Arxcode-installing-help and -change the setup stuff as in the ‘Clean up settings’ section.

-

Then we will create our default database…

-

…/evennia/bin/windows/evennia.bat migrate

-

…and do the first run. You need winpty because Windows does not have a TTY/PTY -by default, and so the Python console input commands (used for prompts on first -run) will fail and you will end up in an unhappy place. Future runs, you should -not need winpty.

-

winpty …/evennia/bin/windows/evennia.bat start

-

Once this is done, you should have your Evennia server running Arxcode up -on localhost at port 4000, and the webserver at http://localhost:4001/

-

And you are done! Huzzah!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Async-Process.html b/docs/0.9.5/Async-Process.html deleted file mode 100644 index 7149f2ea8f..0000000000 --- a/docs/0.9.5/Async-Process.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - - - - Async Process — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Async Process

-

This is considered an advanced topic.

-
-

Synchronous versus Asynchronous

-

Most program code operates synchronously. This means that each statement in your code gets -processed and finishes before the next can begin. This makes for easy-to-understand code. It is also -a requirement in many cases - a subsequent piece of code often depend on something calculated or -defined in a previous statement.

-

Consider this piece of code in a traditional Python program:

-
    print("before call ...")
-    long_running_function()
-    print("after call ...")
-
-
-
-

When run, this will print "before call ...", after which the long_running_function gets to work -for however long time. Only once that is done, the system prints "after call ...". Easy and -logical to follow. Most of Evennia work in this way and often it’s important that commands get -executed in the same strict order they were coded.

-

Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it -swiftly switches between dealing with player input so quickly that each player feels like they do -things at the same time. This is a clever illusion however: If one user, say, runs a command -containing that long_running_function, all other players are effectively forced to wait until it -finishes.

-

Now, it should be said that on a modern computer system this is rarely an issue. Very few commands -run so long that other users notice it. And as mentioned, most of the time you want to enforce -all commands to occur in strict sequence.

-

When delays do become noticeable and you don’t care in which order the command actually completes, -you can run it asynchronously. This makes use of the run_async() function in -src/utils/utils.py:

-
    run_async(function, *args, **kwargs)
-
-
-

Where function will be called asynchronously with *args and **kwargs. Example:

-
    from evennia import utils
-    print("before call ...")
-    utils.run_async(long_running_function)
-    print("after call ...")
-
-
-

Now, when running this you will find that the program will not wait around for -long_running_function to finish. In fact you will see "before call ..." and "after call ..." -printed out right away. The long-running function will run in the background and you (and other -users) can go on as normal.

-
-
-

Customizing asynchronous operation

-

A complication with using asynchronous calls is what to do with the result from that call. What if -long_running_function returns a value that you need? It makes no real sense to put any lines of -code after the call to try to deal with the result from long_running_function above - as we saw -the "after call ..." got printed long before long_running_function was finished, making that -line quite pointless for processing any data from the function. Instead one has to use callbacks.

-

utils.run_async takes reserved kwargs that won’t be passed into the long-running function:

-
    -
  • at_return(r) (the callback) is called when the asynchronous function (long_running_function -above) finishes successfully. The argument r will then be the return value of that function (or -None).

    -
        def at_return(r):
    -        print(r)
    -
    -
    -
  • -
  • at_return_kwargs - an optional dictionary that will be fed as keyword arguments to the -at_return callback.

  • -
  • at_err(e) (the errback) is called if the asynchronous function fails and raises an exception. -This exception is passed to the errback wrapped in a Failure object e. If you do not supply an -errback of your own, Evennia will automatically add one that silently writes errors to the evennia -log. An example of an errback is found below:

  • -
-
        def at_err(e):
-            print("There was an error:", str(e))
-
-
-
    -
  • at_err_kwargs - an optional dictionary that will be fed as keyword arguments to the at_err -errback.

  • -
-

An example of making an asynchronous call from inside a Command definition:

-
    from evennia import utils, Command
-
-    class CmdAsync(Command):
-
-       key = "asynccommand"
-
-       def func(self):
-
-           def long_running_function():
-               #[... lots of time-consuming code  ...]
-               return final_value
-
-           def at_return_function(r):
-               self.caller.msg("The final value is %s" % r)
-
-           def at_err_function(e):
-               self.caller.msg("There was an error: %s" % e)
-
-           # do the async call, setting all callbacks
-           utils.run_async(long_running_function, at_return=at_return_function,
-at_err=at_err_function)
-
-
-

That’s it - from here on we can forget about long_running_function and go on with what else need -to be done. Whenever it finishes, the at_return_function function will be called and the final -value will -pop up for us to see. If not we will see an error message.

-
-
-

delay

-

The delay function is a much simpler sibling to run_async. It is in fact just a way to delay the -execution of a command until a future time. This is equivalent to something like time.sleep() -except delay is asynchronous while sleep would lock the entire server for the duration of the -sleep.

-
     from evennia.utils import delay
-
-     # [...]
-     # e.g. inside a Command, where `self.caller` is available
-     def callback(obj):
-        obj.msg("Returning!")
-     delay(10, callback, self.caller)
-
-
-

This will delay the execution of the callback for 10 seconds. This function is explored much more in -the Command Duration Tutorial.

-

You can also try the following snippet just see how it works:

-
@py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self)
-
-
-

Wait 10 seconds and ‘Test!’ should be echoed back to you.

-
-
-

The @interactive decorator

-

As of Evennia 0.9, the @interactive decorator -is available. This makes any function or method possible to ‘pause’ and/or await player input -in an interactive way.

-
    from evennia.utils import interactive
-
-    @interactive
-    def myfunc(caller):
-
-      while True:
-          caller.msg("Getting ready to wait ...")
-          yield(5)
-          caller.msg("Now 5 seconds have passed.")
-
-          response = yield("Do you want to wait another 5 secs?")
-
-          if response.lower() not in ("yes", "y"):
-              break
-
-
-

The @interactive decorator gives the function the ability to pause. The use -of yield(seconds) will do just that - it will asynchronously pause for the -number of seconds given before continuing. This is technically equivalent to -using call_async with a callback that continues after 5 secs. But the code -with @interactive is a little easier to follow.

-

Within the @interactive function, the response = yield("question") question -allows you to ask the user for input. You can then process the input, just like -you would if you used the Python input function. There is one caveat to this -functionality though - it will only work if the function/method has an -argument named exactly caller. This is because internally Evennia will look -for the caller argument and treat that as the source of input.

-

All of this makes the @interactive decorator very useful. But it comes with a -few caveats. Notably, decorating a function/method with @interactive turns it -into a Python generator. The most -common issue is that you cannot use return <value> from a generator (just an -empty return works). To return a value from a function/method you have decorated -with @interactive, you must instead use a special Twisted function -twisted.internet.defer.returnValue. Evennia also makes this function -conveniently available from evennia.utils:

-
    from evennia.utils import interactive, returnValue
-
-    @interactive
-    def myfunc():
-
-        # ...
-        result = 10
-
-        # this must be used instead of `return result`
-        returnValue(result)
-
-
-
-
-
-

Assorted notes

-

Overall, be careful with choosing when to use asynchronous calls. It is mainly useful for large -administration operations that have no direct influence on the game world (imports and backup -operations come to mind). Since there is no telling exactly when an asynchronous call actually ends, -using them for in-game commands is to potentially invite confusion and inconsistencies (and very -hard-to-reproduce bugs).

-

The very first synchronous example above is not really correct in the case of Twisted, which is -inherently an asynchronous server. Notably you might find that you will not see the first before call ... text being printed out right away. Instead all texts could end up being delayed until -after the long-running process finishes. So all commands will retain their relative order as -expected, but they may appear with delays or in groups.

-
-
-

Further reading

-

Technically, run_async is just a very thin and simplified wrapper around a -Twisted Deferred object; the -wrapper sets -up a default errback also if none is supplied. If you know what you are doing there is nothing -stopping you from bypassing the utility function, building a more sophisticated callback chain after -your own liking.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Attributes.html b/docs/0.9.5/Attributes.html deleted file mode 100644 index 6038288904..0000000000 --- a/docs/0.9.5/Attributes.html +++ /dev/null @@ -1,485 +0,0 @@ - - - - - - - - - Attributes — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Attributes

-

When performing actions in Evennia it is often important that you store data for later. If you write -a menu system, you have to keep track of the current location in the menu tree so that the player -can give correct subsequent commands. If you are writing a combat system, you might have a -combattant’s next roll get easier dependent on if their opponent failed. Your characters will -probably need to store roleplaying-attributes like strength and agility. And so on.

-

Typeclassed game entities (Accounts, Objects, -Scripts and Channels) always have Attributes associated with them. -Attributes are used to store any type of data ‘on’ such entities. This is different from storing -data in properties already defined on entities (such as key or location) - these have very -specific names and require very specific types of data (for example you couldn’t assign a python -list to the key property no matter how hard you tried). Attributes come into play when you -want to assign arbitrary data to arbitrary names.

-

Attributes are not secure by default and any player may be able to change them unless you -prevent this behavior.

-
-

The .db and .ndb shortcuts

-

To save persistent data on a Typeclassed object you normally use the db (DataBase) operator. Let’s -try to save some data to a Rose (an Object):

-
    # saving
-    rose.db.has_thorns = True
-    # getting it back
-    is_ouch = rose.db.has_thorns
-
-
-
-

This looks like any normal Python assignment, but that db makes sure that an Attribute is -created behind the scenes and is stored in the database. Your rose will continue to have thorns -throughout the life of the server now, until you deliberately remove them.

-

To be sure to save non-persistently, i.e. to make sure NOT to create a database entry, you use -ndb (NonDataBase). It works in the same way:

-
    # saving
-    rose.ndb.has_thorns = True
-    # getting it back
-    is_ouch = rose.ndb.has_thorns
-
-
-

Technically, ndb has nothing to do with Attributes, despite how similar they look. No -Attribute object is created behind the scenes when using ndb. In fact the database is not -invoked at all since we are not interested in persistence. There is however an important reason to -use ndb to store data rather than to just store variables direct on entities - ndb-stored data -is tracked by the server and will not be purged in various cache-cleanup operations Evennia may do -while it runs. Data stored on ndb (as well as db) will also be easily listed by example the -@examine command.

-

You can also del properties on db and ndb as normal. This will for example delete an -Attribute:

-
    del rose.db.has_thorns
-
-
-

Both db and ndb defaults to offering an all property on themselves. This returns all -associated attributes or non-persistent properties.

-
     list_of_all_rose_attributes = rose.db.all
-     list_of_all_rose_ndb_attrs = rose.ndb.all
-
-
-

If you use all as the name of an attribute, this will be used instead. Later deleting your custom -all will return the default behaviour.

-
-
-

The AttributeHandler

-

The .db and .ndb properties are very convenient but if you don’t know the name of the Attribute -beforehand they cannot be used. Behind the scenes .db actually accesses the AttributeHandler -which sits on typeclassed entities as the .attributes property. .ndb does the same for the -.nattributes property.

-

The handlers have normal access methods that allow you to manage and retrieve Attributes and -NAttributes:

-
    -
  • has('attrname') - this checks if the object has an Attribute with this key. This is equivalent -to doing obj.db.attrname.

  • -
  • get(...) - this retrieves the given Attribute. Normally the value property of the Attribute is -returned, but the method takes keywords for returning the Attribute object itself. By supplying an -accessing_object to the call one can also make sure to check permissions before modifying -anything.

  • -
  • add(...) - this adds a new Attribute to the object. An optional lockstring can be -supplied here to restrict future access and also the call itself may be checked against locks.

  • -
  • remove(...) - Remove the given Attribute. This can optionally be made to check for permission -before performing the deletion. - clear(...) - removes all Attributes from object.

  • -
  • all(...) - returns all Attributes (of the given category) attached to this object.

  • -
-

See this section for more about locking down Attribute -access and editing. The Nattribute offers no concept of access control.

-

Some examples:

-
    import evennia
-    obj = evennia.search_object("MyObject")
-
-    obj.attributes.add("test", "testvalue")
-    print(obj.db.test)                 # prints "testvalue"
-    print(obj.attributes.get("test"))  #       "
-    print(obj.attributes.all())        # prints [<AttributeObject>]
-    obj.attributes.remove("test")
-
-
-
-
-

Properties of Attributes

-

An Attribute object is stored in the database. It has the following properties:

-
    -
  • key - the name of the Attribute. When doing e.g. obj.db.attrname = value, this property is set -to attrname.

  • -
  • value - this is the value of the Attribute. This value can be anything which can be pickled - -objects, lists, numbers or what have you (see -this section for more info). In the -example -obj.db.attrname = value, the value is stored here.

  • -
  • category - this is an optional property that is set to None for most Attributes. Setting this -allows to use Attributes for different functionality. This is usually not needed unless you want -to use Attributes for very different functionality (Nicks is an example of using -Attributes -in this way). To modify this property you need to use the Attribute -Handler.

  • -
  • strvalue - this is a separate value field that only accepts strings. This severely limits the -data possible to store, but allows for easier database lookups. This property is usually not used -except when re-using Attributes for some other purpose (Nicks use it). It is only -accessible via the Attribute Handler.

  • -
-

There are also two special properties:

-
    -
  • attrtype - this is used internally by Evennia to separate Nicks, from Attributes (Nicks -use Attributes behind the scenes).

  • -
  • model - this is a natural-key describing the model this Attribute is attached to. This is on -the form appname.modelclass, like objects.objectdb. It is used by the Attribute and -NickHandler to quickly sort matches in the database. Neither this nor attrtype should normally -need to be modified.

  • -
-

Non-database attributes have no equivalence to category nor strvalue, attrtype or model.

-
-
-

Persistent vs non-persistent

-

So persistent data means that your data will survive a server reboot, whereas with -non-persistent data it will not …

-

… So why would you ever want to use non-persistent data? The answer is, you don’t have to. Most of -the time you really want to save as much as you possibly can. Non-persistent data is potentially -useful in a few situations though.

-
    -
  • You are worried about database performance. Since Evennia caches Attributes very aggressively, -this is not an issue unless you are reading and writing to your Attribute very often (like many -times per second). Reading from an already cached Attribute is as fast as reading any Python -property. But even then this is not likely something to worry about: Apart from Evennia’s own -caching, modern database systems themselves also cache data very efficiently for speed. Our -default -database even runs completely in RAM if possible, alleviating much of the need to write to disk -during heavy loads.

  • -
  • A more valid reason for using non-persistent data is if you want to lose your state when logging -off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you -are implementing some caching of your own. Or maybe you are testing a buggy Script that -does potentially harmful stuff to your character object. With non-persistent storage you can be -sure -that whatever is messed up, it’s nothing a server reboot can’t clear up.

  • -
  • NAttributes have no restrictions at all on what they can store (see next section), since they -don’t need to worry about being saved to the database - they work very well for temporary storage.

  • -
  • You want to implement a fully or partly non-persistent world. Who are we to argue with your -grand vision!

  • -
-
-
-

What types of data can I save in an Attribute?

-
-

None of the following affects NAttributes, which does not invoke the database at all. There are no -restrictions to what can be stored in a NAttribute.

-
-

The database doesn’t know anything about Python objects, so Evennia must serialize Attribute -values into a string representation in order to store it to the database. This is done using the -pickle module of Python (the only exception is if you use the strattr keyword of the -AttributeHandler to save to the strvalue field of the Attribute. In that case you can only save -strings which will not be pickled).

-

It’s important to note that when you access the data in an Attribute you are always de-serializing -it from the database representation every time. This is because we allow for storing -database-entities in Attributes too. If we cached it as its Python form, we might end up with -situations where the database entity was deleted since we last accessed the Attribute. -De-serializing data with a database-entity in it means querying the database for that object and -making sure it still exists (otherwise it will be set to None). Performance-wise this is usually -not a big deal. But if you are accessing the Attribute as part of some big loop or doing a large -amount of reads/writes you should first extract it to a temporary variable, operate on that and -then save the result back to the Attribute. If you are storing a more complex structure like a -dict or a list you should make sure to “disconnect” it from the database before looping over it, -as mentioned in the Retrieving Mutable Objects section -below.

-
-

Storing single objects

-

With a single object, we mean anything that is not iterable, like numbers, strings or custom class -instances without the __iter__ method.

-
    -
  • You can generally store any non-iterable Python entity that can be -pickled.

  • -
  • Single database objects/typeclasses can be stored as any other in the Attribute. These can -normally not be pickled, but Evennia will behind the scenes convert them to an internal -representation using their classname, database-id and creation-date with a microsecond precision, -guaranteeing you get the same object back when you access the Attribute later.

  • -
  • If you hide a database object inside a non-iterable custom class (like stored as a variable -inside it), Evennia will not know it’s there and won’t convert it safely. Storing classes with -such hidden database objects is not supported and will lead to errors!

  • -
-
# Examples of valid single-value  attribute data:
-obj.db.test1 = 23
-obj.db.test1 = False
-# a database object (will be stored as an internal representation)
-obj.db.test2 = myobj
-
-# example of an invalid, "hidden" dbobject
-class Invalid(object):
-    def __init__(self, dbobj):
-        # no way for Evennia to know this is a dbobj
-        self.dbobj = dbobj
-invalid = Invalid(myobj)
-obj.db.invalid = invalid # will cause error!
-
-
-
-
-

Storing multiple objects

-

This means storing objects in a collection of some kind and are examples of iterables, pickle-able -entities you can loop over in a for-loop. Attribute-saving supports the following iterables:

-
    -
  • Tuples, like (1,2,"test", <dbobj>).

  • -
  • Lists, like [1,2,"test", <dbobj>].

  • -
  • Dicts, like {1:2, "test":<dbobj>].

  • -
  • Sets, like {1,2,"test",<dbobj>}.

  • -
  • collections.OrderedDict, like OrderedDict((1,2), ("test", <dbobj>)).

  • -
  • collections.Deque, like -deque((1,2,"test",<dbobj>)).

  • -
  • Nestings of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each -containing dicts, etc.

  • -
  • All other iterables (i.e. entities with the __iter__ method) will be converted to a list. -Since you can use any combination of the above iterables, this is generally not much of a -limitation.

  • -
-

Any entity listed in the Single object section above can be -stored in the iterable.

-
-

As mentioned in the previous section, database entities (aka typeclasses) are not possible to -pickle. So when storing an iterable, Evennia must recursively traverse the iterable and all its -nested sub-iterables in order to find eventual database objects to convert. This is a very fast -process but for efficiency you may want to avoid too deeply nested structures if you can.

-
-
# examples of valid iterables to store
-obj.db.test3 = [obj1, 45, obj2, 67]
-# a dictionary
-obj.db.test4 = {'str':34, 'dex':56, 'agi':22, 'int':77}
-# a mixed dictionary/list
-obj.db.test5 = {'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5]}
-# a tuple with a list in it
-obj.db.test6 = (1,3,4,8, ["test", "test2"], 9)
-# a set
-obj.db.test7 = set([1,2,3,4,5])
-# in-situ manipulation
-obj.db.test8 = [1,2,{"test":1}]
-obj.db.test8[0] = 4
-obj.db.test8[2]["test"] = 5
-# test8 is now [4,2,{"test":5}]
-
-
-
-
-

Retrieving Mutable objects

-

A side effect of the way Evennia stores Attributes is that mutable iterables (iterables that can -be modified in-place after they were created, which is everything except tuples) are handled by -custom objects called _SaverList, _SaverDict etc. These _Saver... classes behave just like the -normal variant except that they are aware of the database and saves to it whenever new data gets -assigned to them. This is what allows you to do things like self.db.mylist[7] = val and be sure -that the new version of list is saved. Without this you would have to load the list into a temporary -variable, change it and then re-assign it to the Attribute in order for it to save.

-

There is however an important thing to remember. If you retrieve your mutable iterable into another -variable, e.g. mylist2 = obj.db.mylist, your new variable (mylist2) will still be a -_SaverList. This means it will continue to save itself to the database whenever it is updated!

-
     obj.db.mylist = [1,2,3,4]
-     mylist = obj.db.mylist
-     mylist[3] = 5 # this will also update database
-     print(mylist) # this is now [1,2,3,5]
-     print(obj.db.mylist) # this is also [1,2,3,5]
-
-
-

To “disconnect” your extracted mutable variable from the database you simply need to convert the -_Saver... iterable to a normal Python structure. So to convert a _SaverList, you use the -list() function, for a _SaverDict you use dict() and so on.

-
     obj.db.mylist = [1,2,3,4]
-     mylist = list(obj.db.mylist) # convert to normal list
-     mylist[3] = 5
-     print(mylist) # this is now [1,2,3,5]
-     print(obj.db.mylist) # this is still [1,2,3,4]
-
-
-

A further problem comes with nested mutables, like a dict containing lists of dicts or something -like that. Each of these nested mutables would be _Saver* structures connected to the database and -disconnecting the outermost one of them would not disconnect those nested within. To make really -sure you disonnect a nested structure entirely from the database, Evennia provides a special -function evennia.utils.dbserialize.deserialize:

-
from evennia.utils.dbserialize import deserialize
-
-decoupled_mutables = deserialize(nested_mutables)
-
-
-
-

The result of this operation will be a structure only consisting of normal Python mutables (list -instead of _SaverList and so on).

-

Remember, this is only valid for mutable iterables. -Immutable objects (strings, numbers, tuples etc) are -already disconnected from the database from the onset.

-
     obj.db.mytup = (1,2,[3,4])
-     obj.db.mytup[0] = 5 # this fails since tuples are immutable
-
-     # this works but will NOT update database since outermost is a tuple
-     obj.db.mytup[2][1] = 5
-     print(obj.db.mytup[2][1]) # this still returns 4, not 5
-
-     mytup1 = obj.db.mytup # mytup1 is already disconnected from database since outermost
-                           # iterable is a tuple, so we can edit the internal list as we want
-                           # without affecting the database.
-
-
-
-

Attributes will fetch data fresh from the database whenever you read them, so -if you are performing big operations on a mutable Attribute property (such as looping over a list -or dict) you should make sure to “disconnect” the Attribute’s value first and operate on this -rather than on the Attribute. You can gain dramatic speed improvements to big loops this -way.

-
-
-
-
-

Locking and checking Attributes

-

Attributes are normally not locked down by default, but you can easily change that for individual -Attributes (like those that may be game-sensitive in games with user-level building).

-

First you need to set a lock string on your Attribute. Lock strings are specified Locks. -The relevant lock types are

-
    -
  • attrread - limits who may read the value of the Attribute

  • -
  • attredit - limits who may set/change this Attribute

  • -
-

You cannot use the db handler to modify Attribute object (such as setting a lock on them) - The -db handler will return the Attribute’s value, not the Attribute object itself. Instead you use -the AttributeHandler and set it to return the object instead of the value:

-
     lockstring = "attread:all();attredit:perm(Admins)"
-     obj.attributes.get("myattr", return_obj=True).locks.add(lockstring)
-
-
-

Note the return_obj keyword which makes sure to return the Attribute object so its LockHandler -could be accessed.

-

A lock is no good if nothing checks it – and by default Evennia does not check locks on Attributes. -You have to add a check to your commands/code wherever it fits (such as before setting an -Attribute).

-
    # in some command code where we want to limit
-    # setting of a given attribute name on an object
-    attr = obj.attributes.get(attrname,
-                              return_obj=True,
-                              accessing_obj=caller,
-                              default=None,
-                              default_access=False)
-    if not attr:
-        caller.msg("You cannot edit that Attribute!")
-        return
-    # edit the Attribute here
-
-
-

The same keywords are available to use with obj.attributes.set() and obj.attributes.remove(), -those will check for the attredit lock type.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Banning.html b/docs/0.9.5/Banning.html deleted file mode 100644 index 3a080f74cd..0000000000 --- a/docs/0.9.5/Banning.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - Banning — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Banning

-

Whether due to abuse, blatant breaking of your rules, or some other reason, you will eventually find -no other recourse but to kick out a particularly troublesome player. The default command set has -admin tools to handle this, primarily ban, unban, and boot.

-
-

Creating a ban

-

Say we have a troublesome player “YouSuck” - this is a person that refuses common courtesy - an -abusive -and spammy account that is clearly created by some bored internet hooligan only to cause grief. You -have tried to be nice. Now you just want this troll gone.

-
-

Name ban

-

The easiest recourse is to block the account YouSuck from ever connecting again.

-
 ban YouSuck
-
-
-

This will lock the name YouSuck (as well as ‘yousuck’ and any other capitalization combination), and -next time they try to log in with this name the server will not let them!

-

You can also give a reason so you remember later why this was a good thing (the banned account will -never see this)

-
 ban YouSuck:This is just a troll.
-
-
-

If you are sure this is just a spam account, you might even consider deleting the player account -outright:

-
 account/delete YouSuck
-
-
-

Generally, banning the name is the easier and safer way to stop the use of an account – if you -change your mind you can always remove the block later whereas a deletion is permanent.

-
-
-

IP ban

-

Just because you block YouSuck’s name might not mean the trolling human behind that account gives -up. They can just create a new account YouSuckMore and be back at it. One way to make things harder -for them is to tell the server to not allow connections from their particular IP address.

-

First, when the offending account is online, check which IP address they use. This you can do with -the who command, which will show you something like this:

-
 Account Name     On for     Idle     Room     Cmds     Host
- YouSuckMore      01:12      2m       22       212      237.333.0.223
-
-
-

The “Host” bit is the IP address from which the account is connecting. Use this to define the ban -instead of the name:

-
 ban 237.333.0.223
-
-
-

This will stop YouSuckMore connecting from their computer. Note however that IP address might change -easily - either due to how the player’s Internet Service Provider operates or by the user simply -changing computers. You can make a more general ban by putting asterisks * as wildcards for the -groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from -237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea -to put down a ban like this to include any number in that subnet:

-
 ban 237.333.0.*
-
-
-

You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly -locked regardless of where they connect from.

-

Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be -blocking out innocent players who just happen to connect from the same subnet as the offender.

-
-
-
-

Booting

-

YouSuck is not really noticing all this banning yet though - and won’t until having logged out and -trying to log back in again. Let’s help the troll along.

-
 boot YouSuck
-
-
-

Good riddance. You can give a reason for booting too (to be echoed to the player before getting -kicked out).

-
 boot YouSuck:Go troll somewhere else.
-
-
-
-

Lifting a ban

-

Use the unban (or ban) command without any arguments and you will see a list of all currently -active bans:

-
Active bans
-id   name/ip       date                      reason
-1    yousuck       Fri Jan 3 23:00:22 2020   This is just a Troll.
-2    237.333.0.*   Fri Jan 3 23:01:03 2020   YouSuck's IP.
-
-
-

Use the id from this list to find out which ban to lift.

-
 unban 2
-  
-Cleared ban 2: 237.333.0.*
-
-
-
-
-
-

Summary of abuse-handling tools

-

Below are other useful commands for dealing with annoying players.

-
    -
  • who – (as admin) Find the IP of a account. Note that one account can be connected to from -multiple IPs depending on what you allow in your settings.

  • -
  • examine/account thomas – Get all details about an account. You can also use *thomas to get -the account. If not given, you will get the Object thomas if it exists in the same location, which -is not what you want in this case.

  • -
  • boot thomas – Boot all sessions of the given account name.

  • -
  • boot 23 – Boot one specific client session/IP by its unique id.

  • -
  • ban – List all bans (listed with ids)

  • -
  • ban thomas – Ban the user with the given account name

  • -
  • ban/ip 134.233.2.111 – Ban by IP

  • -
  • ban/ip 134.233.2.* – Widen IP ban

  • -
  • ban/ip 134.233.*.* – Even wider IP ban

  • -
  • unban 34 – Remove ban with id #34

  • -
  • cboot mychannel = thomas – Boot a subscriber from a channel you control

  • -
  • clock mychannel = control:perm(Admin);listen:all();send:all() – Fine control of access to -your channel using lock definitions.

  • -
-

Locking a specific command (like page) is accomplished like so:

-
    -
  1. Examine the source of the command. The default page command class has the lock -string “cmd:not pperm(page_banned)”. This means that unless the player has the ‘permission’ -“page_banned” they can use this command. You can assign any lock string to allow finer customization -in your commands. You might look for the value of an Attribute or Tag, your -current location etc.

  2. -
  3. perm/account thomas = page_banned – Give the account the ‘permission’ which causes (in this -case) the lock to fail.

  4. -
-
    -
  • perm/del/account thomas = page_banned – Remove the given permission

  • -
  • tel thomas = jail – Teleport a player to a specified location or #dbref

  • -
  • type thomas = FlowerPot – Turn an annoying player into a flower pot (assuming you have a -FlowerPot typeclass ready)

  • -
  • userpassword thomas = fooBarFoo – Change a user’s password

  • -
  • account/delete thomas – Delete a player account (not recommended, use ban instead)

  • -
  • server – Show server statistics, such as CPU load, memory usage, and how many objects are -cached

  • -
  • time – Gives server uptime, runtime, etc

  • -
  • reload – Reloads the server without disconnecting anyone

  • -
  • reset – Restarts the server, kicking all connections

  • -
  • shutdown – Stops the server cold without it auto-starting again

  • -
  • py – Executes raw Python code, allows for direct inspection of the database and account -objects on the fly. For advanced users.

  • -
-

Useful Tip: evennia changepassword <username> entered into the command prompt will reset the -password of any account, including the superuser or admin accounts. This is a feature of Django.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Batch-Code-Processor.html b/docs/0.9.5/Batch-Code-Processor.html deleted file mode 100644 index 3f65c6ad7e..0000000000 --- a/docs/0.9.5/Batch-Code-Processor.html +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - - - - Batch Code Processor — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Batch Code Processor

-

For an introduction and motivation to using batch processors, see here. This -page describes the Batch-code processor. The Batch-command one is covered [here](Batch-Command- -Processor).

-
-

Basic Usage

-

The batch-code processor is a superuser-only function, invoked by

-
 > @batchcode path.to.batchcodefile
-
-
-

Where path.to.batchcodefile is the path to a batch-code file. Such a file should have a name -ending in “.py” (but you shouldn’t include that in the path). The path is given like a python path -relative to a folder you define to hold your batch files, set by BATCH_IMPORT_PATH in your -settings. Default folder is (assuming your game is called “mygame”) mygame/world/. So if you want -to run the example batch file in mygame/world/batch_code.py, you could simply use

-
 > @batchcode batch_code
-
-
-

This will try to run through the entire batch file in one go. For more gradual, interactive -control you can use the /interactive switch. The switch /debug will put the processor in -debug mode. Read below for more info.

-
-
-

The batch file

-

A batch-code file is a normal Python file. The difference is that since the batch processor loads -and executes the file rather than importing it, you can reliably update the file, then call it -again, over and over and see your changes without needing to @reload the server. This makes for -easy testing. In the batch-code file you have also access to the following global variables:

-
    -
  • caller - This is a reference to the object running the batchprocessor.

  • -
  • DEBUG - This is a boolean that lets you determine if this file is currently being run in debug- -mode or not. See below how this can be useful.

  • -
-

Running a plain Python file through the processor will just execute the file from beginning to end. -If you want to get more control over the execution you can use the processor’s interactive mode. -This runs certain code blocks on their own, rerunning only that part until you are happy with it. In -order to do this you need to add special markers to your file to divide it up into smaller chunks. -These take the form of comments, so the file remains valid Python.

-

Here are the rules of syntax of the batch-code *.py file.

-
    -
  • #CODE as the first on a line marks the start of a code block. It will last until the beginning -of another marker or the end of the file. Code blocks contain functional python code. Each #CODE -block will be run in complete isolation from other parts of the file, so make sure it’s self- -contained.

  • -
  • #HEADER as the first on a line marks the start of a header block. It lasts until the next -marker or the end of the file. This is intended to hold imports and variables you will need for all -other blocks .All python code defined in a header block will always be inserted at the top of every -#CODE blocks in the file. You may have more than one #HEADER block, but that is equivalent to -having one big one. Note that you can’t exchange data between code blocks, so editing a header- -variable in one code block won’t affect that variable in any other code block!

  • -
  • #INSERT path.to.file will insert another batchcode (Python) file at that position.

  • -
  • A # that is not starting a #HEADER, #CODE or #INSERT instruction is considered a comment.

  • -
  • Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as -a separate python module.

  • -
-

Below is a version of the example file found in evennia/contrib/tutorial_examples/.

-
    #
-    # This is an example batch-code build file for Evennia.
-    #
-    
-    #HEADER
-    
-    # This will be included in all other #CODE blocks
-    
-    from evennia import create_object, search_object
-    from evennia.contrib.tutorial_examples import red_button
-    from typeclasses.objects import Object
-    
-    limbo = search_object('Limbo')[0]
-    
-    
-    #CODE
- 
-    red_button = create_object(red_button.RedButton, key="Red button",
-                               location=limbo, aliases=["button"])
-    
-    # caller points to the one running the script
-    caller.msg("A red button was created.")
-    
-    # importing more code from another batch-code file
-    #INSERT batch_code_insert
-    
-    #CODE
-    
-    table = create_object(Object, key="Blue Table", location=limbo)
-    chair = create_object(Object, key="Blue Chair", location=limbo)
-    
-    string = "A %s and %s were created."
-    if DEBUG:
-        table.delete()
-        chair.delete()
-        string += " Since debug was active, " \
-             "they were deleted again."
-    caller.msg(string % (table, chair))
-
-
-

This uses Evennia’s Python API to create three objects in sequence.

-
-
-

Debug mode

-

Try to run the example script with

-
 > @batchcode/debug tutorial_examples.example_batch_code
-
-
-

The batch script will run to the end and tell you it completed. You will also get messages that the -button and the two pieces of furniture were created. Look around and you should see the button -there. But you won’t see any chair nor a table! This is because we ran this with the /debug -switch, which is directly visible as DEBUG==True inside the script. In the above example we -handled this state by deleting the chair and table again.

-

The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for -bugs in your code or try to see if things behave as they should. Running the script over and over -would then create an ever-growing stack of chairs and tables, all with the same name. You would have -to go back and painstakingly delete them later.

-
-
-

Interactive mode

-

Interactive mode works very similar to the [batch-command processor counterpart](Batch-Command- -Processor). It allows you more step-wise control over how the batch file is executed. This is useful -for debugging or for picking and choosing only particular blocks to run. Use @batchcode with the -/interactive flag to enter interactive mode.

-
 > @batchcode/interactive tutorial_examples.example_batch_code
-
-
-

You should see the following:

-
01/02: red_button = create_object(red_button.RedButton, [...]         (hh for help)
-
-
-

This shows that you are on the first #CODE block, the first of only two commands in this batch -file. Observe that the block has not actually been executed at this point!

-

To take a look at the full code snippet you are about to run, use ll (a batch-processor version of -look).

-
    from evennia.utils import create, search
-    from evennia.contrib.tutorial_examples import red_button
-    from typeclasses.objects import Object
-    
-    limbo = search.objects(caller, 'Limbo', global_search=True)[0]
-
-    red_button = create.create_object(red_button.RedButton, key="Red button",
-                                      location=limbo, aliases=["button"])
-    
-    # caller points to the one running the script
-    caller.msg("A red button was created.")
-
-
-

Compare with the example code given earlier. Notice how the content of #HEADER has been pasted at -the top of the #CODE block. Use pp to actually execute this block (this will create the button -and give you a message). Use nn (next) to go to the next command. Use hh for a list of commands.

-

If there are tracebacks, fix them in the batch file, then use rr to reload the file. You will -still be at the same code block and can rerun it easily with pp as needed. This makes for a simple -debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large -batch file this can be very useful (don’t forget the /debug mode either).

-

Use nn and bb (next and back) to step through the file; e.g. nn 12 will jump 12 steps forward -(without processing any blocks in between). All normal commands of Evennia should work too while -working in interactive mode.

-
-
-

Limitations and Caveats

-

The batch-code processor is by far the most flexible way to build a world in Evennia. There are -however some caveats you need to keep in mind.

-
-

Safety

-

Or rather the lack of it. There is a reason only superusers are allowed to run the batch-code -processor by default. The code-processor runs without any Evennia security checks and allows -full access to Python. If an untrusted party could run the code-processor they could execute -arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to -allow other users to access the batch-code processor you should make sure to run Evennia as a -separate and very limited-access user on your machine (i.e. in a ‘jail’). By comparison, the batch- -command processor is much safer since the user running it is still ‘inside’ the game and can’t -really do anything outside what the game commands allow them to.

-
-
-

No communication between code blocks

-

Global variables won’t work in code batch files, each block is executed as stand-alone environments. -#HEADER blocks are literally pasted on top of each #CODE block so updating some header-variable -in your block will not make that change available in another block. Whereas a python execution -limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode

-
    -
  • this would be a classical example of “spaghetti code”.

  • -
-

The main practical issue with this is when building e.g. a room in one code block and later want to -connect that room with a room you built in the current block. There are two ways to do this:

-
    -
  • Perform a database search for the name of the room you created (since you cannot know in advance -which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of “A -dark forest” rooms). There is an easy way to handle this though - use Tags or Aliases. You -can assign any number of tags and/or aliases to any object. Make sure that one of those tags or -aliases is unique to the room (like “room56”) and you will henceforth be able to always uniquely -search and find it later.

  • -
  • Use the caller global property as an inter-block storage. For example, you could have a -dictionary of room references in an ndb:

    -
    #HEADER
    -if caller.ndb.all_rooms is None:
    -    caller.ndb.all_rooms = {}
    -
    -#CODE
    -# create and store the castle
    -castle = create_object("rooms.Room", key="Castle")
    -caller.ndb.all_rooms["castle"] = castle
    -
    -#CODE
    -# in another node we want to access the castle
    -castle = caller.ndb.all_rooms.get("castle")
    -
    -
    -
  • -
-

Note how we check in #HEADER if caller.ndb.all_rooms doesn’t already exist before creating the -dict. Remember that #HEADER is copied in front of every #CODE block. Without that if statement -we’d be wiping the dict every block!

-
-
-

Don’t treat a batchcode file like any Python file

-

Despite being a valid Python file, a batchcode file should only be run by the batchcode processor. -You should not do things like define Typeclasses or Commands in them, or import them into other -code. Importing a module in Python will execute base level of the module, which in the case of your -average batchcode file could mean creating a lot of new objects every time.

-
-
-

Don’t let code rely on the batch-file’s real file path

-

When you import things into your batchcode file, don’t use relative imports but always import with -paths starting from the root of your game directory or evennia library. Code that relies on the -batch file’s “actual” location will fail. Batch code files are read as text and the strings -executed. When the code runs it has no knowledge of what file those strings where once a part of.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Batch-Command-Processor.html b/docs/0.9.5/Batch-Command-Processor.html deleted file mode 100644 index 97cf14f93d..0000000000 --- a/docs/0.9.5/Batch-Command-Processor.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - - - - Batch Command Processor — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Batch Command Processor

-

For an introduction and motivation to using batch processors, see here. This -page describes the Batch-command processor. The Batch-code one is covered [here](Batch-Code- -Processor).

-
-

Basic Usage

-

The batch-command processor is a superuser-only function, invoked by

-
 > @batchcommand path.to.batchcmdfile
-
-
-

Where path.to.batchcmdfile is the path to a batch-command file with the “.ev” file ending. -This path is given like a python path relative to a folder you define to hold your batch files, set -with BATCH_IMPORT_PATH in your settings. Default folder is (assuming your game is in the mygame -folder) mygame/world. So if you want to run the example batch file in -mygame/world/batch_cmds.ev, you could use

-
 > @batchcommand batch_cmds
-
-
-

A batch-command file contains a list of Evennia in-game commands separated by comments. The -processor will run the batch file from beginning to end. Note that it will not stop if commands in -it fail (there is no universal way for the processor to know what a failure looks like for all -different commands). So keep a close watch on the output, or use Interactive mode (see below) to -run the file in a more controlled, gradual manner.

-
-
-

The batch file

-

The batch file is a simple plain-text file containing Evennia commands. Just like you would write -them in-game, except you have more freedom with line breaks.

-

Here are the rules of syntax of an *.ev file. You’ll find it’s really, really simple:

-
    -
  • All lines having the # (hash)-symbol as the first one on the line are considered comments. -All non-comment lines are treated as a command and/or their arguments.

  • -
  • Comment lines have an actual function – they mark the end of the previous command definition. -So never put two commands directly after one another in the file - separate them with a comment, or -the second of the two will be considered an argument to the first one. Besides, using plenty of -comments is good practice anyway.

  • -
  • A line that starts with the word #INSERT is a comment line but also signifies a special -instruction. The syntax is #INSERT <path.batchfile> and tries to import a given batch-cmd file -into this one. The inserted batch file (file ending .ev) will run normally from the point of the -#INSERT instruction.

  • -
  • Extra whitespace in a command definition is ignored. - A completely empty line translates in to -a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant -for commands accepting such formatting, such as the @desc command).

  • -
  • The very last command in the file is not required to end with a comment.

  • -
  • You cannot nest another @batchcommand statement into your batch file. If you want to link many -batch-files together, use the #INSERT batch instruction instead. You also cannot launch the -@batchcode command from your batch file, the two batch processors are not compatible.

  • -
-

Below is a version of the example file found in evennia/contrib/tutorial_examples/batch_cmds.ev.

-
    #
-    # This is an example batch build file for Evennia.
-    #
-    
-    # This creates a red button
-    @create button:tutorial_examples.red_button.RedButton
-    # (This comment ends input for @create)
-    # Next command. Let's create something.
-    @set button/desc =
-      This is a large red button. Now and then
-      it flashes in an evil, yet strangely tantalizing way.
-    
-      A big sign sits next to it. It says:
-
-    
-    -----------
-    
-     Press me!
-    
-    -----------
-
-    
-      ... It really begs to be pressed! You
-    know you want to!
-    
-    # This inserts the commands from another batch-cmd file named
-    # batch_insert_file.ev.
-    #INSERT examples.batch_insert_file
-    
-      
-    # (This ends the @set command). Note that single line breaks
-    # and extra whitespace in the argument are ignored. Empty lines
-    # translate into line breaks in the output.
-    # Now let's place the button where it belongs (let's say limbo #2 is
-    # the evil lair in our example)
-    @teleport #2
-    # (This comments ends the @teleport command.)
-    # Now we drop it so others can see it.
-    # The very last command in the file needs not be ended with #.
-    drop button
-
-
-

To test this, run @batchcommand on the file:

-
> @batchcommand contrib.tutorial_examples.batch_cmds
-
-
-

A button will be created, described and dropped in Limbo. All commands will be executed by the user -calling the command.

-
-

Note that if you interact with the button, you might find that its description changes, loosing -your custom-set description above. This is just the way this particular object works.

-
-
-
-

Interactive mode

-

Interactive mode allows you to more step-wise control over how the batch file is executed. This is -useful for debugging and also if you have a large batch file and is only updating a small part of it -– running the entire file again would be a waste of time (and in the case of @create-ing objects -you would to end up with multiple copies of same-named objects, for example). Use @batchcommand -with the /interactive flag to enter interactive mode.

-
 > @batchcommand/interactive tutorial_examples.batch_cmds
-
-
-

You will see this:

-
01/04: @create button:tutorial_examples.red_button.RedButton  (hh for help)
-
-
-

This shows that you are on the @create command, the first out of only four commands in this batch -file. Observe that the command @create has not been actually processed at this point!

-

To take a look at the full command you are about to run, use ll (a batch-processor version of -look). Use pp to actually process the current command (this will actually @create the button) -– and make sure it worked as planned. Use nn (next) to go to the next command. Use hh for a -list of commands.

-

If there are errors, fix them in the batch file, then use rr to reload the file. You will still be -at the same command and can rerun it easily with pp as needed. This makes for a simple debug -cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch -file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g. -if @create in the example above had failed, the following commands would have had nothing to -operate on).

-

Use nn and bb (next and back) to step through the file; e.g. nn 12 will jump 12 steps forward -(without processing any command in between). All normal commands of Evennia should work too while -working in interactive mode.

-
-
-

Limitations and Caveats

-

The batch-command processor is great for automating smaller builds or for testing new commands and -objects repeatedly without having to write so much. There are several caveats you have to be aware -of when using the batch-command processor for building larger, complex worlds though.

-

The main issue is that when you run a batch-command script you (you, as in your superuser -character) are actually moving around in the game creating and building rooms in sequence, just as -if you had been entering those commands manually, one by one. You have to take this into account -when creating the file, so that you can ‘walk’ (or teleport) to the right places in order.

-

This also means there are several pitfalls when designing and adding certain types of objects. Here -are some examples:

-
    -
  • Rooms that change your Command Set: Imagine that you build a ‘dark’ room, which -severely limits the cmdsets of those entering it (maybe you have to find the light switch to -proceed). In your batch script you would create this room, then teleport to it - and promptly be -shifted into the dark state where none of your normal build commands work …

  • -
  • Auto-teleportation: Rooms that automatically teleport those that enter them to another place -(like a trap room, for example). You would be teleported away too.

  • -
  • Mobiles: If you add aggressive mobs, they might attack you, drawing you into combat. If they -have AI they might even follow you around when building - or they might move away from you before -you’ve had time to finish describing and equipping them!

  • -
-

The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever -effects are in play. Add an on/off switch to objects and make sure it’s always set to off upon -creation. It’s all doable, one just needs to keep it in mind.

-
-
-

Assorted notes

-

The fact that you build as ‘yourself’ can also be considered an advantage however, should you ever -decide to change the default command to allow others than superusers to call the processor. Since -normal access-checks are still performed, a malevolent builder with access to the processor should -not be able to do all that much damage (this is the main drawback of the Batch Code -Processor)

-
    -
  • GNU Emacs users might find it interesting to use emacs’ -evennia mode. This is an Emacs major mode found in evennia/utils/evennia-mode.el. It offers -correct syntax highlighting and indentation with <tab> when editing .ev files in Emacs. See the -header of that file for installation instructions.

  • -
  • VIM users can use amfl’s vim-evennia -mode instead, see its readme for install instructions.

  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Batch-Processors.html b/docs/0.9.5/Batch-Processors.html deleted file mode 100644 index 0125683cb1..0000000000 --- a/docs/0.9.5/Batch-Processors.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - Batch Processors — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Batch Processors

-

Building a game world is a lot of work, especially when starting out. Rooms should be created, -descriptions have to be written, objects must be detailed and placed in their proper places. In many -traditional MUD setups you had to do all this online, line by line, over a telnet session.

-

Evennia already moves away from much of this by shifting the main coding work to external Python -modules. But also building would be helped if one could do some or all of it externally. Enter -Evennia’s batch processors (there are two of them). The processors allows you, as a game admin, to -build your game completely offline in normal text files (batch files) that the processors -understands. Then, when you are ready, you use the processors to read it all into Evennia (and into -the database) in one go.

-

You can of course still build completely online should you want to - this is certainly the easiest -way to go when learning and for small build projects. But for major building work, the advantages of -using the batch-processors are many:

-
    -
  • It’s hard to compete with the comfort of a modern desktop text editor; Compared to a traditional -MUD line input, you can get much better overview and many more features. Also, accidentally pressing -Return won’t immediately commit things to the database.

  • -
  • You might run external spell checkers on your batch files. In the case of one of the batch- -processors (the one that deals with Python code), you could also run external debuggers and code -analyzers on your file to catch problems before feeding it to Evennia.

  • -
  • The batch files (as long as you keep them) are records of your work. They make a natural starting -point for quickly re-building your world should you ever decide to start over.

  • -
  • If you are an Evennia developer, using a batch file is a fast way to setup a test-game after -having reset the database.

  • -
  • The batch files might come in useful should you ever decide to distribute all or part of your -world to others.

  • -
-

There are two batch processors, the Batch-command processor and the Batch-code processor. The -first one is the simpler of the two. It doesn’t require any programming knowledge - you basically -just list in-game commands in a text file. The code-processor on the other hand is much more -powerful but also more complex - it lets you use Evennia’s API to code your world in full-fledged -Python code.

- -

If you plan to use international characters in your batchfiles you are wise to read about file -encodings below.

-
-

A note on File Encodings

-

As mentioned, both the processors take text files as input and then proceed to process them. As long -as you stick to the standard ASCII character set (which means -the normal English characters, basically) you should not have to worry much about this section.

-

Many languages however use characters outside the simple ASCII table. Common examples are various -apostrophes and umlauts but also completely different symbols like those of the greek or cyrillic -alphabets.

-

First, we should make it clear that Evennia itself handles international characters just fine. It -(and Django) uses unicode strings internally.

-

The problem is that when reading a text file like the batchfile, we need to know how to decode the -byte-data stored therein to universal unicode. That means we need an encoding (a mapping) for how -the file stores its data. There are many, many byte-encodings used around the world, with opaque -names such as Latin-1, ISO-8859-3 or ARMSCII-8 to pick just a few examples. Problem is that -it’s practially impossible to determine which encoding was used to save a file just by looking at it -(it’s just a bunch of bytes!). You have to know.

-

With this little introduction it should be clear that Evennia can’t guess but has to assume an -encoding when trying to load a batchfile. The text editor and Evennia must speak the same “language” -so to speak. Evennia will by default first try the international UTF-8 encoding, but you can have -Evennia try any sequence of different encodings by customizing the ENCODINGS list in your settings -file. Evennia will use the first encoding in the list that do not raise any errors. Only if none -work will the server give up and return an error message.

-

You can often change the text editor encoding (this depends on your editor though), otherwise you -need to add the editor’s encoding to Evennia’s ENCODINGS list. If you are unsure, write a test -file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works -as it should.

-

More help with encodings can be found in the entry Text Encodings and also in the -Wikipedia article here.

-

A footnote for the batch-code processor: Just because Evennia can parse your file and your -fancy special characters, doesn’t mean that Python allows their use. Python syntax only allows -international characters inside strings. In all other source code only ASCII set characters are -allowed.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Bootstrap-&-Evennia.html b/docs/0.9.5/Bootstrap-&-Evennia.html deleted file mode 100644 index 0867dc3d7e..0000000000 --- a/docs/0.9.5/Bootstrap-&-Evennia.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - - Bootstrap & Evennia — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Bootstrap & Evennia

-
-
-

What is Bootstrap?

-

Evennia’s new default web page uses a framework called Bootstrap. This -framework is in use across the internet - you’ll probably start to recognize its influence once you -learn some of the common design patterns. This switch is great for web developers, perhaps like -yourself, because instead of wondering about setting up different grid systems or what custom class -another designer used, we have a base, a bootstrap, to work from. Bootstrap is responsive by -default, and comes with some default styles that Evennia has lightly overrode to keep some of the -same colors and styles you’re used to from the previous design.

-

For your reading pleasure, a brief overview of Bootstrap follows. For more in-depth info, please -read the documentation.

-
-
-

The Layout System

-

Other than the basic styling Bootstrap includes, it also includes a built in layout and grid -system. -The first part of this system is the -container.

-

The container is meant to hold all your page content. Bootstrap provides two types: fixed-width and -full-width. -Fixed-width containers take up a certain max-width of the page - they’re useful for limiting the -width on Desktop or Tablet platforms, instead of making the content span the width of the page.

-
<div class="container">
-    <!--- Your content here -->
-</div>
-
-
-

Full width containers take up the maximum width available to them - they’ll span across a wide- -screen desktop or a smaller screen phone, edge-to-edge.

-
<div class="container-fluid">
-    <!--- This content will span the whole page -->
-</div>
-
-
-

The second part of the layout system is the grid. -This is the bread-and-butter of the layout of Bootstrap - it allows you to change the size of -elements depending on the size of the screen, without writing any media queries. We’ll briefly go -over it - to learn more, please read the docs or look at the source code for Evennia’s home page in -your browser.

-
-

Important! Grid elements should be in a .container or .container-fluid. This will center the -contents of your site.

-
-

Bootstrap’s grid system allows you to create rows and columns by applying classes based on -breakpoints. The default breakpoints are extra small, small, medium, large, and extra-large. If -you’d like to know more about these breakpoints, please take a look at the documentation for -them.

-

To use the grid system, first create a container for your content, then add your rows and columns -like so:

-
<div class="container">
-    <div class="row">
-        <div class="col">
-           1 of 3
-        </div>
-        <div class="col">
-           2 of 3
-        </div>
-        <div class="col">
-           3 of 3
-        </div>
-    </div>
-</div>
-
-
-

This layout would create three equal-width columns.

-

To specify your sizes - for instance, Evennia’s default site has three columns on desktop and -tablet, but reflows to single-column on smaller screens. Try it out!

-
<div class="container">
-    <div class="row">
-        <div class="col col-md-6 col-lg-3">
-            1 of 4
-        </div>
-        <div class="col col-md-6 col-lg-3">
-            2 of 4
-        </div>
-        <div class="col col-md-6 col-lg-3">
-            3 of 4
-        </div>
-        <div class="col col-md-6 col-lg-3">
-            4 of 4
-        </div>
-    </div>
-</div>
-
-
-

This layout would be 4 columns on large screens, 2 columns on medium screens, and 1 column on -anything smaller.

-

To learn more about Bootstrap’s grid, please take a look at the -docs

-
-
-
-

More Bootstrap

-

Bootstrap also provides a huge amount of utilities, as well as styling and content elements. To -learn more about them, please read the Bootstrap docs or read one of our other web tutorials.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Bootstrap-Components-and-Utilities.html b/docs/0.9.5/Bootstrap-Components-and-Utilities.html deleted file mode 100644 index 042d1019bf..0000000000 --- a/docs/0.9.5/Bootstrap-Components-and-Utilities.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - - Bootstrap Components and Utilities — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Bootstrap Components and Utilities

-

Bootstrap provides many utilities and components you can use when customizing Evennia’s web -presence. We’ll go over a few examples here that you might find useful.

-
-

Please take a look at either the basic web tutorial or the web -character view tutorial -to get a feel for how to add pages to Evennia’s website to test these examples.

-
-
-

General Styling

-

Bootstrap provides base styles for your site. These can be customized through CSS, but the default -styles are intended to provide a consistent, clean look for sites.

-
-

Color

-

Most elements can be styled with default colors. Take a look at the -documentation to learn more about these colors

-
    -
  • suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color -or background color.

  • -
-
-
-

Borders

-

Simply adding a class of ‘border’ to an element adds a border to the element. For more in-depth -info, please read the documentation on -borders..

-
<span class="border border-dark"></span>
-
-
-

You can also easily round corners just by adding a class.

-
<img src="..." class="rounded" />
-
-
-
-
-

Spacing

-

Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might -like to add margins or padding through CSS itself - however these classes are used in the default -Evennia site. Take a look at the docs to -learn more.

-
-
-
-
-

Components

-
-

Buttons

-

Buttons in Bootstrap are very easy to use - -button styling can be added to <button>, <a>, and <input> elements.

-
<a class="btn btn-primary" href="#" role="button">I'm a Button</a>
-<button class="btn btn-primary" type="submit">Me too!</button>
-<input class="btn btn-primary" type="button" value="Button">
-<input class="btn btn-primary" type="submit" value="Also a Button">
-<input class="btn btn-primary" type="reset" value="Button as Well">
-
-
-
-
-

Cards

-

Cards provide a container for other elements -that stands out from the rest of the page. The “Accounts”, “Recently Connected”, and “Database -Stats” on the default webpage are all in cards. Cards provide quite a bit of formatting options - -the following is a simple example, but read the documentation or look at the site’s source for more.

-
<div class="card">
-  <div class="card-body">
-    <h4 class="card-title">Card title</h4>
-    <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
-    <p class="card-text">Fancy, isn't it?</p>
-    <a href="#" class="card-link">Card link</a>
-  </div>
-</div>
-
-
-
-
-

Jumbotron

-

Jumbotrons are useful for featuring an -image or tagline for your game. They can flow with the rest of your content or take up the full -width of the page - Evennia’s base site uses the former.

-
<div class="jumbotron jumbotron-fluid">
-  <div class="container">
-    <h1 class="display-3">Full Width Jumbotron</h1>
-    <p class="lead">Look at the source of the default Evennia page for a regular Jumbotron</p>
-  </div>
-</div>
-
-
-
-
-

Forms

-

Forms are highly customizable with Bootstrap. -For a more in-depth look at how to use forms and their styles in your own Evennia site, please read -over the web character gen tutorial.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Builder-Docs.html b/docs/0.9.5/Builder-Docs.html deleted file mode 100644 index 55817fd254..0000000000 --- a/docs/0.9.5/Builder-Docs.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - Builder Docs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Builder Docs

-

This section contains information useful to world builders.

-
-

Building basics

- -
-
-

Advanced building and World building

- -
-
-

The Tutorial world

- -
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Building-Permissions.html b/docs/0.9.5/Building-Permissions.html deleted file mode 100644 index dd5f93a3ad..0000000000 --- a/docs/0.9.5/Building-Permissions.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - Building Permissions — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Building Permissions

-

OBS: This gives only a brief introduction to the access system. Locks and permissions are fully -detailed here.

-
-

The super user

-

There are strictly speaking two types of users in Evennia, the super user and everyone else. The -superuser is the first user you create, object #1. This is the all-powerful server-owner account. -Technically the superuser not only has access to everything, it bypasses the permission checks -entirely. This makes the superuser impossible to lock out, but makes it unsuitable to actually play- -test the game’s locks and restrictions with (see @quell below). Usually there is no need to have -but one superuser.

-
-
-

Assigning permissions

-

Whereas permissions can be used for anything, those put in settings.PERMISSION_HIERARCHY will have -a ranking relative each other as well. We refer to these types of permissions as hierarchical -permissions. When building locks to check these permissions, the perm() lock function is -used. By default Evennia creates the following hierarchy (spelled exactly like this):

-
    -
  1. Developers basically have the same access as superusers except that they do not sidestep -the Permission system. Assign only to really trusted server-admin staff since this level gives -access both to server reload/shutdown functionality as well as (and this may be more critical) gives -access to the all-powerful @py command that allows the execution of arbitrary Python code on the -command line.

  2. -
  3. Admins can do everything except affecting the server functions themselves. So an Admin -couldn’t reload or shutdown the server for example. They also cannot execute arbitrary Python code -on the console or import files from the hard drive.

  4. -
  5. Builders - have all the build commands, but cannot affect other accounts or mess with the -server.

  6. -
  7. Helpers are almost like a normal Player, but they can also add help files to the database.

  8. -
  9. Players is the default group that new players end up in. A new player have permission to use -tells and to use and create new channels.

  10. -
-

A user having a certain level of permission automatically have access to locks specifying access of -a lower level.

-

To assign a new permission from inside the game, you need to be able to use the @perm command. -This is an Developer-level command, but it could in principle be made lower-access since it only -allows assignments equal or lower to your current level (so you cannot use it to escalate your own -permission level). So, assuming you yourself have Developer access (or is superuser), you assign -a new account “Tommy” to your core staff with the command

-
@perm/account Tommy = Developer
-
-
-

or

-
@perm *Tommy = Developer
-
-
-

We use a switch or the *name format to make sure to put the permission on the Account and not on -any eventual Character that may also be named “Tommy”. This is usually what you want since the -Account will then remain an Developer regardless of which Character they are currently controlling. -To limit permission to a per-Character level you should instead use quelling (see below). Normally -permissions can be any string, but for these special hierarchical permissions you can also use -plural (“Developer” and “Developers” both grant the same powers).

-
-
-

Quelling your permissions

-

When developing it can be useful to check just how things would look had your permission-level been -lower. For this you can use quelling. Normally, when you puppet a Character you are using your -Account-level permission. So even if your Character only has Accounts level permissions, your -Developer-level Account will take precedence. With the @quell command you can change so that the -Character’s permission takes precedence instead:

-
 @quell
-
-
-

This will allow you to test out the game using the current Character’s permission level. A developer -or builder can thus in principle maintain several test characters, all using different permission -levels. Note that you cannot escalate your permissions this way; If the Character happens to have a -higher permission level than the Account, the Account’s (lower) permission will still be used.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Building-Quickstart.html b/docs/0.9.5/Building-Quickstart.html deleted file mode 100644 index 79bd88e076..0000000000 --- a/docs/0.9.5/Building-Quickstart.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - Building Quickstart — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Building Quickstart

-

The default command definitions coming with Evennia -follows a style similar to that of MUX, so the -commands should be familiar if you used any such code bases before.

-
-

Throughout the larger documentation you may come across commands prefixed -with @. This is just an optional marker used in some places to make a -command stand out. Evennia defaults to ignoring the use of @ in front of -your command (so entering dig is the same as entering @dig).

-
-

The default commands have the following style (where [...] marks optional parts):

-
 command[/switch/switch...] [arguments ...]
-
-
-

A switch is a special, optional flag to the command to make it behave differently. It is always -put directly after the command name, and begins with a forward slash (/). The arguments are one -or more inputs to the commands. It’s common to use an equal sign (=) when assigning something to -an object.

-

Below are some examples of commands you can try when logged in to the game. Use help <command> for -learning more about each command and their detailed options.

-
-

Stepping Down From Godhood

-

If you just installed Evennia, your very first player account is called user #1, also known as the -superuser or god user. This user is very powerful, so powerful that it will override many game -restrictions such as locks. This can be useful, but it also hides some functionality that you might -want to test.

-

To temporarily step down from your superuser position you can use the quell command in-game:

-
quell
-
-
-

This will make you start using the permission of your current character’s level instead of your -superuser level. If you didn’t change any settings your game Character should have an Developer -level permission - high as can be without bypassing locks like the superuser does. This will work -fine for the examples on this page. Use unquell to get back to superuser status again afterwards.

-
-
-

Creating an Object

-

Basic objects can be anything – swords, flowers and non-player characters. They are created using -the create command:

-
create box
-
-
-

This created a new ‘box’ (of the default object type) in your inventory. Use the command inventory -(or i) to see it. Now, ‘box’ is a rather short name, let’s rename it and tack on a few aliases.

-
name box = very large box;box;very;crate
-
-
-

We now renamed the box to very large box (and this is what we will see when looking at it), but we -will also recognize it by any of the other names we give - like crate or simply box as before. -We could have given these aliases directly after the name in the create command, this is true for -all creation commands - you can always tag on a list of ;-separated aliases to the name of your -new object. If you had wanted to not change the name itself, but to only add aliases, you could have -used the alias command.

-

We are currently carrying the box. Let’s drop it (there is also a short cut to create and drop in -one go by using the /drop switch, for example create/drop box).

-
drop box
-
-
-

Hey presto - there it is on the ground, in all its normality.

-
examine box
-
-
-

This will show some technical details about the box object. For now we will ignore what this -information means.

-

Try to look at the box to see the (default) description.

-
look box
-You see nothing special.
-
-
-

The description you get is not very exciting. Let’s add some flavor.

-
describe box = This is a large and very heavy box.
-
-
-

If you try the get command we will pick up the box. So far so good, but if we really want this to -be a large and heavy box, people should not be able to run off with it that easily. To prevent -this we need to lock it down. This is done by assigning a Lock to it. Make sure the box was -dropped in the room, then try this:

-
lock box = get:false()
-
-
-

Locks represent a rather big topic, but for now that will do what we want. This will lock -the box so noone can lift it. The exception is superusers, they override all locks and will pick it -up anyway. Make sure you are quelling your superuser powers and try to get the box now:

-
> get box
-You can't get that.
-
-
-

Think thís default error message looks dull? The get command looks for an Attribute -named get_err_msg for returning a nicer error message (we just happen to know this, you would need -to peek into the -code for -the get command to find out.). You set attributes using the set command:

-
set box/get_err_msg = It's way too heavy for you to lift.
-
-
-

Try to get it now and you should see a nicer error message echoed back to you. To see what this -message string is in the future, you can use ‘examine.’

-
examine box/get_err_msg
-
-
-

Examine will return the value of attributes, including color codes. examine here/desc would return -the raw description of your current room (including color codes), so that you can copy-and-paste to -set its description to something else.

-

You create new Commands (or modify existing ones) in Python outside the game. See the Adding -Commands tutorial for help with creating your first own Command.

-
-
-

Get a Personality

-

Scripts are powerful out-of-character objects useful for many “under the hood” things. -One of their optional abilities is to do things on a timer. To try out a first script, let’s put one -on ourselves. There is an example script in evennia/contrib/tutorial_examples/bodyfunctions.py -that is called BodyFunctions. To add this to us we will use the script command:

-
script self = tutorial_examples.bodyfunctions.BodyFunctions
-
-
-

(note that you don’t have to give the full path as long as you are pointing to a place inside the -contrib directory, it’s one of the places Evennia looks for Scripts). Wait a while and you will -notice yourself starting making random observations.

-
script self
-
-
-

This will show details about scripts on yourself (also examine works). You will see how long it is -until it “fires” next. Don’t be alarmed if nothing happens when the countdown reaches zero - this -particular script has a randomizer to determine if it will say something or not. So you will not see -output every time it fires.

-

When you are tired of your character’s “insights”, kill the script with

-
script/stop self = tutorial_examples.bodyfunctions.BodyFunctions
-
-
-

You create your own scripts in Python, outside the game; the path you give to script is literally -the Python path to your script file. The Scripts page explains more details.

-
-
-

Pushing Your Buttons

-

If we get back to the box we made, there is only so much fun you can do with it at this point. It’s -just a dumb generic object. If you renamed it to stone and changed its description noone would be -the wiser. However, with the combined use of custom Typeclasses, Scripts -and object-based Commands, you could expand it and other items to be as unique, complex -and interactive as you want.

-

Let’s take an example. So far we have only created objects that use the default object typeclass -named simply Object. Let’s create an object that is a little more interesting. Under -evennia/contrib/tutorial_examples there is a module red_button.py. It contains the enigmatic -RedButton typeclass.

-

Let’s make us one of those!

-
create/drop button:tutorial_examples.red_button.RedButton
-
-
-

We import the RedButton python class the same way you would import it in Python except Evennia makes -sure to look inevennia/contrib/ so you don’t have to write the full path every time. There you go

-
    -
  • one red button.

  • -
-

The RedButton is an example object intended to show off a few of Evennia’s features. You will find -that the Typeclass and Commands controlling it are inside -evennia/contrib/tutorial_examples/.

-

If you wait for a while (make sure you dropped it!) the button will blink invitingly. Why don’t you -try to push it …? Surely a big red button is meant to be pushed. You know you want to.

-
-
-

Making Yourself a House

-

The main command for shaping the game world is dig. For example, if you are standing in Limbo you -can dig a route to your new house location like this:

-
dig house = large red door;door;in,to the outside;out
-
-
-

This will create a new room named ‘house’. Spaces at the start/end of names and aliases are ignored -so you could put more air if you wanted. This call will directly create an exit from your current -location named ‘large red door’ and a corresponding exit named ‘to the outside’ in the house room -leading back to Limbo. We also define a few aliases to those exits, so people don’t have to write -the full thing all the time.

-

If you wanted to use normal compass directions (north, west, southwest etc), you could do that with -dig too. But Evennia also has a limited version of dig that helps for compass directions (and -also up/down and in/out). It’s called tunnel:

-
tunnel sw = cliff
-
-
-

This will create a new room “cliff” with an exit “southwest” leading there and a path “northeast” -leading back from the cliff to your current location.

-

You can create new exits from where you are using the open command:

-
open north;n = house
-
-
-

This opens an exit north (with an alias n) to the previously created room house.

-

If you have many rooms named house you will get a list of matches and have to select which one you -want to link to. You can also give its database (#dbref) number, which is unique to every object. -This can be found with the examine command or by looking at the latest constructions with -objects.

-

Follow the north exit to your ‘house’ or teleport to it:

-
north
-
-
-

or:

-
teleport house
-
-
-

To manually open an exit back to Limbo (if you didn’t do so with the dig command):

-
open door = limbo
-
-
-

(or give limbo’s dbref which is #2)

-
-
-

Reshuffling the World

-

You can find things using the find command. Assuming you are back at Limbo, let’s teleport the -large box to our house.

-
> teleport box = house
-very large box is leaving Limbo, heading for house.
-Teleported very large box -> house.
-
-
-

We can still find the box by using find:

-
> find box
-One Match(#1-#8):
-very large box(#8) - src.objects.objects.Object
-
-
-

Knowing the #dbref of the box (#8 in this example), you can grab the box and get it back here -without actually yourself going to house first:

-
teleport #8 = here
-
-
-

(You can usually use here to refer to your current location. To refer to yourself you can use -self or me). The box should now be back in Limbo with you.

-

We are getting tired of the box. Let’s destroy it.

-
destroy box
-
-
-

You can destroy many objects in one go by giving a comma-separated list of objects (or their -#dbrefs, if they are not in the same location) to the command.

-
-
-

Adding a Help Entry

-

An important part of building is keeping the help files updated. You can add, delete and append to -existing help entries using the sethelp command.

-
sethelp/add MyTopic = This help topic is about ...
-
-
-
-
-

Adding a World

-

After this brief introduction to building you may be ready to see a more fleshed-out example. -Evennia comes with a tutorial world for you to explore.

-

First you need to switch back to superuser by using the unquell command. Next, place yourself in -Limbo and run the following command:

-
batchcommand tutorial_world.build
-
-
-

This will take a while (be patient and don’t re-run the command). You will see all the commands used -to build the world scroll by as the world is built for you.

-

You will end up with a new exit from Limbo named tutorial. Apart from being a little solo- -adventure in its own right, the tutorial world is a good source for learning Evennia building (and -coding).

-

Read the batch -file to see -exactly how it’s built, step by step. See also more info about the tutorial world [here](Tutorial- -World-Introduction).

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Building-a-mech-tutorial.html b/docs/0.9.5/Building-a-mech-tutorial.html deleted file mode 100644 index bd4eb09abe..0000000000 --- a/docs/0.9.5/Building-a-mech-tutorial.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - - - - Building a mech tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Building a mech tutorial

-
-

This page was adapted from the article “Building a Giant Mech in Evennia” by Griatch, published in -Imaginary Realities Volume 6, issue 1, 2014. The original article is no longer available online, -this is a version adopted to be compatible with the latest Evennia.

-
-
-

Creating the Mech

-

Let us create a functioning giant mech using the Python MUD-creation system Evennia. Everyone likes -a giant mech, right? Start in-game as a character with build privileges (or the superuser).

-
@create/drop Giant Mech ; mech
-
-
-

Boom. We created a Giant Mech Object and dropped it in the room. We also gave it an alias mech. -Let’s describe it.

-
@desc mech = This is a huge mech. It has missiles and stuff.
-
-
-

Next we define who can “puppet” the mech object.

-
@lock mech = puppet:all()
-
-
-

This makes it so that everyone can control the mech. More mechs to the people! (Note that whereas -Evennia’s default commands may look vaguely MUX-like, you can change the syntax to look like -whatever interface style you prefer.)

-

Before we continue, let’s make a brief detour. Evennia is very flexible about its objects and even -more flexible about using and adding commands to those objects. Here are some ground rules well -worth remembering for the remainder of this article:

-
    -
  • The Account represents the real person logging in and has no game-world existence.

  • -
  • Any Object can be puppeted by an Account (with proper permissions).

  • -
  • Characters, Rooms, and Exits are just -children of normal Objects.

  • -
  • Any Object can be inside another (except if it creates a loop).

  • -
  • Any Object can store custom sets of commands on it. Those commands can:

    -
      -
    • be made available to the puppeteer (Account),

    • -
    • be made available to anyone in the same location as the Object, and

    • -
    • be made available to anyone “inside” the Object

    • -
    • Also Accounts can store commands on themselves. Account commands are always available unless -commands on a puppeted Object explicitly override them.

    • -
    -
  • -
-

In Evennia, using the @ic command will allow you to puppet a given Object (assuming you have -puppet-access to do so). As mentioned above, the bog-standard Character class is in fact like any -Object: it is auto-puppeted when logging in and just has a command set on it containing the normal -in-game commands, like look, inventory, get and so on.

-
@ic mech
-
-
-

You just jumped out of your Character and are now the mech! If people look at you in-game, they -will look at a mech. The problem at this point is that the mech Object has no commands of its own. -The usual things like look, inventory and get sat on the Character object, remember? So at the -moment the mech is not quite as cool as it could be.

-
@ic <Your old Character>
-
-
-

You just jumped back to puppeting your normal, mundane Character again. All is well.

-
-

(But, you ask, where did that @ic command come from, if the mech had no commands on it? The -answer is that it came from the Account’s command set. This is important. Without the Account being -the one with the @ic command, we would not have been able to get back out of our mech again.)

-
-
-

Arming the Mech

-

Let us make the mech a little more interesting. In our favorite text editor, we will create some new -mech-suitable commands. In Evennia, commands are defined as Python classes.

-
# in a new file mygame/commands/mechcommands.py
-
-from evennia import Command
-
-class CmdShoot(Command):
-    """
-    Firing the mech’s gun
-
-    Usage:
-      shoot [target]
-
-    This will fire your mech’s main gun. If no
-    target is given, you will shoot in the air.
-    """
-    key = "shoot"
-    aliases = ["fire", "fire!"]
-
-    def func(self):
-        "This actually does the shooting"
-
-        caller = self.caller
-        location = caller.location
-
-        if not self.args:
-            # no argument given to command - shoot in the air
-            message = "BOOM! The mech fires its gun in the air!"
-            location.msg_contents(message)
-            return
-
-        # we have an argument, search for target
-        target = caller.search(self.args.strip())
-        if target:
-            message = "BOOM! The mech fires its gun at %s" % target.key
-            location.msg_contents(message)
-
-class CmdLaunch(Command):
-    # make your own 'launch'-command here as an exercise!
-    # (it's very similar to the 'shoot' command above).
-
-
-
-

This is saved as a normal Python module (let’s call it mechcommands.py), in a place Evennia looks -for such modules (mygame/commands/). This command will trigger when the player gives the command -“shoot”, “fire,” or even “fire!” with an exclamation mark. The mech can shoot in the air or at a -target if you give one. In a real game the gun would probably be given a chance to hit and give -damage to the target, but this is enough for now.

-

We also make a second command for launching missiles (CmdLaunch). To save -space we won’t describe it here; it looks the same except it returns a text -about the missiles being fired and has different key and aliases. We leave -that up to you to create as an exercise. You could have it print “WOOSH! The -mech launches missiles against !”, for example.

-

Now we shove our commands into a command set. A Command Set (CmdSet) is a container -holding any number of commands. The command set is what we will store on the mech.

-
# in the same file mygame/commands/mechcommands.py
-
-from evennia import CmdSet
-from evennia import default_cmds
-
-class MechCmdSet(CmdSet):
-    """
-    This allows mechs to do do mech stuff.
-    """
-    key = "mechcmdset"
-
-    def at_cmdset_creation(self):
-        "Called once, when cmdset is first created"
-        self.add(CmdShoot())
-        self.add(CmdLaunch())
-
-
-

This simply groups all the commands we want. We add our new shoot/launch commands. Let’s head back -into the game. For testing we will manually attach our new CmdSet to the mech.

-
@py self.search("mech").cmdset.add("commands.mechcommands.MechCmdSet")
-
-
-

This is a little Python snippet (run from the command line as an admin) that searches for the mech -in our current location and attaches our new MechCmdSet to it. What we add is actually the Python -path to our cmdset class. Evennia will import and initialize it behind the scenes.

-
@ic mech
-
-
-

We are back as the mech! Let’s do some shooting!

-
fire!
-BOOM! The mech fires its gun in the air!
-
-
-

There we go, one functioning mech. Try your own launch command and see that it works too. We can -not only walk around as the mech — since the CharacterCmdSet is included in our MechCmdSet, the mech -can also do everything a Character could do, like look around, pick up stuff, and have an inventory. -We could now shoot the gun at a target or try the missile launch command. Once you have your own -mech, what else do you need?

-
-

Note: You’ll find that the mech’s commands are available to you by just standing in the same -location (not just by puppeting it). We’ll solve this with a lock in the next section.

-
-
-
-
-

Making a Mech production line

-

What we’ve done so far is just to make a normal Object, describe it and put some commands on it. -This is great for testing. The way we added it, the MechCmdSet will even go away if we reload the -server. Now we want to make the mech an actual object “type” so we can create mechs without those -extra steps. For this we need to create a new Typeclass.

-

A Typeclass is a near-normal Python class that stores its existence to the database -behind the scenes. A Typeclass is created in a normal Python source file:

-
# in the new file mygame/typeclasses/mech.py
-
-from typeclasses.objects import Object
-from commands.mechcommands import MechCmdSet
-from evennia import default_cmds
-
-class Mech(Object):
-    """
-    This typeclass describes an armed Mech.
-    """
-    def at_object_creation(self):
-        "This is called only when object is first created"
-        self.cmdset.add_default(default_cmds.CharacterCmdSet)
-        self.cmdset.add(MechCmdSet, permanent=True)
-        self.locks.add("puppet:all();call:false()")
-        self.db.desc = "This is a huge mech. It has missiles and stuff."
-
-
-

For convenience we include the full contents of the default CharacterCmdSet in there. This will -make a Character’s normal commands available to the mech. We also add the mech-commands from before, -making sure they are stored persistently in the database. The locks specify that anyone can puppet -the meck and no-one can “call” the mech’s Commands from ‘outside’ it - you have to puppet it to be -able to shoot.

-

That’s it. When Objects of this type are created, they will always start out with the mech’s command -set and the correct lock. We set a default description, but you would probably change this with -@desc to individualize your mechs as you build them.

-

Back in the game, just exit the old mech (@ic back to your old character) then do

-
@create/drop The Bigger Mech ; bigmech : mech.Mech
-
-
-

We create a new, bigger mech with an alias bigmech. Note how we give the python-path to our -Typeclass at the end — this tells Evennia to create the new object based on that class (we don’t -have to give the full path in our game dir typeclasses.mech.Mech because Evennia knows to look in -the typeclasses folder already). A shining new mech will appear in the room! Just use

-
@ic bigmech
-
-
-

to take it on a test drive.

-
-
-

Future Mechs

-

To expand on this you could add more commands to the mech and remove others. Maybe the mech -shouldn’t work just like a Character after all. Maybe it makes loud noises every time it passes from -room to room. Maybe it cannot pick up things without crushing them. Maybe it needs fuel, ammo and -repairs. Maybe you’ll lock it down so it can only be puppeted by emo teenagers.

-

Having you puppet the mech-object directly is also just one way to implement a giant mech in -Evennia.

-

For example, you could instead picture a mech as a “vehicle” that you “enter” as your normal -Character (since any Object can move inside another). In that case the “insides” of the mech Object -could be the “cockpit”. The cockpit would have the MechCommandSet stored on itself and all the -shooting goodness would be made available to you only when you enter it.

-

And of course you could put more guns on it. And make it fly.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Building-menus.html b/docs/0.9.5/Building-menus.html deleted file mode 100644 index 4e128c87a9..0000000000 --- a/docs/0.9.5/Building-menus.html +++ /dev/null @@ -1,1320 +0,0 @@ - - - - - - - - - Building menus — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Building menus

-
-
-

The building_menu contrib

-

This contrib allows you to write custom and easy to use building menus. As the name implies, these -menus are most useful for building things, that is, your builders might appreciate them, although -you can use them for your players as well.

-

Building menus are somewhat similar to EvMenu although they don’t use the same system at all and -are intended to make building easier. They replicate what other engines refer to as “building -editors”, which allow to you to build in a menu instead of having to enter a lot of complex -commands. Builders might appreciate this simplicity, and if the code that was used to create them -is simple as well, coders could find this contrib useful.

-
-

A simple menu

-

Before diving in, there are some things to point out:

-
    -
  • Building menus work on an object. This object will be edited by manipulations in the menu. So -you can create a menu to add/edit a room, an exit, a character and so on.

  • -
  • Building menus are arranged in layers of choices. A choice gives access to an option or to a sub- -menu. Choices are linked to commands (usually very short). For instance, in the example shown -below, to edit the room key, after opening the building menu, you can type k. That will lead you -to the key choice where you can enter a new key for the room. Then you can enter @ to leave this -choice and go back to the entire menu. (All of this can be changed).

  • -
  • To open the menu, you will need something like a command. This contrib offers a basic command for -demonstration, but we will override it in this example, using the same code with more flexibility.

  • -
-

So let’s add a very basic example to begin with.

-
-

A generic editing command

-

Let’s begin by adding a new command. You could add or edit the following file (there’s no trick -here, feel free to organize the code differently):

-
# file: commands/building.py
-from evennia.contrib.building_menu import BuildingMenu
-from commands.command import Command
-
-class EditCmd(Command):
-
-    """
-    Editing command.
-
-    Usage:
-      @edit [object]
-
-    Open a building menu to edit the specified object.  This menu allows to
-    specific information about this object.
-
-    Examples:
-      @edit here
-      @edit self
-      @edit #142
-
-    """
-
-    key = "@edit"
-    locks = "cmd:id(1) or perm(Builders)"
-    help_category = "Building"
-
-    def func(self):
-        if not self.args.strip():
-            self.msg("|rYou should provide an argument to this function: the object to edit.|n")
-            return
-
-        obj = self.caller.search(self.args.strip(), global_search=True)
-        if not obj:
-            return
-
-        if obj.typename == "Room":
-            Menu = RoomBuildingMenu
-        else:
-            self.msg("|rThe object {} cannot be
-edited.|n".format(obj.get_display_name(self.caller)))
-            return
-
-        menu = Menu(self.caller, obj)
-        menu.open()
-
-
-

This command is rather simple in itself:

-
    -
  1. It has a key @edit and a lock to only allow builders to use it.

  2. -
  3. In its func method, it begins by checking the arguments, returning an error if no argument is -specified.

  4. -
  5. It then searches for the given argument. We search globally. The search method used in this -way will return the found object or None. It will also send the error message to the caller if -necessary.

  6. -
  7. Assuming we have found an object, we check the object typename. This will be used later when -we want to display several building menus. For the time being, we only handle Room. If the -caller specified something else, we’ll display an error.

  8. -
  9. Assuming this object is a Room, we have defined a Menu object containing the class of our -building menu. We build this class (creating an instance), giving it the caller and the object to -edit.

  10. -
  11. We then open the building menu, using the open method.

  12. -
-

The end might sound a bit surprising at first glance. But the process is still very simple: we -create an instance of our building menu and call its open method. Nothing more.

-
-

Where is our building menu?

-
-

If you go ahead and add this command and test it, you’ll get an error. We haven’t defined -RoomBuildingMenu yet.

-

To add this command, edit commands/default_cmdsets.py. Import our command, adding an import line -at the top of the file:

-
"""
-...
-"""
-
-from evennia import default_cmds
-
-# The following line is to be added
-from commands.building import EditCmd
-
-
-

And in the class below (CharacterCmdSet), add the last line of this code:

-
class CharacterCmdSet(default_cmds.CharacterCmdSet):
-    """
-    The `CharacterCmdSet` contains general in-game commands like `look`,
-    `get`, etc available on in-game Character objects. It is merged with
-    the `AccountCmdSet` when an Account puppets a Character.
-    """
-    key = "DefaultCharacter"
-
-    def at_cmdset_creation(self):
-        """
-        Populates the cmdset
-        """
-        super().at_cmdset_creation()
-        #
-        # any commands you add below will overload the default ones.
-        #
-        self.add(EditCmd())
-
-
-
-
-

Our first menu

-

So far, we can’t use our building menu. Our @edit command will throw an error. We have to define -the RoomBuildingMenu class. Open the commands/building.py file and add to the end of the file:

-
# ... at the end of commands/building.py
-# Our building menu
-
-class RoomBuildingMenu(BuildingMenu):
-
-    """
-    Building menu to edit a room.
-
-    For the time being, we have only one choice: key, to edit the room key.
-
-    """
-
-    def init(self, room):
-        self.add_choice("key", "k", attr="key")
-
-
-

Save these changes, reload your game. You can now use the @edit command. Here’s what we get -(notice that the commands we enter into the game are prefixed with > , though this prefix will -probably not appear in your MUD client):

-
> look
-Limbo(#2)
-Welcome to your new Evennia-based game! Visit http://www.evennia.com if you need
-help, want to contribute, report issues or just join the community.
-As Account #1 you can create a demo/tutorial area with @batchcommand tutorial_world.build.
-
-> @edit here
-Building menu: Limbo
-
- [K]ey: Limbo
- [Q]uit the menu
-
-> q
-Closing the building menu.
-
-> @edit here
-Building menu: Limbo
-
- [K]ey: Limbo
- [Q]uit the menu
-
-> k
--------------------------------------------------------------------------------
-key for Limbo(#2)
-
-You can change this value simply by entering it.
-
-Use @ to go back to the main menu.
-
-Current value: Limbo
-
-> A beautiful meadow
--------------------------------------------------------------------------------
-
-key for A beautiful meadow(#2)
-
-You can change this value simply by entering it.
-
-Use @ to go back to the main menu.
-
-Current value: A beautiful meadow
-
-> @
-Building menu: A beautiful meadow
-
- [K]ey: A beautiful meadow
- [Q]uit the menu
-
-> q
-
-Closing the building menu.
-
-> look
-A beautiful meadow(#2)
-Welcome to your new Evennia-based game! Visit http://www.evennia.com if you need
-help, want to contribute, report issues or just join the community.
-As Account #1 you can create a demo/tutorial area with @batchcommand tutorial_world.build.
-
-
-

Before diving into the code, let’s examine what we have:

-
    -
  • When we use the @edit here command, a building menu for this room appears.

  • -
  • This menu has two choices:

    -
      -
    • Enter k to edit the room key. You will go into a choice where you can simply type the key -room key (the way we have done here). You can use @ to go back to the menu.

    • -
    • You can use q to quit the menu.

    • -
    -
  • -
-

We then check, with the look command, that the menu has modified this room key. So by adding a -class, with a method and a single line of code within, we’ve added a menu with two choices.

-
-
-

Code explanation

-

Let’s examine our code again:

-
class RoomBuildingMenu(BuildingMenu):
-
-    """
-    Building menu to edit a room.
-
-    For the time being, we have only one choice: key, to edit the room key.
-
-    """
-
-    def init(self, room):
-        self.add_choice("key", "k", attr="key")
-
-
-
    -
  • We first create a class inheriting from BuildingMenu. This is usually the case when we want to -create a building menu with this contrib.

  • -
  • In this class, we override the init method, which is called when the menu opens.

  • -
  • In this init method, we call add_choice. This takes several arguments, but we’ve defined only -three here:

    -
      -
    • The choice name. This is mandatory and will be used by the building menu to know how to -display this choice.

    • -
    • The command key to access this choice. We’ve given a simple "k". Menu commands usually are -pretty short (that’s part of the reason building menus are appreciated by builders). You can also -specify additional aliases, but we’ll see that later.

    • -
    • We’ve added a keyword argument, attr. This tells the building menu that when we are in this -choice, the text we enter goes into this attribute name. It’s called attr, but it could be a room -attribute or a typeclass persistent or non-persistent attribute (we’ll see other examples as well).

    • -
    -
  • -
-
-

We’ve added the menu choice for key here, why is another menu choice defined for quit?

-
-

Our building menu creates a choice at the end of our choice list if it’s a top-level menu (sub-menus -don’t have this feature). You can, however, override it to provide a different “quit” message or to -perform some actions.

-

I encourage you to play with this code. As simple as it is, it offers some functionalities already.

-
-
-
-

Customizing building menus

-

This somewhat long section explains how to customize building menus. There are different ways -depending on what you would like to achieve. We’ll go from specific to more advanced here.

-
-

Generic choices

-

In the previous example, we’ve used add_choice. This is one of three methods you can use to add -choices. The other two are to handle more generic actions:

-
    -
  • add_choice_edit: this is called to add a choice which points to the EvEditor. It is used to -edit a description in most cases, although you could edit other things. We’ll see an example -shortly. add_choice_edit uses most of the add_choice keyword arguments we’ll see, but usually -we specify only two (sometimes three):

    -
      -
    • The choice title as usual.

    • -
    • The choice key (command key) as usual.

    • -
    • Optionally, the attribute of the object to edit, with the attr keyword argument. By -default, attr contains db.desc. It means that this persistent data attribute will be edited by -the EvEditor. You can change that to whatever you want though.

    • -
    -
  • -
  • add_choice_quit: this allows to add a choice to quit the editor. Most advisable! If you don’t -do it, the building menu will do it automatically, except if you really tell it not to. Again, you -can specify the title and key of this menu. You can also call a function when this menu closes.

  • -
-

So here’s a more complete example (you can replace your RoomBuildingMenu class in -commands/building.py to see it):

-
class RoomBuildingMenu(BuildingMenu):
-
-    """
-    Building menu to edit a room.
-    """
-
-    def init(self, room):
-        self.add_choice("key", "k", attr="key")
-        self.add_choice_edit("description", "d")
-        self.add_choice_quit("quit this editor", "q")
-
-
-

So far, our building menu class is still thin… and yet we already have some interesting feature. -See for yourself the following MUD client output (again, the commands are prefixed with > to -distinguish them):

-
> @reload
-
-> @edit here
-Building menu: A beautiful meadow
-
- [K]ey: A beautiful meadow
- [D]escription:
-   Welcome to your new Evennia-based game! Visit http://www.evennia.com if you need
-help, want to contribute, report issues or just join the community.
-As Account #1 you can create a demo/tutorial area with @batchcommand tutorial_world.build.
- [Q]uit this editor
-
-> d
-
-----------Line Editor [editor]----------------------------------------------------
-01| Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need
-02| help, want to contribute, report issues or just join the community.
-03| As Account #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n.
-
-> :DD
-
-----------[l:03 w:034 c:0247]------------(:h for help)----------------------------
-Cleared 3 lines from buffer.
-
-> This is a beautiful meadow. But so beautiful I can't describe it.
-
-01| This is a beautiful meadow. But so beautiful I can't describe it.
-
-> :wq
-Building menu: A beautiful meadow
-
- [K]ey: A beautiful meadow
- [D]escription:
-   This is a beautiful meadow.  But so beautiful I can't describe it.
- [Q]uit this editor
-
-> q
-Closing the building menu.
-
-> look
-A beautiful meadow(#2)
-This is a beautiful meadow.  But so beautiful I can't describe it.
-
-
-

So by using the d shortcut in our building menu, an EvEditor opens. You can use the EvEditor -commands (like we did here, :DD to remove all, :wq to save and quit). When you quit the editor, -the description is saved (here, in room.db.desc) and you go back to the building menu.

-

Notice that the choice to quit has changed too, which is due to our adding add_choice_quit. In -most cases, you will probably not use this method, since the quit menu is added automatically.

-
-
-

add_choice options

-

add_choice and the two methods add_choice_edit and add_choice_quit take a lot of optional -arguments to make customization easier. Some of these options might not apply to add_choice_edit -or add_choice_quit however.

-

Below are the options of add_choice, specify them as arguments:

-
    -
  • The first positional, mandatory argument is the choice title, as we have seen. This will -influence how the choice appears in the menu.

  • -
  • The second positional, mandatory argument is the command key to access to this menu. It is best -to use keyword arguments for the other arguments.

  • -
  • The aliases keyword argument can contain a list of aliases that can be used to access to this -menu. For instance: add_choice(..., aliases=['t'])

  • -
  • The attr keyword argument contains the attribute to edit when this choice is selected. It’s a -string, it has to be the name, from the object (specified in the menu constructor) to reach this -attribute. For instance, a attr of "key" will try to find obj.key to read and write the -attribute. You can specify more complex attribute names, for instance, attr="db.desc" to set the -desc persistent attribute, or attr="ndb.something" so use a non-persistent data attribute on the -object.

  • -
  • The text keyword argument is used to change the text that will be displayed when the menu choice -is selected. Menu choices provide a default text that you can change. Since this is a long text, -it’s useful to use multi-line strings (see an example below).

  • -
  • The glance keyword argument is used to specify how to display the current information while in -the menu, when the choice hasn’t been opened. If you examine the previous examples, you will see -that the current (key or db.desc) was shown in the menu, next to the command key. This is -useful for seeing at a glance the current value (hence the name). Again, menu choices will provide -a default glance if you don’t specify one.

  • -
  • The on_enter keyword argument allows to add a callback to use when the menu choice is opened. -This is more advanced, but sometimes useful.

  • -
  • The on_nomatch keyword argument is called when, once in the menu, the caller enters some text -that doesn’t match any command (including the @ command). By default, this will edit the -specified attr.

  • -
  • The on_leave keyword argument allows to specify a callback used when the caller leaves the menu -choice. This can be useful for cleanup as well.

  • -
-

These are a lot of possibilities, and most of the time you won’t need them all. Here is a short -example using some of these arguments (again, replace the RoomBuildingMenu class in -commands/building.py with the following code to see it working):

-
class RoomBuildingMenu(BuildingMenu):
-
-    """
-    Building menu to edit a room.
-
-    For the time being, we have only one choice: key, to edit the room key.
-
-    """
-
-    def init(self, room):
-        self.add_choice("title", key="t", attr="key", glance="{obj.key}", text="""
-                -------------------------------------------------------------------------------
-                Editing the title of {{obj.key}}(#{{obj.id}})
-
-                You can change the title simply by entering it.
-                Use |y{back}|n to go back to the main menu.
-
-                Current title: |c{{obj.key}}|n
-        """.format(back="|n or |y".join(self.keys_go_back)))
-        self.add_choice_edit("description", "d")
-
-
-

Reload your game and see it in action:

-
> @edit here
-Building menu: A beautiful meadow
-
- [T]itle: A beautiful meadow
- [D]escription:
-   This is a beautiful meadow.  But so beautiful I can't describe it.
- [Q]uit the menu
-
-> t
--------------------------------------------------------------------------------
-
-Editing the title of A beautiful meadow(#2)
-
-You can change the title simply by entering it.
-Use @ to go back to the main menu.
-
-Current title: A beautiful meadow
-
-> @
-
-Building menu: A beautiful meadow
-
- [T]itle: A beautiful meadow
- [D]escription:
-   This is a beautiful meadow.  But so beautiful I can't describe it.
- [Q]uit the menu
-
-> q
-Closing the building menu.
-
-
-

The most surprising part is no doubt the text. We use the multi-line syntax (with """). -Excessive spaces will be removed from the left for each line automatically. We specify some -information between braces… sometimes using double braces. What might be a bit odd:

-
    -
  • {back} is a direct format argument we’ll use (see the .format specifiers).

  • -
  • {{obj...}} refers to the object being edited.  We use two braces, because .format` will remove -them.

  • -
-

In glance, we also use {obj.key} to indicate we want to show the room’s key.

-
-
-

Everything can be a function

-

The keyword arguments of add_choice are often strings (type str). But each of these arguments -can also be a function. This allows for a lot of customization, since we define the callbacks that -will be executed to achieve such and such an operation.

-

To demonstrate, we will try to add a new feature. Our building menu for rooms isn’t that bad, but -it would be great to be able to edit exits too. So we can add a new menu choice below -description… but how to actually edit exits? Exits are not just an attribute to set: exits are -objects (of type Exit by default) which stands between two rooms (object of type Room). So how -can we show that?

-

First let’s add a couple of exits in limbo, so we have something to work with:

-
@tunnel n
-@tunnel s
-
-
-

This should create two new rooms, exits leading to them from limbo and back to limbo.

-
> look
-A beautiful meadow(#2)
-This is a beautiful meadow.  But so beautiful I can't describe it.
-Exits: north(#4) and south(#7)
-
-
-

We can access room exits with the exits property:

-
> @py here.exits
-[<Exit: north>, <Exit: south>]
-
-
-

So what we need is to display this list in our building menu… and to allow to edit it would be -great. Perhaps even add new exits?

-

First of all, let’s write a function to display the glance on existing exits. Here’s the code, -it’s explained below:

-
class RoomBuildingMenu(BuildingMenu):
-
-    """
-    Building menu to edit a room.
-
-    """
-
-    def init(self, room):
-        self.add_choice("title", key="t", attr="key", glance="{obj.key}", text="""
-                -------------------------------------------------------------------------------
-                Editing the title of {{obj.key}}(#{{obj.id}})
-
-                You can change the title simply by entering it.
-                Use |y{back}|n to go back to the main menu.
-
-                Current title: |c{{obj.key}}|n
-        """.format(back="|n or |y".join(self.keys_go_back)))
-        self.add_choice_edit("description", "d")
-        self.add_choice("exits", "e", glance=glance_exits, attr="exits")
-
-
-# Menu functions
-def glance_exits(room):
-    """Show the room exits."""
-    if room.exits:
-        glance = ""
-        for exit in room.exits:
-            glance += "\n  |y{exit}|n".format(exit=exit.key)
-
-        return glance
-
-    return "\n  |gNo exit yet|n"
-
-
-

When the building menu opens, it displays each choice to the caller. A choice is displayed with its -title (rendered a bit nicely to show the key as well) and the glance. In the case of the exits -choice, the glance is a function, so the building menu calls this function giving it the object -being edited (the room here). The function should return the text to see.

-
> @edit here
-Building menu: A beautiful meadow
-
- [T]itle: A beautiful meadow
- [D]escription:
-   This is a beautiful meadow.  But so beautiful I can't describe it.
- [E]xits:
-  north
-  south
- [Q]uit the menu
-
-> q
-Closing the editor.
-
-
-
-

How do I know the parameters of the function to give?

-
-

The function you give can accept a lot of different parameters. This allows for a flexible approach -but might seem complicated at first. Basically, your function can accept any parameter, and the -building menu will send only the parameter based on their names. If your function defines an -argument named caller for instance (like def func(caller): ), then the building menu knows that -the first argument should contain the caller of the building menu. Here are the arguments, you -don’t have to specify them (if you do, they need to have the same name):

-
    -
  • menu: if your function defines an argument named menu, it will contain the building menu -itself.

  • -
  • choice: if your function defines an argument named choice, it will contain the Choice object -representing this menu choice.

  • -
  • string: if your function defines an argument named string, it will contain the user input to -reach this menu choice. This is not very useful, except on nomatch callbacks which we’ll see -later.

  • -
  • obj: if your function defines an argument named obj, it will contain the building menu edited -object.

  • -
  • caller: if your function defines an argument named caller, it will contain the caller of the -building menu.

  • -
  • Anything else: any other argument will contain the object being edited by the building menu.

  • -
-

So in our case:

-
def glance_exits(room):
-
-
-

The only argument we need is room. It’s not present in the list of possible arguments, so the -editing object of the building menu (the room, here) is given.

-
-

Why is it useful to get the menu or choice object?

-
-

Most of the time, you will not need these arguments. In very rare cases, you will use them to get -specific data (like the default attribute that was set). This tutorial will not elaborate on these -possibilities. Just know that they exist.

-

We should also define a text callback, so that we can enter our menu to see the room exits. We’ll -see how to edit them in the next section but this is a good opportunity to show a more complete -callback. To see it in action, as usual, replace the class and functions in commands/building.py:

-
# Our building menu
-
-class RoomBuildingMenu(BuildingMenu):
-
-    """
-    Building menu to edit a room.
-
-    """
-
-    def init(self, room):
-        self.add_choice("title", key="t", attr="key", glance="{obj.key}", text="""
-                -------------------------------------------------------------------------------
-                Editing the title of {{obj.key}}(#{{obj.id}})
-
-                You can change the title simply by entering it.
-                Use |y{back}|n to go back to the main menu.
-
-                Current title: |c{{obj.key}}|n
-        """.format(back="|n or |y".join(self.keys_go_back)))
-        self.add_choice_edit("description", "d")
-        self.add_choice("exits", "e", glance=glance_exits, attr="exits", text=text_exits)
-
-
-# Menu functions
-def glance_exits(room):
-    """Show the room exits."""
-    if room.exits:
-        glance = ""
-        for exit in room.exits:
-            glance += "\n  |y{exit}|n".format(exit=exit.key)
-
-        return glance
-
-    return "\n  |gNo exit yet|n"
-
-def text_exits(caller, room):
-    """Show the room exits in the choice itself."""
-    text = "-" * 79
-    text += "\n\nRoom exits:"
-    text += "\n Use |y@c|n to create a new exit."
-    text += "\n\nExisting exits:"
-    if room.exits:
-        for exit in room.exits:
-            text += "\n  |y@e {exit}|n".format(exit=exit.key)
-            if exit.aliases.all():
-                text += " (|y{aliases}|n)".format(aliases="|n, |y".join(
-                        alias for alias in exit.aliases.all()))
-            if exit.destination:
-                text += " toward {destination}".format(destination=exit.get_display_name(caller))
-    else:
-        text += "\n\n |gNo exit has yet been defined.|n"
-
-    return text
-
-
-

Look at the second callback in particular. It takes an additional argument, the caller (remember, -the argument names are important, their order is not relevant). This is useful for displaying -destination of exits accurately. Here is a demonstration of this menu:

-
> @edit here
-Building menu: A beautiful meadow
-
- [T]itle: A beautiful meadow
- [D]escription:
-   This is a beautiful meadow.  But so beautiful I can't describe it.
- [E]xits:
-  north
-  south
- [Q]uit the menu
-
-> e
--------------------------------------------------------------------------------
-
-Room exits:
- Use @c to create a new exit.
-
-Existing exits:
-  @e north (n) toward north(#4)
-  @e south (s) toward south(#7)
-
-> @
-Building menu: A beautiful meadow
-
- [T]itle: A beautiful meadow
- [D]escription:
-   This is a beautiful meadow.  But so beautiful I can't describe it.
- [E]xits:
-  north
-  south
- [Q]uit the menu
-
-> q
-Closing the building menu.
-
-
-

Using callbacks allows a great flexibility. We’ll now see how to handle sub-menus.

-
- -
-

Generic menu options

-

There are some options that can be set on any menu class. These options allow for greater -customization. They are class attributes (see the example below), so just set them in the class -body:

-
    -
  • keys_go_back (default to ["@"]): the keys to use to go back in the menu hierarchy, from choice -to root menu, from sub-menu to parent-menu. By default, only a @ is used. You can change this -key for one menu or all of them. You can define multiple return commands if you want.

  • -
  • sep_keys (default "."): this is the separator for nested keys. There is no real need to -redefine it except if you really need the dot as a key, and need nested keys in your menu.

  • -
  • joker_key (default to "*"): used for nested keys to indicate “any key”. Again, you shouldn’t -need to change it unless you want to be able to use the @*@ in a command key, and also need nested -keys in your menu.

  • -
  • min_shortcut (default to 1): although we didn’t see it here, one can create a menu choice -without giving it a key. If so, the menu system will try to “guess” the key. This option allows to -change the minimum length of any key for security reasons.

  • -
-

To set one of them just do so in your menu class(es):

-
class RoomBuildingMenu(BuildingMenu):
-    keys_go_back = ["/"]
-    min_shortcut = 2
-
-
-
-
-
-

Conclusion

-

Building menus mean to save you time and create a rich yet simple interface. But they can be -complicated to learn and require reading the source code to find out how to do such and such a -thing. This documentation, however long, is an attempt at describing this system, but chances are -you’ll still have questions about it after reading it, especially if you try to push this system to -a great extent. Do not hesitate to read the documentation of this contrib, it’s meant to be -exhaustive but user-friendly.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Choosing-An-SQL-Server.html b/docs/0.9.5/Choosing-An-SQL-Server.html deleted file mode 100644 index 94ffea0492..0000000000 --- a/docs/0.9.5/Choosing-An-SQL-Server.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - - - - Choosing An SQL Server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Choosing An SQL Server

-

This page gives an overview of the supported SQL databases as well as instructions on install:

-
    -
  • SQLite3 (default)

  • -
  • PostgreSQL

  • -
  • MySQL / MariaDB

  • -
-

Since Evennia uses Django, most of our notes are based off of what we -know from the community and their documentation. While the information below may be useful, you can -always find the most up-to-date and “correct” information at Django’s Notes about supported -Databases page.

-
-

SQLite3

-

SQLite3 is a light weight single-file database. It is our default database -and Evennia will set this up for you automatically if you give no other options. SQLite stores the -database in a single file (mygame/server/evennia.db3). This means it’s very easy to reset this -database - just delete (or move) that evennia.db3 file and run evennia migrate again! No server -process is needed and the administrative overhead and resource consumption is tiny. It is also very -fast since it’s run in-memory. For the vast majority of Evennia installs it will probably be all -that’s ever needed.

-

SQLite will generally be much faster than MySQL/PostgreSQL but its performance comes with two -drawbacks:

-
    -
  • SQLite ignores length constraints by design; it is possible -to store very large strings and numbers in fields that technically should not accept them. This is -not something you will notice; your game will read and write them and function normally, but this -can create some data migration problems requiring careful thought if you do need to change -databases later.

  • -
  • SQLite can scale well to storage of millions of objects, but if you end up with a thundering herd -of users trying to access your MUD and web site at the same time, or you find yourself writing long- -running functions to update large numbers of objects on a live game, either will yield errors and -interference. SQLite does not work reliably with multiple concurrent threads or processes accessing -its records. This has to do with file-locking clashes of the database file. So for a production -server making heavy use of process- or thread pools (or when using a third-party webserver like -Apache), a proper database is a more appropriate choice.

  • -
-
-

Install of SQlite3

-

This is installed and configured as part of Evennia. The database file is created as -mygame/server/evennia.db3 when you run

-
evennia migrate
-
-
-

without changing any database options. An optional requirement is the sqlite3 client program - -this is required if you want to inspect the database data manually. A shortcut for using it with the -evennia database is evennia dbshell. Linux users should look for the sqlite3 package for their -distro while Mac/Windows should get the sqlite-tools package from this -page.

-

To inspect the default Evennia database (once it’s been created), go to your game dir and do

-
    sqlite3 server/evennia.db3
-    # or
-    evennia dbshell
-
-
-

This will bring you into the sqlite command line. Use .help for instructions and .quit to exit. -See here for a cheat-sheet of commands.

-
-
-
-

PostgreSQL

-

PostgreSQL is an open-source database engine, recommended by Django. -While not as fast as SQLite for normal usage, it will scale better than SQLite, especially if your -game has an very large database and/or extensive web presence through a separate server process.

-
-

Install and initial setup of PostgreSQL

-

First, install the posgresql server. Version 9.6 is tested with Evennia. Packages are readily -available for all distributions. You need to also get the psql client (this is called postgresql- client on debian-derived systems). Windows/Mac users can find what they need on the postgresql -download page. You should be setting up a password for your -database-superuser (always called postgres) when you install.

-

For interaction with Evennia you need to also install psycopg2 to your Evennia install (pip install psycopg2-binary in your virtualenv). This acts as the python bridge to the database server.

-

Next, start the postgres client:

-
    psql -U postgres --password
-
-
-
-

:warning: Warning: With the --password argument, Postgres should prompt you for a password. -If it won’t, replace that with -p yourpassword instead. Do not use the -p argument unless you -have to since the resulting command, and your password, will be logged in the shell history.

-
-

This will open a console to the postgres service using the psql client.

-

On the psql command line:

-
CREATE USER evennia WITH PASSWORD 'somepassword';
-CREATE DATABASE evennia;
-
--- Postgres-specific optimizations
--- https://docs.djangoproject.com/en/dev/ref/databases/#optimizing-postgresql-s-configuration
-ALTER ROLE evennia SET client_encoding TO 'utf8';
-ALTER ROLE evennia SET default_transaction_isolation TO 'read committed';
-ALTER ROLE evennia SET timezone TO 'UTC';
-
-GRANT ALL PRIVILEGES ON DATABASE evennia TO evennia;
--- Other useful commands:
---  \l       (list all databases and permissions)
---  \q       (exit)
-
-
-

Here is a cheat-sheet for psql commands.

-

We create a database user ‘evennia’ and a new database named evennia (you can call them whatever -you want though). We then grant the ‘evennia’ user full privileges to the new database so it can -read/write etc to it. -If you in the future wanted to completely wipe the database, an easy way to do is to log in as the -postgres superuser again, then do DROP DATABASE evennia;, then CREATE and GRANT steps above -again to recreate the database and grant privileges.

-
-
-

Evennia PostgreSQL configuration

-

Edit `mygame/server/conf/secret_settings.py and add the following section:

-
#
-# PostgreSQL Database Configuration
-#
-DATABASES = {
-        'default': {
-            'ENGINE': 'django.db.backends.postgresql_psycopg2',
-            'NAME': 'evennia',
-            'USER': 'evennia',
-            'PASSWORD': 'somepassword',
-            'HOST': 'localhost',
-            'PORT': ''    # use default
-        }}
-
-
-

If you used some other name for the database and user, enter those instead. Run

-
evennia migrate
-
-
-

to populate your database. Should you ever want to inspect the database directly you can from now on -also use

-
evennia dbshell
-
-
-

as a shortcut to get into the postgres command line for the right database and user.

-

With the database setup you should now be able to start start Evennia normally with your new -database.

-
-
-
-

MySQL / MariaDB

-

MySQL is a commonly used proprietary database system, on par with -PostgreSQL. There is an open-source alternative called MariaDB that mimics -all functionality and command syntax of the former. So this section covers both.

-
-

Installing and initial setup of MySQL/MariaDB

-

First, install and setup MariaDB or MySQL for your specific server. Linux users should look for the -mysql-server or mariadb-server packages for their respective distributions. Windows/Mac users -will find what they need from the MySQL downloads or MariaDB -downloads pages. You also need the respective database clients -(mysql, mariadb-client), so you can setup the database itself. When you install the server you -should usually be asked to set up the database root user and password.

-

You will finally also need a Python interface to allow Evennia to talk to the database. Django -recommends the mysqlclient one. Install this into the evennia virtualenv with pip install mysqlclient.

-

Start the database client (this is named the same for both mysql and mariadb):

-
mysql -u root -p
-
-
-

You should get to enter your database root password (set this up when you installed the database -server).

-

Inside the database client interface:

-
CREATE USER 'evennia'@'localhost' IDENTIFIED BY 'somepassword';
-CREATE DATABASE evennia;
-ALTER DATABASE `evennia` CHARACTER SET utf8; -- note that it's `evennia` with back-ticks, not
-quotes!
-GRANT ALL PRIVILEGES ON evennia.* TO 'evennia'@'localhost';
-FLUSH PRIVILEGES;
--- use 'exit' to quit client
-
-
-

Here is a mysql command cheat sheet.

-

Above we created a new local user and database (we called both ‘evennia’ here, you can name them -what you prefer). We set the character set to utf8 to avoid an issue with prefix character length -that can pop up on some installs otherwise. Next we grant the ‘evennia’ user all privileges on the -evennia database and make sure the privileges are applied. Exiting the client brings us back to -the normal terminal/console.

-
-

Note: If you are not using MySQL for anything else you might consider granting the ‘evennia’ user -full privileges with GRANT ALL PRIVILEGES ON *.* TO 'evennia'@'localhost';. If you do, it means -you can use evennia dbshell later to connect to mysql, drop your database and re-create it as a -way of easy reset. Without this extra privilege you will be able to drop the database but not re- -create it without first switching to the database-root user.

-
-
-
-
-

Add MySQL configuration to Evennia

-

To tell Evennia to use your new database you need to edit mygame/server/conf/settings.py (or -secret_settings.py if you don’t want your db info passed around on git repositories).

-
-

Note: The Django documentation suggests using an external db.cnf or other external conf- -formatted file. Evennia users have however found that this leads to problems (see e.g. issue -#1184). To avoid trouble we recommend you simply put the configuration in -your settings as below.

-
-
    #
-    # MySQL Database Configuration
-    #
-    DATABASES = {
-       'default': {
-           'ENGINE': 'django.db.backends.mysql',
-           'NAME': 'evennia',
-           'USER': 'evennia',
-           'PASSWORD': 'somepassword',
-           'HOST': 'localhost',  # or an IP Address that your DB is hosted on
-           'PORT': '', # use default port
-       }
-    }
-
-
-

Change this to fit your database setup. Next, run:

-
evennia migrate
-
-
-

to populate your database. Should you ever want to inspect the database directly you can from now on -also use

-
evennia dbshell
-
-
-

as a shortcut to get into the postgres command line for the right database and user.

-

With the database setup you should now be able to start start Evennia normally with your new -database.

-
-
-

Others

-

No testing has been performed with Oracle, but it is also supported through Django. There are -community maintained drivers for MS SQL and possibly a few -others. If you try other databases out, consider expanding this page with instructions.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Client-Support-Grid.html b/docs/0.9.5/Client-Support-Grid.html deleted file mode 100644 index 1d3cac071e..0000000000 --- a/docs/0.9.5/Client-Support-Grid.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - Client Support Grid — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Client Support Grid

-

This grid tries to gather info about different MU clients when used with Evennia. -If you want to report a problem, update an entry or add a client, make a -new documentation issue for it. Everyone’s encouraged to report their findings.

-
-

Client Grid

-

Legend:

-
    -
  • Name: The name of the client. Also note if it’s OS-specific.

  • -
  • Version: Which version or range of client versions were tested.

  • -
  • Comments: Any quirks on using this client with Evennia should be added here.

  • -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Name

Version tested

Comments

Evennia Webclient

1.0+

Evennia-specific

tintin++

2.0+

No MXP support

tinyfugue

5.0+

No UTF-8 support

MUSHclient (Win)

4.94

NAWS reports full text area

Zmud (Win)

7.21

UNTESTED

Cmud (Win)

v3

UNTESTED

Potato_

2.0.0b16

No MXP, MCCP support. Win 32bit does not understand

“localhost”, must use 127.0.0.1.

Mudlet

3.4+

No known issues. Some older versions showed <> as html

under MXP.

SimpleMU (Win)

full

Discontinued. NAWS reports pixel size.

Atlantis (Mac)

0.9.9.4

No known issues.

GMUD

0.0.1

Can’t handle any telnet handshakes. Not recommended.

BeipMU (Win)

3.0.255

No MXP support. Best to enable “MUD prompt handling”, disable

“Handle HTML tags”.

MudRammer (IOS)

1.8.7

Bad Telnet Protocol compliance: displays spurious characters.

MUDMaster

1.3.1

UNTESTED

BlowTorch (Andr)

1.1.3

Telnet NOP displays as spurious character.

Mukluk (Andr)

2015.11.20

Telnet NOP displays as spurious character. Has UTF-8/Emoji

support.

Gnome-MUD (Unix)

0.11.2

Telnet handshake errors. First (only) attempt at logging in

fails.

Spyrit

0.4

No MXP, OOB support.

JamochaMUD

5.2

Does not support ANSI within MXP text.

DuckClient (Chrome)

4.2

No MXP support. Displays Telnet Go-Ahead and

WILL SUPPRESS-GO-AHEAD as ù character. Also seems to run

the version command on connection, which will not work in

MULTISESSION_MODES above 1.

KildClient

2.11.1

No known issues.

-
-
-

Workarounds for client issues:

-
-

Issue: Telnet NOP displays as spurious character.

-

Known clients:

-
    -
  • BlowTorch (Andr)

  • -
  • Mukluk (Andr)

  • -
-

Workaround:

-
    -
  • In-game: Use @option NOPKEEPALIVE=off for the session, or use the /save -parameter to disable it for that Evennia account permanently.

  • -
  • Client-side: Set a gag-type trigger on the NOP character to make it invisible to the client.

  • -
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Coding-FAQ.html b/docs/0.9.5/Coding-FAQ.html deleted file mode 100644 index b4f2a3292b..0000000000 --- a/docs/0.9.5/Coding-FAQ.html +++ /dev/null @@ -1,494 +0,0 @@ - - - - - - - - - Coding FAQ — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Coding FAQ

-

This FAQ page is for users to share their solutions to coding problems. Keep it brief and link to -the docs if you can rather than too lengthy explanations. Don’t forget to check if an answer already -exists before answering - maybe you can clarify that answer rather than to make a new Q&A section.

-
-

Table of Contents

- -
-
-

Will I run out of dbrefs?

-

Q: The #dbref of a database object is ever-increasing. Evennia doesn’t allow you to change or -reuse them. Will not a big/old game run out of dbref integers eventually?

-

A: No. For example, the default sqlite3 database’s max dbref is 2**64. If you created 10 000 -objects every second every minute and every day of the year it would take ~60 million years for you -to run out of dbref numbers. That’s a database of 140 TeraBytes, if every row was empty. If you are -still using Evennia at that point and has this concern, get back to us and we can discuss adding -dbref reuse then.

-
-
-

Removing default commands

-

Q: How does one remove (not replace) e.g. the default get Command from the -Character Command Set?

-

A: Go to mygame/commands/default_cmdsets.py. Find the CharacterCmdSet class. It has one -method named at_cmdset_creation. At the end of that method, add the following line: -self.remove(default_cmds.CmdGet()). See the Adding Commands Tutorial -for more info.

-
-
-

Preventing character from moving based on a condition

-

Q: How does one keep a character from using any exit, if they meet a certain condition? (I.E. in -combat, immobilized, etc.)

-

A: The at_before_move hook is called by Evennia just before performing any move. If it returns -False, the move is aborted. Let’s say we want to check for an Attribute cantmove. -Add the following code to the Character class:

-
def at_before_move(self, destination):
-    "Called just before trying to move"
-    if self.db.cantmove: # replace with condition you want to test
-        self.msg("Something is preventing you from moving!")
-        return False
-    return True
-
-
-
-
-

Reference initiating object in an EvMenu command.

-

Q: An object has a Command on it starts up an EvMenu instance. How do I capture a reference to -that object for use in the menu?

-

A: When an EvMenu is started, the menu object is stored as caller.ndb._menutree. -This is a good place to store menu-specific things since it will clean itself up when the menu -closes. When initiating the menu, any additional keywords you give will be available for you as -properties on this menu object:

-
class MyObjectCommand(Command):
-    # A Command stored on an object (the object is always accessible from
-    # the Command as self.obj)
-    def func(self):
-        # add the object as the stored_obj menu property
-        EvMenu(caller, ..., stored_obj=self.obj)
-
-
-
-

Inside the menu you can now access the object through caller.ndb._menutree.stored_obj.

-
-
-

Adding color to default Evennia Channels

-

Q: How do I add colors to the names of Evennia channels?

-

A: The Channel typeclass’ channel_prefix method decides what is shown at the beginning of a -channel send. Edit mygame/typeclasses/channels.py (and then @reload):

-
# define our custom color names
-CHANNEL_COLORS = {'public': '|015Public|n',
-                  'newbie': '|550N|n|551e|n|552w|n|553b|n|554i|n|555e|n',
-                  'staff': '|010S|n|020t|n|030a|n|040f|n|050f|n'}
-
-# Add to the Channel class
-    # ...
-    def channel_prefix(self, msg, emit=False):
-        prefix_string = ""
-        if self.key in COLORS:
-            prefix_string = "[%s] " % CHANNEL_COLORS.get(self.key.lower())
-        else:
-            prefix_string = "[%s] " % self.key.capitalize()
-        return prefix_string
-
-
-

Additional hint: To make colors easier to change from one place you could instead put the -CHANNEL_COLORS dict in your settings file and import it as from django.conf.settings import CHANNEL_COLORS.

-
-
-

Selectively turn off commands in a room

-

Q: I want certain commands to turn off in a given room. They should still work normally for -staff.

-

A: This is done using a custom cmdset on a room locked with the ‘call’ lock type. Only -if this lock is passed will the commands on the room be made available to an object inside it. Here -is an example of a room where certain commands are disabled for non-staff:

-
# in mygame/typeclasses/rooms.py
-
-from evennia import default_commands, CmdSet
-
-class CmdBlocking(default_commands.MuxCommand):
-    # block commands give, get, inventory and drop
-    key = "give"
-    aliases = ["get", "inventory", "drop"]
-    def func(self):
-        self.caller.msg("You cannot do that in this room.")
-
-class BlockingCmdSet(CmdSet):
-    key = "blocking_cmdset"
-    # default commands have prio 0
-    priority = 1
-    def at_cmdset_creation(self):
-        self.add(CmdBlocking())
-
-class BlockingRoom(Room):
-    def at_object_creation(self):
-        self.cmdset.add(BlockingCmdSet, permanent=True)
-        # only share commands with players in the room that
-        # are NOT Builders or higher
-        self.locks.add("call:not perm(Builders)")
-
-
-

After @reload, make some BlockingRooms (or switch a room to it with @typeclass). Entering one -will now replace the given commands for anyone that does not have the Builders or higher -permission. Note that the ‘call’ lock is special in that even the superuser will be affected by it -(otherwise superusers would always see other player’s cmdsets and a game would be unplayable for -superusers).

-
-
-

Select Command based on a condition

-

Q: I want a command to be available only based on a condition. For example I want the “werewolf” -command to only be available on a full moon, from midnight to three in-game time.

-

A: This is easiest accomplished by putting the “werewolf” command on the Character as normal, -but to lock it with the “cmd” type lock. Only if the “cmd” lock type is passed will the -command be available.

-
# in mygame/commands/command.py
-
-from evennia import Command
-
-class CmdWerewolf(Command):
-    key = "werewolf"
-    # lock full moon, between 00:00 (midnight) and 03:00.
-    locks = "cmd:is_full_moon(0, 3)"
-    def func(self):
-        # ...
-
-
-

Add this to the default cmdset as usual. The is_full_moon lock -function does not yet exist. We must create that:

-
# in mygame/server/conf/lockfuncs.py
-
-def is_full_moon(accessing_obj, accessed_obj,
-                 starthour, endhour, *args, **kwargs):
-    # calculate if the moon is full here and
-    # if current game time is between starthour and endhour
-    # return True or False
-
-
-
-

After a @reload, the werewolf command will be available only at the right time, that is when the -is_full_moon lock function returns True.

-
-
-

Automatically updating code when reloading

-

Q: I have a development server running Evennia. Can I have the server update its code-base when -I reload?

-

A: Having a development server that pulls updated code whenever you reload it can be really -useful if you have limited shell access to your server, or want to have it done automatically. If -you have your project in a configured Git environment, it’s a matter of automatically calling git pull when you reload. And that’s pretty straightforward:

-

In /server/conf/at_server_startstop.py:

-
import subprocess
-
-# ... other hooks ...
-
-def at_server_reload_stop():
-    """
-    This is called only time the server stops before a reload.
-    """
-    print("Pulling from the game repository...")
-    process = subprocess.call(["git", "pull"], shell=False)
-
-
-

That’s all. We call subprocess to execute a shell command (that code works on Windows and Linux, -assuming the current directory is your game directory, which is probably the case when you run -Evennia). call waits for the process to complete, because otherwise, Evennia would reload on -partially-modified code, which would be problematic.

-

Now, when you enter @reload on your development server, the game repository is updated from the -configured remote repository (Github, for instance). Your development cycle could resemble -something like:

-
    -
  1. Coding on the local machine.

  2. -
  3. Testing modifications.

  4. -
  5. Committing once, twice or more (being sure the code is still working, unittests are pretty useful -here).

  6. -
  7. When the time comes, login to the development server and run @reload.

  8. -
-

The reloading might take one or two additional seconds, since Evennia will pull from your remote Git -repository. But it will reload on it and you will have your modifications ready, without needing -connecting to your server using SSH or something similar.

-
-
-

Changing all exit messages

-

Q: How can I change the default exit messages to something like “XXX leaves east” or “XXX -arrives from the west”?

-

A: the default exit messages are stored in two hooks, namely announce_move_from and -announce_move_to, on the Character typeclass (if what you want to change is the message other -characters will see when a character exits).

-

These two hooks provide some useful features to easily update the message to be displayed. They -take both the default message and mapping as argument. You can easily call the parent hook with -these information:

-
    -
  • The message represents the string of characters sent to characters in the room when a character -leaves.

  • -
  • The mapping is a dictionary containing additional mappings (you will probably not need it for -simple customization).

  • -
-

It is advisable to look in the code of both -hooks, and read the -hooks’ documentation. The explanations on how to quickly update the message are shown below:

-
# In typeclasses/characters.py
-"""
-Characters
-
-"""
-from evennia import DefaultCharacter
-
-class Character(DefaultCharacter):
-    """
-    The default character class.
-
-    ...
-    """
-
-    def announce_move_from(self, destination, msg=None, mapping=None):
-        """
-        Called if the move is to be announced. This is
-        called while we are still standing in the old
-        location.
-
-        Args:
-            destination (Object): The place we are going to.
-            msg (str, optional): a replacement message.
-            mapping (dict, optional): additional mapping objects.
-
-        You can override this method and call its parent with a
-        message to simply change the default message.  In the string,
-        you can use the following as mappings (between braces):
-            object: the object which is moving.
-            exit: the exit from which the object is moving (if found).
-            origin: the location of the object before the move.
-            destination: the location of the object after moving.
-
-        """
-        super().announce_move_from(destination, msg="{object} leaves {exit}.")
-
-    def announce_move_to(self, source_location, msg=None, mapping=None):
-        """
-        Called after the move if the move was not quiet. At this point
-        we are standing in the new location.
-
-        Args:
-            source_location (Object): The place we came from
-            msg (str, optional): the replacement message if location.
-            mapping (dict, optional): additional mapping objects.
-
-        You can override this method and call its parent with a
-        message to simply change the default message.  In the string,
-        you can use the following as mappings (between braces):
-            object: the object which is moving.
-            exit: the exit from which the object is moving (if found).
-            origin: the location of the object before the move.
-            destination: the location of the object after moving.
-
-        """
-        super().announce_move_to(source_location, msg="{object} arrives from the {exit}.")
-
-
-

We override both hooks, but call the parent hook to display a different message. If you read the -provided docstrings, you will better understand why and how we use mappings (information between -braces). You can provide additional mappings as well, if you want to set a verb to move, for -instance, or other, extra information.

-
-
-

Add parsing with the “to” delimiter

-

Q: How do I change commands to undestand say give obj to target as well as the default give obj = target?

-

A: You can make change the default MuxCommand parent with your own class making a small change -in its parse method:

-
    # in mygame/commands/command.py
-    from evennia import default_cmds
-    class MuxCommand(default_cmds.MuxCommand):
-        def parse(self):
-            """Implement an additional parsing of 'to'"""
-            super().parse()
-            if " to " in self.args:
-                self.lhs, self.rhs = self.args.split(" to ", 1)
-
-
-

Next you change the parent of the default commands in settings:

-
    COMMAND_DEFAULT_CLASS = "commands.command.MuxCommand"
-
-
-

Do a @reload and all default commands will now use your new tweaked parent class. A copy of the -MuxCommand class is also found commented-out in the mygame/commands/command.py file.

-
-
-

Store last used session IP address

-

Q: If a user has already logged out of an Evennia account, their IP is no longer visible to -staff that wants to ban-by-ip (instead of the user) with @ban/ip?

-

A: One approach is to write the IP from the last session onto the “account” account object.

-

typeclasses/accounts.py

-
    def at_post_login(self, session=None, **kwargs):
-        super().at_post_login(session=session, **kwargs)
-        self.db.lastsite = self.sessions.all()[-1].address
-
-
-

Adding timestamp for login time and appending to a list to keep the last N login IP addresses and -timestamps is possible, also. Additionally, if you don’t want the list to grow beyond a -do_not_exceed length, conditionally pop a value after you’ve added it, if the length has grown too -long.

-

NOTE: You’ll need to add import time to generate the login timestamp.

-
    def at_post_login(self, session=None, **kwargs):
-        super().at_post_login(session=session, **kwargs)
-        do_not_exceed = 24  # Keep the last two dozen entries
-        session = self.sessions.all()[-1]  # Most recent session
-        if not self.db.lastsite:
-           self.db.lastsite = []
-        self.db.lastsite.insert(0, (session.address, int(time.time())))
-        if len(self.db.lastsite) > do_not_exceed:
-            self.db.lastsite.pop()
-
-
-

This only stores the data. You may want to interface the @ban command or make a menu-driven viewer -for staff to browse the list and display how long ago the login occurred.

-
-
-

Non-latin characters in EvTable

-

Q: When using e.g. Chinese characters in EvTable, some lines appear to be too wide, for example

-
+------+------+
-|      |      |
-|  测试  |  测试  |
-|      |      |
-+~~~~~~+~~~~~~+
-
-
-

A: The reason for this is because certain non-latin characters are visually much wider than -their len() suggests. There is little Evennia can (reliably) do about this. If you are using such -characters, you need to make sure to use a suitable mono-spaced font where are width are equal. You -can set this in your web client and need to recommend it for telnet-client users. See this -discussion where some suitable fonts are suggested.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Coding-Introduction.html b/docs/0.9.5/Coding-Introduction.html deleted file mode 100644 index 057114569d..0000000000 --- a/docs/0.9.5/Coding-Introduction.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - Coding Introduction — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Coding Introduction

-

Evennia allows for a lot of freedom when designing your game - but to code efficiently you still -need to adopt some best practices as well as find a good place to start to learn.

-

Here are some pointers to get you going.

-
-

Python

-

Evennia is developed using Python. Even if you are more of a designer than a coder, it is wise to -learn how to read and understand basic Python code. If you are new to Python, or need a refresher, -take a look at our two-part Python introduction.

-
-
-

Explore Evennia interactively

-

When new to Evennia it can be hard to find things or figure out what is available. Evennia offers a -special interactive python shell that allows you to experiment and try out things. It’s recommended -to use ipython for this since the vanilla python prompt is very limited. Here -are some simple commands to get started:

-
# [open a new console/terminal]
-# [activate your evennia virtualenv in this console/terminal]
-pip install ipython    # [only needed the first time]
-cd mygame
-evennia shell
-
-
-

This will open an Evennia-aware python shell (using ipython). From within this shell, try

-
import evennia
-evennia.<TAB>
-
-
-

That is, enter evennia. and press the <TAB> key. This will show you all the resources made -available at the top level of Evennia’s “flat API”. See the flat API page for more -info on how to explore it efficiently.

-

You can complement your exploration by peeking at the sections of the much more detailed -Developer Central. The Tutorials section also contains a growing collection -of system- or implementation-specific help.

-
-
-

Use a python syntax checker

-

Evennia works by importing your own modules and running them as part of the server. Whereas Evennia -should just gracefully tell you what errors it finds, it can nevertheless be a good idea for you to -check your code for simple syntax errors before you load it into the running server. There are -many python syntax checkers out there. A fast and easy one is -pyflakes, a more verbose one is -pylint. You can also check so that your code looks up to snuff using -pep8. Even with a syntax checker you will not be able to catch -every possible problem - some bugs or problems will only appear when you actually run the code. But -using such a checker can be a good start to weed out the simple problems.

-
-
-

Plan before you code

-

Before you start coding away at your dream game, take a look at our Game Planning -page. It might hopefully help you avoid some common pitfalls and time sinks.

-
-
-

Code in your game folder, not in the evennia/ repository

-

As part of the Evennia setup you will create a game folder to host your game code. This is your -home. You should never need to modify anything in the evennia library (anything you download -from us, really). You import useful functionality from here and if you see code you like, copy&paste -it out into your game folder and edit it there.

-

If you find that Evennia doesn’t support some functionality you need, make a -Feature Request about it. Same goes for bugs. If you add features or fix bugs -yourself, please consider Contributing your changes upstream!

-
-
-

Learn to read tracebacks

-

Python is very good at reporting when and where things go wrong. A traceback shows everything you -need to know about crashing code. The text can be pretty long, but you usually are only interested -in the last bit, where it says what the error is and at which module and line number it happened - -armed with this info you can resolve most problems.

-

Evennia will usually not show the full traceback in-game though. Instead the server outputs errors -to the terminal/console from which you started Evennia in the first place. If you want more to show -in-game you can add IN_GAME_ERRORS = True to your settings file. This will echo most (but not all) -tracebacks both in-game as well as to the terminal/console. This is a potential security problem -though, so don’t keep this active when your game goes into production.

-
-

A common confusing error is finding that objects in-game are suddenly of the type DefaultObject -rather than your custom typeclass. This happens when you introduce a critical Syntax error to the -module holding your custom class. Since such a module is not valid Python, Evennia can’t load it at -all. Instead of crashing, Evennia will then print the full traceback to the terminal/console and -temporarily fall back to the safe DefaultObject until you fix the problem and reload.

-
-
-
-

Docs are here to help you

-

Some people find reading documentation extremely dull and shun it out of principle. That’s your -call, but reading docs really does help you, promise! Evennia’s documentation is pretty thorough -and knowing what is possible can often give you a lot of new cool game ideas. That said, if you -can’t find the answer in the docs, don’t be shy to ask questions! The -discussion group and the -irc chat are also there for you.

-
-
-

The most important point

-

And finally, of course, have fun!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Coding-Utils.html b/docs/0.9.5/Coding-Utils.html deleted file mode 100644 index be8f8994f2..0000000000 --- a/docs/0.9.5/Coding-Utils.html +++ /dev/null @@ -1,426 +0,0 @@ - - - - - - - - - Coding Utils — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Coding Utils

-

Evennia comes with many utilities to help with common coding tasks. Most are accessible directly -from the flat API, otherwise you can find them in the evennia/utils/ folder.

-
-

Searching

-

A common thing to do is to search for objects. There it’s easiest to use the search method defined -on all objects. This will search for objects in the same location and inside the self object:

-
     obj = self.search(objname)
-
-
-

The most common time one needs to do this is inside a command body. obj = self.caller.search(objname) will search inside the caller’s (typically, the character that typed -the command) .contents (their “inventory”) and .location (their “room”).

-

Give the keyword global_search=True to extend search to encompass entire database. Aliases will -also be matched by this search. You will find multiple examples of this functionality in the default -command set.

-

If you need to search for objects in a code module you can use the functions in -evennia.utils.search. You can access these as shortcuts evennia.search_*.

-
     from evennia import search_object
-     obj = search_object(objname)
-
-
- -

Note that these latter methods will always return a list of results, even if the list has one or -zero entries.

-
-
-

Create

-

Apart from the in-game build commands (@create etc), you can also build all of Evennia’s game -entities directly in code (for example when defining new create commands).

-
   import evennia
-
-   myobj = evennia.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj")
-
-
- -

Each of these create-functions have a host of arguments to further customize the created entity. See -evennia/utils/create.py for more information.

-
-
-

Logging

-

Normally you can use Python print statements to see output to the terminal/log. The print -statement should only be used for debugging though. For producion output, use the logger which -will create proper logs either to terminal or to file.

-
     from evennia import logger
-     #
-     logger.log_err("This is an Error!")
-     logger.log_warn("This is a Warning!")
-     logger.log_info("This is normal information")
-     logger.log_dep("This feature is deprecated")
-
-
-

There is a special log-message type, log_trace() that is intended to be called from inside a -traceback - this can be very useful for relaying the traceback message back to log without having it -kill the server.

-
     try:
-       # [some code that may fail...]
-     except Exception:
-       logger.log_trace("This text will show beneath the traceback itself.")
-
-
-

The log_file logger, finally, is a very useful logger for outputting arbitrary log messages. This -is a heavily optimized asynchronous log mechanism using -threads to avoid overhead. You should be -able to use it for very heavy custom logging without fearing disk-write delays.

-
 logger.log_file(message, filename="mylog.log")
-
-
-

If not an absolute path is given, the log file will appear in the mygame/server/logs/ directory. -If the file already exists, it will be appended to. Timestamps on the same format as the normal -Evennia logs will be automatically added to each entry. If a filename is not specified, output will -be written to a file game/logs/game.log.

-
-
-

Time Utilities

-
-

Game time

-

Evennia tracks the current server time. You can access this time via the evennia.gametime -shortcut:

-
from evennia import gametime
-
-# all the functions below return times in seconds).
-
-# total running time of the server
-runtime = gametime.runtime()
-# time since latest hard reboot (not including reloads)
-uptime = gametime.uptime()
-# server epoch (its start time)
-server_epoch = gametime.server_epoch()
-
-# in-game epoch (this can be set by `settings.TIME_GAME_EPOCH`.
-# If not, the server epoch is used.
-game_epoch = gametime.game_epoch()
-# in-game time passed since time started running
-gametime = gametime.gametime()
-# in-game time plus game epoch (i.e. the current in-game
-# time stamp)
-gametime = gametime.gametime(absolute=True)
-# reset the game time (back to game epoch)
-gametime.reset_gametime()
-
-
-
-

The setting TIME_FACTOR determines how fast/slow in-game time runs compared to the real world. The -setting TIME_GAME_EPOCH sets the starting game epoch (in seconds). The functions from the -gametime module all return their times in seconds. You can convert this to whatever units of time -you desire for your game. You can use the @time command to view the server time info.

-

You can also schedule things to happen at specific in-game times using the -gametime.schedule function:

-
import evennia
-
-def church_clock:
-    limbo = evennia.search_object(key="Limbo")
-    limbo.msg_contents("The church clock chimes two.")
-
-gametime.schedule(church_clock, hour=2)
-
-
-
-
-

utils.time_format()

-

This function takes a number of seconds as input (e.g. from the gametime module above) and -converts it to a nice text output in days, hours etc. It’s useful when you want to show how old -something is. It converts to four different styles of output using the style keyword:

-
    -
  • style 0 - 5d:45m:12s (standard colon output)

  • -
  • style 1 - 5d (shows only the longest time unit)

  • -
  • style 2 - 5 days, 45 minutes (full format, ignores seconds)

  • -
  • style 3 - 5 days, 45 minutes, 12 seconds (full format, with seconds)

  • -
-
-
-

utils.delay()

-
from evennia import utils
-
-def _callback(obj, text):
-    obj.msg(text)
-
-# wait 10 seconds before sending "Echo!" to obj (which we assume is defined)
-deferred = utils.delay(10, _callback, obj, "Echo!", persistent=False)
-
-# code here will run immediately, not waiting for the delay to fire!
-
-
-
-

This creates an asynchronous delayed call. It will fire the given callback function after the given -number of seconds. This is a very light wrapper over a Twisted -Deferred. Normally this is run -non-persistently, which means that if the server is @reloaded before the delay is over, the -callback will never run (the server forgets it). If setting persistent to True, the delay will be -stored in the database and survive a @reload - but for this to work it is susceptible to the same -limitations incurred when saving to an Attribute.

-

The deferred return object can usually be ignored, but calling its .cancel() method will abort -the delay prematurely.

-

utils.delay is the lightest form of delayed call in Evennia. For other way to create time-bound -tasks, see the TickerHandler and Scripts.

-
-

Note that many delayed effects can be achieved without any need for an active timer. For example -if you have a trait that should recover a point every 5 seconds you might just need its value when -it’s needed, but checking the current time and calculating on the fly what value it should have.

-
-
-
-
-

Object Classes

-
-

utils.inherits_from()

-

This useful function takes two arguments - an object to check and a parent. It returns True if -object inherits from parent at any distance (as opposed to Python’s in-built is_instance() that -will only catch immediate dependence). This function also accepts as input any combination of -classes, instances or python-paths-to-classes.

-

Note that Python code should usually work with duck -typing. But in Evennia’s case it can sometimes be useful -to check if an object inherits from a given Typeclass as a way of identification. Say -for example that we have a typeclass Animal. This has a subclass Felines which in turn has a -subclass HouseCat. Maybe there are a bunch of other animal types too, like horses and dogs. Using -inherits_from will allow you to check for all animals in one go:

-
     from evennia import utils
-     if (utils.inherits_from(obj, "typeclasses.objects.animals.Animal"):
-        obj.msg("The bouncer stops you in the door. He says: 'No talking animals allowed.'")
-
-
-
-
-
-

Text utilities

-

In a text game, you are naturally doing a lot of work shuffling text back and forth. Here is a non- -complete selection of text utilities found in evennia/utils/utils.py (shortcut evennia.utils). -If nothing else it can be good to look here before starting to develop a solution of your own.

-
-

utils.fill()

-

This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also -indents as needed.

-
     outtxt = fill(intxt, width=78, indent=4)
-
-
-
-
-

utils.crop()

-

This function will crop a very long line, adding a suffix to show the line actually continues. This -can be useful in listings when showing multiple lines would mess up things.

-
     intxt = "This is a long text that we want to crop."
-     outtxt = crop(intxt, width=19, suffix="[...]")
-     # outtxt is now "This is a long text[...]"
-
-
-
-
-

utils.dedent()

-

This solves what may at first glance appear to be a trivial problem with text - removing -indentations. It is used to shift entire paragraphs to the left, without disturbing any further -formatting they may have. A common case for this is when using Python triple-quoted strings in code

-
    -
  • they will retain whichever indentation they have in the code, and to make easily-readable source -code one usually don’t want to shift the string to the left edge.

  • -
-
    #python code is entered at a given indentation
-          intxt = """
-          This is an example text that will end
-          up with a lot of whitespace on the left.
-                    It also has indentations of
-                    its own."""
-          outtxt = dedent(intxt)
-          # outtxt will now retain all internal indentation
-          # but be shifted all the way to the left.
-
-
-

Normally you do the dedent in the display code (this is for example how the help system homogenizes -help entries).

-
-
-

to_str() and to_bytes()

-

Evennia supplies two utility functions for converting text to the correct -encodings. to_str() and to_bytes(). Unless you are adding a custom protocol and -need to send byte-data over the wire, to_str is the only one you’ll need.

-

The difference from Python’s in-built str() and bytes() operators are that -the Evennia ones makes use of the ENCODINGS setting and will try very hard to -never raise a traceback but instead echo errors through logging. See -here for more info.

-
-
-

Ansi Coloring Tools

- -
-
-
-

Display utilities

-
-

Making ascii tables

-

The EvTable class (evennia/utils/evtable.py) can be used -to create correctly formatted text tables. There is also -EvForm (evennia/utils/evform.py). This reads a fixed-format -text template from a file in order to create any level of sophisticated ascii layout. Both evtable -and evform have lots of options and inputs so see the header of each module for help.

-

The third-party PrettyTable module is also included in -Evennia. PrettyTable is considered deprecated in favor of EvTable since PrettyTable cannot handle -ANSI colour. PrettyTable can be found in evennia/utils/prettytable/. See its homepage above for -instructions.

-
- -
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Command-Cooldown.html b/docs/0.9.5/Command-Cooldown.html deleted file mode 100644 index 9c51ec8a07..0000000000 --- a/docs/0.9.5/Command-Cooldown.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - - - Command Cooldown — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Command Cooldown

-

Some types of games want to limit how often a command can be run. If a -character casts the spell Firestorm, you might not want them to spam that -command over and over. Or in an advanced combat system, a massive swing may -offer a chance of lots of damage at the cost of not being able to re-do it for -a while. Such effects are called cooldowns.

-

This page exemplifies a very resource-efficient way to do cooldowns. A more -‘active’ way is to use asynchronous delays as in the command duration -tutorial, the two might be useful to -combine if you want to echo some message to the user after the cooldown ends.

-
-

Non-persistent cooldown

-

This little recipe will limit how often a particular command can be run. Since -Commands are class instances, and those are cached in memory, a command -instance will remember things you store on it. So just store the current time -of execution! Next time the command is run, it just needs to check if it has -that time stored, and compare it with the current time to see if a desired -delay has passed.

-
import time
-from evennia import default_cmds
-    
-class CmdSpellFirestorm(default_cmds.MuxCommand):
-    """
-    Spell - Firestorm
-
-    Usage:
-      cast firestorm <target>
-    
-    This will unleash a storm of flame. You can only release one
-    firestorm every five minutes (assuming you have the mana).
-    """
-    key = "cast firestorm"
-    locks = "cmd:isFireMage()"
-        
-    def func(self):
-        "Implement the spell"
-    
-        # check cooldown (5 minute cooldown)
-        now = time.time()
-        if hasattr(self, "lastcast") and \
-                now - self.lastcast < 5 * 60:
-            message = "You cannot cast this spell again yet."
-            self.caller.msg(message)
-            return
-    
-        #[the spell effect is implemented]
-    
-        # if the spell was successfully cast, store the casting time
-        self.lastcast = now
-
-
-

We just check the lastcast flag, and update it if everything works out. -Simple and very effective since everything is just stored in memory. The -drawback of this simple scheme is that it’s non-persistent. If you do -@reload, the cache is cleaned and all such ongoing cooldowns will be -forgotten. It is also limited only to this one command, other commands cannot -(easily) check for this value.

-
-
-

Persistent cooldown

-

This is essentially the same mechanism as the simple one above, except we use -the database to store the information which means the cooldown will survive a -server reload/reboot. Since commands themselves have no representation in the -database, you need to use the caster for the storage.

-
    # inside the func() of CmdSpellFirestorm as above
-
-    # check cooldown (5 minute cooldown)
-            
-    now = time.time()
-    lastcast = self.caller.db.firestorm_lastcast
-            
-    if lastcast and now - lastcast < 5 * 60:
-        message = "You need to wait before casting this spell again."
-        self.caller.msg(message)
-        return
-      
-    #[the spell effect is implemented]
-    
-    # if the spell was successfully cast, store the casting time
-    self.caller.db.firestorm_lastcast = now
-
-
-

Since we are storing as an Attribute, we need to identify the -variable as firestorm_lastcast so we are sure we get the right one (we’ll -likely have other skills with cooldowns after all). But this method of -using cooldowns also has the advantage of working between commands - you can -for example let all fire-related spells check the same cooldown to make sure -the casting of Firestorm blocks all fire-related spells for a while. Or, in -the case of taking that big swing with the sword, this could now block all -other types of attacks for a while before the warrior can recover.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Command-Duration.html b/docs/0.9.5/Command-Duration.html deleted file mode 100644 index e923362989..0000000000 --- a/docs/0.9.5/Command-Duration.html +++ /dev/null @@ -1,512 +0,0 @@ - - - - - - - - - Command Duration — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Command Duration

-

Before reading this tutorial, if you haven’t done so already, you might want to -read the documentation on commands to get a basic understanding of -how commands work in Evennia.

-

In some types of games a command should not start and finish immediately. -Loading a crossbow might take a bit of time to do - time you don’t have when -the enemy comes rushing at you. Crafting that armour will not be immediate -either. For some types of games the very act of moving or changing pose all -comes with a certain time associated with it.

-
-

The simple way to pause commands with yield

-

Evennia allows a shortcut in syntax to create simple pauses in commands. This -syntax uses the yield keyword. The yield keyword is used in Python to -create generators, although you don’t need to know what generators are to use -this syntax. A short example will probably make it clear:

-
class CmdTest(Command):
-
-    """
-    A test command just to test waiting.
-
-    Usage:
-        test
-
-    """
-
-    key = "test"
-    locks = "cmd:all()"
-
-    def func(self):
-        self.msg("Before ten seconds...")
-        yield 10
-        self.msg("Afterwards.")
-
-
-
-

Important: The yield functionality will only work in the func method of -Commands. It only works because Evennia has especially -catered for it in Commands. If you want the same functionality elsewhere you -must use the interactive decorator.

-
-

The important line is the yield 10. It tells Evennia to “pause” the command -and to wait for 10 seconds to execute the rest. If you add this command and -run it, you’ll see the first message, then, after a pause of ten seconds, the -next message. You can use yield several times in your command.

-

This syntax will not “freeze” all commands. While the command is “pausing”, -you can execute other commands (or even call the same command again). And -other players aren’t frozen either.

-
-

Note: this will not save anything in the database. If you reload the game -while a command is “paused”, it will not resume after the server has -reloaded.

-
-
-
-

The more advanced way with utils.delay

-

The yield syntax is easy to read, easy to understand, easy to use. But it’s not that flexible if -you want more advanced options. Learning to use alternatives might be much worth it in the end.

-

Below is a simple command example for adding a duration for a command to finish.

-
from evennia import default_cmds, utils
-    
-class CmdEcho(default_cmds.MuxCommand):
-    """
-    wait for an echo
-    
-    Usage:
-      echo <string>
-    
-    Calls and waits for an echo
-    """
-    key = "echo"
-    locks = "cmd:all()"
-    
-    def func(self):
-        """
-         This is called at the initial shout.
-        """
-        self.caller.msg("You shout '%s' and wait for an echo ..." % self.args)
-        # this waits non-blocking for 10 seconds, then calls self.echo
-        utils.delay(10, self.echo) # call echo after 10 seconds
-    
-    def echo(self):
-        "Called after 10 seconds."
-        shout = self.args
-        string = "You hear an echo: %s ... %s ... %s"
-        string = string % (shout.upper(), shout.capitalize(), shout.lower())
-        self.caller.msg(string)
-
-
-

Import this new echo command into the default command set and reload the server. You will find that -it will take 10 seconds before you see your shout coming back. You will also find that this is a -non-blocking effect; you can issue other commands in the interim and the game will go on as usual. -The echo will come back to you in its own time.

-
-

About utils.delay()

-

utils.delay(timedelay, callback, persistent=False, *args, **kwargs) is a useful function. It will -wait timedelay seconds, then call the callback function, optionally passing to it the arguments -provided to utils.delay by way of *args and/or **kwargs`.

-
-

Note: The callback argument should be provided with a python path to the desired function, for -instance my_object.my_function instead of my_object.my_function(). Otherwise my_function would -get called and run immediately upon attempting to pass it to the delay function. -If you want to provide arguments for utils.delay to use, when calling your callback function, you -have to do it separatly, for instance using the utils.delay *args and/or **kwargs, as mentioned -above.

-
-
-

If you are not familiar with the syntax *args and **kwargs, see the Python documentation -here.

-
-

Looking at it you might think that utils.delay(10, callback) in the code above is just an -alternative to some more familiar thing like time.sleep(10). This is not the case. If you do -time.sleep(10) you will in fact freeze the entire server for ten seconds! The utils.delay()is -a thin wrapper around a Twisted -Deferred that will delay -execution until 10 seconds have passed, but will do so asynchronously, without bothering anyone else -(not even you - you can continue to do stuff normally while it waits to continue).

-

The point to remember here is that the delay() call will not “pause” at that point when it is -called (the way yield does in the previous section). The lines after the delay() call will -actually execute right away. What you must do is to tell it which function to call after the time -has passed (its “callback”). This may sound strange at first, but it is normal practice in -asynchronous systems. You can also link such calls together as seen below:

-
from evennia import default_cmds, utils
-    
-class CmdEcho(default_cmds.MuxCommand):
-    """
-    waits for an echo
-    
-    Usage:
-      echo <string>
-    
-    Calls and waits for an echo
-    """
-    key = "echo"
-    locks = "cmd:all()"
-    
-    def func(self):
-        "This sets off a chain of delayed calls"
-        self.caller.msg("You shout '%s', waiting for an echo ..." % self.args)
-
-        # wait 2 seconds before calling self.echo1
-        utils.delay(2, self.echo1)
-    
-    # callback chain, started above
-    def echo1(self):
-        "First echo"
-        self.caller.msg("... %s" % self.args.upper())
-        # wait 2 seconds for the next one
-        utils.delay(2, self.echo2)
-
-    def echo2(self):
-        "Second echo"
-        self.caller.msg("... %s" % self.args.capitalize())
-        # wait another 2 seconds
-        utils.delay(2, callback=self.echo3)
-
-    def echo3(self):
-        "Last echo"
-        self.caller.msg("... %s ..." % self.args.lower())
-
-
-

The above version will have the echoes arrive one after another, each separated by a two second -delay.

-
> echo Hello!
-... HELLO!
-... Hello!
-... hello! ...
-
-
-
-
-
-

Blocking commands

-

As mentioned, a great thing about the delay introduced by yield or utils.delay() is that it does -not block. It just goes on in the background and you are free to play normally in the interim. In -some cases this is not what you want however. Some commands should simply “block” other commands -while they are running. If you are in the process of crafting a helmet you shouldn’t be able to also -start crafting a shield at the same time, or if you just did a huge power-swing with your weapon you -should not be able to do it again immediately.

-

The simplest way of implementing blocking is to use the technique covered in the Command -Cooldown tutorial. In that tutorial we implemented cooldowns by having the -Command store the current time. Next time the Command was called, we compared the current time to -the stored time to determine if enough time had passed for a renewed use. This is a very -efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player -when enough time has passed unless they keep trying.

-

Here is an example where we will use utils.delay to tell the player when the cooldown has passed:

-
from evennia import utils, default_cmds
-    
-class CmdBigSwing(default_cmds.MuxCommand):
-    """
-    swing your weapon in a big way
-
-    Usage:
-      swing <target>
-    
-    Makes a mighty swing. Doing so will make you vulnerable
-    to counter-attacks before you can recover.
-    """
-    key = "bigswing"
-    locks = "cmd:all()"
-    
-    def func(self):
-        "Makes the swing"
-
-        if self.caller.ndb.off_balance:
-            # we are still off-balance.
-            self.caller.msg("You are off balance and need time to recover!")
-            return
-      
-        # [attack/hit code goes here ...]
-        self.caller.msg("You swing big! You are off balance now.")
-
-        # set the off-balance flag
-        self.caller.ndb.off_balance = True
-            
-        # wait 8 seconds before we can recover. During this time
-        # we won't be able to swing again due to the check at the top.
-        utils.delay(8, self.recover)
-    
-    def recover(self):
-        "This will be called after 8 secs"
-        del self.caller.ndb.off_balance
-        self.caller.msg("You regain your balance.")
-
-
-

Note how, after the cooldown, the user will get a message telling them they are now ready for -another swing.

-

By storing the off_balance flag on the character (rather than on, say, the Command instance -itself) it can be accessed by other Commands too. Other attacks may also not work when you are off -balance. You could also have an enemy Command check your off_balance status to gain bonuses, to -take another example.

-
-
-

Abortable commands

-

One can imagine that you will want to abort a long-running command before it has a time to finish. -If you are in the middle of crafting your armor you will probably want to stop doing that when a -monster enters your smithy.

-

You can implement this in the same way as you do the “blocking” command above, just in reverse. -Below is an example of a crafting command that can be aborted by starting a fight:

-
from evennia import utils, default_cmds
-    
-class CmdCraftArmour(default_cmds.MuxCommand):
-    """
-    Craft armour
-    
-    Usage:
-       craft <name of armour>
-    
-    This will craft a suit of armour, assuming you
-    have all the components and tools. Doing some
-    other action (such as attacking someone) will
-    abort the crafting process.
-    """
-    key = "craft"
-    locks = "cmd:all()"
-    
-    def func(self):
-        "starts crafting"
-
-        if self.caller.ndb.is_crafting:
-            self.caller.msg("You are already crafting!")
-            return
-        if self._is_fighting():
-            self.caller.msg("You can't start to craft "
-                            "in the middle of a fight!")
-            return
-            
-        # [Crafting code, checking of components, skills etc]
-
-        # Start crafting
-        self.caller.ndb.is_crafting = True
-        self.caller.msg("You start crafting ...")
-        utils.delay(60, self.step1)
-    
-    def _is_fighting(self):
-        "checks if we are in a fight."
-        if self.caller.ndb.is_fighting:
-            del self.caller.ndb.is_crafting
-            return True
-      
-    def step1(self):
-        "first step of armour construction"
-        if self._is_fighting():
-            return
-        self.msg("You create the first part of the armour.")
-        utils.delay(60, callback=self.step2)
-
-    def step2(self):
-        "second step of armour construction"
-        if self._is_fighting():
-            return
-        self.msg("You create the second part of the armour.")
-        utils.delay(60, step3)
-
-    def step3(self):
-        "last step of armour construction"
-        if self._is_fighting():
-            return
-    
-        # [code for creating the armour object etc]
-
-        del self.caller.ndb.is_crafting
-        self.msg("You finalize your armour.")
-    
-    
-# example of a command that aborts crafting
-    
-class CmdAttack(default_cmds.MuxCommand):
-    """
-    attack someone
-    
-    Usage:
-        attack <target>
-    
-    Try to cause harm to someone. This will abort
-    eventual crafting you may be currently doing.
-    """
-    key = "attack"
-    aliases = ["hit", "stab"]
-    locks = "cmd:all()"
-    
-    def func(self):
-        "Implements the command"
-
-        self.caller.ndb.is_fighting = True
-    
-        # [...]
-
-
-

The above code creates a delayed crafting command that will gradually create the armour. If the -attack command is issued during this process it will set a flag that causes the crafting to be -quietly canceled next time it tries to update.

-
-
-

Persistent delays

-

In the latter examples above we used .ndb storage. This is fast and easy but it will reset all -cooldowns/blocks/crafting etc if you reload the server. If you don’t want that you can replace -.ndb with .db. But even this won’t help because the yield keyword is not persisent and nor is -the use of delay shown above. To resolve this you can use delay with the persistent=True -keyword. But wait! Making something persistent will add some extra complications, because now you -must make sure Evennia can properly store things to the database.

-

Here is the original echo-command reworked to function with persistence:

-
from evennia import default_cmds, utils
-    
-# this is now in the outermost scope and takes two args!
-def echo(caller, args):
-    "Called after 10 seconds."
-    shout = args
-    string = "You hear an echo: %s ... %s ... %s"
-    string = string % (shout.upper(), shout.capitalize(), shout.lower())
-    caller.msg(string)
-
-class CmdEcho(default_cmds.MuxCommand):
-    """
-    wait for an echo
-    
-    Usage:
-      echo <string>
-    
-    Calls and waits for an echo
-    """
-    key = "echo"
-    locks = "cmd:all()"
-    
-    def func(self):
-        """
-         This is called at the initial shout.
-        """
-        self.caller.msg("You shout '%s' and wait for an echo ..." % self.args)
-        # this waits non-blocking for 10 seconds, then calls echo(self.caller, self.args)
-        utils.delay(10, echo, self.caller, self.args, persistent=True) # changes!
-    
-
-
-

Above you notice two changes:

-
    -
  • The callback (echo) was moved out of the class and became its own stand-alone function in the -outermost scope of the module. It also now takes caller and args as arguments (it doesn’t have -access to them directly since this is now a stand-alone function).

  • -
  • utils.delay specifies the echo function (not self.echo - it’s no longer a method!) and sends -self.caller and self.args as arguments for it to use. We also set persistent=True.

  • -
-

The reason for this change is because Evennia needs to pickle the callback into storage and it -cannot do this correctly when the method sits on the command class. Now this behave the same as the -first version except if you reload (or even shut down) the server mid-delay it will still fire the -callback when the server comes back up (it will resume the countdown and ignore the downtime).

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Command-Prompt.html b/docs/0.9.5/Command-Prompt.html deleted file mode 100644 index a80d66812b..0000000000 --- a/docs/0.9.5/Command-Prompt.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - Command Prompt — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Command Prompt

-

A prompt is quite common in MUDs. The prompt display useful details about your character that you -are likely to want to keep tabs on at all times, such as health, magical power etc. It might also -show things like in-game time, weather and so on. Many modern MUD clients (including Evennia’s own -webclient) allows for identifying the prompt and have it appear in a correct location (usually just -above the input line). Usually it will remain like that until it is explicitly updated.

-
-

Sending a prompt

-

A prompt is sent using the prompt keyword to the msg() method on objects. The prompt will be -sent without any line breaks.

-
    self.msg(prompt="HP: 5, MP: 2, SP: 8")
-
-
-

You can combine the sending of normal text with the sending (updating of the prompt):

-
    self.msg("This is a text", prompt="This is a prompt")
-
-
-

You can update the prompt on demand, this is normally done using OOB-tracking of the relevant -Attributes (like the character’s health). You could also make sure that attacking commands update -the prompt when they cause a change in health, for example.

-

Here is a simple example of the prompt sent/updated from a command class:

-
    from evennia import Command
-
-    class CmdDiagnose(Command):
-        """
-        see how hurt your are
-
-        Usage:
-          diagnose [target]
-
-        This will give an estimate of the target's health. Also
-        the target's prompt will be updated.
-        """
-        key = "diagnose"
-        
-        def func(self):
-            if not self.args:
-                target = self.caller
-            else:
-                target = self.search(self.args)
-                if not target:
-                    return
-            # try to get health, mana and stamina
-            hp = target.db.hp
-            mp = target.db.mp
-            sp = target.db.sp
-
-            if None in (hp, mp, sp):
-                # Attributes not defined
-                self.caller.msg("Not a valid target!")
-                return
-             
-            text = "You diagnose %s as having " \
-                   "%i health, %i mana and %i stamina." \
-                   % (hp, mp, sp)
-            prompt = "%i HP, %i MP, %i SP" % (hp, mp, sp)
-            self.caller.msg(text, prompt=prompt)
-
-
-
-
-

A prompt sent with every command

-

The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a -special flag). Most MUD telnet clients will understand and allow users to catch this and keep the -prompt in place until it updates. So in principle you’d not need to update the prompt every -command.

-

However, with a varying user base it can be unclear which clients are used and which skill level the -users have. So sending a prompt with every command is a safe catch-all. You don’t need to manually -go in and edit every command you have though. Instead you edit the base command class for your -custom commands (like MuxCommand in your mygame/commands/command.py folder) and overload the -at_post_cmd() hook. This hook is always called after the main func() method of the Command.

-
from evennia import default_cmds
-
-class MuxCommand(default_cmds.MuxCommand):
-    # ...
-    def at_post_cmd(self):
-        "called after self.func()."
-        caller = self.caller
-        prompt = "%i HP, %i MP, %i SP" % (caller.db.hp,
-                                          caller.db.mp,
-                                          caller.db.sp)
-        caller.msg(prompt=prompt)
-
-
-
-
-

Modifying default commands

-

If you want to add something small like this to Evennia’s default commands without modifying them -directly the easiest way is to just wrap those with a multiple inheritance to your own base class:

-
# in (for example) mygame/commands/mycommands.py
-
-from evennia import default_cmds
-# our custom MuxCommand with at_post_cmd hook
-from commands.command import MuxCommand
-
-# overloading the look command
-class CmdLook(default_cmds.CmdLook, MuxCommand):
-    pass
-
-
-

The result of this is that the hooks from your custom MuxCommand will be mixed into the default -CmdLook through multiple inheritance. Next you just add this to your default command set:

-
# in mygame/commands/default_cmdsets.py
-
-from evennia import default_cmds
-from commands import mycommands
-
-class CharacterCmdSet(default_cmds.CharacterCmdSet):
-    # ...
-    def at_cmdset_creation(self):
-        # ...
-        self.add(mycommands.CmdLook())
-
-
-

This will automatically replace the default look command in your game with your own version.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Command-Sets.html b/docs/0.9.5/Command-Sets.html deleted file mode 100644 index 309ecf3a2a..0000000000 --- a/docs/0.9.5/Command-Sets.html +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - - - - Command Sets — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Command Sets

-

Command Sets are intimately linked with Commands and you should be familiar with -Commands before reading this page. The two pages were split for ease of reading.

-

A Command Set (often referred to as a CmdSet or cmdset) is the basic unit for storing one or more -Commands. A given Command can go into any number of different command sets. Storing Command -classes in a command set is the way to make commands available to use in your game.

-

When storing a CmdSet on an object, you will make the commands in that command set available to the -object. An example is the default command set stored on new Characters. This command set contains -all the useful commands, from look and inventory to @dig and @reload -(permissions then limit which players may use them, but that’s a separate -topic).

-

When an account enters a command, cmdsets from the Account, Character, its location, and elsewhere -are pulled together into a merge stack. This stack is merged together in a specific order to -create a single “merged” cmdset, representing the pool of commands available at that very moment.

-

An example would be a Window object that has a cmdset with two commands in it: look through window and open window. The command set would be visible to players in the room with the window, -allowing them to use those commands only there. You could imagine all sorts of clever uses of this, -like a Television object which had multiple commands for looking at it, switching channels and so -on. The tutorial world included with Evennia showcases a dark room that replaces certain critical -commands with its own versions because the Character cannot see.

-

If you want a quick start into defining your first commands and using them with command sets, you -can head over to the Adding Command Tutorial which steps through things -without the explanations.

-
-

Defining Command Sets

-

A CmdSet is, as most things in Evennia, defined as a Python class inheriting from the correct parent -(evennia.CmdSet, which is a shortcut to evennia.commands.cmdset.CmdSet). The CmdSet class only -needs to define one method, called at_cmdset_creation(). All other class parameters are optional, -but are used for more advanced set manipulation and coding (see the [merge rules](Command- -Sets#merge-rules) section).

-
# file mygame/commands/mycmdset.py
-
-from evennia import CmdSet
-
-# this is a theoretical custom module with commands we
-# created previously: mygame/commands/mycommands.py
-from commands import mycommands
-
-class MyCmdSet(CmdSet):
-    def at_cmdset_creation(self):
-        """
-        The only thing this method should need
-        to do is to add commands to the set.
-        """
-        self.add(mycommands.MyCommand1())
-        self.add(mycommands.MyCommand2())
-        self.add(mycommands.MyCommand3())
-
-
-

The CmdSet’s add() method can also take another CmdSet as input. In this case all the commands -from that CmdSet will be appended to this one as if you added them line by line:

-
    def at_cmdset_creation():
-        ...
-        self.add(AdditionalCmdSet) # adds all command from this set
-        ...
-
-
-

If you added your command to an existing cmdset (like to the default cmdset), that set is already -loaded into memory. You need to make the server aware of the code changes:

-
@reload
-
-
-

You should now be able to use the command.

-

If you created a new, fresh cmdset, this must be added to an object in order to make the commands -within available. A simple way to temporarily test a cmdset on yourself is use the @py command to -execute a python snippet:

-
@py self.cmdset.add('commands.mycmdset.MyCmdSet')
-
-
-

This will stay with you until you @reset or @shutdown the server, or you run

-
@py self.cmdset.delete('commands.mycmdset.MyCmdSet')
-
-
-

In the example above, a specific Cmdset class is removed. Calling delete without arguments will -remove the latest added cmdset.

-
-

Note: Command sets added using cmdset.add are, by default, not persistent in the database.

-
-

If you want the cmdset to survive a reload, you can do:

-
@py self.cmdset.add(commands.mycmdset.MyCmdSet, permanent=True)
-
-
-

Or you could add the cmdset as the default cmdset:

-
@py self.cmdset.add_default(commands.mycmdset.MyCmdSet)
-
-
-

An object can only have one “default” cmdset (but can also have none). This is meant as a safe fall- -back even if all other cmdsets fail or are removed. It is always persistent and will not be affected -by cmdset.delete(). To remove a default cmdset you must explicitly call cmdset.remove_default().

-

Command sets are often added to an object in its at_object_creation method. For more examples of -adding commands, read the Step by step tutorial. Generally you can -customize which command sets are added to your objects by using self.cmdset.add() or -self.cmdset.add_default().

-
-

Important: Commands are identified uniquely by key or alias (see Commands). If any -overlap exists, two commands are considered identical. Adding a Command to a command set that -already has an identical command will replace the previous command. This is very important. You -must take this behavior into account when attempting to overload any default Evennia commands with -your own. Otherwise, you may accidentally “hide” your own command in your command set when adding a -new one that has a matching alias.

-
-
-

Properties on Command Sets

-

There are several extra flags that you can set on CmdSets in order to modify how they work. All are -optional and will be set to defaults otherwise. Since many of these relate to merging cmdsets, -you might want to read the [Adding and Merging Command Sets](./Command-Sets.md#adding-and-merging- -command-sets) section for some of these to make sense.

-
    -
  • key (string) - an identifier for the cmdset. This is optional, but should be unique. It is used -for display in lists, but also to identify special merging behaviours using the key_mergetype -dictionary below.

  • -
  • mergetype (string) - allows for one of the following string values: “Union”, “Intersect”, -“Replace”, or “Remove”.

  • -
  • priority (int) - This defines the merge order of the merge stack - cmdsets will merge in rising -order of priority with the highest priority set merging last. During a merger, the commands from the -set with the higher priority will have precedence (just what happens depends on the merge -type). If priority is identical, the order in the -merge stack determines preference. The priority value must be greater or equal to -100. Most in- -game sets should usually have priorities between 0 and 100. Evennia default sets have priorities -as follows (these can be changed if you want a different distribution):

    -
      -
    • EmptySet: -101 (should be lower than all other sets)

    • -
    • SessionCmdSet: -20

    • -
    • AccountCmdSet: -10

    • -
    • CharacterCmdSet: 0

    • -
    • ExitCmdSet: 101 (generally should always be available)

    • -
    • ChannelCmdSet: 101 (should usually always be available) - since exits never accept -arguments, there is no collision between exits named the same as a channel even though the commands -“collide”.

    • -
    -
  • -
  • key_mergetype (dict) - a dict of key:mergetype pairs. This allows this cmdset to merge -differently with certain named cmdsets. If the cmdset to merge with has a key matching an entry in -key_mergetype, it will not be merged according to the setting in mergetype but according to the -mode in this dict. Please note that this is more complex than it may seem due to the merge -order of command sets. Please review that section -before using key_mergetype.

  • -
  • duplicates (bool/None default None) - this determines what happens when merging same-priority -cmdsets containing same-key commands together. Thedupicate option will only apply when merging -the cmdset with this option onto one other cmdset with the same priority. The resulting cmdset will -not retain this duplicate setting.

    -
      -
    • None (default): No duplicates are allowed and the cmdset being merged “onto” the old one -will take precedence. The result will be unique commands. However, the system will assume this -value to be True for cmdsets on Objects, to avoid dangerous clashes. This is usually the safe bet.

    • -
    • False: Like None except the system will not auto-assume any value for cmdsets defined on -Objects.

    • -
    • True: Same-named, same-prio commands will merge into the same cmdset. This will lead to a -multimatch error (the user will get a list of possibilities in order to specify which command they -meant). This is is useful e.g. for on-object cmdsets (example: There is a red button and a green button in the room. Both have a press button command, in cmdsets with the same priority. This -flag makes sure that just writing press button will force the Player to define just which object’s -command was intended).

    • -
    -
  • -
  • no_objs this is a flag for the cmdhandler that builds the set of commands available at every -moment. It tells the handler not to include cmdsets from objects around the account (nor from rooms -or inventory) when building the merged set. Exit commands will still be included. This option can -have three values:

    -
      -
    • None (default): Passthrough of any value set explicitly earlier in the merge stack. If never -set explicitly, this acts as False.

    • -
    • True/False: Explicitly turn on/off. If two sets with explicit no_objs are merged, -priority determines what is used.

    • -
    -
  • -
  • no_exits - this is a flag for the cmdhandler that builds the set of commands available at every -moment. It tells the handler not to include cmdsets from exits. This flag can have three values:

    -
      -
    • None (default): Passthrough of any value set explicitly earlier in the merge stack. If -never set explicitly, this acts as False.

    • -
    • True/False: Explicitly turn on/off. If two sets with explicit no_exits are merged, -priority determines what is used.

    • -
    -
  • -
  • no_channels (bool) - this is a flag for the cmdhandler that builds the set of commands available -at every moment. It tells the handler not to include cmdsets from available in-game channels. This -flag can have three values:

    -
      -
    • None (default): Passthrough of any value set explicitly earlier in the merge stack. If -never set explicitly, this acts as False.

    • -
    • True/False: Explicitly turn on/off. If two sets with explicit no_channels are merged, -priority determines what is used.

    • -
    -
  • -
-
-
-
-

Command Sets Searched

-

When a user issues a command, it is matched against the [merged](./Command-Sets.md#adding-and-merging- -command-sets) command sets available to the player at the moment. Which those are may change at any -time (such as when the player walks into the room with the Window object described earlier).

-

The currently valid command sets are collected from the following sources:

-
    -
  • The cmdsets stored on the currently active Session. Default is the empty -SessionCmdSet with merge priority -20.

  • -
  • The cmdsets defined on the Account. Default is the AccountCmdSet with merge priority --10.

  • -
  • All cmdsets on the Character/Object (assuming the Account is currently puppeting such a -Character/Object). Merge priority 0.

  • -
  • The cmdsets of all objects carried by the puppeted Character (checks the call lock). Will not be -included if no_objs option is active in the merge stack.

  • -
  • The cmdsets of the Character’s current location (checks the call lock). Will not be included if -no_objs option is active in the merge stack.

  • -
  • The cmdsets of objects in the current location (checks the call lock). Will not be included if -no_objs option is active in the merge stack.

  • -
  • The cmdsets of Exits in the location. Merge priority +101. Will not be included if no_exits -or no_objs option is active in the merge stack.

  • -
  • The channel cmdset containing commands for posting to all channels the account -or character is currently connected to. Merge priority +101. Will not be included if no_channels -option is active in the merge stack.

  • -
-

Note that an object does not have to share its commands with its surroundings. A Character’s -cmdsets should not be shared for example, or all other Characters would get multi-match errors just -by being in the same room. The ability of an object to share its cmdsets is managed by its call -lock. For example, Character objects defaults to call:false() so that any -cmdsets on them can only be accessed by themselves, not by other objects around them. Another -example might be to lock an object with call:inside() to only make their commands available to -objects inside them, or cmd:holds() to make their commands available only if they are held.

-
-
-

Adding and Merging Command Sets

-

Note: This is an advanced topic. It’s very useful to know about, but you might want to skip it if -this is your first time learning about commands.

-

CmdSets have the special ability that they can be merged together into new sets. Which of the -ingoing commands end up in the merged set is defined by the merge rule and the relative -priorities of the two sets. Removing the latest added set will restore things back to the way it -was before the addition.

-

CmdSets are non-destructively stored in a stack inside the cmdset handler on the object. This stack -is parsed to create the “combined” cmdset active at the moment. CmdSets from other sources are also -included in the merger such as those on objects in the same room (like buttons to press) or those -introduced by state changes (such as when entering a menu). The cmdsets are all ordered after -priority and then merged together in reverse order. That is, the higher priority will be merged -“onto” lower-prio ones. By defining a cmdset with a merge-priority between that of two other sets, -you will make sure it will be merged in between them. -The very first cmdset in this stack is called the Default cmdset and is protected from accidental -deletion. Running obj.cmdset.delete() will never delete the default set. Instead one should add -new cmdsets on top of the default to “hide” it, as described below. Use the special -obj.cmdset.delete_default() only if you really know what you are doing.

-

CmdSet merging is an advanced feature useful for implementing powerful game effects. Imagine for -example a player entering a dark room. You don’t want the player to be able to find everything in -the room at a glance - maybe you even want them to have a hard time to find stuff in their backpack! -You can then define a different CmdSet with commands that override the normal ones. While they are -in the dark room, maybe the look and inv commands now just tell the player they cannot see -anything! Another example would be to offer special combat commands only when the player is in -combat. Or when being on a boat. Or when having taken the super power-up. All this can be done on -the fly by merging command sets.

-
-

Merge Rules

-

Basic rule is that command sets are merged in reverse priority order. That is, lower-prio sets are -merged first and higher prio sets are merged “on top” of them. Think of it like a layered cake with -the highest priority on top.

-

To further understand how sets merge, we need to define some examples. Let’s call the first command -set A and the second B. We assume B is the command set already active on our object and -we will merge A onto B. In code terms this would be done by object.cdmset.add(A). -Remember, B is already active on object from before.

-

We let the A set have higher priority than B. A priority is simply an integer number. As -seen in the list above, Evennia’s default cmdsets have priorities in the range -101 to 120. You -are usually safe to use a priority of 0 or 1 for most game effects.

-

In our examples, both sets contain a number of commands which we’ll identify by numbers, like A1, A2 for set A and B1, B2, B3, B4 for B. So for that example both sets contain commands -with the same keys (or aliases) “1” and “2” (this could for example be “look” and “get” in the real -game), whereas commands 3 and 4 are unique to B. To describe a merge between these sets, we -would write A1,A2 + B1,B2,B3,B4 = ? where ? is a list of commands that depend on which merge -type A has, and which relative priorities the two sets have. By convention, we read this -statement as “New command set A is merged onto the old command set B to form ?”.

-

Below are the available merge types and how they work. Names are partly borrowed from Set -theory.

-
    -
  • Union (default) - The two cmdsets are merged so that as many commands as possible from each -cmdset ends up in the merged cmdset. Same-key commands are merged by priority.

    -
       # Union
    -   A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4
    -
    -
    -
  • -
  • Intersect - Only commands found in both cmdsets (i.e. which have the same keys) end up in -the merged cmdset, with the higher-priority cmdset replacing the lower one’s commands.

    -
       # Intersect
    -   A1,A3,A5 + B1,B2,B4,B5 = A1,A5
    -
    -
    -
  • -
  • Replace - The commands of the higher-prio cmdset completely replaces the lower-priority -cmdset’s commands, regardless of if same-key commands exist or not.

    -
       # Replace
    -   A1,A3 + B1,B2,B4,B5 = A1,A3
    -
    -
    -
  • -
  • Remove - The high-priority command sets removes same-key commands from the lower-priority -cmdset. They are not replaced with anything, so this is a sort of filter that prunes the low-prio -set using the high-prio one as a template.

    -
       # Remove
    -   A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5
    -
    -
    -
  • -
-

Besides priority and mergetype, a command-set also takes a few other variables to control how -they merge:

-
    -
  • duplicates (bool) - determines what happens when two sets of equal priority merge. Default is -that the new set in the merger (i.e. A above) automatically takes precedence. But if -duplicates is true, the result will be a merger with more than one of each name match. This will -usually lead to the player receiving a multiple-match error higher up the road, but can be good for -things like cmdsets on non-player objects in a room, to allow the system to warn that more than one -‘ball’ in the room has the same ‘kick’ command defined on it and offer a chance to select which -ball to kick … Allowing duplicates only makes sense for Union and Intersect, the setting is -ignored for the other mergetypes.

  • -
  • key_mergetypes (dict) - allows the cmdset to define a unique mergetype for particular cmdsets, -identified by their cmdset key. Format is {CmdSetkey:mergetype}. Example: -{'Myevilcmdset','Replace'} which would make sure for this set to always use ‘Replace’ on the -cmdset with the key Myevilcmdset only, no matter what the main mergetype is set to.

  • -
-
-

Warning: The key_mergetypes dictionary can only work on the cmdset we merge onto. When using -key_mergetypes it is thus important to consider the merge priorities - you must make sure that you -pick a priority between the cmdset you want to detect and the next higher one, if any. That is, if -we define a cmdset with a high priority and set it to affect a cmdset that is far down in the merge -stack, we would not “see” that set when it’s time for us to merge. Example: Merge stack is -A(prio=-10), B(prio=-5), C(prio=0), D(prio=5). We now merge a cmdset E(prio=10) onto this stack, -with a key_mergetype={"B":"Replace"}. But priorities dictate that we won’t be merged onto B, we -will be merged onto E (which is a merger of the lower-prio sets at this point). Since we are merging -onto E and not B, our key_mergetype directive won’t trigger. To make sure it works we must make -sure we merge onto B. Setting E’s priority to, say, -4 will make sure to merge it onto B and affect -it appropriately.

-
-

More advanced cmdset example:

-
from commands import mycommands
-
-class MyCmdSet(CmdSet):
-    
-    key = "MyCmdSet"
-    priority = 4
-    mergetype = "Replace"
-    key_mergetypes = {'MyOtherCmdSet':'Union'}
-    
-    def at_cmdset_creation(self):
-        """
-        The only thing this method should need
-        to do is to add commands to the set.
-        """
-        self.add(mycommands.MyCommand1())
-        self.add(mycommands.MyCommand2())
-        self.add(mycommands.MyCommand3())
-
-
-
-
-

Assorted Notes

-

It is very important to remember that two commands are compared both by their key properties -and by their aliases properties. If either keys or one of their aliases match, the two commands -are considered the same. So consider these two Commands:

-
    -
  • A Command with key “kick” and alias “fight”

  • -
  • A Command with key “punch” also with an alias “fight”

  • -
-

During the cmdset merging (which happens all the time since also things like channel commands and -exits are merged in), these two commands will be considered identical since they share alias. It -means only one of them will remain after the merger. Each will also be compared with all other -commands having any combination of the keys and/or aliases “kick”, “punch” or “fight”.

-

… So avoid duplicate aliases, it will only cause confusion.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Command-System.html b/docs/0.9.5/Command-System.html deleted file mode 100644 index d8650eb6bd..0000000000 --- a/docs/0.9.5/Command-System.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - Command System — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Command System

- -

See also:

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Commands.html b/docs/0.9.5/Commands.html deleted file mode 100644 index a18c8c25fc..0000000000 --- a/docs/0.9.5/Commands.html +++ /dev/null @@ -1,776 +0,0 @@ - - - - - - - - - Commands — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Commands

-

Commands are intimately linked to Command Sets and you need to read that page too to -be familiar with how the command system works. The two pages were split for easy reading.

-

The basic way for users to communicate with the game is through Commands. These can be commands -directly related to the game world such as look, get, drop and so on, or administrative -commands such as examine or @dig.

-

The default commands coming with Evennia are ‘MUX-like’ in that they use @ -for admin commands, support things like switches, syntax with the ‘=’ symbol etc, but there is -nothing that prevents you from implementing a completely different command scheme for your game. You -can find the default commands in evennia/commands/default. You should not edit these directly - -they will be updated by the Evennia team as new features are added. Rather you should look to them -for inspiration and inherit your own designs from them.

-

There are two components to having a command running - the Command class and the Command -Set (command sets were split into a separate wiki page for ease of reading).

-
    -
  1. A Command is a python class containing all the functioning code for what a command does - for -example, a get command would contain code for picking up objects.

  2. -
  3. A Command Set (often referred to as a CmdSet or cmdset) is like a container for one or more -Commands. A given Command can go into any number of different command sets. Only by putting the -command set on a character object you will make all the commands therein available to use by that -character. You can also store command sets on normal objects if you want users to be able to use the -object in various ways. Consider a “Tree” object with a cmdset defining the commands climb and -chop down. Or a “Clock” with a cmdset containing the single command check time.

  4. -
-

This page goes into full detail about how to use Commands. To fully use them you must also read the -page detailing Command Sets. There is also a step-by-step Adding Command -Tutorial that will get you started quickly without the extra explanations.

-
-

Defining Commands

-

All commands are implemented as normal Python classes inheriting from the base class Command -(evennia.Command). You will find that this base class is very “bare”. The default commands of -Evennia actually inherit from a child of Command called MuxCommand - this is the class that -knows all the mux-like syntax like /switches, splitting by “=” etc. Below we’ll avoid mux- -specifics and use the base Command class directly.

-
    # basic Command definition
-    from evennia import Command
-
-    class MyCmd(Command):
-       """
-       This is the help-text for the command
-       """
-       key = "mycommand"
-       def parse(self):
-           # parsing the command line here
-       def func(self):
-           # executing the command here
-
-
-

Here is a minimalistic command with no custom parsing:

-
    from evennia import Command
-
-    class CmdEcho(Command):
-        key = "echo"
-
-        def func(self):
-            # echo the caller's input back to the caller
-            self.caller.msg("Echo: {}".format(self.args)
-
-
-
-

You define a new command by assigning a few class-global properties on your inherited class and -overloading one or two hook functions. The full gritty mechanic behind how commands work are found -towards the end of this page; for now you only need to know that the command handler creates an -instance of this class and uses that instance whenever you use this command - it also dynamically -assigns the new command instance a few useful properties that you can assume to always be available.

-
-

Who is calling the command?

-

In Evennia there are three types of objects that may call the command. It is important to be aware -of this since this will also assign appropriate caller, session, sessid and account -properties on the command body at runtime. Most often the calling type is Session.

-
    -
  • A Session. This is by far the most common case when a user is entering a command in -their client.

    -
      -
    • caller - this is set to the puppeted Object if such an object exists. If no -puppet is found, caller is set equal to account. Only if an Account is not found either (such as -before being logged in) will this be set to the Session object itself.

    • -
    • session - a reference to the Session object itself.

    • -
    • sessid - sessid.id, a unique integer identifier of the session.

    • -
    • account - the Account object connected to this Session. None if not logged in.

    • -
    -
  • -
  • An Account. This only happens if account.execute_cmd() was used. No Session -information can be obtained in this case.

    -
      -
    • caller - this is set to the puppeted Object if such an object can be determined (without -Session info this can only be determined in MULTISESSION_MODE=0 or 1). If no puppet is found, -this is equal to account.

    • -
    • session - None*

    • -
    • sessid - None*

    • -
    • account - Set to the Account object.

    • -
    -
  • -
  • An Object. This only happens if object.execute_cmd() was used (for example by an -NPC).

    -
      -
    • caller - This is set to the calling Object in question.

    • -
    • session - None*

    • -
    • sessid - None*

    • -
    • account - None

    • -
    -
  • -
-
-

*): There is a way to make the Session available also inside tests run directly on Accounts and -Objects, and that is to pass it to execute_cmd like so: account.execute_cmd("...", session=<Session>). Doing so will make the .session and .sessid properties available in the -command.

-
-
-
-

Properties assigned to the command instance at run-time

-

Let’s say account Bob with a character BigGuy enters the command look at sword. After the -system having successfully identified this as the “look” command and determined that BigGuy really -has access to a command named look, it chugs the look command class out of storage and either -loads an existing Command instance from cache or creates one. After some more checks it then assigns -it the following properties:

-
    -
  • caller - The character BigGuy, in this example. This is a reference to the object executing the -command. The value of this depends on what type of object is calling the command; see the previous -section.

  • -
  • session - the Session Bob uses to connect to the game and control BigGuy (see also -previous section).

  • -
  • sessid - the unique id of self.session, for quick lookup.

  • -
  • account - the Account Bob (see previous section).

  • -
  • cmdstring - the matched key for the command. This would be look in our example.

  • -
  • args - this is the rest of the string, except the command name. So if the string entered was -look at sword, args would be ” at sword”. Note the space kept - Evennia would correctly -interpret lookat sword too. This is useful for things like /switches that should not use space. -In the MuxCommand class used for default commands, this space is stripped. Also see the -arg_regex property if you want to enforce a space to make lookat sword give a command-not-found -error.

  • -
  • obj - the game Object on which this command is defined. This need not be the caller, -but since look is a common (default) command, this is probably defined directly on BigGuy - so -obj will point to BigGuy. Otherwise obj could be an Account or any interactive object with -commands defined on it, like in the example of the “check time” command defined on a “Clock” object.

  • -
  • cmdset - this is a reference to the merged CmdSet (see below) from which this command was -matched. This variable is rarely used, it’s main use is for the [auto-help system](Help- -System#command-auto-help-system) (Advanced note: the merged cmdset need NOT be the same as -BigGuy.cmdset. The merged set can be a combination of the cmdsets from other objects in the room, -for example).

  • -
  • raw_string - this is the raw input coming from the user, without stripping any surrounding -whitespace. The only thing that is stripped is the ending newline marker.

  • -
-
-

Other useful utility methods:

-
    -
  • .get_help(caller, cmdset) - Get the help entry for this command. By default the arguments are -not -used, but they could be used to implement alternate help-display systems.

  • -
  • .client_width() - Shortcut for getting the client’s screen-width. Note that not all clients will -truthfully report this value - that case the settings.DEFAULT_SCREEN_WIDTH will be returned.

  • -
  • .styled_table(*args, **kwargs) - This returns an [EvTable](module- -evennia.utils.evtable) styled based on the -session calling this command. The args/kwargs are the same as for EvTable, except styling defaults -are set.

  • -
  • .styled_header, _footer, separator - These will produce styled decorations for -display to the user. They are useful for creating listings and forms with colors adjustable per- -user.

  • -
-
-
-
-

Defining your own command classes

-

Beyond the properties Evennia always assigns to the command at run-time (listed above), your job is -to define the following class properties:

-
    -
  • key (string) - the identifier for the command, like look. This should (ideally) be unique. A -key can consist of more than one word, like “press button” or “pull left lever”. Note that both -key and aliases below determine the identity of a command. So two commands are considered if -either matches. This is important for merging cmdsets described below.

  • -
  • aliases (optional list) - a list of alternate names for the command (["glance", "see", "l"]). -Same name rules as for key applies.

  • -
  • locks (string) - a lock definition, usually on the form cmd:<lockfuncs>. Locks is a -rather big topic, so until you learn more about locks, stick to giving the lockstring "cmd:all()" -to make the command available to everyone (if you don’t provide a lock string, this will be assigned -for you).

  • -
  • help_category (optional string) - setting this helps to structure the auto-help into categories. -If none is set, this will be set to General.

  • -
  • save_for_next (optional boolean). This defaults to False. If True, a copy of this command -object (along with any changes you have done to it) will be stored by the system and can be accessed -by the next command by retrieving self.caller.ndb.last_cmd. The next run command will either clear -or replace the storage.

  • -
  • arg_regex (optional raw string): Used to force the parser to limit itself and tell it when the -command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is -done with a regular expression. See the arg_regex section for the details.

  • -
  • auto_help (optional boolean). Defaults to True. This allows for turning off the auto-help -system on a per-command basis. This could be useful if you -either want to write your help entries manually or hide the existence of a command from help’s -generated list.

  • -
  • is_exit (bool) - this marks the command as being used for an in-game exit. This is, by default, -set by all Exit objects and you should not need to set it manually unless you make your own Exit -system. It is used for optimization and allows the cmdhandler to easily disregard this command when -the cmdset has its no_exits flag set.

  • -
  • is_channel (bool)- this marks the command as being used for an in-game channel. This is, by -default, set by all Channel objects and you should not need to set it manually unless you make your -own Channel system. is used for optimization and allows the cmdhandler to easily disregard this -command when its cmdset has its no_channels flag set.

  • -
  • msg_all_sessions (bool): This affects the behavior of the Command.msg method. If unset -(default), calling self.msg(text) from the Command will always only send text to the Session that -actually triggered this Command. If set however, self.msg(text) will send to all Sessions relevant -to the object this Command sits on. Just which Sessions receives the text depends on the object and -the server’s MULTISESSION_MODE.

  • -
-

You should also implement at least two methods, parse() and func() (You could also implement -perm(), but that’s not needed unless you want to fundamentally change how access checks work).

-
    -
  • at_pre_cmd() is called very first on the command. If this function returns anything that -evaluates to True the command execution is aborted at this point.

  • -
  • parse() is intended to parse the arguments (self.args) of the function. You can do this in any -way you like, then store the result(s) in variable(s) on the command object itself (i.e. on self). -To take an example, the default mux-like system uses this method to detect “command switches” and -store them as a list in self.switches. Since the parsing is usually quite similar inside a command -scheme you should make parse() as generic as possible and then inherit from it rather than re- -implementing it over and over. In this way, the default MuxCommand class implements a parse() -for all child commands to use.

  • -
  • func() is called right after parse() and should make use of the pre-parsed input to actually -do whatever the command is supposed to do. This is the main body of the command. The return value -from this method will be returned from the execution as a Twisted Deferred.

  • -
  • at_post_cmd() is called after func() to handle eventual cleanup.

  • -
-

Finally, you should always make an informative doc -string (__doc__) at the top of your -class. This string is dynamically read by the Help System to create the help entry -for this command. You should decide on a way to format your help and stick to that.

-

Below is how you define a simple alternative “smile” command:

-
from evennia import Command
-
-class CmdSmile(Command):
-    """
-    A smile command
-
-    Usage:
-      smile [at] [<someone>]
-      grin [at] [<someone>]
-
-    Smiles to someone in your vicinity or to the room
-    in general.
-
-    (This initial string (the __doc__ string)
-    is also used to auto-generate the help
-    for this command)
-    """
-
-    key = "smile"
-    aliases = ["smile at", "grin", "grin at"]
-    locks = "cmd:all()"
-    help_category = "General"
-
-    def parse(self):
-        "Very trivial parser"
-        self.target = self.args.strip()
-
-    def func(self):
-        "This actually does things"
-        caller = self.caller
-
-        if not self.target or self.target == "here":
-            string = f"{caller.key} smiles"
-        else:
-            target = caller.search(self.target)
-            if not target:
-                return
-            string = f"{caller.key} smiles at {target.key}"
-
-        caller.location.msg_contents(string)
-
-
-
-

The power of having commands as classes and to separate parse() and func() -lies in the ability to inherit functionality without having to parse every -command individually. For example, as mentioned the default commands all -inherit from MuxCommand. MuxCommand implements its own version of parse() -that understands all the specifics of MUX-like commands. Almost none of the -default commands thus need to implement parse() at all, but can assume the -incoming string is already split up and parsed in suitable ways by its parent.

-

Before you can actually use the command in your game, you must now store it -within a command set. See the Command Sets page.

-
-
-

On arg_regex

-

The command parser is very general and does not require a space to end your command name. This means -that the alias : to emote can be used like :smiles without modification. It also means -getstone will get you the stone (unless there is a command specifically named getstone, then -that will be used). If you want to tell the parser to require a certain separator between the -command name and its arguments (so that get stone works but getstone gives you a ‘command not -found’ error) you can do so with the arg_regex property.

-

The arg_regex is a raw regular expression string. The -regex will be compiled by the system at runtime. This allows you to customize how the part -immediately following the command name (or alias) must look in order for the parser to match for -this command. Some examples:

-
    -
  • commandname argument (arg_regex = r"\s.+"): This forces the parser to require the command name -to be followed by one or more spaces. Whatever is entered after the space will be treated as an -argument. However, if you’d forget the space (like a command having no arguments), this would not -match commandname.

  • -
  • commandname or commandname argument (arg_regex = r"\s.+|$"): This makes both look and -look me work but lookme will not.

  • -
  • commandname/switches arguments (arg_regex = r"(?:^(?:\s+|\/).*$)|^$". If you are using -Evennia’s MuxCommand Command parent, you may wish to use this since it will allow /switches to -work as well as having or not having a space.

  • -
-

The arg_regex allows you to customize the behavior of your commands. You can put it in the parent -class of your command to customize all children of your Commands. However, you can also change the -base default behavior for all Commands by modifying settings.COMMAND_DEFAULT_ARG_REGEX.

-
-
-
-

Exiting a command

-

Normally you just use return in one of your Command class’ hook methods to exit that method. That -will however still fire the other hook methods of the Command in sequence. That’s usually what you -want but sometimes it may be useful to just abort the command, for example if you find some -unacceptable input in your parse method. To exit the command this way you can raise -evennia.InterruptCommand:

-
from evennia import InterruptCommand
-
-class MyCommand(Command):
-
-   # ...
-
-   def parse(self):
-       # ...
-       # if this fires, `func()` and `at_post_cmd` will not
-       # be called at all
-       raise InterruptCommand()
-
-
-
-
-
-

Pauses in commands

-

Sometimes you want to pause the execution of your command for a little while before continuing - -maybe you want to simulate a heavy swing taking some time to finish, maybe you want the echo of your -voice to return to you with an ever-longer delay. Since Evennia is running asynchronously, you -cannot use time.sleep() in your commands (or anywhere, really). If you do, the entire game will -be frozen for everyone! So don’t do that. Fortunately, Evennia offers a really quick syntax for -making pauses in commands.

-

In your func() method, you can use the yield keyword. This is a Python keyword that will freeze -the current execution of your command and wait for more before processing.

-
-

Note that you cannot just drop yield into any code and expect it to pause. Evennia will only -pause for you if you yield inside the Command’s func() method. Don’t expect it to work anywhere -else.

-
-

Here’s an example of a command using a small pause of five seconds between messages:

-
from evennia import Command
-
-class CmdWait(Command):
-    """
-    A dummy command to show how to wait
-
-    Usage:
-      wait
-
-    """
-
-    key = "wait"
-    locks = "cmd:all()"
-    help_category = "General"
-
-    def func(self):
-        """Command execution."""
-        self.msg("Starting to wait ...")
-        yield 5
-        self.msg("... This shows after 5 seconds. Waiting ...")
-        yield 2
-        self.msg("... And now another 2 seconds have passed.")
-
-
-

The important line is the yield 5 and yield 2 lines. It will tell Evennia to pause execution -here and not continue until the number of seconds given has passed.

-

There are two things to remember when using yield in your Command’s func method:

-
    -
  1. The paused state produced by the yield is not saved anywhere. So if the server reloads in the -middle of your command pausing, it will not resume when the server comes back up - the remainder -of the command will never fire. So be careful that you are not freezing the character or account in -a way that will not be cleared on reload.

  2. -
  3. If you use yield you may not also use return <values> in your func method. You’ll get an -error explaining this. This is due to how Python generators work. You can however use a “naked” -return just fine. Usually there is no need for func to return a value, but if you ever do need -to mix yield with a final return value in the same func, look at twisted.internet.defer.returnV -alue.

  4. -
-
-
-

Asking for user input

-

The yield keyword can also be used to ask for user input. Again you can’t -use Python’s input in your command, for it would freeze Evennia for -everyone while waiting for that user to input their text. Inside a Command’s -func method, the following syntax can also be used:

-
answer = yield("Your question")
-
-
-

Here’s a very simple example:

-
class CmdConfirm(Command):
-
-    """
-    A dummy command to show confirmation.
-
-    Usage:
-        confirm
-
-    """
-
-    key = "confirm"
-
-    def func(self):
-        answer = yield("Are you sure you want to go on?")
-        if answer.strip().lower() in ("yes", "y"):
-            self.msg("Yes!")
-        else:
-            self.msg("No!")
-
-
-

This time, when the user enters the ‘confirm’ command, she will be asked if she wants to go on. -Entering ‘yes’ or “y” (regardless of case) will give the first reply, otherwise the second reply -will show.

-
-

Note again that the yield keyword does not store state. If the game reloads while waiting for -the user to answer, the user will have to start over. It is not a good idea to use yield for -important or complex choices, a persistent EvMenu might be more appropriate in this case.

-
-
-
-

System commands

-

Note: This is an advanced topic. Skip it if this is your first time learning about commands.

-

There are several command-situations that are exceptional in the eyes of the server. What happens if -the account enters an empty string? What if the ‘command’ given is infact the name of a channel the -user wants to send a message to? Or if there are multiple command possibilities?

-

Such ‘special cases’ are handled by what’s called system commands. A system command is defined -in the same way as other commands, except that their name (key) must be set to one reserved by the -engine (the names are defined at the top of evennia/commands/cmdhandler.py). You can find (unused) -implementations of the system commands in evennia/commands/default/system_commands.py. Since these -are not (by default) included in any CmdSet they are not actually used, they are just there for -show. When the special situation occurs, Evennia will look through all valid CmdSets for your -custom system command. Only after that will it resort to its own, hard-coded implementation.

-

Here are the exceptional situations that triggers system commands. You can find the command keys -they use as properties on evennia.syscmdkeys:

-
    -
  • No input (syscmdkeys.CMD_NOINPUT) - the account just pressed return without any input. Default -is to do nothing, but it can be useful to do something here for certain implementations such as line -editors that interpret non-commands as text input (an empty line in the editing buffer).

  • -
  • Command not found (syscmdkeys.CMD_NOMATCH) - No matching command was found. Default is to -display the “Huh?” error message.

  • -
  • Several matching commands where found (syscmdkeys.CMD_MULTIMATCH) - Default is to show a list of -matches.

  • -
  • User is not allowed to execute the command (syscmdkeys.CMD_NOPERM) - Default is to display the -“Huh?” error message.

  • -
  • Channel (syscmdkeys.CMD_CHANNEL) - This is a Channel name of a channel you are -subscribing to - Default is to relay the command’s argument to that channel. Such commands are -created by the Comm system on the fly depending on your subscriptions.

  • -
  • New session connection (syscmdkeys.CMD_LOGINSTART). This command name should be put in the -settings.CMDSET_UNLOGGEDIN. Whenever a new connection is established, this command is always -called on the server (default is to show the login screen).

  • -
-

Below is an example of redefining what happens when the account doesn’t provide any input (e.g. just -presses return). Of course the new system command must be added to a cmdset as well before it will -work.

-
    from evennia import syscmdkeys, Command
-
-    class MyNoInputCommand(Command):
-        "Usage: Just press return, I dare you"
-        key = syscmdkeys.CMD_NOINPUT
-        def func(self):
-            self.caller.msg("Don't just press return like that, talk to me!")
-
-
-
-
-

Dynamic Commands

-

Note: This is an advanced topic.

-

Normally Commands are created as fixed classes and used without modification. There are however -situations when the exact key, alias or other properties is not possible (or impractical) to pre- -code (Exits is an example of this).

-

To create a command with a dynamic call signature, first define the command body normally in a class -(set your key, aliases to default values), then use the following call (assuming the command -class you created is named MyCommand):

-
     cmd = MyCommand(key="newname",
-                     aliases=["test", "test2"],
-                     locks="cmd:all()",
-                     ...)
-
-
-

All keyword arguments you give to the Command constructor will be stored as a property on the -command object. This will overload existing properties defined on the parent class.

-

Normally you would define your class and only overload things like key and aliases at run-time. -But you could in principle also send method objects (like func) as keyword arguments in order to -make your command completely customized at run-time.

-
-
-

Exits

-

Note: This is an advanced topic.

-

Exits are examples of the use of a Dynamic Command.

-

The functionality of Exit objects in Evennia is not hard-coded in the engine. Instead -Exits are normal typeclassed objects that auto-create a CmdSet on -themselves when they load. This cmdset has a single dynamically created Command with the same -properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit, -this dynamic exit-command is triggered and (after access checks) moves the Character to the exit’s -destination. -Whereas you could customize the Exit object and its command to achieve completely different -behaviour, you will usually be fine just using the appropriate traverse_* hooks on the Exit -object. But if you are interested in really changing how things work under the hood, check out -evennia/objects/objects.py for how the Exit typeclass is set up.

-
-
-

Command instances are re-used

-

Note: This is an advanced topic that can be skipped when first learning about Commands.

-

A Command class sitting on an object is instantiated once and then re-used. So if you run a command -from object1 over and over you are in fact running the same command instance over and over (if you -run the same command but sitting on object2 however, it will be a different instance). This is -usually not something you’ll notice, since every time the Command-instance is used, all the relevant -properties on it will be overwritten. But armed with this knowledge you can implement some of the -more exotic command mechanism out there, like the command having a ‘memory’ of what you last entered -so that you can back-reference the previous arguments etc.

-
-

Note: On a server reload, all Commands are rebuilt and memory is flushed.

-
-

To show this in practice, consider this command:

-
class CmdTestID(Command):
-    key = "testid"
-
-    def func(self):
-
-        if not hasattr(self, "xval"):
-            self.xval = 0
-        self.xval += 1
-
-        self.caller.msg("Command memory ID: {} (xval={})".format(id(self), self.xval))
-
-
-
-

Adding this to the default character cmdset gives a result like this in-game:

-
> testid
-Command memory ID: 140313967648552 (xval=1)
-> testid
-Command memory ID: 140313967648552 (xval=2)
-> testid
-Command memory ID: 140313967648552 (xval=3)
-
-
-

Note how the in-memory address of the testid command never changes, but xval keeps ticking up.

-
-
-

Dynamically created commands

-

This is also an advanced topic.

-

Commands can also be created and added to a cmdset on the fly. Creating a class instance with a -keyword argument, will assign that keyword argument as a property on this paricular command:

-
class MyCmdSet(CmdSet):
-
-    def at_cmdset_creation(self):
-
-        self.add(MyCommand(myvar=1, foo="test")
-
-
-
-

This will start the MyCommand with myvar and foo set as properties (accessable as self.myvar -and self.foo). How they are used is up to the Command. Remember however the discussion from the -previous section - since the Command instance is re-used, those properties will remain on the -command as long as this cmdset and the object it sits is in memory (i.e. until the next reload). -Unless myvar and foo are somehow reset when the command runs, they can be modified and that -change will be remembered for subsequent uses of the command.

-
-
-

How commands actually work

-

Note: This is an advanced topic mainly of interest to server developers.

-

Any time the user sends text to Evennia, the server tries to figure out if the text entered -corresponds to a known command. This is how the command handler sequence looks for a logged-in user:

-
    -
  1. A user enters a string of text and presses enter.

  2. -
  3. The user’s Session determines the text is not some protocol-specific control sequence or OOB -command, but sends it on to the command handler.

  4. -
  5. Evennia’s command handler analyzes the Session and grabs eventual references to Account and -eventual puppeted Characters (these will be stored on the command object later). The caller -property is set appropriately.

  6. -
  7. If input is an empty string, resend command as CMD_NOINPUT. If no such command is found in -cmdset, ignore.

  8. -
  9. If command.key matches settings.IDLE_COMMAND, update timers but don’t do anything more.

  10. -
  11. The command handler gathers the CmdSets available to caller at this time:

    -
      -
    • The caller’s own currently active CmdSet.

    • -
    • CmdSets defined on the current account, if caller is a puppeted object.

    • -
    • CmdSets defined on the Session itself.

    • -
    • The active CmdSets of eventual objects in the same location (if any). This includes commands -on Exits.

    • -
    • Sets of dynamically created System commands representing available -Communications.

    • -
    -
  12. -
  13. All CmdSets of the same priority are merged together in groups. Grouping avoids order- -dependent issues of merging multiple same-prio sets onto lower ones.

  14. -
  15. All the grouped CmdSets are merged in reverse priority into one combined CmdSet according to -each set’s merge rules.

  16. -
  17. Evennia’s command parser takes the merged cmdset and matches each of its commands (using its -key and aliases) against the beginning of the string entered by caller. This produces a set of -candidates.

  18. -
  19. The cmd parser next rates the matches by how many characters they have and how many percent -matches the respective known command. Only if candidates cannot be separated will it return multiple -matches.

    -
      -
    • If multiple matches were returned, resend as CMD_MULTIMATCH. If no such command is found in -cmdset, return hard-coded list of matches.

    • -
    • If no match was found, resend as CMD_NOMATCH. If no such command is found in cmdset, give -hard-coded error message.

    • -
    -
  20. -
  21. If a single command was found by the parser, the correct command object is plucked out of -storage. This usually doesn’t mean a re-initialization.

  22. -
  23. It is checked that the caller actually has access to the command by validating the lockstring -of the command. If not, it is not considered as a suitable match and CMD_NOMATCH is triggered.

  24. -
  25. If the new command is tagged as a channel-command, resend as CMD_CHANNEL. If no such command -is found in cmdset, use hard-coded implementation.

  26. -
  27. Assign several useful variables to the command instance (see previous sections).

  28. -
  29. Call at_pre_command() on the command instance.

  30. -
  31. Call parse() on the command instance. This is fed the remainder of the string, after the name -of the command. It’s intended to pre-parse the string into a form useful for the func() method.

  32. -
  33. Call func() on the command instance. This is the functional body of the command, actually -doing useful things.

  34. -
  35. Call at_post_command() on the command instance.

  36. -
-
-
-

Assorted notes

-

The return value of Command.func() is a Twisted -deferred. -Evennia does not use this return value at all by default. If you do, you must -thus do so asynchronously, using callbacks.

-
     # in command class func()
-     def callback(ret, caller):
-        caller.msg("Returned is %s" % ret)
-     deferred = self.execute_command("longrunning")
-     deferred.addCallback(callback, self.caller)
-
-
-

This is probably not relevant to any but the most advanced/exotic designs (one might use it to -create a “nested” command structure for example).

-

The save_for_next class variable can be used to implement state-persistent commands. For example -it can make a command operate on “it”, where it is determined by what the previous command operated -on.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Communications.html b/docs/0.9.5/Communications.html deleted file mode 100644 index 13fa5ce666..0000000000 --- a/docs/0.9.5/Communications.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - - - - - Communications — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Communications

-

Apart from moving around in the game world and talking, players might need other forms of -communication. This is offered by Evennia’s Comm system. Stock evennia implements a ‘MUX-like’ -system of channels, but there is nothing stopping you from changing things to better suit your -taste.

-

Comms rely on two main database objects - Msg and Channel. There is also the TempMsg which -mimics the API of a Msg but has no connection to the database.

-
-

Msg

-

The Msg object is the basic unit of communication in Evennia. A message works a little like an -e-mail; it always has a sender (a Account) and one or more recipients. The recipients -may be either other Accounts, or a Channel (see below). You can mix recipients to send the message -to both Channels and Accounts if you like.

-

Once created, a Msg is normally not changed. It is peristently saved in the database. This allows -for comprehensive logging of communications. This could be useful for allowing senders/receivers to -have ‘mailboxes’ with the messages they want to keep.

-
-

Properties defined on Msg

-
    -
  • senders - this is a reference to one or many Account or Objects (normally -Characters) sending the message. This could also be an External Connection such as a message -coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be -mixed in any combination.

  • -
  • receivers - a list of target Accounts, Objects (usually Characters) or -Channels to send the message to. The types of receivers can be mixed in any combination.

  • -
  • header - this is a text field for storing a title or header for the message.

  • -
  • message - the actual text being sent.

  • -
  • date_sent - when message was sent (auto-created).

  • -
  • locks - a lock definition.

  • -
  • hide_from - this can optionally hold a list of objects, accounts or channels to hide this Msg -from. This relationship is stored in the database primarily for optimization reasons, allowing for -quickly post-filter out messages not intended for a given target. There is no in-game methods for -setting this, it’s intended to be done in code.

  • -
-

You create new messages in code using evennia.create_message (or -evennia.utils.create.create_message.)

-
-
-
-

TempMsg

-

evennia.comms.models also has TempMsg which mimics the API of Msg but is not connected to the -database. TempMsgs are used by Evennia for channel messages by default. They can be used for any -system expecting a Msg but when you don’t actually want to save anything.

-
-
-

Channels

-

Channels are Typeclassed entities, which mean they can be easily extended and their -functionality modified. To change which channel typeclass Evennia uses, change -settings.BASE_CHANNEL_TYPECLASS.

-

Channels act as generic distributors of messages. Think of them as “switch boards” redistributing -Msg or TempMsg objects. Internally they hold a list of “listening” objects and any Msg (or -TempMsg) sent to the channel will be distributed out to all channel listeners. Channels have -Locks to limit who may listen and/or send messages through them.

-

The sending of text to a channel is handled by a dynamically created Command that -always have the same name as the channel. This is created for each channel by the global -ChannelHandler. The Channel command is added to the Account’s cmdset and normal command locks are -used to determine which channels are possible to write to. When subscribing to a channel, you can -then just write the channel name and the text to send.

-

The default ChannelCommand (which can be customized by pointing settings.CHANNEL_COMMAND_CLASS to -your own command), implements a few convenient features:

-
    -
  • It only sends TempMsg objects. Instead of storing individual entries in the database it instead -dumps channel output a file log in server/logs/channel_<channelname>.log. This is mainly for -practical reasons - we find one rarely need to query individual Msg objects at a later date. Just -stupidly dumping the log to a file also means a lot less database overhead.

  • -
  • It adds a /history switch to view the 20 last messages in the channel. These are read from the -end of the log file. One can also supply a line number to start further back in the file (but always -20 entries at a time). It’s used like this:

    -
     > public/history
    - > public/history 35
    -
    -
    -
  • -
-

There are two default channels created in stock Evennia - MudInfo and Public. MudInfo -receives server-related messages meant for Admins whereas Public is open to everyone to chat on -(all new accounts are automatically joined to it when logging in, it is useful for asking -questions). The default channels are defined by the DEFAULT_CHANNELS list (see -evennia/settings_default.py for more details).

-

You create new channels with evennia.create_channel (or evennia.utils.create.create_channel).

-

In code, messages are sent to a channel using the msg or tempmsg methods of channels:

-
 channel.msg(msgobj, header=None, senders=None, persistent=True)
-
-
-

The argument msgobj can be either a string, a previously constructed Msg or a TempMsg - in the -latter cases all the following keywords are ignored since the message objects already contains all -this information. If msgobj is a string, the other keywords are used for creating a new Msg or -TempMsg on the fly, depending on if persistent is set or not. By default, a TempMsg is emitted -for channel communication (since the default ChannelCommand instead logs to a file).

-
    # assume we have a 'sender' object and a channel named 'mychan'
-
-    # manually sending a message to a channel
-    mychan.msg("Hello!", senders=[sender])
-
-
-
-

Properties defined on Channel

-
    -
  • key - main name for channel

  • -
  • aliases - alternative native names for channels

  • -
  • desc - optional description of channel (seen in listings)

  • -
  • keep_log (bool) - if the channel should store messages (default)

  • -
  • locks - A lock definition. Channels normally use the access_types send, control and -listen.

  • -
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Connection-Screen.html b/docs/0.9.5/Connection-Screen.html deleted file mode 100644 index ced33170ee..0000000000 --- a/docs/0.9.5/Connection-Screen.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - Connection Screen — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Connection Screen

-

When you first connect to your game you are greeted by Evennia’s default connection screen.

-
==============================================================
- Welcome to Evennia, version Beta-ra4d24e8a3cab+!
-
- If you have an existing account, connect to it by typing:
-      connect <username> <password>
- If you need to create an account, type (without the <>'s):
-      create <username> <password>
-
- If you have spaces in your username, enclose it in quotes.
- Enter help for more info. look will re-show this screen.
-==============================================================
-
-
-

Effective, but not very exciting. You will most likely want to change this to be more unique for -your game. This is simple:

-
    -
  1. Edit mygame/server/conf/connection_screens.py.

  2. -
  3. Reload Evennia.

  4. -
-

Evennia will look into this module and locate all globally defined strings in it. These strings -are used as the text in your connection screen and are shown to the user at startup. If more than -one such string/screen is defined in the module, a random screen will be picked from among those -available.

-
-

Commands available at the Connection Screen

-

You can also customize the Commands available to use while the connection screen is -shown (connect, create etc). These commands are a bit special since when the screen is running -the account is not yet logged in. A command is made available at the login screen by adding them to -UnloggedinCmdSet in mygame/commands/default_cmdset.py. See Commands and the -tutorial section on how to add new commands to a default command set.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Continuous-Integration.html b/docs/0.9.5/Continuous-Integration.html deleted file mode 100644 index 2db23242ec..0000000000 --- a/docs/0.9.5/Continuous-Integration.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - Continuous Integration — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Continuous Integration

-

One of the advantages of Evennia over traditional MUSH development systems is that Evennia is -capable of integrating into enterprise level integration environments and source control. Because of -this, it can also be the subject of automation for additional convenience, allowing a more -streamlined development environment.

-
-

What is Continuous Integration?

-

Continuous Integration (CI) is a development -practice that requires developers to integrate code into a shared repository several times a day. -Each check-in is then verified by an automated build, allowing teams to detect problems early.

-

For Evennia, continuous integration allows an automated build process to:

-
    -
  • Pull down a latest build from Source Control.

  • -
  • Run migrations on the backing SQL database.

  • -
  • Automate additional unique tasks for that project.

  • -
  • Run unit tests.

  • -
  • Publish those files to the server directory

  • -
  • Reload the game.

  • -
-
-
-

Preparation

-

To prepare a CI environment for your MU*, it will be necessary to set up some prerequisite -software for your server.

-

Among those you will need:

-
    -
  • A Continuous Integration Environment.

    - -
  • -
  • Source Control

    -
      -
    • This could be Git or SVN or any other available SC.

    • -
    -
  • -
-
-
-

Linux TeamCity Setup

-

For this part of the guide, an example setup will be provided for administrators running a TeamCity -build integration environment on Linux.

-

After meeting the preparation steps for your specific environment, log on to your teamcity interface -at http://<your server>:8111/.

-

Create a new project named “Evennia” and in it construct a new template called continuous- -integration.

-
-

A Quick Overview

-

Templates are fancy objects in TeamCity that allow an administrator to define build steps that are -shared between one or more build projects. Assigning a VCS Root (Source Control) is unnecessary at -this stage, primarily you’ll be worrying about the build steps and your default parameters (both -visible on the tabs to the left.)

-
-
-

Template Setup

-

In this template, you’ll be outlining the steps necessary to build your specific game. (A number of -sample scripts are provided under this section below!) Click Build Steps and prepare your general -flow. For this example, we will be doing a few basic example steps:

-
    -
  • Transforming the Settings.py file

    -
      -
    • We do this to update ports or other information that make your production environment unique -from your development environment.

    • -
    -
  • -
  • Making migrations and migrating the game database.

  • -
  • Publishing the game files.

  • -
  • Reloading the server.

  • -
-

For each step we’ll being use the “Command Line Runner” (a fancy name for a shell script executor).

-
    -
  • Create a build step with the name: Transform Configuration

  • -
  • For the script add:

    -
    #!/bin/bash
    -# Replaces the game configuration with one
    -# appropriate for this deployment.
    -
    -CONFIG="%system.teamcity.build.checkoutDir%/server/conf/settings.py"
    -MYCONF="%system.teamcity.build.checkoutDir%/server/conf/my.cnf"
    -
    -sed -e 's/TELNET_PORTS = [4000]/TELNET_PORTS = [%game.ports%]/g' "$CONFIG" > "$CONFIG".tmp && mv
    -
    -
    -
  • -
-

\(CONFIG".tmp "\)CONFIG” -sed -e ‘s/WEBSERVER_PORTS = [(4001, 4002)]/WEBSERVER_PORTS = [%game.webports%]/g’ “\(CONFIG" > -"\)CONFIG”.tmp && mv “\(CONFIG".tmp "\)CONFIG”

-
# settings.py MySQL DB configuration
-echo Configuring Game Database...
-echo "" >> "$CONFIG"
-echo "######################################################################" >> "$CONFIG"
-echo "# MySQL Database Configuration" >> "$CONFIG"
-echo "######################################################################" >> "$CONFIG"
-
-echo "DATABASES = {" >> "$CONFIG"
-echo "   'default': {" >> "$CONFIG"
-echo "       'ENGINE': 'django.db.backends.mysql'," >> "$CONFIG"
-echo "       'OPTIONS': {" >> "$CONFIG"
-echo "           'read_default_file': 'server/conf/my.cnf'," >> "$CONFIG"
-echo "       }," >> "$CONFIG"
-echo "   }" >> "$CONFIG"
-echo "}" >> "$CONFIG"
-
-# Create the My.CNF file.
-echo "[client]" >> "$MYCONF"
-echo "database = %mysql.db%" >> "$MYCONF"
-echo "user = %mysql.user%" >> "$MYCONF"
-echo "password = %mysql.pass%" >> "$MYCONF"
-echo "default-character-set = utf8" >> "$MYCONF"
-```
-
-
-

If you look at the parameters side of the page after saving this script, you’ll notice that some new -parameters have been populated for you. This is because we’ve included new teamcity configuration -parameters that are populated when the build itself is ran. When creating projects that inherit this -template, we’ll be able to fill in or override those parameters for project-specific configuration.

-
    -
  • Go ahead and create another build step called “Make Database Migration”

    -
      -
    • If you’re using SQLLite on your game, it will be prudent to change working directory on this -step to: %game.dir%

    • -
    -
  • -
  • In this script include:

    -
    #!/bin/bash
    -# Update the DB migration
    -
    -LOGDIR="server/logs"
    -
    -. %evenv.dir%/bin/activate
    -
    -# Check that the logs directory exists.
    -if [ ! -d "$LOGDIR" ]; then
    -  # Control will enter here if $LOGDIR doesn't exist.
    -  mkdir "$LOGDIR"
    -fi
    -
    -evennia makemigrations
    -
    -
    -
  • -
  • Create yet another build step, this time named: “Execute Database Migration”:

    -
      -
    • If you’re using SQLLite on your game, it will be prudent to change working directory on this -step to: %game.dir%

      -
      #!/bin/bash
      -# Apply the database migration.
      -
      -LOGDIR="server/logs"
      -
      -. %evenv.dir%/bin/activate
      -
      -# Check that the logs directory exists.
      -if [ ! -d "$LOGDIR" ]; then
      -  # Control will enter here if $LOGDIR doesn't exist.
      -  mkdir "$LOGDIR"
      -fi
      -
      -evennia migrate
      -
      -
      -
    • -
    -
  • -
-

Our next build step is where we actually publish our build. Up until now, all work on game has been -done in a ‘work’ directory on TeamCity’s build agent. From that directory we will now copy our files -to where our game actually exists on the local server.

-
    -
  • Create a new build step called “Publish Build”:

    -
      -
    • If you’re using SQLLite on your game, be sure to order this step ABOVE the Database Migration -steps. The build order will matter!

      -
      #!/bin/bash
      -# Publishes the build to the proper build directory.
      -
      -DIRECTORY="%game.dir%"
      -
      -if [ ! -d "$DIRECTORY" ]; then
      -  # Control will enter here if $DIRECTORY doesn't exist.
      -  mkdir "$DIRECTORY"
      -fi
      -
      -# Copy all the files.
      -cp -ruv %teamcity.build.checkoutDir%/* "$DIRECTORY"
      -chmod -R 775 "$DIRECTORY"
      -
      -
      -
    • -
    -
  • -
-

Finally the last script will reload our game for us.

-
    -
  • Create a new script called “Reload Game”:

    -
      -
    • The working directory on this build step will be: %game.dir%

      -
      #!/bin/bash
      -# Apply the database migration.
      -
      -LOGDIR="server/logs"
      -PIDDIR="server/server.pid"
      -
      -. %evenv.dir%/bin/activate
      -
      -# Check that the logs directory exists.
      -if [ ! -d "$LOGDIR" ]; then
      -  # Control will enter here if $LOGDIR doesn't exist.
      -  mkdir "$LOGDIR"
      -fi
      -
      -# Check that the server is running.
      -if [ -d "$PIDDIR" ]; then
      -  # Control will enter here if the game is running.
      -  evennia reload
      -fi
      -
      -
      -
    • -
    -
  • -
-

Now the template is ready for use! It would be useful this time to revisit the parameters page and -set the evenv parameter to the directory where your virtualenv exists: IE “/srv/mush/evenv”.

-
-
-

Creating the Project

-

Now it’s time for the last few steps to set up a CI environment.

-
    -
  • Return to the Evennia Project overview/administration page.

  • -
  • Create a new Sub-Project called “Production”

    -
      -
    • This will be the category that holds our actual game.

    • -
    -
  • -
  • Create a new Build Configuration in Production with the name of your MUSH.

    -
      -
    • Base this configuration off of the continuous-integration template we made earlier.

    • -
    -
  • -
  • In the build configuration, enter VCS roots and create a new VCS root that points to the -branch/version control that you are using.

  • -
  • Go to the parameters page and fill in the undefined parameters for your specific configuration.

  • -
  • If you wish for the CI to run every time a commit is made, go to the VCS triggers and add one for -“On Every Commit”.

  • -
-

And you’re done! At this point, you can return to the project overview page and queue a new build -for your game. If everything was set up correctly, the build will complete successfully. Additional -build steps could be added or removed at this point, adding some features like Unit Testing or more!

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Contributing-Docs.html b/docs/0.9.5/Contributing-Docs.html deleted file mode 100644 index 7d2a8336c0..0000000000 --- a/docs/0.9.5/Contributing-Docs.html +++ /dev/null @@ -1,820 +0,0 @@ - - - - - - - - - Contributing to Evennia Docs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - -
-
-
-
- -
-

Contributing to Evennia Docs

-
-

Warning

-

This system is still WIP and many things are bound to change!

-
-

Contributing to the docs is is like contributing to the rest of Evennia: Check out the branch of Evennia -you want to edit the documentation for. Create your own work-branch, make your changes to files -in evennia/docs/source/ and make a PR for it!

-

The documentation source files are *.md (Markdown) files found in evennia/docs/source/. -Markdown files are simple text files that can be edited with a normal text editor. They can also -contain raw HTML directives (but that is very rarely needed). They use -the Markdown syntax with MyST extensions.

-
-

Important

-

You do not need to be able to test/build the docs locally to contribute a documentation PR. -We’ll resolve any issues when we merge and build documentation. If you still want to build -the docs for yourself, instructions are at the end of this document.

-
-
-

Source file structure

-

The sources are organized into several rough categories, with only a few administrative documents -at the root of evennia/docs/source/. The folders are named in singular form since they will -primarily be accessed as link refs (e.g. Component/Accounts)

-
    -
  • source/Components/ are docs describing separate Evennia building blocks, that is, things -that you can import and use. This extends and elaborates on what can be found out by reading -the api docs themselves. Example are documentation for Accounts, Objects and Commands.

  • -
  • source/Concepts/ describes how larger-scale features of Evennia hang together - things that -can’t easily be broken down into one isolated component. This can be general descriptions of -how Models and Typeclasses interact to the path a message takes from the client to the server -and back.

  • -
  • source/Setup/ holds detailed docs on installing, running and maintaining the Evennia server and -the infrastructure around it.

  • -
  • source/Coding/ has help on how to interact with, use and navigate the Evennia codebase itself. -This also has non-Evennia-specific help on general development concepts and how to set up a sane -development environment.

  • -
  • source/Contribs/ holds documentation specifically for packages in the evennia/contribs/ folder. -Any contrib-specific tutorials will be found here instead of in Howtos

  • -
  • source/Howtos/ holds docs that describe how to achieve a specific goal, effect or -result in Evennia. This is often on a tutorial or FAQ form and will refer to the rest of the -documentation for further reading.

    -
      -
    • source/Howtos/Starting/ holds all documents part of the initial tutorial sequence.

    • -
    -
  • -
-

Other files and folders:

-
    -
  • source/api/ contains the auto-generated API documentation as .rst files. Don’t edit these -files manually, your changes will be lost. To refer to these files, use api: followed by -the Python path, for example [rpsystem contrib](evennia.contrib.rpsystem).

  • -
  • source/_templates and source/_static should not be modified unless adding a new doc-page -feature or changing the look of the HTML documentation.

  • -
  • conf.py holds the Sphinx configuration. It should usually not be modified except to update -the Evennia version on a new branch.

  • -
-
-
-
-

Editing syntax

-

The format used for Evennia’s docs is Markdown (Commonmark). While markdown -supports a few alternative forms for some of these, we try to stick to the below forms for consistency.

-
-

Italic/Bold

-

We generally use underscores for italics and double-asterisks for bold:

-
    -
  • _Italic text_ - Italic text

  • -
  • **Bold Text** - Bold text

  • -
-
-
-

Headings

-

We use # to indicate sections/headings. The more # the more of a sub-heading it is (will get -smaller and smaller font).

-
    -
  • # Heading

  • -
  • ## SubHeading

  • -
  • ### SubSubHeading

  • -
  • #### SubSubSubHeading

  • -
-
-

Don’t use the same heading/subheading name more than once in one page. While Markdown -does not prevent it, it will make it impossible to refer to that heading uniquely. -The Evennia documentation preparser will detect this and give you an error.

-
-
-
-

Lists

-

One can create both bullet-point lists and numbered lists:

-
- first bulletpoint
-- second bulletpoint
-- third bulletpoint
-
-
-
    -
  • first bulletpoint

  • -
  • second bulletpoint

  • -
  • third bulletpoint

  • -
-
1. Numbered point one
-2. Numbered point two
-3. Numbered point three
-
-
-
    -
  1. Numbered point one

  2. -
  3. Numbered point two

  4. -
  5. Numbered point three

  6. -
-
-
-

Blockquotes

-

A blockquote will create an indented block. It’s useful for emphasis and is -added by starting one or more lines with >. For ‘notes’ you can also use -an explicit Note.

-
> This is an important
-> thing to remember.
-
-
-
-

Note: This is an important -thing to remember.

-
-
- -
-

Tables

-

A table is done like this:

-
| heading1 | heading2 | heading3 |
-| --- | --- | --- | 
-| value1 | value2 | value3 |
-|  | value 4 | |
-| value 5 | value 6 | |
-
-
- - - - - - - - - - - - - - - - - - - - - -

heading1

heading2

heading3

value1

value2

value3

value 4

value 5

value 6

-

As seen, the Markdown syntax can be pretty sloppy (columns don’t need to line up) as long as you -include the heading separators and make sure to add the correct number of | on every line.

-
-
-

Verbatim text

-

It’s common to want to mark something to be displayed verbatim - just as written - without any -Markdown parsing. In running text, this is done using backticks (`), like `verbatim text` becomes -verbatim text.

-

If you want to put the verbatim text on its own line, you can do so easily by simply indenting -it 4 spaces (add empty lines on each side for readability too):

-
This is normal text
-
-    This is verbatim text
-
-This is normal text
-
-
-

Another way is to use triple-backticks:

-
```
-Everything within these backticks will be verbatim.
-
-```
-
-
-
-

Code blocks

-

A special ‘verbatim’ case is code examples - we want them to get code-highlighting for readability. -This is done by using the triple-backticks and specify which language we use:

-
```python
-from evennia import Command
-class CmdEcho(Command):
-    """
-    Usage: echo <arg>
-    """
-    key = "echo"
-    def func(self):
-        self.caller.msg(self.args.strip())
-```
-
-
-
from evennia import Command
-class CmdEcho(Command):
-  """
-  Usage: echo <arg>
-  """
-  key = "echo"
-  def func(self):
-    self.caller.msg(self.args.strip())
-
-
-
-
-
-

MyST directives

-

Markdown is easy to read and use. But while it does most of what we need, there are some things it’s -not quite as expressive as it needs to be. For this we use extended MyST syntax. This is -on the form

-
```{directive} any_options_here
-
-content
-
-```
-
-
-
-

Note

-

This kind of note may pop more than doing a > Note: ....

-
```{note}
-
-This is some noteworthy content that stretches over more than one line to show how the content indents.
-Also the important/warning notes indents like this.
-
-```
-
-
-
-

Note

-

This is some noteworthy content that stretches over more than one line to show how the content indents. -Also the important/warning notes indents like this.

-
-
-
-

Important

-

This is for particularly important and visible notes.

-
```{important}
-  This is important because it is!
-```
-
-
-
-
-

Important

-

This is important because it is!

-
-
-
-

Warning

-

A warning block is used to draw attention to particularly dangerous things, or features easy to -mess up.

-
```{warning}
-  Be careful about this ...
-```
-
-
-
-

Warning

-

Be careful about this …

-
-
-
-

Version changes and deprecations

-

These will show up as one-line warnings that suggest an added, changed or deprecated -feature beginning with particular version.

-
```{versionadded} 1.0
-```
-
-
-
-

New in version 1.0.

-
-
```{versionchanged} 1.0
-  How the feature changed with this version.
-```
-
-
-
-

Changed in version 1.0: How the feature changed with this version.

-
-
```{deprecated} 1.0
-```
-
-
-
-

Deprecated since version 1.0.

-
-
- -
-

A more flexible code block

-

The regular Markdown Python codeblock is usually enough but for more direct control over the style, one -can also use the {code-block} directive that takes a set of additional :options::

-
```{code-block} python
-:linenos:
-:emphasize-lines: 1-2,8
-:caption: An example code block
-:name: A full code block example
-
-from evennia import Command
-class CmdEcho(Command):
-    """
-    Usage: echo <arg>
-    """
-    key = "echo"
-    def func(self):
-        self.caller.msg(self.args.strip())
-```
-
-
-
-
An example code block
-
1
-2
-3
-4
-5
-6
-7
-8
from evennia import Command
-class CmdEcho(Command):
-    """
-    Usage: echo <arg>
-    """
-    key = "echo"
-    def func(self):
-        self.caller.msg(self.args.strip())
-
-
-
-

Here, :linenos: turns on line-numbers and :emphasize-lines: allows for emphasizing certain lines -in a different color. The :caption: shows an instructive text and :name: is used to reference -this -block through the link that will appear (so it should be unique for a given document).

-
-
-

eval-rst directive

-

As a last resort, we can also fall back to writing ReST directives directly:

-
```{eval-rst}
-
-    This will be evaluated as ReST.
-    All content must be indented.
-
-```
-
-
-

Within a ReST block, one must use Restructured Text syntax, which is not the -same as Markdown.

-
    -
  • Single backticks around text makes it italic.

  • -
  • Double backticks around text makes it verbatim.

  • -
  • A link is written within back-ticks, with an underscore at the end:

    -
    `python <www.python.org>`_
    -
    -
    -
  • -
-

Here is a ReST formatting cheat sheet.

-
-
-
-

Code docstrings

-

The source code docstrings will be parsed as Markdown. When writing a module docstring, you can use Markdown formatting, -including header levels down to 4th level (#### SubSubSubHeader). After the module documentation it’s -a good idea to end with four dashes ----. This will create a visible line between the documentation and the -class/function docs to follow.

-

All non-private classes, methods and functions must have a Google-style docstring, as per the -[Evennia coding style guidelines][github:evennia/CODING_STYLE.md]. This will then be correctly formatted -into pretty api docs.

-
-
-

Technical

-

Evennia leverages Sphinx with the MyST extension, which allows us -to write our docs in light-weight Markdown (more specifically CommonMark, like on github) -rather than Sphinx’ normal ReST syntax. The MyST parser allows for some extra syntax to -make us able to express more complex displays than plain Markdown can.

-

For autodoc-generation generation, we use the sphinx-napoleon -extension to understand our friendly Google-style docstrings used in classes and functions etc.

-
-
-
-

Building the docs locally

-

The sources in evennia/docs/source/ are built into a documentation using the -Sphinx static generator system. To do this locally you need to use a -system with make (Linux/Unix/Mac or Windows-WSL). Lacking -that, you could in principle also run the sphinx build-commands manually - read -the evennia/docs/Makefile to see which commands are run by the make-commands -referred to in this document.

-

You don’t necessarily have to build the docs locally to contribute. Markdown is -not hard and is very readable on its raw text-form.

-

You can furthermore get a good feel for how things will look using a -Markdown-viewer like Grip. Editors like ReText or IDE’s like -PyCharm also have native Markdown previews. Building the docs locally is -however the only way to make sure the outcome is exactly as you expect. The process -will also find any mistakes you made, like making a typo in a link.

-
-

Building only the main documentation

-

This is the fastest way to compile and view your changes. It will only build -the main documentation pages and not the API auto-docs or versions. All is -done in your terminal/console.

-
    -
  • (Optional, but recommended): Activate a virtualenv with Python 3.7.

  • -
  • cd to into the evennia/docs folder.

  • -
  • Install the documentation-build requirements:

    -
    make install
    -or
    -pip install -r requirements.txt
    -
    -
    -
  • -
  • Next, build the html-based documentation (re-run this in the future to build your changes):

    -
    make quick
    -
    -
    -
  • -
  • Note any errors from files you have edited.

  • -
  • The html-based documentation will appear in the new -folder evennia/docs/build/html/.

  • -
  • Use a web browser to open file://<path-to-folder>/evennia/docs/build/html/index.html and view -the docs. Note that you will get errors if clicking a link to the auto-docs, because you didn’t -build them!

  • -
-
-
-

Building the main documentation and API docs

-

The full documentation includes both the doc pages and the API documentation -generated from the Evennia source. For this you must install Evennia and -initialize a new game with a default database (you don’t need to have any server -running)

-
    -
  • It’s recommended that you use a virtualenv. Install your cloned version of Evennia into -by pointing to the repo folder (the one containing /docs):

    -
    pip install -e evennia
    -
    -
    -
  • -
  • Make sure you are in the parent folder containing your evennia/ repo (so two levels -up from evennia/docs/).

  • -
  • Create a new game folder called exactly gamedir at the same level as your evennia -repo with

    -
    evennia --init gamedir
    -
    -
    -
  • -
  • Then cd into it and create a new, empty database. You don’t need to start the -game or do any further changes after this.

    -
    evennia migrate
    -
    -
    -
  • -
  • This is how the structure should look at this point:

    -
      (top)
    -  |
    -  ----- evennia/  (the top-level folder, containing docs/)
    -  |
    -  ----- gamedir/
    -
    -
    -
  • -
-

(If you are already working on a game, you may of course have your ‘real’ game folder there as -well. We won’t touch that.)

-
    -
  • Go to evennia/docs/ and install the doc-building requirements (you only need to do this once):

    -
    make install
    -or
    -pip install -r requirements.txt
    -
    -
    -
  • -
  • Finally, build the full documentation, including the auto-docs:

    -
    make local
    -
    -
    -
  • -
  • The rendered files will appear in a new folder evennia/docs/build/html/. -Note any errors from files you have edited.

  • -
  • Point your web browser to file://<path-to-folder>/evennia/docs/build/html/index.html to -view the full docs.

  • -
-
-

Building with another gamedir

-

If you for some reason want to use another location of your gamedir/, or want it -named something else (maybe you already use the name ‘gamedir’ for your development …), -you can do so by setting the EVGAMEDIR environment variable to the absolute path -of your alternative game dir. For example:

-
EVGAMEDIR=/my/path/to/mygamedir make local
-
-
-
-
-
-

Building for release

-

The full Evennia documentation contains docs from many Evennia -versions, old and new. This is done by pulling documentation from Evennia’s old release -branches and building them all so readers can choose which one to view. Only -specific official Evennia branches will be built, so you can’t use this to -build your own testing branch.

-
    -
  • All local changes must have been committed to git first, since the versioned -docs are built by looking at the git tree.

  • -
  • To build for local checking, run (mv stands for “multi-version”):

    -
    make mv-local
    -
    -
    -
  • -
-

This is as close to the ‘real’ version of the docs as you can get locally. The different versions -will be found under evennia/docs/build/versions/. During deploy a symlink latest will point -to the latest version of the docs.

-
-

Release

-

Releasing the official docs requires git-push access the the Evennia gh-pages branch -on github. So there is no risk of you releasing your local changes accidentally.

-
    -
  • To deploy docs in two steps

    -
    make mv-local
    -make deploy
    -
    -
    -
  • -
  • If you know what you are doing you can also do build + deploy in one step:

    -
    make release
    -
    -
    -
  • -
-

After deployment finishes, the updated live documentation will be -available at https://evennia.github.io/evennia/latest/.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Contributing.html b/docs/0.9.5/Contributing.html deleted file mode 100644 index 428d4ae257..0000000000 --- a/docs/0.9.5/Contributing.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - - Contributing — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Contributing

-

Wanna help out? Great! Here’s how.

-
-

Spreading the word

-

Even if you are not keen on working on the server code yourself, just spreading the word is a big -help - it will help attract more people which leads to more feedback, motivation and interest. -Consider writing about Evennia on your blog or in your favorite (relevant) forum. Write a review -somewhere (good or bad, we like feedback either way). Rate it on places like ohloh. Talk -about it to your friends … that kind of thing.

-
-
-

Donations

-

The best way to support Evennia is to become an Evennia patron. Evennia is a free, -open-source project and any monetary donations you want to offer are completely voluntary. See it as -a way of announcing that you appreciate the work done - a tip of the hat! A patron donates a -(usually small) sum every month to show continued support. If this is not your thing you can also -show your appreciation via a one-time donation (this is a PayPal link but you don’t need -PayPal yourself).

-
-
-

Help with Documentation

-

Evennia depends heavily on good documentation and we are always looking for extra eyes and hands to -improve it. Even small things such as fixing typos are a great help!

-

The documentation is a wiki and as long as you have a GitHub account you can edit it. It can be a -good idea to discuss in the chat or forums if you want to add new pages/tutorials. Otherwise, it -goes a long way just pointing out wiki errors so we can fix them (in an Issue or just over -chat/forum).

-
-
-

Contributing through a forked repository

-

We always need more eyes and hands on the code. Even if you don’t feel confident with tackling a -[bug or feature][issues], just correcting typos, adjusting formatting or simply using the thing -and reporting when stuff doesn’t make sense helps us a lot.

-

The most elegant way to contribute code to Evennia is to use GitHub to create a fork of the -Evennia repository and make your changes to that. Refer to the [Forking Evennia](Version- -Control#forking-evennia) version -control instructions for detailed instructions.

-

Once you have a fork set up, you can not only work on your own game in a separate branch, you can -also commit your fixes to Evennia itself. Make separate branches for all Evennia additions you do - -don’t edit your local master or develop branches directly. It will make your life a lot easier. -If you have a change that you think is suitable for the main Evennia repository, you issue a [Pull -Request][pullrequest]. This will let Evennia devs know you have stuff to share. Bug fixes should -generally be done against the master branch of Evennia, while new features/contribs should go into -the develop branch. If you are unsure, just pick one and we’ll figure it out.

-
-
-

Contributing with Patches

-

To help with Evennia development it’s recommended to do so using a fork repository as described -above. But for small, well isolated fixes you are also welcome to submit your suggested Evennia -fixes/addendums as a [patch][patch].

-

You can include your patch in an Issue or a Mailing list post. Please avoid pasting the full patch -text directly in your post though, best is to use a site like Pastebin and -just supply the link.

-
-
-

Contributing with Contribs

-

While Evennia’s core is pretty much game-agnostic, it also has a contrib/ directory. The contrib -directory contains game systems that are specialized or useful only to certain types of games. Users -are welcome to contribute to the contrib/ directory. Such contributions should always happen via a -Forked repository as described above.

-
    -
  • If you are unsure if your idea/code is suitable as a contrib, ask the devs before putting any -work into it. This can also be a good idea in order to not duplicate efforts. This can also act as -a check that your implementation idea is sound. We are, for example, unlikely to accept contribs -that require large modifications of the game directory structure.

  • -
  • If your code is intended primarily as an example or shows a concept/principle rather than a -working system, it is probably not suitable for contrib/. You are instead welcome to use it as -part of a [new tutorial][tutorials]!

  • -
  • The code should ideally be contained within a single Python module. But if the contribution is -large this may not be practical and it should instead be grouped in its own subdirectory (not as -loose modules).

  • -
  • The contribution should preferably be isolated (only make use of core Evennia) so it can easily be -dropped into use. If it does depend on other contribs or third-party modules, these must be clearly -documented and part of the installation instructions.

  • -
  • The code itself should follow Evennia’s [Code style guidelines][codestyle].

  • -
  • The code must be well documented as described in our documentation style -guide. Expect that your -code will be read and should be possible to understand by others. Include comments as well as a -header in all modules. If a single file, the header should include info about how to include the -contrib in a game (installation instructions). If stored in a subdirectory, this info should go into -a new README.md file within that directory.

  • -
  • Within reason, your contribution should be designed as genre-agnostic as possible. Limit the -amount of game-style-specific code. Assume your code will be applied to a very different game than -you had in mind when creating it.

  • -
  • To make the licensing situation clear we assume all contributions are released with the same -license as Evennia. If this is not possible for some reason, talk to us and we’ll -handle it on a case-by-case basis.

  • -
  • Your contribution must be covered by unit tests. Having unit tests will both help -make your code more stable and make sure small changes does not break it without it being noticed, -it will also help us test its functionality and merge it quicker. If your contribution is a single -module, you can add your unit tests to evennia/contribs/tests.py. If your contribution is bigger -and in its own sub-directory you could just put the tests in your own tests.py file (Evennia will -find it automatically).

  • -
  • Merging of your code into Evennia is not guaranteed. Be ready to receive feedback and to be asked -to make corrections or fix bugs. Furthermore, merging a contrib means the Evennia project takes on -the responsibility of maintaining and supporting it. For various reasons this may be deemed to be -beyond our manpower. However, if your code were to not be accepted for merger for some reason, we -will instead add a link to your online repository so people can still find and use your work if they -want.

  • -
-

pO1X1jbKiv_- -UBBFWIuVDEZxC0M_2pM6ywO&dispatch=5885d80a13c0db1f8e263663d3faee8d66f31424b43e9a70645c907a6cbd8fb4 -[forking]: https://github.com/evennia/evennia/wiki/Version-Control#wiki-forking-from-evennia -[pullrequest]: https://github.com/evennia/evennia/pulls -[issues]: https://github.com/evennia/evennia/issues -[patch]: https://secure.wikimedia.org/wikipedia/en/wiki/Patch_(computing) -[codestyle]: https://github.com/evennia/evennia/blob/master/CODING_STYLE.md -[tutorials]: https://github.com/evennia/evennia/wiki/Tutorials

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Coordinates.html b/docs/0.9.5/Coordinates.html deleted file mode 100644 index 11d040ce28..0000000000 --- a/docs/0.9.5/Coordinates.html +++ /dev/null @@ -1,456 +0,0 @@ - - - - - - - - - Coordinates — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Coordinates

-
-
-

Adding room coordinates in your game

-

This tutorial is moderately difficult in content. You might want to be familiar and at ease with -some Python concepts (like properties) and possibly Django concepts (like queries), although this -tutorial will try to walk you through the process and give enough explanations each time. If you -don’t feel very confident with math, don’t hesitate to pause, go to the example section, which shows -a tiny map, and try to walk around the code or read the explanation.

-

Evennia doesn’t have a coordinate system by default. Rooms and other objects are linked by location -and content:

-
    -
  • An object can be in a location, that is, another object. Like an exit in a room.

  • -
  • An object can access its content. A room can see what objects uses it as location (that would -include exits, rooms, characters and so on).

  • -
-

This system allows for a lot of flexibility and, fortunately, can be extended by other systems. -Here, I offer you a way to add coordinates to every room in a way most compliant with Evennia -design. This will also show you how to use coordinates, find rooms around a given point for -instance.

-
-

Coordinates as tags

-

The first concept might be the most surprising at first glance: we will create coordinates as -tags.

-
-

Why not attributes, wouldn’t that be easier?

-
-

It would. We could just do something like room.db.x = 3. The advantage of using tags is that it -will be easy and effective to search. Although this might not seem like a huge advantage right now, -with a database of thousands of rooms, it might make a difference, particularly if you have a lot of -things based on coordinates.

-

Rather than giving you a step-by-step process, I’ll show you the code. Notice that we use -properties to easily access and update coordinates. This is a Pythonic approach. Here’s our first -Room class, that you can modify in typeclasses/rooms.py:

-
# in typeclasses/rooms.py
-
-from evennia import DefaultRoom
-
-class Room(DefaultRoom):
-    """
-    Rooms are like any Object, except their location is None
-    (which is default). They also use basetype_setup() to
-    add locks so they cannot be puppeted or picked up.
-    (to change that, use at_object_creation instead)
-
-    See examples/object.py for a list of
-    properties and methods available on all Objects.
-    """
-
-    @property
-    def x(self):
-        """Return the X coordinate or None."""
-        x = self.tags.get(category="coordx")
-        return int(x) if isinstance(x, str) else None
-
-    @x.setter
-    def x(self, x):
-        """Change the X coordinate."""
-        old = self.tags.get(category="coordx")
-        if old is not None:
-            self.tags.remove(old, category="coordx")
-        if x is not None:
-            self.tags.add(str(x), category="coordx")
-
-    @property
-    def y(self):
-        """Return the Y coordinate or None."""
-        y = self.tags.get(category="coordy")
-        return int(y) if isinstance(y, str) else None
-
-    @y.setter
-    def y(self, y):
-        """Change the Y coordinate."""
-        old = self.tags.get(category="coordy")
-        if old is not None:
-            self.tags.remove(old, category="coordy")
-        if y is not None:
-            self.tags.add(str(y), category="coordy")
-
-    @property
-    def z(self):
-        """Return the Z coordinate or None."""
-        z = self.tags.get(category="coordz")
-        return int(z) if isinstance(z, str) else None
-
-    @z.setter
-    def z(self, z):
-        """Change the Z coordinate."""
-        old = self.tags.get(category="coordz")
-        if old is not None:
-            self.tags.remove(old, category="coordz")
-        if z is not None:
-            self.tags.add(str(z), category="coordz")
-
-
-

If you aren’t familiar with the concept of properties in Python, I encourage you to read a good -tutorial on the subject. This article on Python properties -is well-explained and should help you understand the idea.

-

Let’s look at our properties for x. First of all is the read property.

-
    @property
-    def x(self):
-        """Return the X coordinate or None."""
-        x = self.tags.get(category="coordx")
-        return int(x) if isinstance(x, str) else None
-
-
-

What it does is pretty simple:

-
    -
  1. It gets the tag of category "coordx". It’s the tag category where we store our X coordinate. -The tags.get method will return None if the tag can’t be found.

  2. -
  3. We convert the value to an integer, if it’s a str. Remember that tags can only contain str, -so we’ll need to convert it.

  4. -
-
-

I thought tags couldn’t contain values?

-
-

Well, technically, they can’t: they’re either here or not. But using tag categories, as we have -done, we get a tag, knowing only its category. That’s the basic approach to coordinates in this -tutorial.

-

Now, let’s look at the method that will be called when we wish to set x in our room:

-
    @x.setter
-    def x(self, x):
-        """Change the X coordinate."""
-        old = self.tags.get(category="coordx")
-        if old is not None:
-            self.tags.remove(old, category="coordx")
-        if x is not None:
-            self.tags.add(str(x), category="coordx")
-
-
-
    -
  1. First, we remove the old X coordinate, if it exists. Otherwise, we’d end up with two tags in our -room with “coordx” as their category, which wouldn’t do at all.

  2. -
  3. Then we add the new tag, giving it the proper category.

  4. -
-
-

Now what?

-
-

If you add this code and reload your game, once you’re logged in with a character in a room as its -location, you can play around:

-
@py here.x
-@py here.x = 0
-@py here.y = 3
-@py here.z = -2
-@py here.z = None
-
-
-

The code might not be that easy to read, but you have to admit it’s fairly easy to use.

-
-
-

Some additional searches

-

Having coordinates is useful for several reasons:

-
    -
  1. It can help in shaping a truly logical world, in its geography, at least.

  2. -
  3. It can allow to look for specific rooms at given coordinates.

  4. -
  5. It can be good in order to quickly find the rooms around a location.

  6. -
  7. It can even be great in path-finding (finding the shortest path between two rooms).

  8. -
-

So far, our coordinate system can help with 1., but not much else. Here are some methods that we -could add to the Room typeclass. These methods will just be search methods. Notice that they are -class methods, since we want to get rooms.

-
-

Finding one room

-

First, a simple one: how to find a room at a given coordinate? Say, what is the room at X=0, Y=0, -Z=0?

-
class Room(DefaultRoom):
-    # ...
-    @classmethod
-    def get_room_at(cls, x, y, z):
-        """
-        Return the room at the given location or None if not found.
-
-        Args:
-            x (int): the X coord.
-            y (int): the Y coord.
-            z (int): the Z coord.
-
-        Return:
-            The room at this location (Room) or None if not found.
-
-        """
-        rooms = cls.objects.filter(
-                db_tags__db_key=str(x), db_tags__db_category="coordx").filter(
-                db_tags__db_key=str(y), db_tags__db_category="coordy").filter(
-                db_tags__db_key=str(z), db_tags__db_category="coordz")
-        if rooms:
-            return rooms[0]
-
-        return None
-
-
-

This solution includes a bit of Django -queries. -Basically, what we do is reach for the object manager and search for objects with the matching tags. -Again, don’t spend too much time worrying about the mechanism, the method is quite easy to use:

-
Room.get_room_at(5, 2, -3)
-
-
-

Notice that this is a class method: you will call it from Room (the class), not an instance. -Though you still can:

-
@py here.get_room_at(3, 8, 0)
-
-
-
-
-

Finding several rooms

-

Here’s another useful method that allows us to look for rooms around a given coordinate. This is -more advanced search and doing some calculation, beware! Look at the following section if you’re -lost.

-
from math import sqrt
-
-class Room(DefaultRoom):
-
-    # ...
-
-    @classmethod
-    def get_rooms_around(cls, x, y, z, distance):
-        """
-        Return the list of rooms around the given coordinates.
-
-        This method returns a list of tuples (distance, room) that
-        can easily be browsed.  This list is sorted by distance (the
-        closest room to the specified position is always at the top
-        of the list).
-
-        Args:
-            x (int): the X coord.
-            y (int): the Y coord.
-            z (int): the Z coord.
-            distance (int): the maximum distance to the specified position.
-
-        Returns:
-            A list of tuples containing the distance to the specified
-            position and the room at this distance.  Several rooms
-            can be at equal distance from the position.
-
-        """
-        # Performs a quick search to only get rooms in a square
-        x_r = list(reversed([str(x - i) for i in range(0, distance + 1)]))
-        x_r += [str(x + i) for i in range(1, distance + 1)]
-        y_r = list(reversed([str(y - i) for i in range(0, distance + 1)]))
-        y_r += [str(y + i) for i in range(1, distance + 1)]
-        z_r = list(reversed([str(z - i) for i in range(0, distance + 1)]))
-        z_r += [str(z + i) for i in range(1, distance + 1)]
-        wide = cls.objects.filter(
-                db_tags__db_key__in=x_r, db_tags__db_category="coordx").filter(
-                db_tags__db_key__in=y_r, db_tags__db_category="coordy").filter(
-                db_tags__db_key__in=z_r, db_tags__db_category="coordz")
-
-        # We now need to filter down this list to find out whether
-        # these rooms are really close enough, and at what distance
-        # In short: we change the square to a circle.
-        rooms = []
-        for room in wide:
-            x2 = int(room.tags.get(category="coordx"))
-            y2 = int(room.tags.get(category="coordy"))
-            z2 = int(room.tags.get(category="coordz"))
-            distance_to_room = sqrt(
-                    (x2 - x) ** 2 + (y2 - y) ** 2 + (z2 - z) ** 2)
-            if distance_to_room <= distance:
-                rooms.append((distance_to_room, room))
-
-        # Finally sort the rooms by distance
-        rooms.sort(key=lambda tup: tup[0])
-        return rooms
-
-
-

This gets more serious.

-
    -
  1. We have specified coordinates as parameters. We determine a broad range using the distance. -That is, for each coordinate, we create a list of possible matches. See the example below.

  2. -
  3. We then search for the rooms within this broader range. It gives us a square -around our location. Some rooms are definitely outside the range. Again, see the example below -to follow the logic.

  4. -
  5. We filter down the list and sort it by distance from the specified coordinates.

  6. -
-

Notice that we only search starting at step 2. Thus, the Django search doesn’t look and cache all -objects, just a wider range than what would be really necessary. This method returns a circle of -coordinates around a specified point. Django looks for a square. What wouldn’t fit in the circle -is removed at step 3, which is the only part that includes systematic calculation. This method is -optimized to be quick and efficient.

-
-
-

An example

-

An example might help. Consider this very simple map (a textual description follows):

-
4 A B C D
-3 E F G H
-2 I J K L
-1 M N O P
-  1 2 3 4
-
-
-

The X coordinates are given below. The Y coordinates are given on the left. This is a simple -square with 16 rooms: 4 on each line, 4 lines of them. All the rooms are identified by letters in -this example: the first line at the top has rooms A to D, the second E to H, the third I to L and -the fourth M to P. The bottom-left room, X=1 and Y=1, is M. The upper-right room X=4 and Y=4 is D.

-

So let’s say we want to find all the neighbors, distance 1, from the room J. J is at X=2, Y=2.

-

So we use:

-
Room.get_rooms_around(x=2, y=2, z=0, distance=1)
-# we'll assume a z coordinate of 0 for simplicity
-
-
-
    -
  1. First, this method gets all the rooms in a square around J. So it gets E F G, I J K, M N O. If -you want, draw the square around these coordinates to see what’s happening.

  2. -
  3. Next, we browse over this list and check the real distance between J (X=2, Y=2) and the room. -The four corners of the square are not in this circle. For instance, the distance between J and M -is not 1. If you draw a circle of center J and radius 1, you’ll notice that the four corners of our -square (E, G, M and O) are not in this circle. So we remove them.

  4. -
  5. We sort by distance from J.

  6. -
-

So in the end we might obtain something like this:

-
[
-    (0, J), # yes, J is part of this circle after all, with a distance of 0
-    (1, F),
-    (1, I),
-    (1, K),
-    (1, N),
-]
-
-
-

You can try with more examples if you want to see this in action.

-
-
-

To conclude

-

You can definitely use this system to map other objects, not just rooms. You can easily remove the -`Z coordinate too, if you simply need X and Y.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Custom-Protocols.html b/docs/0.9.5/Custom-Protocols.html deleted file mode 100644 index 84c3c4d1ce..0000000000 --- a/docs/0.9.5/Custom-Protocols.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - Custom Protocols — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Custom Protocols

-

Note: This is considered an advanced topic and is mostly of interest to users planning to implement -their own custom client protocol.

-

A PortalSession is the basic data object representing an -external -connection to the Evennia Portal – usually a human player running a mud client -of some kind. The way they connect (the language the player’s client and Evennia use to talk to -each other) is called the connection Protocol. The most common such protocol for MUD:s is the -Telnet protocol. All Portal Sessions are stored and managed by the Portal’s sessionhandler.

-

It’s technically sometimes hard to separate the concept of PortalSession from the concept of -Protocol since both depend heavily on the other (they are often created as the same class). When -data flows through this part of the system, this is how it goes

-
# In the Portal
-You <->
-  Protocol + PortalSession <->
-    PortalSessionHandler <->
-      (AMP) <->
-        ServerSessionHandler <->
-          ServerSession <->
-            InputFunc
-
-
-

(See the Message Path for the bigger picture of how data flows through Evennia). The -parts that needs to be customized to make your own custom protocol is the Protocol + PortalSession -(which translates between data coming in/out over the wire to/from Evennia internal representation) -as well as the InputFunc (which handles incoming data).

-
-

Adding custom Protocols

-

Evennia has a plugin-system that add the protocol as a new “service” to the application.

-

Take a look at evennia/server/portal/portal.py, notably the sections towards the end of that file. -These are where the various in-built services like telnet, ssh, webclient etc are added to the -Portal (there is an equivalent but shorter list in evennia/server/server.py).

-

To add a new service of your own (for example your own custom client protocol) to the Portal or -Server, look at mygame/server/conf/server_services_plugins and portal_services_plugins. By -default Evennia will look into these modules to find plugins. If you wanted to have it look for more -modules, you could do the following:

-
    # add to the Server
-    SERVER_SERVICES_PLUGIN_MODULES.append('server.conf.my_server_plugins')
-    # or, if you want to add to the Portal
-    PORTAL_SERVICES_PLUGIN_MODULES.append('server.conf.my_portal_plugins')
-
-
-

When adding a new connection you’ll most likely only need to add new things to the -PORTAL_SERVICES_PLUGIN_MODULES.

-

This module can contain whatever you need to define your protocol, but it must contain a function -start_plugin_services(app). This is called by the Portal as part of its upstart. The function -start_plugin_services must contain all startup code the server need. The app argument is a -reference to the Portal/Server application itself so the custom service can be added to it. The -function should not return anything.

-

This is how it looks:

-
    # mygame/server/conf/portal_services_plugins.py
-
-    # here the new Portal Twisted protocol is defined
-    class MyOwnFactory( ... ):
-       [...]
-
-    # some configs
-    MYPROC_ENABLED = True # convenient off-flag to avoid having to edit settings all the time
-    MY_PORT = 6666
-
-    def start_plugin_services(portal):
-        "This is called by the Portal during startup"
-         if not MYPROC_ENABLED:
-             return
-         # output to list this with the other services at startup
-         print("  myproc: %s" % MY_PORT)
-
-         # some setup (simple example)
-         factory = MyOwnFactory()
-         my_service = internet.TCPServer(MY_PORT, factory)
-         # all Evennia services must be uniquely named
-         my_service.setName("MyService")
-         # add to the main portal application
-         portal.services.addService(my_service)
-
-
-

Once the module is defined and targeted in settings, just reload the server and your new -protocol/services should start with the others.

-
-
-

Writing your own Protocol

-

Writing a stable communication protocol from scratch is not something we’ll cover here, it’s no -trivial task. The good news is that Twisted offers implementations of many common protocols, ready -for adapting.

-

Writing a protocol implementation in Twisted usually involves creating a class inheriting from an -already existing Twisted protocol class and from evennia.server.session.Session (multiple -inheritance), then overloading the methods that particular protocol uses to link them to the -Evennia-specific inputs.

-

Here’s a example to show the concept:

-
# In module that we'll later add to the system through PORTAL_SERVICE_PLUGIN_MODULES
-
-# pseudo code
-from twisted.something import TwistedClient
-# this class is used both for Portal- and Server Sessions
-from evennia.server.session import Session
-
-from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS
-
-class MyCustomClient(TwistedClient, Session):
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.sessionhandler = PORTAL_SESSIONS
-
-    # these are methods we must know that TwistedClient uses for
-    # communication. Name and arguments could vary for different Twisted protocols
-    def onOpen(self, *args, **kwargs):
-        # let's say this is called when the client first connects
-
-        # we need to init the session and connect to the sessionhandler. The .factory
-        # is available through the Twisted parents
-
-        client_address = self.getClientAddress()  # get client address somehow
-
-        self.init_session("mycustom_protocol", client_address, self.factory.sessionhandler)
-        self.sessionhandler.connect(self)
-
-    def onClose(self, reason, *args, **kwargs):
-        # called when the client connection is dropped
-        # link to the Evennia equivalent
-        self.disconnect(reason)
-
-    def onMessage(self, indata, *args, **kwargs):
-        # called with incoming data
-        # convert as needed here
-        self.data_in(data=indata)
-
-    def sendMessage(self, outdata, *args, **kwargs):
-        # called to send data out
-        # modify if needed
-        super().sendMessage(self, outdata, *args, **kwargs)
-
-     # these are Evennia methods. They must all exist and look exactly like this
-     # The above twisted-methods call them and vice-versa. This connects the protocol
-     # the Evennia internals.
-     
-     def disconnect(self, reason=None):
-         """
-         Called when connection closes.
-         This can also be called directly by Evennia when manually closing the connection.
-         Do any cleanups here.
-         """
-         self.sessionhandler.disconnect(self)
-
-     def at_login(self):
-         """
-         Called when this session authenticates by the server (if applicable)
-         """
-
-     def data_in(self, **kwargs):
-         """
-         Data going into the server should go through this method. It
-         should pass data into `sessionhandler.data_in`. THis will be called
-         by the sessionhandler with the data it gets from the approrpriate
-         send_* method found later in this protocol.
-         """
-         self.sessionhandler.data_in(self, text=kwargs['data'])
-
-     def data_out(self, **kwargs):
-         """
-         Data going out from the server should go through this method. It should
-         hand off to the protocol's send method, whatever it's called.
-         """
-         # we assume we have a 'text' outputfunc
-         self.onMessage(kwargs['text'])
-
-     # 'outputfuncs' are defined as `send_<outputfunc_name>`. From in-code, they are called
-     # with `msg(outfunc_name=<data>)`.
-
-     def send_text(self, txt, *args, **kwargs):
-         """
-         Send text, used with e.g. `session.msg(text="foo")`
-         """
-         # we make use of the
-         self.data_out(text=txt)
-
-     def send_default(self, cmdname, *args, **kwargs):
-         """
-         Handles all outputfuncs without an explicit `send_*` method to handle them.
-         """
-         self.data_out(**{cmdname: str(args)})
-
-
-
-

The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to -the Evennia-specific methods.

-
-

Sending data out

-

To send data out through this protocol, you’d need to get its Session and then you could e.g.

-
    session.msg(text="foo")
-
-
-

The message will pass through the system such that the sessionhandler will dig out the session and -check if it has a send_text method (it has). It will then pass the “foo” into that method, which -in our case means sending “foo” across the network.

-
-
-

Receiving data

-

Just because the protocol is there, does not mean Evennia knows what to do with it. An -Inputfunc must exist to receive it. In the case of the text input exemplified above, -Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So -handle that you need to simply add a cmdset with commands on your receiving Session (and/or the -Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the -Inputfunc page for how to do this.

-

These might not be as clear-cut in all protocols, but the principle is there. These four basic -components - however they are accessed - links to the Portal Session, which is the actual common -interface between the different low-level protocols and Evennia.

-
-
-
-

Assorted notes

-

To take two examples, Evennia supports the telnet protocol as well as webclient, via ajax or -websockets. You’ll find that whereas telnet is a textbook example of a Twisted protocol as seen -above, the ajax protocol looks quite different due to how it interacts with the -webserver through long-polling (comet) style requests. All the necessary parts -mentioned above are still there, but by necessity implemented in very different -ways.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Customize-channels.html b/docs/0.9.5/Customize-channels.html deleted file mode 100644 index 106bff344b..0000000000 --- a/docs/0.9.5/Customize-channels.html +++ /dev/null @@ -1,586 +0,0 @@ - - - - - - - - - Customize channels — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Customize channels

-
-
-

Channel commands in Evennia

-

By default, Evennia’s default channel commands are inspired by MUX. They all -begin with “c” followed by the action to perform (like “ccreate” or “cdesc”). -If this default seems strange to you compared to other Evennia commands that -rely on switches, you might want to check this tutorial out.

-

This tutorial will also give you insight into the workings of the channel system. -So it may be useful even if you don’t plan to make the exact changes shown here.

-
-

What we will try to do

-

Our mission: change the default channel commands to have a different syntax.

-

This tutorial will do the following changes:

-
    -
  • Remove all the default commands to handle channels.

  • -
  • Add a + and - command to join and leave a channel. So, assuming there is -a public channel on your game (most often the case), you could type +public -to join it and -public to leave it.

  • -
  • Group the commands to manipulate channels under the channel name, after a -switch. For instance, instead of writing cdesc public = My public channel, -you would write public/desc My public channel.

  • -
-
-

I listed removing the default Evennia commands as a first step in the -process. Actually, we’ll move it at the very bottom of the list, since we -still want to use them, we might get it wrong and rely on Evennia commands -for a while longer.

-
-
-
-

A command to join, another to leave

-

We’ll do the most simple task at first: create two commands, one to join a -channel, one to leave.

-
-

Why not have them as switches? public/join and public/leave for instance?

-
-

For security reasons, I will hide channels to which the caller is not -connected. It means that if the caller is not connected to the “public” -channel, he won’t be able to use the “public” command. This is somewhat -standard: if we create an administrator-only channel, we don’t want players to -try (or even know) the channel command. Again, you could design it a different -way should you want to.

-

First create a file named comms.py in your commands package. It’s -a rather logical place, since we’ll write different commands to handle -communication.

-

Okay, let’s add the first command to join a channel:

-
# in commands/comms.py
-from evennia.utils.search import search_channel
-from commands.command import Command
-
-class CmdConnect(Command):
-    """
-    Connect to a channel.
-    """
-
-    key = "+"
-    help_category = "Comms"
-    locks = "cmd:not pperm(channel_banned)"
-    auto_help = False
-
-    def func(self):
-        """Implement the command"""
-        caller = self.caller
-        args = self.args
-        if not args:
-            self.msg("Which channel do you want to connect to?")
-            return
-
-        channelname = self.args
-        channel = search_channel(channelname)
-        if not channel:
-            return
-
-        # Check permissions
-        if not channel.access(caller, 'listen'):
-            self.msg("%s: You are not allowed to listen to this channel." % channel.key)
-            return
-
-        # If not connected to the channel, try to connect
-        if not channel.has_connection(caller):
-            if not channel.connect(caller):
-                self.msg("%s: You are not allowed to join this channel." % channel.key)
-                return
-            else:
-                self.msg("You now are connected to the %s channel. " % channel.key.lower())
-        else:
-            self.msg("You already are connected to the %s channel. " % channel.key.lower())
-
-
-

Okay, let’s review this code, but if you’re used to Evennia commands, it shouldn’t be too strange:

-
    -
  1. We import search_channel. This is a little helper function that we will use to search for -channels by name and aliases, found in evennia.utils.search. It’s just more convenient.

  2. -
  3. Our class CmdConnect contains the body of our command to join a channel.

  4. -
  5. Notice the key of this command is simply "+". When you enter +something in the game, it will -try to find a command key +something. Failing that, it will look at other potential matches. -Evennia is smart enough to understand that when we type +something, + is the command key and -something is the command argument. This will, of course, fail if you have a command beginning by -+ conflicting with the CmdConnect key.

  6. -
  7. We have altered some class attributes, like auto_help. If you want to know what they do and -why they have changed here, you can check the documentation on commands.

  8. -
  9. In the command body, we begin by extracting the channel name. Remember that this name should be -in the command arguments (that is, in self.args). Following the same example, if a player enters -+something, self.args should contain "something". We use search_channel to see if this -channel exists.

  10. -
  11. We then check the access level of the channel, to see if the caller can listen to it (not -necessarily use it to speak, mind you, just listen to others speak, as these are two different locks -on Evennia).

  12. -
  13. Finally, we connect the caller if he’s not already connected to the channel. We use the -channel’s connect method to do this. Pretty straightforward eh?

  14. -
-

Now we’ll add a command to leave a channel. It’s almost the same, turned upside down:

-
class CmdDisconnect(Command):
-    """
-    Disconnect from a channel.
-    """
-
-    key = "-"
-    help_category = "Comms"
-    locks = "cmd:not pperm(channel_banned)"
-    auto_help = False
-
-    def func(self):
-        """Implement the command"""
-        caller = self.caller
-        args = self.args
-        if not args:
-            self.msg("Which channel do you want to disconnect from?")
-            return
-
-        channelname = self.args
-        channel = search_channel(channelname)
-        if not channel:
-            return
-
-        # If connected to the channel, try to disconnect
-        if channel.has_connection(caller):
-            if not channel.disconnect(caller):
-                self.msg("%s: You are not allowed to disconnect from this channel." % channel.key)
-                return
-            else:
-                self.msg("You stop listening to the %s channel. " % channel.key.lower())
-        else:
-            self.msg("You are not connected to the %s channel. " % channel.key.lower())
-
-
-

So far, you shouldn’t have trouble following what this command does: it’s -pretty much the same as the CmdConnect class in logic, though it accomplishes -the opposite. If you are connected to the channel public you could -disconnect from it using -public. Remember, you can use channel aliases too -(+pub and -pub will also work, assuming you have the alias pub on the -public channel).

-

It’s time to test this code, and to do so, you will need to add these two -commands. Here is a good time to say it: by default, Evennia connects accounts -to channels. Some other games (usually with a higher multisession mode) will -want to connect characters instead of accounts, so that several characters in -the same account can be connected to various channels. You can definitely add -these commands either in the AccountCmdSet or CharacterCmdSet, the caller -will be different and the command will add or remove accounts of characters. -If you decide to install these commands on the CharacterCmdSet, you might -have to disconnect your superuser account (account #1) from the channel before -joining it with your characters, as Evennia tends to subscribe all accounts -automatically if you don’t tell it otherwise.

-

So here’s an example of how to add these commands into your AccountCmdSet. -Edit the file commands/default_cmdsets.py to change a few things:

-
# In commands/default_cmdsets.py
-from evennia import default_cmds
-from commands.comms import CmdConnect, CmdDisconnect
-
-
-# ... Skip to the AccountCmdSet class ...
-
-class AccountCmdSet(default_cmds.AccountCmdSet):
-    """
-    This is the cmdset available to the Account at all times. It is
-    combined with the `CharacterCmdSet` when the Account puppets a
-    Character. It holds game-account-specific commands, channel
-    commands, etc.
-    """
-    key = "DefaultAccount"
-
-    def at_cmdset_creation(self):
-        """
-        Populates the cmdset
-        """
-        super().at_cmdset_creation()
-
-        # Channel commands
-        self.add(CmdConnect())
-        self.add(CmdDisconnect())
-
-
-

Save, reload your game, and you should be able to use +public and -public -now!

-
-
-

A generic channel command with switches

-

It’s time to dive a little deeper into channel processing. What happens in -Evennia when a player enters public Hello everybody!?

-

Like exits, channels are a particular command that Evennia automatically -creates and attaches to individual channels. So when you enter public message in your game, Evennia calls the public command.

-
-

But I didn’t add any public command…

-
-

Evennia will just create these commands automatically based on the existing -channels. The base command is the command we’ll need to edit.

-
-

Why edit it? It works just fine to talk.

-
-

Unfortunately, if we want to add switches to our channel names, we’ll have to -edit this command. It’s not too hard, however, we’ll just start writing a -standard command with minor twitches.

-
-

Some additional imports

-

You’ll need to add a line of import in your commands/comms.py file. We’ll -see why this import is important when diving in the command itself:

-
from evennia.comms.models import ChannelDB
-
-
-
-
-

The class layout

-
# In commands/comms.py
-class ChannelCommand(Command):
-    """
-    {channelkey} channel
-
-    {channeldesc}
-
-    Usage:
-      {lower_channelkey} <message>
-      {lower_channelkey}/history [start]
-      {lower_channelkey}/me <message>
-      {lower_channelkey}/who
-
-    Switch:
-      history: View 20 previous messages, either from the end or
-          from <start> number of messages from the end.
-      me: Perform an emote on this channel.
-      who: View who is connected to this channel.
-
-    Example:
-      {lower_channelkey} Hello World!
-      {lower_channelkey}/history
-      {lower_channelkey}/history 30
-      {lower_channelkey}/me grins.
-      {lower_channelkey}/who
-    """
-    # note that channeldesc and lower_channelkey will be filled
-    # automatically by ChannelHandler
-
-    # this flag is what identifies this cmd as a channel cmd
-    # and branches off to the system send-to-channel command
-    # (which is customizable by admin)
-    is_channel = True
-    key = "general"
-    help_category = "Channel Names"
-    obj = None
-    arg_regex = ""
-
-
-

There are some differences here compared to most common commands.

-
    -
  • There is something disconcerting in the class docstring. Some information is -between curly braces. This is a format-style which is only used for channel -commands. {channelkey} will be replaced by the actual channel key (like -public). {channeldesc} will be replaced by the channel description (like -“public channel”). And {lower_channelkey}.

  • -
  • We have set is_channel to True in the command class variables. You -shouldn’t worry too much about that: it just tells Evennia this is a special -command just for channels.

  • -
  • key is a bit misleading because it will be replaced eventually. So we -could set it to virtually anything.

  • -
  • The obj class variable is another one we won’t detail right now.

  • -
  • arg_regex is important: the default arg_regex in the channel command will -forbid to use switches (a slash just after the channel name is not allowed). -That’s why we enforce it here, we allow any syntax.

  • -
-
-

What will become of this command?

-
-

Well, when we’ll be through with it, and once we’ll add it as the default -command to handle channels, Evennia will create one per existing channel. For -instance, the public channel will receive one command of this class, with key -set to public and aliases set to the channel aliases (like ['pub']).

-
-

Can I see it work?

-
-

Not just yet, there’s still a lot of code needed.

-

Okay we have the command structure but it’s rather empty.

-
-
-

The parse method

-

The parse method is called before func in every command. Its job is to -parse arguments and in our case, we will analyze switches here.

-
# ...
-    def parse(self):
-        """
-        Simple parser
-        """
-        # channel-handler sends channame:msg here.
-        channelname, msg = self.args.split(":", 1)
-        self.switch = None
-        if msg.startswith("/"):
-            try:
-                switch, msg = msg[1:].split(" ", 1)
-            except ValueError:
-                switch = msg[1:]
-                msg = ""
-
-            self.switch = switch.lower().strip()
-
-        self.args = (channelname.strip(), msg.strip())
-
-
-

Reading the comments we see that the channel handler will send the command in a -strange way: a string with the channel name, a colon and the actual message -entered by the player. So if the player enters “public hello”, the command -args will contain "public:hello". You can look at the way the channel name -and message are parsed, this can be used in a lot of different commands.

-

Next we check if there’s any switch, that is, if the message starts with a -slash. This would be the case if a player entered public/me jumps up and down, for instance. If there is a switch, we save it in self.switch. We -alter self.args at the end to contain a tuple with two values: the channel -name, and the message (if a switch was used, notice that the switch will be -stored in self.switch, not in the second element of self.args).

-
-
-

The command func

-

Finally, let’s see the func method in the command class. It will have to -handle switches and also the raw message to send if no switch was used.

-
# ...
-    def func(self):
-        """
-        Create a new message and send it to channel, using
-        the already formatted input.
-        """
-        channelkey, msg = self.args
-        caller = self.caller
-        channel = ChannelDB.objects.get_channel(channelkey)
-
-        # Check that the channel exists
-        if not channel:
-            self.msg(_("Channel '%s' not found.") % channelkey)
-            return
-
-        # Check that the caller is connected
-        if not channel.has_connection(caller):
-            string = "You are not connected to channel '%s'."
-            self.msg(string % channelkey)
-            return
-
-        # Check that the caller has send access
-        if not channel.access(caller, 'send'):
-            string = "You are not permitted to send to channel '%s'."
-            self.msg(string % channelkey)
-            return
-
-        # Handle the various switches
-        if self.switch == "me":
-            if not msg:
-                self.msg("What do you want to do on this channel?")
-            else:
-                msg = "{} {}".format(caller.key, msg)
-                channel.msg(msg, online=True)
-        elif self.switch:
-            self.msg("{}: Invalid switch {}.".format(channel.key, self.switch))
-        elif not msg:
-            self.msg("Say what?")
-        else:
-            if caller in channel.mutelist:
-                self.msg("You currently have %s muted." % channel)
-                return
-            channel.msg(msg, senders=self.caller, online=True)
-
-
-
    -
  • First of all, we try to get the channel object from the channel name we have -in the self.args tuple. We use ChannelDB.objects.get_channel this time -because we know the channel name isn’t an alias (that was part of the deal, -channelname in the parse method contains a command key).

  • -
  • We check that the channel does exist.

  • -
  • We then check that the caller is connected to the channel. Remember, if the -caller isn’t connected, we shouldn’t allow him to use this command (that -includes the switches on channels).

  • -
  • We then check that the caller has access to the channel’s send lock. This -time, we make sure the caller can send messages to the channel, no matter what -operation he’s trying to perform.

  • -
  • Finally we handle switches. We try only one switch: me. This switch would -be used if a player entered public/me jumps up and down (to do a channel -emote).

  • -
  • We handle the case where the switch is unknown and where there’s no switch -(the player simply wants to talk on this channel).

  • -
-

The good news: The code is not too complicated by itself. The bad news is that -this is just an abridged version of the code. If you want to handle all the -switches mentioned in the command help, you will have more code to write. This -is left as an exercise.

-
-
-

End of class

-

It’s almost done, but we need to add a method in this command class that isn’t -often used. I won’t detail it’s usage too much, just know that Evennia will use -it and will get angry if you don’t add it. So at the end of your class, just -add:

-
# ...
-    def get_extra_info(self, caller, **kwargs):
-        """
-        Let users know that this command is for communicating on a channel.
-
-        Args:
-            caller (TypedObject): A Character or Account who has entered an ambiguous command.
-
-        Returns:
-            A string with identifying information to disambiguate the object, conventionally with a
-preceding space.
-        """
-        return " (channel)"
-
-
-
-
-

Adding this channel command

-

Contrary to most Evennia commands, we won’t add our ChannelCommand to a -CmdSet. Instead we need to tell Evennia that it should use the command we -just created instead of its default channel-command.

-

In your server/conf/settings.py file, add a new setting:

-
# Channel options
-CHANNEL_COMMAND_CLASS = "commands.comms.ChannelCommand"
-
-
-

Then you can reload your game. Try to type public hello and public/me jumps up and down. Don’t forget to enter help public to see if your command has -truly been added.

-
-
-
-

Conclusion and full code

-

That was some adventure! And there’s still things to do! But hopefully, this -tutorial will have helped you in designing your own channel system. Here are a -few things to do:

-
    -
  • Add more switches to handle various actions, like changing the description of -a channel for instance, or listing the connected participants.

  • -
  • Remove the default Evennia commands to handle channels.

  • -
  • Alter the behavior of the channel system so it better aligns with what you -want to do.

  • -
-

As a special bonus, you can find a full, working example of a communication -system similar to the one I’ve shown you: this is a working example, it -integrates all switches and does ever some extra checking, but it’s also very -close from the code I’ve provided here. Notice, however, that this resource is -external to Evennia and not maintained by anyone but the original author of -this article.

-

Read the full example on Github

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Debugging.html b/docs/0.9.5/Debugging.html deleted file mode 100644 index 88a61c98d4..0000000000 --- a/docs/0.9.5/Debugging.html +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - - Debugging — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Debugging

-

Sometimes, an error is not trivial to resolve. A few simple print statements is not enough to find -the cause of the issue. Running a debugger can then be very helpful and save a lot of time. -Debugging -means running Evennia under control of a special debugger program. This allows you to stop the -action at a given point, view the current state and step forward through the program to see how its -logic works.

-

Evennia natively supports these debuggers:

-
    -
  • Pdb is a part of the Python distribution and -available out-of-the-box.

  • -
  • PuDB is a third-party debugger that has a slightly more -‘graphical’, curses-based user interface than pdb. It is installed with pip install pudb.

  • -
-
-

Debugging Evennia

-

To run Evennia with the debugger, follow these steps:

-
    -
  1. Find the point in the code where you want to have more insight. Add the following line at that -point.

    -
    from evennia import set_trace;set_trace()
    -
    -
    -
  2. -
  3. (Re-)start Evennia in interactive (foreground) mode with evennia istart. This is important - -without this step the debugger will not start correctly - it will start in this interactive -terminal.

  4. -
  5. Perform the steps that will trigger the line where you added the set_trace() call. The debugger -will start in the terminal from which Evennia was interactively started.

  6. -
-

The evennia.set_trace function takes the following arguments:

-
    evennia.set_trace(debugger='auto', term_size=(140, 40))
-
-
-

Here, debugger is one of pdb, pudb or auto. If auto, use pudb if available, otherwise -use pdb. The term_size tuple sets the viewport size for pudb only (it’s ignored by pdb).

-
-
-

A simple example using pdb

-

The debugger is useful in different cases, but to begin with, let’s see it working in a command. -Add the following test command (which has a range of deliberate errors) and also add it to your -default cmdset. Then restart Evennia in interactive mode with evennia istart.

-
# In file commands/command.py
-
-
-class CmdTest(Command):
-
-    """
-    A test command just to test pdb.
-
-    Usage:
-        test
-
-    """
-
-    key = "test"
-
-    def func(self):
-        from evennia import set_trace; set_trace()   # <--- start of debugger
-        obj = self.search(self.args)
-        self.msg("You've found {}.".format(obj.get_display_name()))
-
-
-
-

If you type test in your game, everything will freeze. You won’t get any feedback from the game, -and you won’t be able to enter any command (nor anyone else). It’s because the debugger has started -in your console, and you will find it here. Below is an example with pdb.

-
...
-> .../mygame/commands/command.py(79)func()
--> obj = self.search(self.args)
-(Pdb)
-
-
-
-

pdb notes where it has stopped execution and, what line is about to be executed (in our case, obj = self.search(self.args)), and ask what you would like to do.

-
-

Listing surrounding lines of code

-

When you have the pdb prompt (Pdb), you can type in different commands to explore the code. The -first one you should know is list (you can type l for short):

-
(Pdb) l
- 43
- 44         key = "test"
- 45
- 46         def func(self):
- 47             from evennia import set_trace; set_trace()   # <--- start of debugger
- 48  ->         obj = self.search(self.args)
- 49             self.msg("You've found {}.".format(obj.get_display_name()))
- 50
- 51     # -------------------------------------------------------------
- 52     #
- 53     # The default commands inherit from
-(Pdb)
-
-
-

Okay, this didn’t do anything spectacular, but when you become more confident with pdb and find -yourself in lots of different files, you sometimes need to see what’s around in code. Notice that -there is a little arrow (->) before the line that is about to be executed.

-

This is important: about to be, not has just been. You need to tell pdb to go on (we’ll -soon see how).

-
-
-

Examining variables

-

pdb allows you to examine variables (or really, to run any Python instruction). It is very useful -to know the values of variables at a specific line. To see a variable, just type its name (as if -you were in the Python interpreter:

-
(Pdb) self
-<commands.command.CmdTest object at 0x045A0990>
-(Pdb) self.args
-u''
-(Pdb) self.caller
-<Character: XXX>
-(Pdb)
-
-
-

If you try to see the variable obj, you’ll get an error:

-
(Pdb) obj
-*** NameError: name 'obj' is not defined
-(Pdb)
-
-
-

That figures, since at this point, we haven’t created the variable yet.

-
-

Examining variable in this way is quite powerful. You can even run Python code and keep on -executing, which can help to check that your fix is actually working when you have identified an -error. If you have variable names that will conflict with pdb commands (like a list -variable), you can prefix your variable with !, to tell pdb that what follows is Python code.

-
-
-
-

Executing the current line

-

It’s time we asked pdb to execute the current line. To do so, use the next command. You can -shorten it by just typing n:

-
(Pdb) n
-AttributeError: "'CmdTest' object has no attribute 'search'"
-> .../mygame/commands/command.py(79)func()
--> obj = self.search(self.args)
-(Pdb)
-
-
-

Pdb is complaining that you try to call the search method on a command… whereas there’s no -search method on commands. The character executing the command is in self.caller, so we might -change our line:

-
obj = self.caller.search(self.args)
-
-
-
-
-

Letting the program run

-

pdb is waiting to execute the same instruction… it provoked an error but it’s ready to try -again, just in case. We have fixed it in theory, but we need to reload, so we need to enter a -command. To tell pdb to terminate and keep on running the program, use the continue (or c) -command:

-
(Pdb) c
-...
-
-
-

You see an error being caught, that’s the error we have fixed… or hope to have. Let’s reload the -game and try again. You need to run evennia istart again and then run test to get into the -command again.

-
> .../mygame/commands/command.py(79)func()
--> obj = self.caller.search(self.args)
-(Pdb)
-
-
-
-

pdb is about to run the line again.

-
(Pdb) n
-> .../mygame/commands/command.py(80)func()
--> self.msg("You've found {}.".format(obj.get_display_name()))
-(Pdb)
-
-
-

This time the line ran without error. Let’s see what is in the obj variable:

-
(Pdb) obj
-(Pdb) print obj
-None
-(Pdb)
-
-
-

We have entered the test command without parameter, so no object could be found in the search -(self.args is an empty string).

-

Let’s allow the command to continue and try to use an object name as parameter (although, we should -fix that bug too, it would be better):

-
(Pdb) c
-...
-
-
-

Notice that you’ll have an error in the game this time. Let’s try with a valid parameter. I have -another character, barkeep, in this room:

-

test barkeep

-

And again, the command freezes, and we have the debugger opened in the console.

-

Let’s execute this line right away:

-
> .../mygame/commands/command.py(79)func()
--> obj = self.caller.search(self.args)
-(Pdb) n
-> .../mygame/commands/command.py(80)func()
--> self.msg("You've found {}.".format(obj.get_display_name()))
-(Pdb) obj
-<Character: barkeep>
-(Pdb)
-
-
-

At least this time we have found the object. Let’s process…

-
(Pdb) n
-TypeError: 'get_display_name() takes exactly 2 arguments (1 given)'
-> .../mygame/commands/command.py(80)func()
--> self.msg("You've found {}.".format(obj.get_display_name()))
-(Pdb)
-
-
-

As an exercise, fix this error, reload and run the debugger again. Nothing better than some -experimenting!

-

Your debugging will often follow the same strategy:

-
    -
  1. Receive an error you don’t understand.

  2. -
  3. Put a breaking point BEFORE the error occurs.

  4. -
  5. Run the code again and see the debugger open.

  6. -
  7. Run the program line by line,examining variables, checking the logic of instructions.

  8. -
  9. Continue and try again, each step a bit further toward the truth and the working feature.

  10. -
-
-
-

Stepping through a function

-

n is useful, but it will avoid stepping inside of functions if it can. But most of the time, when -we have an error we don’t understand, it’s because we use functions or methods in a way that wasn’t -intended by the developer of the API. Perhaps using wrong arguments, or calling the function in a -situation that would cause a bug. When we have a line in the debugger that calls a function or -method, we can “step” to examine it further. For instance, in the previous example, when pdb was -about to execute obj = self.caller.search(self.args), we may want to see what happens inside of -the search method.

-

To do so, use the step (or s) command. This command will show you the definition of the -function/method and you can then use n as before to see it line-by-line. In our little example, -stepping through a function or method isn’t that useful, but when you have an impressive set of -commands, functions and so on, it might really be handy to examine some feature and make sure they -operate as planned.

-
-
-
-

Cheat-sheet of pdb/pudb commands

-

PuDB and Pdb share the same commands. The only real difference is how it’s presented. The look -command is not needed much in pudb since it displays the code directly in its user interface.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Pdb/PuDB command

To do what

list (or l)

List the lines around the point of execution (not needed for pudb, it will show

this directly).

print (or p)

Display one or several variables.

!

Run Python code (using a ! is often optional).

continue (or c)

Continue execution and terminate the debugger for this time.

next (or n)

Execute the current line and goes to the next one.

step (or s)

Step inside of a function or method to examine it.

<RETURN>

Repeat the last command (don’t type n repeatedly, just type it once and then press

<RETURN> to repeat it).

-

If you want to learn more about debugging with Pdb, you will find an interesting tutorial on that -topic here.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Default-Commands.html b/docs/0.9.5/Default-Commands.html deleted file mode 100644 index c62c9043cd..0000000000 --- a/docs/0.9.5/Default-Commands.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - Default Commands — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Default Commands

-

The full set of default Evennia commands currently contains 98 commands in 9 source -files. Our policy for adding default commands is outlined here. The -Commands documentation explains how Commands work as well as make new or customize -existing ones. Note that this page is auto-generated. Report problems to the issue -tracker.

-
-

Note

-

Some game-states adds their own Commands which are not listed here. Examples include editing a text -with EvEditor, flipping pages in EvMore or using the -Batch-Processor’s interactive mode.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Default-Exit-Errors.html b/docs/0.9.5/Default-Exit-Errors.html deleted file mode 100644 index f1e4bd1ef9..0000000000 --- a/docs/0.9.5/Default-Exit-Errors.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - - Default Exit Errors — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Default Exit Errors

-

Evennia allows for exits to have any name. The command “kitchen” is a valid exit name as well as -“jump out the window” or “north”. An exit actually consists of two parts: an Exit Object -and an Exit Command stored on said exit object. The command has the same key and aliases -as the object, which is why you can see the exit in the room and just write its name to traverse it.

-

If you try to enter the name of a non-existing exit, it is thus the same as trying a non-exising -command; Evennia doesn’t care about the difference:

-
 > jump out the window
- Command 'jump out the window' is not available. Type "help" for help.
-
-
-

Many games don’t need this type of freedom however. They define only the cardinal directions as -valid exit names (Evennia’s @tunnel command also offers this functionality). In this case, the -error starts to look less logical:

-
 > west
- Command 'west' is not available. Maybe you meant "@set" or "@reset"?
-
-
-

Since we for our particular game know that west is an exit direction, it would be better if the -error message just told us that we couldn’t go there.

-
-

Adding default error commands

-

To solve this you need to be aware of how to write and add new commands. -What you need to do is to create new commands for all directions you want to support in your game. -In this example all we’ll do is echo an error message, but you could certainly consider more -advanced uses. You add these commands to the default command set. Here is an example of such a set -of commands:

-
# for example in a file mygame/commands/movecommands.py
-
-from evennia import default_cmds
-
-class CmdExitError(default_cmds.MuxCommand):
-    "Parent class for all exit-errors."
-    locks = "cmd:all()"
-    arg_regex = r"\s|$"
-    auto_help = False
-    def func(self):
-        "returns the error"
-        self.caller.msg("You cannot move %s." % self.key)
-
-class CmdExitErrorNorth(CmdExitError):
-    key = "north"
-    aliases = ["n"]
-
-class CmdExitErrorEast(CmdExitError):
-    key = "east"
-    aliases = ["e"]
-
-class CmdExitErrorSouth(CmdExitError):
-    key = "south"
-    aliases = ["s"]
-
-class CmdExitErrorWest(CmdExitError):
-    key = "west"
-    aliases = ["w"]
-
-
-

Make sure to add the directional commands (not their parent) to the CharacterCmdSet class in -mygame/commands/default_cmdsets.py:

-
# in mygame/commands/default_cmdsets.py
-
-from commands import movecommands
-
-# [...]
-class CharacterCmdSet(default_cmds.CharacterCmdSet):
-    # [...]
-    def at_cmdset_creation(self):
-        # [...]
-        self.add(movecommands.CmdExitErrorNorth())
-        self.add(movecommands.CmdExitErrorEast())
-        self.add(movecommands.CmdExitErrorSouth())
-        self.add(movecommands.CmdExitErrorWest())
-
-
-

After a @reload these commands (assuming you don’t get any errors - check your log) will be -loaded. What happens henceforth is that if you are in a room with an Exitobject (let’s say it’s -“north”), the proper Exit-command will overload your error command (also named “north”). But if you -enter an direction without having a matching exit for it, you will fallback to your default error -commands:

-
 > east
- You cannot move east.
-
-
-

Further expansions by the exit system (including manipulating the way the Exit command itself is -created) can be done by modifying the Exit typeclass directly.

-
-
-

Additional Comments

-

So why didn’t we create a single error command above? Something like this:

-
     class CmdExitError(default_cmds.MuxCommand):
-        "Handles all exit-errors."
-        key = "error_cmd"
-        aliases = ["north", "n",
-                   "east", "e",
-                   "south", "s",
-                   "west", "w"]
-         #[...]
-
-
-

The anwer is that this would not work and understanding why is important in order to not be -confused when working with commands and command sets.

-

The reason it doesn’t work is because Evennia’s command system compares commands both -by key and by aliases. If either of those match, the two commands are considered identical -as far as cmdset merging system is concerned.

-

So the above example would work fine as long as there were no Exits at all in the room. But what -happens when we enter a room with an exit “north”? The Exit’s cmdset is merged onto the default one, -and since there is an alias match, the system determines our CmdExitError to be identical. It is -thus overloaded by the Exit command (which also correctly defaults to a higher priority). The result -is that you can go through the north exit normally but none of the error messages for the other -directions are available since the single error command was completely overloaded by the single -matching “north” exit-command.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Developer-Central.html b/docs/0.9.5/Developer-Central.html deleted file mode 100644 index 53b28c2e42..0000000000 --- a/docs/0.9.5/Developer-Central.html +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - - Developer Central — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - -
-
-
-
- -
-

Developer Central

-

This page serves as a central nexus for information on using Evennia as well as developing the -library itself.

-
-

General Evennia development information

- -
-
-

Evennia API

- -
-

Core components and protocols

- -
-
-

In-game Commands

- -
- -
-

Web

- -
-
-

Other systems

- -
-
-
-

Developer brainstorms and whitepages

-
    -
  • API refactoring, discussing what parts of the Evennia API needs a -refactoring/cleanup/simplification

  • -
  • Docs refactoring, discussing how to reorganize and structure this wiki/docs -better going forward

  • -
  • Webclient brainstorm, some ideas for a future webclient gui

  • -
  • Roadmap, a tentative list of future major features

  • -
  • Change log of big Evennia updates -over time

  • -
-

zY1RoZGc6MQ#gid=0 -[issues]: https://github.com/evennia/evennia/issues

-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Dialogues-in-events.html b/docs/0.9.5/Dialogues-in-events.html deleted file mode 100644 index 18f4293243..0000000000 --- a/docs/0.9.5/Dialogues-in-events.html +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - - - Dialogues in events — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Dialogues in events

-
    -
  • Next tutorial: [adding a voice-operated elevator with events](A-voice-operated-elevator-using- -events).

  • -
-

This tutorial will walk you through the steps to create several dialogues with characters, using the -in-game Python -system. -This tutorial assumes the in-game Python system is installed in your game. If it isn’t, you can -follow the installation steps given in the documentation on in-game -Python, and -come back on this tutorial once the system is installed. You do not need to read the entire -documentation, it’s a good reference, but not the easiest way to learn about it. Hence these -tutorials.

-

The in-game Python system allows to run code on individual objects in some situations. You don’t -have to modify the source code to add these features, past the installation. The entire system -makes it easy to add specific features to some objects, but not all. This is why it can be very -useful to create a dialogue system taking advantage of the in-game Python system.

-
-

What will we try to do?

-
-

In this tutorial, we are going to create a basic dialogue to have several characters automatically -respond to specific messages said by others.

-
-

A first example with a first character

-

Let’s create a character to begin with.

-
@charcreate a merchant
-
-
-

This will create a merchant in the room where you currently are. It doesn’t have anything, like a -description, you can decorate it a bit if you like.

-

As said above, the in-game Python system consists in linking objects with arbitrary code. This code -will be executed in some circumstances. Here, the circumstance is “when someone says something in -the same room”, and might be more specific like “when someone says hello”. We’ll decide what code -to run (we’ll actually type the code in-game). Using the vocabulary of the in-game Python system, -we’ll create a callback: a callback is just a set of lines of code that will run under some -conditions.

-

You can have an overview of every “conditions” in which callbacks can be created using the @call -command (short for @callback). You need to give it an object as argument. Here for instance, we -could do:

-
@call a merchant
-
-
-

You should see a table with three columns, showing the list of events existing on our newly-created -merchant. There are quite a lot of them, as it is, althougn no line of code has been set yet. For -our system, you might be more interested by the line describing the say event:

-
| say              |   0 (0) | After another character has said something in |
-|                  |         | the character's room.                         |
-
-
-

We’ll create a callback on the say event, called when we say “hello” in the merchant’s room:

-
@call/add a merchant = say hello
-
-
-

Before seeing what this command displays, let’s see the command syntax itself:

-
    -
  • @call is the command name, /add is a switch. You can read the help of the command to get the -help of available switches and a brief overview of syntax.

  • -
  • We then enter the object’s name, here “a merchant”. You can enter the ID too (“#3” in my case), -which is useful to edit the object when you’re not in the same room. You can even enter part of the -name, as usual.

  • -
  • An equal sign, a simple separator.

  • -
  • The event’s name. Here, it’s “say”. The available events are displayed when you use @call -without switch.

  • -
  • After a space, we enter the conditions in which this callback should be called. Here, the -conditions represent what the other character should say. We enter “hello”. Meaning that if -someone says something containing “hello” in the room, the callback we are now creating will be -called.

  • -
-

When you enter this command, you should see something like this:

-
After another character has said something in the character's room.
-This event is called right after another character has said
-something in the same location.  The action cannot be prevented
-at this moment.  Instead, this event is ideal to create keywords
-that would trigger a character (like a NPC) in doing something
-if a specific phrase is spoken in the same location.
-
-To use this event, you have to specify a list of keywords as
-parameters that should be present, as separate words, in the
-spoken phrase.  For instance, you can set a callback that would
-fire if the phrase spoken by the character contains "menu" or
-"dinner" or "lunch":
-    @call/add ... = say menu, dinner, lunch
-Then if one of the words is present in what the character says,
-this callback will fire.
-
-Variables you can use in this event:
-    speaker: the character speaking in this room.
-    character: the character connected to this event.
-    message: the text having been spoken by the character.
-
-
-

That’s some list of information. What’s most important to us now is:

-
    -
  • The “say” event is called whenever someone else speaks in the room.

  • -
  • We can set callbacks to fire when specific keywords are present in the phrase by putting them as -additional parameters. Here we have set this parameter to “hello”. We can have several keywords -separated by a comma (we’ll see this in more details later).

  • -
  • We have three default variables we can use in this callback: speaker which contains the -character who speaks, character which contains the character who’s modified by the in-game Python -system (here, or merchant), and message which contains the spoken phrase.

  • -
-

This concept of variables is important. If it makes things more simple to you, think of them as -parameters in a function: they can be used inside of the function body because they have been set -when the function was called.

-

This command has opened an editor where we can type our Python code.

-
----------Line Editor [Callback say of a merchant]--------------------------------
-01|
-----------[l:01 w:000 c:0000]------------(:h for help)----------------------------
-
-
-

For our first test, let’s type something like:

-
character.location.msg_contents("{character} shrugs and says: 'well, yes, hello to you!'",
-mapping=dict(character=character))
-
-
-

Once you have entered this line, you can type :wq to save the editor and quit it.

-

And now if you use the “say” command with a message containing “hello”:

-
You say, "Hello sir merchant!"
-a merchant(#3) shrugs and says: 'well, yes, hello to you!'
-
-
-

If you say something that doesn’t contain “hello”, our callback won’t execute.

-

In summary:

-
    -
  1. When we say something in the room, using the “say” command, the “say” event of all characters -(except us) is called.

  2. -
  3. The in-game Python system looks at what we have said, and checks whether one of our callbacks in -the “say” event contains a keyword that we have spoken.

  4. -
  5. If so, call it, defining the event variables as we have seen.

  6. -
  7. The callback is then executed as normal Python code. Here we have called the msg_contents -method on the character’s location (probably a room) to display a message to the entire room. We -have also used mapping to easily display the character’s name. This is not specific to the in-game -Python system. If you feel overwhelmed by the code we’ve used, just shorten it and use something -more simple, for instance:

  8. -
-
speaker.msg("You have said something to me.")
-
-
-
-
-

The same callback for several keywords

-

It’s easy to create a callback that will be triggered if the sentence contains one of several -keywords.

-
@call/add merchant = say trade, trader, goods
-
-
-

And in the editor that opens:

-
character.location.msg_contents("{character} says: 'Ho well, trade's fine as long as roads are
-safe.'", mapping=dict(character=character))
-
-
-

Then you can say something with either “trade”, “trader” or “goods” in your sentence, which should -call the callback:

-
You say, "and how is your trade going?"
-a merchant(#3) says: 'Ho well, trade's fine as long as roads are safe.'
-
-
-

We can set several keywords when adding the callback. We just need to separate them with commas.

-
-
-

A longer callback

-

So far, we have only set one line in our callbacks. Which is useful, but we often need more. For -an entire dialogue, you might want to do a bit more than that.

-
@call/add merchant = say bandit, bandits
-
-
-

And in the editor you can paste the following lines:

-
character.location.msg_contents("{character} says: 'Bandits he?'",
-mapping=dict(character=character))
-character.location.msg_contents("{character} scratches his head, considering.",
-mapping=dict(character=character))
-character.location.msg_contents("{character} whispers: 'Aye, saw some of them, north from here.  No
-trouble o' mine, but...'", mapping=dict(character=character))
-speaker.msg("{character} looks at you more
-closely.".format(character=character.get_display_name(speaker)))
-speaker.msg("{character} continues in a low voice: 'Ain't my place to say, but if you need to find
-'em, they're encamped some distance away from the road, I guess near a cave or
-something.'.".format(character=character.get_display_name(speaker)))
-
-
-

Now try to ask the merchant about bandits:

-
You say, "have you seen bandits?"
-a merchant(#3) says: 'Bandits he?'
-a merchant(#3) scratches his head, considering.
-a merchant(#3) whispers: 'Aye, saw some of them, north from here.  No trouble o' mine, but...'
-a merchant(#3) looks at you more closely.
-a merchant(#3) continues in a low voice: 'Ain't my place to say, but if you need to find 'em,
-they're encamped some distance away from the road, I guess near a cave or something.'.
-
-
-

Notice here that the first lines of dialogue are spoken to the entire room, but then the merchant is -talking directly to the speaker, and only the speaker hears it. There’s no real limit to what you -can do with this.

-
    -
  • You can set a mood system, storing attributes in the NPC itself to tell you in what mood he is, -which will influence the information he will give… perhaps the accuracy of it as well.

  • -
  • You can add random phrases spoken in some context.

  • -
  • You can use other actions (you’re not limited to having the merchant say something, you can ask -him to move, gives you something, attack if you have a combat system, or whatever else).

  • -
  • The callbacks are in pure Python, so you can write conditions or loops.

  • -
  • You can add in “pauses” between some instructions using chained events. This tutorial won’t -describe how to do that however. You already have a lot to play with.

  • -
-
-
-

Tutorial F.A.Q.

-
    -
  • Q: can I create several characters who would answer to specific dialogue?

  • -
  • A: of course. Te in-game Python system is so powerful because you can set unique code for -various objects. You can have several characters answering to different things. You can even have -different characters in the room answering to greetings. All callbacks will be executed one after -another.

  • -
  • Q: can I have two characters answering to the same dialogue in exactly the same way?

  • -
  • A: It’s possible but not so easy to do. Usually, event grouping is set in code, and depends -on different games. However, if it is for some infrequent occurrences, it’s easy to do using -chained events.

  • -
  • Q: is it possible to deploy callbacks on all characters sharing the same prototype?

  • -
  • A: not out of the box. This depends on individual settings in code. One can imagine that all -characters of some type would share some events, but this is game-specific. Rooms of the same zone -could share the same events as well. It is possible to do but requires modification of the source -code.

  • -
  • Next tutorial: [adding a voice-operated elevator with events](A-voice-operated-elevator-using- -events).

  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Directory-Overview.html b/docs/0.9.5/Directory-Overview.html deleted file mode 100644 index 31dbac5961..0000000000 --- a/docs/0.9.5/Directory-Overview.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - Directory Overview — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Directory Overview

-

This is an overview of the directories relevant to Evennia coding.

-
-

The Game directory

-

The game directory is created with evennia --init <name>. In the Evennia documentation we always -assume it’s called mygame. Apart from the server/ subfolder within, you could reorganize this -folder if you preferred a different code structure for your game.

-
    -
  • mygame/

  • -
  • commands/ - Overload default Commands or add your own Commands/Command -sets here.

  • -
  • server/ - The structure of this folder should not change since Evennia expects it.

    -
      -
    • conf/ - All -server configuration files sits here. The most important file is settings.py.

    • -
    • logs/ - Portal log files are stored here (Server is logging to the terminal by default)

    • -
    -
  • -
  • typeclasses/ - this folder contains empty templates for overloading default game entities of -Evennia. Evennia will automatically use the changes in those templates for the game entities it -creates.

  • -
  • web/ - This holds the Web features of your game.

  • -
  • world/ - this is a “miscellaneous” folder holding everything related to the world you are -building, such as build scripts and rules modules that don’t fit with one of the other folders.

  • -
-
-
-

Evennia library layout:

-

If you cloned the GIT repo following the instructions, you will have a folder named evennia. The -top level of it contains Python package specific stuff such as a readme file, setup.py etc. It -also has two subfoldersbin/ and evennia/ (again).

-

The bin/ directory holds OS-specific binaries that will be used when installing Evennia with pip -as per the Getting started instructions. The library itself is in the evennia -subfolder. From your code you will access this subfolder simply by import evennia.

-
    -
  • evennia

    -
      -
    • __init__.py - The “flat API” of Evennia resides here.

    • -
    • commands/ - The command parser and handler.

      - -
    • -
    • comms/ - Systems for communicating in-game.

    • -
    • contrib/ - Optional plugins too game-specific for core Evennia.

    • -
    • game_template/ - Copied to become the “game directory” when using evennia --init.

    • -
    • help/ - Handles the storage and creation of help entries.

    • -
    • locale/ - Language files (i18n).

    • -
    • locks/ - Lock system for restricting access to in-game entities.

    • -
    • objects/ - In-game entities (all types of items and Characters).

    • -
    • prototypes/ - Object Prototype/spawning system and OLC menu

    • -
    • accounts/ - Out-of-game Session-controlled entities (accounts, bots etc)

    • -
    • scripts/ - Out-of-game entities equivalence to Objects, also with timer support.

    • -
    • server/ - Core server code and Session handling.

      -
        -
      • portal/ - Portal proxy and connection protocols.

      • -
      -
    • -
    • settings_default.py - Root settings of Evennia. Copy settings -from here to mygame/server/settings.py file.

    • -
    • typeclasses/ - Abstract classes for the typeclass storage and database system.

    • -
    • utils/ - Various miscellaneous useful coding resources.

    • -
    • web/ - Web resources and webserver. Partly copied into game directory on -initialization.

    • -
    -
  • -
-

All directories contain files ending in .py. These are Python modules and are the basic units of -Python code. The roots of directories also have (usually empty) files named __init__.py. These are -required by Python so as to be able to find and import modules in other directories. When you have -run Evennia at least once you will find that there will also be .pyc files appearing, these are -pre-compiled binary versions of the .py files to speed up execution.

-

The root of the evennia folder has an __init__.py file containing the “flat API”. -This holds shortcuts to various subfolders in the evennia library. It is provided to make it easier -to find things; it allows you to just import evennia and access things from that rather than -having to import from their actual locations inside the source tree.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Docs-refactoring.html b/docs/0.9.5/Docs-refactoring.html deleted file mode 100644 index 72d7f61afb..0000000000 --- a/docs/0.9.5/Docs-refactoring.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - Docs refactoring — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Docs refactoring

-

This is a whitepage for free discussion about the wiki docs and refactorings needed.

-

Note that this is not a forum. To keep things clean, each opinion text should ideally present a -clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be -held in chat or forum.

-
-

Griatch (Aug 13, 2019)

-

This is how to make a discussion entry for the whitepage. Use any markdown formatting you need. Also -remember to copy your work to the clipboard before saving the page since if someone else edited the -page since you started, you’ll have to reload and write again.

-
-

(Sept 23, 2019)

-

This (now closed) issue by DamnedScholar gives the -following suggestion:

-
-

I think it would be useful for the pages that explain how to use various features of Evennia to -have explicit and easily visible links to the respective API entry or entries. Some pages do, but -not all. I imagine this as a single entry at the top of the page […].

-
-

This (now closed) issue by taladan gives the -following suggestion:

-
-

It would help me (and probably a couple of others) if there is a way to show the file path where a -particular thing exists. Maybe up under the ‘last edited’ line we could have a line like: -evennia/locks/lockhandler.py

-
-

This would help in development to quickly refer to where a resource is located.

-
-
-
-

Kovitikus (Sept. 11, 2019)

-

Batch Code should have a link in the developer area. It is currently only -listed in the tutorials section as an afterthought to a tutorial title.

-
-

In regards to the general structure of each wiki page: I’d like to see a table of contents at the -top of each one, so that it can be quickly navigated and is immediately apparent what sections are -covered on the page. Similar to the current Getting Started page.

-
-

The structuring of the page should also include a quick reference cheatsheet for certain aspects. -Such as Tags including a quick reference section at the top that lists an example of every -available method you can use in a clear and consistent format, along with a comment. Readers -shouldn’t have to decipher the article to gather such basic information and it should instead be -available at first glance.

-

Example of a quick reference:

-

Tags

-
# Add a tag.
-obj.tags.add("label")
-
-# Remove a tag.
-obj.tags.remove("label")
-
-# Remove all tags.
-obj.tags.clear()
-
-# Search for a tag. Evennia must be imported first.
-store_result = evennia.search_tag("label")
-
-# Return a list of all tags.
-obj.tags.all()
-
-
-

Aliases

-
# Add an alias.
-obj.aliases.add("label")
-
-ETC...
-
-
-
-

In regards to comment structure, I often find that smushing together lines with comments to be too -obscure. White space should be used to clearly delineate what information the comment is for. I -understand that the current format is that a comment references whatever is below it, but newbies -may not know that until they realize it.

-

Example of poor formatting:

-
#comment
-command/code
-#comment
-command/code
-
-
-

Example of good formatting:

-
# Comment.
-command/code
-
-# Comment.
-command/code
-
-
-
-
-

Sage (3/28/20)

-

If I want to find information on the correct syntax for is_typeclass(), here’s what I do:

-
    -
  • Pop over to the wiki. Okay, this is a developer functionality. Let’s try that.

  • -
  • Ctrl+F on Developer page. No results.

  • -
  • Ctrl+F on API page. No results. Ctrl+F on Flat API page. No results

  • -
  • Ctrl+F on utils page. No results.

  • -
  • Ctrl+F on utils.utils page. No results.

  • -
  • Ctrl+F in my IDE. Results.

  • -
  • Fortunately, there’s only one result for def is_typeclass. If this was at_look, there would be -several results, and I’d have to go through each of those individually, and most of them would just -call return_appearance

  • -
-

An important part of a refactor, in my opinion, is separating out the “Tutorials” from the -“Reference” documentation.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Dynamic-In-Game-Map.html b/docs/0.9.5/Dynamic-In-Game-Map.html deleted file mode 100644 index 7bb0850309..0000000000 --- a/docs/0.9.5/Dynamic-In-Game-Map.html +++ /dev/null @@ -1,582 +0,0 @@ - - - - - - - - - Dynamic In Game Map — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Dynamic In Game Map

-
-

Introduction

-

An often desired feature in a MUD is to show an in-game map to help navigation. The Static in-game -map tutorial solves this by creating a static map, meaning the map is pre- -drawn once and for all - the rooms are then created to match that map. When walking around, parts of -the static map is then cut out and displayed next to the room description.

-

In this tutorial we’ll instead do it the other way around; We will dynamically draw the map based on -the relationships we find between already existing rooms.

-
-
-

The Grid of Rooms

-

There are at least two requirements needed for this tutorial to work.

-
    -
  1. The structure of your mud has to follow a logical layout. Evennia supports the layout of your -world to be ‘logically’ impossible with rooms looping to themselves or exits leading to the other -side of the map. Exits can also be named anything, from “jumping out the window” to “into the fifth -dimension”. This tutorial assumes you can only move in the cardinal directions (N, E, S and W).

  2. -
  3. Rooms must be connected and linked together for the map to be generated correctly. Vanilla -Evennia comes with a admin command tunnel that allows a -user to create rooms in the cardinal directions, but additional work is needed to assure that rooms -are connected. For example, if you tunnel east and then immediately do tunnel west you’ll find -that you have created two completely stand-alone rooms. So care is needed if you want to create a -“logical” layout. In this tutorial we assume you have such a grid of rooms that we can generate the -map from.

  4. -
-
-
-

Concept

-

Before getting into the code, it is beneficial to understand and conceptualize how this is going to -work. The idea is analogous to a worm that starts at your current position. It chooses a direction -and ‘walks’ outward from it, mapping its route as it goes. Once it has traveled a pre-set distance -it stops and starts over in another direction. An important note is that we want a system which is -easily callable and not too complicated. Therefore we will wrap this entire code into a custom -Python class (not a typeclass as this doesn’t use any core objects from evennia itself).

-

We are going to create something that displays like this when you type ‘look’:

-
    Hallway
-
-          [.]   [.]
-          [@][.][.][.][.]
-          [.]   [.]   [.]
-
-    The distant echoes of the forgotten
-    wail throughout the empty halls.
-
-    Exits: North, East, South
-
-
-

Your current location is defined by [@] while the [.]s are other rooms that the “worm” has seen -since departing from your location.

-
-
-

Setting up the Map Display

-

First we must define the components for displaying the map. For the “worm” to know what symbol to -draw on the map we will have it check an Attribute on the room it visits called sector_type. For -this tutorial we understand two symbols - a normal room and the room with us in it. We also define a -fallback symbol for rooms without said Attribute - that way the map will still work even if we -didn’t prepare the room correctly. Assuming your game folder is named mygame, we create this code -in mygame/world/map.py.

-
# in mygame/world/map.py
-
-# the symbol is identified with a key "sector_type" on the
-# Room. Keys None and "you" must always exist.
-SYMBOLS = { None : ' . ', # for rooms without sector_type Attribute
-            'you' : '[@]',
-            'SECT_INSIDE': '[.]' }
-
-
-

Since trying to access an unset Attribute returns None, this means rooms without the sector_type -Atttribute will show as .. Next we start building the custom class Map. It will hold all -methods we need.

-
# in mygame/world/map.py
-
-class Map(object):
-
-    def __init__(self, caller, max_width=9, max_length=9):
-        self.caller = caller
-        self.max_width = max_width
-        self.max_length = max_length
-        self.worm_has_mapped = {}
-        self.curX = None
-        self.curY = None
-
-
-
    -
  • self.caller is normally your Character object, the one using the map.

  • -
  • self.max_width/length determine the max width and length of the map that will be generated. Note -that it’s important that these variables are set to odd numbers to make sure the display area has -a center point.

  • -
  • self.worm_has_mapped is building off the worm analogy above. This dictionary will store all -rooms the “worm” has mapped as well as its relative position within the grid. This is the most -important variable as it acts as a ‘checker’ and ‘address book’ that is able to tell us where the -worm has been and what it has mapped so far.

  • -
  • self.curX/Y are coordinates representing the worm’s current location on the grid.

  • -
-

Before any sort of mapping can actually be done we need to create an empty display area and do some -sanity checks on it by using the following methods.

-
# in mygame/world/map.py
-
-class Map(object):
-    # [... continued]
-
-    def create_grid(self):
-        # This method simply creates an empty grid/display area
-        # with the specified variables from __init__(self):
-        board = []
-        for row in range(self.max_width):
-            board.append([])
-            for column in range(self.max_length):
-                board[row].append('   ')
-        return board
-
-    def check_grid(self):
-        # this method simply checks the grid to make sure
-        # that both max_l and max_w are odd numbers.
-        return True if self.max_length % 2 != 0 or self.max_width % 2 != 0\
-            else False
-
-
-

Before we can set our worm on its way, we need to know some of the computer science behind all this -called ‘Graph Traversing’. In Pseudo code what we are trying to accomplish is this:

-
# pseudo code
-
-def draw_room_on_map(room, max_distance):
-    self.draw(room)
-
-    if max_distance == 0:
-        return
-
-    for exit in room.exits:
-        if self.has_drawn(exit.destination):
-            # skip drawing if we already visited the destination
-            continue
-        else:
-            # first time here!
-            self.draw_room_on_map(exit.destination, max_distance - 1)
-
-
-

The beauty of Python is that our actual code of doing this doesn’t differ much if at all from this -Pseudo code example.

-
    -
  • max_distance is a variable indicating to our Worm how many rooms AWAY from your current location -will it map. Obviously the larger the number the more time it will take if your current location has -many many rooms around you.

  • -
-

The first hurdle here is what value to use for ‘max_distance’. There is no reason for the worm to -travel further than what is actually displayed to you. For example, if your current location is -placed in the center of a display area of size max_length = max_width = 9, then the worm need only -go 4 spaces in either direction:

-
[.][.][.][.][@][.][.][.][.]
- 4  3  2  1  0  1  2  3  4
-
-
-

The max_distance can be set dynamically based on the size of the display area. As your width/length -changes it becomes a simple algebraic linear relationship which is simply max_distance = (min(max_width, max_length) -1) / 2.

-
-
-

Building the Mapper

-

Now we can start to fill our Map object with some methods. We are still missing a few methods that -are very important:

-
    -
  • self.draw(self, room) - responsible for actually drawing room to grid.

  • -
  • self.has_drawn(self, room) - checks to see if the room has been mapped and worm has already been -here.

  • -
  • self.median(self, number) - a simple utility method that finds the median (middle point) from 0, -n

  • -
  • self.update_pos(self, room, exit_name) - updates the worm’s physical position by reassigning -self.curX/Y. .accordingly

  • -
  • self.start_loc_on_grid(self) - the very first initial draw on the grid representing your -location in the middle of the grid

  • -
  • ‘self.show_map - after everything is done convert the map into a readable string

  • -
  • self.draw_room_on_map(self, room, max_distance) - the main method that ties it all together.`

  • -
-

Now that we know which methods we need, let’s refine our initial __init__(self) to pass some -conditional statements and set it up to start building the display.

-
#mygame/world/map.py
-
-class Map(object):
-
-    def __init__(self, caller, max_width=9, max_length=9):
-        self.caller = caller
-        self.max_width = max_width
-        self.max_length = max_length
-        self.worm_has_mapped = {}
-        self.curX = None
-        self.curY = None
-
-        if self.check_grid():
-            # we have to store the grid into a variable
-            self.grid = self.create_grid()
-            # we use the algebraic relationship
-            self.draw_room_on_map(caller.location,
-                                  ((min(max_width, max_length) -1 ) / 2)
-
-
-
-

Here we check to see if the parameters for the grid are okay, then we create an empty canvas and map -our initial location as the first room!

-

As mentioned above, the code for the self.draw_room_on_map() is not much different than the Pseudo -code. The method is shown below:

-
# in mygame/world/map.py, in the Map class
-
-def draw_room_on_map(self, room, max_distance):
-    self.draw(room)
-
-    if max_distance == 0:
-        return
-
-    for exit in room.exits:
-        if exit.name not in ("north", "east", "west", "south"):
-            # we only map in the cardinal directions. Mapping up/down would be
-            # an interesting learning project for someone who wanted to try it.
-            continue
-        if self.has_drawn(exit.destination):
-            # we've been to the destination already, skip ahead.
-            continue
-
-        self.update_pos(room, exit.name.lower())
-        self.draw_room_on_map(exit.destination, max_distance - 1)
-
-
-

The first thing the “worm” does is to draw your current location in self.draw. Lets define that…

-
#in mygame/word/map.py, in the Map class
-
-def draw(self, room):
-    # draw initial ch location on map first!
-    if room == self.caller.location:
-        self.start_loc_on_grid()
-        self.worm_has_mapped[room] = [self.curX, self.curY]
-    else:
-        # map all other rooms
-        self.worm_has_mapped[room] = [self.curX, self.curY]
-        # this will use the sector_type Attribute or None if not set.
-        self.grid[self.curX][self.curY] = SYMBOLS[room.db.sector_type]
-
-
-

In self.start_loc_on_grid():

-
def median(self, num):
-    lst = sorted(range(0, num))
-    n = len(lst)
-    m = n -1
-    return (lst[n//2] + lst[m//2]) / 2.0
-
-def start_loc_on_grid(self):
-    x = self.median(self.max_width)
-    y = self.median(self.max_length)
-    # x and y are floats by default, can't index lists with float types
-    x, y = int(x), int(y)
-
-    self.grid[x][y] = SYMBOLS['you']
-    self.curX, self.curY = x, y # updating worms current location
-
-
-

After the system has drawn the current map it checks to see if the max_distance is 0 (since this -is the inital start phase it is not). Now we handle the iteration once we have each individual exit -in the room. The first thing it does is check if the room the Worm is in has been mapped already… -lets define that…

-
def has_drawn(self, room):
-    return True if room in self.worm_has_mapped.keys() else False
-
-
-

If has_drawn returns False that means the worm has found a room that hasn’t been mapped yet. It -will then ‘move’ there. The self.curX/Y sort of lags behind, so we have to make sure to track the -position of the worm; we do this in self.update_pos() below.

-
def update_pos(self, room, exit_name):
-    # this ensures the coordinates stays up to date
-    # to where the worm is currently at.
-    self.curX, self.curY = \
-      self.worm_has_mapped[room][0], self.worm_has_mapped[room][1]
-
-    # now we have to actually move the pointer
-    # variables depending on which 'exit' it found
-    if exit_name == 'east':
-        self.curY += 1
-    elif exit_name == 'west':
-        self.curY -= 1
-    elif exit_name == 'north':
-        self.curX -= 1
-    elif exit_name == 'south':
-        self.curX += 1
-
-
-

Once the system updates the position of the worm it feeds the new room back into the original -draw_room_on_map() and starts the process all over again…

-

That is essentially the entire thing. The final method is to bring it all together and make a nice -presentational string out of it using the self.show_map() method.

-
def show_map(self):
-    map_string = ""
-    for row in self.grid:
-        map_string += " ".join(row)
-        map_string += "\n"
-
-    return map_string
-
-
-
-
-

Using the Map

-

In order for the map to get triggered we store it on the Room typeclass. If we put it in -return_appearance we will get the map back every time we look at the room.

-
-

return_appearance is a default Evennia hook available on all objects; it is called e.g. by the -look command to get the description of something (the room in this case).

-
-
# in mygame/typeclasses/rooms.py
-
-from evennia import DefaultRoom
-from world.map import Map
-
-class Room(DefaultRoom):
-    
-    def return_appearance(self, looker):
-        # [...]
-        string = "%s\n" % Map(looker).show_map()
-        # Add all the normal stuff like room description,
-        # contents, exits etc.
-        string += "\n" + super().return_appearance(looker)
-        return string
-
-
-

Obviously this method of generating maps doesn’t take into account of any doors or exits that are -hidden… etc… but hopefully it serves as a good base to start with. Like previously mentioned, it -is very important to have a solid foundation on rooms before implementing this. You can try this on -vanilla evennia by using @tunnel and essentially you can just create a long straight/edgy non- -looping rooms that will show on your in-game map.

-

The above example will display the map above the room description. You could also use an -EvTable to place description and map next to each other. Some other -things you can do is to have a Command that displays with a larger radius, maybe with a -legend and other features.

-

Below is the whole map.py for your reference. You need to update your Room typeclass (see above) -to actually call it. Remember that to see different symbols for a location you also need to set the -sector_type Attribute on the room to one of the keys in the SYMBOLS dictionary. So in this -example, to make a room be mapped as [.] you would set the room’s sector_type to -"SECT_INSIDE". Try it out with @set here/sector_type = "SECT_INSIDE". If you wanted all new -rooms to have a given sector symbol, you could change the default in the SYMBOLS´ dictionary below, or you could add the Attribute in the Room's at_object_creation` method.

-
#mygame/world/map.py
-
-# These are keys set with the Attribute sector_type on the room.
-# The keys None and "you" must always exist.
-SYMBOLS = { None : ' . ',  # for rooms without a sector_type attr
-            'you' : '[@]',
-            'SECT_INSIDE': '[.]' }
-
-class Map(object):
-
-    def __init__(self, caller, max_width=9, max_length=9):
-        self.caller = caller
-        self.max_width = max_width
-        self.max_length = max_length
-        self.worm_has_mapped = {}
-        self.curX = None
-        self.curY = None
-
-        if self.check_grid():
-            # we actually have to store the grid into a variable
-            self.grid = self.create_grid()
-            self.draw_room_on_map(caller.location,
-                                 ((min(max_width, max_length) -1 ) / 2))
-    
-    def update_pos(self, room, exit_name):
-        # this ensures the pointer variables always
-        # stays up to date to where the worm is currently at.
-        self.curX, self.curY = \
-           self.worm_has_mapped[room][0], self.worm_has_mapped[room][1]
-
-        # now we have to actually move the pointer
-        # variables depending on which 'exit' it found
-        if exit_name == 'east':
-            self.curY += 1
-        elif exit_name == 'west':
-            self.curY -= 1
-        elif exit_name == 'north':
-            self.curX -= 1
-        elif exit_name == 'south':
-            self.curX += 1
-
-    def draw_room_on_map(self, room, max_distance):
-        self.draw(room)
-
-        if max_distance == 0:
-            return
-        
-        for exit in room.exits:
-            if exit.name not in ("north", "east", "west", "south"):
-                # we only map in the cardinal directions. Mapping up/down would be
-                # an interesting learning project for someone who wanted to try it.
-                continue
-            if self.has_drawn(exit.destination):
-                # we've been to the destination already, skip ahead.
-                continue
-
-            self.update_pos(room, exit.name.lower())
-            self.draw_room_on_map(exit.destination, max_distance - 1)
-        
-    def draw(self, room):
-        # draw initial caller location on map first!
-        if room == self.caller.location:
-            self.start_loc_on_grid()
-            self.worm_has_mapped[room] = [self.curX, self.curY]
-        else:
-            # map all other rooms
-            self.worm_has_mapped[room] = [self.curX, self.curY]
-            # this will use the sector_type Attribute or None if not set.
-            self.grid[self.curX][self.curY] = SYMBOLS[room.db.sector_type]
-
-    def median(self, num):
-        lst = sorted(range(0, num))
-        n = len(lst)
-        m = n -1
-        return (lst[n//2] + lst[m//2]) / 2.0
-   
-    def start_loc_on_grid(self):
-        x = self.median(self.max_width)
-        y = self.median(self.max_length)
-        # x and y are floats by default, can't index lists with float types
-        x, y = int(x), int(y)
-
-        self.grid[x][y] = SYMBOLS['you']
-        self.curX, self.curY = x, y # updating worms current location
-     
-
-    def has_drawn(self, room):
-        return True if room in self.worm_has_mapped.keys() else False
-
-
-    def create_grid(self):
-        # This method simply creates an empty grid
-        # with the specified variables from __init__(self):
-        board = []
-        for row in range(self.max_width):
-            board.append([])
-            for column in range(self.max_length):
-                board[row].append('   ')
-        return board
-
-    def check_grid(self):
-        # this method simply checks the grid to make sure
-        # both max_l and max_w are odd numbers
-        return True if self.max_length % 2 != 0 or \
-                    self.max_width % 2 != 0 else False
-
-    def show_map(self):
-        map_string = ""
-        for row in self.grid:
-            map_string += " ".join(row)
-            map_string += "\n"
-
-        return map_string
-
-
-
-
-

Final Comments

-

The Dynamic map could be expanded with further capabilities. For example, it could mark exits or -allow NE, SE etc directions as well. It could have colors for different terrain types. One could -also look into up/down directions and figure out how to display that in a good way.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/EvEditor.html b/docs/0.9.5/EvEditor.html deleted file mode 100644 index 7e88d2d801..0000000000 --- a/docs/0.9.5/EvEditor.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - - - - EvEditor — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

EvEditor

-

Evennia offers a powerful in-game line editor in evennia.utils.eveditor.EvEditor. This editor, -mimicking the well-known VI line editor. It offers line-by-line editing, undo/redo, line deletes, -search/replace, fill, dedent and more.

-
-

Launching the editor

-

The editor is created as follows:

-
from evennia.utils.eveditor import EvEditor
-
-EvEditor(caller,
-         loadfunc=None, savefunc=None, quitfunc=None,
-         key="")
-
-
-
    -
  • caller (Object or Account): The user of the editor.

  • -
  • loadfunc (callable, optional): This is a function called when the editor is first started. It -is called with caller as its only argument. The return value from this function is used as the -starting text in the editor buffer.

  • -
  • savefunc (callable, optional): This is called when the user saves their buffer in the editor is -called with two arguments, caller and buffer, where buffer is the current buffer.

  • -
  • quitfunc (callable, optional): This is called when the user quits the editor. If given, all -cleanup and exit messages to the user must be handled by this function.

  • -
  • key (str, optional): This text will be displayed as an identifier and reminder while editing. -It has no other mechanical function.

  • -
  • persistent (default False): if set to True, the editor will survive a reboot.

  • -
-
-
-

Example of usage

-

This is an example command for setting a specific Attribute using the editor.

-
from evennia import Command
-from evennia.utils import eveditor
-
-class CmdSetTestAttr(Command):
-    """
-    Set the "test" Attribute using
-    the line editor.
-
-    Usage:
-       settestattr
-
-    """
-    key = "settestattr"
-    def func(self):
-        "Set up the callbacks and launch the editor"
-        def load(caller):
-            "get the current value"
-            return caller.attributes.get("test")
-        def save(caller, buffer):
-            "save the buffer"
-            caller.attributes.set("test", buffer)
-        def quit(caller):
-            "Since we define it, we must handle messages"
-            caller.msg("Editor exited")
-        key = "%s/test" % self.caller
-        # launch the editor
-        eveditor.EvEditor(self.caller,
-                          loadfunc=load, savefunc=save, quitfunc=quit,
-                          key=key)
-
-
-
-
-

Persistent editor

-

If you set the persistent keyword to True when creating the editor, it will remain open even -when reloading the game. In order to be persistent, an editor needs to have its callback functions -(loadfunc, savefunc and quitfunc) as top-level functions defined in the module. Since these -functions will be stored, Python will need to find them.

-
from evennia import Command
-from evennia.utils import eveditor
-
-def load(caller):
-    "get the current value"
-    return caller.attributes.get("test")
-
-def save(caller, buffer):
-    "save the buffer"
-    caller.attributes.set("test", buffer)
-
-def quit(caller):
-    "Since we define it, we must handle messages"
-    caller.msg("Editor exited")
-
-class CmdSetTestAttr(Command):
-    """
-    Set the "test" Attribute using
-    the line editor.
-
-    Usage:
-       settestattr
-
-    """
-    key = "settestattr"
-    def func(self):
-        "Set up the callbacks and launch the editor"
-        key = "%s/test" % self.caller
-        # launch the editor
-        eveditor.EvEditor(self.caller,
-                          loadfunc=load, savefunc=save, quitfunc=quit,
-                          key=key, persistent=True)
-
-
-
-
-

Line editor usage

-

The editor mimics the VIM editor as best as possible. The below is an excerpt of the return from -the in-editor help command (:h).

-
 <txt>  - any non-command is appended to the end of the buffer.
- :  <l> - view buffer or only line <l>
- :: <l> - view buffer without line numbers or other parsing
- :::    - print a ':' as the only character on the line...
- :h     - this help.
-
- :w     - save the buffer (don't quit)
- :wq    - save buffer and quit
- :q     - quit (will be asked to save if buffer was changed)
- :q!    - quit without saving, no questions asked
-
- :u     - (undo) step backwards in undo history
- :uu    - (redo) step forward in undo history
- :UU    - reset all changes back to initial state
-
- :dd <l>     - delete line <n>
- :dw <l> <w> - delete word or regex <w> in entire buffer or on line <l>
- :DD         - clear buffer
-
- :y  <l>        - yank (copy) line <l> to the copy buffer
- :x  <l>        - cut line <l> and store it in the copy buffer
- :p  <l>        - put (paste) previously copied line directly after <l>
- :i  <l> <txt>  - insert new text <txt> at line <l>. Old line will move down
- :r  <l> <txt>  - replace line <l> with text <txt>
- :I  <l> <txt>  - insert text at the beginning of line <l>
- :A  <l> <txt>  - append text after the end of line <l>
-
- :s <l> <w> <txt> - search/replace word or regex <w> in buffer or on line <l>
-
- :f <l>    - flood-fill entire buffer or line <l>
- :fi <l>   - indent entire buffer or line <l>
- :fd <l>   - de-indent entire buffer or line <l>
-
- :echo - turn echoing of the input on/off (helpful for some clients)
-
-    Legend:
-    <l> - line numbers, or range lstart:lend, e.g. '3:7'.
-    <w> - one word or several enclosed in quotes.
-    <txt> - longer string, usually not needed to be enclosed in quotes.
-
-
-
-
-

The EvEditor to edit code

-

The EvEditor is also used to edit some Python code in Evennia. The @py command supports an -/edit switch that will open the EvEditor in code mode. This mode isn’t significantly different -from the standard one, except it handles automatic indentation of blocks and a few options to -control this behavior.

-
    -
  • :< to remove a level of indentation for the future lines.

  • -
  • :+ to add a level of indentation for the future lines.

  • -
  • := to disable automatic indentation altogether.

  • -
-

Automatic indentation is there to make code editing more simple. Python needs correct indentation, -not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The -EvEditor will try to guess the next level of indentation. If you type a block “if”, for instance, -the EvEditor will propose you an additional level of indentation at the next line. This feature -cannot be perfect, however, and sometimes, you will have to use the above options to handle -indentation.

-

:= can be used to turn automatic indentation off completely. This can be very useful when trying -to paste several lines of code that are already correctly indented, for instance.

-

To see the EvEditor in code mode, you can use the @py/edit command. Type in your code (on one or -several lines). You can then use the :w option (save without quitting) and the code you have -typed will be executed. The :! will do the same thing. Executing code while not closing the -editor can be useful if you want to test the code you have typed but add new lines after your test.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/EvMenu.html b/docs/0.9.5/EvMenu.html deleted file mode 100644 index 6c6be69859..0000000000 --- a/docs/0.9.5/EvMenu.html +++ /dev/null @@ -1,1368 +0,0 @@ - - - - - - - - - EvMenu — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

EvMenu

-
-

Introduction

-

The EvMenu utility class is located in -evennia/utils/evmenu.py. -It allows for easily adding interactive menus to the game; for example to implement Character -creation, building commands or similar. Below is an example of offering NPC conversation choices:

-
The guard looks at you suspiciously.
-"No one is supposed to be in here ..."
-he says, a hand on his weapon.
-_______________________________________________
- 1. Try to bribe him [Cha + 10 gold]
- 2. Convince him you work here [Int]
- 3. Appeal to his vanity [Cha]
- 4. Try to knock him out [Luck + Dex]
- 5. Try to run away [Dex]
-
-
-
-

This is an example of a menu node. Think of a node as a point where the menu stops printing text -and waits for user to give some input. By jumping to different nodes depending on the input, a menu -is constructed.

-
-
-

Ways to create the menu

-
-

node functions

-

The native way to define an EvMenu is to define Python functions, one per node. It will load all -those -functions/nodes either from a module or by being passed a dictionary mapping the node’s names to -said functions, like {"nodename": <function>, ...}. Since you are dealing with raw code, this is -by -far the most powerful way - for example you could have dynamic nodes that change content depending -on game context, time and what you picked before.

-
- -
-
-

Launching the menu

-

Initializing the menu is done using a call to the evennia.utils.evmenu.EvMenu class. This is the -most common way to do so - from inside a Command:

-
# in, for example gamedir/commands/command.py
-
-from evennia.utils.evmenu import EvMenu
-
-class CmdTestMenu(Command):
-
-    key = "testcommand"
-
-    def func(self):
-
-        EvMenu(caller, "world.mymenu")
-
-
-
-

When running this command, the menu will start using the menu nodes loaded from -mygame/world/mymenu.py and use this to build the menu-tree - each function name becomes -the name of a node in the tree. See next section on how to define menu nodes.

-

Alternatively, you could pass the menu-tree to EvMenu directly:

-

-  menutree = {"start": nodestartfunc,
-              "node1": nodefunc1,
-              "node2": nodefunc2,, ...}
-  EvMenu(caller, menutree)
-
-
-
-

This menutree can also be generated from an EvMenu template

-
   from evennia.utils.evmenu import parse_menu_template
-  
-   menutree = parse_menu_template(caller, template_string, goto_callables)
-   EvMenu(caller, menutree)
-
-
-

The template_string and goto_callables are described in Template language -section.

-
-
-

The EvMenu class

-

The EvMenu has the following optional callsign:

-
EvMenu(caller, menu_data,
-       startnode="start",
-       cmdset_mergetype="Replace", cmdset_priority=1,
-       auto_quit=True, auto_look=True, auto_help=True,
-       cmd_on_exit="look",
-       persistent=False,
-       startnode_input="",
-       session=None,
-       debug=False,
-       **kwargs)
-
-
-
-
    -
  • caller (Object or Account): is a reference to the object using the menu. This object will get a -new CmdSet assigned to it, for handling the menu.

  • -
  • menu_data (str, module or dict): is a module or python path to a module where the global-level -functions will each be considered to be a menu node. Their names in the module will be the names -by which they are referred to in the module. Importantly, function names starting with an -underscore -_ will be ignored by the loader. Alternatively, this can be a direct mapping -{"nodename":function, ...}.

  • -
  • startnode (str): is the name of the menu-node to start the menu at. Changing this means that -you can jump into a menu tree at different positions depending on circumstance and thus possibly -re-use menu entries.

  • -
  • cmdset_mergetype (str): This is usually one of “Replace” or “Union” (see [CmdSets](Command- -Sets). -The first means that the menu is exclusive - the user has no access to any other commands while -in the menu. The Union mergetype means the menu co-exists with previous commands (and may -overload -them, so be careful as to what to name your menu entries in this case).

  • -
  • cmdset_priority (int): The priority with which to merge in the menu cmdset. This allows for -advanced usage.

  • -
  • auto_quit, auto_look, auto_help (bool): If either of these are True, the menu -automatically makes a quit, look or help command available to the user. The main reason why -you’d want to turn this off is if you want to use the aliases “q”, “l” or “h” for something in -your -menu. Nevertheless, at least quit is highly recommend - if False, the menu must itself -supply -an “exit node” (a node without any options), or the user will be stuck in the menu until the -server -reloads (or eternally if the menu is persistent)!

  • -
  • cmd_on_exit (str): This command string will be executed right after the menu has closed down. -From experience, it’s useful to trigger a “look” command to make sure the user is aware of the -change of state; but any command can be used. If set to None, no command will be triggered -after -exiting the menu.

  • -
  • persistent (bool) - if True, the menu will survive a reload (so the user will not be kicked -out by the reload - make sure they can exit on their own!)

  • -
  • startnode_input (str or (str, dict) tuple): Pass an input text or a input text + kwargs to the -start node as if it was entered on a fictional previous node. This can be very useful in order to -start a menu differently depending on the Command’s arguments in which it was initialized.

  • -
  • session (Session): Useful when calling the menu from an Account in -MULTISESSION_MODDE higher than 2, to make sure only the right Session sees the menu output.

  • -
  • debug (bool): If set, the menudebug command will be made available in the menu. Use it to -list the current state of the menu and use menudebug <variable> to inspect a specific state -variable from the list.

  • -
  • All other keyword arguments will be available as initial data for the nodes. They will be -available in all nodes as properties on caller.ndb._menutree (see below). These will also -survive a @reload if the menu is persistent.

  • -
-

You don’t need to store the EvMenu instance anywhere - the very act of initializing it will store it -as caller.ndb._menutree on the caller. This object will be deleted automatically when the menu -is exited and you can also use it to store your own temporary variables for access throughout the -menu. Temporary variables you store on a persistent _menutree as it runs will -not survive a @reload, only those you set as part of the original EvMenu call.

-
-
-

The Menu nodes

-

The EvMenu nodes consist of functions on one of these forms.

-
def menunodename1(caller):
-    # code
-    return text, options
-
-def menunodename2(caller, raw_string):
-    # code
-    return text, options
-
-def menunodename3(caller, raw_string, **kwargs):
-    # code
-    return text, options
-
-
-
-
-

While all of the above forms are okay, it’s recommended to stick to the third and last form since -it -gives the most flexibility. The previous forms are mainly there for backwards compatibility with -existing menus from a time when EvMenu was less able.

-
-
-

Input arguments to the node

-
    -
  • caller (Object or Account): The object using the menu - usually a Character but could also be a -Session or Account depending on where the menu is used.

  • -
  • raw_string (str): If this is given, it will be set to the exact text the user entered on the -previous node (that is, the command entered to get to this node). On the starting-node of the -menu, this will be an empty string, unless startnode_input was set.

  • -
  • kwargs (dict): These extra keyword arguments are extra optional arguments passed to the node -when the user makes a choice on the previous node. This may include things like status flags -and details about which exact option was chosen (which can be impossible to determine from -raw_string alone). Just what is passed in kwargs is up to you when you create the previous -node.

  • -
-
-
-

Return values from the node

-

Each function must return two variables, text and options.

-
-

text

-

The text variable is a string or tuple. This text is what will be displayed when the user reaches -this node. If this is a tuple, then the first element of the tuple will be considered the displayed -text and the second the help-text to display when the user enters the help command on this node.

-
    text = ("This is the text to display", "This is the help text for this node")
-
-
-

Returning a None text is allowed and simply leads to a node with no text and only options. If the -help text is not given, the menu will give a generic error message when using help.

-
-
-

options

-

The options list describe all the choices available to the user when viewing this node. If -options is -returned as None, it means that this node is an Exit node - any text is displayed and then the -menu immediately exits, running the exit_cmd if given.

-

Otherwise, options should be a list (or tuple) of dictionaries, one for each option. If only one -option is -available, a single dictionary can also be returned. This is how it could look:

-
def node_test(caller, raw_string, **kwargs):
-
-    text = "A goblin attacks you!"
-
-    options = (
-        {"key": ("Attack", "a", "att"),
-         "desc": "Strike the enemy with all your might",
-         "goto": "node_attack"},
-        {"key": ("Defend", "d", "def"),
-         "desc": "Hold back and defend yourself",
-         "goto": (_defend, {"str": 10, "enemyname": "Goblin"})})
-
-    return text, options
-
-
-
-

This will produce a menu node looking like this:

-
A goblin attacks you!
-________________________________
-
-Attack: Strike the enemy with all your might
-Defend: Hold back and defend yourself
-
-
-
-
-
option-key ‘key’
-

The option’s key is what the user should enter in order to choose that option. If given as a -tuple, the -first string of that tuple will be what is shown on-screen while the rest are aliases for picking -that option. In the above example, the user could enter “Attack” (or “attack”, it’s not -case-sensitive), “a” or “att” in order to attack the goblin. Aliasing is useful for adding custom -coloring to the choice. The first element of the aliasing tuple should then be the colored version, -followed by a version without color - since otherwise the user would have to enter the color codes -to select that choice.

-

Note that the key is optional. If no key is given, it will instead automatically be replaced -with a running number starting from 1. If removing the key part of each option, the resulting -menu node would look like this instead:

-
A goblin attacks you!
-________________________________
-
-1: Strike the enemy with all your might
-2: Hold back and defend yourself
-
-
-
-

Whether you want to use a key or rely on numbers is mostly -a matter of style and the type of menu.

-

EvMenu accepts one important special key given only as "_default". This key is used when a user -enters something that does not match any other fixed keys. It is particularly useful for getting -user input:

-
def node_readuser(caller, raw_string, **kwargs):
-    text = "Please enter your name"
-
-    options = {"key": "_default",
-               "goto": "node_parse_input"}
-
-    return text, options
-
-
-
-

A "_default" option does not show up in the menu, so the above will just be a node saying -"Please enter your name". The name they entered will appear as raw_string in the next node.

-
-
-
-

option-key ‘desc’

-

This simply contains the description as to what happens when selecting the menu option. For -"_default" options or if the key is already long or descriptive, it is not strictly needed. But -usually it’s better to keep the key short and put more detail in desc.

-
-
-

option-key ‘goto’

-

This is the operational part of the option and fires only when the user chooses said option. Here -are three ways to write it

-

-def _action_two(caller, raw_string, **kwargs):
-    # do things ...
-    return "calculated_node_to_go_to"
-
-def _action_three(caller, raw_string, **kwargs):
-    # do things ...
-    return "node_four", {"mode": 4}
-
-def node_select(caller, raw_string, **kwargs):
-
-    text = ("select one",
-            "help - they all do different things ...")
-
-    options = ({"desc": "Option one",
-                            "goto": "node_one"},
-                     {"desc": "Option two",
-                            "goto": _action_two},
-                     {"desc": "Option three",
-                            "goto": (_action_three, {"key": 1, "key2": 2})}
-              )
-
-    return text, options
-
-
-
-

As seen above, goto could just be pointing to a single nodename string - the name of the node to -go to. When given like this, EvMenu will look for a node named like this and call its associated -function as

-
    nodename(caller, raw_string, **kwargs)
-
-
-

Here, raw_string is always the input the user entered to make that choice and kwargs are the -same as those kwargs that already entered the current node (they are passed on).

-

Alternatively the goto could point to a “goto-callable”. Such callables are usually defined in the -same -module as the menu nodes and given names starting with _ (to avoid being parsed as nodes -themselves). These callables will be called the same as a node function - callable(caller, raw_string, **kwargs), where raw_string is what the user entered on this node and **kwargs is -forwarded from the node’s own input.

-

The goto option key could also point to a tuple (callable, kwargs) - this allows for customizing -the kwargs passed into the goto-callable, for example you could use the same callable but change the -kwargs passed into it depending on which option was actually chosen.

-

The “goto callable” must either return a string "nodename" or a tuple ("nodename", mykwargs). -This will lead to the next node being called as either nodename(caller, raw_string, **kwargs) or -nodename(caller, raw_string, **mykwargs) - so this allows changing (or replacing) the options -going -into the next node depending on what option was chosen.

-

There is one important case - if the goto-callable returns None for a nodename, the current -node will run again, possibly with different kwargs. This makes it very easy to re-use a node over -and over, for example allowing different options to update some text form being passed and -manipulated for every iteration.

-
-

The EvMenu also supports the exec option key. This allows for running a callable before the -goto-callable. This functionality comes from a time before goto could be a callable and is -deprecated as of Evennia 0.8. Use goto for all functionality where you’d before use exec.

-
-
-
-
-
-

Temporary storage

-

When the menu starts, the EvMenu instance is stored on the caller as caller.ndb._menutree. Through -this object you can in principle reach the menu’s internal state if you know what you are doing. -This is also a good place to store temporary, more global variables that may be cumbersome to keep -passing from node to node via the **kwargs. The _menutree will be deleted automatically when the -menu closes, meaning you don’t need to worry about cleaning anything up.

-

If you want permanent state storage, it’s instead better to use an Attribute on caller. Remember -that this will remain after the menu closes though, so you need to handle any needed cleanup -yourself.

-
-
-

Customizing Menu formatting

-

The EvMenu display of nodes, options etc are controlled by a series of formatting methods on the -EvMenu class. To customize these, simply create a new child class of EvMenu and override as -needed. Here is an example:

-
from evennia.utils.evmenu import EvMenu
-
-class MyEvMenu(EvMenu):
-
-    def nodetext_formatter(self, nodetext):
-        """
-        Format the node text itself.
-
-        Args:
-            nodetext (str): The full node text (the text describing the node).
-
-        Returns:
-            nodetext (str): The formatted node text.
-
-        """
-
-    def helptext_formatter(self, helptext):
-        """
-        Format the node's help text
-
-        Args:
-            helptext (str): The unformatted help text for the node.
-
-        Returns:
-            helptext (str): The formatted help text.
-
-        """
-
-    def options_formatter(self, optionlist):
-        """
-        Formats the option block.
-
-        Args:
-            optionlist (list): List of (key, description) tuples for every
-                option related to this node.
-            caller (Object, Account or None, optional): The caller of the node.
-
-        Returns:
-            options (str): The formatted option display.
-
-        """
-
-    def node_formatter(self, nodetext, optionstext):
-        """
-        Formats the entirety of the node.
-
-        Args:
-            nodetext (str): The node text as returned by `self.nodetext_formatter`.
-            optionstext (str): The options display as returned by `self.options_formatter`.
-            caller (Object, Account or None, optional): The caller of the node.
-
-        Returns:
-            node (str): The formatted node to display.
-
-        """
-
-
-
-

See evennia/utils/evmenu.py for the details of their default implementations.

-
-
-

Evmenu templating language

-

The EvMenu is very powerful and flexible. But often your menu is simple enough to -not require the full power of EvMenu. For this you can use the Evmenu templating language.

-

This is how the templating is used:

-
from evennia.utils.evmenu import parse_menu_template, EvMenu
-
-template_string = "(will be described below)"
-# this could be empty if you don't need to access any callables
-# in your template
-goto_callables = {"mycallable1": function, ...}
-
-# generate the menutree
-menutree = parse_menu_template(caller, template_string, goto_callables)
-# a normal EvMenu call
-EvMenu(caller, menutree, ...)
-
-
-
-

… So the parse_menu_template is just another way to generate the menutree dict needed by -EvMenu - after this EvMenu works normally.

-

The good thing with this two-step procedude is that you can mix- and match - if you wanted -you could insert a normal, fully flexible function-based node-function in the menutree before -passing -the whole thing into EvMenu and get the best of both worlds. It also makes it -easy to substitute base EvMenu with a child class that changes the menu display.

-

… But if you really don’t need any such customization, you can also apply the template in one step -using -the template2menu helper:

-
from evennia.utils.evmenu import template2menu
-
-template_string = "(will be described below)"
-goto_callables = {"mycallable1": function, ...}
-
-template2menu(caller, template_string, goto_callables, startnode="start", ...)
-
-
-
-

In addition to the template-related arguments, template2menu takes all the same **kwargs -as EvMenu and will parse the template and start the menu for you in one go.

-
-

The templating string

-

The template is a normal string with a very simple format. Each node begins -with a marker ## Node <name of node>, follwowed by a ## Options separator (the Node and -Options are -case-insensitive).

-
template_string = """
-
-## NODE start
-
-<text for the node>
-
-## OPTIONS
-
-# this is a comment. Only line-comments are allowed.
-
-key;alias;alias: description -> goto_str_or_callable
-key;alias;alias: goto_str_or_callable
->pattern: goto_str_or_callable
-
-"""
-
-
-
    -
  • The text after ## NODE defines the name of the node. This must be unique within the -menu because this is what you use for goto statements. The name could have spaces.

  • -
  • The area between ## NODE and ## OPTIONS contains the text of the node. It can have -normal formatting and will retain intentation.

  • -
  • The ## OPTIONS section, until the next ## NODE or the end of the string, -holds the options, one per line.

  • -
  • Option-indenting is ignored but can be useful for readability.

  • -
  • The options-section can also have line-comments, marked by starting the line with #.

  • -
  • A node without a following ## OPTIONS section indicates an end node, and reaching -it will print the text and immediately exit the menu (same as for regular EvMenu).

  • -
-
-
-

Templating options format

-

The normal, full syntax is:

-
key;alias;alias: description -> goto_str_or_callable
-
-
-

An example would be

-
next;n: Go to node Two -> node2
-
-
-

In the menu, this will become an option

-
next: Go to node Two
-
-
-

where you can enter next or n to go to the menu node named node2.

-

To skip the description, just add the goto without the ->:

-
next;n: node2
-
-
-

This will create a menu option without any description:

-
next
-
-
-

A special key is >. This acts as a pattern matcher. Between > and the : one -can fit an optional pattern. This -pattern will first be parsed with glob-style -parsing and then -with regex, and only if -the player’s input matches either will the option be chosen. An input-matching -option cannot have a description.

-
  # this matches the empty string (just pressing return)
-  >: node2
-
-  # this matches input starting with 'test' (regex match)
-  > ^test.+?: testnode
-
-  # this matches any number input (regex match)
-  > [0-9]+?: countnode
-
-  # this matches everything not covered by previous options
-  # (glob-matching, space is stripped without quotes)
-  > *: node3
-
-
-

You can have multiple pattern-matchers for a node but remember that options are -checked in the order they are listed. So make sure to put your pattern-matchers -in decending order of generality; if you have a ‘catch-all’ pattern, -it should be put last or those behind it will never be tried.

-
   next;n: node2
-   back;b: node1
-   >: node2
-
-
-

The above would give you the option to write next/back but you can also just press return to move on -to the next node.

-
-
-

Templating goto-callables

-

Instead of giving the name of a node to go to, you can also give the name -of a goto_callable, which in turn returns the name of the node to go to. You -tell the template it’s a callable by simply adding () at the end.

-
next: Go to node 2 -> goto_node2()
-
-
-

You can also add keyword arguments:

-
back: myfunction(from=foo)
-
-
-
-

Note: ONLY keyword-arguments are supported! Trying to pass a positional -argument will lead to an error.

-
-

The contents of the kwargs-values will be evaluated by literal_eval so -you don’t need to add quotes to strings unless they have spaces in them. Numbers -will be converted correctly, but more complex input structures (like lists or dicts) will -not - if you want more complex input you should use a full function-based EvMenu -node instead.

-

The goto-callable is defined just like any Evmenu goto-func. You must always -use the full form (including **kwargs):

-
def mygotocallable(caller, raw_string, **kwargs):
-  # ...
-  return "nodename_to_goto"
-
-
-
-

Return None to re-run the current node. Any keyword arguments you specify in -your template will be passed to your goto-callable in **kwargs. Unlike in -regular EvMenu nodes you can’t return kwargs to pass it between nodes and other dynamic -tricks.

-

All goto-callables you use in your menu-template must be added to the -goto_callable mapping that you pass to parse_menu_template or -template2menu.

-
-
-

Templating example to show all possible options:

-

-template_string = """
-
-## NODE start
-
-This is the text of the start node.
-Both ## NODE, ## node or ## Node works. The node-name can have
-spaces.
-
-The text area can have multiple lines, line breaks etc.
-
-## OPTIONS
-
-    # here starts the option-defition
-    # comments are only allowed from beginning of line.
-    # Indenting is not necessary, but good for readability
-
-    1: Option number 1 -> node1
-    2: Option number 2 -> node2
-    next: This steps next -> go_back()
-    # the -> can be ignored if there is no desc
-    back: go_back(from_node=start)
-    abort: abort
-
-# ----------------------------------- this is ignored
-
-## NODE node1
-
-Text for Node1. Enter a message!
-<return> to go back.
-
-## options
-
-    # Starting the option-line with >
-    # allows to perform different actions depending on
-    # what is inserted.
-
-    # this catches everything starting with foo
-    > foo*: handle_foo_message()
-
-    # regex are also allowed (this catches number inputs)
-    > [0-9]+?: handle_numbers()
-
-    # this catches the empty return
-    >: start
-
-    # this catches everything else
-    > *: handle_message(from_node=node1)
-
-# -----------------------------------------
-
-## NODE node2
-
-Text for Node2. Just go back.
-
-## options
-
-    >: start
-
-# node abort
-
-This exits the menu since there is no `## options` section.
-
-
-"""
-
-# we assume the callables are defined earlier
-goto_callables = {"go_back": go_back_func,
-                  "handle_foo_message": handle_message,
-                  "handle_numbers": my_number_handler,
-                  "handle_message": handle_message2}
-
-# boom - a menu
-template2menu(caller, template_string, goto_callables)
-
-
-
-
-
-
-

Examples:

- -
-

Example: Simple branching menu

-

Below is an example of a simple branching menu node leading to different other nodes depending on -choice:

-
# in mygame/world/mychargen.py
-
-def define_character(caller):
-    text = \
-    """
-    What aspect of your character do you want
-    to change next?
-    """
-    options = ({"desc": "Change the name",
-                "goto": "set_name"},
-               {"desc": "Change the description",
-                "goto": "set_description"})
-    return text, options
-
-EvMenu(caller, "world.mychargen", startnode="define_character")
-
-
-
-

This will result in the following node display:

-
What aspect of your character do you want
-to change next?
-_________________________
-1: Change the name
-2: Change the description
-
-
-

Note that since we didn’t specify the “name” key, EvMenu will let the user enter numbers instead. In -the following examples we will not include the EvMenu call but just show nodes running inside the -menu. Also, since EvMenu also takes a dictionary to describe the menu, we could have called it -like this instead in the example:

-
EvMenu(caller, {"define_character": define_character}, startnode="define_character")
-
-
-
-
-
-

Example: Dynamic goto

-

-def _is_in_mage_guild(caller, raw_string, **kwargs):
-    if caller.tags.get('mage', category="guild_member"):
-        return "mage_guild_welcome"
-    else:
-        return "mage_guild_blocked"
-
-def enter_guild:
-    text = 'You say to the mage guard:'
-    options ({'desc': 'I need to get in there.',
-              'goto': _is_in_mage_guild},
-             {'desc': 'Never mind',
-              'goto': 'end_conversation'})
-    return text, options
-
-
-

This simple callable goto will analyse what happens depending on who the caller is. The -enter_guild node will give you a choice of what to say to the guard. If you try to enter, you will -end up in different nodes depending on (in this example) if you have the right Tag set on -yourself or not. Note that since we don’t include any ‘key’s in the option dictionary, you will just -get to pick between numbers.

-
-
-

Example: Set caller properties

-

Here is an example of passing arguments into the goto callable and use that to influence -which node it should go to next:

-

-def _set_attribute(caller, raw_string, **kwargs):
-    "Get which attribute to modify and set it"
-
-    attrname, value = kwargs.get("attr", (None, None))
-    next_node = kwargs.get("next_node")
-
-    caller.attributes.add(attrname, attrvalue)
-
-    return next_node
-
-
-def node_background(caller):
-    text = \
-    """
-    {} experienced a traumatic event
-    in their childhood. What was it?
-    """.format(caller.key}
-
-    options = ({"key": "death",
-                "desc": "A violent death in the family",
-                "goto": (_set_attribute, {"attr": ("experienced_violence", True),
-                                          "next_node": "node_violent_background"})},
-               {"key": "betrayal",
-                "desc": "The betrayal of a trusted grown-up",
-                "goto": (_set_attribute, {"attr": ("experienced_betrayal", True),
-                                          "next_node": "node_betrayal_background"})})
-    return text, options
-
-
-

This will give the following output:

-
Kovash the magnificent experienced a traumatic event
-in their childhood. What was it?
-____________________________________________________
-death: A violent death in the family
-betrayal: The betrayal of a trusted grown-up
-
-
-
-

Note above how we use the _set_attribute helper function to set the attribute depending on the -User’s choice. In thie case the helper function doesn’t know anything about what node called it - we -even tell it which nodename it should return, so the choices leads to different paths in the menu. -We could also imagine the helper function analyzing what other choices

-
-
-

Example: Get arbitrary input

-

An example of the menu asking the user for input - any input.

-

-def _set_name(caller, raw_string, **kwargs):
-
-    inp = raw_string.strip()
-
-    prev_entry = kwargs.get("prev_entry")
-
-    if not inp:
-        # a blank input either means OK or Abort
-        if prev_entry:
-            caller.key = prev_entry
-            caller.msg("Set name to {}.".format(prev_entry))
-            return "node_background"
-        else:
-            caller.msg("Aborted.")
-            return "node_exit"
-    else:
-        # re-run old node, but pass in the name given
-        return None, {"prev_entry": inp}
-
-
-def enter_name(caller, raw_string, **kwargs):
-
-    # check if we already entered a name before
-    prev_entry = kwargs.get("prev_entry")
-
-    if prev_entry:
-        text = "Current name: {}.\nEnter another name or <return> to accept."
-    else:
-        text = "Enter your character's name or <return> to abort."
-
-    options = {"key": "_default",
-               "goto": (_set_name, {"prev_entry": prev_entry})}
-
-    return text, options
-
-
-
-

This will display as

-
Enter your character's name or <return> to abort.
-
-> Gandalf
-
-Current name: Gandalf
-Enter another name or <return> to accept.
-
->
-
-Set name to Gandalf.
-
-
-
-

Here we re-use the same node twice for reading the input data from the user. Whatever we enter will -be caught by the _default option and passed into the helper function. We also pass along whatever -name we have entered before. This allows us to react correctly on an “empty” input - continue to the -node named "node_background" if we accept the input or go to an exit node if we presses Return -without entering anything. By returning None from the helper function we automatically re-run the -previous node, but updating its ingoing kwargs to tell it to display a different text.

-
-
-

Example: Storing data between nodes

-

A convenient way to store data is to store it on the caller.ndb._menutree which you can reach from -every node. The advantage of doing this is that the _menutree NAttribute will be deleted -automatically when you exit the menu.

-

-def _set_name(caller, raw_string, **kwargs):
-
-    caller.ndb._menutree.charactersheet = {}
-    caller.ndb._menutree.charactersheet['name'] = raw_string
-    caller.msg("You set your name to {}".format(raw_string)
-    return "background"
-
-def node_set_name(caller):
-    text = 'Enter your name:'
-    options = {'key': '_default',
-               'goto': _set_name}
-
-    return text, options
-
-...
-
-
-def node_view_sheet(caller):
-    text = "Character sheet:\n {}".format(self.ndb._menutree.charactersheet)
-
-    options = ({"key": "Accept",
-                "goto": "finish_chargen"},
-               {"key": "Decline",
-                "goto": "start_over"})
-
-    return text, options
-
-
-
-

Instead of passing the character sheet along from node to node through the kwargs we instead -set it up temporarily on caller.ndb._menutree.charactersheet. This makes it easy to reach from -all nodes. At the end we look at it and, if we accept the character the menu will likely save the -result to permanent storage and exit.

-
-

One point to remember though is that storage on caller.ndb._menutree is not persistent across -@reloads. If you are using a persistent menu (using EvMenu(..., persistent=True) you should -use -caller.db to store in-menu data like this as well. You must then yourself make sure to clean it -when the user exits the menu.

-
-
-
-

Example: Repeating the same node

-

Sometimes you want to make a chain of menu nodes one after another, but you don’t want the user to -be able to continue to the next node until you have verified that what they input in the previous -node is ok. A common example is a login menu:

-

-def _check_username(caller, raw_string, **kwargs):
-    # we assume lookup_username() exists
-    if not lookup_username(raw_string):
-        # re-run current node by returning `None`
-        caller.msg("|rUsername not found. Try again.")
-        return None
-    else:
-        # username ok - continue to next node
-        return "node_password"
-
-
-def node_username(caller):
-    text = "Please enter your user name."
-    options = {"key": "_default",
-               "goto": _check_username}
-    return text, options
-
-
-def _check_password(caller, raw_string, **kwargs):
-
-    nattempts = kwargs.get("nattempts", 0)
-    if nattempts > 3:
-        caller.msg("Too many failed attempts. Logging out")
-        return "node_abort"
-    elif not validate_password(raw_string):
-        caller.msg("Password error. Try again.")
-        return None, {"nattempts", nattempts + 1}
-    else:
-        # password accepted
-        return "node_login"
-
-def node_password(caller, raw_string, **kwargs):
-    text = "Enter your password."
-    options = {"key": "_default",
-               "goto": _check_password}
-    return text, options
-
-
-
-

This will display something like

-
---------------------------
-Please enter your username.
----------------------------
-
-> Fo
-
-------------------------------
-Username not found. Try again.
-______________________________
-abort: (back to start)
-------------------------------
-
-> Foo
-
----------------------------
-Please enter your password.
----------------------------
-
-> Bar
-
---------------------------
-Password error. Try again.
---------------------------
-
-
-

And so on.

-

Here the goto-callables will return to the previous node if there is an error. In the case of -password attempts, this will tick up the nattempts argument that will get passed on from iteration -to iteration until too many attempts have been made.

-
-
-

Defining nodes in a dictionary

-

You can also define your nodes directly in a dictionary to feed into the EvMenu creator.

-
def mynode(caller):
-   # a normal menu node function
-   return text, options
-
-menu_data = {"node1": mynode,
-             "node2": lambda caller: (
-                      "This is the node text",
-                     ({"key": "lambda node 1",
-                       "desc": "go to node 1 (mynode)",
-                       "goto": "node1"},
-                      {"key": "lambda node 2",
-                       "desc": "go to thirdnode",
-                       "goto": "node3"})),
-             "node3": lambda caller, raw_string: (
-                       # ... etc ) }
-
-# start menu, assuming 'caller' is available from earlier
-EvMenu(caller, menu_data, startnode="node1")
-
-
-
-

The keys of the dictionary become the node identifiers. You can use any callable on the right form -to describe each node. If you use Python lambda expressions you can make nodes really on the fly. -If you do, the lambda expression must accept one or two arguments and always return a tuple with two -elements (the text of the node and its options), same as any menu node function.

-

Creating menus like this is one way to present a menu that changes with the circumstances - you -could for example remove or add nodes before launching the menu depending on some criteria. The -drawback is that a lambda expression is much more -limited than a full -function - for example you can’t use other Python keywords like if inside the body of the -lambda.

-

Unless you are dealing with a relatively simple dynamic menu, defining menus with lambda’s is -probably more work than it’s worth: You can create dynamic menus by instead making each node -function more clever. See the NPC shop tutorial for an example of this.

-
-
-
-

Ask for simple input

-

This describes two ways for asking for simple questions from the user. Using Python’s input -will not work in Evennia. input will block the entire server for everyone until that one -player has entered their text, which is not what you want.

-
-

The yield way

-

In the func method of your Commands (only) you can use Python’s built-in yield command to -request input in a similar way to input. It looks like this:

-
result = yield("Please enter your answer:")
-
-
-

This will send “Please enter your answer” to the Command’s self.caller and then pause at that -point. All other players at the server will be unaffected. Once caller enteres a reply, the code -execution will continue and you can do stuff with the result. Here is an example:

-
from evennia import Command
-class CmdTestInput(Command):
-    key = "test"
-    def func(self):
-        result = yield("Please enter something:")
-        self.caller.msg(f"You entered {result}.")
-        result2 = yield("Now enter something else:")
-        self.caller.msg(f"You now entered {result2}.")
-
-
-

Using yield is simple and intuitive, but it will only access input from self.caller and you -cannot abort or time out the pause until the player has responded. Under the hood, it is actually -just a wrapper calling get_input described in the following section.

-
-

Important Note: In Python you cannot mix yield and return <value> in the same method. It has -to do with yield turning the method into a -generator. A return without an argument works, you -can just not do return <value>. This is usually not something you need to do in func() anyway, -but worth keeping in mind.

-
-
-
-

The get_input way

-

The evmenu module offers a helper function named get_input. This is wrapped by the yield -statement which is often easier and more intuitive to use. But get_input offers more flexibility -and power if you need it. While in the same module as EvMenu, get_input is technically unrelated -to it. The get_input allows you to ask and receive simple one-line input from the user without -launching the full power of a menu to do so. To use, call get_input like this:

-
get_input(caller, prompt, callback)
-
-
-

Here caller is the entity that should receive the prompt for input given as prompt. The -callback is a callable function(caller, prompt, user_input) that you define to handle the answer -from the user. When run, the caller will see prompt appear on their screens and any text they -enter will be sent into the callback for whatever processing you want.

-

Below is a fully explained callback and example call:

-
from evennia import Command
-from evennia.utils.evmenu import get_input
-
-def callback(caller, prompt, user_input):
-    """
-    This is a callback you define yourself.
-
-    Args:
-        caller (Account or Object): The one being asked
-          for input
-        prompt (str): A copy of the current prompt
-        user_input (str): The input from the account.
-
-    Returns:
-        repeat (bool): If not set or False, exit the
-          input prompt and clean up. If returning anything
-          True, stay in the prompt, which means this callback
-          will be called again with the next user input.
-    """
-    caller.msg(f"When asked '{prompt}', you answered '{user_input}'.")
-
-get_input(caller, "Write something! ", callback)
-
-
-

This will show as

-
Write something!
-> Hello
-When asked 'Write something!', you answered 'Hello'.
-
-
-
-

Normally, the get_input function quits after any input, but as seen in the example docs, you could -return True from the callback to repeat the prompt until you pass whatever check you want.

-
-

Note: You cannot link consecutive questions by putting a new get_input call inside the -callback. If you want that you should use an EvMenu instead (see the Repeating the same -node example above). Otherwise you can either peek at the -implementation of get_input and implement your own mechanism (it’s just using cmdset nesting) or -you can look at this extension suggested on the mailing -list.

-
-
-

Example: Yes/No prompt

-

Below is an example of a Yes/No prompt using the get_input function:

-
def yesno(caller, prompt, result):
-    if result.lower() in ("y", "yes", "n", "no"):
-        # do stuff to handle the yes/no answer
-        # ...
-        # if we return None/False the prompt state
-        # will quit after this
-    else:
-        # the answer is not on the right yes/no form
-        caller.msg("Please answer Yes or No. \n{prompt}")
-@        # returning True will make sure the prompt state is not exited
-        return True
-
-# ask the question
-get_input(caller, "Is Evennia great (Yes/No)?", yesno)
-
-
-
-
-
-
-

The @list_node decorator

-

The evennia.utils.evmenu.list_node is an advanced decorator for use with EvMenu node functions. -It is used to quickly create menus for manipulating large numbers of items.

-
text here
-______________________________________________
-
-1. option1     7. option7      13. option13
-2. option2     8. option8      14. option14
-3. option3     9. option9      [p]revius page
-4. option4    10. option10      page 2
-5. option5    11. option11     [n]ext page
-6. option6    12. option12
-
-
-
-

The menu will automatically create an multi-page option listing that one can flip through. One can -inpect each entry and then select them with prev/next. This is how it is used:

-
from evennia.utils.evmenu import list_node
-
-
-...
-
-_options(caller):
-    return ['option1', 'option2', ... 'option100']
-
-_select(caller, menuchoice, available_choices):
-    # analyze choice
-    return node_matching_the_choice
-
-@list_node(_options, select=_select, pagesize=10)
-def node_mylist(caller, raw_string, **kwargs):
-    ...
-
-    # the decorator auto-creates the options; any options
-    # returned here would be appended to the auto-options
-    return node_text, {}
-
-
-

The options argument to list_node is either a list, a generator or a callable returning a list -of strings for each option that should be displayed in the node.

-

The select is a callable in the example above but could also be the name of a menu node. If a -callable, the menuchoice argument holds the selection done and available_choices holds all the -options available. The callable should return the menu to go to depending on the selection (or -None to rerun the same node). If the name of a menu node, the selection will be passed as -selection kwarg to that node.

-

The decorated node itself should return text to display in the node. It must return at least an -empty dictionary for its options. It returning options, those will supplement the options -auto-created by the list_node decorator.

-
-
-

Assorted notes

-

The EvMenu is implemented using Commands. When you start a new EvMenu, the user of the -menu will be assigned a CmdSet with the commands they need to navigate the menu. -This means that if you were to, from inside the menu, assign a new command set to the caller, you -may override the Menu Cmdset and kill the menu. If you want to assign cmdsets to the caller as part -of the menu, you should store the cmdset on caller.ndb._menutree and wait to actually assign it -until the exit node.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/EvMore.html b/docs/0.9.5/EvMore.html deleted file mode 100644 index 03b40e2e1d..0000000000 --- a/docs/0.9.5/EvMore.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - EvMore — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

EvMore

-

When sending a very long text to a user client, it might scroll beyond of the height of the client -window. The evennia.utils.evmore.EvMore class gives the user the in-game ability to only view one -page of text at a time. It is usually used via its access function, evmore.msg.

-

The name comes from the famous unix pager utility more which performs just this function.

-
-

Using EvMore

-

To use the pager, just pass the long text through it:

-
from evennia.utils import evmore
-
-evmore.msg(receiver, long_text)
-
-
-

Where receiver is an Object or a Account. If the text is longer than the -client’s screen height (as determined by the NAWS handshake or by settings.CLIENT_DEFAULT_HEIGHT) -the pager will show up, something like this:

-
-

[…] -aute irure dolor in reprehenderit in voluptate velit -esse cillum dolore eu fugiat nulla pariatur. Excepteur -sint occaecat cupidatat non proident, sunt in culpa qui -officia deserunt mollit anim id est laborum.

-
-
-

(more [1/6] return|back|top|end|abort)

-
-

where the user will be able to hit the return key to move to the next page, or use the suggested -commands to jump to previous pages, to the top or bottom of the document as well as abort the -paging.

-

The pager takes several more keyword arguments for controlling the message output. See the -evmore-API for more info.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Evennia-API.html b/docs/0.9.5/Evennia-API.html deleted file mode 100644 index cf052e253b..0000000000 --- a/docs/0.9.5/Evennia-API.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - - - API Summary — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

API Summary

-

evennia - library root

- -
-

Shortcuts

-

Evennia’s ‘flat API’ has shortcuts to common tools, available by only importing evennia. -The flat API is defined in __init__.py viewable here

-
-

Main config

- -
-
-

Search functions

- -
-
-

Create functions

- -
-
-

Typeclasses

- -
-
-

Commands

- -
-
-

Utilities

- -
-
-

Global singleton handlers

- -
-
-

Database core models (for more advanced lookups)

- -
-
-

Contributions

- -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Evennia-Game-Index.html b/docs/0.9.5/Evennia-Game-Index.html deleted file mode 100644 index 932cf2db51..0000000000 --- a/docs/0.9.5/Evennia-Game-Index.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - Evennia Game Index — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Evennia Game Index

-

The Evennia game index is a list of games built or -being built with Evennia. Anyone is allowed to add their game to the index

-
    -
  • also if you have just started development and don’t yet accept external -players. It’s a chance for us to know you are out there and for you to make us -intrigued about or excited for your upcoming game!

  • -
-

All we ask is that you check so your game-name does not collide with one -already in the list - be nice!

-
-

Connect with the wizard

-

From your game dir, run

-
evennia connections
-
-
-

This will start the Evennia Connection wizard. From the menu, select to add -your game to the Evennia Game Index. Follow the prompts and don’t forget to -save your new settings in the end. Use quit at any time if you change your -mind.

-
-

The wizard will create a new file mygame/server/conf/connection_settings.py -with the settings you chose. This is imported from the end of your main -settings file and will thus override it. You can edit this new file if you -want, but remember that if you run the wizard again, your changes may get -over-written.

-
-
-
-

Manual Settings

-

If you don’t want to use the wizard (maybe because you already have the client installed from an -earlier version), you can also configure your index entry in your settings file -(mygame/server/conf/settings.py). Add the following:

-
GAME_INDEX_ENABLED = True
-
-GAME_INDEX_LISTING = {
-    # required
-    'game_status': 'pre-alpha',            # pre-alpha, alpha, beta, launched
-    'listing_contact': "dummy@dummy.com",  # not publicly shown.
-    'short_description': 'Short blurb',
-
-    # optional
-    'long_description':
-        "Longer description that can use Markdown like *bold*, _italic_"
-        "and [linkname](http://link.com). Use \n for line breaks."
-    'telnet_hostname': 'dummy.com',
-    'telnet_port': '1234',
-    'web_client_url': 'dummy.com/webclient',
-    'game_website': 'dummy.com',
-    # 'game_name': 'MyGame',  # set only if different than settings.SERVERNAME
-}
-
-
-

Of these, the game_status, short_description and listing_contact are -required. The listing_contact is not publicly visible and is only meant as a -last resort if we need to get in touch with you over any listing issue/bug (so -far this has never happened).

-

If game_name is not set, the settings.SERVERNAME will be used. Use empty strings -('') for optional fields you don’t want to specify at this time.

-
-
-

Non-public games

-

If you don’t specify neither telnet_hostname + port nor -web_client_url, the Game index will list your game as Not yet public. -Non-public games are moved to the bottom of the index since there is no way -for people to try them out. But it’s a good way to show you are out there, even -if you are not ready for players yet.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Evennia-Introduction.html b/docs/0.9.5/Evennia-Introduction.html deleted file mode 100644 index f37b73af5d..0000000000 --- a/docs/0.9.5/Evennia-Introduction.html +++ /dev/null @@ -1,310 +0,0 @@ - - - - - - - - - Evennia Introduction — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Evennia Introduction

-
-

A MUD (originally Multi-User Dungeon, with later variants Multi-User Dimension and Multi-User -Domain) is a multiplayer real-time virtual world described primarily in text. MUDs combine elements -of role-playing games, hack and slash, player versus player, interactive fiction and online chat. -Players can read or view descriptions of rooms, objects, other players, non-player characters, and -actions performed in the virtual world. Players typically interact with each other and the world by -typing commands that resemble a natural language. - Wikipedia

-
-

If you are reading this, it’s quite likely you are dreaming of creating and running a text-based -massively-multiplayer game (MUD/MUX/MUSH etc) of your very own. You -might just be starting to think about it, or you might have lugged around that perfect game in -your mind for years … you know just how good it would be, if you could only make it come to -reality. We know how you feel. That is, after all, why Evennia came to be.

-

Evennia is in principle a MUD-building system: a bare-bones Python codebase and server intended to -be highly extendable for any style of game. “Bare-bones” in this context means that we try to impose -as few game-specific things on you as possible. So whereas we for convenience offer basic building -blocks like objects, characters, rooms, default commands for building and administration etc, we -don’t prescribe any combat rules, mob AI, races, skills, character classes or other things that will -be different from game to game anyway. It is possible that we will offer some such systems as -contributions in the future, but these will in that case all be optional.

-

What we do however, is to provide a solid foundation for all the boring database, networking, and -behind-the-scenes administration stuff that all online games need whether they like it or not. -Evennia is fully persistent, that means things you drop on the ground somewhere will still be -there a dozen server reboots later. Through Django we support a large variety of different database -systems (a database is created for you automatically if you use the defaults).

-

Using the full power of Python throughout the server offers some distinct advantages. All your -coding, from object definitions and custom commands to AI scripts and economic systems is done in -normal Python modules rather than some ad-hoc scripting language. The fact that you script the game -in the same high-level language that you code it in allows for very powerful and custom game -implementations indeed.

-

The server ships with a default set of player commands that are similar to the MUX command set. We -do not aim specifically to be a MUX server, but we had to pick some default to go with (see -this for more about our original motivations). It’s easy to remove or add commands, or -to have the command syntax mimic other systems, like Diku, LP, MOO and so on. Or why not create a -new and better command system of your own design.

-
-

Can I test it somewhere?

-

Evennia’s demo server can be found at demo.evennia.com. If you prefer to -connect to the demo via your own telnet client you can do so at silvren.com, port 4280. Here is -a screenshot.

-

Once you installed Evennia yourself it comes with its own tutorial - this shows off some of the -possibilities and gives you a small single-player quest to play. The tutorial takes only one -single in-game command to install as explained here.

-
-
-

Brief summary of features

-
-

Technical

-
    -
  • Game development is done by the server importing your normal Python modules. Specific server -features are implemented by overloading hooks that the engine calls appropriately.

  • -
  • All game entities are simply Python classes that handle database negotiations behind the scenes -without you needing to worry.

  • -
  • Command sets are stored on individual objects (including characters) to offer unique functionality -and object-specific commands. Sets can be updated and modified on the fly to expand/limit player -input options during play.

  • -
  • Scripts are used to offer asynchronous/timed execution abilities. Scripts can also be persistent. -There are easy mechanisms to thread particularly long-running processes and built-in ways to start -“tickers” for games that wants them.

  • -
  • In-game communication channels are modular and can be modified to any functionality, including -mailing systems and full logging of all messages.

  • -
  • Server can be fully rebooted/reloaded without users disconnecting.

  • -
  • An Account can freely connect/disconnect from game-objects, offering an easy way to implement -multi-character systems and puppeting.

  • -
  • Each Account can optionally control multiple Characters/Objects at the same time using the same -login information.

  • -
  • Spawning of individual objects via a prototypes-like system.

  • -
  • Tagging can be used to implement zones and object groupings.

  • -
  • All source code is extensively documented.

  • -
  • Unit-testing suite, including tests of default commands and plugins.

  • -
-
-
-

Default content

-
    -
  • Basic classes for Objects, Characters, Rooms and Exits

  • -
  • Basic login system, using the Account’s login name as their in-game Character’s name for -simplicity

  • -
  • “MUX-like” command set with administration, building, puppeting, channels and social commands

  • -
  • In-game Tutorial

  • -
  • Contributions folder with working, but optional, code such as alternative login, menus, character -generation and more

  • -
-
-
-

Standards/Protocols supported

-
    -
  • TCP/websocket HTML5 browser web client, with ajax/comet fallback for older browsers

  • -
  • Telnet and Telnet + SSL with mud-specific extensions (MCCP, -MSSP, TTYPE, -MSDP, -GMCP, -MXP links)

  • -
  • ANSI and xterm256 colours

  • -
  • SSH

  • -
  • HTTP - Website served by in-built webserver and connected to same database as game.

  • -
  • IRC - external IRC channels can be connected to in-game chat channels

  • -
  • RSS feeds can be echoed to in-game channels (things like Twitter can easily be added)

  • -
  • Several different databases supported (SQLite3, MySQL, PostgreSQL, …)

  • -
-

For more extensive feature information, see the Developer Central.

-
-
-
-

What you need to know to work with Evennia

-

Assuming you have Evennia working (see the quick start instructions) and have -gotten as far as to start the server and connect to it with the client of your choice, here’s what -you need to know depending on your skills and needs.

-
-

I don’t know (or don’t want to do) any programming - I just want to run a game!

-

Evennia comes with a default set of commands for the Python newbies and for those who need to get a -game running now. Stock Evennia is enough for running a simple ‘Talker’-type game - you can build -and describe rooms and basic objects, have chat channels, do emotes and other things suitable for a -social or free-form MU*. Combat, mobs and other game elements are not included, so you’ll have a -very basic game indeed if you are not willing to do at least some coding.

-
-
-

I know basic Python, or I am willing to learn

-

Evennia’s source code is extensively documented and is viewable online. -We also have a comprehensive online manual with lots of examples. -But while Python is -considered a very easy programming language to get into, you do have a learning curve to climb if -you are new to programming. You should probably sit down -with a Python beginner’s tutorial (there are plenty of them on -the web if you look around) so you at least know what you are seeing. See also our -link page for some reading suggestions. To efficiently code your dream game in -Evennia you don’t need to be a Python guru, but you do need to be able to read example code -containing at least these basic Python features:

- -

Obviously, the more things you feel comfortable with, the easier time you’ll have to find your way. -With just basic knowledge you should be able to define your own Commands, create custom -Objects as well as make your world come alive with basic Scripts. You can -definitely build a whole advanced and customized game from extending Evennia’s examples only.

-
-
-

I know my Python stuff and I am willing to use it!

-

Even if you started out as a Python beginner, you will likely get to this point after working on -your game for a while. With more general knowledge in Python the full power of Evennia opens up for -you. Apart from modifying commands, objects and scripts, you can develop everything from advanced -mob AI and economic systems, through sophisticated combat and social mini games, to redefining how -commands, players, rooms or channels themselves work. Since you code your game by importing normal -Python modules, there are few limits to what you can accomplish.

-

If you also happen to know some web programming (HTML, CSS, Javascript) there is also a web -presence (a website and a mud web client) to play around with …

-
-
-

Where to from here?

-

From here you can continue browsing the online documentation to -find more info about Evennia. Or you can jump into the Tutorials and get your hands -dirty with code right away. You can also read the developer’s dev blog for many tidbits and snippets about Evennia’s development and -structure.

-

Some more hints:

-
    -
  1. Get engaged in the community. Make an introductory post to our mailing list/forum and get to know people. It’s also -highly recommended you hop onto our Developer chat -on IRC. This allows you to chat directly with other developers new and old as well as with the devs -of Evennia itself. This chat is logged (you can find links on http://www.evennia.com) and can also -be searched from the same place for discussion topics you are interested in.

  2. -
  3. Read the Game Planning wiki page. It gives some ideas for your work flow and the -state of mind you should aim for - including cutting down the scope of your game for its first -release.

  4. -
  5. Do the Tutorial for basic MUSH-like game carefully from -beginning to end and try to understand what does what. Even if you are not interested in a MUSH for -your own game, you will end up with a small (very small) game that you can build or learn from.

  6. -
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Evennia-for-Diku-Users.html b/docs/0.9.5/Evennia-for-Diku-Users.html deleted file mode 100644 index 34428b8447..0000000000 --- a/docs/0.9.5/Evennia-for-Diku-Users.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - - - - Evennia for Diku Users — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Evennia for Diku Users

-

Evennia represents a learning curve for those who used to code on -Diku type MUDs. While coding in Python is easy if you -already know C, the main effort is to get rid of old C programming habits. Trying to code Python the -way you code C will not only look ugly, it will lead to less optimal and harder to maintain code. -Reading Evennia example code is a good way to get a feel for how different problems are approached -in Python.

-

Overall, Python offers an extensive library of resources, safe memory management and excellent -handling of errors. While Python code does not run as fast as raw C code does, the difference is not -all that important for a text-based game. The main advantage of Python is an extremely fast -development cycle with and easy ways to create game systems that would take many times more code and -be much harder to make stable and maintainable in C.

-
-

Core Differences

-
    -
  • As mentioned, the main difference between Evennia and a Diku-derived codebase is that Evennia is -written purely in Python. Since Python is an interpreted language there is no compile stage. It is -modified and extended by the server loading Python modules at run-time. It also runs on all computer -platforms Python runs on (which is basically everywhere).

  • -
  • Vanilla Diku type engines save their data in custom flat file type storage solutions. By -contrast, Evennia stores all game data in one of several supported SQL databases. Whereas flat files -have the advantage of being easier to implement, they (normally) lack many expected safety features -and ways to effectively extract subsets of the stored data. For example, if the server loses power -while writing to a flatfile it may become corrupt and the data lost. A proper database solution is -not susceptible to this - at no point is the data in a state where it cannot be recovered. Databases -are also highly optimized for querying large data sets efficiently.

  • -
-
-
-

Some Familiar Things

-

Diku expresses the character object referenced normally by:

-

struct char ch* then all character-related fields can be accessed by ch->. In Evennia, one must -pay attention to what object you are using, and when you are accessing another through back- -handling, that you are accessing the right object. In Diku C, accessing character object is normally -done by:

-
/* creating pointer of both character and room struct */
-
-void(struct char ch*, struct room room*){
-    int dam;
-    if (ROOM_FLAGGED(room, ROOM_LAVA)){
-        dam = 100
-        ch->damage_taken = dam
-    };
-};
-
-
-

As an example for creating Commands in Evennia via the from evennia import Command the character -object that calls the command is denoted by a class property as self.caller. In this example -self.caller is essentially the ‘object’ that has called the Command, but most of the time it is an -Account object. For a more familiar Diku feel, create a variable that becomes the account object as:

-
#mygame/commands/command.py
-
-from evennia import Command
-
-class CmdMyCmd(Command):
-    """
-    This is a Command Evennia Object
-    """
-    
-    [...]
-
-    def func(self):
-        ch = self.caller
-        # then you can access the account object directly by using the familiar ch.
-        ch.msg("...")
-        account_name = ch.name
-        race = ch.db.race
-
-
-
-

As mentioned above, care must be taken what specific object you are working with. If focused on a -room object and you need to access the account object:

-
#mygame/typeclasses/room.py
-
-from evennia import DefaultRoom
-
-class MyRoom(DefaultRoom):
-    [...]
-
-    def is_account_object(self, object):
-        # a test to see if object is an account
-        [...]
-
-    def myMethod(self):
-        #self.caller would not make any sense, since self refers to the
-        # object of 'DefaultRoom', you must find the character obj first:
-        for ch in self.contents:
-            if self.is_account_object(ch):
-                # now you can access the account object with ch:
-                account_name = ch.name
-                race = ch.db.race
-
-
-
-
-

Emulating Evennia to Look and Feel Like A Diku/ROM

-

To emulate a Diku Mud on Evennia some work has to be done before hand. If there is anything that all -coders and builders remember from Diku/Rom days is the presence of VNUMs. Essentially all data was -saved in flat files and indexed by VNUMs for easy access. Evennia has the ability to emulate VNUMS -to the extent of categorising rooms/mobs/objs/trigger/zones[…] into vnum ranges.

-

Evennia has objects that are called Scripts. As defined, they are the ‘out of game’ instances that -exist within the mud, but never directly interacted with. Scripts can be used for timers, mob AI, -and even a stand alone databases.

-

Because of their wonderful structure all mob, room, zone, triggers, etc… data can be saved in -independently created global scripts.

-

Here is a sample mob file from a Diku Derived flat file.

-
#0
-mob0~
-mob0~
-mob0
-~
-   Mob0
-~
-10 0 0 0 0 0 0 0 0 E
-1 20 9 0d0+10 1d2+0
-10 100
-8 8 0
-E
-#1
-Puff dragon fractal~
-Puff~
-Puff the Fractal Dragon is here, contemplating a higher reality.
-~
-   Is that some type of differential curve involving some strange, and unknown
-calculus that she seems to be made out of?
-~
-516106 0 0 0 2128 0 0 0 1000 E
-34 9 -10 6d6+340 5d5+5
-340 115600
-8 8 2
-BareHandAttack: 12
-E
-T 95
-
-
-

Each line represents something that the MUD reads in and does something with it. This isn’t easy to -read, but let’s see if we can emulate this as a dictionary to be stored on a database script created -in Evennia.

-

First, let’s create a global script that does absolutely nothing and isn’t attached to anything. You -can either create this directly in-game with the @py command or create it in another file to do some -checks and balances if for whatever reason the script needs to be created again. Progmatically it -can be done like so:

-
from evennia import create_script
-
-mob_db = create_script("typeclasses.scripts.DefaultScript", key="mobdb",
-                       persistent=True, obj=None)
-mob_db.db.vnums = {}
-
-
-

Just by creating a simple script object and assigning it a ‘vnums’ attribute as a type dictionary. -Next we have to create the mob layout…

-
# vnum : mob_data
-
-mob_vnum_1 = {
-            'key' : 'puff',
-            'sdesc' : 'puff the fractal dragon',
-            'ldesc' : 'Puff the Fractal Dragon is here, ' \
-                      'contemplating a higher reality.',
-            'ddesc' : ' Is that some type of differential curve ' \
-                      'involving some strange, and unknown calculus ' \
-                      'that she seems to be made out of?',
-            [...]
-        }
-
-# Then saving it to the data, assuming you have the script obj stored in a variable.
-mob_db.db.vnums[1] = mob_vnum_1
-
-
-

This is a very ‘caveman’ example, but it gets the idea across. You can use the keys in the -mob_db.vnums to act as the mob vnum while the rest contains the data…

-

Much simpler to read and edit. If you plan on taking this route, you must keep in mind that by -default evennia ‘looks’ at different properties when using the look command for instance. If you -create an instance of this mob and make its self.key = 1, by default evennia will say

-

Here is : 1

-

You must restructure all default commands so that the mud looks at different properties defined on -your mob.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Evennia-for-MUSH-Users.html b/docs/0.9.5/Evennia-for-MUSH-Users.html deleted file mode 100644 index 13e66a666a..0000000000 --- a/docs/0.9.5/Evennia-for-MUSH-Users.html +++ /dev/null @@ -1,317 +0,0 @@ - - - - - - - - - Evennia for MUSH Users — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Evennia for MUSH Users

-

This page is adopted from an article originally posted for the MUSH community here on -musoapbox.net.

-

MUSHes are text multiplayer games traditionally used for -heavily roleplay-focused game styles. They are often (but not always) utilizing game masters and -human oversight over code automation. MUSHes are traditionally built on the TinyMUSH-family of game -servers, like PennMUSH, TinyMUSH, TinyMUX and RhostMUSH. Also their siblings -MUCK and MOO are -often mentioned together with MUSH since they all inherit from the same -TinyMUD base. A major feature is the -ability to modify and program the game world from inside the game by using a custom scripting -language. We will refer to this online scripting as softcode here.

-

Evennia works quite differently from a MUSH both in its overall design and under the hood. The same -things are achievable, just in a different way. Here are some fundamental differences to keep in -mind if you are coming from the MUSH world.

-
-

Developers vs Players

-

In MUSH, users tend to code and expand all aspects of the game from inside it using softcode. A MUSH -can thus be said to be managed solely by Players with different levels of access. Evennia on the -other hand, differentiates between the role of the Player and the Developer.

-
    -
  • An Evennia Developer works in Python from outside the game, in what MUSH would consider -“hardcode”. Developers implement larger-scale code changes and can fundamentally change how the game -works. They then load their changes into the running Evennia server. Such changes will usually not -drop any connected players.

  • -
  • An Evennia Player operates from inside the game. Some staff-level players are likely to double -as developers. Depending on access level, players can modify and expand the game’s world by digging -new rooms, creating new objects, alias commands, customize their experience and so on. Trusted staff -may get access to Python via the @py command, but this would be a security risk for normal Players -to use. So the Player usually operates by making use of the tools prepared for them by the -Developer - tools that can be as rigid or flexible as the developer desires.

  • -
-
-
-

Collaborating on a game - Python vs Softcode

-

For a Player, collaborating on a game need not be too different between MUSH and Evennia. The -building and description of the game world can still happen mostly in-game using build commands, -using text tags and inline functions to prettify and customize the -experience. Evennia offers external ways to build a world but those are optional. There is also -nothing in principle stopping a Developer from offering a softcode-like language to Players if -that is deemed necessary.

-

For Developers of the game, the difference is larger: Code is mainly written outside the game in -Python modules rather than in-game on the command line. Python is a very popular and well-supported -language with tons of documentation and help to be found. The Python standard library is also a -great help for not having to reinvent the wheel. But that said, while Python is considered one of -the easier languages to learn and use it is undoubtedly very different from MUSH softcode.

-

While softcode allows collaboration in-game, Evennia’s external coding instead opens up the -possibility for collaboration using professional version control tools and bug tracking using -websites like github (or bitbucket for a free private repo). Source code can be written in proper -text editors and IDEs with refactoring, syntax highlighting and all other conveniences. In short, -collaborative development of an Evennia game is done in the same way most professional collaborative -development is done in the world, meaning all the best tools can be used.

-
-
-

@parent vs @typeclass and @spawn

-

Inheritance works differently in Python than in softcode. Evennia has no concept of a “master -object” that other objects inherit from. There is in fact no reason at all to introduce “virtual -objects” in the game world - code and data are kept separate from one another.

-

In Python (which is an object oriented -language) one instead creates classes - these are like blueprints from which you spawn any number -of object instances. Evennia also adds the extra feature that every instance is persistent in the -database (this means no SQL is ever needed). To take one example, a unique character in Evennia is -an instances of the class Character.

-

One parallel to MUSH’s @parent command may be Evennia’s @typeclass command, which changes which -class an already existing object is an instance of. This way you can literally turn a Character -into a Flowerpot on the spot.

-

if you are new to object oriented design it’s important to note that all object instances of a class -does not have to be identical. If they did, all Characters would be named the same. Evennia allows -to customize individual objects in many different ways. One way is through Attributes, which are -database-bound properties that can be linked to any object. For example, you could have an Orc -class that defines all the stuff an Orc should be able to do (probably in turn inheriting from some -Monster class shared by all monsters). Setting different Attributes on different instances -(different strength, equipment, looks etc) would make each Orc unique despite all sharing the same -class.

-

The @spawn command allows one to conveniently choose between different “sets” of Attributes to -put on each new Orc (like the “warrior” set or “shaman” set) . Such sets can even inherit one -another which is again somewhat remniscent at least of the effect of @parent and the object- -based inheritance of MUSH.

-

There are other differences for sure, but that should give some feel for things. Enough with the -theory. Let’s get down to more practical matters next. To install, see the -Getting Started instructions.

-
-
-

A first step making things more familiar

-

We will here give two examples of customizing Evennia to be more familiar to a MUSH Player.

-
-

Activating a multi-descer

-

By default Evennia’s desc command updates your description and that’s it. There is a more feature- -rich optional “multi-descer” in evennia/contrib/multidesc.py though. This alternative allows for -managing and combining a multitude of keyed descriptions.

-

To activate the multi-descer, cd to your game folder and into the commands sub-folder. There -you’ll find the file default_cmdsets.py. In Python lingo all *.py files are called modules. -Open the module in a text editor. We won’t go into Evennia in-game Commands and Command sets -further here, but suffice to say Evennia allows you to change which commands (or versions of -commands) are available to the player from moment to moment depending on circumstance.

-

Add two new lines to the module as seen below:

-
# the file mygame/commands/default_cmdsets.py
-# [...]
-
-from evennia.contrib import multidescer   # <- added now
-
-class CharacterCmdSet(default_cmds.CharacterCmdSet):
-    """
-    The CharacterCmdSet contains general in-game commands like look,
-    get etc available on in-game Character objects. It is merged with
-    the AccountCmdSet when an Account puppets a Character.
-    """
-    key = "DefaultCharacter"
-
-    def at_cmdset_creation(self):
-        """
-        Populates the cmdset
-        """
-        super().at_cmdset_creation()
-        #
-        # any commands you add below will overload the default ones.
-        #
-        self.add(multidescer.CmdMultiDesc())      # <- added now
-# [...]
-
-
-

Note that Python cares about indentation, so make sure to indent with the same number of spaces as -shown above!

-

So what happens above? We import the module evennia/contrib/multidescer.py at the top. Once -imported we can access stuff inside that module using full stop (.). The multidescer is defined as -a class CmdMultiDesc (we could find this out by opening said module in a text editor). At the -bottom we create a new instance of this class and add it to the CharacterCmdSet class. For the -sake of this tutorial we only need to know that CharacterCmdSet contains all commands that should -be be available to the Character by default.

-

This whole thing will be triggered when the command set is first created, which happens on server -start. So we need to reload Evennia with @reload - no one will be disconnected by doing this. If -all went well you should now be able to use desc (or +desc) and find that you have more -possibilities:

-
> help +desc                  # get help on the command
-> +desc eyes = His eyes are blue.
-> +desc basic = A big guy.
-> +desc/set basic + + eyes    # we add an extra space between
-> look me
-A big guy. His eyes are blue.
-
-
-

If there are errors, a traceback will show in the server log - several lines of text showing -where the error occurred. Find where the error is by locating the line number related to the -default_cmdsets.py file (it’s the only one you’ve changed so far). Most likely you mis-spelled -something or missed the indentation. Fix it and either @reload again or run evennia start as -needed.

-
-
-

Customizing the multidescer syntax

-

As seen above the multidescer uses syntax like this (where |/ are Evennia’s tags for line breaks) -:

-
> +desc/set basic + |/|/ + cape + footwear + |/|/ + attitude
-
-
-

This use of + was prescribed by the Developer that coded this +desc command. What if the -Player doesn’t like this syntax though? Do players need to pester the dev to change it? Not -necessarily. While Evennia does not allow the player to build their own multi-descer on the command -line, it does allow for re-mapping the command syntax to one they prefer. This is done using the -nick command.

-

Here’s a nick that changes how to input the command above:

-
> nick setdesc $1 $2 $3 $4 = +desc/set $1 + |/|/ + $2 + $3 + |/|/ + $4
-
-
-

The string on the left will be matched against your input and if matching, it will be replaced with -the string on the right. The $-type tags will store space-separated arguments and put them into -the replacement. The nick allows shell-like wildcards, so you -can use *, ?, [...], [!...] etc to match parts of the input.

-

The same description as before can now be set as

-
> setdesc basic cape footwear attitude
-
-
-

With the nick functionality players can mitigate a lot of syntax dislikes even without the -developer changing the underlying Python code.

-
-
-
-

Next steps

-

If you are a Developer and are interested in making a more MUSH-like Evennia game, a good start is -to look into the Evennia Tutorial for a first MUSH-like game. -That steps through building a simple little game from scratch and helps to acquaint you with the -various corners of Evennia. There is also the Tutorial for running roleplaying sessions that can be of interest.

-

An important aspect of making things more familiar for Players is adding new and tweaking existing -commands. How this is done is covered by the Tutorial on adding new commands. You may also find it useful to shop through the evennia/contrib/ folder. The Tutorial -world is a small single-player quest you can try (it’s not very MUSH- -like but it does show many Evennia concepts in action). Beyond that there are many more -tutorials to try out. If you feel you want a more visual overview you can also look at -Evennia in pictures.

-

… And of course, if you need further help you can always drop into the Evennia chatroom or post a -question in our forum/mailing list!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Evennia-for-roleplaying-sessions.html b/docs/0.9.5/Evennia-for-roleplaying-sessions.html deleted file mode 100644 index b5940064e3..0000000000 --- a/docs/0.9.5/Evennia-for-roleplaying-sessions.html +++ /dev/null @@ -1,841 +0,0 @@ - - - - - - - - - Evennia for roleplaying sessions — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Evennia for roleplaying sessions

-

This tutorial will explain how to set up a realtime or play-by-post tabletop style game using a -fresh Evennia server.

-

The scenario is thus: You and a bunch of friends want to play a tabletop role playing game online. -One of you will be the game master and you are all okay with playing using written text. You want -both the ability to role play in real-time (when people happen to be online at the same time) as -well as the ability for people to post when they can and catch up on what happened since they were -last online.

-

This is the functionality we will be needing and using:

-
    -
  • The ability to make one of you the GM (game master), with special abilities.

  • -
  • A Character sheet that players can create, view and fill in. It can also be locked so only the -GM can modify it.

  • -
  • A dice roller mechanism, for whatever type of dice the RPG rules require.

  • -
  • Rooms, to give a sense of location and to compartmentalize play going on- This means both -Character movements from location to location and GM explicitly moving them around.

  • -
  • Channels, for easily sending text to all subscribing accounts, regardless of location.

  • -
  • Account-to-Account messaging capability, including sending to multiple recipients -simultaneously, regardless of location.

  • -
-

We will find most of these things are already part of vanilla Evennia, but that we can expand on the -defaults for our particular use-case. Below we will flesh out these components from start to finish.

-
-

Starting out

-

We will assume you start from scratch. You need Evennia installed, as per the Getting -Started instructions. Initialize a new game directory with evennia init <gamedirname>. In this tutorial we assume your game dir is simply named mygame. You can use the -default database and keep all other settings to default for now. Familiarize yourself with the -mygame folder before continuing. You might want to browse the [First Steps Coding](First-Steps- -Coding) tutorial, just to see roughly where things are modified.

-
-
-

The Game Master role

-

In brief:

-
    -
  • Simplest way: Being an admin, just give one account Admins permission using the standard @perm -command.

  • -
  • Better but more work: Make a custom command to set/unset the above, while tweaking the Character -to show your renewed GM status to the other accounts.

  • -
-
-

The permission hierarchy

-

Evennia has the following permission hierarchy out of -the box: Players, Helpers, Builders, Admins and finally Developers. We could change these but -then we’d need to update our Default commands to use the changes. We want to keep this simple, so -instead we map our different roles on top of this permission ladder.

-
    -
  1. Players is the permission set on normal players. This is the default for anyone creating a new -account on the server.

  2. -
  3. Helpers are like Players except they also have the ability to create/edit new help entries. -This could be granted to players who are willing to help with writing lore or custom logs for -everyone.

  4. -
  5. Builders is not used in our case since the GM should be the only world-builder.

  6. -
  7. Admins is the permission level the GM should have. Admins can do everything builders can -(create/describe rooms etc) but also kick accounts, rename them and things like that.

  8. -
  9. Developers-level permission are the server administrators, the ones with the ability to -restart/shutdown the server as well as changing the permission levels.

  10. -
-
-

The superuser is not part of the hierarchy and actually -completely bypasses it. We’ll assume server admin(s) will “just” be Developers.

-
-
-
-

How to grant permissions

-

Only Developers can (by default) change permission level. Only they have access to the @perm -command:

-
> @perm Yvonne
-Permissions on Yvonne: accounts
-
-> @perm Yvonne = Admins
-> @perm Yvonne
-Permissions on Yvonne: accounts, admins
-
-> @perm/del Yvonne = Admins
-> @perm Yvonne
-Permissions on Yvonne: accounts
-
-
-

There is no need to remove the basic Players permission when adding the higher permission: the -highest will be used. Permission level names are not case sensitive. You can also use both plural -and singular, so “Admins” gives the same powers as “Admin”.

-
-
-

Optional: Making a GM-granting command

-

Use of @perm works out of the box, but it’s really the bare minimum. Would it not be nice if other -accounts could tell at a glance who the GM is? Also, we shouldn’t really need to remember that the -permission level is called “Admins”. It would be easier if we could just do @gm <account> and -@notgm <account> and at the same time change something make the new GM status apparent.

-

So let’s make this possible. This is what we’ll do:

-
    -
  1. We’ll customize the default Character class. If an object of this class has a particular flag, -its name will have the string(GM) added to the end.

  2. -
  3. We’ll add a new command, for the server admin to assign the GM-flag properly.

  4. -
-
-

Character modification

-

Let’s first start by customizing the Character. We recommend you browse the beginning of the -Account page to make sure you know how Evennia differentiates between the OOC “Account -objects” (not to be confused with the Accounts permission, which is just a string specifying your -access) and the IC “Character objects”.

-

Open mygame/typeclasses/characters.py and modify the default Character class:

-
# in mygame/typeclasses/characters.py
-
-# [...]
-
-class Character(DefaultCharacter):
-    # [...]
-    def get_display_name(self, looker, **kwargs):
-        """
-        This method customizes how character names are displayed. We assume
-        only permissions of types "Developers" and "Admins" require
-        special attention.
-        """
-        name = self.key
-        selfaccount = self.account     # will be None if we are not puppeted
-        lookaccount = looker.account   #              - " -
-
-        if selfaccount and selfaccount.db.is_gm:
-           # A GM. Show name as name(GM)
-           name = "%s(GM)" % name
-
-        if lookaccount and \
-          (lookaccount.permissions.get("Developers") or lookaccount.db.is_gm):
-            # Developers/GMs see name(#dbref) or name(GM)(#dbref)
-            return "%s(#%s)" % (name, self.id)
-        else:
-            return name
-
-
-
-

Above, we change how the Character’s name is displayed: If the account controlling this Character is -a GM, we attach the string (GM) to the Character’s name so everyone can tell who’s the boss. If we -ourselves are Developers or GM’s we will see database ids attached to Characters names, which can -help if doing database searches against Characters of exactly the same name. We base the “gm- -ingness” on having an flag (an Attribute) named is_gm. We’ll make sure new GM’s -actually get this flag below.

-
-

Extra exercise: This will only show the (GM) text on Characters puppeted by a GM account, -that is, it will show only to those in the same location. If we wanted it to also pop up in, say, -who listings and channels, we’d need to make a similar change to the Account typeclass in -mygame/typeclasses/accounts.py. We leave this as an exercise to the reader.

-
-
-
-

New @gm/@ungm command

-

We will describe in some detail how to create and add an Evennia command here with the -hope that we don’t need to be as detailed when adding commands in the future. We will build on -Evennia’s default “mux-like” commands here.

-

Open mygame/commands/command.py and add a new Command class at the bottom:

-
# in mygame/commands/command.py
-
-from evennia import default_cmds
-
-# [...]
-
-import evennia
-
-class CmdMakeGM(default_cmds.MuxCommand):
-    """
-    Change an account's GM status
-
-    Usage:
-      @gm <account>
-      @ungm <account>
-
-    """
-    # note using the key without @ means both @gm !gm etc will work
-    key = "gm"
-    aliases = "ungm"
-    locks = "cmd:perm(Developers)"
-    help_category = "RP"
-
-    def func(self):
-        "Implement the command"
-        caller = self.caller
-
-        if not self.args:
-            caller.msg("Usage: @gm account or @ungm account")
-            return
-
-        accountlist = evennia.search_account(self.args) # returns a list
-        if not accountlist:
-            caller.msg("Could not find account '%s'" % self.args)
-            return
-        elif len(accountlist) > 1:
-            caller.msg("Multiple matches for '%s': %s" % (self.args, accountlist))
-            return
-        else:
-            account = accountlist[0]
-
-        if self.cmdstring == "gm":
-            # turn someone into a GM
-            if account.permissions.get("Admins"):
-                caller.msg("Account %s is already a GM." % account)
-            else:
-                account.permissions.add("Admins")
-                caller.msg("Account %s is now a GM." % account)
-                account.msg("You are now a GM (changed by %s)." % caller)
-                account.character.db.is_gm = True
-        else:
-            # @ungm was entered - revoke GM status from someone
-            if not account.permissions.get("Admins"):
-                caller.msg("Account %s is not a GM." % account)
-            else:
-                account.permissions.remove("Admins")
-                caller.msg("Account %s is no longer a GM." % account)
-                account.msg("You are no longer a GM (changed by %s)." % caller)
-                del account.character.db.is_gm
-
-
-
-

All the command does is to locate the account target and assign it the Admins permission if we -used @gm or revoke it if using the @ungm alias. We also set/unset the is_gm Attribute that is -expected by our new Character.get_display_name method from earlier.

-
-

We could have made this into two separate commands or opted for a syntax like @gm/revoke <accountname>. Instead we examine how this command was called (stored in self.cmdstring) in order -to act accordingly. Either way works, practicality and coding style decides which to go with.

-
-

To actually make this command available (only to Developers, due to the lock on it), we add it to -the default Account command set. Open the file mygame/commands/default_cmdsets.py and find the -AccountCmdSet class:

-
# mygame/commands/default_cmdsets.py
-
-# [...]
-from commands.command import CmdMakeGM
-
-class AccountCmdSet(default_cmds.AccountCmdSet):
-    # [...]
-    def at_cmdset_creation(self):
-        # [...]
-        self.add(CmdMakeGM())
-
-
-
-

Finally, issue the @reload command to update the server to your changes. Developer-level players -(or the superuser) should now have the @gm/@ungm command available.

-
-
-
-
-

Character sheet

-

In brief:

-
    -
  • Use Evennia’s EvTable/EvForm to build a Character sheet

  • -
  • Tie individual sheets to a given Character.

  • -
  • Add new commands to modify the Character sheet, both by Accounts and GMs.

  • -
  • Make the Character sheet lockable by a GM, so the Player can no longer modify it.

  • -
-
-

Building a Character sheet

-

There are many ways to build a Character sheet in text, from manually pasting strings together to -more automated ways. Exactly what is the best/easiest way depends on the sheet one tries to create. -We will here show two examples using the EvTable and EvForm utilities.Later we will create -Commands to edit and display the output from those utilities.

-
-

Note that due to the limitations of the wiki, no color is used in any of the examples. See the -text tag documentation for how to add color to the tables and forms.

-
-
-

Making a sheet with EvTable

-

EvTable is a text-table generator. It helps with displaying text in -ordered rows and columns. This is an example of using it in code:

-
# this can be tried out in a Python shell like iPython
-
-from evennia.utils import evtable
-
-# we hardcode these for now, we'll get them as input later
-STR, CON, DEX, INT, WIS, CHA = 12, 13, 8, 10, 9, 13
-
-table = evtable.EvTable("Attr", "Value",
-                        table = [
-                           ["STR", "CON", "DEX", "INT", "WIS", "CHA"],
-                           [STR, CON, DEX, INT, WIS, CHA]
-                        ], align='r', border="incols")
-
-
-

Above, we create a two-column table by supplying the two columns directly. We also tell the table to -be right-aligned and to use the “incols” border type (borders drawns only in between columns). The -EvTable class takes a lot of arguments for customizing its look, you can see some of the possible -keyword arguments here. Once you have the table you -could also retroactively add new columns and rows to it with table.add_row() and -table.add_column(): if necessary the table will expand with empty rows/columns to always remain -rectangular.

-

The result from printing the above table will be

-
table_string = str(table)
-
-print(table_string)
-
- Attr | Value
-~~~~~~+~~~~~~~
-  STR |    12
-  CON |    13
-  DEX |     8
-  INT |    10
-  WIS |     9
-  CHA |    13
-
-
-

This is a minimalistic but effective Character sheet. By combining the table_string with other -strings one could build up a reasonably full graphical representation of a Character. For more -advanced layouts we’ll look into EvForm next.

-
-
-

Making a sheet with EvForm

-

EvForm allows the creation of a two-dimensional “graphic” made by -text characters. On this surface, one marks and tags rectangular regions (“cells”) to be filled with -content. This content can be either normal strings or EvTable instances (see the previous section, -one such instance would be the table variable in that example).

-

In the case of a Character sheet, these cells would be comparable to a line or box where you could -enter the name of your character or their strength score. EvMenu also easily allows to update the -content of those fields in code (it use EvTables so you rebuild the table first before re-sending it -to EvForm).

-

The drawback of EvForm is that its shape is static; if you try to put more text in a region than it -was sized for, the text will be cropped. Similarly, if you try to put an EvTable instance in a field -too small for it, the EvTable will do its best to try to resize to fit, but will eventually resort -to cropping its data or even give an error if too small to fit any data.

-

An EvForm is defined in a Python module. Create a new file mygame/world/charsheetform.py and -modify it thus:

-
#coding=utf-8
-
-# in mygame/world/charsheetform.py
-
-FORMCHAR = "x"
-TABLECHAR = "c"
-
-FORM = """
-.--------------------------------------.
-|                                      |
-| Name: xxxxxxxxxxxxxx1xxxxxxxxxxxxxxx |
-|       xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
-|                                      |
- >------------------------------------<
-|                                      |
-| ccccccccccc  Advantages:             |
-| ccccccccccc   xxxxxxxxxxxxxxxxxxxxxx |
-| ccccccccccc   xxxxxxxxxx3xxxxxxxxxxx |
-| ccccccccccc   xxxxxxxxxxxxxxxxxxxxxx |
-| ccccc2ccccc  Disadvantages:          |
-| ccccccccccc   xxxxxxxxxxxxxxxxxxxxxx |
-| ccccccccccc   xxxxxxxxxx4xxxxxxxxxxx |
-| ccccccccccc   xxxxxxxxxxxxxxxxxxxxxx |
-|                                      |
-+--------------------------------------+
-"""
-
-
-

The #coding statement (which must be put on the very first line to work) tells Python to use the -utf-8 encoding for the file. Using the FORMCHAR and TABLECHAR we define what single-character we -want to use to “mark” the regions of the character sheet holding cells and tables respectively. -Within each block (which must be separated from one another by at least one non-marking character) -we embed identifiers 1-4 to identify each block. The identifier could be any single character except -for the FORMCHAR and TABLECHAR

-
-

You can still use FORMCHAR and TABLECHAR elsewhere in your sheet, but not in a way that it -would identify a cell/table. The smallest identifiable cell/table area is 3 characters wide -including the identifier (for example x2x).

-
-

Now we will map content to this form.

-
# again, this can be tested in a Python shell
-
-# hard-code this info here, later we'll ask the
-# account for this info. We will re-use the 'table'
-# variable from the EvTable example.
-
-NAME = "John, the wise old admin with a chip on his shoulder"
-ADVANTAGES = "Language-wiz, Intimidation, Firebreathing"
-DISADVANTAGES = "Bad body odor, Poor eyesight, Troubled history"
-
-from evennia.utils import evform
-
-# load the form from the module
-form = evform.EvForm("world/charsheetform.py")
-
-# map the data to the form
-form.map(cells={"1":NAME, "3": ADVANTAGES, "4": DISADVANTAGES},
-         tables={"2":table})
-
-
-

We create some RP-sounding input and re-use the table variable from the previous EvTable -example.

-
-

Note, that if you didn’t want to create the form in a separate module you could also load it -directly into the EvForm call like this: EvForm(form={"FORMCHAR":"x", "TABLECHAR":"c", "FORM": formstring}) where FORM specifies the form as a string in the same way as listed in the module -above. Note however that the very first line of the FORM string is ignored, so start with a \n.

-
-

We then map those to the cells of the form:

-
print(form)
-
-
-
.--------------------------------------.
-|                                      |
-| Name: John, the wise old admin with |
-|        a chip on his shoulder        |
-|                                      |
- >------------------------------------<
-|                                      |
-|  Attr|Value  Advantages:             |
-| ~~~~~+~~~~~   Language-wiz,          |
-|   STR|   12   Intimidation,          |
-|   CON|   13   Firebreathing          |
-|   DEX|    8  Disadvantages:          |
-|   INT|   10   Bad body odor, Poor    |
-|   WIS|    9   eyesight, Troubled     |
-|   CHA|   13   history                |
-|                                      |
-+--------------------------------------+
-
-
-

As seen, the texts and tables have been slotted into the text areas and line breaks have been added -where needed. We chose to just enter the Advantages/Disadvantages as plain strings here, meaning -long names ended up split between rows. If we wanted more control over the display we could have -inserted \n line breaks after each line or used a borderless EvTable to display those as well.

-
-
-
-

Tie a Character sheet to a Character

-

We will assume we go with the EvForm example above. We now need to attach this to a Character so -it can be modified. For this we will modify our Character class a little more:

-
# mygame/typeclasses/character.py
-
-from evennia.utils import evform, evtable
-
-[...]
-
-class Character(DefaultCharacter):
-    [...]
-    def at_object_creation(self):
-        "called only once, when object is first created"
-        # we will use this to stop account from changing sheet
-        self.db.sheet_locked = False
-        # we store these so we can build these on demand
-        self.db.chardata  = {"str": 0,
-                             "con": 0,
-                             "dex": 0,
-                             "int": 0,
-                             "wis": 0,
-                             "cha": 0,
-                             "advantages": "",
-                             "disadvantages": ""}
-        self.db.charsheet = evform.EvForm("world/charsheetform.py")
-        self.update_charsheet()
-
-    def update_charsheet(self):
-        """
-        Call this to update the sheet after any of the ingoing data
-        has changed.
-        """
-        data = self.db.chardata
-        table = evtable.EvTable("Attr", "Value",
-                        table = [
-                           ["STR", "CON", "DEX", "INT", "WIS", "CHA"],
-                           [data["str"], data["con"], data["dex"],
-                            data["int"], data["wis"], data["cha"]]],
-                           align='r', border="incols")
-        self.db.charsheet.map(tables={"2": table},
-                              cells={"1":self.key,
-                                     "3":data["advantages"],
-                                     "4":data["disadvantages"]})
-
-
-
-

Use @reload to make this change available to all newly created Characters. Already existing -Characters will not have the charsheet defined, since at_object_creation is only called once. -The easiest to force an existing Character to re-fire its at_object_creation is to use the -@typeclass command in-game:

-
@typeclass/force <Character Name>
-
-
-
-
-

Command for Account to change Character sheet

-

We will add a command to edit the sections of our Character sheet. Open -mygame/commands/command.py.

-
# at the end of mygame/commands/command.py
-
-ALLOWED_ATTRS = ("str", "con", "dex", "int", "wis", "cha")
-ALLOWED_FIELDNAMES = ALLOWED_ATTRS + \
-                     ("name", "advantages", "disadvantages")
-
-def _validate_fieldname(caller, fieldname):
-    "Helper function to validate field names."
-    if fieldname not in ALLOWED_FIELDNAMES:
-        err = "Allowed field names: %s" % (", ".join(ALLOWED_FIELDNAMES))
-        caller.msg(err)
-        return False
-    if fieldname in ALLOWED_ATTRS and not value.isdigit():
-        caller.msg("%s must receive a number." % fieldname)
-        return False
-    return True
-
-class CmdSheet(MuxCommand):
-    """
-    Edit a field on the character sheet
-
-    Usage:
-      @sheet field value
-
-    Examples:
-      @sheet name Ulrik the Warrior
-      @sheet dex 12
-      @sheet advantages Super strength, Night vision
-
-    If given without arguments, will view the current character sheet.
-
-    Allowed field names are:
-       name,
-       str, con, dex, int, wis, cha,
-       advantages, disadvantages
-
-    """
-
-    key = "sheet"
-    aliases = "editsheet"
-    locks = "cmd: perm(Players)"
-    help_category = "RP"
-
-    def func(self):
-        caller = self.caller
-        if not self.args or len(self.args) < 2:
-            # not enough arguments. Display the sheet
-            if sheet:
-                caller.msg(caller.db.charsheet)
-            else:
-                caller.msg("You have no character sheet.")
-            return
-
-        # if caller.db.sheet_locked:
-            caller.msg("Your character sheet is locked.")
-            return
-
-        # split input by whitespace, once
-        fieldname, value = self.args.split(None, 1)
-        fieldname = fieldname.lower() # ignore case
-
-        if not _validate_fieldnames(caller, fieldname):
-            return
-        if fieldname == "name":
-            self.key = value
-        else:
-            caller.chardata[fieldname] = value
-        caller.update_charsheet()
-        caller.msg("%s was set to %s." % (fieldname, value))
-
-
-
-

Most of this command is error-checking to make sure the right type of data was input. Note how the -sheet_locked Attribute is checked and will return if not set.

-

This command you import into mygame/commands/default_cmdsets.py and add to the CharacterCmdSet, -in the same way the @gm command was added to the AccountCmdSet earlier.

-
-
-

Commands for GM to change Character sheet

-

Game masters use basically the same input as Players do to edit a character sheet, except they can -do it on other players than themselves. They are also not stopped by any sheet_locked flags.

-
# continuing in mygame/commands/command.py
-
-class CmdGMsheet(MuxCommand):
-    """
-    GM-modification of char sheets
-
-    Usage:
-      @gmsheet character [= fieldname value]
-
-    Switches:
-      lock - lock the character sheet so the account
-             can no longer edit it (GM's still can)
-      unlock - unlock character sheet for Account
-             editing.
-
-    Examples:
-      @gmsheet Tom
-      @gmsheet Anna = str 12
-      @gmsheet/lock Tom
-
-    """
-    key = "gmsheet"
-    locks = "cmd: perm(Admins)"
-    help_category = "RP"
-
-    def func(self):
-        caller = self.caller
-        if not self.args:
-            caller.msg("Usage: @gmsheet character [= fieldname value]")
-
-        if self.rhs:
-            # rhs (right-hand-side) is set only if a '='
-            # was given.
-            if len(self.rhs) < 2:
-                caller.msg("You must specify both a fieldname and value.")
-                return
-            fieldname, value = self.rhs.split(None, 1)
-            fieldname = fieldname.lower()
-            if not _validate_fieldname(caller, fieldname):
-                return
-            charname = self.lhs
-        else:
-            # no '=', so we must be aiming to look at a charsheet
-            fieldname, value = None, None
-            charname = self.args.strip()
-
-        character = caller.search(charname, global_search=True)
-        if not character:
-            return
-
-        if "lock" in self.switches:
-            if character.db.sheet_locked:
-                caller.msg("The character sheet is already locked.")
-            else:
-                character.db.sheet_locked = True
-                caller.msg("%s can no longer edit their character sheet." % character.key)
-        elif "unlock" in self.switches:
-            if not character.db.sheet_locked:
-                caller.msg("The character sheet is already unlocked.")
-            else:
-                character.db.sheet_locked = False
-                caller.msg("%s can now edit their character sheet." % character.key)
-
-        if fieldname:
-            if fieldname == "name":
-                character.key = value
-            else:
-                character.db.chardata[fieldname] = value
-            character.update_charsheet()
-            caller.msg("You set %s's %s to %s." % (character.key, fieldname, value)
-        else:
-            # just display
-            caller.msg(character.db.charsheet)
-
-
-

The @gmsheet command takes an additional argument to specify which Character’s character sheet to -edit. It also takes /lock and /unlock switches to block the Player from tweaking their sheet.

-

Before this can be used, it should be added to the default CharacterCmdSet in the same way as the -normal @sheet. Due to the lock set on it, this command will only be available to Admins (i.e. -GMs) or higher permission levels.

-
-
-
-

Dice roller

-

Evennia’s contrib folder already comes with a full dice roller. To add it to the game, simply -import contrib.dice.CmdDice into mygame/commands/default_cmdsets.py and add CmdDice to the -CharacterCmdset as done with other commands in this tutorial. After a @reload you will be able -to roll dice using normal RPG-style format:

-
roll 2d6 + 3
-7
-
-
-

Use help dice to see what syntax is supported or look at evennia/contrib/dice.py to see how it’s -implemented.

-
-
-

Rooms

-

Evennia comes with rooms out of the box, so no extra work needed. A GM will automatically have all -needed building commands available. A fuller go-through is found in the Building -tutorial. Here are some useful highlights:

-
    -
  • @dig roomname;alias = exit_there;alias, exit_back;alias - this is the basic command for digging -a new room. You can specify any exit-names and just enter the name of that exit to go there.

  • -
  • @tunnel direction = roomname - this is a specialized command that only accepts directions in the -cardinal directions (n,ne,e,se,s,sw,w,nw) as well as in/out and up/down. It also automatically -builds “matching” exits back in the opposite direction.

  • -
  • @create/drop objectname - this creates and drops a new simple object in the current location.

  • -
  • @desc obj - change the look-description of the object.

  • -
  • @tel object = location - teleport an object to a named location.

  • -
  • @search objectname - locate an object in the database.

  • -
-
-

TODO: Describe how to add a logging room, that logs says and poses to a log file that people can -access after the fact.

-
-
-
-

Channels

-

Evennia comes with Channels in-built and they are described fully in the -documentation. For brevity, here are the relevant commands for normal use:

-
    -
  • @ccreate new_channel;alias;alias = short description - Creates a new channel.

  • -
  • addcom channel - join an existing channel. Use addcom alias = channel to add a new alias you -can use to talk to the channel, as many as desired.

  • -
  • delcom alias or channel - remove an alias from a channel or, if the real channel name is given, -unsubscribe completely.

  • -
  • @channels lists all available channels, including your subscriptions and any aliases you have -set up for them.

  • -
-

You can read channel history: if you for example are chatting on the public channel you can do -public/history to see the 20 last posts to that channel or public/history 32 to view twenty -posts backwards, starting with the 32nd from the end.

-
-
-

PMs

-

To send PMs to one another, players can use the @page (or tell) command:

-
page recipient = message
-page recipient, recipient, ... = message
-
-
-

Players can use page alone to see the latest messages. This also works if they were not online -when the message was sent.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Execute-Python-Code.html b/docs/0.9.5/Execute-Python-Code.html deleted file mode 100644 index d76450068d..0000000000 --- a/docs/0.9.5/Execute-Python-Code.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - Execute Python Code — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Execute Python Code

-

The @py command supplied with the default command set of Evennia allows you to execute Python -commands directly from inside the game. An alias to @py is simply “!”. Access to the @py -command should be severely restricted. This is no joke - being able to execute arbitrary Python -code on the server is not something you should entrust to just anybody.

-
@py 1+2
-<<< 3
-
-
-
-

Available variables

-

A few local variables are made available when running @py. These offer entry into the running -system.

-
    -
  • self / me - the calling object (i.e. you)

  • -
  • here - the current caller’s location

  • -
  • obj - a dummy Object instance

  • -
  • evennia - Evennia’s flat API - through this you can access all of Evennia.

  • -
-

For accessing other objects in the same room you need to use self.search(name). For objects in -other locations, use one of the evennia.search_* methods. See [below](./Execute-Python-Code.md#finding- -objects).

-
-
-

Returning output

-

This is an example where we import and test one of Evennia’s utilities found in -src/utils/utils.py, but also accessible through ev.utils:

-
@py from ev import utils; utils.time_format(33333)
-<<< Done.
-
-
-

Note that we didn’t get any return value, all we where told is that the code finished executing -without error. This is often the case in more complex pieces of code which has no single obvious -return value. To see the output from the time_format() function we need to tell the system to -echo it to us explicitly with self.msg().

-
@py from ev import utils; self.msg(str(utils.time_format(33333)))
-09:15
-<<< Done.
-
-
-
-

Warning: When using the msg function wrap our argument in str() to convert it into a string -above. This is not strictly necessary for most types of data (Evennia will usually convert to a -string behind the scenes for you). But for lists and tuples you will be confused by the output -if you don’t wrap them in str(): only the first item of the iterable will be returned. This is -because doing msg(text) is actually just a convenience shortcut; the full argument that msg -accepts is something called an outputfunc on the form (cmdname, (args), {kwargs}) (see the -message path for more info). Sending a list/tuple confuses Evennia to think you are -sending such a structure. Converting it to a string however makes it clear it should just be -displayed as-is.

-
-

If you were to use Python’s standard print, you will see the result in your current stdout (your -terminal by default, otherwise your log file).

-
-
-

Finding objects

-

A common use for @py is to explore objects in the database, for debugging and performing specific -operations that are not covered by a particular command.

-

Locating an object is best done using self.search():

-
@py self.search("red_ball")
-<<< Ball
-
-@py self.search("red_ball").db.color = "red"
-<<< Done.
-
-@py self.search("red_ball").db.color
-<<< red
-
-
-

self.search() is by far the most used case, but you can also search other database tables for -other Evennia entities like scripts or configuration entities. To do this you can use the generic -search entries found in ev.search_*.

-
@py evennia.search_script("sys_game_time")
-<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
-
-
-

(Note that since this becomes a simple statement, we don’t have to wrap it in self.msg() to get -the output). You can also use the database model managers directly (accessible through the objects -properties of database models or as evennia.managers.*). This is a bit more flexible since it -gives you access to the full range of database search methods defined in each manager.

-
@py evennia.managers.scripts.script_search("sys_game_time")
-<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
-
-
-

The managers are useful for all sorts of database studies.

-
@py ev.managers.configvalues.all()
-<<< [<ConfigValue: default_home]>, <ConfigValue:site_name>, ...]
-
-
-
-
-

Testing code outside the game

-

@py has the advantage of operating inside a running server (sharing the same process), where you -can test things in real time. Much of this can be done from the outside too though.

-

In a terminal, cd to the top of your game directory (this bit is important since we need access to -your config file) and run

-
evennia shell
-
-
-

Your default Python interpreter will start up, configured to be able to work with and import all -modules of your Evennia installation. From here you can explore the database and test-run individual -modules as desired.

-

It’s recommended that you get a more fully featured Python interpreter like -iPython. If you use a virtual environment, you can just get it -with pip install ipython. IPython allows you to better work over several lines, and also has a lot -of other editing features, such as tab-completion and __doc__-string reading.

-
$ evennia shell
-
-IPython 0.10 -- An enhanced Interactive Python
-...
-
-In [1]: import evennia
-In [2]: evennia.managers.objects.all()
-Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]
-
-
-

See the page about the Evennia-API for more things to explore.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/First-Steps-Coding.html b/docs/0.9.5/First-Steps-Coding.html deleted file mode 100644 index cbaaea041d..0000000000 --- a/docs/0.9.5/First-Steps-Coding.html +++ /dev/null @@ -1,397 +0,0 @@ - - - - - - - - - First Steps Coding — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

First Steps Coding

-

This section gives a brief step-by-step introduction on how to set up Evennia for the first time so -you can modify and overload the defaults easily. You should only need to do these steps once. It -also walks through you making your first few tweaks.

-

Before continuing, make sure you have Evennia installed and running by following the Getting -Started instructions. You should have initialized a new game folder with the -evennia --init foldername command. We will in the following assume this folder is called -“mygame”.

-

It might be a good idea to eye through the brief Coding Introduction too -(especially the recommendations in the section about the evennia “flat” API and about using evennia shell will help you here and in the future).

-

To follow this tutorial you also need to know the basics of operating your computer’s -terminal/command line. You also need to have a text editor to edit and create source text files. -There are plenty of online tutorials on how to use the terminal and plenty of good free text -editors. We will assume these things are already familiar to you henceforth.

-
-

Your First Changes

-

Below are some first things to try with your new custom modules. You can test these to get a feel -for the system. See also Tutorials for more step-by-step help and special cases.

-
-

Tweak Default Character

-

We will add some simple rpg attributes to our default Character. In the next section we will follow -up with a new command to view those attributes.

-
    -
  1. Edit mygame/typeclasses/characters.py and modify the Character class. The -at_object_creation method also exists on the DefaultCharacter parent and will overload it. The -get_abilities method is unique to our version of Character.

    -
    class Character(DefaultCharacter):
    -    # [...]
    -    def at_object_creation(self):
    -        """
    -        Called only at initial creation. This is a rather silly
    -        example since ability scores should vary from Character to
    -        Character and is usually set during some character
    -        generation step instead.
    -        """
    -        #set persistent attributes
    -        self.db.strength = 5
    -        self.db.agility = 4
    -        self.db.magic = 2
    -
    -    def get_abilities(self):
    -        """
    -        Simple access method to return ability
    -        scores as a tuple (str,agi,mag)
    -        """
    -        return self.db.strength, self.db.agility, self.db.magic
    -
    -
    -
  2. -
  3. Reload the server (you will still be connected to the game after doing -this). Note that if you examine yourself you will not see any new Attributes appear yet. Read -the next section to understand why.

  4. -
-
-

Updating Yourself

-

It’s important to note that the new Attributes we added above will only be stored on -newly created characters. The reason for this is simple: The at_object_creation method, where we -added those Attributes, is per definition only called when the object is first created, then never -again. This is usually a good thing since those Attributes may change over time - calling that hook -would reset them back to start values. But it also means that your existing character doesn’t have -them yet. You can see this by calling the get_abilities hook on yourself at this point:

-
# (you have to be superuser to use @py)
-@py self.get_abilities()
-<<< (None, None, None)
-
-
-

This is easily remedied.

-
@update self
-
-
-

This will (only) re-run at_object_creation on yourself. You should henceforth be able to get the -abilities successfully:

-
@py self.get_abilities()
-<<< (5, 4, 2)
-
-
-

This is something to keep in mind if you start building your world before your code is stable - -startup-hooks will not (and should not) automatically run on existing objects - you have to update -your existing objects manually. Luckily this is a one-time thing and pretty simple to do. If the -typeclass you want to update is in typeclasses.myclass.MyClass, you can do the following (e.g. -from evennia shell):

-
from typeclasses.myclass import MyClass
-# loop over all MyClass instances in the database
-# and call .swap_typeclass on them
-for obj in MyClass.objects.all():
-    obj.swap_typeclass(MyClass, run_start_hooks="at_object_creation")
-
-
-

Using swap_typeclass to the same typeclass we already have will re-run the creation hooks (this is -what the @update command does under the hood). From in-game you can do the same with @py:

-
@py typeclasses.myclass import MyClass;[obj.swap_typeclass(MyClass) for obj in
-MyClass.objects.all()]
-
-
-

See the Object Typeclass tutorial for more help and the -Typeclasses and Attributes page for detailed documentation about -Typeclasses and Attributes.

-
-
-

Troubleshooting: Updating Yourself

-

One may experience errors for a number of reasons. Common beginner errors are spelling mistakes, -wrong indentations or code omissions leading to a SyntaxError. Let’s say you leave out a colon -from the end of a class function like so: def at_object_creation(self). The client will reload -without issue. However, if you look at the terminal/console (i.e. not in-game), you will see -Evennia complaining (this is called a traceback):

-
Traceback (most recent call last):
-File "C:\mygame\typeclasses\characters.py", line 33
-     def at_object_creation(self)
-                                 ^
-SyntaxError: invalid syntax
-
-
-

Evennia will still be restarting and following the tutorial, doing @py self.get_abilities() will -return the right response (None, None, None). But when attempting to @typeclass/force self you -will get this response:

-
    AttributeError: 'DefaultObject' object has no attribute 'get_abilities'
-
-
-

The full error will show in the terminal/console but this is confusing since you did add -get_abilities before. Note however what the error says - you (self) should be a Character but -the error talks about DefaultObject. What has happened is that due to your unhandled SyntaxError -earlier, Evennia could not load the character.py module at all (it’s not valid Python). Rather -than crashing, Evennia handles this by temporarily falling back to a safe default - DefaultObject

-
    -
  • in order to keep your MUD running. Fix the original SyntaxError and reload the server. Evennia -will then be able to use your modified Character class again and things should work.

  • -
-
-

Note: Learning how to interpret an error traceback is a critical skill for anyone learning Python. -Full tracebacks will appear in the terminal/Console you started Evennia from. The traceback text can -sometimes be quite long, but you are usually just looking for the last few lines: The description of -the error and the filename + line number for where the error occurred. In the example above, we see -it’s a SyntaxError happening at line 33 of mygame\typeclasses\characters.py. In this case it -even points out where on the line it encountered the error (the missing colon). Learn to read -tracebacks and you’ll be able to resolve the vast majority of common errors easily.

-
-
-
-
-

Add a New Default Command

-

The @py command used above is only available to privileged users. We want any player to be able to -see their stats. Let’s add a new command to list the abilities we added in the previous -section.

-
    -
  1. Open mygame/commands/command.py. You could in principle put your command anywhere but this -module has all the imports already set up along with some useful documentation. Make a new class at -the bottom of this file:

    -
        class CmdAbilities(BaseCommand):
    -        """
    -        List abilities
    -
    -        Usage:
    -          abilities
    -
    -        Displays a list of your current ability values.
    -        """
    -        key = "abilities"
    -        aliases = ["abi"]
    -        lock = "cmd:all()"
    -        help_category = "General"
    -
    -        def func(self):
    -            """implements the actual functionality"""
    -
    -             str, agi, mag = self.caller.get_abilities()
    -             string = "STR: %s, AGI: %s, MAG: %s" % (str, agi, mag)
    -             self.caller.msg(string)
    -
    -
    -
  2. -
  3. Next you edit mygame/commands/default_cmdsets.py and add a new import to it near the top:

    -
        from commands.command import CmdAbilities
    -
    -
    -
  4. -
  5. In the CharacterCmdSet class, add the following near the bottom (it says where):

    -
        self.add(CmdAbilities())
    -
    -
    -
  6. -
  7. Reload the server (noone will be disconnected by doing this).

  8. -
-

You (and anyone else) should now be able to use abilities (or its alias abi) as part of your -normal commands in-game:

-
abilities
-STR: 5, AGI: 4, MAG: 2
-
-
-

See the Adding a Command tutorial for more examples and the -Commands section for detailed documentation about the Command system.

-
-
-

Make a New Type of Object

-

Let’s test to make a new type of object. This example is an “wise stone” object that returns some -random comment when you look at it, like this:

-
> look stone
-
-A very wise stone
-
-This is a very wise old stone.
-It grumbles and says: 'The world is like a rock of chocolate.'
-
-
-
    -
  1. Create a new module in mygame/typeclasses/. Name it wiseobject.py for this example.

  2. -
  3. In the module import the base Object (typeclasses.objects.Object). This is empty by default, -meaning it is just a proxy for the default evennia.DefaultObject.

  4. -
  5. Make a new class in your module inheriting from Object. Overload hooks on it to add new -functionality. Here is an example of how the file could look:

    -
    from random import choice
    -from typeclasses.objects import Object
    -
    -class WiseObject(Object):
    -    """
    -    An object speaking when someone looks at it. We
    -    assume it looks like a stone in this example.
    -    """
    -    def at_object_creation(self):
    -        """Called when object is first created"""
    -        self.db.wise_texts = \
    -               ["Stones have feelings too.",
    -                "To live like a stone is to not have lived at all.",
    -                "The world is like a rock of chocolate."]
    -
    -    def return_appearance(self, looker):
    -        """
    -        Called by the look command. We want to return
    -        a wisdom when we get looked at.
    -        """
    -        # first get the base string from the
    -        # parent's return_appearance.
    -        string = super().return_appearance(looker)
    -        wisewords = "\n\nIt grumbles and says: '%s'"
    -        wisewords = wisewords % choice(self.db.wise_texts)
    -        return string + wisewords
    -
    -
    -
  6. -
  7. Check your code for bugs. Tracebacks will appear on your command line or log. If you have a grave -Syntax Error in your code, the source file itself will fail to load which can cause issues with the -entire cmdset. If so, fix your bug and reload the server from the command line -(noone will be disconnected by doing this).

  8. -
  9. Use @create/drop stone:wiseobject.WiseObject to create a talkative stone. If the @create -command spits out a warning or cannot find the typeclass (it will tell you which paths it searched), -re-check your code for bugs and that you gave the correct path. The @create command starts looking -for Typeclasses in mygame/typeclasses/.

  10. -
  11. Use look stone to test. You will see the default description (“You see nothing special”) -followed by a random message of stony wisdom. Use @desc stone = This is a wise old stone. to make -it look nicer. See the Builder Docs for more information.

  12. -
-

Note that at_object_creation is only called once, when the stone is first created. If you make -changes to this method later, already existing stones will not see those changes. As with the -Character example above you can use @typeclass/force to tell the stone to re-run its -initialization.

-

The at_object_creation is a special case though. Changing most other aspects of the typeclass does -not require manual updating like this - you just need to @reload to have all changes applied -automatically to all existing objects.

-
-
-
-

Where to Go From Here?

-

There are more Tutorials, including one for building a whole little MUSH-like -game - that is instructive also if you have no interest in -MUSHes per se. A good idea is to also get onto the IRC -chat and the mailing -list to get in touch with the community and other -developers.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Game-Planning.html b/docs/0.9.5/Game-Planning.html deleted file mode 100644 index ce4eceb208..0000000000 --- a/docs/0.9.5/Game-Planning.html +++ /dev/null @@ -1,335 +0,0 @@ - - - - - - - - - Game Planning — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Game Planning

-

So you have Evennia up and running. You have a great game idea in mind. Now it’s time to start -cracking! But where to start? Here are some ideas for a workflow. Note that the suggestions on this -page are just that - suggestions. Also, they are primarily aimed at a lone hobby designer or a small -team developing a game in their free time.

-

Below are some minimal steps for getting the first version of a new game world going with players. -It’s worth to at least make the attempt to do these steps in order even if you are itching to jump -ahead in the development cycle. On the other hand, you should also make sure to keep your work fun -for you, or motivation will falter. Making a full game is a lot of work as it is, you’ll need all -your motivation to make it a reality.

-

Remember that 99.99999% of all great game ideas never lead to a game. Especially not to an online -game that people can actually play and enjoy. So our first all overshadowing goal is to beat those -odds and get something out the door! Even if it’s a scaled-down version of your dream game, -lacking many “must-have” features! It’s better to get it out there and expand on it later than to -code in isolation forever until you burn out, lose interest or your hard drive crashes.

-

Like is common with online games, getting a game out the door does not mean you are going to be -“finished” with the game - most MUDs add features gradually over the course of years - it’s often -part of the fun!

-
-

Planning (step 1)

-

This is what you do before having coded a single line or built a single room. Many prospective game -developers are very good at parts of this process, namely in defining what their world is “about”: -The theme, the world concept, cool monsters and so on. It is by all means very important to define -what is the unique appeal of your game. But it’s unfortunately not enough to make your game a -reality. To do that you must also have an idea of how to actually map those great ideas onto -Evennia.

-

A good start is to begin by planning out the basic primitives of the game and what they need to be -able to do. Below are a far-from-complete list of examples (and for your first version you should -definitely try for a much shorter list):

-
-

Systems

-

These are the behind-the-scenes features that exist in your game, often without being represented by -a specific in-game object.

-
    -
  • Should your game rules be enforced by coded systems or are you planning for human game masters to -run and arbitrate rules?

  • -
  • What are the actual mechanical game rules? How do you decide if an action succeeds or fails? What -“rolls” does the game need to be able to do? Do you base your game off an existing system or make up -your own?

  • -
  • Does the flow of time matter in your game - does night and day change? What about seasons? Maybe -your magic system is affected by the phase of the moon?

  • -
  • Do you want changing, global weather? This might need to operate in tandem over a large number of -rooms.

  • -
  • Do you want a game-wide economy or just a simple barter system? Or no formal economy at all?

  • -
  • Should characters be able to send mail to each other in-game?

  • -
  • Should players be able to post on Bulletin boards?

  • -
  • What is the staff hierarchy in your game? What powers do you want your staff to have?

  • -
  • What should a Builder be able to build and what commands do they need in order to do that?

  • -
  • etc.

  • -
-
-
-

Rooms

-

Consider the most basic room in your game.

-
    -
  • Is a simple description enough or should the description be able to change (such as with time, by -light conditions, weather or season)?

  • -
  • Should the room have different statuses? Can it have smells, sounds? Can it be affected by -dramatic weather, fire or magical effects? If so, how would this affect things in the room? Or are -these things something admins/game masters should handle manually?

  • -
  • Can objects be hidden in the room? Can a person hide in the room? How does the room display this?

  • -
  • etc.

  • -
-
-
-

Objects

-

Consider the most basic (non-player-controlled) object in your game.

-
    -
  • How numerous are your objects? Do you want large loot-lists or are objects just role playing props -created on demand?

  • -
  • Does the game use money? If so, is each coin a separate object or do you just store a bank account -value?

  • -
  • What about multiple identical objects? Do they form stacks and how are those stacks handled in -that case?

  • -
  • Does an object have weight or volume (so you cannot carry an infinite amount of them)?

  • -
  • Can objects be broken? If so, does it have a health value? Is burning it causing the same damage -as smashing it? Can it be repaired?

  • -
  • Is a weapon a specific type of object or are you supposed to be able to fight with a chair too? -Can you fight with a flower or piece of paper as well?

  • -
  • NPCs/mobs are also objects. Should they just stand around or should they have some sort of AI?

  • -
  • Are NPCs/mobs differet entities? How is an Orc different from a Kobold, in code - are they the -same object with different names or completely different types of objects, with custom code?

  • -
  • Should there be NPCs giving quests? If so, how would you track quest status and what happens when -multiple players try to do the same quest? Do you use instances or some other mechanism?

  • -
  • etc.

  • -
-
-
-

Characters

-

These are the objects controlled directly by Players.

-
    -
  • Can players have more than one Character active at a time or are they allowed to multi-play?

  • -
  • How does a Player create their Character? A Character-creation screen? Answering questions? -Filling in a form?

  • -
  • Do you want to use classes (like “Thief”, “Warrior” etc) or some other system, like Skill-based?

  • -
  • How do you implement different “classes” or “races”? Are they separate types of objects or do you -simply load different stats on a basic object depending on what the Player wants?

  • -
  • If a Character can hide in a room, what skill will decide if they are detected?

  • -
  • What skill allows a Character to wield a weapon and hit? Do they need a special skill to wield a -chair rather than a sword?

  • -
  • Does a Character need a Strength attribute to tell how much they can carry or which objects they -can smash?

  • -
  • What does the skill tree look like? Can a Character gain experience to improve? By killing -enemies? Solving quests? By roleplaying?

  • -
  • etc.

  • -
-

A MUD’s a lot more involved than you would think and these things hang together in a complex web. It -can easily become overwhelming and it’s tempting to want all functionality right out of the door. -Try to identify the basic things that “make” your game and focus only on them for your first -release. Make a list. Keep future expansions in mind but limit yourself.

-
-
-
-

Coding (step 2)

-

This is the actual work of creating the “game” part of your game. Many “game-designer” types tend to -gloss over this bit and jump directly to World Building. Vice versa, many “game-coder” types -tend to jump directly to this part without doing the Planning first. Neither way is good and -will lead to you having to redo all your hard work at least once, probably more.

-

Evennia’s Developer Central tries to help you with this bit of development. We -also have a slew of Tutorials with worked examples. Evennia tries hard to make this -part easier for you, but there is no way around the fact that if you want anything but a very basic -Talker-type game you will have to bite the bullet and code your game (or find a coder willing to -do it for you).

-

Even if you won’t code anything yourself, as a designer you need to at least understand the basic -paradigms of Evennia, such as Objects, Commands and Scripts and -how they hang together. We recommend you go through the [Tutorial World](Tutorial-World- -Introduction) in detail (as well as glancing at its code) to get at least a feel for what is -involved behind the scenes. You could also look through the tutorial for building a game from -scratch.

-

During Coding you look back at the things you wanted during the Planning phase and try to -implement them. Don’t be shy to update your plans if you find things easier/harder than you thought. -The earlier you revise problems, the easier they will be to fix.

-

A good idea is to host your code online (publicly or privately) using version control. Not only will -this make it easy for multiple coders to collaborate (and have a bug-tracker etc), it also means -your work is backed up at all times. The Version Control tutorial has -instructions for setting up a sane developer environment with proper version control.

-
-

“Tech Demo” Building

-

This is an integral part of your Coding. It might seem obvious to experienced coders, but it cannot -be emphasized enough that you should test things on a small scale before putting your untested -code into a large game-world. The earlier you test, the easier and cheaper it will be to fix bugs -and even rework things that didn’t work out the way you thought they would. You might even have to -go back to the Planning phase if your ideas can’t handle their meet with reality.

-

This means building singular in-game examples. Make one room and one object of each important type -and test so they work correctly in isolation. Then add more if they are supposed to interact with -each other in some way. Build a small series of rooms to test how mobs move around … and so on. In -short, a test-bed for your growing code. It should be done gradually until you have a fully -functioning (if not guaranteed bug-free) miniature tech demo that shows all the features you want -in the first release of your game. There does not need to be any game play or even a theme to your -tests, this is only for you and your co-coders to see. The more testing you do on this small scale, -the less headaches you will have in the next phase.

-
-
-
-

World Building (step 3)

-

Up until this point we’ve only had a few tech-demo objects in the database. This step is the act of -populating the database with a larger, thematic world. Too many would-be developers jump to this -stage too soon (skipping the Coding or even Planning stages). What if the rooms you build -now doesn’t include all the nice weather messages the code grows to support? Or the way you store -data changes under the hood? Your building work would at best require some rework and at worst you -would have to redo the whole thing. And whereas Evennia’s typeclass system does allow you to edit -the properties of existing objects, some hooks are only called at object creation … Suffice to -say you are in for a lot of unnecessary work if you build stuff en masse without having the -underlying code systems in some reasonable shape first.

-

So before starting to build, the “game” bit (Coding + Testing) should be more or less -complete, at least to the level of your initial release.

-

Before starting to build, you should also plan ahead again. Make sure it is clear to yourself and -your eventual builders just which parts of the world you want for your initial release. Establish -for everyone which style, quality and level of detail you expect. Your goal should not be to -complete your entire world in one go. You want just enough to make the game’s “feel” come across. -You want a minimal but functioning world where the intended game play can be tested and roughly -balanced. You can always add new areas later.

-

During building you get free and extensive testing of whatever custom build commands and systems you -have made at this point. Since Building often involves different people than those Coding, you also -get a chance to hear if some things are hard to understand or non-intuitive. Make sure to respond -to this feedback.

-
-
-

Alpha Release

-

As mentioned, don’t hold onto your world more than necessary. Get it out there with a huge Alpha -flag and let people try it! Call upon your alpha-players to try everything - they will find ways -to break your game in ways that you never could have imagined. In Alpha you might be best off to -focus on inviting friends and maybe other MUD developers, people who you can pester to give proper -feedback and bug reports (there will be bugs, there is no way around it). Follow the quick -instructions for Online Setup to make your game visible online. If you hadn’t -already, make sure to put up your game on the Evennia game index so -people know it’s in the works (actually, even pre-alpha games are allowed in the index so don’t be -shy)!

-
-
-

Beta Release/Perpetual Beta

-

Once things stabilize in Alpha you can move to Beta and let more people in. Many MUDs are in -perpetual beta, meaning they are never considered -“finished”, but just repeat the cycle of Planning, Coding, Testing and Building over and over as new -features get implemented or Players come with suggestions. As the game designer it is now up to you -to gradually perfect your vision.

-
-
-

Congratulate yourself!

-

You are worthy of a celebration since at this point you have joined the small, exclusive crowd who -have made their dream game a reality!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Gametime-Tutorial.html b/docs/0.9.5/Gametime-Tutorial.html deleted file mode 100644 index e81323332a..0000000000 --- a/docs/0.9.5/Gametime-Tutorial.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - Gametime Tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Gametime Tutorial

-

A lot of games use a separate time system we refer to as game time. This runs in parallel to what -we usually think of as real time. The game time might run at a different speed, use different -names for its time units or might even use a completely custom calendar. You don’t need to rely on a -game time system at all. But if you do, Evennia offers basic tools to handle these various -situations. This tutorial will walk you through these features.

-
-

A game time with a standard calendar

-

Many games let their in-game time run faster or slower than real time, but still use our normal -real-world calendar. This is common both for games set in present day as well as for games in -historical or futuristic settings. Using a standard calendar has some advantages:

-
    -
  • Handling repetitive actions is much easier, since converting from the real time experience to the -in-game perceived one is easy.

  • -
  • The intricacies of the real world calendar, with leap years and months of different length etc are -automatically handled by the system.

  • -
-

Evennia’s game time features assume a standard calendar (see the relevant section below for a custom -calendar).

-
-

Setting up game time for a standard calendar

-

All is done through the settings. Here are the settings you should use if you want a game time with -a standard calendar:

-
# in a file settings.py in mygame/server/conf
-# The time factor dictates if the game world runs faster (timefactor>1)
-# or slower (timefactor<1) than the real world.
-TIME_FACTOR = 2.0
-
-# The starting point of your game time (the epoch), in seconds.
-# In Python a value of 0 means Jan 1 1970 (use negatives for earlier
-# start date). This will affect the returns from the utils.gametime
-# module.
-TIME_GAME_EPOCH = None
-
-
-

By default, the game time runs twice as fast as the real time. You can set the time factor to be 1 -(the game time would run exactly at the same speed than the real time) or lower (the game time will -be slower than the real time). Most games choose to have the game time spinning faster (you will -find some games that have a time factor of 60, meaning the game time runs sixty times as fast as the -real time, a minute in real time would be an hour in game time).

-

The epoch is a slightly more complex setting. It should contain a number of seconds that would -indicate the time your game started. As indicated, an epoch of 0 would mean January 1st, 1970. If -you want to set your time in the future, you just need to find the starting point in seconds. There -are several ways to do this in Python, this method will show you how to do it in local time:

-
# We're looking for the number of seconds representing
-# January 1st, 2020
-from datetime import datetime
-import time
-start = datetime(2020, 1, 1)
-time.mktime(start.timetuple())
-
-
-

This should return a huge number - the number of seconds since Jan 1 1970. Copy that directly into -your settings (editing server/conf/settings.py):

-
# in a file settings.py in mygame/server/conf
-TIME_GAME_EPOCH = 1577865600
-
-
-

Reload the game with @reload, and then use the @time command. You should see something like -this:

-
+----------------------------+-------------------------------------+
-| Server time                |                                     |
-+~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
-| Current uptime             | 20 seconds                          |
-| Total runtime              | 1 day, 1 hour, 55 minutes           |
-| First start                | 2017-02-12 15:47:50.565000          |
-| Current time               | 2017-02-13 17:43:10.760000          |
-+----------------------------+-------------------------------------+
-| In-Game time               | Real time x 2                       |
-+~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
-| Epoch (from settings)      | 2020-01-01 00:00:00                 |
-| Total time passed:         | 1 day, 17 hours, 34 minutes         |
-| Current time               | 2020-01-02 17:34:55.430000          |
-+----------------------------+-------------------------------------+
-
-
-

The line that is most relevant here is the game time epoch. You see it shown at 2020-01-01. From -this point forward, the game time keeps increasing. If you keep typing @time, you’ll see the game -time updated correctly… and going (by default) twice as fast as the real time.

-
- -
-
-

A game time with a custom calendar

-

Using a custom calendar to handle game time is sometimes needed if you want to place your game in a -fictional universe. For instance you may want to create the Shire calendar which Tolkien described -having 12 months, each which 30 days. That would give only 360 days per year (presumably hobbits -weren’t really fond of the hassle of following the astronomical calendar). Another example would be -creating a planet in a different solar system with, say, days 29 hours long and months of only 18 -days.

-

Evennia handles custom calendars through an optional contrib module, called custom_gametime. -Contrary to the normal gametime module described above it is not active by default.

-
-

Setting up the custom calendar

-

In our first example of the Shire calendar, used by hobbits in books by Tolkien, we don’t really -need the notion of weeks… but we need the notion of months having 30 days, not 28.

-

The custom calendar is defined by adding the TIME_UNITS setting to your settings file. It’s a -dictionary containing as keys the name of the units, and as value the number of seconds (the -smallest unit for us) in this unit. Its keys must be picked among the following: “sec”, “min”, -“hour”, “day”, “week”, “month” and “year” but you don’t have to include them all. Here is the -configuration for the Shire calendar:

-
# in a file settings.py in mygame/server/conf
-TIME_UNITS = {"sec": 1,
-              "min": 60,
-              "hour": 60 * 60,
-              "day": 60 * 60 * 24,
-              "month": 60 * 60 * 24 * 30,
-              "year": 60 * 60 * 24 * 30 * 12 }
-
-
-

We give each unit we want as keys. Values represent the number of seconds in that unit. Hour is -set to 60 * 60 (that is, 3600 seconds per hour). Notice that we don’t specify the week unit in this -configuration: instead, we skip from days to months directly.

-

In order for this setting to work properly, remember all units have to be multiples of the previous -units. If you create “day”, it needs to be multiple of hours, for instance.

-

So for our example, our settings may look like this:

-
# in a file settings.py in mygame/server/conf
-# Time factor
-TIME_FACTOR = 4
-
-# Game time epoch
-TIME_GAME_EPOCH = 0
-
-# Units
-TIME_UNITS = {
-        "sec": 1,
-        "min": 60,
-        "hour": 60 * 60,
-        "day": 60 * 60 * 24,
-        "month": 60 * 60 * 24 * 30,
-        "year": 60 * 60 * 24 * 30 * 12,
-}
-
-
-

Notice we have set a time epoch of 0. Using a custom calendar, we will come up with a nice display -of time on our own. In our case the game time starts at year 0, month 0, day 0, and at midnight.

-

Note that while we use “month”, “week” etc in the settings, your game may not use those terms in- -game, instead referring to them as “cycles”, “moons”, “sand falls” etc. This is just a matter of you -displaying them differently. See next section.

-
-
-

A command to display the current game time

-

As pointed out earlier, the @time command is meant to be used with a standard calendar, not a -custom one. We can easily create a new command though. We’ll call it time, as is often the case -on other MU*. Here’s an example of how we could write it (for the example, you can create a file -showtime.py in your commands directory and paste this code in it):

-
# in a file mygame/commands/gametime.py
-
-from evennia.contrib import custom_gametime
-
-from commands.command import Command
-
-class CmdTime(Command):
-
-    """
-    Display the time.
-
-    Syntax:
-        time
-
-    """
-
-    key = "time"
-    locks = "cmd:all()"
-
-    def func(self):
-        """Execute the time command."""
-        # Get the absolute game time
-        year, month, day, hour, min, sec = custom_gametime.custom_gametime(absolute=True)
-        string = "We are in year {year}, day {day}, month {month}."
-        string += "\nIt's {hour:02}:{min:02}:{sec:02}."
-        self.msg(string.format(year=year, month=month, day=day,
-                hour=hour, min=min, sec=sec))
-
-
-

Don’t forget to add it in your CharacterCmdSet to see this command:

-
# in mygame/commands/default_cmdset.py
-
-from commands.gametime import CmdTime   # <-- Add
-
-# ...
-
-class CharacterCmdSet(default_cmds.CharacterCmdSet):
-    """
-    The `CharacterCmdSet` contains general in-game commands like `look`,
-    `get`, etc available on in-game Character objects. It is merged with
-    the `AccountCmdSet` when an Account puppets a Character.
-    """
-    key = "DefaultCharacter"
-
-    def at_cmdset_creation(self):
-        """
-        Populates the cmdset
-        """
-        super().at_cmdset_creation()
-        # ...
-        self.add(CmdTime())   # <- Add
-
-
-

Reload your game with the @reload command. You should now see the time command. If you enter -it, you might see something like:

-
We are in year 0, day 0, month 0.
-It's 00:52:17.
-
-
-

You could display it a bit more prettily with names for months and perhaps even days, if you want. -And if “months” are called “moons” in your game, this is where you’d add that.

-
- -
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Getting-Started.html b/docs/0.9.5/Getting-Started.html deleted file mode 100644 index 03bed3bfac..0000000000 --- a/docs/0.9.5/Getting-Started.html +++ /dev/null @@ -1,638 +0,0 @@ - - - - - - - - - Getting Started — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - -
-
-
-
- -
-

Getting Started

-

This will help you download, install and start Evennia for the first time.

-
-

Note: You don’t need to make anything visible to the ‘net in order to run and -test out Evennia. Apart from downloading and updating you don’t even need an -internet connection until you feel ready to share your game with the world.

-
- -
-

Quick Start

-

For the impatient. If you have trouble with a step, you should jump on to the -more detailed instructions for your platform.

-
    -
  1. Install Python, GIT and python-virtualenv. Start a Console/Terminal.

  2. -
  3. cd to some place you want to do your development (like a folder -/home/anna/muddev/ on Linux or a folder in your personal user directory on Windows).

  4. -
  5. git clone https://github.com/evennia/evennia.git

  6. -
  7. virtualenv evenv

  8. -
  9. source evenv/bin/activate (Linux, Mac), evenv\Scripts\activate (Windows)

  10. -
  11. pip install -e evennia

  12. -
  13. evennia --init mygame

  14. -
  15. cd mygame

  16. -
  17. evennia migrate

  18. -
  19. evennia start (make sure to make a superuser when asked) -Evennia should now be running and you can connect to it by pointing a web browser to -http://localhost:4001 or a MUD telnet client to localhost:4000 (use 127.0.0.1 if your OS does -not recognize localhost).

  20. -
-

We also release Docker images -based on master and develop branches.

-
-
-

Requirements

-

Any system that supports Python3.7+ should work. We’ll describe how to install -everything in the following sections.

-
    -
  • Linux/Unix

  • -
  • Windows (Vista, Win7, Win8, Win10)

  • -
  • Mac OSX (>=10.5 recommended)

  • -
  • Python (v3.7, 3.8 or 3.9)

    -
      -
    • virtualenv for making isolated -Python environments. Installed with pip install virtualenv.

    • -
    -
  • -
  • GIT - version control software for getting and -updating Evennia itself - Mac users can use the -git-osx-installer or the -MacPorts version.

  • -
  • Twisted (v21.0+)

    -
      -
    • ZopeInterface (v3.0+) - usually included in -Twisted packages

    • -
    • Linux/Mac users may need the gcc and python-dev packages or equivalent.

    • -
    • Windows users need MS Visual C++ and maybe -pypiwin32.

    • -
    -
  • -
  • Django (v3.2.x), be warned that latest dev -version is usually untested with Evennia)

  • -
-
-
-

Linux Install

-

If you run into any issues during the installation and first start, please -check out Linux Troubleshooting.

-

For Debian-derived systems (like Ubuntu, Mint etc), start a terminal and -install the dependencies:

-
sudo apt-get update
-sudo apt-get install python3 python3-pip python3-dev python3-setuptools python3-git
-python3-virtualenv gcc
-
-# If you are using an Ubuntu version that defaults to Python3, like 18.04+, use this instead:
-sudo apt-get update
-sudo apt-get install python3.7 python3-pip python3.7-dev python3-setuptools virtualenv gcc
-
-
-
-

Note that, the default Python version for your distribution may still not be Python3.7 after this. -This is ok - we’ll specify exactly which Python to use later. -You should make sure to not be root after this step, running as root is a -security risk. Now create a folder where you want to do all your Evennia -development:

-
mkdir muddev
-cd muddev
-
-
-

Next we fetch Evennia itself:

-
git clone https://github.com/evennia/evennia.git
-
-
-

A new folder evennia will appear containing the Evennia library. This only -contains the source code though, it is not installed yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a virtualenv. If you are unsure about what a -virtualenv is and why it’s useful, see the Glossary entry on -virtualenv.

-

Run python -V to see which version of Python your system defaults to.

-
# If your Linux defaults to Python3.7+:
-virtualenv evenv
-
-# If your Linux defaults to Python2 or an older version
-# of Python3, you must instead point to Python3.7+ explicitly:
-virtualenv -p /usr/bin/python3.7 evenv
-
-
-

A new folder evenv will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system (or the Linux distro lagging behind -on Python package versions). It will also always use the right version of Python. -Activate the virtualenv:

-
source evenv/bin/activate
-
-
-

The text (evenv) should appear next to your prompt to show that the virtual -environment is active.

-
-

Remember that you need to activate the virtualenv like this every time you -start a new terminal to get access to the Python packages (notably the -important evennia program) we are about to install.

-
-

Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the evennia/ and evenv/ -folders) and run

-
pip install -e evennia
-
-
-

For more info about pip, see the Glossary entry on pip. If -install failed with any issues, see Linux Troubleshooting.

-

Next we’ll start our new game, here called “mygame”. This will create yet -another new folder where you will be creating your new game:

-
evennia --init mygame
-
-
-

Your final folder structure should look like this:

-
./muddev
-    evenv/
-    evennia/
-    mygame/
-
-
-

You can configure Evennia extensively, for example -to use a different database. For now we’ll just stick -to the defaults though.

-
cd mygame
-evennia migrate      # (this creates the database)
-evennia start        # (create a superuser when asked. Email is optional.)
-
-
-
-

Server logs are found in mygame/server/logs/. To easily view server logs -live in the terminal, use evennia -l (exit the log-view with Ctrl-C).

-
-

Your game should now be running! Open a web browser at http://localhost:4001 -or point a telnet client to localhost:4000 and log in with the user you -created. Check out where to go next.

-
-
-

Mac Install

-

The Evennia server is a terminal program. Open the terminal e.g. from -Applications->Utilities->Terminal. Here is an introduction to the Mac -terminal -if you are unsure how it works. If you run into any issues during the -installation, please check out Mac Troubleshooting.

-
    -
  • Python should already be installed but you must make sure it’s a high enough version. -(This discusses -how you may upgrade it). Remember that you need Python3.7, not Python2.7!

  • -
  • GIT can be obtained with -git-osx-installer or via -MacPorts as described -here.

  • -
  • If you run into issues with installing Twisted later you may need to -install gcc and the Python headers.

  • -
-

After this point you should not need sudo or any higher privileges to install anything.

-

Now create a folder where you want to do all your Evennia development:

-
mkdir muddev
-cd muddev
-
-
-

Next we fetch Evennia itself:

-
git clone https://github.com/evennia/evennia.git
-
-
-

A new folder evennia will appear containing the Evennia library. This only -contains the source code though, it is not installed yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a virtualenv. If you are unsure about what a -virtualenv is and why it’s useful, see the Glossary entry on virtualenv.

-

Run python -V to check which Python your system defaults to.

-
# If your Mac defaults to Python3:
-virtualenv evenv
-
-# If your Mac defaults to Python2 you need to specify the Python3.7 binary explicitly:
-virtualenv -p /path/to/your/python3.7 evenv
-
-
-

A new folder evenv will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system. Activate the virtualenv:

-
source evenv/bin/activate
-
-
-

The text (evenv) should appear next to your prompt to show the virtual -environment is active.

-
-

Remember that you need to activate the virtualenv like this every time you -start a new terminal to get access to the Python packages (notably the -important evennia program) we are about to install.

-
-

Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the evennia/ and evenv/ -folders) and run

-
pip install --upgrade pip   # Old pip versions may be an issue on Mac.
-pip install --upgrade setuptools   # Ditto concerning Mac issues.
-pip install -e evennia
-
-
-

For more info about pip, see the Glossary entry on pip. If -install failed with any issues, see Mac Troubleshooting.

-

Next we’ll start our new game. We’ll call it “mygame” here. This creates a new -folder where you will be creating your new game:

-
evennia --init mygame
-
-
-

Your final folder structure should look like this:

-
./muddev
-    evenv/
-    evennia/
-    mygame/
-
-
-

You can configure Evennia extensively, for example -to use a different database. We’ll go with the -defaults here.

-
cd mygame
-evennia migrate  # (this creates the database)
-evennia start    # (create a superuser when asked. Email is optional.)
-
-
-
-

Server logs are found in mygame/server/logs/. To easily view server logs -live in the terminal, use evennia -l (exit the log-view with Ctrl-C).

-
-

Your game should now be running! Open a web browser at http://localhost:4001 -or point a telnet client to localhost:4000 and log in with the user you -created. Check out where to go next.

-
-
-

Windows Install

-

If you run into any issues during the installation, please check out -Windows Troubleshooting.

-
-

If you are running Windows10, consider using the Windows Subsystem for Linux -(WSL) instead. -You should then follow the Linux install instructions above.

-
-

The Evennia server itself is a command line program. In the Windows launch -menu, start All Programs -> Accessories -> command prompt and you will get -the Windows command line interface. Here is one of many tutorials on using the Windows command -line -if you are unfamiliar with it.

-
    -
  • Install Python from the Python homepage. You will -need to be a -Windows Administrator to install packages. You want Python version 3.7.0 (latest verified -version), usually -the 64-bit version (although it doesn’t matter too much). When installing, make sure -to check-mark all install options, especially the one about making Python -available on the path (you may have to scroll to see it). This allows you to -just write python in any console without first finding where the python -program actually sits on your hard drive.

  • -
  • You need to also get GIT and install it. You -can use the default install options but when you get asked to “Adjust your PATH -environment”, you should select the second option “Use Git from the Windows -Command Prompt”, which gives you more freedom as to where you can use the -program.

  • -
  • Finally you must install the Microsoft Visual C++ compiler for -Python. Download and run the linked installer and -install the C++ tools. Keep all the defaults. Allow the install of the “Win10 SDK”, even if you are -on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due -to a failure to build the “Twisted wheels”, this is where you are missing things.

  • -
  • You may need the pypiwin32 Python headers. Install -these only if you have issues.

  • -
-

You can install Evennia wherever you want. cd to that location and create a -new folder for all your Evennia development (let’s call it muddev).

-
mkdir muddev
-cd muddev
-
-
-
-

Hint: If cd isn’t working you can use pushd instead to force the -directory change.

-
-

Next we fetch Evennia itself:

-
git clone https://github.com/evennia/evennia.git
-
-
-

A new folder evennia will appear containing the Evennia library. This only -contains the source code though, it is not installed yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a virtualenv. If you are unsure about what a -virtualenv is and why it’s useful, see the Glossary entry on virtualenv.

-

In your console, try python -V to see which version of Python your system -defaults to.

-
pip install virtualenv
-
-# If your setup defaults to Python3.7:
-virtualenv evenv
-
-# If your setup defaults to Python2, specify path to python3.exe explicitly:
-virtualenv -p C:\Python37\python.exe evenv
-
-# If you get an infinite spooling response, press CTRL + C to interrupt and try using:
-python -m venv evenv
-
-
-
-

A new folder evenv will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system. Activate the virtualenv:

-
# If you are using a standard command prompt, you can use the following:
-evenv\scripts\activate.bat
-
-# If you are using a PS Shell, Git Bash, or other, you can use the following:
-.\evenv\scripts\activate
-
-
-
-

The text (evenv) should appear next to your prompt to show the virtual -environment is active.

-
-

Remember that you need to activate the virtualenv like this every time you -start a new console window if you want to get access to the Python packages -(notably the important evennia program) we are about to install.

-
-

Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the evennia and evenv -folders when you use the dir command) and run

-
pip install -e evennia
-
-
-

For more info about pip, see the Glossary entry on pip. If -the install failed with any issues, see [Windows Troubleshooting](./Getting-Started.md#windows- -troubleshooting). -Next we’ll start our new game, we’ll call it “mygame” here. This creates a new folder where you will -be -creating your new game:

-
evennia --init mygame
-
-
-

Your final folder structure should look like this:

-
path\to\muddev
-    evenv\
-    evennia\
-    mygame\
-
-
-

You can configure Evennia extensively, for example -to use a different database. We’ll go with the -defaults here.

-
cd mygame
-evennia migrate    # (this creates the database)
-evennia start      # (create a superuser when asked. Email is optional.)
-
-
-
-

Server logs are found in mygame/server/logs/. To easily view server logs -live in the terminal, use evennia -l (exit the log-view with Ctrl-C).

-
-

Your game should now be running! Open a web browser at http://localhost:4001 -or point a telnet client to localhost:4000 and log in with the user you -created. Check out where to go next.

-
-
-

Where to Go Next

-

Welcome to Evennia! Your new game is fully functioning, but empty. If you just -logged in, stand in the Limbo room and run

-
@batchcommand tutorial_world.build
-
-
-

to build Evennia’s tutorial world - it’s a small solo quest to -explore. Only run the instructed @batchcommand once. You’ll get a lot of text scrolling by as the -tutorial is built. Once done, the tutorial exit will have appeared out of Limbo - just write -tutorial to enter it.

-

Once you get back to Limbo from the tutorial (if you get stuck in the tutorial quest you can do -@tel #2 to jump to Limbo), a good idea is to learn how to [start, stop and reload](Start-Stop- -Reload) the Evennia server. You may also want to familiarize yourself with some commonly used terms -in our Glossary. After that, why not experiment with creating some new items and build -some new rooms out from Limbo.

-

From here on, you could move on to do one of our introductory tutorials or simply dive -headlong into Evennia’s comprehensive manual. While -Evennia has no major game systems out of the box, we do supply a range of optional contribs that -you can use or borrow from. They range from dice rolling and alternative color schemes to barter and -combat systems. You can find the growing list of contribs -here.

-

If you have any questions, you can always ask in the developer -chat -#evennia on irc.freenode.net or by posting to the Evennia -forums. You can also join the Discord -Server.

-

Finally, if you are itching to help out or support Evennia (awesome!) have an -issue to report or a feature to request, see here.

-

Enjoy your stay!

-
-
-

Troubleshooting

-

If you have issues with installing or starting Evennia for the first time, -check the section for your operating system below. If you have an issue not -covered here, please report it -so it can be fixed or a workaround found!

-

Remember, the server logs are in mygame/server/logs/. To easily view server logs in the terminal, -you can run evennia -l, or (in the future) start the server with evennia start -l.

-
-

Linux Troubleshooting

-
    -
  • If you get an error when installing Evennia (especially with lines mentioning -failing to include Python.h) then try sudo apt-get install python3-setuptools python3-dev. -Once installed, run pip install -e evennia again.

  • -
  • Under some not-updated Linux distributions you may run into errors with a -too-old setuptools or missing functools. If so, update your environment -with pip install --upgrade pip wheel setuptools. Then try pip install -e evennia again.

  • -
  • If you get an setup.py not found error message while trying to pip install, make sure you are -in the right directory. You should be at the same level of the evenv directory, and the -evennia git repository. Note that there is an evennia directory inside of the repository too.

  • -
  • One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/ with errors -like distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('incremental>=16.10.1'). This appears possible to solve by simply updating Ubuntu -with sudo apt-get update && sudo apt-get dist-upgrade.

  • -
  • Users of Fedora (notably Fedora 24) has reported a gcc error saying the directory -/usr/lib/rpm/redhat/redhat-hardened-cc1 is missing, despite gcc itself being installed. The -confirmed work-around seems to be to -install the redhat-rpm-config package with e.g. sudo dnf install redhat-rpm-config.

  • -
  • Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues -with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to -yourself?)

  • -
-
-
-

Mac Troubleshooting

-
    -
  • Mac users have reported a critical MemoryError when trying to start Evennia on Mac with a Python -version below 2.7.12. If you get this error, update to the latest XCode and Python2 version.

  • -
  • Some Mac users have reported not being able to connect to localhost (i.e. your own computer). If -so, try to connect to 127.0.0.1 instead, which is the same thing. Use port 4000 from mud clients -and port 4001 from the web browser as usual.

  • -
-
-
-

Windows Troubleshooting

-
    -
  • If you installed Python but the python command is not available (even in a new console), then -you might have missed installing Python on the path. In the Windows Python installer you get a list -of options for what to install. Most or all options are pre-checked except this one, and you may -even have to scroll down to see it. Reinstall Python and make sure it’s checked.

  • -
  • If your MUD client cannot connect to localhost:4000, try the equivalent 127.0.0.1:4000 -instead. Some MUD clients on Windows does not appear to understand the alias localhost.

  • -
  • If you run virtualenv evenv and get a 'virtualenv' is not recognized as an internal or external command, operable program or batch file. error, you can mkdir evenv, cd evenv and then python -m virtualenv . as a workaround.

  • -
  • Some Windows users get an error installing the Twisted ‘wheel’. A wheel is a pre-compiled binary -package for Python. A common reason for this error is that you are using a 32-bit version of Python, -but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a -slightly older Twisted version. So if, say, version 18.1 failed, install 18.0 manually with pip install twisted==18.0. Alternatively you could try to get a 64-bit version of Python (uninstall the -32bit one). If so, you must then deactivate the virtualenv, delete the evenv folder and recreate -it anew (it will then use the new Python executable).

  • -
  • If your server won’t start, with no error messages (and no log files at all when starting from -scratch), try to start with evennia ipstart instead. If you then see an error about system cannot find the path specified, it may be that the file evennia/evennia/server/twistd.bat has the wrong -path to the twistd executable. This file is auto-generated, so try to delete it and then run -evennia start to rebuild it and see if it works. If it still doesn’t work you need to open it in a -text editor like Notepad. It’s just one line containing the path to the twistd.exe executable as -determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and -you should update the line to the real location.

  • -
  • Some users have reported issues with Windows WSL and anti-virus software during Evennia -development. Timeout errors and the inability to run evennia connections may be due to your anti- -virus software interfering. Try disabling or changing your anti-virus software settings.

  • -
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Glossary.html b/docs/0.9.5/Glossary.html deleted file mode 100644 index a20d70b682..0000000000 --- a/docs/0.9.5/Glossary.html +++ /dev/null @@ -1,513 +0,0 @@ - - - - - - - - - Glossary — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Glossary

-

This explains common recurring terms used in the Evennia docs. It will be expanded as needed.

-
    -
  • account - the player’s account on the game

  • -
  • admin-site - the Django web page for manipulating the database

  • -
  • attribute - persistent, custom data stored on typeclasses

  • -
  • channel - game communication channels

  • -
  • character - the player’s avatar in the game, controlled from -account

  • -
  • core - a term used for the code distributed with Evennia proper

  • -
  • django - web framework Evennia uses for database access and web integration

  • -
  • field - a typeclass property representing a database -column

  • -
  • git - the version-control system we use

  • -
  • github - the online hosting of our source code

  • -
  • migrate - updating the database schema

  • -
  • multisession mode` - a setting defining how users connect to Evennia

  • -
  • object - Python instance, general term or in-game -typeclass

  • -
  • pip - the Python installer

  • -
  • player - the human connecting to the game with their client

  • -
  • puppet - when an account controls an in-game -object

  • -
  • property - a python property

  • -
  • evenv - see virtualenv

  • -
  • repository - a store of source code + source history

  • -
  • script - a building block for custom storage, systems and time-keepint

  • -
  • session - represents one client connection

  • -
  • ticker - Allows to run events on a steady ‘tick’

  • -
  • twisted - networking engine responsible for Evennia’s event loop and -communications

  • -
  • typeclass - Evennia’s database-connected Python class

  • -
  • upstream - see github

  • -
  • virtualenv - a Python program and way to make an isolated Python install

  • -
-
-
-

account

-

The term ‘account’ refers to the player’s unique account on the game. It is -represented by the Account typeclass and holds things like email, password, -configuration etc.

-

When a player connects to the game, they connect to their account. The account has no -representation in the game world. Through their Account they can instead choose to -puppet one (or more, depending on game mode) Characters in -the game.

-

In the default multisession mode of Evennia, you immediately start -puppeting a Character with the same name as your Account when you log in - mimicking how older -servers used to work.

-
-
-

admin-site

-

This usually refers to Django’s Admin site or database-administration web page -(link to Django docs). The admin site is -an automatically generated web interface to the database (it can be customized extensively). It’s -reachable from the admin link on the default Evennia website you get with your server.

-
-
-

attribute

-

The term Attribute should not be confused with (properties or -fields. The Attribute represents arbitrary pieces of data that can be attached -to any typeclassed entity in Evennia. Attributes allows storing new persistent -data on typeclasses without changing their underlying database schemas. Read more about Attributes -here.

-
-
-

channel

-

A Channel refers to an in-game communication channel. It’s an entity that people subscribe to and -which re-distributes messages between all subscribers. Such subscribers default to being -Accounts, for out-of-game communication but could also be Objects (usually -Characters) if one wanted to adopt Channels for things like in-game walkie- -talkies or phone systems. It is represented by the Channel typeclass. You can read more about the -comm system here.

-
-
-

character

-

The Character is the term we use for the default avatar being puppeted by the -account in the game world. It is represented by the Character typeclass (which -is a child of Object). Many developers use children of this class to represent -monsters and other NPCs. You can read more about it here.

-
-
-

django

-

Django is a professional and very popular Python web framework, -similar to Rails for the Ruby language. It is one of Evennia’s central library dependencies (the -other one is Twisted). Evennia uses Django for two main things - to map all -database operations to Python and for structuring our web site.

-

Through Django, we can work with any supported database (SQlite3, Postgres, MySQL …) using generic -Python instead of database-specific SQL: A database table is represented in Django as a Python class -(called a model). An Python instance of such a class represents a row in that table.

-

There is usually no need to know the details of Django’s database handling in order to use Evennia - -it will handle most of the complexity for you under the hood using what we call -typeclasses. But should you need the power of Django you can always get it. -Most commonly people want to use “raw” Django when doing more advanced/custom database queries than -offered by Evennia’s default search functions. One will then need -to read about Django’s querysets. Querysets are Python method calls on a special form that lets -you build complex queries. They get converted into optimized SQL queries under the hood, suitable -for your current database. [Here is our tutorial/explanation of Django queries](Tutorial-Searching- -For-Objects#queries-in-django).

-
-

By the way, Django (and Evennia) does allow you to fall through and send raw SQL if you really -want to. It’s highly unlikely to be needed though; the Django database abstraction is very, very -powerful.

-
-

The other aspect where Evennia uses Django is for web integration. On one end Django gives an -infrastructure for wiring Python functions (called views) to URLs: the view/function is called -when a user goes that URL in their browser, enters data into a form etc. The return is the web page -to show. Django also offers templating with features such as being able to add special markers in -HTML where it will insert the values of Python variables on the fly (like showing the current player -count on the web page). [Here is one of our tutorials on wiring up such a web page](Add-a-simple- -new-web-page). Django also comes with the admin site, which automatically -maps the database into a form accessible from a web browser.

-
-
-

contrib

-

Contribs are optional and often more game-specific code-snippets contributed by the Evennia community. -They are distributed with Evennia in the contrib/ folder.

-
-
-

core

-

This term is sometimes used to represent the main Evennia library code suite, excluding its -contrib directory. It can sometimes come up in code reviews, such as

-
-

Evennia is game-agnostic but this feature is for a particular game genre. So it does not belong in -core. Better make it a contrib.

-
-
-
-

field

-

A field or database field in Evennia refers to a property on a -typeclass directly linked to an underlying database column. Only a few fixed -properties per typeclass are database fields but they are often tied to the core functionality of -that base typeclass (for example Objects store its location as a field). In all -other cases, attributes are used to add new persistent data to the typeclass. -Read more about typeclass properties here.

-
-
-

git

-

Git is a version control -tool. It allows us to track the development of the Evennia code by dividing it into units called -commits. A ‘commit’ is sort of a save-spot - you save the current state of your code and can then -come back to it later if later changes caused problems. By tracking commits we know what ‘version’ -of the code we are currently using.

-

Evennia’s source code + its source history is jointly called a repository. -This is centrally stored at our online home on GitHub. Everyone using or -developing Evennia makes a ‘clone’ of this repository to their own computer - everyone -automatically gets everything that is online, including all the code history.

-
-

Don’t confuse Git and GitHub. The former is the version control system. The -latter is a website (run by a company) that allows you to upload source code controlled by Git for -others to see (among other things).

-
-

Git allows multiple users from around the world to efficiently collaborate on Evennia’s code: People -can make local commits on their cloned code. The commits they do can then be uploaded to GitHub and -reviewed by the Evennia lead devs - and if the changes look ok they can be safely merged into the -central Evennia code - and everyone can pull those changes to update their local copies.

-

Developers using Evennia often uses Git on their own games in the same way - to track their changes -and to help collaboration with team mates. This is done completely independently of Evennia’s Git -usage.

-

Common usage (for non-Evennia developers):

-
    -
  • git clone <github-url> - clone an online repository to your computer. This is what you do when -you ‘download’ Evennia. You only need to do this once.

  • -
  • git pull (inside local copy of repository) - sync your local repository with what is online.

  • -
-
-

Full usage of Git is way beyond the scope of this glossary. See Tutorial - version -control for more info and links to the Git documentation.

-
-
-
-

migrate

-

This term is used for upgrading the database structure (it’s schema )to a new version. Most often -this is due to Evennia’s upstream schema changing. When that happens you need to -migrate that schema to the new version as well. Once you have used git to pull the -latest changes, just cd into your game dir and run

-
evennia migrate
-
-
-

That should be it (see virtualenv if you get a warning that the evennia -command is not available). See also Updating your game for more details.

-
-

Technically, migrations are shipped as little Python snippets of code that explains which database -actions must be taken to upgrade from one version of the schema to the next. When you run the -command above, those snippets are run in sequence.

-
-
-
-

multisession mode

-

This term refers to the MULTISESSION_MODE setting, which has a value of 0 to 3. The mode alters -how players can connect to the game, such as how many Sessions a player can start with one account -and how many Characters they can control at the same time. It is described in detail -here.

-
-
-

github

-

Github is where Evennia’s source code and documentation is hosted. -This online repository of code we also sometimes refer to as upstream.

-

GitHub is a business, offering free hosting to Open-source projects like Evennia. Despite the -similarity in name, don’t confuse GitHub the website with Git, the versioning -system. Github hosts Git repositories online and helps with collaboration and -infrastructure. Git itself is a separate project.

-
-
-

object

-

In general Python (and other object-oriented languages), an object is what we call the instance of a class. But one of Evennia’s -core typeclasses is also called “Object”. To separate these in the docs we -try to use object to refer to the general term and capitalized Object when we refer to the -typeclass.

-

The Object is a typeclass that represents all in-game entities, including -Characters, rooms, trees, weapons etc. Read more about Objects -here.

-
-
-

pip

-

pip comes with Python and is the main tool for installing third- -party Python packages from the web. Once a python package is installed you can do import <packagename> in your Python code.

-

Common usage:

-
    -
  • pip install <package-name> - install the given package along with all its dependencies.

  • -
  • pip search <name> - search Python’s central package repository PyPi for a -package of that name.

  • -
  • pip install --upgrade <package_name> - upgrade a package you already have to the latest version.

  • -
  • pip install <packagename>==1.5 - install exactly a specific package version.

  • -
  • pip install <folder> - install a Python package you have downloaded earlier (or cloned using -git).

  • -
  • pip install -e <folder> - install a local package by just making a soft link to the folder. This -means that if the code in <folder> changes, the installed Python package is immediately updated. -If not using -e, one would need to run pip install --upgrade <folder> every time to make the -changes available when you import this package into your code. Evennia is installed this way.

  • -
-

For development, pip is usually used together with a virtualenv to install -all packages and dependencies needed for a project in one, isolated location on the hard drive.

-
-
-

puppet

-

An account can take control and “play as” any Object. When -doing so, we call this puppeting, (like puppeteering). -Normally the entity being puppeted is of the Character subclass but it does -not have to be.

-
-
-

property

-

A property is a general term used for properties on any Python object. The term also sometimes -refers to the property built-in function of Python (read more here). Note the distinction between properties, -fields and Attributes.

-
-
-

repository

-

A repository is a version control/git term. It represents a folder containing -source code plus its versioning history.

-
-

In Git’s case, that history is stored in a hidden folder .git. If you ever feel the need to look -into this folder you probably already know enough Git to know why.

-
-

The evennia folder you download from us with git clone is a repository. The code on -GitHub is often referred to as the ‘online repository’ (or the upstream -repository). If you put your game dir under version control, that of course becomes a repository as -well.

-
-
-

script

-

When we refer to Scripts, we generally refer to the Script typeclass. Scripts are -the mavericks of Evennia - they are like Objects but without any in-game -existence. They are useful as custom places to store data but also as building blocks in persistent -game systems. Since the can be initialized with timing capabilities they can also be used for long- -time persistent time keeping (for fast updates other types of timers may be better though). Read -more about Scripts here

-
-
-

session

-

A Session is a Python object representing a single client connection to the server. A -given human player could connect to the game from different clients and each would get a Session -(even if you did not allow them to actually log in and get access to an -account).

-

Sessions are not typeclassed and has no database persistence. But since they -always exist (also when not logged in), they share some common functionality with typeclasses that -can be useful for certain game states.

-
-
-

tag

-

A Tag is a simple label one can attach to one or more objects in the game. Tagging is a -powerful way to group entities and can also be used to indicate they have particular shared abilities. -Tags are shared between objects (unlike Attributes).

-
-
-

ticker

-

The Ticker handler runs Evennia’s optional ‘ticker’ system. In other engines, such -as DIKU, all game events are processed only at specific -intervals called ‘ticks’. Evennia has no such technical limitation (events are processed whenever -needed) but using a fixed tick can still be useful for certain types of game systems, like combat. -Ticker Handler allows you to emulate any number of tick rates (not just one) and subscribe actions -to be called when those ticks come around.

-
-
-

typeclass

-

The typeclass is an Evennia-specific term. A typeclass allows developers to work with -database-persistent objects as if they were normal Python objects. It makes use of specific -Django features to link a Python class to a database table. Sometimes we refer to -such code entities as being typeclassed.

-

Evennia’s main typeclasses are Account, Object, -Script and Channel. Children of the base class (such as -Character) will use the same database table as the parent, but can have vastly -different Python capabilities (and persistent features through Attributes and -Tags. A typeclass can be coded and treated pretty much like any other Python class -except it must inherit (at any distance) from one of the base typeclasses. Also, creating a new -instance of a typeclass will add a new row to the database table to which it is linked.

-

The core typeclasses in the Evennia library are all named DefaultAccount, -DefaultObject etc. When you initialize your [game dir] you automatically get empty children of -these, called Account, Object etc that you can start working with.

-
-
-

twisted

-

Twisted is a heavy-duty asynchronous networking engine. It is one -of Evennia’s two major library dependencies (the other one is Django). Twisted is -what “runs” Evennia - it handles Evennia’s event loop. Twisted also has the building blocks we need -to construct network protocols and communicate with the outside world; such as our MUD-custom -version of Telnet, Telnet+SSL, SSH, webclient-websockets etc. Twisted also runs our integrated web -server, serving the Django-based website for your game.

-
-
-

virtualenv

-

The standard virtualenv program comes with Python. It is -used to isolate all Python packages needed by a given Python project into one folder (we call that -folder evenv but it could be called anything). A package environment created this way is usually -referred to as “a virtualenv”. If you ever try to run the evennia program and get an error saying -something like “the command ‘evennia’ is not available” - it’s probably because your virtualenv is -not ‘active’ yet (see below).

-

Usage:

-
    -
  • virtualenv <name> - initialize a new virtualenv <name> in a new folder <name> in the current -location. Called evenv in these docs.

  • -
  • virtualenv -p path/to/alternate/python_executable <name> - create a virtualenv using another -Python version than default.

  • -
  • source <folder_name>/bin/activate(linux/mac) - activate the virtualenv in <folder_name>.

  • -
  • <folder_name>\Scripts\activate (windows)

  • -
  • deactivate - turn off the currently activated virtualenv.

  • -
-

A virtualenv is ‘activated’ only for the console/terminal it was started in, but it’s safe to -activate the same virtualenv many times in different windows if you want. Once activated, all Python -packages now installed with pip will install to evenv rather than to a global -location like /usr/local/bin or C:\Program Files.

-
-

Note that if you have root/admin access you could install Evennia globally just fine, without -using a virtualenv. It’s strongly discouraged and considered bad practice though. Experienced Python -developers tend to rather create one new virtualenv per project they are working on, to keep the -varying installs cleanly separated from one another.

-
-

When you execute Python code within this activated virtualenv, only those packages installed -within will be possible to import into your code. So if you installed a Python package globally on -your computer, you’ll need to install it again in your virtualenv.

-
-

Virtualenvs only deal with Python programs/packages. Other programs on your computer couldn’t -care less if your virtualenv is active or not. So you could use git without the virtualenv being -active, for example.

-
-

When your virtualenv is active you should see your console/terminal prompt change to

-
(evenv) ...
-
-
-

… or whatever name you gave the virtualenv when you initialized it.

-
-

We sometimes say that we are “in” the virtualenv when it’s active. But just to be clear - you -never have to actually cd into the evenv folder. You can activate it from anywhere and will -still be considered “in” the virtualenv wherever you go until you deactivate or close the -console/terminal.

-
-

So, when do I need to activate my virtualenv? If the virtualenv is not active, none of the Python -packages/programs you installed in it will be available to you. So at a minimum, it needs to be -activated whenever you want to use the evennia command for any reason.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Grapevine.html b/docs/0.9.5/Grapevine.html deleted file mode 100644 index 6b77c9babb..0000000000 --- a/docs/0.9.5/Grapevine.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - - Grapevine — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Grapevine

-

Grapevine is a new chat network for MU**** games. By -connecting an in-game channel to the grapevine network, players on your game -can chat with players in other games, also non-Evennia ones.

-
-

Configuring Grapevine

-

To use Grapevine, you first need the pyopenssl module. Install it into your -Evennia python environment with

-
pip install pyopenssl
-
-
-

To configure Grapevine, you’ll need to activate it in your settings file.

-
    GRAPEVINE_ENABLED = True
-
-
-

Next, register an account at https://grapevine.haus. When you have logged in, -go to your Settings/Profile and to the Games sub menu. Here you register your -new game by filling in its information. At the end of registration you are going -to get a Client ID and a Client Secret. These should not be shared.

-

Open/create the file mygame/server/conf/secret_settings.py and add the following:

-
  GRAPEVINE_CLIENT_ID = "<client ID>"
-  GRAPEVINE_CLIENT_SECRET = "<client_secret>"
-
-
-

You can also customize the Grapevine channels you are allowed to connect to. This -is added to the GRAPEVINE_CHANNELS setting. You can see which channels are available -by going to the Grapevine online chat here: https://grapevine.haus/chat.

-

Start/reload Evennia and log in as a privileged user. You should now have a new -command available: @grapevine2chan. This command is called like this:

-
 @grapevine2chan[/switches] <evennia_channel> = <grapevine_channel>
-
-
-

Here, the evennia_channel must be the name of an existing Evennia channel and -grapevine_channel one of the supported channels in GRAPEVINE_CHANNELS.

-
-

At the time of writing, the Grapevine network only has two channels: -testing and gossip. Evennia defaults to allowing connecting to both. Use -testing for trying your connection.

-
-
-
-

Setting up Grapevine, step by step

-

You can connect Grapevine to any Evennia channel (so you could connect it to -the default public channel if you like), but for testing, let’s set up a -new channel gw.

-
 @ccreate gw = This is connected to an gw channel!
-
-
-

You will automatically join the new channel.

-

Next we will create a connection to the Grapevine network.

-
 @grapevine2chan gw = gossip
-
-
-

Evennia will now create a new connection and connect it to Grapevine. Connect -to https://grapevine.haus/chat to check.

-

Write something in the Evennia channel gw and check so a message appears in -the Grapevine chat. Write a reply in the chat and the grapevine bot should echo -it to your channel in-game.

-

Your Evennia gamers can now chat with users on external Grapevine channels!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Guest-Logins.html b/docs/0.9.5/Guest-Logins.html deleted file mode 100644 index 052a8c7724..0000000000 --- a/docs/0.9.5/Guest-Logins.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - Guest Logins — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Guest Logins

-

Evennia supports guest logins out of the box. A guest login is an anonymous, low-access account -and can be useful if you want users to have a chance to try out your game without committing to -creating a real account.

-

Guest accounts are turned off by default. To activate, add this to your game/settings.py file:

-
GUEST_ENABLED = True
-
-
-

Henceforth users can use connect guest (in the default command set) to login with a guest account. -You may need to change your Connection Screen to inform them of this -possibility. Guest accounts work differently from normal accounts - they are automatically deleted -whenever the user logs off or the server resets (but not during a reload). They are literally re- -usable throw-away accounts.

-

You can add a few more variables to your settings.py file to customize your guests:

-
    -
  • BASE_GUEST_TYPECLASS - the python-path to the default typeclass for guests. -Defaults to "typeclasses.accounts.Guest".

  • -
  • PERMISSION_GUEST_DEFAULT - permission level for guest accounts. Defaults to "Guests", -which is the lowest permission level in the hierarchy.

  • -
  • GUEST_START_LOCATION - the #dbref to the starting location newly logged-in guests should -appear at. Defaults to "#2 (Limbo).

  • -
  • GUEST_HOME - guest home locations. Defaults to Limbo as well.

  • -
  • GUEST_LIST - this is a list holding the possible guest names to use when entering the game. The -length of this list also sets how many guests may log in at the same time. By default this is a list -of nine names from "Guest1" to "Guest9".

  • -
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/HAProxy-Config.html b/docs/0.9.5/HAProxy-Config.html deleted file mode 100644 index eb61dae915..0000000000 --- a/docs/0.9.5/HAProxy-Config.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - HAProxy Config (Optional) — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

HAProxy Config (Optional)

-
-

Making Evennia, HTTPS and Secure Websockets play nicely together

-

This we can do by installing a proxy between Evennia and the outgoing ports of your server. -Essentially, -Evennia will think it’s only running locally (on localhost, IP 127.0.0.1) - the proxy will -transparently -map that to the “real” outgoing ports and handle HTTPS/WSS for us.

-
Evennia <-> (inside-visible IP/ports) <-> Proxy <-> (outside-visible IP/ports) <-> Internet
-
-
-

Here we will use HAProxy, an open-source proxy that is easy to set up -and use. We will -also be using LetsEncrypt, especially the excellent -helper-program Certbot which pretty much automates the whole -certificate setup process for us.

-

Before starting you also need the following:

-
    -
  • (optional) The host name of your game (like myawesomegame.com). This is something you must -previously have purchased from a domain registrar and set up with DNS to point to the IP of your -server.

  • -
  • If you don’t have a domain name or haven’t set it up yet, you must at least know the IP of your -server. Find this with ifconfig or similar from inside the server. If you use a hosting service -like DigitalOcean you can also find the droplet’s IP address in the control panel.

  • -
  • You must open port 80 in your firewall. This is used by Certbot below to auto-renew certificates. -So you can’t really run another webserver alongside this setup without tweaking.

  • -
  • You must open port 443 (HTTPS) in your firewall.

  • -
  • You must open port 4002 (the default Websocket port) in your firewall.

  • -
-
-
-

Getting certificates

-

Certificates guarantee that you are you. Easiest is to get this with -Letsencrypt and the -Certbot program. Certbot has a lot of install instructions -for various operating systems. Here’s for Debian/Ubuntu:

-
sudo apt install certbot
-
-
-

Make sure to stop Evennia and that no port-80 using service is running, then

-
sudo certbot certonly --standalone
-
-
-

You will get some questions you need to answer, such as an email to send certificate errors to and -the host name (or IP, supposedly) to use with this certificate. After this, the certificates will -end up in /etc/letsencrypt/live/<your-host-or-ip>/*pem (example from Ubuntu). The critical files -for our purposes are fullchain.pem and privkey.pem.

-

Certbot sets up a cron-job/systemd job to regularly renew the certificate. To check this works, try

-
sudo certbot renew --dry-run
-
-
-

The certificate is only valid for 3 months at a time, so make sure this test works (it requires port -80 to be open). Look up Certbot’s page for more help.

-

We are not quite done. HAProxy expects these two files to be one file.

-
sudo cp /etc/letsencrypt/live/<your-host-or-ip>/privkey.pem /etc/letsencrypt/live/<your-host-or-
-ip>/<yourhostname>.pem
-sudo bash -c "cat /etc/letsencrypt/live/<your-host-or-ip>/fullchain.pem >>
-/etc/letsencrypt/live/<your-host-or-ip>/<yourhostname>.pem"
-
-
-

This will create a new .pem file by concatenating the two files together. The yourhostname.pem -file (or whatever you named it) is what we will use when the the HAProxy config file (below) asks -for “your-certificate.pem”.

-
-
-

Installing and configuring HAProxy

-

Installing HaProxy is usually as simple as:

-
# Debian derivatives (Ubuntu, Mint etc)
-sudo apt install haproxy
-
-# Redhat derivatives (dnf instead of yum for very recent Fedora distros)
-sudo yum install haproxy
-
-
-
-

Configuration of HAProxy is done in a single file. Put this wherever you like, for example in -your game dir; name it something like haproxy.conf.

-

Here is an example tested on Centos7 and Ubuntu. Make sure to change the file to put in your own -values.

-
# base stuff to set up haproxy
-global
-    log /dev/log local0
-    chroot /var/lib/haproxy
-    maxconn  4000
-    user  haproxy
-    tune.ssl.default-dh-param 2048
-    ## uncomment this when everything works
-    # daemon
-defaults
-    mode http
-    option forwardfor
-
-# Evennia Specifics
-listen evennia-https-website
-    bind <ip-address-or-hostname>:<public-SSL-port--probably-443> ssl no-sslv3 no-tlsv10 crt
-/etc/letsencrypt/live/<your-host-or-ip>/<yourhostname>.pem
-    server localhost 127.0.0.1:<evennia-web-port-probably-4001>
-    timeout client 10m
-    timeout server 10m
-    timeout connect 5m
-
-listen evennia-secure-websocket
-    bind <ip-address-or-hostname>:<wss-port--probably-4002> ssl no-sslv3 no-tlsv10 crt
-/etc/letsencrypt/live/<your-host-or-ip>/<yourhostname>.pem
-    server localhost 127.0.0.1:<WEBSOCKET_CLIENT_PORT-probably-4002>
-    timeout client 10m
-    timeout server 10m
-    timeout connect 5m
-
-
-
-
-

Putting it all together

-

Get back to the Evennia game dir and edit mygame/server/conf/settings.py. Add:

-
WEBSERVER_INTERFACES = ['127.0.0.1']
-WEBSOCKET_CLIENT_INTERFACE = '127.0.0.1'
-
-
-

and

-
WEBSOCKET_CLIENT_URL="wss://fullhost.domain.name:4002/"
-
-
-

Make sure to reboot (stop + start) evennia completely:

-
evennia reboot
-
-
-

Finally you start the proxy:

-
sudo haproxy -f /path/to/the/above/config_file.cfg
-
-
-

Make sure you can connect to your game from your browser and that you end up with an https:// page -and can use the websocket webclient.

-

Once everything works you may want to start the proxy automatically and in the background. Stop the -proxy with Ctrl-C and uncomment the line # daemon in the config file, then start the proxy again

-
    -
  • it will now start in the bacground.

  • -
-

You may also want to have the proxy start automatically; this you can do with cron, the inbuilt -Linux mechanism for running things at specific times.

-
sudo crontab -e
-
-
-

Choose your editor and add a new line at the end of the crontab file that opens:

-
@reboot haproxy -f /path/to/the/above/config_file.cfg
-
-
-

Save the file and haproxy should start up automatically when you reboot the server.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Help-System-Tutorial.html b/docs/0.9.5/Help-System-Tutorial.html deleted file mode 100644 index 77a84609b8..0000000000 --- a/docs/0.9.5/Help-System-Tutorial.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - Help System Tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Help System Tutorial

-

Before doing this tutorial you will probably want to read the intro in Basic Web tutorial. Reading the three first parts of the Django tutorial might help as well.

-

This tutorial will show you how to access the help system through your website. Both help commands -and regular help entries will be visible, depending on the logged-in user or an anonymous character.

-

This tutorial will show you how to:

-
    -
  • Create a new page to add to your website.

  • -
  • Take advantage of a basic view and basic templates.

  • -
  • Access the help system on your website.

  • -
  • Identify whether the viewer of this page is logged-in and, if so, to what account.

  • -
-
-

Creating our app

-

The first step is to create our new Django app. An app in Django can contain pages and -mechanisms: your website may contain different apps. Actually, the website provided out-of-the-box -by Evennia has already three apps: a “webclient” app, to handle the entire webclient, a “website” -app to contain your basic pages, and a third app provided by Django to create a simple admin -interface. So we’ll create another app in parallel, giving it a clear name to represent our help -system.

-

From your game directory, use the following command:

-
evennia startapp help_system
-
-
-
-

Note: calling the app “help” would have been more explicit, but this name is already used by -Django.

-
-

This will create a directory named help_system at the root of your game directory. It’s a good -idea to keep things organized and move this directory in the “web” directory of your game. Your -game directory should look like:

-
mygame/
-    ...
-    web/
-        help_system/
-        ...
-
-
-

The “web/help_system” directory contains files created by Django. We’ll use some of them, but if -you want to learn more about them all, you should read the Django -tutorial.

-

There is a last thing to be done: your folder has been added, but Django doesn’t know about it, it -doesn’t know it’s a new app. We need to tell it, and we do so by editing a simple setting. Open -your “server/conf/settings.py” file and add, or edit, these lines:

-
# Web configuration
-INSTALLED_APPS += (
-        "web.help_system",
-)
-
-
-

You can start Evennia if you want, and go to your website, probably at -http://localhost:4001 . You won’t see anything different though: we added -the app but it’s fairly empty.

-
-
-

Our new page

-

At this point, our new app contains mostly empty files that you can explore. In order to create -a page for our help system, we need to add:

-
    -
  • A view, dealing with the logic of our page.

  • -
  • A template to display our new page.

  • -
  • A new URL pointing to our page.

  • -
-
-

We could get away by creating just a view and a new URL, but that’s not a recommended way to work -with your website. Building on templates is so much more convenient.

-
-
-

Create a view

-

A view in Django is a simple Python function placed in the “views.py” file in your app. It will -handle the behavior that is triggered when a user asks for this information by entering a URL (the -connection between views and URLs will be discussed later).

-

So let’s create our view. You can open the “web/help_system/views.py” file and paste the following -lines:

-
from django.shortcuts import render
-
-def index(request):
-    """The 'index' view."""
-    return render(request, "help_system/index.html")
-
-
-

Our view handles all code logic. This time, there’s not much: when this function is called, it will -render the template we will now create. But that’s where we will do most of our work afterward.

-
-
-

Create a template

-

The render function called into our view asks the template help_system/index.html. The -templates of our apps are stored in the app directory, “templates” sub-directory. Django may have -created the “templates” folder already. If not, create it yourself. In it, create another folder -“help_system”, and inside of this folder, create a file named “index.html”. Wow, that’s some -hierarchy. Your directory structure (starting from web) should look like this:

-
web/
-    help_system/
-        ...
-        templates/
-            help_system/
-                index.html
-
-
-

Open the “index.html” file and paste in the following lines:

-
{% extends "base.html" %}
-{% block titleblock %}Help index{% endblock %}
-{% block content %}
-<h2>Help index</h2>
-{% endblock %}
-
-
-

Here’s a little explanation line by line of what this template does:

-
    -
  1. It loads the “base.html” template. This describes the basic structure of all your pages, with -a menu at the top and a footer, and perhaps other information like images and things to be present -on each page. You can create templates that do not inherit from “base.html”, but you should have a -good reason for doing so.

  2. -
  3. The “base.html” template defines all the structure of the page. What is left is to override -some sections of our pages. These sections are called blocks. On line 2, we override the block -named “blocktitle”, which contains the title of our page.

  4. -
  5. Same thing here, we override the block named “content”, which contains the main content of our -web page. This block is bigger, so we define it on several lines.

  6. -
  7. This is perfectly normal HTML code to display a level-2 heading.

  8. -
  9. And finally we close the block named “content”.

  10. -
-
-
-

Create a new URL

-

Last step to add our page: we need to add a URL leading to it… otherwise users won’t be able to -access it. The URLs of our apps are stored in the app’s directory “urls.py” file.

-

Open the “web/help_system/urls.py” file (you might have to create it) and write in it:

-
# URL patterns for the help_system app
-
-from django.urls import include, path
-from web.help_system.views import index
-
-urlpatterns = [
-    path(r'', index, name="help-index")
-]
-
-
-

We also need to add our app as a namespace holder for URLS. Edit the file “web/urls.py” (you might -have to create this one too). In it you will find the custom_patterns variable. Replace it with:

-
custom_patterns = [
-    path(r'help_system/', include('web.help_system.urls')),
-]
-
-
-

When a user will ask for a specific URL on your site, Django will:

-
    -
  1. Read the list of custom patterns defined in “web/urls.py”. There’s one pattern here, which -describes to Django that all URLs beginning by ‘help/’ should be sent to the ‘help_system’ app. The -‘help/’ part is removed.

  2. -
  3. Then Django will check the “web.help_system/urls.py” file. It contains only one URL, which is -empty.

  4. -
-

In other words, if the URL is ‘/help/’, then Django will execute our defined view.

-
-
-

Let’s see it work

-

You can now reload or start Evennia. Open a tab in your browser and go to -http://localhost:4001/help_system/ . If everything goes well, -you should see your new page… which isn’t empty since Evennia uses our “base.html” template. In -the content of our page, there’s only a heading that reads “help index”. Notice that the title of -our page is “mygame - Help index” (“mygame” is replaced by the name of your game).

-

From now on, it will be easier to move forward and add features.

-
-
-

A brief reminder

-

We’ll be trying the following things:

-
    -
  • Have the help of commands and help entries accessed online.

  • -
  • Have various commands and help entries depending on whether the user is logged in or not.

  • -
-

In terms of pages, we’ll have:

-
    -
  • One to display the list of help topics.

  • -
  • One to display the content of a help topic.

  • -
-

The first one would link to the second.

-
-

Should we create two URLs?

-
-

The answer is… maybe. It depends on what you want to do. We have our help index accessible -through the “/help_system/” URL. We could have the detail of a help entry accessible through -“/help_system/desc” (to see the detail of the “desc” command). The problem is that our commands or -help topics may contain special characters that aren’t to be present in URLs. There are different -ways around this problem. I have decided to use a GET variable here, which would create URLs like -this:

-
/help_system?name=desc
-
-
-

If you use this system, you don’t have to add a new URL: GET and POST variables are accessible -through our requests and we’ll see how soon enough.

-
-
-
-

Handling logged-in users

-

One of our requirements is to have a help system tailored to our accounts. If an account with admin -access logs in, the page should display a lot of commands that aren’t accessible to common users. -And perhaps even some additional help topics.

-

Fortunately, it’s fairly easy to get the logged in account in our view (remember that we’ll do most -of our coding there). The request object, passed to our function, contains a user attribute. -This attribute will always be there: we cannot test whether it’s None or not, for instance. But -when the request comes from a user that isn’t logged in, the user attribute will contain an -anonymous Django user. We then can use the is_anonymous method to see whether the user is logged- -in or not. Last gift by Evennia, if the user is logged in, request.user contains a reference to -an account object, which will help us a lot in coupling the game and online system.

-

So we might end up with something like:

-
def index(request):
-    """The 'index' view."""
-    user = request.user
-    if not user.is_anonymous() and user.character:
-        character = user.character
-
-
-
-

Note: this code works when your MULTISESSION_MODE is set to 0 or 1. When it’s above, you would -have something like:

-
-
def index(request):
-    """The 'index' view."""
-    user = request.user
-    if not user.is_anonymous() and user.db._playable_characters:
-        character = user.db._playable_characters[0]
-
-
-

In this second case, it will select the first character of the account.

-

But what if the user’s not logged in? Again, we have different solutions. One of the most simple -is to create a character that will behave as our default character for the help system. You can -create it through your game: connect to it and enter:

-
@charcreate anonymous
-
-
-

The system should answer:

-
Created new character anonymous. Use @ic anonymous to enter the game as this character.
-
-
-

So in our view, we could have something like this:

-
from typeclasses.characters import Character
-
-def index(request):
-    """The 'index' view."""
-    user = request.user
-    if not user.is_anonymous() and user.character:
-        character = user.character
-    else:
-        character = Character.objects.get(db_key="anonymous")
-
-
-

This time, we have a valid character no matter what: remember to adapt this code if you’re running -in multisession mode above 1.

-
-
-

The full system

-

What we’re going to do is to browse through all commands and help entries, and list all the commands -that can be seen by this character (either our ‘anonymous’ character, or our logged-in character).

-

The code is longer, but it presents the entire concept in our view. Edit the -“web/help_system/views.py” file and paste into it:

-
from django.http import Http404
-from django.shortcuts import render
-from evennia.help.models import HelpEntry
-
-from typeclasses.characters import Character
-
-def index(request):
-    """The 'index' view."""
-    user = request.user
-    if not user.is_anonymous() and user.character:
-        character = user.character
-    else:
-        character = Character.objects.get(db_key="anonymous")
-
-    # Get the categories and topics accessible to this character
-    categories, topics = _get_topics(character)
-
-    # If we have the 'name' in our GET variable
-    topic = request.GET.get("name")
-    if topic:
-        if topic not in topics:
-            raise Http404("This help topic doesn't exist.")
-
-        topic = topics[topic]
-        context = {
-                "character": character,
-                "topic": topic,
-        }
-        return render(request, "help_system/detail.html", context)
-    else:
-        context = {
-                "character": character,
-                "categories": categories,
-        }
-        return render(request, "help_system/index.html", context)
-
-def _get_topics(character):
-    """Return the categories and topics for this character."""
-    cmdset = character.cmdset.all()[0]
-    commands = cmdset.commands
-    entries = [entry for entry in HelpEntry.objects.all()]
-    categories = {}
-    topics = {}
-
-    # Browse commands
-    for command in commands:
-        if not command.auto_help or not command.access(character):
-            continue
-
-        # Create the template for a command
-        template = {
-                "name": command.key,
-                "category": command.help_category,
-                "content": command.get_help(character, cmdset),
-        }
-
-        category = command.help_category
-        if category not in categories:
-            categories[category] = []
-        categories[category].append(template)
-        topics[command.key] = template
-
-    # Browse through the help entries
-    for entry in entries:
-        if not entry.access(character, 'view', default=True):
-            continue
-
-        # Create the template for an entry
-        template = {
-                "name": entry.key,
-                "category": entry.help_category,
-                "content": entry.entrytext,
-        }
-
-        category = entry.help_category
-        if category not in categories:
-            categories[category] = []
-        categories[category].append(template)
-        topics[entry.key] = template
-
-    # Sort categories
-    for entries in categories.values():
-        entries.sort(key=lambda c: c["name"])
-
-    categories = list(sorted(categories.items()))
-    return categories, topics
-
-
-

That’s a bit more complicated here, but all in all, it can be divided in small chunks:

-
    -
  • The index function is our view:

    -
      -
    • It begins by getting the character as we saw in the previous section.

    • -
    • It gets the help topics (commands and help entries) accessible to this character. It’s another -function that handles that part.

    • -
    • If there’s a GET variable “name” in our URL (like “/help?name=drop”), it will retrieve it. If -it’s not a valid topic’s name, it returns a 404. Otherwise, it renders the template called -“detail.html”, to display the detail of our topic.

    • -
    • If there’s no GET variable “name”, render “index.html”, to display the list of topics.

    • -
    -
  • -
  • The _get_topics is a private function. Its sole mission is to retrieve the commands a character -can execute, and the help entries this same character can see. This code is more Evennia-specific -than Django-specific, it will not be detailed in this tutorial. Just notice that all help topics -are stored in a dictionary. This is to simplify our job when displaying them in our templates.

  • -
-

Notice that, in both cases when we asked to render a template, we passed to render a third -argument which is the dictionary of variables used in our templates. We can pass variables this -way, and we will use them in our templates.

-
-

The index template

-

Let’s look at our full “index” template. You can open the -“web/help_system/templates/help_system/index.html” file and paste the following into it:

-
{% extends "base.html" %}
-{% block titleblock %}Help index{% endblock %}
-{% block content %}
-<h2>Help index</h2>
-{% if categories %}
-    {% for category, topics in categories %}
-        <h2>{{ category|capfirst }}</h2>
-        <table>
-        <tr>
-        {% for topic in topics %}
-            {% if forloop.counter|divisibleby:"5" %}
-                </tr>
-                <tr>
-            {% endif %}
-            <td><a href="{% url 'help-index' %}?name={{ topic.name|urlencode }}">
-            {{ topic.name }}</td>
-        {% endfor %}
-        </tr>
-        </table>
-    {% endfor %}
-{% endif %}
-{% endblock %}
-
-
-

This template is definitely more detailed. What it does is:

-
    -
  1. Browse through all categories.

  2. -
  3. For all categories, display a level-2 heading with the name of the category.

  4. -
  5. All topics in a category (remember, they can be either commands or help entries) are displayed in -a table. The trickier part may be that, when the loop is above 5, it will create a new line. The -table will have 5 columns at the most per row.

  6. -
  7. For every cell in the table, we create a link redirecting to the detail page (see below). The -URL would look something like “help?name=say”. We use urlencode to ensure special characters are -properly escaped.

  8. -
-
-
-

The detail template

-

It’s now time to show the detail of a topic (command or help entry). You can create the file -“web/help_system/templates/help_system/detail.html”. You can paste into it the following code:

-
{% extends "base.html" %}
-{% block titleblock %}Help for {{ topic.name }}{% endblock %}
-{% block content %}
-<h2>{{ topic.name|capfirst }} help topic</h2>
-<p>Category: {{ topic.category|capfirst }}</p>
-{{ topic.content|linebreaks }}
-{% endblock %}
-
-
-

This template is much easier to read. Some filters might be unknown to you, but they are just -used to format here.

-
-
-

Put it all together

-

Remember to reload or start Evennia, and then go to -http://localhost:4001/help_system. You should see the list of -commands and topics accessible by all characters. Try to login (click the “login” link in the menu -of your website) and go to the same page again. You should now see a more detailed list of commands -and help entries. Click on one to see its detail.

-
-
-
-

To improve this feature

-

As always, a tutorial is here to help you feel comfortable adding new features and code by yourself. -Here are some ideas of things to improve this little feature:

-
    -
  • Links at the bottom of the detail template to go back to the index might be useful.

  • -
  • A link in the main menu to link to this page would be great… for the time being you have to -enter the URL, users won’t guess it’s there.

  • -
  • Colors aren’t handled at this point, which isn’t exactly surprising. You could add it though.

  • -
  • Linking help entries between one another won’t be simple, but it would be great. For instance, if -you see a help entry about how to use several commands, it would be great if these commands were -themselves links to display their details.

  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Help-System.html b/docs/0.9.5/Help-System.html deleted file mode 100644 index dd06566f2a..0000000000 --- a/docs/0.9.5/Help-System.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - Help System — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Help System

-

An important part of Evennia is the online help system. This allows the players and staff alike to -learn how to use the game’s commands as well as other information pertinent to the game. The help -system has many different aspects, from the normal editing of help entries from inside the game, to -auto-generated help entries during code development using the auto-help system.

-
-

Viewing the help database

-

The main command is help:

-
 help [searchstring]
-
-
-

This will show a list of help entries, ordered after categories. You will find two sections, -Command help entries and Other help entries (initially you will only have the first one). You -can use help to get more info about an entry; you can also give partial matches to get suggestions. -If you give category names you will only be shown the topics in that category.

-
-
-

Command Auto-help system

-

A common item that requires help entries are in-game commands. Keeping these entries up-to-date with -the actual source code functionality can be a chore. Evennia’s commands are therefore auto- -documenting straight from the sources through its auto-help system. Only commands that you and -your character can actually currently use are picked up by the auto-help system. That means an admin -will see a considerably larger amount of help topics than a normal player when using the default -help command.

-

The auto-help system uses the __doc__ strings of your command classes and formats this to a nice- -looking help entry. This makes for a very easy way to keep the help updated - just document your -commands well and updating the help file is just a @reload away. There is no need to manually -create and maintain help database entries for commands; as long as you keep the docstrings updated -your help will be dynamically updated for you as well.

-

Example (from a module with command definitions):

-
    class CmdMyCmd(Command):
-       """
-       mycmd - my very own command
-    
-       Usage:
-         mycmd[/switches] <args>
-    
-       Switches:
-         test - test the command
-         run  - do something else
-    
-       This is my own command that does this and that.
-    
-       """
-       # [...]
-
-       help_category = "General"    # default
-       auto_help = True             # default
-       
-       # [...]
-
-
-

The text at the very top of the command class definition is the class’ __doc__-string and will be -shown to users looking for help. Try to use a consistent format - all default commands are using the -structure shown above.

-

You should also supply the help_category class property if you can; this helps to group help -entries together for people to more easily find them. See the help command in-game to see the -default categories. If you don’t specify the category, “General” is assumed.

-

If you don’t want your command to be picked up by the auto-help system at all (like if you want to -write its docs manually using the info in the next section or you use a cmdset that -has its own help functionality) you can explicitly set auto_help class property to False in your -command definition.

-

Alternatively, you can keep the advantages of auto-help in commands, but control the display of -command helps. You can do so by overriding the command’s get_help() method. By default, this -method will return the class docstring. You could modify it to add custom behavior: the text -returned by this method will be displayed to the character asking for help in this command.

-
-
-

Database help entries

-

These are all help entries not involving commands (this is handled automatically by the Command -Auto-help system). Non-automatic help entries describe how -your particular game is played - its rules, world descriptions and so on.

-

A help entry consists of four parts:

-
    -
  • The topic. This is the name of the help entry. This is what players search for when they are -looking for help. The topic can contain spaces and also partial matches will be found.

  • -
  • The help category. Examples are Administration, Building, Comms or General. This is an -overall grouping of similar help topics, used by the engine to give a better overview.

  • -
  • The text - the help text itself, of any length.

  • -
  • locks - a lock definition. This can be used to limit access to this help entry, maybe -because it’s staff-only or otherwise meant to be restricted. Help commands check for access_types -view and edit. An example of a lock string would be view:perm(Builders).

  • -
-

You can create new help entries in code by using evennia.create_help_entry().

-
from evennia import create_help_entry
-entry = create_help_entry("emote",
-                "Emoting is important because ...",
-                category="Roleplaying", locks="view:all()")
-
-
-

From inside the game those with the right permissions can use the @sethelp command to add and -modify help entries.

-
> @sethelp/add emote = The emote command is ...
-
-
-

Using @sethelp you can add, delete and append text to existing entries. By default new entries -will go in the General help category. You can change this using a different form of the @sethelp -command:

-
> @sethelp/add emote, Roleplaying = Emoting is important because ...
-
-
-

If the category Roleplaying did not already exist, it is created and will appear in the help -index.

-

You can, finally, define a lock for the help entry by following the category with a lock -definition:

-
> @sethelp/add emote, Roleplaying, view:all() = Emoting is ...
-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/How-To-Get-And-Give-Help.html b/docs/0.9.5/How-To-Get-And-Give-Help.html deleted file mode 100644 index 8bdb75b006..0000000000 --- a/docs/0.9.5/How-To-Get-And-Give-Help.html +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - - How To Get And Give Help — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

How To Get And Give Help

-
-

How to get Help

-

If you cannot find what you are looking for in the online documentation, here’s what to do:

-
    -
  • If you think the documentation is not clear enough and are short on time, fill in our quick little -online form and let us know - no login required. Maybe the docs need to be improved or a new -tutorial added! Note that while this form is useful as a suggestion box we cannot answer questions -or reply to you. Use the discussion group or chat (linked below) if you want feedback.

  • -
  • If you have trouble with a missing feature or a problem you think is a bug, go to the -issue tracker and search to see if has been reported/suggested already. If you can’t find an -existing entry create a new one.

  • -
  • If you need help, want to start a discussion or get some input on something you are working on, -make a post to the discussions group This is technically a ‘mailing list’, but you don’t -need to use e-mail; you can post and read all messages just as easily from your browser via the -online interface.

  • -
  • If you want more direct discussions with developers and other users, consider dropping into our -IRC chat channel #evennia on the Freenode network. Please note however that you have to be -patient if you don’t get any response immediately; we are all in very different time zones and many -have busy personal lives. So you might have to hang around for a while - you’ll get noticed -eventually!

  • -
-
-
-

How to give Help

-

Evennia is a completely non-funded project. It relies on the time donated by its users and -developers in order to progress.

-

The first and easiest way you as a user can help us out is by taking part in -community discussions and by giving feedback on what is good or bad. Report bugs you find and features -you lack to our issue tracker. Just the simple act of letting developers know you are out -there using their program is worth a lot. Generally mentioning and reviewing Evennia elsewhere is -also a nice way to spread the word.

-

If you’d like to help develop Evennia more hands-on, here are some ways to get going:

-
    -
  • Look through our online documentation wiki and see if you -can help improve or expand the documentation (even small things like fixing typos!). You don’t need -any particular permissions to edit the wiki.

  • -
  • Send a message to our discussion group and/or our IRC chat asking about what -needs doing, along with what your interests and skills are.

  • -
  • Take a look at our issue tracker and see if there’s something you feel like taking on. -here are bugs that need fixes. At any given time there may also be some -[bounties][issues-bounties] open - these are issues members of the community has put up money to see -fixed (if you want to put up a bounty yourself you can do so via our page on -[bountysource][bountysource]).

  • -
  • Check out the Contributing page on how to practically contribute with code using -github.

  • -
-

… And finally, if you want to help motivate and support development you can also drop some coins -in the developer’s cup. You can make a donation via PayPal or, even better, -[become an Evennia patron on Patreon][patreon]! This is a great way to tip your hat and show that you -appreciate the work done with the server! Finally, if you want to encourage the community to resolve -a particular

-

b2 -[donate-img]: http://images-focus-opensocial.googleusercontent.com/gadgets/proxy?url=https://www.paypalobjects.com/en%5fUS/SE/i/btn/btn%5fdonateCC%5fLG.gif&container=focus&gadget=a&rewriteMime=image/* -[patreon]: https://www.patreon.com/griatch -[patreon-img]: http://www.evennia.com/_/rsrc/1424724909023/home/evennia_patreon_100x100.png -[issues-bounties]: https://github.com/evennia/evennia/labels/bounty -[bountysource]: https://www.bountysource.com/teams/evennia

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/How-to-connect-Evennia-to-Twitter.html b/docs/0.9.5/How-to-connect-Evennia-to-Twitter.html deleted file mode 100644 index 5ccc96bd46..0000000000 --- a/docs/0.9.5/How-to-connect-Evennia-to-Twitter.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - How to connect Evennia to Twitter — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

How to connect Evennia to Twitter

-

Twitter is an online social networking service that enables -users to send and read short 280-character messages called “tweets”. Following is a short tutorial -explaining how to enable users to send tweets from inside Evennia.

-
-

Configuring Twitter

-

You must first have a Twitter account. Log in and register an App at the Twitter Dev -Site. Make sure you enable access to “write” tweets!

-

To tweet from Evennia you will need both the “API Token” and the “API secret” strings as well as the -“Access Token” and “Access Secret” strings.

-

Twitter changed their requirements to require a Mobile number on the Twitter account to register new -apps with write access. If you’re unable to do this, please see this Dev -post which describes how to get around -it.

-
-
-

Install the twitter python module

-

To use Twitter you must install the Twitter Python module:

-
pip install python-twitter
-
-
-
-
-

A basic tweet command

-

Evennia doesn’t have a tweet command out of the box so you need to write your own little -Command in order to tweet. If you are unsure about how commands work and how to add -them, it can be an idea to go through the Adding a Command Tutorial -before continuing.

-

You can create the command in a separate command module (something like mygame/commands/tweet.py) -or together with your other custom commands, as you prefer.

-

This is how it can look:

-
import twitter
-from evennia import Command
-
-# here you insert your unique App tokens
-# from the Twitter dev site
-TWITTER_API = twitter.Api(consumer_key='api_key',
-                          consumer_secret='api_secret',
-                          access_token_key='access_token_key',
-                          access_token_secret='access_token_secret')
-
-class CmdTweet(Command):
-    """
-    Tweet a message
-
-    Usage:
-      tweet <message>
-
-    This will send a Twitter tweet to a pre-configured Twitter account.
-    A tweet has a maximum length of 280 characters.
-    """
-
-    key = "tweet"
-    locks = "cmd:pperm(tweet) or pperm(Developers)"
-    help_category = "Comms"
-
-    def func(self):
-        "This performs the tweet"
- 
-        caller = self.caller
-        tweet = self.args
-
-        if not tweet:
-            caller.msg("Usage: tweet <message>")
-            return
- 
-        tlen = len(tweet)
-        if tlen > 280:
-            caller.msg("Your tweet was %i chars long (max 280)." % tlen)
-            return
-
-        # post the tweet
-        TWITTER_API.PostUpdate(tweet)
-
-        caller.msg("You tweeted:\n%s" % tweet)
-
-
-

Be sure to substitute your own actual API/Access keys and secrets in the appropriate places.

-

We default to limiting tweet access to players with Developers-level access or to those players -that have the permission “tweet” (allow individual characters to tweet with @perm/player playername = tweet). You may change the lock as you feel is appropriate. Change the overall -permission to Players if you want everyone to be able to tweet.

-

Now add this command to your default command set (e.g in mygame/commands/defalt_cmdsets.py”) and -reload the server. From now on those with access can simply use tweet <message> to see the tweet -posted from the game’s Twitter account.

-
-
-

Next Steps

-

This shows only a basic tweet setup, other things to do could be:

-
    -
  • Auto-Adding the character name to the tweet

  • -
  • More error-checking of postings

  • -
  • Changing locks to make tweeting open to more people

  • -
  • Echo your tweets to an in-game channel

  • -
-

Rather than using an explicit command you can set up a Script to send automatic tweets, for example -to post updated game stats. See the Tweeting Game Stats tutorial for -help.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/IRC.html b/docs/0.9.5/IRC.html deleted file mode 100644 index a0d071bc49..0000000000 --- a/docs/0.9.5/IRC.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - - - IRC — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

IRC

-

Disambiguation: This page is related to using IRC inside an Evennia game. To join the official -Evennia IRC chat, connect to irc.freenode.net and join #evennia. Alternatively, you can join our -Discord, which is mirrored to IRC.

-

IRC (Internet Relay Chat) is a long standing -chat protocol used by many open-source projects for communicating in real time. By connecting one of -Evennia’s Channels to an IRC channel you can communicate also with people not on -an mud themselves. You can also use IRC if you are only running your Evennia MUD locally on your -computer (your game doesn’t need to be open to the public)! All you need is an internet connection. -For IRC operation you also need twisted.words. -This is available simply as a package python-twisted-words in many Linux distros, or directly -downloadable from the link.

-
-

Configuring IRC

-

To configure IRC, you’ll need to activate it in your settings file.

-
    IRC_ENABLED = True
-
-
-

Start Evennia and log in as a privileged user. You should now have a new command available: -@irc2chan. This command is called like this:

-
 @irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>
-
-
-

If you already know how IRC works, this should be pretty self-evident to use. Read the help entry -for more features.

-
-
-

Setting up IRC, step by step

-

You can connect IRC to any Evennia channel (so you could connect it to the default public channel -if you like), but for testing, let’s set up a new channel irc.

-
 @ccreate irc = This is connected to an irc channel!
-
-
-

You will automatically join the new channel.

-

Next we will create a connection to an external IRC network and channel. There are many, many IRC -nets. Here is a list of some of the biggest -ones, the one you choose is not really very important unless you want to connect to a particular -channel (also make sure that the network allows for “bots” to connect).

-

For testing, we choose the Freenode network, irc.freenode.net. We will connect to a test -channel, let’s call it #myevennia-test (an IRC channel always begins with #). It’s best if you -pick an obscure channel name that didn’t exist previously - if it didn’t exist it will be created -for you.

-
-

Don’t connect to #evennia for testing and debugging, that is Evennia’s official chat channel! -You are welcome to connect your game to #evennia once you have everything working though - it -can be a good way to get help and ideas. But if you do, please do so with an in-game channel open -only to your game admins and developers).

-
-

The port needed depends on the network. For Freenode this is 6667.

-

What will happen is that your Evennia server will connect to this IRC channel as a normal user. This -“user” (or “bot”) needs a name, which you must also supply. Let’s call it “mud-bot”.

-

To test that the bot connects correctly you also want to log onto this channel with a separate, -third-party IRC client. There are hundreds of such clients available. If you use Firefox, the -Chatzilla plugin is good and easy. Freenode also offers its own web-based chat page. Once you -have connected to a network, the command to join is usually /join #channelname (don’t forget the -#).

-

Next we connect Evennia with the IRC channel.

-
 @irc2chan irc = irc.freenode.net 6667 #myevennia-test mud-bot
-
-
-

Evennia will now create a new IRC bot mud-bot and connect it to the IRC network and the channel -#myevennia. If you are connected to the IRC channel you will soon see the user mud-bot connect.

-

Write something in the Evennia channel irc.

-
 irc Hello, World!
-[irc] Anna: Hello, World!
-
-
-

If you are viewing your IRC channel with a separate IRC client you should see your text appearing -there, spoken by the bot:

-
mud-bot> [irc] Anna: Hello, World!
-
-
-

Write Hello! in your IRC client window and it will appear in your normal channel, marked with the -name of the IRC channel you used (#evennia here).

-
[irc] Anna@#myevennia-test: Hello!
-
-
-

Your Evennia gamers can now chat with users on external IRC channels!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Implementing-a-game-rule-system.html b/docs/0.9.5/Implementing-a-game-rule-system.html deleted file mode 100644 index 4594b6ff5e..0000000000 --- a/docs/0.9.5/Implementing-a-game-rule-system.html +++ /dev/null @@ -1,426 +0,0 @@ - - - - - - - - - Implementing a game rule system — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Implementing a game rule system

-

The simplest way to create an online roleplaying game (at least from a code perspective) is to -simply grab a paperback RPG rule book, get a staff of game masters together and start to run scenes -with whomever logs in. Game masters can roll their dice in front of their computers and tell the -players the results. This is only one step away from a traditional tabletop game and puts heavy -demands on the staff - it is unlikely staff will be able to keep up around the clock even if they -are very dedicated.

-

Many games, even the most roleplay-dedicated, thus tend to allow for players to mediate themselves -to some extent. A common way to do this is to introduce coded systems - that is, to let the -computer do some of the heavy lifting. A basic thing is to add an online dice-roller so everyone can -make rolls and make sure noone is cheating. Somewhere at this level you find the most bare-bones -roleplaying MUSHes.

-

The advantage of a coded system is that as long as the rules are fair the computer is too - it makes -no judgement calls and holds no personal grudges (and cannot be accused of holding any). Also, the -computer doesn’t need to sleep and can always be online regardless of when a player logs on. The -drawback is that a coded system is not flexible and won’t adapt to the unprogrammed actions human -players may come up with in role play. For this reason many roleplay-heavy MUDs do a hybrid -variation - they use coded systems for things like combat and skill progression but leave role play -to be mostly freeform, overseen by staff game masters.

-

Finally, on the other end of the scale are less- or no-roleplay games, where game mechanics (and -thus player fairness) is the most important aspect. In such games the only events with in-game value -are those resulting from code. Such games are very common and include everything from hack-and-slash -MUDs to various tactical simulations.

-

So your first decision needs to be just what type of system you are aiming for. This page will try -to give some ideas for how to organize the “coded” part of your system, however big that may be.

-
-

Overall system infrastructure

-

We strongly recommend that you code your rule system as stand-alone as possible. That is, don’t -spread your skill check code, race bonus calculation, die modifiers or what have you all over your -game.

-
    -
  • Put everything you would need to look up in a rule book into a module in mygame/world. Hide away -as much as you can. Think of it as a black box (or maybe the code representation of an all-knowing -game master). The rest of your game will ask this black box questions and get answers back. Exactly -how it arrives at those results should not need to be known outside the box. Doing it this way -makes it easier to change and update things in one place later.

  • -
  • Store only the minimum stuff you need with each game object. That is, if your Characters need -values for Health, a list of skills etc, store those things on the Character - don’t store how to -roll or change them.

  • -
  • Next is to determine just how you want to store things on your Objects and Characters. You can -choose to either store things as individual Attributes, like character.db.STR=34 and -character.db.Hunting_skill=20. But you could also use some custom storage method, like a -dictionary character.db.skills = {"Hunting":34, "Fishing":20, ...}. A much more fancy solution is -to look at the Ainneve Trait -handler. Finally you could even go -with a custom django model. Which is the better depends on your game and the -complexity of your system.

  • -
  • Make a clear API into your -rules. That is, make methods/functions that you feed with, say, your Character and which skill you -want to check. That is, you want something similar to this:

    -
        from world import rules
    -    result = rules.roll_skill(character, "hunting")
    -    result = rules.roll_challenge(character1, character2, "swords")
    -
    -
    -
  • -
-

You might need to make these functions more or less complex depending on your game. For example the -properties of the room might matter to the outcome of a roll (if the room is dark, burning etc). -Establishing just what you need to send into your game mechanic module is a great way to also get a -feel for what you need to add to your engine.

-
-
-

Coded systems

-

Inspired by tabletop role playing games, most game systems mimic some sort of die mechanic. To this -end Evennia offers a full dice -roller in its contrib -folder. For custom implementations, Python offers many ways to randomize a result using its in-built -random module. No matter how it’s implemented, we will in this text refer to the action of -determining an outcome as a “roll”.

-

In a freeform system, the result of the roll is just compared with values and people (or the game -master) just agree on what it means. In a coded system the result now needs to be processed somehow. -There are many things that may happen as a result of rule enforcement:

-
    -
  • Health may be added or deducted. This can effect the character in various ways.

  • -
  • Experience may need to be added, and if a level-based system is used, the player might need to be -informed they have increased a level.

  • -
  • Room-wide effects need to be reported to the room, possibly affecting everyone in the room.

  • -
-

There are also a slew of other things that fall under “Coded systems”, including things like -weather, NPC artificial intelligence and game economy. Basically everything about the world that a -Game master would control in a tabletop role playing game can be mimicked to some level by coded -systems.

-
-
-

Example of Rule module

-

Here is a simple example of a rule module. This is what we assume about our simple example game:

-
    -
  • Characters have only four numerical values:

    -
      -
    • Their level, which starts at 1.

    • -
    • A skill combat, which determines how good they are at hitting things. Starts between 5 and

    • -
    -
  • -
-
    -
    • -
    • Their Strength, STR, which determine how much damage they do. Starts between 1 and 10.

    • -
    • Their Health points, HP, which starts at 100.

    • -
    -
  1. -
-
    -
  • When a Character reaches HP = 0, they are presumed “defeated”. Their HP is reset and they get a -failure message (as a stand-in for death code).

  • -
  • Abilities are stored as simple Attributes on the Character.

  • -
  • “Rolls” are done by rolling a 100-sided die. If the result plus the combatvalue is greater than -the other character, it’s a success and damage is rolled. Damage is rolled as a six-sided die + the -value of STR (for this example we ignore weapons and assume STR is all that matters).

  • -
  • Every successful attack roll gives 1-3 experience points (XP). Every time the number of XP -reaches (level + 1) ** 2, the Character levels up. When leveling up, the Character’s combat -value goes up by 2 points and STR by one (this is a stand-in for a real progression system).

  • -
  • Characters with the name dummy will gain no XP. Allowing us to make a dummy to train with.

  • -
-
-

Character

-

The Character typeclass is simple. It goes in mygame/typeclasses/characters.py. There is already -an empty Character class there that Evennia will look to and use.

-
from random import randint
-from evennia import DefaultCharacter
-
-class Character(DefaultCharacter):
-    """
-    Custom rule-restricted character. We randomize
-    the initial skill and ability values bettween 1-10.
-    """
-    def at_object_creation(self):
-        "Called only when first created"
-        self.db.level = 1
-        self.db.HP = 100
-        self.db.XP = 0
-        self.db.STR = randint(1, 10)
-        self.db.combat = randint(5, 10)
-
-
-

@reload the server to load up the new code. Doing examine self will however not show the new -Attributes on yourself. This is because the at_object_creation hook is only called on new -Characters. Your Character was already created and will thus not have them. To force a reload, use -the following command:

-
@typeclass/force/reset self
-
-
-

The examine self command will now show the new Attributes.

-
-
-

Rule module

-

This is a module mygame/world/rules.py.

-
"""
-mygame/world/rules.py
-"""
-from random import randint
-
-
-def roll_hit():
-    "Roll 1d100"
-    return randint(1, 100)
-
-
-def roll_dmg():
-    "Roll 1d6"
-    return randint(1, 6)
-
-
-def check_defeat(character):
-    "Checks if a character is 'defeated'."
-    if character.db.HP <= 0:
-        character.msg("You fall down, defeated!")
-        character.db.HP = 100   # reset
-
-
-def add_XP(character, amount):
-    "Add XP to character, tracking level increases."
-    if "training_dummy" in character.tags.all():  # don't allow the training dummy to level
-        character.location.msg_contents("Training Dummies can not gain XP.")
-        return
-    else:
-        character.db.XP += amount
-        if character.db.XP >= (character.db.level + 1) ** 2:
-            character.db.level += 1
-            character.db.STR += 1
-            character.db.combat += 2
-            character.msg("You are now level %i!" % character.db.level)
-
-
-def skill_combat(*args):
-    """
-    This determines outcome of combat. The one who
-    rolls under their combat skill AND higher than
-    their opponent's roll hits.
-    """
-    char1, char2 = args
-    roll1, roll2 = roll_hit(), roll_hit()
-    failtext = "You are hit by %s for %i damage!"
-    wintext = "You hit %s for %i damage!"
-    xp_gain = randint(1, 3)
-
-    # display messages showing attack numbers
-    attack_message = f"{char1.name} rolls {roll1} + combat {char1.db.combat} " \
-    f"= {char1.db.combat+roll1} | {char2.name} rolls {roll2} + combat " \
-    f"{char2.db.combat} = {char2.db.combat+roll2}"
-    char1.location.msg_contents(attack_message)
-    attack_summary = f"{char1.name} {char1.db.combat+roll1} " \
-    f"vs {char2.name} {char2.db.combat+roll2}"
-    char1.location.msg_contents(attack_summary)
-
-    if char1.db.combat+roll1 > char2.db.combat+roll2:
-        # char 1 hits
-        dmg = roll_dmg() + char1.db.STR
-        char1.msg(wintext % (char2, dmg))
-        add_XP(char1, xp_gain)
-        char2.msg(failtext % (char1, dmg))
-        char2.db.HP -= dmg
-        check_defeat(char2)
-    elif char2.db.combat+roll2 > char1.db.combat+roll1:
-        # char 2 hits
-        dmg = roll_dmg() + char2.db.STR
-        char1.msg(failtext % (char2, dmg))
-        char1.db.HP -= dmg
-        check_defeat(char1)
-        char2.msg(wintext % (char1, dmg))
-        add_XP(char2, xp_gain)
-    else:
-        # a draw
-        drawtext = "Neither of you can find an opening."
-        char1.msg(drawtext)
-        char2.msg(drawtext)
-
-
-SKILLS = {"combat": skill_combat}
-
-
-def roll_challenge(character1, character2, skillname):
-    """
-    Determine the outcome of a skill challenge between
-    two characters based on the skillname given.
-    """
-    if skillname in SKILLS:
-        SKILLS[skillname](character1, character2)
-    else:
-        raise RunTimeError("Skillname %s not found." % skillname)
-
-
-

These few functions implement the entirety of our simple rule system. We have a function to check -the “defeat” condition and reset the HP back to 100 again. We define a generic “skill” function. -Multiple skills could all be added with the same signature; our SKILLS dictionary makes it easy to -look up the skills regardless of what their actual functions are called. Finally, the access -function roll_challenge just picks the skill and gets the result.

-

In this example, the skill function actually does a lot - it not only rolls results, it also informs -everyone of their results via character.msg() calls.

-
-
-

Attack Command

-

Here is an example of usage in a game command:

-
from evennia import Command
-from world import rules
-
-class CmdAttack(Command):
-    """
-    attack an opponent
-
-    Usage:
-      attack <target>
-
-    This will attack a target in the same room, dealing
-    damage with your bare hands.
-    """
-    def func(self):
-        "Implementing combat"
-
-        caller = self.caller
-        if not self.args:
-            caller.msg("You need to pick a target to attack.")
-            return
-
-        target = caller.search(self.args)
-        if target:
-            rules.roll_challenge(caller, target, "combat")
-
-
-

Note how simple the command becomes and how generic you can make it. It becomes simple to offer any -number of Combat commands by just extending this functionality - you can easily roll challenges and -pick different skills to check. And if you ever decided to, say, change how to determine hit chance, -you don’t have to change every command, but need only change the single roll_hit function inside -your rules module.

-
-
-

Training dummy

-

Create a dummy to test the attack command. Give it a training_dummy tag anything with the tag -training_dummy will not gain xp.

-
-

create/drop dummy:characters.Character
-tag dummy=training_dummy

-
-

Attack it with your new command

-
-

attack dummy

-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Inputfuncs.html b/docs/0.9.5/Inputfuncs.html deleted file mode 100644 index 1255477c30..0000000000 --- a/docs/0.9.5/Inputfuncs.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - - - - Inputfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Inputfuncs

-

An inputfunc is an Evennia function that handles a particular input (an inputcommand) from -the client. The inputfunc is the last destination for the inputcommand along the ingoing message -path. The inputcommand always has the form (commandname, (args), {kwargs}) and Evennia will use this to try to find and call an inputfunc on the form

-
    def commandname(session, *args, **kwargs):
-        # ...
-
-
-
-

Or, if no match was found, it will call an inputfunc named “default” on this form

-
    def default(session, cmdname, *args, **kwargs):
-        # cmdname is the name of the mismatched inputcommand
-
-
-
-
-

Adding your own inputfuncs

-

This is simple. Add a function on the above form to mygame/server/conf/inputfuncs.py. Your -function must be in the global, outermost scope of that module and not start with an underscore -(_) to be recognized as an inputfunc. Reload the server. That’s it. To overload a default -inputfunc (see below), just add a function with the same name.

-

The modules Evennia looks into for inputfuncs are defined in the list settings.INPUT_FUNC_MODULES. -This list will be imported from left to right and later imported functions will replace earlier -ones.

-
-
-

Default inputfuncs

-

Evennia defines a few default inputfuncs to handle the common cases. These are defined in -evennia/server/inputfuncs.py.

-
-

text

-
    -
  • Input: ("text", (textstring,), {})

  • -
  • Output: Depends on Command triggered

  • -
-

This is the most common of inputcommands, and the only one supported by every traditional mud. The -argument is usually what the user sent from their command line. Since all text input from the user -like this is considered a Command, this inputfunc will do things like nick-replacement -and then pass on the input to the central Commandhandler.

-
-
-

echo

-
    -
  • Input: ("echo", (args), {})

  • -
  • Output: ("text", ("Echo returns: %s" % args), {})

  • -
-

This is a test input, which just echoes the argument back to the session as text. Can be used for -testing custom client input.

-
-
-

default

-

The default function, as mentioned above, absorbs all non-recognized inputcommands. The default one -will just log an error.

-
-
-

client_options

-
    -
  • Input: ("client_options, (), {key:value, ...})

  • -
  • Output:

  • -
  • normal: None

  • -
  • get: ("client_options", (), {key:value, ...})

  • -
-

This is a direct command for setting protocol options. These are settable with the @option -command, but this offers a client-side way to set them. Not all connection protocols makes use of -all flags, but here are the possible keywords:

-
    -
  • get (bool): If this is true, ignore all other kwargs and immediately return the current settings -as an outputcommand ("client_options", (), {key=value, ...})-

  • -
  • client (str): A client identifier, like “mushclient”.

  • -
  • version (str): A client version

  • -
  • ansi (bool): Supports ansi colors

  • -
  • xterm256 (bool): Supports xterm256 colors or not

  • -
  • mxp (bool): Supports MXP or not

  • -
  • utf-8 (bool): Supports UTF-8 or not

  • -
  • screenreader (bool): Screen-reader mode on/off

  • -
  • mccp (bool): MCCP compression on/off

  • -
  • screenheight (int): Screen height in lines

  • -
  • screenwidth (int): Screen width in characters

  • -
  • inputdebug (bool): Debug input functions

  • -
  • nomarkup (bool): Strip all text tags

  • -
  • raw (bool): Leave text tags unparsed

  • -
-
-

Note that there are two GMCP aliases to this inputfunc - hello and supports_set, which means -it will be accessed via the GMCP Hello and Supports.Set instructions assumed by some clients.

-
-
-
-

get_client_options

-
    -
  • Input: ("get_client_options, (), {key:value, ...})

  • -
  • Output: ("client_options, (), {key:value, ...})

  • -
-

This is a convenience wrapper that retrieves the current options by sending “get” to -client_options above.

-
-
-

get_inputfuncs

-
    -
  • Input: ("get_inputfuncs", (), {})

  • -
  • Output: ("get_inputfuncs", (), {funcname:docstring, ...})

  • -
-

Returns an outputcommand on the form ("get_inputfuncs", (), {funcname:docstring, ...}) - a list of -all the available inputfunctions along with their docstrings.

-
-
-

login

-
-

Note: this is currently experimental and not very well tested.

-
-
    -
  • Input: ("login", (username, password), {})

  • -
  • Output: Depends on login hooks

  • -
-

This performs the inputfunc version of a login operation on the current Session.

-
-
-

get_value

-

Input: ("get_value", (name, ), {}) -Output: ("get_value", (value, ), {})

-

Retrieves a value from the Character or Account currently controlled by this Session. Takes one -argument, This will only accept particular white-listed names, you’ll need to overload the function -to expand. By default the following values can be retrieved:

-
    -
  • “name” or “key”: The key of the Account or puppeted Character.

  • -
  • “location”: Name of the current location, or “None”.

  • -
  • “servername”: Name of the Evennia server connected to.

  • -
-
-
-

repeat

-
    -
  • Input: ("repeat", (), {"callback":funcname,                     "interval": secs, "stop": False})

  • -
  • Output: Depends on the repeated function. Will return ("text", (repeatlist),{} with a list of -accepted names if given an unfamiliar callback name.

  • -
-

This will tell evennia to repeatedly call a named function at a given interval. Behind the scenes -this will set up a Ticker. Only previously acceptable functions are possible to -repeat-call in this way, you’ll need to overload this inputfunc to add the ones you want to offer. -By default only two example functions are allowed, “test1” and “test2”, which will just echo a text -back at the given interval. Stop the repeat by sending "stop": True (note that you must include -both the callback name and interval for Evennia to know what to stop).

-
-
-

unrepeat

-
    -
  • Input: ("unrepeat", (), ("callback":funcname,                           "interval": secs)

  • -
  • Output: None

  • -
-

This is a convenience wrapper for sending “stop” to the repeat inputfunc.

-
-
-

monitor

-
    -
  • Input: ("monitor", (), ("name":field_or_argname, stop=False)

  • -
  • Output (on change): ("monitor", (), {"name":name, "value":value})

  • -
-

This sets up on-object monitoring of Attributes or database fields. Whenever the field or Attribute -changes in any way, the outputcommand will be sent. This is using the -MonitorHandler behind the scenes. Pass the “stop” key to stop monitoring. Note -that you must supply the name also when stopping to let the system know which monitor should be -cancelled.

-

Only fields/attributes in a whitelist are allowed to be used, you have to overload this function to -add more. By default the following fields/attributes can be monitored:

-
    -
  • “name”: The current character name

  • -
  • “location”: The current location

  • -
  • “desc”: The description Argument

  • -
-
-
-
-

unmonitor

-
    -
  • Input: ("unmonitor", (), {"name":name})

  • -
  • Output: None

  • -
-

A convenience wrapper that sends “stop” to the monitor function.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Installing-on-Android.html b/docs/0.9.5/Installing-on-Android.html deleted file mode 100644 index 32e3cb2243..0000000000 --- a/docs/0.9.5/Installing-on-Android.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - Installing on Android — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Installing on Android

-

This page describes how to install and run the Evennia server on an Android phone. This will involve -installing a slew of third-party programs from the Google Play store, so make sure you are okay with -this before starting.

-
-

Install Termux

-

The first thing to do is install a terminal emulator that allows a “full” version of linux to be -run. Note that Android is essentially running on top of linux so if you have a rooted phone, you may -be able to skip this step. You don’t require a rooted phone to install Evennia though.

-

Assuming we do not have root, we will install -Termux. -Termux provides a base installation of Linux essentials, including apt and Python, and makes them -available under a writeable directory. It also gives us a terminal where we can enter commands. By -default, Android doesn’t give you permissions to the root folder, so Termux pretends that its own -installation directory is the root directory.

-

Termux will set up a base system for us on first launch, but we will need to install some -prerequisites for Evennia. Commands you should run in Termux will look like this:

-
$ cat file.txt
-
-
-

The $ symbol is your prompt - do not include it when running commands.

-
-
-

Prerequisites

-

To install some of the libraries Evennia requires, namely Pillow and Twisted, we have to first -install some packages they depend on. In Termux, run the following

-
$ pkg install -y clang git zlib ndk-sysroot libjpeg-turbo libcrypt python
-
-
-

Termux ships with Python 3, perfect. Python 3 has venv (virtualenv) and pip (Python’s module -installer) built-in.

-

So, let’s set up our virtualenv. This keeps the Python packages we install separate from the system -versions.

-
$ cd
-$ python3 -m venv evenv
-
-
-

This will create a new folder, called evenv, containing the new python executable. -Next, let’s activate our new virtualenv. Every time you want to work on Evennia, you need to run the -following command:

-
$ source evenv/bin/activate
-
-
-

Your prompt will change to look like this:

-
(evenv) $
-
-
-

Update the updaters and installers in the venv: pip, setuptools and wheel.

-
python3 -m pip install --upgrade pip setuptools wheel
-
-
-
-

Installing Evennia

-

Now that we have everything in place, we’re ready to download and install Evennia itself.

-

Mysterious incantations

-
export LDFLAGS="-L/data/data/com.termux/files/usr/lib/"
-export CFLAGS="-I/data/data/com.termux/files/usr/include/"
-
-
-

(these tell clang, the C compiler, where to find the bits for zlib when building Pillow)

-

Install the latest Evennia in a way that lets you edit the source

-
(evenv) $ pip install --upgrade -e 'git+https://github.com/evennia/evennia#egg=evennia'
-
-
-

This step will possibly take quite a while - we are downloading Evennia and are then installing it, -building all of the requirements for Evennia to run. If you run into trouble on this step, please -see Troubleshooting.

-

You can go to the dir where Evennia is installed with cd $VIRTUAL_ENV/src/evennia. git grep (something) can be handy, as can git diff

-
-
-

Final steps

-

At this point, Evennia is installed on your phone! You can now continue with the original Getting -Started instruction, we repeat them here for clarity.

-

To start a new game:

-
(evenv) $ evennia --init mygame
-(evenv) $ ls
-mygame evenv
-
-
-

To start the game for the first time:

-
(evenv) $ cd mygame
-(evenv) $ evennia migrate
-(evenv) $ evennia start
-
-
-

Your game should now be running! Open a web browser at http://localhost:4001 or point a telnet -client to localhost:4000 and log in with the user you created.

-
-
-
-

Running Evennia

-

When you wish to run Evennia, get into your Termux console and make sure you have activated your -virtualenv as well as are in your game’s directory. You can then run evennia start as normal.

-
$ cd ~ && source evenv/bin/activate
-(evenv) $ cd mygame
-(evenv) $ evennia start
-
-
-

You may wish to look at the Linux Instructions for more.

-
-
-

Caveats

-
    -
  • Android’s os module doesn’t support certain functions - in particular getloadavg. Thusly, running -the command @server in-game will throw an exception. So far, there is no fix for this problem.

  • -
  • As you might expect, performance is not amazing.

  • -
  • Android is fairly aggressive about memory handling, and you may find that your server process is -killed if your phone is heavily taxed. Termux seems to keep a notification up to discourage this.

  • -
-
-
-

Troubleshooting

-

As time goes by and errors are reported, this section will be added to.

-

Some steps to try anyway:

-
    -
  • Make sure your packages are up-to-date, try running pkg update && pkg upgrade -y

  • -
  • Make sure you’ve installed the clang package. If not, try pkg install clang -y

  • -
  • Make sure you’re in the right directory. `cd ~/mygame

  • -
  • Make sure you’ve sourced your virtualenv. type cd && source evenv/bin/activate

  • -
  • See if a shell will start: cd ~/mygame ; evennia shell

  • -
  • Look at the log files in ~/mygame/server/logs/

  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Internationalization.html b/docs/0.9.5/Internationalization.html deleted file mode 100644 index 1c2d363470..0000000000 --- a/docs/0.9.5/Internationalization.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - Internationalization — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Internationalization

-

Internationalization (often abbreviated i18n since there are 18 characters between the first “i” -and the last “n” in that word) allows Evennia’s core server to return texts in other languages than -English - without anyone having to edit the source code. Take a look at the locale directory of -the Evennia installation, there you will find which languages are currently supported.

-
-

Changing server language

-

Change language by adding the following to your mygame/server/conf/settings.py file:

-
    USE_I18N = True
-    LANGUAGE_CODE = 'en'
-
-
-

Here 'en' should be changed to the abbreviation for one of the supported languages found in -locale/. Restart the server to activate i18n. The two-character international language codes are -found here.

-
-

Windows Note: If you get errors concerning gettext or xgettext on Windows, see the Django -documentation. A -self-installing and up-to-date version of gettext for Windows (32/64-bit) is available on -Github.

-
-
-
-

Translating Evennia

-
-

Important Note: Evennia offers translations of hard-coded strings in the server, things like -“Connection closed” or “Server restarted”, strings that end users will see and which game devs are -not supposed to change on their own. Text you see in the log file or on the command line (like error -messages) are generally not translated (this is a part of Python).

-
-
-

In addition, text in default Commands and in default Typeclasses will not be translated by -switching i18n language. To translate Commands and Typeclass hooks you must overload them in your -game directory and translate their returns to the language you want. This is because from Evennia’s -perspective, adding i18n code to commands tend to add complexity to code that is meant to be -changed anyway. One of the goals of Evennia is to keep the user-changeable code as clean and easy- -to-read as possible.

-
-

If you cannot find your language in evennia/locale/ it’s because noone has translated it yet. -Alternatively you might have the language but find the translation bad … You are welcome to help -improve the situation!

-

To start a new translation you need to first have cloned the Evennia repositry with GIT and -activated a python virtualenv as described on the Getting Started page. You now -need to cd to the evennia/ directory. This is not your created game folder but the main -Evennia library folder. If you see a folder locale/ then you are in the right place. From here you -run:

-
 evennia makemessages <language-code>
-
-
-

where <language-code> is the two-letter locale code -for the language you want, like ‘sv’ for Swedish or ‘es’ for Spanish. After a moment it will tell -you the language has been processed. For instance:

-
 evennia makemessages sv
-
-
-

If you started a new language a new folder for that language will have emerged in the locale/ -folder. Otherwise the system will just have updated the existing translation with eventual new -strings found in the server. Running this command will not overwrite any existing strings so you can -run it as much as you want.

-
-

Note: in Django, the makemessages command prefixes the locale name by the -l option (... makemessages -l sv for instance). This syntax is not allowed in Evennia, due to the fact that -l -is the option to tail log files. Hence, makemessages doesn’t use the -l flag.

-
-

Next head to locale/<language-code>/LC_MESSAGES and edit the **.po file you find there. You can -edit this with a normal text editor but it is easiest if you use a special po-file editor from the -web (search the web for “po editor” for many free alternatives).

-

The concept of translating is simple, it’s just a matter of taking the english strings you find in -the **.po file and add your language’s translation best you can. The **.po format (and many -supporting editors) allow you to mark translations as “fuzzy”. This tells the system (and future -translators) that you are unsure about the translation, or that you couldn’t find a translation that -exactly matched the intention of the original text. Other translators will see this and might be -able to improve it later. -Finally, you need to compile your translation into a more efficient form. Do so from the evennia -folder -again:

-
evennia compilemessages
-
-
-

This will go through all languages and create/update compiled files (**.mo) for them. This needs -to be done whenever a **.po file is updated.

-

When you are done, send the **.po and *.mo file to the Evennia developer list (or push it into -your own repository clone) so we can integrate your translation into Evennia!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Learn-Python-for-Evennia-The-Hard-Way.html b/docs/0.9.5/Learn-Python-for-Evennia-The-Hard-Way.html deleted file mode 100644 index 5d068316b3..0000000000 --- a/docs/0.9.5/Learn-Python-for-Evennia-The-Hard-Way.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - Learn Python for Evennia The Hard Way — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Learn Python for Evennia The Hard Way

-
-
-

WORK IN PROGRESS - DO NOT USE

-

Evennia provides a great foundation to build your very own MU* whether you have programming -experience or none at all. Whilst Evennia has a number of in-game building commands and tutorials -available to get you started, when approaching game systems of any complexity it is advisable to -have the basics of Python under your belt before jumping into the code. There are many Python -tutorials freely available online however this page focuses on Learn Python the Hard Way (LPTHW) by -Zed Shaw. This tutorial takes you through the basics of Python and progresses you to creating your -very own online text based game. Whilst completing the course feel free to install Evennia and try -out some of our beginner tutorials. On completion you can return to this page, which will act as an -overview to the concepts separating your online text based game and the inner-workings of Evennia. --The latter portion of the tutorial focuses working your engine into a webpage and is not strictly -required for development in Evennia.

-
-

Exercise 23

-

You may have returned here when you were invited to read some code. If you haven’t already, you -should now have the knowledge necessary to install Evennia. Head over to the Getting Started page -for install instructions. You can also try some of our tutorials to get you started on working with -Evennia.

-
-
-

Bridging the gap.

-

If you have successfully completed the Learn Python the Hard Way tutorial you should now have a -simple browser based Interactive Fiction engine which looks similar to this. -This engine is built using a single interactive object type, the Room class. The Room class holds a -description of itself that is presented to the user and a list of hardcoded commands which if -selected correctly will present you with the next rooms’ description and commands. Whilst your game -only has one interactive object, MU* have many more: Swords and shields, potions and scrolls or even -laser guns and robots. Even the player has an in-game representation in the form of your character. -Each of these examples are represented by their own object with their own description that can be -presented to the user.

-

A basic object in Evennia has a number of default functions but perhaps most important is the idea -of location. In your text engine you receive a description of a room but you are not really in the -room because you have no in-game representation. However, in Evennia when you enter a Dungeon you -ARE in the dungeon. That is to say your character.location = Dungeon whilst the Dungeon.contents now -has a spunky young adventurer added to it. In turn, your character.contents may have amongst it a -number of swords and potions to help you on your adventure and their location would be you.

-

In reality each of these “objects” are just an entry in your Evennia projects database which keeps -track of all these attributes, such as location and contents. Making changes to those attributes and -the rules in which they are changed is the most fundamental perspective of how your game works. We -define those rules in the objects Typeclass. The Typeclass is a Python class with a special -connection to the games database which changes values for us through various class methods. Let’s -look at your characters Typeclass rules for changing location.

-
         1. `self.at_before_move(destination)` (if this returns False, move is aborted)
-         2. `self.announce_move_from(destination)`
-         3. (move happens here)
-         4. `self.announce_move_to(source_location)`
-         5. `self.at_after_move(source_location)`
-
-
-

First we check if it’s okay to leave our current location, then we tell everyone there that we’re -leaving. We move locations and tell everyone at our new location that we’ve arrived before checking -we’re okay to be there. By default stages 1 and 5 are empty ready for us to add some rules. We’ll -leave an explanation as to how to make those changes for the tutorial section, but imagine if you -were an astronaut. A smart astronaut might stop at step 1 to remember to put his helmet on whilst a -slower astronaut might realise he’s forgotten in step 5 before shortly after ceasing to be an -astronaut.

-

With all these objects and all this moving around it raises another problem. In your text engine the -commands available to the player were hard-coded to the room. That means if we have commands we -always want available to the player we’ll need to have those commands hard-coded on every single -room. What about an armoury? When all the swords are gone the command to take a sword would still -remain causing confusion. Evennia solves this problem by giving each object the ability to hold -commands. Rooms can have commands attached to them specific to that location, like climbing a tree; -Players can have commands which are always available to them, like ‘look’, ‘get’ and ‘say’; and -objects can have commands attached to them which unlock when taking possession of it, like attack -commands when obtaining a weapon.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Licensing.html b/docs/0.9.5/Licensing.html deleted file mode 100644 index ae84ec0de2..0000000000 --- a/docs/0.9.5/Licensing.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - Licensing — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Licensing

-

Evennia is licensed under the very friendly BSD -(3-clause) license. You can find the license as -LICENSE.txt in the Evennia -repository’s root.

-

Q: When creating a game using Evennia, what does the license permit me to do with it?

-

A: It’s your own game world to do with as you please! Keep it to yourself or re-distribute it -under another license of your choice - or sell it and become filthy rich for all we care.

-

Q: I have modified the Evennia library itself, what does the license say about that?

-

A: Our license allows you to do whatever you want with your modified Evennia, including -re-distributing or selling it, as long as you include our license and copyright info found in -LICENSE.txt along with your distribution.

-

… Of course, if you fix bugs or add some new snazzy feature we softly nudge you to make those -changes available so they can be added to the core Evennia package for everyone’s benefit. The -license doesn’t require you to do it, but that doesn’t mean we won’t still greatly appreciate it -if you do!

-

Q: Can I re-distribute the Evennia server package along with my custom game implementation?

-

A: Sure. As long as the text in LICENSE.txt is included.

-

Q: What about Contributions?

-

The contributions in evennia/evennia/contrib are considered to be released under the same license -as Evennia itself, unless the individual contributor has specifically defined otherwise.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Links.html b/docs/0.9.5/Links.html deleted file mode 100644 index 2f024f4010..0000000000 --- a/docs/0.9.5/Links.html +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - - - - Links — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- - - - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Locks.html b/docs/0.9.5/Locks.html deleted file mode 100644 index 2e19aebd5a..0000000000 --- a/docs/0.9.5/Locks.html +++ /dev/null @@ -1,607 +0,0 @@ - - - - - - - - - Locks — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Locks

-

For most games it is a good idea to restrict what people can do. In Evennia such restrictions are -applied and checked by something called locks. All Evennia entities (Commands, -Objects, Scripts, Accounts, Help System, -messages and channels) are accessed through locks.

-

A lock can be thought of as an “access rule” restricting a particular use of an Evennia entity. -Whenever another entity wants that kind of access the lock will analyze that entity in different -ways to determine if access should be granted or not. Evennia implements a “lockdown” philosophy - -all entities are inaccessible unless you explicitly define a lock that allows some or full access.

-

Let’s take an example: An object has a lock on itself that restricts how people may “delete” that -object. Apart from knowing that it restricts deletion, the lock also knows that only players with -the specific ID of, say, 34 are allowed to delete it. So whenever a player tries to run delete -on the object, the delete command makes sure to check if this player is really allowed to do so. -It calls the lock, which in turn checks if the player’s id is 34. Only then will it allow delete -to go on with its job.

-
-

Setting and checking a lock

-

The in-game command for setting locks on objects is lock:

-
 > lock obj = <lockstring>
-
-
-

The <lockstring> is a string of a certain form that defines the behaviour of the lock. We will go -into more detail on how <lockstring> should look in the next section.

-

Code-wise, Evennia handles locks through what is usually called locks on all relevant entities. -This is a handler that allows you to add, delete and check locks.

-
     myobj.locks.add(<lockstring>)
-
-
-

One can call locks.check() to perform a lock check, but to hide the underlying implementation all -objects also have a convenience function called access. This should preferably be used. In the -example below, accessing_obj is the object requesting the ‘delete’ access whereas obj is the -object that might get deleted. This is how it would look (and does look) from inside the delete -command:

-
     if not obj.access(accessing_obj, 'delete'):
-         accessing_obj.msg("Sorry, you may not delete that.")
-         return
-
-
-
-
-

Defining locks

-

Defining a lock (i.e. an access restriction) in Evennia is done by adding simple strings of lock -definitions to the object’s locks property using obj.locks.add().

-

Here are some examples of lock strings (not including the quotes):

-
     delete:id(34)   # only allow obj #34 to delete
-     edit:all()      # let everyone edit
-     # only those who are not "very_weak" or are Admins may pick this up
-     get: not attr(very_weak) or perm(Admin)
-
-
-

Formally, a lockstring has the following syntax:

-
     access_type: [NOT] lockfunc1([arg1,..]) [AND|OR] [NOT] lockfunc2([arg1,...]) [...]
-
-
-

where [] marks optional parts. AND, OR and NOT are not case sensitive and excess spaces are -ignored. lockfunc1, lockfunc2 etc are special lock functions available to the lock system.

-

So, a lockstring consists of the type of restriction (the access_type), a colon (:) and then an -expression involving function calls that determine what is needed to pass the lock. Each function -returns either True or False. AND, OR and NOT work as they do normally in Python. If the -total result is True, the lock is passed.

-

You can create several lock types one after the other by separating them with a semicolon (;) in -the lockstring. The string below yields the same result as the previous example:

-
delete:id(34);edit:all();get: not attr(very_weak) or perm(Admin)
-
-
-
-

Valid access_types

-

An access_type, the first part of a lockstring, defines what kind of capability a lock controls, -such as “delete” or “edit”. You may in principle name your access_type anything as long as it is -unique for the particular object. The name of the access types is not case-sensitive.

-

If you want to make sure the lock is used however, you should pick access_type names that you (or -the default command set) actually checks for, as in the example of delete above that uses the -‘delete’ access_type.

-

Below are the access_types checked by the default commandset.

-
    -
  • Commands

    -
      -
    • cmd - this defines who may call this command at all.

    • -
    -
  • -
  • Objects:

    -
      -
    • control - who is the “owner” of the object. Can set locks, delete it etc. Defaults to the -creator of the object.

    • -
    • call - who may call Object-commands stored on this Object except for the Object itself. By -default, Objects share their Commands with anyone in the same location (e.g. so you can ‘press’ a -Button object in the room). For Characters and Mobs (who likely only use those Commands for -themselves and don’t want to share them) this should usually be turned off completely, using -something like call:false().

    • -
    • examine - who may examine this object’s properties.

    • -
    • delete - who may delete the object.

    • -
    • edit - who may edit properties and attributes of the object.

    • -
    • view - if the look command will display/list this object

    • -
    • get- who may pick up the object and carry it around.

    • -
    • puppet - who may “become” this object and control it as their “character”.

    • -
    • attrcreate - who may create new attributes on the object (default True)

    • -
    -
  • -
  • Characters:

    -
      -
    • Same as for Objects

    • -
    -
  • -
  • Exits:

    -
      -
    • Same as for Objects

    • -
    • traverse - who may pass the exit.

    • -
    -
  • -
  • Accounts:

    -
      -
    • examine - who may examine the account’s properties.

    • -
    • delete - who may delete the account.

    • -
    • edit - who may edit the account’s attributes and properties.

    • -
    • msg - who may send messages to the account.

    • -
    • boot - who may boot the account.

    • -
    -
  • -
  • Attributes: (only checked by obj.secure_attr)

    -
      -
    • attrread - see/access attribute

    • -
    • attredit - change/delete attribute

    • -
    -
  • -
  • Channels:

    -
      -
    • control - who is administrating the channel. This means the ability to delete the channel, -boot listeners etc.

    • -
    • send - who may send to the channel.

    • -
    • listen - who may subscribe and listen to the channel.

    • -
    -
  • -
  • HelpEntry:

    -
      -
    • examine - who may view this help entry (usually everyone)

    • -
    • edit - who may edit this help entry.

    • -
    -
  • -
-

So to take an example, whenever an exit is to be traversed, a lock of the type traverse will be -checked. Defining a suitable lock type for an exit object would thus involve a lockstring traverse: <lock functions>.

-
-
-

Custom access_types

-

As stated above, the access_type part of the lock is simply the ‘name’ or ‘type’ of the lock. The -text is an arbitrary string that must be unique for an object. If adding a lock with the same -access_type as one that already exists on the object, the new one override the old one.

-

For example, if you wanted to create a bulletin board system and wanted to restrict who can either -read a board or post to a board. You could then define locks such as:

-
     obj.locks.add("read:perm(Player);post:perm(Admin)")
-
-
-

This will create a ‘read’ access type for Characters having the Player permission or above and a -‘post’ access type for those with Admin permissions or above (see below how the perm() lock -function works). When it comes time to test these permissions, simply check like this (in this -example, the obj may be a board on the bulletin board system and accessing_obj is the player -trying to read the board):

-
     if not obj.access(accessing_obj, 'read'):
-         accessing_obj.msg("Sorry, you may not read that.")
-         return
-
-
-
-
-

Lock functions

-

A lock function is a normal Python function put in a place Evennia looks for such functions. The -modules Evennia looks at is the list settings.LOCK_FUNC_MODULES. All functions in any of those -modules will automatically be considered a valid lock function. The default ones are found in -evennia/locks/lockfuncs.py and you can start adding your own in mygame/server/conf/lockfuncs.py. -You can append the setting to add more module paths. To replace a default lock function, just add -your own with the same name.

-

A lock function must always accept at least two arguments - the accessing object (this is the -object wanting to get access) and the accessed object (this is the object with the lock). Those -two are fed automatically as the first two arguments to the function when the lock is checked. Any -arguments explicitly given in the lock definition will appear as extra arguments.

-
    # A simple example lock function. Called with e.g. `id(34)`. This is
-    # defined in, say mygame/server/conf/lockfuncs.py
-    
-    def id(accessing_obj, accessed_obj, *args, **kwargs):
-        if args:
-            wanted_id = args[0]
-            return accessing_obj.id == wanted_id
-        return False
-
-
-

The above could for example be used in a lock function like this:

-
    # we have `obj` and `owner_object` from before
-    obj.locks.add("edit: id(%i)" % owner_object.id)
-
-
-

We could check if the “edit” lock is passed with something like this:

-
    # as part of a Command's func() method, for example
-    if not obj.access(caller, "edit"):
-        caller.msg("You don't have access to edit this!")
-        return
-
-
-

In this example, everyone except the caller with the right id will get the error.

-
-

(Using the * and ** syntax causes Python to magically put all extra arguments into a list -args and all keyword arguments into a dictionary kwargs respectively. If you are unfamiliar with -how *args and **kwargs work, see the Python manuals).

-
-

Some useful default lockfuncs (see src/locks/lockfuncs.py for more):

-
    -
  • true()/all() - give access to everyone

  • -
  • false()/none()/superuser() - give access to none. Superusers bypass the check entirely and are -thus the only ones who will pass this check.

  • -
  • perm(perm) - this tries to match a given permission property, on an Account firsthand, on a -Character second. See below.

  • -
  • perm_above(perm) - like perm but requires a “higher” permission level than the one given.

  • -
  • id(num)/dbref(num) - checks so the access_object has a certain dbref/id.

  • -
  • attr(attrname) - checks if a certain Attribute exists on accessing_object.

  • -
  • attr(attrname, value) - checks so an attribute exists on accessing_object and has the given -value.

  • -
  • attr_gt(attrname, value) - checks so accessing_object has a value larger (>) than the given -value.

  • -
  • attr_ge, attr_lt, attr_le, attr_ne - corresponding for >=, <, <= and !=.

  • -
  • holds(objid) - checks so the accessing objects contains an object of given name or dbref.

  • -
  • inside() - checks so the accessing object is inside the accessed object (the inverse of -holds()).

  • -
  • pperm(perm), pid(num)/pdbref(num) - same as perm, id/dbref but always looks for -permissions and dbrefs of Accounts, not on Characters.

  • -
  • serversetting(settingname, value) - Only returns True if Evennia has a given setting or a -setting set to a given value.

  • -
-
-
-
-

Checking simple strings

-

Sometimes you don’t really need to look up a certain lock, you just want to check a lockstring. A -common use is inside Commands, in order to check if a user has a certain permission. The lockhandler -has a method check_lockstring(accessing_obj, lockstring, bypass_superuser=False) that allows this.

-
     # inside command definition
-     if not self.caller.locks.check_lockstring(self.caller, "dummy:perm(Admin)"):
-         self.caller.msg("You must be an Admin or higher to do this!")
-         return
-
-
-

Note here that the access_type can be left to a dummy value since this method does not actually do -a Lock lookup.

-
-
-

Default locks

-

Evennia sets up a few basic locks on all new objects and accounts (if we didn’t, noone would have -any access to anything from the start). This is all defined in the root Typeclasses -of the respective entity, in the hook method basetype_setup() (which you usually don’t want to -edit unless you want to change how basic stuff like rooms and exits store their internal variables). -This is called once, before at_object_creation, so just put them in the latter method on your -child object to change the default. Also creation commands like create changes the locks of -objects you create - for example it sets the control lock_type so as to allow you, its creator, to -control and delete the object.

-
-
-
-

Permissions

-
-

This section covers the underlying code use of permissions. If you just want to learn how to -practically assign permissions in-game, refer to the Building Permissions -page, which details how you use the perm command.

-
-

A permission is simply a list of text strings stored in the handler permissions on Objects -and Accounts. Permissions can be used as a convenient way to structure access levels and -hierarchies. It is set by the perm command. Permissions are especially handled by the perm() and -pperm() lock functions listed above.

-

Let’s say we have a red_key object. We also have red chests that we want to unlock with this key.

-
perm red_key = unlocks_red_chests
-
-
-

This gives the red_key object the permission “unlocks_red_chests”. Next we lock our red chests:

-
lock red chest = unlock:perm(unlocks_red_chests)
-
-
-

What this lock will expect is to the fed the actual key object. The perm() lock function will -check the permissions set on the key and only return true if the permission is the one given.

-

Finally we need to actually check this lock somehow. Let’s say the chest has an command open <key> -sitting on itself. Somewhere in its code the command needs to figure out which key you are using and -test if this key has the correct permission:

-
    # self.obj is the chest
-    # and used_key is the key we used as argument to
-    # the command. The self.caller is the one trying
-    # to unlock the chest
-    if not self.obj.access(used_key, "unlock"):
-        self.caller.msg("The key does not fit!")
-        return
-
-
-

All new accounts are given a default set of permissions defined by -settings.PERMISSION_ACCOUNT_DEFAULT.

-

Selected permission strings can be organized in a permission hierarchy by editing the tuple -settings.PERMISSION_HIERARCHY. Evennia’s default permission hierarchy is as follows:

-
 Developer        # like superuser but affected by locks
- Admin            # can administrate accounts
- Builder          # can edit the world
- Helper           # can edit help files
- Player           # can chat and send tells (default level)
-
-
-

(Also the plural form works, so you could use Developers etc too).

-
-

There is also a Guest level below Player that is only active if settings.GUEST_ENABLED is -set. This is never part of settings.PERMISSION_HIERARCHY.

-
-

The main use of this is that if you use the lock function perm() mentioned above, a lock check for -a particular permission in the hierarchy will also grant access to those with higher hierarchy -access. So if you have the permission “Admin” you will also pass a lock defined as perm(Builder) -or any of those levels below “Admin”.

-

When doing an access check from an Object or Character, the perm() lock function will -always first use the permissions of any Account connected to that Object before checking for -permissions on the Object. In the case of hierarchical permissions (Admins, Builders etc), the -Account permission will always be used (this stops an Account from escalating their permission by -puppeting a high-level Character). If the permission looked for is not in the hierarchy, an exact -match is required, first on the Account and if not found there (or if no Account is connected), then -on the Object itself.

-

Here is how you use perm to give an account more permissions:

-
 perm/account Tommy = Builders
- perm/account/del Tommy = Builders # remove it again
-
-
-

Note the use of the /account switch. It means you assign the permission to the -Accounts Tommy instead of any Character that also happens to be named -“Tommy”.

-

Putting permissions on the Account guarantees that they are kept, regardless of which Character -they are currently puppeting. This is especially important to remember when assigning permissions -from the hierarchy tree - as mentioned above, an Account’s permissions will overrule that of its -character. So to be sure to avoid confusion you should generally put hierarchy permissions on the -Account, not on their Characters (but see also quelling).

-

Below is an example of an object without any connected account

-
    obj1.permissions = ["Builders", "cool_guy"]
-    obj2.locks.add("enter:perm_above(Accounts) and perm(cool_guy)")
-    
-    obj2.access(obj1, "enter") # this returns True!
-
-
-

And one example of a puppet with a connected account:

-
    account.permissions.add("Accounts")
-    puppet.permissions.add("Builders", "cool_guy")
-    obj2.locks.add("enter:perm_above(Accounts) and perm(cool_guy)")
-    
-    obj2.access(puppet, "enter") # this returns False!
-
-
-
-

Superusers

-

There is normally only one superuser account and that is the one first created when starting -Evennia (User #1). This is sometimes known as the “Owner” or “God” user. A superuser has more than -full access - it completely bypasses all locks so no checks are even run. This allows for the -superuser to always have access to everything in an emergency. But it also hides any eventual errors -you might have made in your lock definitions. So when trying out game systems you should either use -quelling (see below) or make a second Developer-level character so your locks get tested correctly.

-
-
-

Quelling

-

The quell command can be used to enforce the perm() lockfunc to ignore permissions on the -Account and instead use the permissions on the Character only. This can be used e.g. by staff to -test out things with a lower permission level. Return to the normal operation with unquell. Note -that quelling will use the smallest of any hierarchical permission on the Account or Character, so -one cannot escalate one’s Account permission by quelling to a high-permission Character. Also the -superuser can quell their powers this way, making them affectable by locks.

-
-
-

More Lock definition examples

-
examine: attr(eyesight, excellent) or perm(Builders)
-
-
-

You are only allowed to do examine on this object if you have ‘excellent’ eyesight (that is, has -an Attribute eyesight with the value excellent defined on yourself) or if you have the -“Builders” permission string assigned to you.

-
open: holds('the green key') or perm(Builder)
-
-
-

This could be called by the open command on a “door” object. The check is passed if you are a -Builder or has the right key in your inventory.

-
cmd: perm(Builders)
-
-
-

Evennia’s command handler looks for a lock of type cmd to determine if a user is allowed to even -call upon a particular command or not. When you define a command, this is the kind of lock you must -set. See the default command set for lots of examples. If a character/account don’t pass the cmd -lock type the command will not even appear in their help list.

-
cmd: not perm(no_tell)
-
-
-

“Permissions” can also be used to block users or implement highly specific bans. The above example -would be be added as a lock string to the tell command. This will allow everyone not having the -“permission” no_tell to use the tell command. You could easily give an account the “permission” -no_tell to disable their use of this particular command henceforth.

-
    dbref = caller.id
-    lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Admin);get:all()" %
-(dbref, dbref)
-    new_obj.locks.add(lockstring)
-
-
-

This is how the create command sets up new objects. In sequence, this permission string sets the -owner of this object be the creator (the one running create). Builders may examine the object -whereas only Admins and the creator may delete it. Everyone can pick it up.

-
-
-

A complete example of setting locks on an object

-

Assume we have two objects - one is ourselves (not superuser) and the other is an Object -called box.

-
 > create/drop box
- > desc box = "This is a very big and heavy box."
-
-
-

We want to limit which objects can pick up this heavy box. Let’s say that to do that we require the -would-be lifter to to have an attribute strength on themselves, with a value greater than 50. We -assign it to ourselves to begin with.

-
 > set self/strength = 45
-
-
-

Ok, so for testing we made ourselves strong, but not strong enough. Now we need to look at what -happens when someone tries to pick up the the box - they use the get command (in the default set). -This is defined in evennia/commands/default/general.py. In its code we find this snippet:

-
    if not obj.access(caller, 'get'):
-        if obj.db.get_err_msg:
-            caller.msg(obj.db.get_err_msg)
-        else:
-            caller.msg("You can't get that.")
-        return
-
-
-

So the get command looks for a lock with the type get (not so surprising). It also looks for an -Attribute on the checked object called get_err_msg in order to return a customized -error message. Sounds good! Let’s start by setting that on the box:

-
 > set box/get_err_msg = You are not strong enough to lift this box.
-
-
-

Next we need to craft a Lock of type get on our box. We want it to only be passed if the accessing -object has the attribute strength of the right value. For this we would need to create a lock -function that checks if attributes have a value greater than a given value. Luckily there is already -such a one included in evennia (see evennia/locks/lockfuncs.py), called attr_gt.

-

So the lock string will look like this: get:attr_gt(strength, 50). We put this on the box now:

-
 lock box = get:attr_gt(strength, 50)
-
-
-

Try to get the object and you should get the message that we are not strong enough. Increase your -strength above 50 however and you’ll pick it up no problem. Done! A very heavy box!

-

If you wanted to set this up in python code, it would look something like this:

-
   
- from evennia import create_object
-    
-    # create, then set the lock
-    box = create_object(None, key="box")
-    box.locks.add("get:attr_gt(strength, 50)")
-    
-    # or we can assign locks in one go right away
-    box = create_object(None, key="box", locks="get:attr_gt(strength, 50)")
-    
-    # set the attributes
-    box.db.desc = "This is a very big and heavy box."
-    box.db.get_err_msg = "You are not strong enough to lift this box."
-    
-    # one heavy box, ready to withstand all but the strongest...
-
-
-
-
-

On Django’s permission system

-

Django also implements a comprehensive permission/security system of its own. The reason we don’t -use that is because it is app-centric (app in the Django sense). Its permission strings are of the -form appname.permstring and it automatically adds three of them for each database model in the app

-
    -
  • for the app evennia/object this would be for example ‘object.create’, ‘object.admin’ and -‘object.edit’. This makes a lot of sense for a web application, not so much for a MUD, especially -when we try to hide away as much of the underlying architecture as possible.

  • -
-

The django permissions are not completely gone however. We use it for validating passwords during -login. It is also used exclusively for managing Evennia’s web-based admin site, which is a graphical -front-end for the database of Evennia. You edit and assign such permissions directly from the web -interface. It’s stand-alone from the permissions described above.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Manually-Configuring-Color.html b/docs/0.9.5/Manually-Configuring-Color.html deleted file mode 100644 index 4c1504899f..0000000000 --- a/docs/0.9.5/Manually-Configuring-Color.html +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - - - - Manually Configuring Color — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Manually Configuring Color

-

This is a small tutorial for customizing your character objects, using the example of letting users -turn on and off ANSI color parsing as an example. @options NOCOLOR=True will now do what this -tutorial shows, but the tutorial subject can be applied to other toggles you may want, as well.

-

In the Building guide’s Colors page you can learn how to add color to your -game by using special markup. Colors enhance the gaming experience, but not all users want color. -Examples would be users working from clients that don’t support color, or people with various seeing -disabilities that rely on screen readers to play your game. Also, whereas Evennia normally -automatically detects if a client supports color, it may get it wrong. Being able to turn it on -manually if you know it should work could be a nice feature.

-

So here’s how to allow those users to remove color. It basically means you implementing a simple -configuration system for your characters. This is the basic sequence:

-
    -
  1. Define your own default character typeclass, inheriting from Evennia’s default.

  2. -
  3. Set an attribute on the character to control markup on/off.

  4. -
  5. Set your custom character class to be the default for new accounts.

  6. -
  7. Overload the msg() method on the typeclass and change how it uses markup.

  8. -
  9. Create a custom command to allow users to change their setting.

  10. -
-
-

Setting up a custom Typeclass

-

Create a new module in mygame/typeclasses named, for example, mycharacter.py. Alternatively you -can simply add a new class to ‘mygamegame/typeclasses/characters.py’.

-

In your new module(or characters.py), create a new Typeclass inheriting from -evennia.DefaultCharacter. We will also import evennia.utils.ansi, which we will use later.

-
    from evennia import Character
-    from evennia.utils import ansi
-
-    class ColorableCharacter(Character):
-        at_object_creation(self):
-            # set a color config value
-            self.db.config_color = True
-
-
-

Above we set a simple config value as an Attribute.

-

Let’s make sure that new characters are created of this type. Edit your -mygame/server/conf/settings.py file and add/change BASE_CHARACTER_TYPECLASS to point to your new -character class. Observe that this will only affect new characters, not those already created. You -have to convert already created characters to the new typeclass by using the @typeclass command -(try on a secondary character first though, to test that everything works - you don’t want to render -your root user unusable!).

-
 @typeclass/reset/force Bob = mycharacter.ColorableCharacter
-
-
-

@typeclass changes Bob’s typeclass and runs all its creation hooks all over again. The /reset -switch clears all attributes and properties back to the default for the new typeclass - this is -useful in this case to avoid ending up with an object having a “mixture” of properties from the old -typeclass and the new one. /force might be needed if you edit the typeclass and want to update the -object despite the actual typeclass name not having changed.

-
-
-

Overload the msg() method

-

Next we need to overload the msg() method. What we want is to check the configuration value before -calling the main function. The original msg method call is seen in evennia/objects/objects.py -and is called like this:

-
    msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
-
-
-

As long as we define a method on our custom object with the same name and keep the same number of -arguments/keywords we will overload the original. Here’s how it could look:

-
    class ColorableCharacter(Character):
-        # [...]
-        msg(self, text=None, from_obj=None, session=None, options=None,
-            **kwargs):
-            "our custom msg()"
-            if self.db.config_color is not None: # this would mean it was not set
-                if not self.db.config_color:
-                    # remove the ANSI from the text
-                    text = ansi.strip_ansi(text)
-            super().msg(text=text, from_obj=from_obj,
-                                               session=session, **kwargs)
-
-
-

Above we create a custom version of the msg() method. If the configuration Attribute is set, it -strips the ANSI from the text it is about to send, and then calls the parent msg() as usual. You -need to @reload before your changes become visible.

-

There we go! Just flip the attribute config_color to False and your users will not see any color. -As superuser (assuming you use the Typeclass ColorableCharacter) you can test this with the @py -command:

-
 @py self.db.config_color = False
-
-
-
-
-

Custom color config command

-

For completeness, let’s add a custom command so users can turn off their color display themselves if -they want.

-

In mygame/commands, create a new file, call it for example configcmds.py (it’s likely that -you’ll want to add other commands for configuration down the line). You can also copy/rename the -command template.

-
    from evennia import Command
-
-    class CmdConfigColor(Command):
-        """
-        Configures your color
-
-        Usage:
-          @togglecolor on|off
-
-        This turns ANSI-colors on/off.
-        Default is on.
-        """
-
-        key = "@togglecolor"
-        aliases = ["@setcolor"]
-
-        def func(self):
-            "implements the command"
-            # first we must remove whitespace from the argument
-            self.args = self.args.strip()
-            if not self.args or not self.args in ("on", "off"):
-                self.caller.msg("Usage: @setcolor on|off")
-                return
-            if self.args == "on":
-                self.caller.db.config_color = True
-                # send a message with a tiny bit of formatting, just for fun
-                self.caller.msg("Color was turned |won|W.")
-            else:
-                self.caller.db.config_color = False
-                self.caller.msg("Color was turned off.")
-
-
-

Lastly, we make this command available to the user by adding it to the default CharacterCmdSet in -mygame/commands/default_cmdsets.py and reloading the server. Make sure you also import the -command:

-
from mygame.commands import configcmds
-class CharacterCmdSet(default_cmds.CharacterCmdSet):
-    # [...]
-    def at_cmdset_creation(self):
-        """
-        Populates the cmdset
-        """
-        super().at_cmdset_creation()
-        #
-        # any commands you add below will overload the default ones.
-        #
-
-        # here is the only line that we edit
-        self.add(configcmds.CmdConfigColor())
-
-
-
-
-

More colors

-

Apart from ANSI colors, Evennia also supports Xterm256 colors (See [Colors](./TextTags.md#colored- -text)). The msg() method supports the xterm256 keyword for manually activating/deactiving -xterm256. It should be easy to expand the above example to allow players to customize xterm256 -regardless of if Evennia thinks their client supports it or not.

-

To get a better understanding of how msg() works with keywords, you can try this as superuser:

-
@py self.msg("|123Dark blue with xterm256, bright blue with ANSI", xterm256=True)
-@py self.msg("|gThis should be uncolored", nomarkup=True)
-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Mass-and-weight-for-objects.html b/docs/0.9.5/Mass-and-weight-for-objects.html deleted file mode 100644 index ae72b54081..0000000000 --- a/docs/0.9.5/Mass-and-weight-for-objects.html +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - Mass and weight for objects — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Mass and weight for objects

-

An easy addition to add dynamic variety to your world objects is to give them some mass. Why mass -and not weight? Weight varies in setting; for example things on the Moon weigh 1/6 as much. On -Earth’s surface and in most environments, no relative weight factor is needed.

-

In most settings, mass can be used as weight to spring a pressure plate trap or a floor giving way, -determine a character’s burden weight for travel speed… The total mass of an object can -contribute to the force of a weapon swing, or a speeding meteor to give it a potential striking -force.

-
-

Objects

-

Now that we have reasons for keeping track of object mass, let’s look at the default object class -inside your mygame/typeclasses/objects.py and see how easy it is to total up mass from an object and -its contents.

-
# inside your mygame/typeclasses/objects.py
-
-class Object(DefaultObject):
-# [...]
-    def get_mass(self):
-        mass = self.attributes.get('mass', 1) # Default objects have 1 unit mass.
-        return mass + sum(obj.get_mass() for obj in self.contents)
-
-
-

Adding the get_mass definition to the objects you want to sum up the masses for is done with -Python’s “sum” function which operates on all the contents, in this case by summing them to -return a total mass value.

-

If you only wanted specific object types to have mass or have the new object type in a different -module, see [[Adding-Object-Typeclass-Tutorial]] with its Heavy class object. You could set the -default for Heavy types to something much larger than 1 gram or whatever unit you want to use. Any -non-default mass would be stored on the mass [[Attributes]] of the objects.

-
-
-

Characters and rooms

-

You can add a get_mass definition to characters and rooms, also.

-

If you were in a one metric-ton elevator with four other friends also wearing armor and carrying -gold bricks, you might wonder if this elevator’s going to move, and how fast.

-

Assuming the unit is grams and the elevator itself weights 1,000 kilograms, it would already be -@set elevator/mass=1000000, we’re @set me/mass=85000 and our armor is @set armor/mass=50000. -We’re each carrying 20 gold bars each @set gold bar/mass=12400 then step into the elevator and see -the following message in the elevator’s appearance: Elevator weight and contents should not exceed 3 metric tons. Are we safe? Maybe not if you consider dynamic loading. But at rest:

-
# Elevator object knows when it checks itself:
-if self.get_mass() < 3000000:
-    pass  # Elevator functions as normal.
-else:
-    pass  # Danger! Alarm sounds, cable snaps, elevator stops...
-
-
-
-
-

Inventory

-

Example of listing mass of items in your inventory, don’t forget to add it to your -default_cmdsets.py file:

-
from evennia import utils
-
-class CmdInventory(Command):
-    """
-    view inventory
-    Usage:
-      inventory
-      inv
-    Switches:
-      /weight to display all available channels.
-    Shows your inventory: carrying, wielding, wearing, obscuring.
-    """
-
-    key = "inventory"
-    aliases = ["inv", "i"]
-    locks = "cmd:all()"
-
-    def func(self):
-        "check inventory"
-        items = self.caller.contents
-        if not items:
-            string = "You are not carrying anything."
-        else:
-            table = utils.evtable.EvTable("name", "weight")
-            for item in items:
-                mass = item.get_mass()
-                table.add_row(item.name, mass)
-            string = f"|wYou are carrying:|n\n{table}"
-        self.caller.msg(string)
-
-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Messagepath.html b/docs/0.9.5/Messagepath.html deleted file mode 100644 index ae28a51340..0000000000 --- a/docs/0.9.5/Messagepath.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - Messagepath — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Messagepath

-

The main functionality of Evennia is to communicate with clients connected to it; a player enters -commands or their client queries for a gui update (ingoing data). The server responds or sends data -on its own as the game changes (outgoing data). It’s important to understand how this flow of -information works in Evennia.

-
-

The ingoing message path

-

We’ll start by tracing data from the client to the server. Here it is in short:

-
Client ->
- PortalSession ->
-  PortalSessionhandler ->
-   (AMP) ->
-    ServerSessionHandler ->
-      ServerSession ->
-        Inputfunc
-
-
-
-

Client (ingoing)

-

The client sends data to Evennia in two ways.

-
    -
  • When first connecting, the client can send data to the server about its -capabilities. This is things like “I support xterm256 but not unicode” and is -mainly used when a Telnet client connects. This is called a “handshake” and -will generally set some flags on the Portal Session that -are later synced to the Server Session. Since this is not something the player -controls, we’ll not explore this further here.

  • -
  • The client can send an inputcommand to the server. Traditionally this only -happens when the player enters text on the command line. But with a custom -client GUI, a command could also come from the pressing of a button. Finally -the client may send commands based on a timer or some trigger.

  • -
-

Exactly how the inputcommand looks when it travels from the client to Evennia -depends on the Protocol used:

-
    -
  • Telnet: A string. If GMCP or MSDP OOB protocols are used, this string will -be formatted in a special way, but it’s still a raw string. If Telnet SSL is -active, the string will be encrypted.

  • -
  • SSH: An encrypted string

  • -
  • Webclient: A JSON-serialized string.

  • -
-
-
-

Portal Session (ingoing)

-

Each client is connected to the game via a Portal Session, one per connection. This Session is -different depending on the type of connection (telnet, webclient etc) and thus know how to handle -that particular data type. So regardless of how the data arrives, the Session will identify the type -of the instruction and any arguments it should have. For example, the telnet protocol will figure -that anything arriving normally over the wire should be passed on as a “text” type.

-
-
-

PortalSessionHandler (ingoing)

-

The PortalSessionhandler manages all connected Sessions in the Portal. Its data_in method -(called by each Portal Session) will parse the command names and arguments from the protocols and -convert them to a standardized form we call the inputcommand:

-
    (commandname, (args), {kwargs})
-
-
-

All inputcommands must have a name, but they may or may not have arguments and keyword arguments - -in fact no default inputcommands use kwargs at all. The most common inputcommand is “text”, which -has the argument the player input on the command line:

-
    ("text", ("look",), {})
-
-
-

This inputcommand-structure is pickled together with the unique session-id of the Session to which -it belongs. This is then sent over the AMP connection.

-
-
-

ServerSessionHandler (ingoing)

-

On the Server side, the AMP unpickles the data and associates the session id with the server-side -Session. Data and Session are passed to the server-side SessionHandler.data_in. This -in turn calls ServerSession.data_in()

-
-
-

ServerSession (ingoing)

-

The method ServerSession.data_in is meant to offer a single place to override if they want to -examine all data passing into the server from the client. It is meant to call the -ssessionhandler.call_inputfuncs with the (potentially processed) data (so this is technically a -sort of detour back to the sessionhandler).

-

In call_inputfuncs, the inputcommand’s name is compared against the names of all the inputfuncs -registered with the server. The inputfuncs are named the same as the inputcommand they are supposed -to handle, so the (default) inputfunc for handling our “look” command is called “text”. These are -just normal functions and one can plugin new ones by simply putting them in a module where Evennia -looks for such functions.

-

If a matching inputfunc is found, it will be called with the Session and the inputcommand’s -arguments:

-
    text(session, *("look",), **{})
-
-
-

If no matching inputfunc is found, an inputfunc named “default” will be tried and if that is also -not found, an error will be raised.

-
-
-

Inputfunc

-

The Inputfunc must be on the form func(session, *args, **kwargs). An exception is -the default inputfunc which has form default(session, cmdname, *args, **kwargs), where cmdname -is the un-matched inputcommand string.

-

This is where the message’s path diverges, since just what happens next depends on the type of -inputfunc was triggered. In the example of sending “look”, the inputfunc is named “text”. It will -pass the argument to the cmdhandler which will eventually lead to the look command being -executed.

-
-
-
-

The outgoing message path

-

Next let’s trace the passage from server to client.

-
msg ->
- ServerSession ->
-  ServerSessionHandler ->
-   (AMP) ->
-    PortalSessionHandler ->
-     PortalSession ->
-      Client
-
-
-
-

msg

-

All outgoing messages start in the msg method. This is accessible from three places:

-
    -
  • Object.msg

  • -
  • Account.msg

  • -
  • Session.msg

  • -
-

The call sign of the msg method looks like this:

-
    msg(text=None, from_obj=None, session=None, options=None, **kwargs)
-
-
-

For our purposes, what is important to know is that with the exception of from_obj, session and -options, all keywords given to the msg method is the name of an outputcommand and its -arguments. So text is actually such a command, taking a string as its argument. The reason text -sits as the first keyword argument is that it’s so commonly used (caller.msg("Text") for example). -Here are some examples

-
    msg("Hello!")   # using the 'text' outputfunc
-    msg(prompt="HP:%i, SP: %i, MP: %i" % (HP, SP, MP))
-    msg(mycommand=((1,2,3,4), {"foo": "bar"})
-
-
-
-

Note the form of the mycommand outputfunction. This explicitly defines the arguments and keyword -arguments for the function. In the case of the text and prompt calls we just specify a string - -this works too: The system will convert this into a single argument for us later in the message -path.

-
-

Note: The msg method sits on your Object- and Account typeclasses. It means you can easily -override msg and make custom- or per-object modifications to the flow of data as it passes -through.

-
-
-
-

ServerSession (outgoing)

-

Nothing is processed on the Session, it just serves as a gathering points for all different msg. -It immediately passes the data on to …

-
-
-

ServerSessionHandler (outgoing)

-

In the ServerSessionhandler, the keywords from the msg method are collated into one or more -outputcommands on a standardized form (identical to inputcommands):

-
    (commandname, (args), {kwargs})
-
-
-

This will intelligently convert different input to the same form. So msg("Hello") will end up as -an outputcommand ("text", ("Hello",), {}).

-

This is also the point where Inlinefuncs are parsed, depending on the -session to receive the data. Said data is pickled together with the Session id then sent over the -AMP bridge.

-
-
-

PortalSessionHandler (outgoing)

-

After the AMP connection has unpickled the data and paired the session id to the matching -PortalSession, the handler next determines if this Session has a suitable method for handling the -outputcommand.

-

The situation is analogous to how inputfuncs work, except that protocols are fixed things that don’t -need a plugin infrastructure like the inputfuncs are handled. So instead of an “outputfunc”, the -handler looks for methods on the PortalSession with names of the form send_<commandname>.

-

For example, the common sending of text expects a PortalSession method send_text. This will be -called as send_text(*("Hello",), **{}). If the “prompt” outputfunction was used, send_prompt is -called. In all other cases the send_default(cmdname, *args, **kwargs) will be called - this is the -case for all client-custom outputcommands, like when wanting to tell the client to update a graphic -or play a sound.

-
-
-

PortalSession (outgoing)

-

At this point it is up to the session to convert the command into a form understood by this -particular protocol. For telnet, send_text will just send the argument as a string (since that is -what telnet clients expect when “text” is coming). If send_default was called (basically -everything that is not traditional text or a prompt), it will pack the data as an GMCP or MSDP -command packet if the telnet client supports either (otherwise it won’t send at all). If sending to -the webclient, the data will get packed into a JSON structure at all times.

-
-
-

Client (outgoing)

-

Once arrived at the client, the outputcommand is handled in the way supported by the client (or it -may be quietly ignored if not). “text” commands will be displayed in the main window while others -may trigger changes in the GUI or play a sound etc.

-
-
-

Full example of Outgoing Message

-

For a full outgoing message, you need to have the outgoing function defined in the javascript. See -https://evennia.readthedocs.io/en/latest/Web-Client-Webclient.html for getting set up with custom -webclient code. Once you have a custom plugin defined and loaded, create a new function in the -plugin, onCustomFunc() for example:

-
    var onCustomFunc = function(args, kwargs) {
-        var = args.var
-        console.log(var)
-    }
-
-
-

You’ll also need to add the function to what the main plugin function returns:

-
    return {
-        init: init,
-        onCustomFunc,
-    }
-
-
-

This defines the function and looks for “var” as a variable that is passed to it. Once you have this -in place in your custom plugin, you also need to update the static/webclient/js/webclient_gui.js -file to recognize the new function when it’s called. First you should add a new function inside the -plugin_handler function to recognize the new function:

-
    var onCustomFunc = function (cmdname, args, kwargs) {
-        for( let n=0; n < ordered_plugins.length; n++ ) {
-            let plugin = ordered_plugins[n];
-            if( 'onCustomFunc' in plugin ) {
-                if( plugin.onCustomFunc(args, kwargs) ) {
-                    // True -- means this plugin claims this command exclusively.
-                    return;
-                }
-            }
-        }
-    }
-
-
-

This looks through all the plugins for a function that corresponds to the custom function being -called. Next, add the custom function to the return statement of the plugin handler:

-
    return {
-        add: add,
-        onKeydown: onKeydown,
-        onBeforeUnload: onBeforeUnload,
-        onLoggedIn: onLoggedIn,
-        onText: onText,
-        onGotOptions: onGotOptions,
-        onPrompt: onPrompt,
-        onDefault: onDefault,
-        onSilence: onSilence,
-        onConnectionClose: onConnectionClose,
-        onSend: onSend,
-        init: init,
-        postInit: postInit,
-        onCustomFunc: onCustomFunc,
-    }
-
-
-

Lastly, you will also need to need to add an entry to the Evennia emitter to tie the python function -call to this new javascript function (this is in the $(document).ready function):

-
    Evennia.emitter.on("customFunc", plugin_handler.onCustomFunc);
-
-
-

Now you can make a call from your python code to the new custom function to pass information from -the server to the client:

-
    character.msg(customFunc=({"var": "blarg"}))
-
-
-

When this code in your python is run, you should be able to see the “blarg” string printed in the -web client console. You should now be able to update the function call and definition to pass any -information needed between server and client.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/MonitorHandler.html b/docs/0.9.5/MonitorHandler.html deleted file mode 100644 index cd7baf4eb9..0000000000 --- a/docs/0.9.5/MonitorHandler.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - MonitorHandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

MonitorHandler

-

The MonitorHandler is a system for watching changes in properties or Attributes on objects. A -monitor can be thought of as a sort of trigger that responds to change.

-

The main use for the MonitorHandler is to report changes to the client; for example the client -Session may ask Evennia to monitor the value of the Characer’s health attribute and report -whenever it changes. This way the client could for example update its health bar graphic as needed.

-
-

Using the MonitorHandler

-

The MontorHandler is accessed from the singleton evennia.MONITOR_HANDLER. The code for the handler -is in evennia.scripts.monitorhandler.

-

Here’s how to add a new monitor:

-
from evennia import MONITOR_HANDLER
-
-MONITOR_HANDLER.add(obj, fieldname, callback,
-                    idstring="", persistent=False, **kwargs)
-
-
-
-
    -
  • obj (Typeclassed entity) - the object to monitor. Since this must be -typeclassed, it means you can’t monitor changes on Sessions with the monitorhandler, for -example.

  • -
  • fieldname (str) - the name of a field or Attribute on obj. If you want to -monitor a database field you must specify its full name, including the starting db_ (like -db_key, db_location etc). Any names not starting with db_ are instead assumed to be the names -of Attributes. This difference matters, since the MonitorHandler will automatically know to watch -the db_value field of the Attribute.

  • -
  • callback(callable) - This will be called as callback(fieldname=fieldname, obj=obj, **kwargs) -when the field updates.

  • -
  • idstring (str) - this is used to separate multiple monitors on the same object and fieldname. -This is required in order to properly identify and remove the monitor later. It’s also used for -saving it.

  • -
  • persistent (bool) - if True, the monitor will survive a server reboot.

  • -
-

Example:

-
from evennia import MONITOR_HANDLER as monitorhandler
-
-def _monitor_callback(fieldname="", obj=None, **kwargs):
-    # reporting callback that works both
-    # for db-fields and Attributes
-    if fieldname.startswith("db_"):
-        new_value = getattr(obj, fieldname)
-    else: # an attribute
-        new_value = obj.attributes.get(fieldname)
-
-    obj.msg("%s.%s changed to '%s'." % \
-                  (obj.key, fieldname, new_value))
-
-# (we could add _some_other_monitor_callback here too)
-
-# monitor Attribute (assume we have obj from before)
-monitorhandler.add(obj, "desc", _monitor_callback)
-
-# monitor same db-field with two different callbacks (must separate by id_string)
-monitorhandler.add(obj, "db_key", _monitor_callback, id_string="foo")
-monitorhandler.add(obj, "db_key", _some_other_monitor_callback, id_string="bar")
-
-
-
-

A monitor is uniquely identified by the combination of the object instance it is monitoring, the -name of the field/attribute to monitor on that object and its idstring (obj + fieldname + -idstring). The idstring will be the empty string unless given explicitly.

-

So to “un-monitor” the above you need to supply enough information for the system to uniquely find -the monitor to remove:

-
monitorhandler.remove(obj, "desc")
-monitorhandler.remove(obj, "db_key", idstring="foo")
-monitorhandler.remove(obj, "db_key", idstring="bar")
-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/NPC-shop-Tutorial.html b/docs/0.9.5/NPC-shop-Tutorial.html deleted file mode 100644 index 362d2d07e1..0000000000 --- a/docs/0.9.5/NPC-shop-Tutorial.html +++ /dev/null @@ -1,442 +0,0 @@ - - - - - - - - - NPC shop Tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

NPC shop Tutorial

-

This tutorial will describe how to make an NPC-run shop. We will make use of the EvMenu -system to present shoppers with a menu where they can buy things from the store’s stock.

-

Our shop extends over two rooms - a “front” room open to the shop’s customers and a locked “store -room” holding the wares the shop should be able to sell. We aim for the following features:

-
    -
  • The front room should have an Attribute storeroom that points to the store room.

  • -
  • Inside the front room, the customer should have a command buy or browse. This will open a -menu listing all items available to buy from the store room.

  • -
  • A customer should be able to look at individual items before buying.

  • -
  • We use “gold” as an example currency. To determine cost, the system will look for an Attribute -gold_value on the items in the store room. If not found, a fixed base value of 1 will be assumed. -The wealth of the customer should be set as an Attribute gold on the Character. If not set, they -have no gold and can’t buy anything.

  • -
  • When the customer makes a purchase, the system will check the gold_value of the goods and -compare it to the gold Attribute of the customer. If enough gold is available, this will be -deducted and the goods transferred from the store room to the inventory of the customer.

  • -
  • We will lock the store room so that only people with the right key can get in there.

  • -
-
-

The shop menu

-

We want to show a menu to the customer where they can list, examine and buy items in the store. This -menu should change depending on what is currently for sale. Evennia’s EvMenu utility will manage -the menu for us. It’s a good idea to read up on EvMenu if you are not familiar with it.

-
-

Designing the menu

-

The shopping menu’s design is straightforward. First we want the main screen. You get this when you -enter a shop and use the browse or buy command:

-
*** Welcome to ye Old Sword shop! ***
-   Things for sale (choose 1-3 to inspect, quit to exit):
-_________________________________________________________
-1. A rusty sword (5 gold)
-2. A sword with a leather handle (10 gold)
-3. Excalibur (100 gold)
-
-
-

There are only three items to buy in this example but the menu should expand to however many items -are needed. When you make a selection you will get a new screen showing the options for that -particular item:

-
You inspect A rusty sword:
-
-This is an old weapon maybe once used by soldiers in some
-long forgotten army. It is rusty and in bad condition.
-__________________________________________________________
-1. Buy A rusty sword (5 gold)
-2. Look for something else.
-
-
-

Finally, when you buy something, a brief message should pop up:

-
You pay 5 gold and purchase A rusty sword!
-
-
-

or

-
You cannot afford 5 gold for A rusty sword!
-
-
-

After this you should be back to the top level of the shopping menu again and can continue browsing.

-
-
-

Coding the menu

-

EvMenu defines the nodes (each menu screen with options) as normal Python functions. Each node -must be able to change on the fly depending on what items are currently for sale. EvMenu will -automatically make the quit command available to us so we won’t add that manually. For compactness -we will put everything needed for our shop in one module, mygame/typeclasses/npcshop.py.

-
# mygame/typeclasses/npcshop.py
-
-from evennia.utils import evmenu
-
-def menunode_shopfront(caller):
-    "This is the top-menu screen."
-
-    shopname = caller.location.key
-    wares = caller.location.db.storeroom.contents
-
-    # Wares includes all items inside the storeroom, including the
-    # door! Let's remove that from our for sale list.
-    wares = [ware for ware in wares if ware.key.lower() != "door"]
-
-    text = "*** Welcome to %s! ***\n" % shopname
-    if wares:
-        text += "   Things for sale (choose 1-%i to inspect);" \
-                " quit to exit:" % len(wares)
-    else:
-        text += "   There is nothing for sale; quit to exit."
-
-    options = []
-    for ware in wares:
-        # add an option for every ware in store
-        options.append({"desc": "%s (%s gold)" %
-                             (ware.key, ware.db.gold_value or 1),
-                        "goto": "menunode_inspect_and_buy"})
-    return text, options
-
-
-

In this code we assume the caller to be inside the shop when accessing the menu. This means we can -access the shop room via caller.location and get its key to display as the shop’s name. We also -assume the shop has an Attribute storeroom we can use to get to our stock. We loop over our goods -to build up the menu’s options.

-

Note that all options point to the same menu node called menunode_inspect_and_buy! We can’t know -which goods will be available to sale so we rely on this node to modify itself depending on the -circumstances. Let’s create it now.

-
# further down in mygame/typeclasses/npcshop.py
-
-def menunode_inspect_and_buy(caller, raw_string):
-    "Sets up the buy menu screen."
-
-    wares = caller.location.db.storeroom.contents
-    # Don't forget, we will need to remove that pesky door again!
-    wares = [ware for ware in wares if ware.key.lower() != "door"]
-    iware = int(raw_string) - 1
-    ware = wares[iware]
-    value = ware.db.gold_value or 1
-    wealth = caller.db.gold or 0
-    text = "You inspect %s:\n\n%s" % (ware.key, ware.db.desc)
-
-    def buy_ware_result(caller):
-        "This will be executed first when choosing to buy."
-        if wealth >= value:
-            rtext = "You pay %i gold and purchase %s!" % \
-                         (value, ware.key)
-            caller.db.gold -= value
-            ware.move_to(caller, quiet=True)
-        else:
-            rtext = "You cannot afford %i gold for %s!" % \
-                          (value, ware.key)
-        caller.msg(rtext)
-
-    options = ({"desc": "Buy %s for %s gold" % \
-                        (ware.key, ware.db.gold_value or 1),
-                "goto": "menunode_shopfront",
-                "exec": buy_ware_result},
-               {"desc": "Look for something else",
-                "goto": "menunode_shopfront"})
-
-    return text, options
-
-
-

In this menu node we make use of the raw_string argument to the node. This is the text the menu -user entered on the previous node to get here. Since we only allow numbered options in our menu, -raw_input must be an number for the player to get to this point. So we convert it to an integer -index (menu lists start from 1, whereas Python indices always starts at 0, so we need to subtract -1). We then use the index to get the corresponding item from storage.

-

We just show the customer the desc of the item. In a more elaborate setup you might want to show -things like weapon damage and special stats here as well.

-

When the user choose the “buy” option, EvMenu will execute the exec instruction before we go -back to the top node (the goto instruction). For this we make a little inline function -buy_ware_result. EvMenu will call the function given to exec like any menu node but it does not -need to return anything. In buy_ware_result we determine if the customer can afford the cost and -give proper return messages. This is also where we actually move the bought item into the inventory -of the customer.

-
-
-

The command to start the menu

-

We could in principle launch the shopping menu the moment a customer steps into our shop room, but -this would probably be considered pretty annoying. It’s better to create a Command for -customers to explicitly wanting to shop around.

-
# mygame/typeclasses/npcshop.py
-
-from evennia import Command
-
-class CmdBuy(Command):
-    """
-    Start to do some shopping
-
-    Usage:
-      buy
-      shop
-      browse
-
-    This will allow you to browse the wares of the
-    current shop and buy items you want.
-    """
-    key = "buy"
-    aliases = ("shop", "browse")
-
-    def func(self):
-        "Starts the shop EvMenu instance"
-        evmenu.EvMenu(self.caller,
-                      "typeclasses.npcshop",
-                      startnode="menunode_shopfront")
-
-
-

This will launch the menu. The EvMenu instance is initialized with the path to this very module - -since the only global functions available in this module are our menu nodes, this will work fine -(you could also have put those in a separate module). We now just need to put this command in a -CmdSet so we can add it correctly to the game:

-
from evennia import CmdSet
-
-class ShopCmdSet(CmdSet):
-    def at_cmdset_creation(self):
-        self.add(CmdBuy())
-
-
-
-
-
-

Building the shop

-

There are really only two things that separate our shop from any other Room:

-
    -
  • The shop has the storeroom Attribute set on it, pointing to a second (completely normal) room.

  • -
  • It has the ShopCmdSet stored on itself. This makes the buy command available to users entering -the shop.

  • -
-

For testing we could easily add these features manually to a room using @py or other admin -commands. Just to show how it can be done we’ll instead make a custom Typeclass for -the shop room and make a small command that builders can use to build both the shop and the -storeroom at once.

-
# bottom of mygame/typeclasses/npcshop.py
-
-from evennia import DefaultRoom, DefaultExit, DefaultObject
-from evennia.utils.create import create_object
-
-# class for our front shop room
-class NPCShop(DefaultRoom):
-    def at_object_creation(self):
-        # we could also use add(ShopCmdSet, permanent=True)
-        self.cmdset.add_default(ShopCmdSet)
-        self.db.storeroom = None
-
-# command to build a complete shop (the Command base class
-# should already have been imported earlier in this file)
-class CmdBuildShop(Command):
-    """
-    Build a new shop
-
-    Usage:
-        @buildshop shopname
-
-    This will create a new NPCshop room
-    as well as a linked store room (named
-    simply <storename>-storage) for the
-    wares on sale. The store room will be
-    accessed through a locked door in
-    the shop.
-    """
-    key = "@buildshop"
-    locks = "cmd:perm(Builders)"
-    help_category = "Builders"
-
-    def func(self):
-        "Create the shop rooms"
-        if not self.args:
-            self.msg("Usage: @buildshop <storename>")
-            return
-        # create the shop and storeroom
-        shopname = self.args.strip()
-        shop = create_object(NPCShop,
-                             key=shopname,
-                             location=None)
-        storeroom = create_object(DefaultRoom,
-                             key="%s-storage" % shopname,
-                             location=None)
-        shop.db.storeroom = storeroom
-        # create a door between the two
-        shop_exit = create_object(DefaultExit,
-                                  key="back door",
-                                  aliases=["storage", "store room"],
-                                  location=shop,
-                                  destination=storeroom)
-        storeroom_exit = create_object(DefaultExit,
-                                  key="door",
-                                  location=storeroom,
-                                  destination=shop)
-        # make a key for accessing the store room
-        storeroom_key_name = "%s-storekey" % shopname
-        storeroom_key = create_object(DefaultObject,
-                                       key=storeroom_key_name,
-                                       location=shop)
-        # only allow chars with this key to enter the store room
-        shop_exit.locks.add("traverse:holds(%s)" % storeroom_key_name)
-
-        # inform the builder about progress
-        self.caller.msg("The shop %s was created!" % shop)
-
-
-

Our typeclass is simple and so is our buildshop command. The command (which is for Builders only) -just takes the name of the shop and builds the front room and a store room to go with it (always -named "<shopname>-storage". It connects the rooms with a two-way exit. You need to add -CmdBuildShop [to the default cmdset](./Adding-Command-Tutorial.md#step-2-adding-the-command-to-a- -default-cmdset) before you can use it. Once having created the shop you can now @teleport to it or -@open a new exit to it. You could also easily expand the above command to automatically create -exits to and from the new shop from your current location.

-

To avoid customers walking in and stealing everything, we create a Lock on the storage -door. It’s a simple lock that requires the one entering to carry an object named -<shopname>-storekey. We even create such a key object and drop it in the shop for the new shop -keeper to pick up.

-
-

If players are given the right to name their own objects, this simple lock is not very secure and -you need to come up with a more robust lock-key solution.

-
-
-

We don’t add any descriptions to all these objects so looking “at” them will not be too thrilling. -You could add better default descriptions as part of the @buildshop command or leave descriptions -this up to the Builder.

-
-
-
-

The shop is open for business!

-

We now have a functioning shop and an easy way for Builders to create it. All you need now is to -@open a new exit from the rest of the game into the shop and put some sell-able items in the store -room. Our shop does have some shortcomings:

-
    -
  • For Characters to be able to buy stuff they need to also have the gold Attribute set on -themselves.

  • -
  • We manually remove the “door” exit from our items for sale. But what if there are other unsellable -items in the store room? What if the shop owner walks in there for example - anyone in the store -could then buy them for 1 gold.

  • -
  • What if someone else were to buy the item we’re looking at just before we decide to buy it? It -would then be gone and the counter be wrong - the shop would pass us the next item in the list.

  • -
-

Fixing these issues are left as an exercise.

-

If you want to keep the shop fully NPC-run you could add a Script to restock the shop’s -store room regularly. This shop example could also easily be owned by a human Player (run for them -by a hired NPC) - the shop owner would get the key to the store room and be responsible for keeping -it well stocked.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/New-Models.html b/docs/0.9.5/New-Models.html deleted file mode 100644 index d17dcb0937..0000000000 --- a/docs/0.9.5/New-Models.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - New Models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

New Models

-

Note: This is considered an advanced topic.

-

Evennia offers many convenient ways to store object data, such as via Attributes or Scripts. This is -sufficient for most use cases. But if you aim to build a large stand-alone system, trying to squeeze -your storage requirements into those may be more complex than you bargain for. Examples may be to -store guild data for guild members to be able to change, tracking the flow of money across a game- -wide economic system or implement other custom game systems that requires the storage of custom data -in a quickly accessible way. Whereas Tags or Scripts can handle many situations, -sometimes things may be easier to handle by adding your own database model.

-
-

Overview of database tables

-

SQL-type databases (which is what Evennia supports) are basically highly optimized systems for -retrieving text stored in tables. A table may look like this

-
     id | db_key    | db_typeclass_path          | db_permissions  ...
-    ------------------------------------------------------------------
-     1  |  Griatch  | evennia.DefaultCharacter   | Developers       ...
-     2  |  Rock     | evennia.DefaultObject      | None            ...
-
-
-

Each line is considerably longer in your database. Each column is referred to as a “field” and every -row is a separate object. You can check this out for yourself. If you use the default sqlite3 -database, go to your game folder and run

-
 evennia dbshell
-
-
-

You will drop into the database shell. While there, try:

-
 sqlite> .help       # view help
-
- sqlite> .tables     # view all tables
-
- # show the table field names for objects_objectdb
- sqlite> .schema objects_objectdb
-
- # show the first row from the objects_objectdb table
- sqlite> select * from objects_objectdb limit 1;
-
- sqlite> .exit
-
-
-

Evennia uses Django, which abstracts away the database SQL -manipulation and allows you to search and manipulate your database entirely in Python. Each database -table is in Django represented by a class commonly called a model since it describes the look of -the table. In Evennia, Objects, Scripts, Channels etc are examples of Django models that we then -extend and build on.

-
-
-

Adding a new database table

-

Here is how you add your own database table/models:

-
    -
  1. In Django lingo, we will create a new “application” - a subsystem under the main Evennia program. -For this example we’ll call it “myapp”. Run the following (you need to have a working Evennia -running before you do this, so make sure you have run the steps in [Getting Started](Getting- -Started) first):

    -
     cd mygame/world
    - evennia startapp myapp
    -
    -
    -
  2. -
  3. A new folder myapp is created. “myapp” will also be the name (the “app label”) from now on. We -chose to put it in the world/ subfolder here, but you could put it in the root of your mygame if -that makes more sense.

  4. -
  5. The myapp folder contains a few empty default files. What we are -interested in for now is models.py. In models.py you define your model(s). Each model will be a -table in the database. See the next section and don’t continue until you have added the models you -want.

  6. -
  7. You now need to tell Evennia that the models of your app should be a part of your database -scheme. Add this line to your mygame/server/conf/settings.pyfile (make sure to use the path where -you put myapp and don’t forget the comma at the end of the tuple):

    -
    INSTALLED_APPS = INSTALLED_APPS + ("world.myapp", )
    -
    -
    -
  8. -
  9. From mygame/, run

    -
     evennia makemigrations myapp
    - evennia migrate
    -
    -
    -
  10. -
-

This will add your new database table to the database. If you have put your game under version -control (if not, you should), don’t forget to git add myapp/* to add all items -to version control.

-
-
-

Defining your models

-

A Django model is the Python representation of a database table. It can be handled like any other -Python class. It defines fields on itself, objects of a special type. These become the “columns” -of the database table. Finally, you create new instances of the model to add new rows to the -database.

-

We won’t describe all aspects of Django models here, for that we refer to the vast Django -documentation on the subject. Here is a -(very) brief example:

-
from django.db import models
-
-class MyDataStore(models.Model):
-    "A simple model for storing some data"
-    db_key = models.CharField(max_length=80, db_index=True)
-    db_category = models.CharField(max_length=80, null=True, blank=True)
-    db_text = models.TextField(null=True, blank=True)
-    # we need this one if we want to be
-    # able to store this in an Evennia Attribute!
-    db_date_created = models.DateTimeField('date created', editable=False,
-                                            auto_now_add=True, db_index=True)
-
-
-

We create four fields: two character fields of limited length and one text field which has no -maximum length. Finally we create a field containing the current time of us creating this object.

-
-

The db_date_created field, with exactly this name, is required if you want to be able to store -instances of your custom model in an Evennia Attribute. It will automatically be set -upon creation and can after that not be changed. Having this field will allow you to do e.g. -obj.db.myinstance = mydatastore. If you know you’ll never store your model instances in Attributes -the db_date_created field is optional.

-
-

You don’t have to start field names with db_, this is an Evennia convention. It’s nevertheless -recommended that you do use db_, partly for clarity and consistency with Evennia (if you ever want -to share your code) and partly for the case of you later deciding to use Evennia’s -SharedMemoryModel parent down the line.

-

The field keyword db_index creates a database index for this field, which allows quicker -lookups, so it’s recommended to put it on fields you know you’ll often use in queries. The -null=True and blank=True keywords means that these fields may be left empty or set to the empty -string without the database complaining. There are many other field types and keywords to define -them, see django docs for more info.

-

Similar to using django-admin you -are able to do evennia inspectdb to get an automated listing of model information for an existing -database. As is the case with any model generating tool you should only use this as a starting -point for your models.

-
-
-

Creating a new model instance

-

To create a new row in your table, you instantiate the model and then call its save() method:

-
     from evennia.myapp import MyDataStore
-
-     new_datastore = MyDataStore(db_key="LargeSword",
-                                 db_category="weapons",
-                                 db_text="This is a huge weapon!")
-     # this is required to actually create the row in the database!
-     new_datastore.save()
-
-
-
-

Note that the db_date_created field of the model is not specified. Its flag at_now_add=True -makes sure to set it to the current date when the object is created (it can also not be changed -further after creation).

-

When you update an existing object with some new field value, remember that you have to save the -object afterwards, otherwise the database will not update:

-
    my_datastore.db_key = "Larger Sword"
-    my_datastore.save()
-
-
-

Evennia’s normal models don’t need to explicitly save, since they are based on SharedMemoryModel -rather than the raw django model. This is covered in the next section.

-
-
-

Using the SharedMemoryModel parent

-

Evennia doesn’t base most of its models on the raw django.db.models but on the Evennia base model -evennia.utils.idmapper.models.SharedMemoryModel. There are two main reasons for this:

-
    -
  1. Ease of updating fields without having to explicitly call save()

  2. -
  3. On-object memory persistence and database caching

  4. -
-

The first (and least important) point means that as long as you named your fields db_*, Evennia -will automatically create field wrappers for them. This happens in the model’s -Metaclass so there is no speed -penalty for this. The name of the wrapper will be the same name as the field, minus the db_ -prefix. So the db_key field will have a wrapper property named key. You can then do:

-
    my_datastore.key = "Larger Sword"
-
-
-

and don’t have to explicitly call save() afterwards. The saving also happens in a more efficient -way under the hood, updating only the field rather than the entire model using django optimizations. -Note that if you were to manually add the property or method key to your model, this will be used -instead of the automatic wrapper and allows you to fully customize access as needed.

-

To explain the second and more important point, consider the following example using the default -Django model parent:

-
    shield = MyDataStore.objects.get(db_key="SmallShield")
-    shield.cracked = True # where cracked is not a database field
-
-
-

And then later:

-
    shield = MyDataStore.objects.get(db_key="SmallShield")
-    print(shield.cracked)  # error!
-
-
-

The outcome of that last print statement is undefined! It could maybe randomly work but most -likely you will get an AttributeError for not finding the cracked property. The reason is that -cracked doesn’t represent an actual field in the database. It was just added at run-time and thus -Django don’t care about it. When you retrieve your shield-match later there is no guarantee you -will get back the same Python instance of the model where you defined cracked, even if you -search for the same database object.

-

Evennia relies heavily on on-model handlers and other dynamically created properties. So rather than -using the vanilla Django models, Evennia uses SharedMemoryModel, which levies something called -idmapper. The idmapper caches model instances so that we will always get the same instance back -after the first lookup of a given object. Using idmapper, the above example would work fine and you -could retrieve your cracked property at any time - until you rebooted when all non-persistent data -goes.

-

Using the idmapper is both more intuitive and more efficient per object; it leads to a lot less -reading from disk. The drawback is that this system tends to be more memory hungry overall. So if -you know that you’ll never need to add new properties to running instances or know that you will -create new objects all the time yet rarely access them again (like for a log system), you are -probably better off making “plain” Django models rather than using SharedMemoryModel and its -idmapper.

-

To use the idmapper and the field-wrapper functionality you just have to have your model classes -inherit from evennia.utils.idmapper.models.SharedMemoryModel instead of from the default -django.db.models.Model:

-
from evennia.utils.idmapper.models import SharedMemoryModel
-
-class MyDataStore(SharedMemoryModel):
-    # the rest is the same as before, but db_* is important; these will
-    # later be settable as .key, .category, .text ...
-    db_key = models.CharField(max_length=80, db_index=True)
-    db_category = models.CharField(max_length=80, null=True, blank=True)
-    db_text = models.TextField(null=True, blank=True)
-    db_date_created = models.DateTimeField('date created', editable=False,
-                                            auto_now_add=True, db_index=True)
-
-
-
-
-

Searching for your models

-

To search your new custom database table you need to use its database manager to build a query. -Note that even if you use SharedMemoryModel as described in the previous section, you have to use -the actual field names in the query, not the wrapper name (so db_key and not just key).

-
     from world.myapp import MyDataStore
-
-     # get all datastore objects exactly matching a given key
-     matches = MyDataStore.objects.filter(db_key="Larger Sword")
-     # get all datastore objects with a key containing "sword"
-     # and having the category "weapons" (both ignoring upper/lower case)
-     matches2 = MyDataStore.objects.filter(db_key__icontains="sword",
-                                           db_category__iequals="weapons")
-     # show the matching data (e.g. inside a command)
-     for match in matches2:
-        self.caller.msg(match.db_text)
-
-
-

See the Django query documentation for a -lot more information about querying the database.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Nicks.html b/docs/0.9.5/Nicks.html deleted file mode 100644 index 9fe67e2f0c..0000000000 --- a/docs/0.9.5/Nicks.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - - - - - Nicks — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Nicks

-

Nicks, short for Nicknames is a system allowing an object (usually a Account) to -assign custom replacement names for other game entities.

-

Nicks are not to be confused with Aliases. Setting an Alias on a game entity actually changes an -inherent attribute on that entity, and everyone in the game will be able to use that alias to -address the entity thereafter. A Nick on the other hand, is used to map a different way you -alone can refer to that entity. Nicks are also commonly used to replace your input text which means -you can create your own aliases to default commands.

-

Default Evennia use Nicks in three flavours that determine when Evennia actually tries to do the -substitution.

-
    -
  • inputline - replacement is attempted whenever you write anything on the command line. This is the -default.

  • -
  • objects - replacement is only attempted when referring to an object

  • -
  • accounts - replacement is only attempted when referring an account

  • -
-

Here’s how to use it in the default command set (using the nick command):

-
 nick ls = look
-
-
-

This is a good one for unix/linux users who are accustomed to using the ls command in their daily -life. It is equivalent to nick/inputline ls = look.

-
 nick/object mycar2 = The red sports car
-
-
-

With this example, substitutions will only be done specifically for commands expecting an object -reference, such as

-
 look mycar2
-
-
-

becomes equivalent to “look The red sports car”.

-
 nick/accounts tom = Thomas Johnsson
-
-
-

This is useful for commands searching for accounts explicitly:

-
 @find *tom
-
-
-

One can use nicks to speed up input. Below we add ourselves a quicker way to build red buttons. In -the future just writing rb will be enough to execute that whole long string.

-
 nick rb = @create button:examples.red_button.RedButton
-
-
-

Nicks could also be used as the start for building a “recog” system suitable for an RP mud.

-
 nick/account Arnold = The mysterious hooded man
-
-
-

The nick replacer also supports unix-style templating:

-
 nick build $1 $2 = @create/drop $1;$2
-
-
-

This will catch space separated arguments and store them in the the tags $1 and $2, to be -inserted in the replacement string. This example allows you to do build box crate and have Evennia -see @create/drop box;crate. You may use any $ numbers between 1 and 99, but the markers must -match between the nick pattern and the replacement.

-
-

If you want to catch “the rest” of a command argument, make sure to put a $ tag with no spaces -to the right of it - it will then receive everything up until the end of the line.

-
-

You can also use shell-type wildcards:

-
    -
  • * - matches everything.

  • -
  • ? - matches a single character.

  • -
  • [seq] - matches everything in the sequence, e.g. [xyz] will match both x, y and z

  • -
  • [!seq] - matches everything not in the sequence. e.g. [!xyz] will match all but x,y z.

  • -
-
-

Coding with nicks

-

Nicks are stored as the Nick database model and are referred from the normal Evennia -object through the nicks property - this is known as the NickHandler. The NickHandler -offers effective error checking, searches and conversion.

-
    # A command/channel nick:
-      obj.nicks.add("greetjack", "tell Jack = Hello pal!")
-    
-    # An object nick:
-      obj.nicks.add("rose", "The red flower", nick_type="object")
-    
-    # An account nick:
-      obj.nicks.add("tom", "Tommy Hill", nick_type="account")
-    
-    # My own custom nick type (handled by my own game code somehow):
-      obj.nicks.add("hood", "The hooded man", nick_type="my_identsystem")
-    
-    # get back the translated nick:
-     full_name = obj.nicks.get("rose", nick_type="object")
-    
-    # delete a previous set nick
-      object.nicks.remove("rose", nick_type="object")
-
-
-

In a command definition you can reach the nick handler through self.caller.nicks. See the nick -command in evennia/commands/default/general.py for more examples.

-

As a last note, The Evennia channel alias systems are using nicks with the -nick_type="channel" in order to allow users to create their own custom aliases to channels.

-
-
-
-

Advanced note

-

Internally, nicks are Attributes saved with the db_attrype set to “nick” (normal -Attributes has this set to None).

-

The nick stores the replacement data in the Attribute.db_value field as a tuple with four fields -(regex_nick, template_string, raw_nick, raw_template). Here regex_nick is the converted regex -representation of the raw_nick and the template-string is a version of the raw_template -prepared for efficient replacement of any $- type markers. The raw_nick and raw_template are -basically the unchanged strings you enter to the nick command (with unparsed $ etc).

-

If you need to access the tuple for some reason, here’s how:

-
tuple = obj.nicks.get("nickname", return_tuple=True)
-# or, alternatively
-tuple = obj.nicks.get("nickname", return_obj=True).value
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/OOB.html b/docs/0.9.5/OOB.html deleted file mode 100644 index 140f47807c..0000000000 --- a/docs/0.9.5/OOB.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - - - - OOB — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

OOB

-

OOB, or Out-Of-Band, means sending data between Evennia and the user’s client without the user -prompting it or necessarily being aware that it’s being passed. Common uses would be to update -client health-bars, handle client button-presses or to display certain tagged text in a different -window pane.

-
-

Briefly on input/outputcommands

-

Inside Evennia, all server-client communication happens in the same way (so plain text is also an -‘OOB message’ as far as Evennia is concerned). The message follows the Message Path. -You should read up on that if you are unfamiliar with it. As the message travels along the path it -has a standardized internal form: a tuple with a string, a tuple and a dict:

-
("cmdname", (args), {kwargs})
-
-
-

This is often referred to as an inputcommand or outputcommand, depending on the direction it’s -traveling. The end point for an inputcommand, (the ‘Evennia-end’ of the message path) is a matching -Inputfunc. This function is called as cmdname(session, *args, **kwargs) where -session is the Session-source of the command. Inputfuncs can easily be added by the developer to -support/map client commands to actions inside Evennia (see the inputfunc page for more -details).

-

When a message is outgoing (at the ‘Client-end’ of the message path) the outputcommand is handled by -a matching Outputfunc. This is responsible for converting the internal Evennia representation to a -form suitable to send over the wire to the Client. Outputfuncs are hard-coded. Which is chosen and -how it processes the outgoing data depends on the nature of the client it’s connected to. The only -time one would want to add new outputfuncs is as part of developing support for a new Evennia -Protocol.

-
-
-

Sending and receiving an OOB message

-

Sending is simple. You just use the normal msg method of the object whose session you want to send -to. For example in a Command:

-
    caller.msg(cmdname=((args, ...), {key:value, ...}))
-
-
-

A special case is the text input/outputfunc. It’s so common that it’s the default of the msg -method. So these are equivalent:

-
    caller.msg("Hello")
-    caller.msg(text="Hello")
-
-
-

You don’t have to specify the full output/input definition. So for example, if your particular -command only needs kwargs, you can skip the (args) part. Like in the text case you can skip -writing the tuple if there is only one arg … and so on - the input is pretty flexible. If there -are no args at all you need to give the empty tuple msg(cmdname=(,) (giving None would mean a -single argument None).

-

Which commands you can send depends on the client. If the client does not support an explicit OOB -protocol (like many old/legacy MUD clients) Evennia can only send text to them and will quietly -drop any other types of outputfuncs.

-
-

Remember that a given message may go to multiple clients with different capabilities. So unless -you turn off telnet completely and only rely on the webclient, you should never rely on non-text -OOB messages always reaching all targets.

-
-

Inputfuncs lists the default inputfuncs available to handle incoming OOB messages. To -accept more you need to add more inputfuncs (see that page for more info).

-
-
-

Supported OOB protocols

-

Evennia supports clients using one of the following protocols:

-
-

Telnet

-

By default telnet (and telnet+SSL) supports only the plain text outputcommand. Evennia however -detects if the Client supports one of two MUD-specific OOB extensions to the standard telnet -protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the -client uses. If the client supports both, GMCP will be used.

-
-

Note that for Telnet, text has a special status as the “in-band” operation. So the text -outputcommand sends the text argument directly over the wire, without going through the OOB -translations described below.

-
-
-

Telnet + GMCP

-

GMCP, the Generic Mud Communication Protocol sends data on the -form cmdname + JSONdata. Here the cmdname is expected to be on the form “Package.Subpackage”. -There could also be additional Sub-sub packages etc. The names of these ‘packages’ and ‘subpackages’ -are not that well standardized beyond what individual MUDs or companies have chosen to go with over -the years. You can decide on your own package names, but here are what others are using:

- -

Evennia will translate underscores to . and capitalize to fit the specification. So the -outputcommand foo_bar will become a GMCP command-name Foo.Bar. A GMCP command “Foo.Bar” will be -come foo_bar. To send a GMCP command that turns into an Evennia inputcommand without an -underscore, use the Core package. So Core.Cmdname becomes just cmdname in Evennia and vice -versa.

-

On the wire, a GMCP instruction for ("cmdname", ("arg",), {}) will look like this:

-
IAC SB GMCP "cmdname" "arg" IAC SE
-
-
-

where all the capitalized words are telnet character constants specified in -evennia/server/portal/telnet_oob.py. These are parsed/added by the protocol and we don’t include -these in the listings below.

-
-
-
-
-

Input/Outputfunc | GMCP-Command

-

[cmd_name, [], {}] | Cmd.Name -[cmd_name, [arg], {}] | Cmd.Name arg -[cmd_na_me, [args],{}] | Cmd.Na.Me [args] -[cmd_name, [], {kwargs}] | Cmd.Name {kwargs} -[cmdname, [args, {kwargs}] | Core.Cmdname [[args],{kwargs}]

-

Since Evennia already supplies default inputfuncs that don’t match the names expected by the most -common GMCP implementations we have a few hard-coded mappings for those:

-
-
-

GMCP command name | Input/Outputfunc name

-

“Core.Hello” | “client_options” -“Core.Supports.Get” | “client_options” -“Core.Commands.Get” | “get_inputfuncs” -“Char.Value.Get” | “get_value” -“Char.Repeat.Update” | “repeat” -“Char.Monitor.Update” | “monitor”

-
-

Telnet + MSDP

-

MSDP, the Mud Server Data Protocol, is a competing standard -to GMCP. The MSDP protocol page specifies a range of “recommended” available MSDP command names. -Evennia does not support those - since MSDP doesn’t specify a special format for its command names -(like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual -name.

-

MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports -strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs -needed. When sending MSDP for ("cmdname", ("arg",), {}) the resulting MSDP instruction will look -like this:

-
IAC SB MSDP VAR cmdname VAL arg IAC SE
-
-
-

The various available MSDP constants like VAR (variable), VAL (value), ARRAYOPEN/ARRAYCLOSE -and TABLEOPEN/TABLECLOSE are specified in evennia/server/portal/telnet_oob.

-
-
-
-

Outputfunc/Inputfunc | MSDP instruction

-

[cmdname, [], {}] | VAR cmdname VAL -[cmdname, [arg], {}] | VAR cmdname VAL arg -[cmdname, [args],{}] | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg … ARRAYCLOSE -[cmdname, [], {kwargs}] | VAR cmdname VAL TABLEOPEN VAR key VAL val … TABLECLOSE -[cmdname, [args], {kwargs}] | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg … ARRAYCLOSE VAR cmdname -VAL TABLEOPEN VAR key VAL val … TABLECLOSE

-

Observe that VAR ... VAL always identifies cmdnames, so if there are multiple arrays/dicts tagged -with the same cmdname they will be appended to the args, kwargs of that inputfunc. Vice-versa, a -different VAR ... VAL (outside a table) will come out as a second, different command input.

-
-

SSH

-

SSH only supports the text input/outputcommand.

-
-
-

Web client

-

Our web client uses pure JSON structures for all its communication, including text. This maps -directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs. So the -same example ("cmdname", ("arg",), {}) will be sent/received as a valid JSON structure

-
["cmdname, ["arg"], {}]
-
-
-

Since JSON is native to Javascript, this becomes very easy for the webclient to handle.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Objects.html b/docs/0.9.5/Objects.html deleted file mode 100644 index 8920758b98..0000000000 --- a/docs/0.9.5/Objects.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - Objects — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Objects

-

All in-game objects in Evennia, be it characters, chairs, monsters, rooms or hand grenades are -represented by an Evennia Object. Objects form the core of Evennia and is probably what you’ll -spend most time working with. Objects are Typeclassed entities.

-
-

How to create your own object types

-

An Evennia Object is, per definition, a Python class that includes evennia.DefaultObject among its -parents. In mygame/typeclasses/objects.py there is already a class Object that inherits from -DefaultObject and that you can inherit from. You can put your new typeclass directly in that -module or you could organize your code in some other way. Here we assume we make a new module -mygame/typeclasses/flowers.py:

-
    # mygame/typeclasses/flowers.py
-
-    from typeclasses.objects import Object
-
-    class Rose(Object):
-        """
-        This creates a simple rose object
-        """
-        def at_object_creation(self):
-            "this is called only once, when object is first created"
-            # add a persistent attribute 'desc'
-            # to object (silly example).
-            self.db.desc = "This is a pretty rose with thorns."
-
-
-

You could save this in the mygame/typeclasses/objects.py (then you’d not need to import Object) -or you can put it in a new module. Let’s say we do the latter, making a module -typeclasses/flowers.py. Now you just need to point to the class Rose with the @create command -to make a new rose:

-
 @create/drop MyRose:flowers.Rose
-
-
-

What the @create command actually does is to use evennia.create_object. You can do the same -thing yourself in code:

-
    from evennia import create_object
-    new_rose = create_object("typeclasses.flowers.Rose", key="MyRose")
-
-
-

(The @create command will auto-append the most likely path to your typeclass, if you enter the -call manually you have to give the full path to the class. The create.create_object function is -powerful and should be used for all coded object creating (so this is what you use when defining -your own building commands). Check out the ev.create_* functions for how to build other entities -like Scripts).

-

This particular Rose class doesn’t really do much, all it does it make sure the attribute -desc(which is what the look command looks for) is pre-set, which is pretty pointless since you -will usually want to change this at build time (using the @desc command or using the -Spawner). The Object typeclass offers many more hooks that is available -to use though - see next section.

-
-
-

Properties and functions on Objects

-

Beyond the properties assigned to all typeclassed objects (see that page for a list -of those), the Object also has the following custom properties:

-
    -
  • aliases - a handler that allows you to add and remove aliases from this object. Use -aliases.add() to add a new alias and aliases.remove() to remove one.

  • -
  • location - a reference to the object currently containing this object.

  • -
  • home is a backup location. The main motivation is to have a safe place to move the object to if -its location is destroyed. All objects should usually have a home location for safety.

  • -
  • destination - this holds a reference to another object this object links to in some way. Its -main use is for Exits, it’s otherwise usually unset.

  • -
  • nicks - as opposed to aliases, a Nick holds a convenient nickname replacement for a -real name, word or sequence, only valid for this object. This mainly makes sense if the Object is -used as a game character - it can then store briefer shorts, example so as to quickly reference game -commands or other characters. Use nicks.add(alias, realname) to add a new one.

  • -
  • account - this holds a reference to a connected Account controlling this object (if -any). Note that this is set also if the controlling account is not currently online - to test if -an account is online, use the has_account property instead.

  • -
  • sessions - if account field is set and the account is online, this is a list of all active -sessions (server connections) to contact them through (it may be more than one if multiple -connections are allowed in settings).

  • -
  • has_account - a shorthand for checking if an online account is currently connected to this -object.

  • -
  • contents - this returns a list referencing all objects ‘inside’ this object (i,e. which has this -object set as their location).

  • -
  • exits - this returns all objects inside this object that are Exits, that is, has the -destination property set.

  • -
-

The last two properties are special:

-
    -
  • cmdset - this is a handler that stores all command sets defined on the -object (if any).

  • -
  • scripts - this is a handler that manages Scripts attached to the object (if any).

  • -
-

The Object also has a host of useful utility functions. See the function headers in -src/objects/objects.py for their arguments and more details.

-
    -
  • msg() - this function is used to send messages from the server to an account connected to this -object.

  • -
  • msg_contents() - calls msg on all objects inside this object.

  • -
  • search() - this is a convenient shorthand to search for a specific object, at a given location -or globally. It’s mainly useful when defining commands (in which case the object executing the -command is named caller and one can do caller.search() to find objects in the room to operate -on).

  • -
  • execute_cmd() - Lets the object execute the given string as if it was given on the command line.

  • -
  • move_to - perform a full move of this object to a new location. This is the main move method -and will call all relevant hooks, do all checks etc.

  • -
  • clear_exits() - will delete all Exits to and from this object.

  • -
  • clear_contents() - this will not delete anything, but rather move all contents (except Exits) to -their designated Home locations.

  • -
  • delete() - deletes this object, first calling clear_exits() and -clear_contents().

  • -
-

The Object Typeclass defines many more hook methods beyond at_object_creation. Evennia calls -these hooks at various points. When implementing your custom objects, you will inherit from the -base parent and overload these hooks with your own custom code. See evennia.objects.objects for an -updated list of all the available hooks or the API for DefaultObject -here.

-
-
-

Subclasses of Object

-

There are three special subclasses of Object in default Evennia - Characters, Rooms and -Exits. The reason they are separated is because these particular object types are fundamental, -something you will always need and in some cases requires some extra attention in order to be -recognized by the game engine (there is nothing stopping you from redefining them though). In -practice they are all pretty similar to the base Object.

-
-

Characters

-

Characters are objects controlled by Accounts. When a new Account -logs in to Evennia for the first time, a new Character object is created and -the Account object is assigned to the account attribute. A Character object -must have a Default Commandset set on itself at -creation, or the account will not be able to issue any commands! If you just -inherit your own class from evennia.DefaultCharacter and make sure to use -super() to call the parent methods you should be fine. In -mygame/typeclasses/characters.py is an empty Character class ready for you -to modify.

-
-
-

Rooms

-

Rooms are the root containers of all other objects. The only thing really separating a room from -any other object is that they have no location of their own and that default commands like @dig -creates objects of this class - so if you want to expand your rooms with more functionality, just -inherit from ev.DefaultRoom. In mygame/typeclasses/rooms.py is an empty Room class ready for -you to modify.

-
-
-

Exits

-

Exits are objects connecting other objects (usually Rooms) together. An object named North or -in might be an exit, as well as door, portal or jump out the window. An exit has two things -that separate them from other objects. Firstly, their destination property is set and points to a -valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits -define a special Transit Command on themselves when they are created. This command is -named the same as the exit object and will, when called, handle the practicalities of moving the -character to the Exits’s destination - this allows you to just enter the name of the exit on its -own to move around, just as you would expect.

-

The exit functionality is all defined on the Exit typeclass, so you could in principle completely -change how exits work in your game (it’s not recommended though, unless you really know what you are -doing). Exits are locked using an access_type called traverse and also make use of a few -hook methods for giving feedback if the traversal fails. See evennia.DefaultExit for more info. -In mygame/typeclasses/exits.py there is an empty Exit class for you to modify.

-

The process of traversing an exit is as follows:

-
    -
  1. The traversing obj sends a command that matches the Exit-command name on the Exit object. The -cmdhandler detects this and triggers the command defined on the Exit. Traversal always -involves the “source” (the current location) and the destination (this is stored on the Exit -object).

  2. -
  3. The Exit command checks the traverse lock on the Exit object

  4. -
  5. The Exit command triggers at_traverse(obj, destination) on the Exit object.

  6. -
  7. In at_traverse, object.move_to(destination) is triggered. This triggers the following hooks, -in order:

    -
      -
    1. obj.at_before_move(destination) - if this returns False, move is aborted.

    2. -
    3. origin.at_object_leave(obj, destination)

    4. -
    5. obj.announce_move_from(destination)

    6. -
    7. Move is performed by changing obj.location from source location to destination.

    8. -
    9. obj.announce_move_to(source)

    10. -
    11. destination.at_object_receive(obj, source)

    12. -
    13. obj.at_after_move(source)

    14. -
    -
  8. -
  9. On the Exit object, at_after_traverse(obj, source) is triggered.

  10. -
-

If the move fails for whatever reason, the Exit will look for an Attribute err_traverse on itself -and display this as an error message. If this is not found, the Exit will instead call -at_failed_traverse(obj) on itself.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Online-Setup.html b/docs/0.9.5/Online-Setup.html deleted file mode 100644 index 973436db81..0000000000 --- a/docs/0.9.5/Online-Setup.html +++ /dev/null @@ -1,602 +0,0 @@ - - - - - - - - - Online Setup — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Online Setup

-

Evennia development can be made without any Internet connection beyond fetching updates. At some -point however, you are likely to want to make your game visible online, either as part opening it to -the public or to allow other developers or beta testers access to it.

-
-

Connecting from the outside

-

Accessing your Evennia server from the outside is not hard on its own. Any issues are usually due to -the various security measures your computer, network or hosting service has. These will generally -(and correctly) block outside access to servers on your machine unless you tell them otherwise.

-

We will start by showing how to host your server on your own local computer. Even if you plan to -host your “real” game on a remote host later, setting it up locally is useful practice. We cover -remote hosting later in this document.

-

Out of the box, Evennia uses three ports for outward communication. If your computer has a firewall, -these should be open for in/out communication (and only these, other ports used by Evennia are -internal to your computer only).

-
    -
  • 4000, telnet, for traditional mud clients

  • -
  • 4001, HTTP for the website)

  • -
  • 4002, websocket, for the web client

  • -
-

Evennia will by default accept incoming connections on all interfaces (0.0.0.0) so in principle -anyone knowing the ports to use and has the IP address to your machine should be able to connect to -your game.

-
    -
  • Make sure Evennia is installed and that you have activated the virtualenv. Start the server with -evennia start --log. The --log (or -l) will make sure that the logs are echoed to the -terminal.

  • -
-
-

Note: If you need to close the log-view, use Ctrl-C. Use just evennia --log on its own to -start tailing the logs again.

-
-
    -
  • Make sure you can connect with your web browser to http://localhost:4001 or, alternatively, -http://127.0.0.1:4001 which is the same thing. You should get your Evennia web site and be able to -play the game in the web client. Also check so that you can connect with a mud client to host -localhost, port 4000 or host 127.0.0.1, port 4000.

  • -
  • Google for “my ip” or use any online service to figure out -what your “outward-facing” IP address is. For our purposes, let’s say your outward-facing IP is -203.0.113.0.

  • -
  • Next try your outward-facing IP by opening http://203.0.113.0:4001 in a browser. If this works, -that’s it! Also try telnet, with the server set to 203.0.113.0 and port 4000. However, most -likely it will not work. If so, read on.

  • -
  • If your computer has a firewall, it may be blocking the ports we need (it may also block telnet -overall). If so, you need to open the outward-facing ports to in/out communication. See the -manual/instructions for your firewall software on how to do this. To test you could also temporarily -turn off your firewall entirely to see if that was indeed the problem.

  • -
  • Another common problem for not being able to connect is that you are using a hardware router -(like a wifi router). The router sits ‘between’ your computer and the Internet. So the IP you find -with Google is the router’s IP, not that of your computer. To resolve this you need to configure -your router to forward data it gets on its ports to the IP and ports of your computer sitting in -your private network. How to do this depends on the make of your router; you usually configure it -using a normal web browser. In the router interface, look for “Port forwarding” or maybe “Virtual -server”. If that doesn’t work, try to temporarily wire your computer directly to the Internet outlet -(assuming your computer has the ports for it). You’ll need to check for your IP again. If that -works, you know the problem is the router.

  • -
-
-

Note: If you need to reconfigure a router, the router’s Internet-facing ports do not have to -have to have the same numbers as your computer’s (and Evennia’s) ports! For example, you might want -to connect Evennia’s outgoing port 4001 to an outgoing router port 80 - this is the port HTTP -requests use and web browsers automatically look for - if you do that you could go to -http://203.0.113.0 without having to add the port at the end. This would collide with any other -web services you are running through this router though.

-
-
-

Settings example

-

You can connect Evennia to the Internet without any changes to your settings. The default settings -are easy to use but are not necessarily the safest. You can customize your online presence in your -settings file. To have Evennia recognize changed port settings you have -to do a full evennia reboot to also restart the Portal and not just the Server component.

-

Below is an example of a simple set of settings, mostly using the defaults. Evennia will require -access to five computer ports, of which three (only) should be open to the outside world. Below we -continue to assume that our server address is 203.0.113.0.

-
# in mygame/server/conf/settings.py
-
-SERVERNAME = "MyGame"
-
-# open to the internet: 4000, 4001, 4002
-# closed to the internet (internal use): 4005, 4006
-TELNET_PORTS = [4000]
-WEBSOCKET_CLIENT_PORT = 4002
-WEBSERVER_PORTS = [(4001, 4005)]
-AMP_PORT = 4006
-
-# Optional - security measures limiting interface access
-# (don't set these before you know things work without them)
-TELNET_INTERFACES = ['203.0.113.0']
-WEBSOCKET_CLIENT_INTERFACE = '203.0.113.0'
-ALLOWED_HOSTS = [".mymudgame.com"]
-
-# uncomment if you want to lock the server down for maintenance.
-# LOCKDOWN_MODE = True
-
-
-
-

Read on for a description of the individual settings.

-
-
-

Telnet

-
# Required. Change to whichever outgoing Telnet port(s)
-# you are allowed to use on your host.
-TELNET_PORTS = [4000]
-# Optional for security. Restrict which telnet
-# interfaces we should accept. Should be set to your
-# outward-facing IP address(es). Default is ´0.0.0.0´
-# which accepts all interfaces.
-TELNET_INTERFACES = ['0.0.0.0']
-
-
-

The TELNET_* settings are the most important ones for getting a traditional base game going. Which -IP addresses you have available depends on your server hosting solution (see the next sections). -Some hosts will restrict which ports you are allowed you use so make sure to check.

-
-
-

Web server

-
# Required. This is a list of tuples
-# (outgoing_port, internal_port). Only the outgoing
-# port should be open to the world!
-# set outgoing port to 80 if you want to run Evennia
-# as the only web server on your machine (if available).
-WEBSERVER_PORTS = [(4001, 4005)]
-# Optional for security. Change this to the IP your
-# server can be reached at (normally the same
-# as TELNET_INTERFACES)
-WEBSERVER_INTERFACES = ['0.0.0.0']
-# Optional for security. Protects against
-# man-in-the-middle attacks. Change  it to your server's
-# IP address or URL when you run a production server.
-ALLOWED_HOSTS = ['*']
-
-
-

The web server is always configured with two ports at a time. The outgoing port (4001 by -default) is the port external connections can use. If you don’t want users to have to specify the -port when they connect, you should set this to 80 - this however only works if you are not running -any other web server on the machine. -The internal port (4005 by default) is used internally by Evennia to communicate between the -Server and the Portal. It should not be available to the outside world. You usually only need to -change the outgoing port unless the default internal port is clashing with some other program.

-
-
-

Web client

-
# Required. Change this to the main IP address of your server.
-WEBSOCKET_CLIENT_INTERFACE = '0.0.0.0'
-# Optional and needed only if using a proxy or similar. Change
-# to the IP or address where the client can reach
-# your server. The ws:// part is then required. If not given, the client
-# will use its host location.
-WEBSOCKET_CLIENT_URL = ""
-# Required. Change to a free port for the websocket client to reach
-# the server on. This will be automatically appended
-# to WEBSOCKET_CLIENT_URL by the web client.
-WEBSOCKET_CLIENT_PORT = 4002
-
-
-

The websocket-based web client needs to be able to call back to the server, and these settings must -be changed for it to find where to look. If it cannot find the server you will get an warning in -your browser’s Console (in the dev tools of the browser), and the client will revert to the AJAX- -based of the client instead, which tends to be slower.

-
-
-

Other ports

-
# Optional public facing. Only allows SSL connections (off by default).
-SSL_PORTS = [4003]
-SSL_INTERFACES = ['0.0.0.0']
-# Optional public facing. Only if you allow SSH connections (off by default).
-SSH_PORTS = [4004]
-SSH_INTERFACES = ['0.0.0.0']
-# Required private. You should only change this if there is a clash
-# with other services on your host. Should NOT be open to the
-# outside world.
-AMP_PORT = 4006
-
-
-

The AMP_PORT is required to work, since this is the internal port linking Evennia’s -Server and Portal components together. The other ports are encrypted ports that may be -useful for custom protocols but are otherwise not used.

-
-
-

Lockdown mode

-

When you test things out and check configurations you may not want players to drop in on you. -Similarly, if you are doing maintenance on a live game you may want to take it offline for a while -to fix eventual problems without risking people connecting. To do this, stop the server with -evennia stop and add LOCKDOWN_MODE = True to your settings file. When you start the server -again, your game will only be accessible from localhost.

-
-
-

Registering with the Evennia game directory

-

Once your game is online you should make sure to register it with the Evennia Game -Index. Registering with the index will help people find your server, -drum up interest for your game and also shows people that Evennia is being used. You can do this -even if you are just starting development - if you don’t give any telnet/web address it will appear -as Not yet public and just be a teaser. If so, pick pre-alpha as the development status.

-

To register, stand in your game dir, run

-
evennia connections
-
-
-

and follow the instructions. See the Game index page for more details.

-
-
-
-

SSL

-

SSL can be very useful for web clients. It will protect the credentials and gameplay of your users -over a web client if they are in a public place, and your websocket can also be switched to WSS for -the same benefit. SSL certificates used to cost money on a yearly basis, but there is now a program -that issues them for free with assisted setup to make the entire process less painful.

-

Options that may be useful in combination with an SSL proxy:

-
# See above for the section on Lockdown Mode.
-# Useful for a proxy on the public interface connecting to Evennia on localhost.
-LOCKDOWN_MODE = True
-
-# Have clients communicate via wss after connecting with https to port 4001.
-# Without this, you may get DOMException errors when the browser tries
-# to create an insecure websocket from a secure webpage.
-WEBSOCKET_CLIENT_URL = "wss://fqdn:4002"
-
-
-
-

Let’s Encrypt

-

Let’s Encrypt is a certificate authority offering free certificates to -secure a website with HTTPS. To get started issuing a certificate for your web server using Let’s -Encrypt, see these links:

- -

Also, on Freenode visit the #letsencrypt channel for assistance from the community. For an -additional resource, Let’s Encrypt has a very active community -forum.

-

[A blog where someone sets up Let’s Encrypt](https://www.digitalocean.com/community/tutorials/how- -to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04)

-

The only process missing from all of the above documentation is how to pass verification. This is -how Let’s Encrypt verifies that you have control over your domain (not necessarily ownership, it’s -Domain Validation (DV)). This can be done either with configuring a certain path on your web server -or through a TXT record in your DNS. Which one you will want to do is a personal preference, but can -also be based on your hosting choice. In a controlled/cPanel environment, you will most likely have -to use DNS verification.

-
-
-
-

Relevant SSL Proxy Setup Information

- -
-
-

Hosting locally or remotely?

-
-

Using your own computer as a server

-

What we showed above is by far the simplest and probably cheapest option: Run Evennia on your own -home computer. Moreover, since Evennia is its own web server, you don’t need to install anything -extra to have a website.

-

Advantages

-
    -
  • Free (except for internet costs and the electrical bill).

  • -
  • Full control over the server and hardware (it sits right there!).

  • -
  • Easy to set up.

  • -
  • Suitable for quick setups - e.g. to briefly show off results to your collaborators.

  • -
-

Disadvantages

-
    -
  • You need a good internet connection, ideally without any upload/download limits/costs.

  • -
  • If you want to run a full game this way, your computer needs to always be on. It could be noisy, -and as mentioned, the electrical bill must be considered.

  • -
  • No support or safety - if your house burns down, so will your game. Also, you are yourself -responsible for doing regular backups.

  • -
  • Potentially not as easy if you don’t know how to open ports in your firewall or router.

  • -
  • Home IP numbers are often dynamically allocated, so for permanent online time you need to set up a -DNS to always re-point to the right place (see below).

  • -
  • You are personally responsible for any use/misuse of your internet connection– though unlikely -(but not impossible) if running your server somehow causes issues for other customers on the -network, goes against your ISP’s terms of service (many ISPs insist on upselling you to a business- -tier connection) or you are the subject of legal action by a copyright holder, you may find your -main internet connection terminated as a consequence.

  • -
-
-

Setting up your own machine as a server

-

The first section of this page describes how to do this -and allow users to connect to the IP address of your machine/router.

-

A complication with using a specific IP address like this is that your home IP might not remain the -same. Many ISPs (Internet Service Providers) allocates a dynamic IP to you which could change at -any time. When that happens, that IP you told people to go to will be worthless. Also, that long -string of numbers is not very pretty, is it? It’s hard to remember and not easy to use in marketing -your game. What you need is to alias it to a more sensible domain name - an alias that follows you -around also when the IP changes.

-
    -
  1. To set up a domain name alias, we recommend starting with a free domain name from -FreeDNS. Once you register there (it’s free) you have access to tens -of thousands domain names that people have “donated” to allow you to use for your own sub domain. -For example, strangled.net is one of those available domains. So tying our IP address to -strangled.net using the subdomain evennia would mean that one could henceforth direct people to -http://evennia.strangled.net:4001 for their gaming needs - far easier to remember!

  2. -
  3. So how do we make this new, nice domain name follow us also if our IP changes? For this we need -to set up a little program on our computer. It will check whenever our ISP decides to change our IP -and tell FreeDNS that. There are many alternatives to be found from FreeDNS:s homepage, one that -works on multiple platforms is inadyn. Get it from their page or, -in Linux, through something like apt-get install inadyn.

  4. -
  5. Next, you login to your account on FreeDNS and go to the -Dynamic page. You should have a list of your subdomains. Click -the Direct URL link and you’ll get a page with a text message. Ignore that and look at the URL of -the page. It should be ending in a lot of random letters. Everything after the question mark is your -unique “hash”. Copy this string.

  6. -
  7. You now start inadyn with the following command (Linux):

    -

    inadyn --dyndns_system default@freedns.afraid.org -a <my.domain>,<hash> &

    -
  8. -
-

where <my.domain> would be evennia.strangled.net and <hash> the string of numbers we copied -from FreeDNS. The & means we run in the background (might not be valid in other operating -systems). inadyn will henceforth check for changes every 60 seconds. You should put the inadyn -command string in a startup script somewhere so it kicks into gear whenever your computer starts.

-
-
-
-

Remote hosting

-

Your normal “web hotel” will probably not be enough to run Evennia. A web hotel is normally aimed at -a very specific usage - delivering web pages, at the most with some dynamic content. The “Python -scripts” they refer to on their home pages are usually only intended to be CGI-like scripts launched -by their webserver. Even if they allow you shell access (so you can install the Evennia dependencies -in the first place), resource usage will likely be very restricted. Running a full-fledged game -server like Evennia will probably be shunned upon or be outright impossible. If you are unsure, -contact your web hotel and ask about their policy on you running third-party servers that will want -to open custom ports.

-

The options you probably need to look for are shell account services, VPS:es or Cloud -services. A “Shell account” service means that you get a shell account on a server and can log in -like any normal user. By contrast, a VPS (Virtual Private Server) service usually means that you -get root access, but in a virtual machine. There are also Cloud-type services which allows for -starting up multiple virtual machines and pay for what resources you use.

-

Advantages

-
    -
  • Shell accounts/VPS/clouds offer more flexibility than your average web hotel - it’s the ability to -log onto a shared computer away from home.

  • -
  • Usually runs a Linux flavor, making it easy to install Evennia.

  • -
  • Support. You don’t need to maintain the server hardware. If your house burns down, at least your -game stays online. Many services guarantee a certain level of up-time and also do regular backups -for you. Make sure to check, some offer lower rates in exchange for you yourself being fully -responsible for your data/backups.

  • -
  • Usually offers a fixed domain name, so no need to mess with IP addresses.

  • -
  • May have the ability to easily deploy docker versions of evennia -and/or your game.

  • -
-

Disadvantages

-
    -
  • Might be pretty expensive (more so than a web hotel). Note that Evennia will normally need at -least 100MB RAM and likely much more for a large production game.

  • -
  • Linux flavors might feel unfamiliar to users not used to ssh/PuTTy and the Linux command line.

  • -
  • You are probably sharing the server with many others, so you are not completely in charge. CPU -usage might be limited. Also, if the server people decides to take the server down for maintenance, -you have no choice but to sit it out (but you’ll hopefully be warned ahead of time).

  • -
-
-

Installing Evennia on a remote server

-

Firstly, if you are familiar with server infrastructure, consider using [Docker](Running-Evennia-in- -Docker) to deploy your game to the remote server; it will likely ease installation and deployment. -Docker images may be a little confusing if you are completely new to them though.

-

If not using docker, and assuming you know how to connect to your account over ssh/PuTTy, you should -be able to follow the Setup Quickstart instructions normally. You only need Python -and GIT pre-installed; these should both be available on any servers (if not you should be able to -easily ask for them to be installed). On a VPS or Cloud service you can install them yourself as -needed.

-

If virtualenv is not available and you can’t get it, you can download it (it’s just a single file) -from the virtualenv pypi. Using virtualenv you can -install everything without actually needing to have further root access. Ports might be an issue, -so make sure you know which ports are available to use and reconfigure Evennia accordingly.

-
-
-
-

Hosting options

-

To find commercial solutions, browse the web for “shell access”, “VPS” or “Cloud services” in your -region. You may find useful offers for “low cost” VPS hosting on Low End Box. The associated -Low End Talk forum can be useful for health checking the many small businesses that offer -“value” hosting, and occasionally for technical suggestions.

-

There are all sorts of services available. Below are some international suggestions offered by -Evennia users:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hosting name

Type

Lowest price

Comments

silvren.com

Shell account

Free for MU*

Private hobby provider so don’t assume backups or expect immediate support. To ask for an account,connect with a MUD client to rostdev.mushpark.com, port 4201 and ask for “Jarin”.

Digital Ocean

VPS

$5/month

You can get a $50 credit if you use the referral link https://m.do.co/c/8f64fec2670c - if you do, once you’ve had it long enough to have paid $25 we will get that as a referral bonus to help Evennia development.

Amazon Web services

Cloud

~$5/month / on-demand

Free Tier first 12 months. Regions available around the globe.

Amazon Lightsail

Cloud

$5/month

Free first month. AWS’s new “fixed cost” offering.

Azure App Services

Cloud

Free

Provides a free tier for hobbyist. Limited regions to be deployed to under the free tier

Huawei Cloud

Cloud

on demand

Similar to Amazon. Free Tier first 12 months. Limited regions to be deployed to

Genesis MUD hosting

Shell account

$8/month

Dedicated MUD host with very limited memory offerings. As for 2017, runs a 13 years old Python version (2.4) so you’d need to either convince them to update or compile yourself. Note that Evennia needs at least the “Deluxe” package (50MB RAM) and probably a lot higher for a production game. This host is not recommended for Evennia.

Host1Plus

VPS & Cloud

$4/month

$4-$8/month depending on length of sign-up period.

Scaleway

Cloud

€3/month / on-demand

EU based (Paris, Amsterdam). Smallest option provides 2GB RAM.

Prgmr

VPS

$5/month

1 month free with a year prepay. You likely want some experience with servers with this option as they don’t have a lot of support.

Linode

Cloud

$5/month / on-demand

Multiple regions. Smallest option provides 1GB RAM

-

Please help us expand this list.

-
-
-
-

Cloud9

-

If you are interested in running Evennia in the online dev environment Cloud9, you -can spin it up through their normal online setup using the Evennia Linux install instructions. The -one extra thing you will have to do is update mygame/server/conf/settings.py and add -WEBSERVER_PORTS = [(8080, 4001)]. This will then let you access the web server and do everything -else as normal.

-

Note that, as of December 2017, Cloud9 was re-released by Amazon as a service within their AWS cloud -service offering. New customers entitled to the 1 year AWS “free tier” may find it provides -sufficient resources to operate a Cloud9 development environment without charge. -https://aws.amazon.com/cloud9/

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Parsing-command-arguments,-theory-and-best-practices.html b/docs/0.9.5/Parsing-command-arguments,-theory-and-best-practices.html deleted file mode 100644 index 1260a1bb54..0000000000 --- a/docs/0.9.5/Parsing-command-arguments,-theory-and-best-practices.html +++ /dev/null @@ -1,831 +0,0 @@ - - - - - - - - - Parsing command arguments, theory and best practices — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Parsing command arguments, theory and best practices

-

This tutorial will elaborate on the many ways one can parse command arguments. The first step after -adding a command usually is to parse its arguments. There are lots of -ways to do it, but some are indeed better than others and this tutorial will try to present them.

-

If you’re a Python beginner, this tutorial might help you a lot. If you’re already familiar with -Python syntax, this tutorial might still contain useful information. There are still a lot of -things I find in the standard library that come as a surprise, though they were there all along. -This might be true for others.

-

In this tutorial we will:

-
    -
  • Parse arguments with numbers.

  • -
  • Parse arguments with delimiters.

  • -
  • Take a look at optional arguments.

  • -
  • Parse argument containing object names.

  • -
-
-

What are command arguments?

-

I’m going to talk about command arguments and parsing a lot in this tutorial. So let’s be sure we -talk about the same thing before going any further:

-
-

A command is an Evennia object that handles specific user input.

-
-

For instance, the default look is a command. After having created your Evennia game, and -connected to it, you should be able to type look to see what’s around. In this context, look is -a command.

-
-

Command arguments are additional text passed after the command.

-
-

Following the same example, you can type look self to look at yourself. In this context, self -is the text specified after look. " self" is the argument to the look command.

-

Part of our task as a game developer is to connect user inputs (mostly commands) with actions in the -game. And most of the time, entering commands is not enough, we have to rely on arguments for -specifying actions with more accuracy.

-

Take the say command. If you couldn’t specify what to say as a command argument (say hello!), -you would have trouble communicating with others in the game. One would need to create a different -command for every kind of word or sentence, which is, of course, not practical.

-

Last thing: what is parsing?

-
-

In our case, parsing is the process by which we convert command arguments into something we can -work with.

-
-

We don’t usually use the command argument as is (which is just text, of type str in Python). We -need to extract useful information. We might want to ask the user for a number, or the name of -another character present in the same room. We’re going to see how to do all that now.

-
-
-

Working with strings

-

In object terms, when you write a command in Evennia (when you write the Python class), the -arguments are stored in the args attribute. Which is to say, inside your func method, you can -access the command arguments in self.args.

-
-

self.args

-

To begin with, look at this example:

-
class CmdTest(Command):
-
-    """
-    Test command.
-
-    Syntax:
-      test [argument]
-
-    Enter any argument after test.
-
-    """
-
-    key = "test"
-
-    def func(self):
-        self.msg(f"You have entered: {self.args}.")
-
-
-

If you add this command and test it, you will receive exactly what you have entered without any -parsing:

-
> test Whatever
-You have entered:  Whatever.
-> test
-You have entered: .
-
-
-
-

The lines starting with > indicate what you enter into your client. The other lines are what -you receive from the game server.

-
-

Notice two things here:

-
    -
  1. The left space between our command key (“test”, here) and our command argument is not removed. -That’s why there are two spaces in our output at line 2. Try entering something like “testok”.

  2. -
  3. Even if you don’t enter command arguments, the command will still be called with an empty string -in self.args.

  4. -
-

Perhaps a slight modification to our code would be appropriate to see what’s happening. We will -force Python to display the command arguments as a debug string using a little shortcut.

-
class CmdTest(Command):
-
-    """
-    Test command.
-
-    Syntax:
-      test [argument]
-
-    Enter any argument after test.
-
-    """
-
-    key = "test"
-
-    def func(self):
-        self.msg(f"You have entered: {self.args!r}.")
-
-
-

The only line we have changed is the last one, and we have added !r between our braces to tell -Python to print the debug version of the argument (the repr-ed version). Let’s see the result:

-
> test Whatever
-You have entered: ' Whatever'.
-> test
-You have entered: ''.
-> test And something with '?
-You have entered: " And something with '?".
-
-
-

This displays the string in a way you could see in the Python interpreter. It might be easier to -read… to debug, anyway.

-

I insist so much on that point because it’s crucial: the command argument is just a string (of type -str) and we will use this to parse it. What you will see is mostly not Evennia-specific, it’s -Python-specific and could be used in any other project where you have the same need.

-
-
-

Stripping

-

As you’ve seen, our command arguments are stored with the space. And the space between the command -and the arguments is often of no importance.

-
-

Why is it ever there?

-
-

Evennia will try its best to find a matching command. If the user enters your command key with -arguments (but omits the space), Evennia will still be able to find and call the command. You might -have seen what happened if the user entered testok. In this case, testok could very well be a -command (Evennia checks for that) but seeing none, and because there’s a test command, Evennia -calls it with the arguments "ok".

-

But most of the time, we don’t really care about this left space, so you will often see code to -remove it. There are different ways to do it in Python, but a command use case is the strip -method on str and its cousins, lstrip and rstrip.

-
    -
  • strip: removes one or more characters (either spaces or other characters) from both ends of the -string.

  • -
  • lstrip: same thing but only removes from the left end (left strip) of the string.

  • -
  • rstrip: same thing but only removes from the right end (right strip) of the string.

  • -
-

Some Python examples might help:

-
>>> '   this is '.strip() # remove spaces by default
-'this is'
->>> "   What if I'm right?   ".lstrip() # strip spaces from the left
-"What if I'm right?   "
->>> 'Looks good to me...'.strip('.') # removes '.'
-'Looks good to me'
->>> '"Now, what is it?"'.strip('"?') # removes '"' and '?' from both ends
-'Now, what is it'
-
-
-

Usually, since we don’t need the space separator, but still want our command to work if there’s no -separator, we call lstrip on the command arguments:

-
class CmdTest(Command):
-
-    """
-    Test command.
-
-    Syntax:
-      test [argument]
-
-    Enter any argument after test.
-
-    """
-
-    key = "test"
-
-    def parse(self):
-        """Parse arguments, just strip them."""
-        self.args = self.args.lstrip()
-
-    def func(self):
-        self.msg(f"You have entered: {self.args!r}.")
-
-
-
-

We are now beginning to override the command’s parse method, which is typically useful just for -argument parsing. This method is executed before func and so self.args in func() will contain -our self.args.lstrip().

-
-

Let’s try it:

-
> test Whatever
-You have entered: 'Whatever'.
-> test
-You have entered: ''.
-> test And something with '?
-You have entered: "And something with '?".
-> test     And something with lots of spaces
-You have entered: 'And something with lots of spaces'.
-
-
-

Spaces at the end of the string are kept, but all spaces at the beginning are removed:

-
-

strip, lstrip and rstrip without arguments will strip spaces, line breaks and other common -separators. You can specify one or more characters as a parameter. If you specify more than one -character, all of them will be stripped from your original string.

-
-
-
-

Convert arguments to numbers

-

As pointed out, self.args is a string (of type str). What if we want the user to enter a -number?

-

Let’s take a very simple example: creating a command, roll, that allows to roll a six-sided die. -The player has to guess the number, specifying the number as argument. To win, the player has to -match the number with the die. Let’s see an example:

-
> roll 3
-You roll a die.  It lands on the number 4.
-You played 3, you have lost.
-> dice 1
-You roll a die.  It lands on the number 2.
-You played 1, you have lost.
-> dice 1
-You roll a die.  It lands on the number 1.
-You played 1, you have won!
-
-
-

If that’s your first command, it’s a good opportunity to try to write it. A command with a simple -and finite role always is a good starting choice. Here’s how we could (first) write it… but it -won’t work as is, I warn you:

-
from random import randint
-
-from evennia import Command
-
-class CmdRoll(Command):
-
-    """
-    Play random, enter a number and try your luck.
-
-    Usage:
-      roll <number>
-
-    Enter a valid number as argument.  A random die will be rolled and you
-    will win if you have specified the correct number.
-
-    Example:
-      roll 3
-
-    """
-
-    key = "roll"
-
-    def parse(self):
-        """Convert the argument to a number."""
-        self.args = self.args.lstrip()
-
-    def func(self):
-        # Roll a random die
-        figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
-        self.msg(f"You roll a die.  It lands on the number {figure}.")
-
-        if self.args == figure: # THAT WILL BREAK!
-            self.msg(f"You played {self.args}, you have won!")
-        else:
-            self.msg(f"You played {self.args}, you have lost.")
-
-
-

If you try this code, Python will complain that you try to compare a number with a string: figure -is a number and self.args is a string and can’t be compared as-is in Python. Python doesn’t do -“implicit converting” as some languages do. By the way, this might be annoying sometimes, and other -times you will be glad it tries to encourage you to be explicit rather than implicit about what to -do. This is an ongoing debate between programmers. Let’s move on!

-

So we need to convert the command argument from a str into an int. There are a few ways to do -it. But the proper way is to try to convert and deal with the ValueError Python exception.

-

Converting a str into an int in Python is extremely simple: just use the int function, give it -the string and it returns an integer, if it could. If it can’t, it will raise ValueError. So -we’ll need to catch that. However, we also have to indicate to Evennia that, should the number be -invalid, no further parsing should be done. Here’s a new attempt at our command with this -converting:

-
from random import randint
-
-from evennia import Command, InterruptCommand
-
-class CmdRoll(Command):
-
-    """
-    Play random, enter a number and try your luck.
-
-    Usage:
-      roll <number>
-
-    Enter a valid number as argument.  A random die will be rolled and you
-    will win if you have specified the correct number.
-
-    Example:
-      roll 3
-
-    """
-
-    key = "roll"
-
-    def parse(self):
-        """Convert the argument to number if possible."""
-        args = self.args.lstrip()
-
-        # Convert to int if possible
-        # If not, raise InterruptCommand.  Evennia will catch this
-        # exception and not call the 'func' method.
-        try:
-            self.entered = int(args)
-        except ValueError:
-            self.msg(f"{args} is not a valid number.")
-            raise InterruptCommand
-
-    def func(self):
-        # Roll a random die
-        figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
-        self.msg(f"You roll a die.  It lands on the number {figure}.")
-
-        if self.entered == figure:
-            self.msg(f"You played {self.entered}, you have won!")
-        else:
-            self.msg(f"You played {self.entered}, you have lost.")
-
-
-

Before enjoying the result, let’s examine the parse method a little more: what it does is try to -convert the entered argument from a str to an int. This might fail (if a user enters roll something). In such a case, Python raises a ValueError exception. We catch it in our -try/except block, send a message to the user and raise the InterruptCommand exception in -response to tell Evennia to not run func(), since we have no valid number to give it.

-

In the func method, instead of using self.args, we use self.entered which we have defined in -our parse method. You can expect that, if func() is run, then self.entered contains a valid -number.

-

If you try this command, it will work as expected this time: the number is converted as it should -and compared to the die roll. You might spend some minutes playing this game. Time out!

-

Something else we could want to address: in our small example, we only want the user to enter a -positive number between 1 and 6. And the user can enter roll 0 or roll -8 or roll 208 for -that matter, the game still works. It might be worth addressing. Again, you could write a -condition to do that, but since we’re catching an exception, we might end up with something cleaner -by grouping:

-
from random import randint
-
-from evennia import Command, InterruptCommand
-
-class CmdRoll(Command):
-
-    """
-    Play random, enter a number and try your luck.
-
-    Usage:
-      roll <number>
-
-    Enter a valid number as argument.  A random die will be rolled and you
-    will win if you have specified the correct number.
-
-    Example:
-      roll 3
-
-    """
-
-    key = "roll"
-
-    def parse(self):
-        """Convert the argument to number if possible."""
-        args = self.args.lstrip()
-
-        # Convert to int if possible
-        try:
-            self.entered = int(args)
-            if not 1 <= self.entered <= 6:
-                # self.entered is not between 1 and 6 (including both)
-                raise ValueError
-        except ValueError:
-            self.msg(f"{args} is not a valid number.")
-            raise InterruptCommand
-
-    def func(self):
-        # Roll a random die
-        figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
-        self.msg(f"You roll a die.  It lands on the number {figure}.")
-
-        if self.entered == figure:
-            self.msg(f"You played {self.entered}, you have won!")
-        else:
-            self.msg(f"You played {self.entered}, you have lost.")
-
-
-

Using grouped exceptions like that makes our code easier to read, but if you feel more comfortable -checking, afterward, that the number the user entered is in the right range, you can do so in a -latter condition.

-
-

Notice that we have updated our parse method only in this last attempt, not our func() method -which remains the same. This is one goal of separating argument parsing from command processing, -these two actions are best kept isolated.

-
-
-
-

Working with several arguments

-

Often a command expects several arguments. So far, in our example with the “roll” command, we only -expect one argument: a number and just a number. What if we want the user to specify several -numbers? First the number of dice to roll, then the guess?

-
-

You won’t win often if you roll 5 dice but that’s for the example.

-
-

So we would like to interpret a command like this:

-
> roll 3 12
-
-
-

(To be understood: roll 3 dice, my guess is the total number will be 12.)

-

What we need is to cut our command argument, which is a str, break it at the space (we use the -space as a delimiter). Python provides the str.split method which we’ll use. Again, here are -some examples from the Python interpreter:

-
>>> args = "3 12"
->>> args.split(" ")
-['3', '12']
->>> args = "a command with several arguments"
->>> args.split(" ")
-['a', 'command', 'with', 'several', 'arguments']
->>>
-
-
-

As you can see, str.split will “convert” our strings into a list of strings. The specified -argument (" " in our case) is used as delimiter. So Python browses our original string. When it -sees a delimiter, it takes whatever is before this delimiter and append it to a list.

-

The point here is that str.split will be used to split our argument. But, as you can see from the -above output, we can never be sure of the length of the list at this point:

-
>>> args = "something"
->>> args.split(" ")
-['something']
->>> args = ""
->>> args.split(" ")
-['']
->>>
-
-
-

Again we could use a condition to check the number of split arguments, but Python offers a better -approach, making use of its exception mechanism. We’ll give a second argument to str.split, the -maximum number of splits to do. Let’s see an example, this feature might be confusing at first -glance:

-
>>> args = "that is something great"
->>> args.split(" ", 1) # one split, that is a list with two elements (before, after)
-
-
-

[‘that’, ‘is something great’]

-
-
-
-
-

Read this example as many times as needed to understand it. The second argument we give to -str.split is not the length of the list that should be returned, but the number of times we have -to split. Therefore, we specify 1 here, but we get a list of two elements (before the separator, -after the separator).

-
-

What will happen if Python can’t split the number of times we ask?

-
-

It won’t:

-
>>> args = "whatever"
->>> args.split(" ", 1) # there isn't even a space here...
-['whatever']
->>>
-
-
-

This is one moment I would have hoped for an exception and didn’t get one. But there’s another way -which will raise an exception if there is an error: variable unpacking.

-

We won’t talk about this feature in details here. It would be complicated. But the code is really -straightforward to use. Let’s take our example of the roll command but let’s add a first argument: -the number of dice to roll.

-
from random import randint
-
-from evennia import Command, InterruptCommand
-
-class CmdRoll(Command):
-
-    """
-    Play random, enter a number and try your luck.
-
-    Specify two numbers separated by a space.  The first number is the
-    number of dice to roll (1, 2, 3) and the second is the expected sum
-    of the roll.
-
-    Usage:
-      roll <dice> <number>
-
-    For instance, to roll two 6-figure dice, enter 2 as first argument.
-    If you think the sum of these two dice roll will be 10, you could enter:
-
-        roll 2 10
-
-    """
-
-    key = "roll"
-
-    def parse(self):
-        """Split the arguments and convert them."""
-        args = self.args.lstrip()
-
-        # Split: we expect two arguments separated by a space
-        try:
-            number, guess = args.split(" ", 1)
-        except ValueError:
-            self.msg("Invalid usage.  Enter two numbers separated by a space.")
-            raise InterruptCommand
-
-        # Convert the entered number (first argument)
-        try:
-            self.number = int(number)
-            if self.number <= 0:
-                raise ValueError
-        except ValueError:
-            self.msg(f"{number} is not a valid number of dice.")
-            raise InterruptCommand
-
-        # Convert the entered guess (second argument)
-        try:
-            self.guess = int(guess)
-            if not 1 <= self.guess <= self.number * 6:
-                raise ValueError
-        except ValueError:
-            self.msg(f"{self.guess} is not a valid guess.")
-            raise InterruptCommand
-
-    def func(self):
-        # Roll a random die X times (X being self.number)
-        figure = 0
-        for _ in range(self.number):
-            figure += randint(1, 6)
-
-        self.msg(f"You roll {self.number} dice and obtain the sum {figure}.")
-
-        if self.guess == figure:
-            self.msg(f"You played {self.guess}, you have won!")
-        else:
-            self.msg(f"You played {self.guess}, you have lost.")
-
-
-

The beginning of the parse() method is what interests us most:

-
try:
-    number, guess = args.split(" ", 1)
-except ValueError:
-    self.msg("Invalid usage.  Enter two numbers separated by a space.")
-    raise InterruptCommand
-
-
-

We split the argument using str.split but we capture the result in two variables. Python is smart -enough to know that we want what’s left of the space in the first variable, what’s right of the -space in the second variable. If there is not even a space in the string, Python will raise a -ValueError exception.

-

This code is much easier to read than browsing through the returned strings of str.split. We can -convert both variables the way we did previously. Actually there are not so many changes in this -version and the previous one, most of it is due to name changes for clarity.

-
-

Splitting a string with a maximum of splits is a common occurrence while parsing command -arguments. You can also see the str.rspli8t method that does the same thing but from the right of -the string. Therefore, it will attempt to find delimiters at the end of the string and work toward -the beginning of it.

-
-

We have used a space as a delimiter. This is absolutely not necessary. You might remember that -most default Evennia commands can take an = sign as a delimiter. Now you know how to parse them -as well:

-
>>> cmd_key = "tel"
->>> cmd_args = "book = chest"
->>> left, right = cmd_args.split("=") # mighht raise ValueError!
->>> left
-'book '
->>> right
-' chest'
->>>
-
-
-
-
-

Optional arguments

-

Sometimes, you’ll come across commands that have optional arguments. These arguments are not -necessary but they can be set if more information is needed. I will not provide the entire command -code here but just enough code to show the mechanism in Python:

-

Again, we’ll use str.split, knowing that we might not have any delimiter at all. For instance, -the player could enter the “tel” command like this:

-
> tel book
-> tell book = chest
-
-
-

The equal sign is optional along with whatever is specified after it. A possible solution in our -parse method would be:

-
    def parse(self):
-        args = self.args.lstrip()
-
-        # = is optional
-        try:
-            obj, destination = args.split("=", 1)
-        except ValueError:
-            obj = args
-            destination = None
-
-
-

This code would place everything the user entered in obj if she didn’t specify any equal sign. -Otherwise, what’s before the equal sign will go in obj, what’s after the equal sign will go in -destination. This makes for quick testing after that, more robust code with less conditions that -might too easily break your code if you’re not careful.

-
-

Again, here we specified a maximum numbers of splits. If the users enters:

-
-
> tel book = chest = chair
-
-
-

Then destination will contain: " chest = chair". This is often desired, but it’s up to you to -set parsing however you like.

-
-
-
-

Evennia searches

-

After this quick tour of some str methods, we’ll take a look at some Evennia-specific features -that you won’t find in standard Python.

-

One very common task is to convert a str into an Evennia object. Take the previous example: -having "book" in a variable is great, but we would prefer to know what the user is talking -about… what is this "book"?

-

To get an object from a string, we perform an Evennia search. Evennia provides a search method on -all typeclassed objects (you will most likely use the one on characters or accounts). This method -supports a very wide array of arguments and has its own tutorial. -Some examples of useful cases follow:

-
-

Local searches

-

When an account or a character enters a command, the account or character is found in the caller -attribute. Therefore, self.caller will contain an account or a character (or a session if that’s -a session command, though that’s not as frequent). The search method will be available on this -caller.

-

Let’s take the same example of our little “tel” command. The user can specify an object as -argument:

-
    def parse(self):
-        name = self.args.lstrip()
-
-
-

We then need to “convert” this string into an Evennia object. The Evennia object will be searched -in the caller’s location and its contents by default (that is to say, if the command has been -entered by a character, it will search the object in the character’s room and the character’s -inventory).

-
    def parse(self):
-        name = self.args.lstrip()
-
-        self.obj = self.caller.search(name)
-
-
-

We specify only one argument to the search method here: the string to search. If Evennia finds a -match, it will return it and we keep it in the obj attribute. If it can’t find anything, it will -return None so we need to check for that:

-
    def parse(self):
-        name = self.args.lstrip()
-
-        self.obj = self.caller.search(name)
-        if self.obj is None:
-            # A proper error message has already been sent to the caller
-            raise InterruptCommand
-
-
-

That’s it. After this condition, you know that whatever is in self.obj is a valid Evennia object -(another character, an object, an exit…).

-
-
-

Quiet searches

-

By default, Evennia will handle the case when more than one match is found in the search. The user -will be asked to narrow down and re-enter the command. You can, however, ask to be returned the -list of matches and handle this list yourself:

-
    def parse(self):
-        name = self.args.lstrip()
-
-        objs = self.caller.search(name, quiet=True)
-        if not objs:
-            # This is an empty list, so no match
-            self.msg(f"No {name!r} was found.")
-            raise InterruptCommand
-        
-        self.obj = objs[0] # Take the first match even if there are several
-
-
-

All we have changed to obtain a list is a keyword argument in the search method: quiet. If set -to True, then errors are ignored and a list is always returned, so we need to handle it as such. -Notice in this example, self.obj will contain a valid object too, but if several matches are -found, self.obj will contain the first one, even if more matches are available.

-
-
-

Global searches

-

By default, Evennia will perform a local search, that is, a search limited by the location in which -the caller is. If you want to perform a global search (search in the entire database), just set the -global_search keyword argument to True:

-
    def parse(self):
-        name = self.args.lstrip()
-        self.obj = self.caller.search(name, global_search=True)
-
-
-
-
-
-

Conclusion

-

Parsing command arguments is vital for most game designers. If you design “intelligent” commands, -users should be able to guess how to use them without reading the help, or with a very quick peek at -said help. Good commands are intuitive to users. Better commands do what they’re told to do. For -game designers working on MUDs, commands are the main entry point for users into your game. This is -no trivial. If commands execute correctly (if their argument is parsed, if they don’t behave in -unexpected ways and report back the right errors), you will have happier players that might stay -longer on your game. I hope this tutorial gave you some pointers on ways to improve your command -parsing. There are, of course, other ways you will discover, or ways you are already using in your -code.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Portal-And-Server.html b/docs/0.9.5/Portal-And-Server.html deleted file mode 100644 index 999b0379b6..0000000000 --- a/docs/0.9.5/Portal-And-Server.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - Portal And Server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Portal And Server

-

Evennia consists of two processes, known as Portal and Server. They can be controlled from -inside the game or from the command line as described here.

-

If you are new to the concept, the main purpose of separating the two is to have accounts connect to -the Portal but keep the MUD running on the Server. This way one can restart/reload the game (the -Server part) without Accounts getting disconnected.

-

portal and server layout

-

The Server and Portal are glued together via an AMP (Asynchronous Messaging Protocol) connection. -This allows the two programs to communicate seamlessly.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Profiling.html b/docs/0.9.5/Profiling.html deleted file mode 100644 index e958aab8d8..0000000000 --- a/docs/0.9.5/Profiling.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - - - - Profiling — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Profiling

-

This is considered an advanced topic mainly of interest to server developers.

-
-

Introduction

-

Sometimes it can be useful to try to determine just how efficient a particular piece of code is, or -to figure out if one could speed up things more than they are. There are many ways to test the -performance of Python and the running server.

-

Before digging into this section, remember Donald Knuth’s words of -wisdom:

-
-

[…]about 97% of the time: Premature optimization is the root of all evil.

-
-

That is, don’t start to try to optimize your code until you have actually identified a need to do -so. This means your code must actually be working before you start to consider optimization. -Optimization will also often make your code more complex and harder to read. Consider readability -and maintainability and you may find that a small gain in speed is just not worth it.

-
-
-

Simple timer tests

-

Python’s timeit module is very good for testing small things. For example, in order to test if it -is faster to use a for loop or a list comprehension you could use the following code:

-
    import timeit
-    # Time to do 1000000 for loops
-    timeit.timeit("for i in range(100):\n    a.append(i)", setup="a = []")
-   <<< 10.70982813835144
-    # Time to do 1000000 list comprehensions
-    timeit.timeit("a = [i for i in range(100)]")
-   <<<  5.358283996582031
-
-
-

The setup keyword is used to set up things that should not be included in the time measurement, -like a = [] in the first call.

-

By default the timeit function will re-run the given test 1000000 times and returns the total -time to do so (so not the average per test). A hint is to not use this default for testing -something that includes database writes - for that you may want to use a lower number of repeats -(say 100 or 1000) using the number=100 keyword.

-
-
-

Using cProfile

-

Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done -with pypy at this point). Due to the way Evennia’s processes are handled, there is no point in -using the normal way to start the profiler (python -m cProfile evennia.py). Instead you start the -profiler through the launcher:

-
evennia --profiler start
-
-
-

This will start Evennia with the Server component running (in daemon mode) under cProfile. You could -instead try --profile with the portal argument to profile the Portal (you would then need to -start the Server separately).

-

Please note that while the profiler is running, your process will use a lot more memory than usual. -Memory usage is even likely to climb over time. So don’t leave it running perpetually but monitor it -carefully (for example using the top command on Linux or the Task Manager’s memory display on -Windows).

-

Once you have run the server for a while, you need to stop it so the profiler can give its report. -Do not kill the program from your task manager or by sending it a kill signal - this will most -likely also mess with the profiler. Instead either use evennia.py stop or (which may be even -better), use @shutdown from inside the game.

-

Once the server has fully shut down (this may be a lot slower than usual) you will find that -profiler has created a new file mygame/server/logs/server.prof.

-
-
-

Analyzing the profile

-

The server.prof file is a binary file. There are many ways to analyze and display its contents, -all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works).

-

We recommend the -Runsnake visualizer to see the processor usage -of different processes in a graphical form. For more detailed listing of usage time, you can use -KCachegrind. To make KCachegrind work with -Python profiles you also need the wrapper script -pyprof2calltree. You can get pyprof2calltree via -pip whereas KCacheGrind is something you need to get via your package manager or their homepage.

-

How to analyze and interpret profiling data is not a trivial issue and depends on what you are -profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing -list if you need help and be ready to be able to supply your server.prof file for comparison, -along with the exact conditions under which it was obtained.

-
-
-

The Dummyrunner

-

It is difficult to test “actual” game performance without having players in your game. For this -reason Evennia comes with the Dummyrunner system. The Dummyrunner is a stress-testing system: a -separate program that logs into your game with simulated players (aka “bots” or “dummies”). Once -connected these dummies will semi-randomly perform various tasks from a list of possible actions. -Use Ctrl-C to stop the Dummyrunner.

-
-

Warning: You should not run the Dummyrunner on a production database. It will spawn many objects -and also needs to run with general permissions.

-
-

To launch the Dummyrunner, first start your server normally (with or without profiling, as above). -Then start a new terminal/console window and active your virtualenv there too. In the new terminal, -try to connect 10 dummy players:

-
evennia --dummyrunner 10
-
-
-

The first time you do this you will most likely get a warning from Dummyrunner. It will tell you to -copy an import string to the end of your settings file. Quit the Dummyrunner (Ctrl-C) and follow -the instructions. Restart Evennia and try evennia --dummyrunner 10 again. Make sure to remove that -extra settings line when running a public server.

-

The actions perform by the dummies is controlled by a settings file. The default Dummyrunner -settings file is evennia/server/server/profiling/dummyrunner_settings.py but you shouldn’t modify -this directly. Rather create/copy the default file to mygame/server/conf/ and modify it there. To -make sure to use your file over the default, add the following line to your settings file:

-
DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py"
-
-
-
-

Hint: Don’t start with too many dummies. The Dummyrunner defaults to taxing the server much more -intensely than an equal number of human players. A good dummy number to start with is 10-100.

-
-

Once you have the dummyrunner running, stop it with Ctrl-C.

-

Generally, the dummyrunner system makes for a decent test of general performance; but it is of -course hard to actually mimic human user behavior. For this, actual real-game testing is required.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Python-3.html b/docs/0.9.5/Python-3.html deleted file mode 100644 index 82b2d5cd3b..0000000000 --- a/docs/0.9.5/Python-3.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - Python 3 — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Python 3

-

Evennia supports Python 3+ since v0.8. This page is deprecated.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Python-basic-introduction.html b/docs/0.9.5/Python-basic-introduction.html deleted file mode 100644 index 6bb3e201ff..0000000000 --- a/docs/0.9.5/Python-basic-introduction.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - Python basic introduction — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Python basic introduction

-

This is the first part of our beginner’s guide to the basics of using Python with Evennia. It’s -aimed at you with limited or no programming/Python experience. But also if you are an experienced -programmer new to Evennia or Python you might still pick up a thing or two. It is by necessity brief -and low on detail. There are countless Python guides and tutorials, books and videos out there for -learning more in-depth - use them!

-

Contents:

- -

This quickstart assumes you have gotten Evennia started. You should make sure -that you are able to see the output from the server in the console from which you started it. Log -into the game either with a mud client on localhost:4000 or by pointing a web browser to -localhost:4001/webclient. Log in as your superuser (the user you created during install).

-

Below, lines starting with a single > means command input.

-
-

Evennia Hello world

-

The py (or ! which is an alias) command allows you as a superuser to run raw Python from in- -game. From the game’s input line, enter the following:

-
> py print("Hello World!")
-
-
-

You will see

-
> print("Hello world!")
-Hello World
-
-
-

To understand what is going on: some extra info: The print(...) function is the basic, in-built -way to output text in Python. The quotes "..." means you are inputing a string (i.e. text). You -could also have used single-quotes '...', Python accepts both.

-

The first return line (with >>>) is just py echoing what you input (we won’t include that in the -examples henceforth).

-
-

Note: You may sometimes see people/docs refer to @py or other commands starting with @. -Evennia ignores @ by default, so @py is the exact same thing as py.

-
-

The print command is a standard Python structure. We can use that here in the py command, and -it’s great for debugging and quick testing. But if you need to send a text to an actual player, -print won’t do, because it doesn’t know who to send to. Try this:

-
> py me.msg("Hello world!")
-Hello world!
-
-
-

This looks the same as the print result, but we are now actually messaging a specific object, -me. The me is something uniquely available in the py command (we could also use self, it’s -an alias). It represents “us”, the ones calling the py command. The me is an example of an -Object instance. Objects are fundamental in Python and Evennia. The me object not only -represents the character we play in the game, it also contains a lot of useful resources for doing -things with that Object. One such resource is msg. msg works like print except it sends the -text to the object it is attached to. So if we, for example, had an object you, doing -you.msg(...) would send a message to the object you.

-

You access an Object’s resources by using the full-stop character .. So self.msg accesses the -msg resource and then we call it like we did print, with our “Hello World!” greeting in -parentheses.

-
-

Important: something like print(...) we refer to as a function, while msg(...) which sits on -an object is called a method.

-
-

For now, print and me.msg behaves the same, just remember that you’re going to mostly be using -the latter in the future. Try printing other things. Also try to include |r at the start of your -string to make the output red in-game. Use color to learn more color tags.

-
-
-

Importing modules

-

Keep your game running, then open a text editor of your choice. If your game folder is called -mygame, create a new text file test.py in the subfolder mygame/world. This is how the file -structure should look:

-
mygame/
-    world/
-        test.py
-
-
-

For now, only add one line to test.py:

-
print("Hello World!")
-
-
-

Don’t forget to save the file. A file with the ending .py is referred to as a Python module. To -use this in-game we have to import it. Try this:

-
> @py import world.test
-Hello World
-
-
-

If you make some error (we’ll cover how to handle errors below) you may need to run the @reload -command for your changes to take effect.

-

So importing world.test actually means importing world/test.py. Think of the period . as -replacing / (or \ for Windows) in your path. The .py ending of test.py is also never -included in this “Python-path”, but only files with that ending can be imported this way. Where is -mygame in that Python-path? The answer is that Evennia has already told Python that your mygame -folder is a good place to look for imports. So we don’t include mygame in the path - Evennia -handles this for us.

-

When you import the module, the top “level” of it will execute. In this case, it will immediately -print “Hello World”.

-
-

If you look in the folder you’ll also often find new files ending with .pyc. These are compiled -Python binaries that Python auto-creates when running code. Just ignore them, you should never edit -those anyway.

-
-

Now try to run this a second time:

-
> py import world.test
-
-
-

You will not see any output this second time or any subsequent times! This is not a bug. Rather -it is because Python is being clever - it stores all imported modules and to be efficient it will -avoid importing them more than once. So your print will only run the first time, when the module -is first imported. To see it again you need to @reload first, so Python forgets about the module -and has to import it again.

-

We’ll get back to importing code in the second part of this tutorial. For now, let’s press on.

-
-
-

Parsing Python errors

-

Next, erase the single print statement you had in test.py and replace it with this instead:

-
me.msg("Hello World!")
-
-
-

As you recall we used this from py earlier - it echoed “Hello World!” in-game. -Save your file and reload your server - this makes sure Evennia sees the new version of your code. -Try to import it from py in the same way as earlier:

-
> py import world.test
-
-
-

No go - this time you get an error!

-
File "./world/test.py", line 1, in <module>
-    me.msg("Hello world!")
-NameError: name 'me' is not defined
-
-
-

This is called a traceback. Python’s errors are very friendly and will most of the time tell you -exactly what and where things are wrong. It’s important that you learn to parse tracebacks so you -can fix your code. Let’s look at this one. A traceback is to be read from the bottom up. The last -line is the error Python balked at, while the two lines above it details exactly where that error -was encountered.

-
    -
  1. An error of type NameError is the problem …

  2. -
  3. … more specifically it is due to the variable me not being defined.

  4. -
  5. This happened on the line me.msg("Hello world!")

  6. -
  7. … which is on line 1 of the file ./world/test.py.

  8. -
-

In our case the traceback is short. There may be many more lines above it, tracking just how -different modules called each other until it got to the faulty line. That can sometimes be useful -information, but reading from the bottom is always a good start.

-

The NameError we see here is due to a module being its own isolated thing. It knows nothing about -the environment into which it is imported. It knew what print is because that is a special -reserved Python keyword. But me is not such a -reserved word. As far as the module is concerned me is just there out of nowhere. Hence the -NameError.

-
-
-

Our first function

-

Let’s see if we can resolve that NameError from the previous section. We know that me is defined -at the time we use the @py command because if we do py me.msg("Hello World!") directly in-game -it works fine. What if we could send that me to the test.py module so it knows what it is? One -way to do this is with a function.

-

Change your mygame/world/test.py file to look like this:

-
def hello_world(who):
-    who.msg("Hello World!")
-
-
-

Now that we are moving onto multi-line Python code, there are some important things to remember:

-
    -
  • Capitalization matters in Python. It must be def and not DEF, who is not the same as Who -etc.

  • -
  • Indentation matters in Python. The second line must be indented or it’s not valid code. You should -also use a consistent indentation length. We strongly recommend that you set up your editor to -always indent 4 spaces (not a single tab-character) when you press the TAB key - it will make -your life a lot easier.

  • -
  • def is short for “define” and defines a function (or a method, if sitting on an object). -This is a reserved Python keyword; try not to use -these words anywhere else.

  • -
  • A function name can not have spaces but otherwise we could have called it almost anything. We call -it hello_world. Evennia follows [Python’s standard naming style](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#a-quick-list-of-code-style- -points) with lowercase letters and underscores. Use this style for now.

  • -
  • who is what we call the argument to our function. Arguments are variables we pass to the -function. We could have named it anything and we could also have multiple arguments separated by -commas. What who is depends on what we pass to this function when we call it later (hint: we’ll -pass me to it).

  • -
  • The colon (:) at the end of the first line indicates that the header of the function is -complete.

  • -
  • The indentation marks the beginning of the actual operating code of the function (the function’s -body). If we wanted more lines to belong to this function those lines would all have to have to -start at this indentation level.

  • -
  • In the function body we take the who argument and treat it as we would have treated me earlier

  • -
  • we expect it to have a .msg method we can use to send “Hello World” to.

  • -
-

First, reload your game to make it aware of the updated Python module. Now we have defined our -first function, let’s use it.

-
> reload
-> py import world.test
-
-
-

Nothing happened! That is because the function in our module won’t do anything just by importing it. -It will only act when we call it. We will need to enter the module we just imported and do so.

-
> py import world.test ; world.test.hello_world(me)
-Hello world!
-
-
-

There is our “Hello World”! The ; is the way to put multiple Python-statements on one line.

-
-

Some MUD clients use ; for their own purposes to separate client-inputs. If so you’ll get a -NameError stating that world is not defined. Check so you understand why this is! Change the use -of ; in your client or use the Evennia web client if this is a problem.

-
-

In the second statement we access the module path we imported (world.test) and reach for the -hello_world function within. We call the function with me, which becomes the who variable we -use inside the hello_function.

-
-

As an exercise, try to pass something else into hello_world. Try for example to pass who as -the number 5 or the simple string "foo". You’ll get errors that they don’t have the attribute -msg. As we’ve seen, me does make msg available which is why it works (you’ll learn more -about Objects like me in the next part of this tutorial). If you are familiar with other -programming languages you may be tempted to start validating who to make sure it works as -expected. This is usually not recommended in Python which suggests it’s better to -handle the error if it happens rather than to make -a lot of code to prevent it from happening. See also duck -typing.

-
-
-
-
-

Looking at the log

-

As you start to explore Evennia, it’s important that you know where to look when things go wrong. -While using the friendly py command you’ll see errors directly in-game. But if something goes -wrong in your code while the game runs, you must know where to find the log.

-

Open a terminal (or go back to the terminal you started Evennia in), make sure your virtualenv is -active and that you are standing in your game directory (the one created with evennia --init -during installation). Enter

-
evennia --log
-
-
-

(or evennia -l)

-

This will show the log. New entries will show up in real time. Whenever you want to leave the log, -enter Ctrl-C or Cmd-C depending on your system. As a game dev it is important to look at the -log output when working in Evennia - many errors will only appear with full details here. You may -sometimes have to scroll up in the history if you miss it.

-

This tutorial is continued in Part 2, where we’ll start learning -about objects and to explore the Evennia library.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Python-basic-tutorial-part-two.html b/docs/0.9.5/Python-basic-tutorial-part-two.html deleted file mode 100644 index 62fa35a4e1..0000000000 --- a/docs/0.9.5/Python-basic-tutorial-part-two.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - - Python basic tutorial part two — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Python basic tutorial part two

-

In the first part of this Python-for-Evennia basic tutorial we learned -how to run some simple Python code from inside the game. We also made our first new module -containing a function that we called. Now we’re going to start exploring the very important -subject of objects.

-

Contents:

- -
-

On the subject of objects

-

In the first part of the tutorial we did things like

-
> py me.msg("Hello World!")
-
-
-

To learn about functions and imports we also passed that me on to a function hello_world in -another module.

-

Let’s learn some more about this me thing we are passing around all over the place. In the -following we assume that we named our superuser Character “Christine”.

-
> py me
-Christine
-> py me.key
-Christine
-
-
-

These returns look the same at first glance, but not if we examine them more closely:

-
> py type(me)
-<class 'typeclasses.characters.Character'>
-> py type(me.key)
-<type str>
-
-
-
-

Note: In some MU clients, such as Mudlet and MUSHclient simply returning type(me), you may not -see the proper return from the above commands. This is likely due to the HTML-like tags <...>, -being swallowed by the client.

-
-

The type function is, like print, another in-built function in Python. It -tells us that we (me) are of the class typeclasses.characters.Character. -Meanwhile me.key is a property on us, a string. It holds the name of this -object.

-
-

When you do py me, the me is defined in such a way that it will use its .key property to -represent itself. That is why the result is the same as when doing py me.key. Also, remember that -as noted in the first part of the tutorial, the me is not a reserved Python word; it was just -defined by the Evennia developers as a convenient short-hand when creating the py command. So -don’t expect me to be available elsewhere.

-
-

A class is like a “factory” or blueprint. From a class you then create individual instances. So -if class isDog, an instance of Dog might be fido. Our in-game persona is of a class -Character. The superuser christine is an instance of the Character class (an instance is -also often referred to as an object). This is an important concept in object oriented -programming. You are wise to familiarize yourself with it a little.

-
-

In other terms:

-
    -
  • class: A description of a thing, all the methods (code) and data (information)

  • -
  • object: A thing, defined as an instance of a class.

  • -
-

So in “Fido is a Dog”, “Fido” is an object–a unique thing–and “Dog” is a class. Coders would -also say, “Fido is an instance of Dog”. There can be other dogs too, such as Butch and Fifi. They, -too, would be instances of Dog.

-

As another example: “Christine is a Character”, or “Christine is an instance of -typeclasses.characters.Character”. To start, all characters will be instances of -typeclass.characters.Character.

-

You’ll be writing your own class soon! The important thing to know here is how classes and objects -relate.

-
-

The string 'typeclasses.characters.Character' we got from the type() function is not arbitrary. -You’ll recognize this from when we imported world.test in part one. This is a path exactly -describing where to find the python code describing this class. Python treats source code files on -your hard drive (known as modules) as well as folders (known as packages) as objects that you -access with the . operator. It starts looking at a place that Evennia has set up for you - namely -the root of your own game directory.

-

Open and look at your game folder (named mygame if you exactly followed the Getting Started -instructions) in a file editor or in a new terminal/console. Locate the file -mygame/typeclasses/characters.py

-
mygame/
-    typeclasses
-        characters.py
-
-
-

This represents the first part of the python path - typeclasses.characters (the .py file ending -is never included in the python path). The last bit, .Character is the actual class name inside -the characters.py module. Open that file in a text editor and you will see something like this:

-
"""
-(Doc string for module)
-"""
-
-from evennia import DefaultCharacter
-
-class Character(DefaultCharacter):
-    """
-    (Doc string for class)
-    """
-    pass
-
-
-
-

There is Character, the last part of the path. Note how empty this file is. At first glance one -would think a Character had no functionality at all. But from what we have used already we know it -has at least the key property and the method msg! Where is the code? The answer is that this -‘emptiness’ is an illusion caused by something called inheritance. Read on.

-

Firstly, in the same way as the little hello.py we did in the first part of the tutorial, this is -an example of full, multi-line Python code. Those triple-quoted strings are used for strings that -have line breaks in them. When they appear on their own like this, at the top of a python module, -class or similar they are called doc strings. Doc strings are read by Python and is used for -producing online help about the function/method/class/module. By contrast, a line starting with # -is a comment. It is ignored completely by Python and is only useful to help guide a human to -understand the code.

-

The line

-
    class Character(DefaultCharacter):
-
-
-

means that the class Character is a child of the class DefaultCharacter. This is called -inheritance and is another fundamental concept. The answer to the question “where is the code?” is -that the code is inherited from its parent, DefaultCharacter. And that in turn may inherit code -from its parent(s) and so on. Since our child, Character is empty, its functionality is exactly -identical to that of its parent. The moment we add new things to Character, these will take -precedence. And if we add something that already existed in the parent, our child-version will -override the version in the parent. This is very practical: It means that we can let the parent do -the heavy lifting and only tweak the things we want to change. It also means that we could easily -have many different Character classes, all inheriting from DefaultCharacter but changing different -things. And those can in turn also have children …

-

Let’s go on an expedition up the inheritance tree.

-
-
-

Exploring the Evennia library

-

Let’s figure out how to tweak Character. Right now we don’t know much about DefaultCharacter -though. Without knowing that we won’t know what to override. At the top of the file you find

-
from evennia import DefaultCharacter
-
-
-

This is an import statement again, but on a different form to what we’ve seen before. from ... import ... is very commonly used and allows you to precisely dip into a module to extract just the -component you need to use. In this case we head into the evennia package to get -DefaultCharacter.

-

Where is evennia? To find it you need to go to the evennia folder (repository) you originally -cloned from us. If you open it, this is how it looks:

-
evennia/
-   __init__.py
-   bin/
-   CHANGELOG.txt etc.
-   ...
-   evennia/
-   ...
-
-
-

There are lots of things in there. There are some docs but most of those have to do with the -distribution of Evennia and does not concern us right now. The evennia subfolder is what we are -looking for. This is what you are accessing when you do from evennia import .... It’s set up by -Evennia as a good place to find modules when the server starts. The exact layout of the Evennia -library is covered by our directory overview. You can -also explore it online on github.

-

The structure of the library directly reflects how you import from it.

-
    -
  • To, for example, import the text justify function from -evennia/utils/utils.py you would do from evennia.utils.utils import justify. In your code you -could then just call justify(...) to access its functionality.

  • -
  • You could also do from evennia.utils import utils. In code you would then have to write -utils.justify(...). This is practical if want a lot of stuff from that utils.py module and don’t -want to import each component separately.

  • -
  • You could also do import evennia. You would then have to enter the full -evennia.utils.utils.justify(...) every time you use it. Using from to only import the things you -need is usually easier and more readable.

  • -
  • See this overview about the different ways to -import in Python.

  • -
-

Now, remember that our characters.py module did from evennia import DefaultCharacter. But if we -look at the contents of the evennia folder, there is no DefaultCharacter anywhere! This is -because Evennia gives a large number of optional “shortcuts”, known as the “flat” API. The intention is to make it easier to remember where to find stuff. The flat API is defined in -that weirdly named __init__.py file. This file just basically imports useful things from all over -Evennia so you can more easily find them in one place.

-

We could just look at the documenation to find out where we can look -at our DefaultCharacter parent. But for practice, let’s figure it out. Here is where -DefaultCharacter is imported from inside __init__.py:

-
from .objects.objects import DefaultCharacter
-
-
-

The period at the start means that it imports beginning from the same location this module sits(i.e. -the evennia folder). The full python-path accessible from the outside is thus -evennia.objects.objects.DefaultCharacter. So to import this into our game it’d be perfectly valid -to do

-
from evennia.objects.objects import DefaultCharacter
-
-
-

Using

-
from evennia import DefaultCharacter
-
-
-

is the same thing, just a little easier to remember.

-
-

To access the shortcuts of the flat API you must use from evennia import .... Using something like import evennia.DefaultCharacter will not work. -See more about the Flat API here.

-
-
-
-

Tweaking our Character class

-

In the previous section we traced the parent of our Character class to be -DefaultCharacter in -evennia/objects/objects.py. -Open that file and locate the DefaultCharacter class. It’s quite a bit down -in this module so you might want to search using your editor’s (or browser’s) -search function. Once you find it, you’ll find that the class starts like this:

-

-class DefaultCharacter(DefaultObject):
-    """
-    This implements an Object puppeted by a Session - that is, a character
-    avatar controlled by an account.
-    """
-
-    def basetype_setup(self):
-        """
-        Setup character-specific security.
-        You should normally not need to overload this, but if you do,
-        make sure to reproduce at least the two last commands in this
-        method (unless you want to fundamentally change how a
-        Character object works).
-        """
-        super().basetype_setup()
-        self.locks.add(";".join(["get:false()",     # noone can pick up the character
-                                 "call:false()"]))  # no commands can be called on character from
-outside
-        # add the default cmdset
-        self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
-
-    def at_after_move(self, source_location, **kwargs):
-        """
-        We make sure to look around after a move.
-        """
-        if self.location.access(self, "view"):
-            self.msg(self.at_look(self.location))
-
-    def at_pre_puppet(self, account, session=None, **kwargs):
-        """
-        Return the character from storage in None location in `at_post_unpuppet`.
-        """
-
-    # ...
-
-
-
-

… And so on (you can see the full class online here). Here we -have functional code! These methods may not be directly visible in Character back in our game dir, -but they are still available since Character is a child of DefaultCharacter above. Here is a -brief summary of the methods we find in DefaultCharacter (follow in the code to see if you can see -roughly where things happen)::

-
    -
  • basetype_setup is called by Evennia only once, when a Character is first created. In the -DefaultCharacter class it sets some particular Locks so that people can’t pick up and -puppet Characters just like that. It also adds the Character Cmdset so that -Characters always can accept command-input (this should usually not be modified - the normal hook to -override is at_object_creation, which is called after basetype_setup (it’s in the parent)).

  • -
  • at_after_move makes it so that every time the Character moves, the look command is -automatically fired (this would not make sense for just any regular Object).

  • -
  • at_pre_puppet is called when an Account begins to puppet this Character. When not puppeted, the -Character is hidden away to a None location. This brings it back to the location it was in before. -Without this, “headless” Characters would remain in the game world just standing around.

  • -
  • at_post_puppet is called when puppeting is complete. It echoes a message to the room that his -Character has now connected.

  • -
  • at_post_unpuppet is called once stopping puppeting of the Character. This hides away the -Character to a None location again.

  • -
  • There are also some utility properties which makes it easier to get some time stamps from the -Character.

  • -
-

Reading the class we notice another thing:

-
class DefaultCharacter(DefaultObject):
-    # ...
-
-
-

This means that DefaultCharacter is in itself a child of something called DefaultObject! Let’s -see what this parent class provides. It’s in the same module as DefaultCharacter, you just need to -scroll up near the top:

-
class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
-   # ...
-
-
-

This is a really big class where the bulk of code defining an in-game object resides. It consists of -a large number of methods, all of which thus also becomes available on the DefaultCharacter class -below and by extension in your Character class over in your game dir. In this class you can for -example find the msg method we have been using before.

-
-

You should probably not expect to understand all details yet, but as an exercise, find and read -the doc string of msg.

-
-
-

As seen, DefaultObject actually has multiple parents. In one of those the basic key property -is defined, but we won’t travel further up the inheritance tree in this tutorial. If you are -interested to see them, you can find TypeclassBase in evennia/typeclasses/models.py and ObjectDB in evennia/obj -ects/models.py. We -will also not go into the details of Multiple Inheritance or -Metaclasses here. The general rule -is that if you realize that you need these features, you already know enough to use them.

-
-

Remember the at_pre_puppet method we looked at in DefaultCharacter? If you look at the -at_pre_puppet hook as defined in DefaultObject you’ll find it to be completely empty (just a -pass). So if you puppet a regular object it won’t be hiding/retrieving the object when you -unpuppet it. The DefaultCharacter class overrides its parent’s functionality with a version of -its own. And since it’s DefaultCharacter that our Character class inherits back in our game dir, -it’s that version of at_pre_puppet we’ll get. Anything not explicitly overridden will be passed -down as-is.

-

While it’s useful to read the code, we should never actually modify anything inside the evennia -folder. Only time you would want that is if you are planning to release a bug fix or new feature for -Evennia itself. Instead you override the default functionality inside your game dir.

-

So to conclude our little foray into classes, objects and inheritance, locate the simple little -at_before_say method in the DefaultObject class:

-
    def at_before_say(self, message, **kwargs):
-        """
-        (doc string here)
-        """
-        return message
-
-
-

If you read the doc string you’ll find that this can be used to modify the output of say before it -goes out. You can think of it like this: Evennia knows the name of this method, and when someone -speaks, Evennia will make sure to redirect the outgoing message through this method. It makes it -ripe for us to replace with a version of our own.

-
-

In the Evennia documentation you may sometimes see the term hook used for a method explicitly -meant to be overridden like this.

-
-

As you can see, the first argument to at_before_say is self. In Python, the first argument of a -method is always a back-reference to the object instance on which the method is defined. By -convention this argument is always called self but it could in principle be named anything. The -self is very useful. If you wanted to, say, send a message to the same object from inside -at_before_say, you would do self.msg(...).

-

What can trip up newcomers is that you don’t include self when you call the method. Try:

-
> @py me.at_before_say("Hello World!")
-Hello World!
-
-
-

Note that we don’t send self but only the message argument. Python will automatically add self -for us. In this case, self will become equal to the Character instance me.

-

By default the at_before_say method doesn’t do anything. It just takes the message input and -returns it just the way it was (the return is another reserved Python word).

-
-

We won’t go into **kwargs here, but it (and its sibling *args) is also important to -understand, extra reading is here for **kwargs.

-
-

Now, open your game folder and edit mygame/typeclasses/characters.py. Locate your Character -class and modify it as such:

-
class Character(DefaultCharacter):
-    """
-    (docstring here)
-    """
-    def at_before_say(self, message, **kwargs):
-        "Called before say, allows for tweaking message"
-        return f"{message} ..."
-
-
-

So we add our own version of at_before_say, duplicating the def line from the parent but putting -new code in it. All we do in this tutorial is to add an ellipsis (...) to the message as it passes -through the method.

-

Note that f in front of the string, it means we turned the string into a ‘formatted string’. We -can now easily inject stuff directly into the string by wrapping them in curly brackets { }. In -this example, we put the incoming message into the string, followed by an ellipsis. This is only -one way to format a string. Python has very powerful string formatting and -you are wise to learn it well, considering your game will be mainly text-based.

-
-

You could also copy & paste the relevant method from DefaultObject here to get the full doc -string. For more complex methods, or if you only want to change some small part of the default -behavior, copy & pasting will eliminate the need to constantly look up the original method and keep -you sane.

-
-

In-game, now try

-
> @reload
-> say Hello
-You say, "Hello ..."
-
-
-

An ellipsis ... is added to what you said! This is a silly example but you have just made your -first code change to core functionality - without touching any of Evennia’s original code! We just -plugged in our own version of the at_before_say method and it replaced the default one. Evennia -happily redirected the message through our version and we got a different output.

-
-

For sane overriding of parent methods you should also be aware of Python’s -super, which allows you to call the -methods defined on a parent in your child class.

-
-
-
-

The Evennia shell

-

Now on to some generally useful tools as you continue learning Python and Evennia. We have so far -explored using py and have inserted Python code directly in-game. We have also modified Evennia’s -behavior by overriding default functionality with our own. There is a third way to conveniently -explore Evennia and Python - the Evennia shell.

-

Outside of your game, cd to your mygame folder and make sure any needed virtualenv is running. -Next:

-
> pip install ipython      # only needed once
-
-
-

The IPython program is just a nicer interface to the -Python interpreter - you only need to install it once, after which Evennia will use it -automatically.

-
> evennia shell
-
-
-

If you did this call from your game dir you will now be in a Python prompt managed by the IPython -program.

-
IPython ...
-...
-In [1]: IPython has some very nice ways to explore what Evennia has to offer.
-
-> import evennia
-> evennia.<TAB>
-
-
-

That is, write evennia. and press the Tab key. You will be presented with a list of all available -resources in the Evennia Flat API. We looked at the __init__.py file in the evennia folder -earlier, so some of what you see should be familiar. From the IPython prompt, do:

-
> from evennia import DefaultCharacter
-> DefaultCharacter.at_before_say?
-
-
-

Don’t forget that you can use <TAB> to auto-complete code as you write. Appending a single ? to -the end will show you the doc-string for at_before_say we looked at earlier. Use ?? to get the -whole source code.

-

Let’s look at our over-ridden version instead. Since we started the evennia shell from our game -dir we can easily get to our code too:

-
> from typeclasses.characters import Character
-> Character.at_before_say??
-
-
-

This will show us the changed code we just did. Having a window with IPython running is very -convenient for quickly exploring code without having to go digging through the file structure!

-
-
-

Where to go from here

-

This should give you a running start using Python with Evennia. If you are completely new to -programming or Python you might want to look at a more formal Python tutorial. You can find links -and resources on our link page.

-

We have touched upon many of the concepts here but to use Evennia and to be able to follow along in -the code, you will need basic understanding of Python -modules, -variables, -conditional statements, -loops, -functions, -lists, dictionaries, list comprehensions -and string formatting. You should also have a basic -understanding of object-oriented programming -and what Python Classes are.

-

Once you have familiarized yourself, or if you prefer to pick Python up as you go, continue to one -of the beginning-level Evennia tutorials to gradually build up your understanding.

-

Good luck!

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Quirks.html b/docs/0.9.5/Quirks.html deleted file mode 100644 index 1e46efab53..0000000000 --- a/docs/0.9.5/Quirks.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - - Quirks — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Quirks

-

This is a list of various quirks or common stumbling blocks that people often ask about or report -when using (or trying to use) Evennia. They are not bugs.

-
-

Forgetting to use @reload to see changes to your typeclasses

-

Firstly: Reloading the server is a safe and usually quick operation which will not disconnect any -accounts.

-

New users tend to forget this step. When editing source code (such as when tweaking typeclasses and -commands or adding new commands to command sets) you need to either use the in-game @reload -command or, from the command line do python evennia.py reload before you see your changes.

-
-
-

Web admin to create new Account

-

If you use the default login system and are trying to use the Web admin to create a new Player -account, you need to consider which MULTIACCOUNT_MODE you are in. If you are in -MULTIACCOUNT_MODE 0 or 1, the login system expects each Account to also have a Character -object named the same as the Account - there is no character creation screen by default. If using -the normal mud login screen, a Character with the same name is automatically created and connected -to your Account. From the web interface you must do this manually.

-

So, when creating the Account, make sure to also create the Character from the same form as you -create the Account from. This should set everything up for you. Otherwise you need to manually set -the “account” property on the Character and the “character” property on the Account to point to each -other. You must also set the lockstring of the Character to allow the Account to “puppet” this -particular character.

-
-
-

Mutable attributes and their connection to the database

-

When storing a mutable object (usually a list or a dictionary) in an Attribute

-
     object.db.mylist = [1,2,3]
-
-
-

you should know that the connection to the database is retained also if you later extract that -Attribute into another variable (what is stored and retrieved is actually a PackedList or a -PackedDict that works just like their namesakes except they save themselves to the database when -changed). So if you do

-
     alist = object.db.mylist
-     alist.append(4)
-
-
-

this updates the database behind the scenes, so both alist and object.db.mylist are now -[1,2,3,4]

-

If you don’t want this, Evennia provides a way to stably disconnect the mutable from the database by -use of evennia.utils.dbserialize.deserialize:

-
     from evennia.utils.dbserialize import deserialize
-
-     blist = deserialize(object.db.mylist)
-     blist.append(4)
-
-
-

The property blist is now [1,2,3,4] whereas object.db.mylist remains unchanged. If you want to -update the database you’d need to explicitly re-assign the updated data to the mylist Attribute.

-
-
-

Commands are matched by name or alias

-

When merging command sets it’s important to remember that command objects are identified -both by key or alias. So if you have a command with a key look and an alias ls, introducing -another command with a key ls will be assumed by the system to be identical to the first one. -This usually means merging cmdsets will overload one of them depending on priority. Whereas this is -logical once you know how command objects are handled, it may be confusing if you are just looking -at the command strings thinking they are parsed as-is.

-
-
-

Objects turning to DefaultObject

-

A common confusing error for new developers is finding that one or more objects in-game are suddenly -of the type DefaultObject rather than the typeclass you wanted it to be. This happens when you -introduce a critical Syntax error to the module holding your custom class. Since such a module is -not valid Python, Evennia can’t load it at all to get to the typeclasses within. To keep on running, -Evennia will solve this by printing the full traceback to the terminal/console and temporarily fall -back to the safe DefaultObject until you fix the problem and reload. Most errors of this kind will -be caught by any good text editors. Keep an eye on the terminal/console during a reload to catch -such errors - you may have to scroll up if your window is small.

-
-
-

Overriding of magic methods

-

Python implements a system of magic -methods, usually -prefixed and suffixed by double-underscores (__example__) that allow object instances to have -certain operations performed on them without needing to do things like turn them into strings or -numbers first– for example, is obj1 greater than or equal to obj2?

-

Neither object is a number, but given obj1.size == "small" and obj2.size == "large", how might -one compare these two arbitrary English adjective strings to figure out which is greater than the -other? By defining the __ge__ (greater than or equal to) magic method on the object class in which -you figure out which word has greater significance, perhaps through use of a mapping table -({'small':0, 'large':10}) or other lookup and comparing the numeric values of each.

-

Evennia extensively makes use of magic methods on typeclasses to do things like initialize objects, -check object existence or iterate over objects in an inventory or container. If you override or -interfere with the return values from the methods Evennia expects to be both present and working, it -can result in very inconsistent and hard-to-diagnose errors.

-

The moral of the story– it can be dangerous to tinker with magic methods on typeclassed objects. -Try to avoid doing so.

-
-
-

Known upstream bugs

-
    -
  • There is currently (Autumn 2017) a bug in the zope.interface installer on some Linux Ubuntu -distributions (notably Ubuntu 16.04 LTS). Zope is a dependency of Twisted. The error manifests in -the server not starting with an error that zope.interface is not found even though pip list -shows it’s installed. The reason is a missing empty __init__.py file at the root of the zope -package. If the virtualenv is named “evenv” as suggested in the Getting Started -instructions, use the following command to fix it:

    -
    touch evenv/local/lib/python2.7/site-packages/zope/__init__.py
    -
    -
    -

    This will create the missing file and things should henceforth work correctly.

    -
  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/RSS.html b/docs/0.9.5/RSS.html deleted file mode 100644 index bc24d6a47f..0000000000 --- a/docs/0.9.5/RSS.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - RSS — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

RSS

-

RSS is a format for easily tracking updates on websites. The -principle is simple - whenever a site is updated, a small text file is updated. An RSS reader can -then regularly go online, check this file for updates and let the user know what’s new.

-

Evennia allows for connecting any number of RSS feeds to any number of in-game channels. Updates to -the feed will be conveniently echoed to the channel. There are many potential uses for this: For -example the MUD might use a separate website to host its forums. Through RSS, the players can then -be notified when new posts are made. Another example is to let everyone know you updated your dev -blog. Admins might also want to track the latest Evennia updates through our own RSS feed -here.

-
-

Configuring RSS

-

To use RSS, you first need to install the feedparser python -module.

-
pip install feedparser
-
-
-

Next you activate RSS support in your config file by settting RSS_ENABLED=True.

-

Start/reload Evennia as a privileged user. You should now have a new command available, @rss2chan:

-
 @rss2chan <evennia_channel> = <rss_url>
-
-
-
-

Setting up RSS, step by step

-

You can connect RSS to any Evennia channel, but for testing, let’s set up a new channel “rss”.

-
 @ccreate rss = RSS feeds are echoed to this channel!
-
-
-

Let’s connect Evennia’s code-update feed to this channel. The RSS url for evennia updates is -https://github.com/evennia/evennia/commits/master.atom, so let’s add that:

-
 @rss2chan rss = https://github.com/evennia/evennia/commits/master.atom
-
-
-

That’s it, really. New Evennia updates will now show up as a one-line title and link in the channel. -Give the @rss2chan command on its own to show all connections. To remove a feed from a channel, -you specify the connection again (use the command to see it in the list) but add the /delete -switch:

-
 @rss2chan/delete rss = https://github.com/evennia/evennia/commits/master.atom
-
-
-

You can connect any number of RSS feeds to a channel this way. You could also connect them to the -same channels as IRC to have the feed echo to external chat channels as well.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Roadmap.html b/docs/0.9.5/Roadmap.html deleted file mode 100644 index d4c4e18eea..0000000000 --- a/docs/0.9.5/Roadmap.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - Roadmap — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Roadmap

-

As of Autumn 2016, Evennia’s development roadmap is tracked through the Evennia Projects -Page.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Running-Evennia-in-Docker.html b/docs/0.9.5/Running-Evennia-in-Docker.html deleted file mode 100644 index 57d0005164..0000000000 --- a/docs/0.9.5/Running-Evennia-in-Docker.html +++ /dev/null @@ -1,409 +0,0 @@ - - - - - - - - - Running Evennia in Docker — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Running Evennia in Docker

-

Evennia has an official docker image which makes -running an Evennia-based game in a Docker container easy.

-
-

Install Evennia through docker

-

First, install the docker program so you can run the Evennia container. You can get it freely from -docker.com. Linux users can likely also get it through their normal -package manager.

-

To fetch the latest evennia docker image, run:

-
docker pull evennia/evennia
-
-
-

This is a good command to know, it is also how you update to the latest version when we make updates -in the future. This tracks the master branch of Evennia.

-
-

Note: If you want to experiment with the (unstable) develop branch, use docker pull evennia/evennia:develop.

-
-

Next cd to a place where your game dir is, or where you want to create it. Then run:

-
docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 --rm -v $PWD:/usr/src/game --user
-
-
-

\(UID:\)GID evennia/evennia

-

Having run this (see next section for a description of what’s what), you will be at a prompt inside -the docker container:

-
evennia|docker /usr/src/game $
-
-
-

This is a normal shell prompt. We are in the /usr/src/game location inside the docker container. -If you had anything in the folder you started from, you should see it here (with ls) since we -mounted the current directory to /usr/src/game (with -v above). You have the evennia command -available and can now proceed to create a new game as per the Getting Started -instructions (you can skip the virtualenv and install ‘globally’ in the container though).

-

You can run Evennia from inside this container if you want to, it’s like you are root in a little -isolated Linux environment. To exit the container and all processes in there, press Ctrl-D. If you -created a new game folder, you will find that it has appeared on-disk.

-
-

The game folder or any new files that you created from inside the container will appear as owned -by root. If you want to edit the files outside of the container you should change the ownership. -On Linux/Mac you do this with sudo chown myname:myname -R mygame, where you replace myname with -your username and mygame with whatever your game folder is named.

-
-
-

Description of the docker run command

-
    docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 --rm -v $PWD:/usr/src/game --user
-$UID:$GID evennia/evennia
-
-
-

This is what it does:

-
    -
  • docker run ... evennia/evennia tells us that we want to run a new container based on the -evennia/evennia docker image. Everything in between are options for this. The evennia/evennia is -the name of our official docker image on the dockerhub -repository. If you didn’t do docker pull evennia/evennia first, the image will be downloaded when running this, otherwise your already -downloaded version will be used. It contains everything needed to run Evennia.

  • -
  • -it has to do with creating an interactive session inside the container we start.

  • -
  • --rm will make sure to delete the container when it shuts down. This is nice to keep things tidy -on your drive.

  • -
  • -p 4000:4000 -p 4001:4001 -p 4002:4002 means that we map ports 4000, 4001 and 4002 from -inside the docker container to same-numbered ports on our host machine. These are ports for telnet, -webserver and websockets. This is what allows your Evennia server to be accessed from outside the -container (such as by your MUD client)!

  • -
  • -v $PWD:/usr/src/game mounts the current directory (outside the container) to the path -/usr/src/game inside the container. This means that when you edit that path in the container you -will actually be modifying the “real” place on your hard drive. If you didn’t do this, any changes -would only exist inside the container and be gone if we create a new one. Note that in linux a -shortcut for the current directory is $PWD. If you don’t have this for your OS, you can replace it -with the full path to the current on-disk directory (like C:/Development/evennia/game or wherever -you want your evennia files to appear).

  • -
  • --user $UID:$GID ensures the container’s modifications to $PWD are done with you user and -group IDs instead of root’s IDs (root is the user running evennia inside the container). This avoids -having stale .pid files in your filesystem between container reboots which you have to force -delete with sudo rm server/*.pid before each boot.

  • -
-
-
-
-

Running your game as a docker image

-

If you run the docker command given in the previous section from your game dir you can then -easily start Evennia and have a running server without any further fuss.

-

But apart from ease of install, the primary benefit to running an Evennia-based game in a container -is to simplify its deployment into a public production environment. Most cloud-based hosting -providers these days support the ability to run container-based applications. This makes deploying -or updating your game as simple as building a new container image locally, pushing it to your Docker -Hub account, and then pulling from Docker Hub into your AWS/Azure/other docker-enabled hosting -account. The container eliminates the need to install Python, set up a virtualenv, or run pip to -install dependencies.

-
-

Start Evennia and run through docker

-

For remote or automated deployment you may want to start Evennia immediately as soon as the docker -container comes up. If you already have a game folder with a database set up you can also start the -docker container and pass commands directly to it. The command you pass will be the main process to -run in the container. From your game dir, run for example this command:

-
docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 --rm -v $PWD:/usr/src/game
-
-
-

evennia/evennia evennia start –log

-

This will start Evennia as the foreground process, echoing the log to the terminal. Closing the -terminal will kill the server. Note that you must use a foreground command like evennia start --log or evennia ipstart to start the server - otherwise the foreground process will finish -immediately and the container go down.

-
-
-

Create your own game image

-

You may want to create your own image in order to bake in your gamedir directly into the docker -container for easy upload and deployment. -These steps assume that you have created or otherwise obtained a game directory already. First, cd -to your game dir and create a new empty text file named Dockerfile. Save the following two lines -into it:

-
FROM evennia/evennia:latest
-
-ENTRYPOINT evennia start --log
-
-
-

These are instructions for building a new docker image. This one is based on the official -evennia/evennia image. We add the second line to -make make sure evennia starts immediately along with the container (so we don’t need to enter it and -run commands).

-

To build the image:

-
    docker build -t mydhaccount/mygame .
-
-
-

(don’t forget the period . at the end, it tells docker to use use the Dockerfile from the -current location). Here mydhaccount is the name of your dockerhub account. If you don’t have a -dockerhub account you can build the image locally only (name the container whatever you like in that -case, like just mygame).

-

Docker images are stored centrally on your computer. You can see which ones you have available -locally with docker images. Once built, you have a couple of options to run your game.

-

If you have a docker-hub account, you can push your (new or updated) image there:

-
    docker push myhdaccount/mygame
-
-
-
-
-

Run container from your game image for development

-

To run the container based on your game image locally for development, mount the local game -directory as before:

-
docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 -v $PWD:/usr/src/game --user $UID:$GID
-mydhaccount/mygame
-
-
-

Evennia will start and you’ll get output in the terminal, perfect for development. You should be -able to connect to the game with your clients normally.

-
-
-

Deploy game image for production

-

Each time you rebuild the docker image as per the above instructions, the latest copy of your game -directory is actually copied inside the image (at /usr/src/game/). If you don’t mount your on-disk -folder there, the internal one will be used. So for deploying evennia on a server, omit the -v -option and just give the following command:

-
docker run -it --rm -d -p 4000:4000 -p 4001:4001 -p 4002:4002 --user $UID:$GID mydhaccount/mygame
-
-
-

Your game will be downloaded from your docker-hub account and a new container will be built using -the image and started on the server! If your server environment forces you to use different ports, -you can just map the normal ports differently in the command above.

-

Above we added the -d option, which starts the container in daemon mode - you won’t see any -return in the console. You can see it running with docker ps:

-
$ docker ps
-
-CONTAINER ID     IMAGE       COMMAND                  CREATED              ...
-f6d4ca9b2b22     mygame      "/bin/sh -c 'evenn..."   About a minute ago   ...
-
-
-

Note the container ID, this is how you manage the container as it runs.

-
   docker logs f6d4ca9b2b22
-
-
-

Looks at the STDOUT output of the container (i.e. the normal server log)

-
   docker logs -f f6d4ca9b2b22
-
-
-

Tail the log (so it updates to your screen ‘live’).

-
   docker pause f6d4ca9b2b22
-
-
-

Suspend the state of the container.

-
   docker unpause f6d4ca9b2b22
-
-
-

Un-suspend it again after a pause. It will pick up exactly where it were.

-
   docker stop f6d4ca9b2b22
-
-
-

Stop the container. To get it up again you need to use docker run, specifying ports etc. A new -container will get a new container id to reference.

-
-
-
-

How it Works

-

The evennia/evennia docker image holds the evennia library and all of its dependencies. It also -has an ONBUILD directive which is triggered during builds of images derived from it. This -ONBUILD directive handles setting up a volume and copying your game directory code into the proper -location within the container.

-

In most cases, the Dockerfile for an Evennia-based game will only need the FROM evennia/evennia:latest directive, and optionally a MAINTAINER directive if you plan to publish -your image on Docker Hub and would like to provide contact info.

-

For more information on Dockerfile directives, see the Dockerfile -Reference.

-

For more information on volumes and Docker containers, see the Docker site’s Manage data in -containers page.

-
-

What if I Don’t Want “LATEST”?

-

A new evennia/evennia image is built automatically whenever there is a new commit to the master -branch of Evennia. It is possible to create your own custom evennia base docker image based on any -arbitrary commit.

-
    -
  1. Use git tools to checkout the commit that you want to base your image upon. (In the example -below, we’re checking out commit a8oc3d5b.)

  2. -
-
git checkout -b my-stable-branch a8oc3d5b
-
-
-
    -
  1. Change your working directory to the evennia directory containing Dockerfile. Note that -Dockerfile has changed over time, so if you are going far back in the commit history you might -want to bring a copy of the latest Dockerfile with you and use that instead of whatever version -was used at the time.

  2. -
  3. Use the docker build command to build the image based off of the currently checked out commit. -The example below assumes your docker account is mydhaccount.

  4. -
-
docker build -t mydhaccount/evennia .
-
-
-
    -
  1. Now you have a base evennia docker image built off of a specific commit. To use this image to -build your game, you would modify FROM directive in the Dockerfile for your game directory -to be:

  2. -
-
FROM mydhacct/evennia:latest
-
-
-

Note: From this point, you can also use the docker tag command to set a specific tag on your image -and/or upload it into Docker Hub under your account.

-
    -
  1. At this point, build your game using the same docker build command as usual. Change your -working directory to be your game directory and run

  2. -
-
docker build -t mydhaccountt/mygame .
-
-
-
-
-
-

Additional Creature Comforts

-

The Docker ecosystem includes a tool called docker-compose, which can orchestrate complex multi- -container applications, or in our case, store the default port and terminal parameters that we want -specified every time we run our container. A sample docker-compose.yml file to run a containerized -Evennia game in development might look like this:

-
version: '2'
-
-services:
-  evennia:
-    image: mydhacct/mygame
-    stdin_open: true
-    tty: true
-    ports:
-      - "4001-4002:4001-4002"
-      - "4000:4000"
-    volumes:
-      - .:/usr/src/game
-
-
-

With this file in the game directory next to the Dockerfile, starting the container is as simple -as

-
docker-compose up
-
-
-

For more information about docker-compose, see Getting Started with docker- -compose.

-
-

Note that with this setup you lose the --user $UID option. The problem is that the variable -UID is not available inside the configuration file docker-compose.yml. A workaround is to -hardcode your user and group id. In a terminal run echo  $UID:$GID and if for example you get -1000:1000 you can add to docker-compose.yml a line user: 1000:1000 just below the image: ... -line.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Screenshot.html b/docs/0.9.5/Screenshot.html deleted file mode 100644 index 2524f193de..0000000000 --- a/docs/0.9.5/Screenshot.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - Screenshot — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Screenshot

-

evennia_screenshot2017 -(right-click and choose your browser’s equivalent of “view image” to see it full size)

-

This screenshot shows a vanilla install of the just started Evennia MUD server. -In the bottom window we can see the log messages from the running server.

-

In the top left window we see the default website of our new game displayed in a web browser (it has -shrunk to accomodate the smaller screen). Evennia contains its own webserver to serve this page. The -default site shows some brief info about the database. From here you can also reach Django’s admin -interface for editing the database online.

-

To the upper right is the included web-browser client showing a connection to the server on port -4001. This allows users to access the game without downloading a mud client separately.

-

Bottom right we see a login into the stock game using a third-party MUD client -(Mudlet). This connects to the server via the telnet protocol on port 4000.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Scripts.html b/docs/0.9.5/Scripts.html deleted file mode 100644 index 1e861efc2f..0000000000 --- a/docs/0.9.5/Scripts.html +++ /dev/null @@ -1,486 +0,0 @@ - - - - - - - - - Scripts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Scripts

-

Scripts are the out-of-character siblings to the in-character -Objects. Scripts are so flexible that the “Script” is a bit limiting

-
    -
  • we had to pick something to name them after all. Other possible names -(depending on what you’d use them for) would be OOBObjects, -StorageContainers or TimerObjects.

  • -
-

Scripts can be used for many different things in Evennia:

-
    -
  • They can attach to Objects to influence them in various ways - or exist -independently of any one in-game entity (so-called Global Scripts).

  • -
  • They can work as timers and tickers - anything that may change with Time. But -they can also have no time dependence at all. Note though that if all you want -is just to have an object method called repeatedly, you should consider using -the TickerHandler which is more limited but is specialized on -just this task.

  • -
  • They can describe State changes. A Script is an excellent platform for -hosting a persistent, but unique system handler. For example, a Script could be -used as the base to track the state of a turn-based combat system. Since -Scripts can also operate on a timer they can also update themselves regularly -to perform various actions.

  • -
  • They can act as data stores for storing game data persistently in the database -(thanks to its ability to have Attributes).

  • -
  • They can be used as OOC stores for sharing data between groups of objects, for -example for tracking the turns in a turn-based combat system or barter exchange.

  • -
-

Scripts are Typeclassed entities and are manipulated in a similar -way to how it works for other such Evennia entities:

-
# create a new script
-new_script = evennia.create_script(key="myscript", typeclass=...)
-
-# search (this is always a list, also if there is only one match)
-list_of_myscript = evennia.search_script("myscript")
-
-
-
-
-

Defining new Scripts

-

A Script is defined as a class and is created in the same way as other -typeclassed entities. The class has several properties -to control the timer-component of the scripts. These are all optional - -leaving them out will just create a Script with no timer components (useful to act as -a database store or to hold a persistent game system, for example).

-

This you can do for example in the module -evennia/typeclasses/scripts.py. Below is an example Script -Typeclass.

-
from evennia import DefaultScript
-
-class MyScript(DefaultScript):
-
-    def at_script_creation(self):
-        self.key = "myscript"
-        self.interval = 60  # 1 min repeat
-
-    def at_repeat(self):
-        # do stuff every minute
-
-
-

In mygame/typeclasses/scripts.py is the Script class which inherits from DefaultScript -already. This is provided as your own base class to do with what you like: You can tweak Script if -you want to change the default behavior and it is usually convenient to inherit from this instead. -Here’s an example:

-
    # for example in mygame/typeclasses/scripts.py
-    # Script class is defined at the top of this module
-
-    import random
-
-    class Weather(Script):
-        """
-        A timer script that displays weather info. Meant to
-        be attached to a room.
-          
-        """
-        def at_script_creation(self):
-            self.key = "weather_script"
-            self.desc = "Gives random weather messages."
-            self.interval = 60 * 5  # every 5 minutes
-            self.persistent = True  # will survive reload
-
-        def at_repeat(self):
-            "called every self.interval seconds."
-            rand = random.random()
-            if rand < 0.5:
-                weather = "A faint breeze is felt."
-            elif rand < 0.7:
-                weather = "Clouds sweep across the sky."
-            else:
-                weather = "There is a light drizzle of rain."
-            # send this message to everyone inside the object this
-            # script is attached to (likely a room)
-            self.obj.msg_contents(weather)
-
-
-

If we put this script on a room, it will randomly report some weather -to everyone in the room every 5 minutes.

-

To activate it, just add it to the script handler (scripts) on an -Room. That object becomes self.obj in the example above. Here we -put it on a room called myroom:

-
    myroom.scripts.add(scripts.Weather)
-
-
-
-

Note that typeclasses in your game dir is added to the setting TYPECLASS_PATHS. -Therefore we don’t need to give the full path (typeclasses.scripts.Weather -but only scripts.Weather above.

-
-

If you wanted to stop and delete that script on the Room. You could do that -with the script handler by passing the delete method with the script key (self.key) as:

-
    myroom.scripts.delete('weather_script')
-
-
-
-

Note that If no key is given, this will delete all scripts on the object!

-
-

You can also create scripts using the evennia.create_script function:

-
    from evennia import create_script
-    create_script('typeclasses.weather.Weather', obj=myroom)
-
-
-

Note that if you were to give a keyword argument to create_script, that would -override the default value in your Typeclass. So for example, here is an instance -of the weather script that runs every 10 minutes instead (and also not survive -a server reload):

-
    create_script('typeclasses.weather.Weather', obj=myroom,
-                   persistent=False, interval=10*60)
-
-
-

From in-game you can use the @script command to launch the Script on things:

-
     @script here = typeclasses.scripts.Weather
-
-
-

You can conveniently view and kill running Scripts by using the @scripts -command in-game.

-
-
-

Properties and functions defined on Scripts

-

A Script has all the properties of a typeclassed object, such as db and ndb(see -Typeclasses). Setting key is useful in order to manage scripts (delete them by name -etc). These are usually set up in the Script’s typeclass, but can also be assigned on the fly as -keyword arguments to evennia.create_script.

-
    -
  • desc - an optional description of the script’s function. Seen in script listings.

  • -
  • interval - how often the script should run. If interval == 0 (default), this script has no -timing component, will not repeat and will exist forever. This is useful for Scripts used for -storage or acting as bases for various non-time dependent game systems.

  • -
  • start_delay - (bool), if we should wait interval seconds before firing for the first time or -not.

  • -
  • repeats - How many times we should repeat, assuming interval > 0. If repeats is set to <= 0, -the script will repeat indefinitely. Note that each firing of the script (including the first one) -counts towards this value. So a Script with start_delay=False and repeats=1 will start, -immediately fire and shut down right away.

  • -
  • persistent- if this script should survive a server reset or server shutdown. (You don’t need -to set this for it to survive a normal reload - the script will be paused and seamlessly restart -after the reload is complete).

  • -
-

There is one special property:

-
    -
  • obj - the Object this script is attached to (if any). You should not need to set -this manually. If you add the script to the Object with myobj.scripts.add(myscriptpath) or give -myobj as an argument to the utils.create.create_script function, the obj property will be set -to myobj for you.

  • -
-

It’s also imperative to know the hook functions. Normally, overriding -these are all the customization you’ll need to do in Scripts. You can -find longer descriptions of these in src/scripts/scripts.py.

-
    -
  • at_script_creation() - this is usually where the script class sets things like interval and -repeats; things that control how the script runs. It is only called once - when the script is -first created.

  • -
  • is_valid() - determines if the script should still be running or not. This is called when -running obj.scripts.validate(), which you can run manually, but which is also called by Evennia -during certain situations such as reloads. This is also useful for using scripts as state managers. -If the method returns False, the script is stopped and cleanly removed.

  • -
  • at_start() - this is called when the script starts or is unpaused. For persistent scripts this -is at least once ever server startup. Note that this will always be called right away, also if -start_delay is True.

  • -
  • at_repeat() - this is called every interval seconds, or not at all. It is called right away at -startup, unless start_delay is True, in which case the system will wait interval seconds -before calling.

  • -
  • at_stop() - this is called when the script stops for whatever reason. It’s a good place to do -custom cleanup.

  • -
  • at_server_reload() - this is called whenever the server is warm-rebooted (e.g. with the -@reload command). It’s a good place to save non-persistent data you might want to survive a -reload.

  • -
  • at_server_shutdown() - this is called when a system reset or systems shutdown is invoked.

  • -
-

Running methods (usually called automatically by the engine, but possible to also invoke manually)

-
    -
  • start() - this will start the script. This is called automatically whenever you add a new script -to a handler. at_start() will be called.

  • -
  • stop() - this will stop the script and delete it. Removing a script from a handler will stop it -automatically. at_stop() will be called.

  • -
  • pause() - this pauses a running script, rendering it inactive, but not deleting it. All -properties are saved and timers can be resumed. This is called automatically when the server reloads -and will not lead to the at_stop() hook being called. This is a suspension of the script, not a -change of state.

  • -
  • unpause() - resumes a previously paused script. The at_start() hook will be called to allow -it to reclaim its internal state. Timers etc are restored to what they were before pause. The server -automatically unpauses all paused scripts after a server reload.

  • -
  • force_repeat() - this will forcibly step the script, regardless of when it would otherwise have -fired. The timer will reset and the at_repeat() hook is called as normal. This also counts towards -the total number of repeats, if limited.

  • -
  • time_until_next_repeat() - for timed scripts, this returns the time in seconds until it next -fires. Returns None if interval==0.

  • -
  • remaining_repeats() - if the Script should run a limited amount of times, this tells us how many -are currently left.

  • -
  • reset_callcount(value=0) - this allows you to reset the number of times the Script has fired. It -only makes sense if repeats > 0.

  • -
  • restart(interval=None, repeats=None, start_delay=None) - this method allows you to restart the -Script in-place with different run settings. If you do, the at_stop hook will be called and the -Script brought to a halt, then the at_start hook will be called as the Script starts up with your -(possibly changed) settings. Any keyword left at None means to not change the original setting.

  • -
-
-
-

Global Scripts

-

A script does not have to be connected to an in-game object. If not it is -called a Global script. You can create global scripts by simply not supplying an object to store -it on:

-
     # adding a global script
-     from evennia import create_script
-     create_script("typeclasses.globals.MyGlobalEconomy",
-                    key="economy", persistent=True, obj=None)
-
-
-

Henceforth you can then get it back by searching for its key or other identifier with -evennia.search_script. In-game, the scripts command will show all scripts.

-

Evennia supplies a convenient “container” called GLOBAL_SCRIPTS that can offer an easy -way to access global scripts. If you know the name (key) of the script you can get it like so:

-
from evennia import GLOBAL_SCRIPTS
-
-my_script = GLOBAL_SCRIPTS.my_script
-# needed if there are spaces in name or name determined on the fly
-another_script = GLOBAL_SCRIPTS.get("another script")
-# get all global scripts (this returns a Queryset)
-all_scripts = GLOBAL_SCRIPTS.all()
-# you can operate directly on the script
-GLOBAL_SCRIPTS.weather.db.current_weather = "Cloudy"
-
-
-
-
-

Note that global scripts appear as properties on GLOBAL_SCRIPTS based on their key. -If you were to create two global scripts with the same key (even with different typeclasses), -the GLOBAL_SCRIPTS container will only return one of them (which one depends on order in -the database). Best is to organize your scripts so that this does not happen. Otherwise, use -evennia.search_script to get exactly the script you want.

-
-

There are two ways to make a script appear as a property on GLOBAL_SCRIPTS. The first is -to manually create a new global script with create_script as mentioned above. Often you want this -to happen automatically when the server starts though. For this you add a python global dictionary -named GLOBAL_SCRIPTS to your settings.py file. The settings.py fie is located in -mygame/conf/settings.py:

-
GLOBAL_SCRIPTS = {
-    "my_script": {
-        "typeclass": "typeclasses.scripts.Weather",
-        "repeats": -1,
-        "interval": 50,
-        "desc": "Weather script",
-        "persistent": True
-    },
-    "storagescript": {
-        "typeclass": "typeclasses.scripts.Storage",
-        "persistent": True
-    },
-    {
-    "another_script": {
-        "typeclass": "typeclasses.another_script.AnotherScript"
-    }
-}
-
-
-

Here the key (myscript and storagescript above) is required, all other fields are optional. If -typeclass is not given, a script of type settings.BASE_SCRIPT_TYPECLASS is assumed. The keys -related to timing and intervals are only needed if the script is timed.

-
-

Note: Provide the full path to the scripts module in GLOBAL_SCRIPTS. In the example with -another_script, you can also create separate script modules that exist beyond scrypt.py for -further organizational requirements if needed.

-
-

Evennia will use the information in settings.GLOBAL_SCRIPTS to automatically create and start -these -scripts when the server starts (unless they already exist, based on their key). You need to reload -the server before the setting is read and new scripts become available. You can then find the key -you gave as properties on evennia.GLOBAL_SCRIPTS -(such as evennia.GLOBAL_SCRIPTS.storagescript).

-
-

Note: Make sure that your Script typeclass does not have any critical errors. If so, you’ll see -errors in your log and your Script will temporarily fall back to being a DefaultScript type.

-
-

Moreover, a script defined this way is guaranteed to exist when you try to access it:

-
from evennia import GLOBAL_SCRIPTS
-# first stop the script
-GLOBAL_SCRIPTS.storagescript.stop()
-# running the `scripts` command now will show no storagescript
-# but below now it's recreated again!
-storage = GLOBAL_SCRIPTS.storagescript
-
-
-

That is, if the script is deleted, next time you get it from GLOBAL_SCRIPTS, it will use the -information -in settings to recreate it for you.

-
-

Note that if your goal with the Script is to store persistent data, you should set it as -persistent=True, either in settings.GLOBAL_SCRIPTS or in the Scripts typeclass. Otherwise any -data you wanted to store on it will be gone (since a new script of the same name is restarted -instead).

-
-
-
-

Dealing with Errors

-

Errors inside an timed, executing script can sometimes be rather terse or point to -parts of the execution mechanism that is hard to interpret. One way to make it -easier to debug scripts is to import Evennia’s native logger and wrap your -functions in a try/catch block. Evennia’s logger can show you where the -traceback occurred in your script.

-

-from evennia.utils import logger
-
-class Weather(DefaultScript):
-
-    # [...]
-
-    def at_repeat(self):
-        
-        try:
-            # [...] code as above
-        except Exception:
-            # logs the error
-            logger.log_trace()
-    
-
-
-
-
-

Example of a timed script

-

In-game you can try out scripts using the @script command. In the -evennia/contrib/tutorial_examples/bodyfunctions.py is a little example script -that makes you do little ‘sounds’ at random intervals. Try the following to apply an -example time-based script to your character.

-
> @script self = bodyfunctions.BodyFunctions
-
-
-
-

Note: Since evennia/contrib/tutorial_examples is in the default setting -TYPECLASS_PATHS, we only need to specify the final part of the path, -that is, bodyfunctions.BodyFunctions.

-
-

If you want to inflict your flatulence script on another person, place or -thing, try something like the following:

-
> @py self.location.search('matt').scripts.add('bodyfunctions.BodyFunctions')
-
-
-

Here’s how you stop it on yourself.

-
> @script/stop self = bodyfunctions.BodyFunctions
-
-
-

This will kill the script again. You can use the @scripts command to list all -active scripts in the game, if any (there are none by default).

-

For another example of a Script in use, check out the Turn Based Combat System -tutorial.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Security.html b/docs/0.9.5/Security.html deleted file mode 100644 index e484319a7a..0000000000 --- a/docs/0.9.5/Security.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - - - Security — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Security

-

Hackers these days aren’t discriminating, and their backgrounds range from bored teenagers to -international intelligence agencies. Their scripts and bots endlessly crawl the web, looking for -vulnerable systems they can break into. Who owns the system is irrelevant– it doesn’t matter if it -belongs to you or the Pentagon, the goal is to take advantage of poorly-secured systems and see what -resources can be controlled or stolen from them.

-

If you’re considering deploying to a cloud-based host, you have a vested interest in securing your -applications– you likely have a credit card on file that your host can freely bill. Hackers pegging -your CPU to mine cryptocurrency or saturating your network connection to participate in a botnet or -send spam can run up your hosting bill, get your service suspended or get your address/site -blacklisted by ISPs. It can be a difficult legal or political battle to undo this damage after the -fact.

-

As a developer about to expose a web application to the threat landscape of the modern internet, -here are a few tips to consider to increase the security of your Evennia install.

-
-

Know your logs

-

In case of emergency, check your logs! By default they are located in the server/logs/ folder. -Here are some of the more important ones and why you should care:

-
    -
  • http_requests.log will show you what HTTP requests have been made against Evennia’s built-in -webserver (TwistedWeb). This is a good way to see if people are innocuously browsing your site or -trying to break it through code injection.

  • -
  • portal.log will show you various networking-related information. This is a good place to check -for odd or unusual types or amounts of connections to your game, or other networking-related -issues– like when users are reporting an inability to connect.

  • -
  • server.log is the MUX administrator’s best friend. Here is where you’ll find information -pertaining to who’s trying to break into your system by guessing at passwords, who created what -objects, and more. If your game fails to start or crashes and you can’t tell why, this is the first -place you should look for answers. Security-related events are prefixed with an [SS] so when -there’s a problem you might want to pay special attention to those.

  • -
-
-
-

Disable development/debugging options

-

There are a few Evennia/Django options that are set when you first create your game to make it more -obvious to you where problems arise. These options should be disabled before you push your game into -production– leaving them on can expose variables or code someone with malicious intent can easily -abuse to compromise your environment.

-

In server/conf/settings.py:

-
# Disable Django's debug mode
-DEBUG = False
-# Disable the in-game equivalent
-IN_GAME_ERRORS = False
-# If you've registered a domain name, force Django to check host headers. Otherwise leave this
-
-
-

as-is. -# Note the leading period– it is not a typo! -ALLOWED_HOSTS = [‘.example.com’]

-
-
-

Handle user-uploaded images with care

-

If you decide to allow users to upload their own images to be served from your site, special care -must be taken. Django will read the file headers to confirm it’s an image (as opposed to a document -or zip archive), but code can be injected into an image -file after the headers -that can be interpreted as HTML and/or give an attacker a web shell through which they can access -other filesystem resources.

-

Django has a more comprehensive overview of how to handle user-uploaded -files, but -in short you should take care to do one of two things–

-
    -
  • Serve all user-uploaded assets from a separate domain or CDN (not a subdomain of the one you -already have!). For example, you may be browsing reddit.com but note that all the user-submitted -images are being served from the redd.it domain. There are both security and performance benefits -to this (webservers tend to load local resources one-by-one, whereas they will request external -resources in bulk).

  • -
  • If you don’t want to pay for a second domain, don’t understand what any of this means or can’t be -bothered with additional infrastructure, then simply reprocess user images upon receipt using an -image library. Convert them to a different format, for example. Destroy the originals!

  • -
-
-
-

Disable the web interface

-

The web interface allows visitors to see an informational page as well as log into a browser-based -telnet client with which to access Evennia. It also provides authentication endpoints against which -an attacker can attempt to validate stolen lists of credentials to see which ones might be shared by -your users. Django’s security is robust, but if you don’t want/need these features and fully intend -to force your users to use traditional clients to access your game, you might consider disabling -either/both to minimize your attack surface.

-

In server/conf/settings.py:

-
# Disable the Javascript webclient
-WEBCLIENT_ENABLED = False
-# Disable the website altogether
-WEBSERVER_ENABLED = False
-
-
-
-
-

Change your ssh port

-

Automated attacks will often target port 22 seeing as how it’s the standard port for SSH traffic. -Also, -many public wifi hotspots block ssh traffic over port 22 so you might not be able to access your -server from these locations if you like to work remotely or don’t have a home internet connection.

-

If you don’t intend on running a website or securing it with TLS, you can mitigate both problems by -changing the port used for ssh to 443, which most/all hotspot providers assume is HTTPS traffic and -allows through.

-

(Ubuntu) In /etc/ssh/sshd_config, change the following variable:

-
# What ports, IPs and protocols we listen for
-Port 443
-
-
-

Save, close, then run the following command:

-
sudo service ssh restart
-
-
-
-
-

Set up a firewall

-

Ubuntu users can make use of the simple ufw utility. Anybody else can use iptables.

-
# Install ufw (if not already)
-sudo apt-get install ufw
-
-
-

UFW’s default policy is to deny everything. We must specify what we want to allow through our -firewall.

-
# Allow terminal connections to your game
-sudo ufw allow 4000/tcp
-# Allow browser connections to your website
-sudo ufw allow 4001/tcp
-
-
-

Use ONE of the next two commands depending on which port your ssh daemon is listening on:

-
sudo ufw allow 22/tcp
-sudo ufw allow 443/tcp
-
-
-

Finally:

-
sudo ufw enable
-
-
-

Now the only ports open will be your administrative ssh port (whichever you chose), and Evennia on -4000-4001.

-
-
-

Use an external webserver

-

Though not officially supported, there are some benefits to deploying a webserver -to handle/proxy traffic to your Evennia instance.

-

For example, Evennia’s game engine and webservice are tightly integrated. If you bring your game -down for maintenance (or if it simply crashes) your website will go down with it. In these cases a -standalone webserver can still be used to display a maintenance page or otherwise communicate to -your users the reason for the downtime, instead of disappearing off the face of the earth and -returning opaque SERVER NOT FOUND error messages.

-

Proper webservers are also written in more efficient programming languages than Python, and while -Twisted can handle its own, putting a webserver in front of it is like hiring a bouncer to deal with -nuisances and crowds before they even get in the door.

-

Many of the popular webservers also let you plug in additional modules (like -mod_security for Apache) that can be used to detect -(and block!) malicious users or requests before they even touch your game or site. There are also -automated solutions for installing and configuring TLS (via Certbot/Let’s -Encrypt) to secure your website against hotspot and -ISP snooping.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Server-Conf.html b/docs/0.9.5/Server-Conf.html deleted file mode 100644 index 827ce14ff8..0000000000 --- a/docs/0.9.5/Server-Conf.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - - Server Conf — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Server Conf

-

Evennia runs out of the box without any changes to its settings. But there are several important -ways to customize the server and expand it with your own plugins.

-
-

Settings file

-

The “Settings” file referenced throughout the documentation is the file -mygame/server/conf/settings.py. This is automatically created on the first run of evennia --init -(see the Getting Started page).

-

Your new settings.py is relatively bare out of the box. Evennia’s core settings file is actually -[evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default -.py) and is considerably more extensive (it is also heavily documented so you should refer to this -file directly for the available settings).

-

Since mygame/server/conf/settings.py is a normal Python module, it simply imports -evennia/settings_default.py into itself at the top.

-

This means that if any setting you want to change were to depend on some other default setting, -you might need to copy & paste both in order to change them and get the effect you want (for most -commonly changed settings, this is not something you need to worry about).

-

You should never edit evennia/settings_default.py. Rather you should copy&paste the select -variables you want to change into your settings.py and edit them there. This will overload the -previously imported defaults.

-
-

Warning: It may be tempting to copy everything from settings_default.py into your own settings -file. There is a reason we don’t do this out of the box though: it makes it directly clear what -changes you did. Also, if you limit your copying to the things you really need you will directly be -able to take advantage of upstream changes and additions to Evennia for anything you didn’t -customize.

-
-

In code, the settings is accessed through

-
    from django.conf import settings
-     # or (shorter):
-    from evennia import settings
-     # example:
-    servername = settings.SERVER_NAME
-
-
-

Each setting appears as a property on the imported settings object. You can also explore all -possible options with evennia.settings_full (this also includes advanced Django defaults that are -not touched in default Evennia).

-
-

It should be pointed out that when importing settings into your code like this, it will be read -only. You cannot edit your settings from your code! The only way to change an Evennia setting is -to edit mygame/server/conf/settings.py directly. You also generally need to restart the server -(possibly also the Portal) before a changed setting becomes available.

-
-
-
-

Other files in the server/conf directory

-

Apart from the main settings.py file,

-
    -
  • at_initial_setup.py - this allows you to add a custom startup method to be called (only) the -very first time Evennia starts (at the same time as user #1 and Limbo is created). It can be made to -start your own global scripts or set up other system/world-related things your game needs to have -running from the start.

  • -
  • at_server_startstop.py - this module contains two functions that Evennia will call every time -the Server starts and stops respectively - this includes stopping due to reloading and resetting as -well as shutting down completely. It’s a useful place to put custom startup code for handlers and -other things that must run in your game but which has no database persistence.

  • -
  • connection_screens.py - all global string variables in this module are interpreted by Evennia as -a greeting screen to show when an Account first connects. If more than one string variable is -present in the module a random one will be picked.

  • -
  • inlinefuncs.py - this is where you can define custom Inline functions.

  • -
  • inputfuncs.py - this is where you define custom Input functions to handle data -from the client.

  • -
  • lockfuncs.py - this is one of many possible modules to hold your own “safe” lock functions to -make available to Evennia’s Locks.

  • -
  • mssp.py - this holds meta information about your game. It is used by MUD search engines (which -you often have to register with) in order to display what kind of game you are running along with -statistics such as number of online accounts and online status.

  • -
  • oobfuncs.py - in here you can define custom OOB functions.

  • -
  • portal_services_plugin.py - this allows for adding your own custom services/protocols to the -Portal. It must define one particular function that will be called by Evennia at startup. There can -be any number of service plugin modules, all will be imported and used if defined. More info can be -found here.

  • -
  • server_services_plugin.py - this is equivalent to the previous one, but used for adding new -services to the Server instead. More info can be found -here.

  • -
-

Some other Evennia systems can be customized by plugin modules but has no explicit template in -conf/:

-
    -
  • cmdparser.py - a custom module can be used to totally replace Evennia’s default command parser. -All this does is to split the incoming string into “command name” and “the rest”. It also handles -things like error messages for no-matches and multiple-matches among other things that makes this -more complex than it sounds. The default parser is very generic, so you are most often best served -by modifying things further down the line (on the command parse level) than here.

  • -
  • at_search.py - this allows for replacing the way Evennia handles search results. It allows to -change how errors are echoed and how multi-matches are resolved and reported (like how the default -understands that “2-ball” should match the second “ball” object if there are two of them in the -room).

  • -
-
-
-

ServerConf

-

There is a special database model called ServerConf that stores server internal data and settings -such as current account count (for interfacing with the webserver), startup status and many other -things. It’s rarely of use outside the server core itself but may be good to -know about if you are an Evennia developer.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Sessions.html b/docs/0.9.5/Sessions.html deleted file mode 100644 index d167b01bfd..0000000000 --- a/docs/0.9.5/Sessions.html +++ /dev/null @@ -1,303 +0,0 @@ - - - - - - - - - Sessions — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Sessions

-

An Evennia Session represents one single established connection to the server. Depending on the -Evennia session, it is possible for a person to connect multiple times, for example using different -clients in multiple windows. Each such connection is represented by a session object.

-

A session object has its own cmdset, usually the “unloggedin” cmdset. This is what -is used to show the login screen and to handle commands to create a new account (or -Account in evennia lingo) read initial help and to log into the game with an existing -account. A session object can either be “logged in” or not. Logged in means that the user has -authenticated. When this happens the session is associated with an Account object (which is what -holds account-centric stuff). The account can then in turn puppet any number of objects/characters.

-
-

Warning: A Session is not persistent - it is not a Typeclass and has no -connection to the database. The Session will go away when a user disconnects and you will lose any -custom data on it if the server reloads. The .db handler on Sessions is there to present a uniform -API (so you can assume .db exists even if you don’t know if you receive an Object or a Session), -but this is just an alias to .ndb. So don’t store any data on Sessions that you can’t afford to -lose in a reload. You have been warned.

-
-
-

Properties on Sessions

-

Here are some important properties available on (Server-)Sessions

-
    -
  • sessid - The unique session-id. This is an integer starting from 1.

  • -
  • address - The connected client’s address. Different protocols give different information here.

  • -
  • logged_in - True if the user authenticated to this session.

  • -
  • account - The Account this Session is attached to. If not logged in yet, this is -None.

  • -
  • puppet - The Character/Object currently puppeted by this Account/Session combo. If -not logged in or in OOC mode, this is None.

  • -
  • ndb - The Non-persistent Attribute handler.

  • -
  • db - As noted above, Sessions don’t have regular Attributes. This is an alias to ndb.

  • -
  • cmdset - The Session’s CmdSetHandler

  • -
-

Session statistics are mainly used internally by Evennia.

-
    -
  • conn_time - How long this Session has been connected

  • -
  • cmd_last - Last active time stamp. This will be reset by sending idle keepalives.

  • -
  • cmd_last_visible - last active time stamp. This ignores idle keepalives and representes the -last time this session was truly visibly active.

  • -
  • cmd_total - Total number of Commands passed through this Session.

  • -
-
-
-

Multisession mode

-

The number of sessions possible to connect to a given account at the same time and how it works is -given by the MULTISESSION_MODE setting:

-
    -
  • MULTISESSION_MODE=0: One session per account. When connecting with a new session the old one is -disconnected. This is the default mode and emulates many classic mud code bases. In default Evennia, -this mode also changes how the create account Command works - it will automatically create a -Character with the same name as the Account. When logging in, the login command is also modified -to have the player automatically puppet that Character. This makes the distinction between Account -and Character minimal from the player’s perspective.

  • -
  • MULTISESSION_MODE=1: Many sessions per account, input/output from/to each session is treated the -same. For the player this means they can connect to the game from multiple clients and see the same -output in all of them. The result of a command given in one client (that is, through one Session) -will be returned to all connected Sessions/clients with no distinction. This mode will have the -Session(s) auto-create and puppet a Character in the same way as mode 0.

  • -
  • MULTISESSION_MODE=2: Many sessions per account, one character per session. In this mode, -puppeting an Object/Character will link the puppet back only to the particular Session doing the -puppeting. That is, input from that Session will make use of the CmdSet of that Object/Character and -outgoing messages (such as the result of a look) will be passed back only to that puppeting -Session. If another Session tries to puppet the same Character, the old Session will automatically -un-puppet it. From the player’s perspective, this will mean that they can open separate game clients -and play a different Character in each using one game account. -This mode will not auto-create a Character and not auto-puppet on login like in modes 0 and 1. -Instead it changes how the account-cmdsets’s OOCLook command works so as to show a simple -‘character select’ menu.

  • -
  • MULTISESSION_MODE=3: Many sessions per account and character. This is the full multi-puppeting -mode, where multiple sessions may not only connect to the player account but multiple sessions may -also puppet a single character at the same time. From the user’s perspective it means one can open -multiple client windows, some for controlling different Characters and some that share a Character’s -input/output like in mode 1. This mode otherwise works the same as mode 2.

  • -
-
-

Note that even if multiple Sessions puppet one Character, there is only ever one instance of that -Character.

-
-
-
-

Returning data to the session

-

When you use msg() to return data to a user, the object on which you call the msg() matters. The -MULTISESSION_MODE also matters, especially if greater than 1.

-

For example, if you use account.msg("hello") there is no way for evennia to know which session it -should send the greeting to. In this case it will send it to all sessions. If you want a specific -session you need to supply its session to the msg call (account.msg("hello", session=mysession)).

-

On the other hand, if you call the msg() message on a puppeted object, like -character.msg("hello"), the character already knows the session that controls it - it will -cleverly auto-add this for you (you can specify a different session if you specifically want to send -stuff to another session).

-

Finally, there is a wrapper for msg() on all command classes: command.msg(). This will -transparently detect which session was triggering the command (if any) and redirects to that session -(this is most often what you want). If you are having trouble redirecting to a given session, -command.msg() is often the safest bet.

-

You can get the session in two main ways:

-
    -
  • Accounts and Objects (including Characters) have a sessions property. -This is a handler that tracks all Sessions attached to or puppeting them. Use e.g. -accounts.sessions.get() to get a list of Sessions attached to that entity.

  • -
  • A Command instance has a session property that always points back to the Session that triggered -it (it’s always a single one). It will be None if no session is involved, like when a mob or -script triggers the Command.

  • -
-
-
-

Customizing the Session object

-

When would one want to customize the Session object? Consider for example a character creation -system: You might decide to keep this on the out-of-character level. This would mean that you create -the character at the end of some sort of menu choice. The actual char-create cmdset would then -normally be put on the account. This works fine as long as you are MULTISESSION_MODE below 2. -For higher modes, replacing the Account cmdset will affect all your connected sessions, also those -not involved in character creation. In this case you want to instead put the char-create cmdset on -the Session level - then all other sessions will keep working normally despite you creating a new -character in one of them.

-

By default, the session object gets the commands.default_cmdsets.UnloggedinCmdSet when the user -first connects. Once the session is authenticated it has no default sets. To add a “logged-in” -cmdset to the Session, give the path to the cmdset class with settings.CMDSET_SESSION. This set -will then henceforth always be present as soon as the account logs in.

-

To customize further you can completely override the Session with your own subclass. To replace the -default Session class, change settings.SERVER_SESSION_CLASS to point to your custom class. This is -a dangerous practice and errors can easily make your game unplayable. Make sure to take heed of the -original and make your -changes carefully.

-
-
-

Portal and Server Sessions

-

Note: This is considered an advanced topic. You don’t need to know this on a first read-through.

-

Evennia is split into two parts, the Portal and the Server. Each side tracks -its own Sessions, syncing them to each other.

-

The “Session” we normally refer to is actually the ServerSession. Its counter-part on the Portal -side is the PortalSession. Whereas the server sessions deal with game states, the portal session -deals with details of the connection-protocol itself. The two are also acting as backups of critical -data such as when the server reboots.

-

New Account connections are listened for and handled by the Portal using the [protocols](Portal-And- -Server) it understands (such as telnet, ssh, webclient etc). When a new connection is established, a -PortalSession is created on the Portal side. This session object looks different depending on -which protocol is used to connect, but all still have a minimum set of attributes that are generic -to all -sessions.

-

These common properties are piped from the Portal, through the AMP connection, to the Server, which -is now informed a new connection has been established. On the Server side, a ServerSession object -is created to represent this. There is only one type of ServerSession; It looks the same -regardless of how the Account connects.

-

From now on, there is a one-to-one match between the ServerSession on one side of the AMP -connection and the PortalSession on the other. Data arriving to the Portal Session is sent on to -its mirror Server session and vice versa.

-

During certain situations, the portal- and server-side sessions are -“synced” with each other:

-
    -
  • The Player closes their client, killing the Portal Session. The Portal syncs with the Server to -make sure the corresponding Server Session is also deleted.

  • -
  • The Player quits from inside the game, killing the Server Session. The Server then syncs with the -Portal to make sure to close the Portal connection cleanly.

  • -
  • The Server is rebooted/reset/shutdown - The Server Sessions are copied over (“saved”) to the -Portal side. When the Server comes back up, this data is returned by the Portal so the two are again -in sync. This way an Account’s login status and other connection-critical things can survive a -server reboot (assuming the Portal is not stopped at the same time, obviously).

  • -
-
-
-

Sessionhandlers

-

Both the Portal and Server each have a sessionhandler to manage the connections. These handlers -are global entities contain all methods for relaying data across the AMP bridge. All types of -Sessions hold a reference to their respective Sessionhandler (the property is called -sessionhandler) so they can relay data. See protocols for more info -on building new protocols.

-

To get all Sessions in the game (i.e. all currently connected clients), you access the server-side -Session handler, which you get by

-
from evennia.server.sessionhandler import SESSION_HANDLER
-
-
-
-

Note: The SESSION_HANDLER singleton has an older alias SESSIONS that is commonly seen in -various places as well.

-
-

See the -sessionhandler.py -module for details on the capabilities of the ServerSessionHandler.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Setting-up-PyCharm.html b/docs/0.9.5/Setting-up-PyCharm.html deleted file mode 100644 index cbd913684e..0000000000 --- a/docs/0.9.5/Setting-up-PyCharm.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - Setting up PyCharm — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Setting up PyCharm

-
-
-

Directions for setting up PyCharm with Evennia

-

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, 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.

-

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:

-
    -
  1. Go to File > Open...

  2. -
  3. Select the folder (i.e. the evennia root)

  4. -
  5. Select “Open in current window” and “Add to currently opened projects”

  6. -
-
-

Setting up the project interpreter

-

It’s a good idea to do this before attempting anything further. The rest of this page assumes your -project is already configured in PyCharm.

-
    -
  1. Go to File > Settings... > Project: \<mygame\> > Project Interpreter

  2. -
  3. Click the Gear symbol > Add local

  4. -
  5. Navigate to your evenv/scripts directory, and select Python.exe

  6. -
-

Enjoy seeing all your imports checked properly, setting breakpoints, and live variable watching!

-
-
-

Attaching PyCharm debugger to Evennia

-
    -
  1. Launch Evennia in your preferred way (usually from a console/terminal)

  2. -
  3. Open your project in PyCharm

  4. -
  5. In the PyCharm menu, select Run > Attach to Local Process...

  6. -
  7. From the list, pick the twistd process with the server.py parameter (Example: twistd.exe --nodaemon --logfile=\<mygame\>\server\logs\server.log --python=\<evennia repo\>\evennia\server\server.py)

  8. -
-

Of course 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

-
-
-
-

Setting up an Evennia run configuration

-

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.

-
    -
  1. Go to Run > Edit Configutations...

  2. -
  3. Click the plus-symbol to add a new configuration and choose Python

  4. -
  5. Add the script: \<yourrepo\>\evenv\Scripts\evennia_launcher.py (substitute your virtualenv if -it’s not named evenv)

  6. -
  7. Set script parameters to: start -l (-l enables console logging)

  8. -
  9. Ensure the chosen interpreter is from your virtualenv

  10. -
  11. Set Working directory to your mygame folder (not evenv nor evennia)

  12. -
  13. 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).

  14. -
-

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).

-

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.

-
-
-

Alternative run configuration - 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...\

  2. -
  3. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should -be project default)

  4. -
  5. Name the configuration as “stop evennia” and fill rest of the fields accordingly to the image: -Stop run configuration

  6. -
  7. Press Apply

  8. -
-

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...\

  2. -
  3. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should -be project default)

  4. -
  5. Name the configuration as “start evennia” and fill rest of the fields accordingly to the image: -Start run configuration

  6. -
  7. 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

  8. -
  9. Skip the following steps if you don’t want the launcher to stop evennia before starting.

  10. -
  11. 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.

  12. -
  13. Click stop evennia and make sure that it’s added to the list like on the image above.

  14. -
  15. Click Apply and close the run configuration window.

  16. -
-

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 -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 diff --git a/docs/0.9.5/Signals.html b/docs/0.9.5/Signals.html deleted file mode 100644 index 4f9fd6fc2e..0000000000 --- a/docs/0.9.5/Signals.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - Signals — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Signals

-

This is feature available from evennia 0.9 and onward.

-

There are multiple ways for you to plug in your own functionality into Evennia. -The most common way to do so is through hooks - methods on typeclasses that -gets called at particular events. Hooks are great when you want a game entity -to behave a certain way when something happens to it. Signals complements -hooks for cases when you want to easily attach new functionality without -overriding things on the typeclass.

-

When certain events happen in Evennia, a Signal is fired. The idea is that -you can “attach” any number of event-handlers to these signals. You can attach -any number of handlers and they’ll all fire whenever any entity triggers the -signal.

-

Evennia uses the Django Signal system.

-
-

Attaching a handler to a signal

-

First you create your handler

-

-def myhandler(sender, **kwargs):
-  # do stuff
-
-
-
-

The **kwargs is mandatory. Then you attach it to the signal of your choice:

-
from evennia.server import signals
-
-signals.SIGNAL_OBJECT_POST_CREATE.connect(myhandler)
-
-
-
-

This particular signal fires after (post) an Account has connected to the game. -When that happens, myhandler will fire with the sender being the Account that just connected.

-

If you want to respond only to the effects of a specific entity you can do so -like this:

-
from evennia import search_account
-from evennia import signals
-
-account = search_account("foo")[0]
-signals.SIGNAL_ACCOUNT_POST_CONNECT.connect(myhandler, account)
-
-
-
-
-

Available signals

-

All signals (including some django-specific defaults) are available in the module -evennia.server.signals -(with a shortcut evennia.signals). Signals are named by the sender type. So SIGNAL_ACCOUNT_* -returns -Account instances as senders, SIGNAL_OBJECT_* returns Objects etc. Extra keywords (kwargs) -should -be extracted from the **kwargs dict in the signal handler.

-
    -
  • SIGNAL_ACCOUNT_POST_CREATE - this is triggered at the very end of Account.create(). Note that -calling evennia.create.create_account (which is called internally by Account.create) will -not -trigger this signal. This is because using Account.create() is expected to be the most commonly -used way for users to themselves create accounts during login. It passes and extra kwarg ip with -the client IP of the connecting account.

  • -
  • SIGNAL_ACCOUNT_POST_LOGIN - this will always fire when the account has authenticated. Sends -extra kwarg session with the new Session object involved.

  • -
  • SIGNAL_ACCCOUNT_POST_FIRST_LOGIN - this fires just before SIGNAL_ACCOUNT_POST_LOGIN but only -if -this is the first connection done (that is, if there are no previous sessions connected). Also -passes the session along as a kwarg.

  • -
  • SIGNAL_ACCOUNT_POST_LOGIN_FAIL - sent when someone tried to log into an account by failed. -Passes -the session as an extra kwarg.

  • -
  • SIGNAL_ACCOUNT_POST_LOGOUT - always fires when an account logs off, no matter if other sessions -remain or not. Passes the disconnecting session along as a kwarg.

  • -
  • SIGNAL_ACCOUNT_POST_LAST_LOGOUT - fires before SIGNAL_ACCOUNT_POST_LOGOUT, but only if this is -the last Session to disconnect for that account. Passes the session as a kwarg.

  • -
  • SIGNAL_OBJECT_POST_PUPPET - fires when an account puppets this object. Extra kwargs session -and account represent the puppeting entities. -SIGNAL_OBJECT_POST_UNPUPPET - fires when the sending object is unpuppeted. Extra kwargs are -session and account.

  • -
  • SIGNAL_ACCOUNT_POST_RENAME - triggered by the setting of Account.username. Passes extra -kwargs old_name, new_name.

  • -
  • SIGNAL_TYPED_OBJECT_POST_RENAME - triggered when any Typeclassed entity’s key is changed. -Extra -kwargs passed are old_key and new_key.

  • -
  • SIGNAL_SCRIPT_POST_CREATE - fires when a script is first created, after any hooks.

  • -
  • SIGNAL_CHANNEL_POST_CREATE - fires when a Channel is first created, after any hooks.

  • -
  • SIGNAL_HELPENTRY_POST_CREATE - fires when a help entry is first created.

  • -
-

The evennia.signals module also gives you conveneient access to the default Django signals (these -use a -different naming convention).

-
    -
  • pre_save - fired when any database entitiy’s .save method fires, before any saving has -happened.

  • -
  • post_save - fires after saving a database entity.

  • -
  • pre_delete - fires just before a database entity is deleted.

  • -
  • post_delete - fires after a database entity was deleted.

  • -
  • pre_init - fires before a typeclass’ __init__ method (which in turn -happens before the at_init hook fires).

  • -
  • post_init - triggers at the end of __init__ (still before the at_init hook).

  • -
-

These are highly specialized Django signals that are unlikely to be useful to most users. But -they are included here for completeness.

-
    -
  • m2m_changed - fires after a Many-to-Many field (like db_attributes) changes.

  • -
  • pre_migrate - fires before database migration starts with evennia migrate.

  • -
  • post_migrate - fires after database migration finished.

  • -
  • request_started - sent when HTTP request begins.

  • -
  • request_finished - sent when HTTP request ends.

  • -
  • settings_changed - sent when changing settings due to @override_settings -decorator (only relevant for unit testing)

  • -
  • template_rendered - sent when test system renders http template (only useful for unit tests).

  • -
  • connection_creation - sent when making initial connection to database.

  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Soft-Code.html b/docs/0.9.5/Soft-Code.html deleted file mode 100644 index 3cf28579b6..0000000000 --- a/docs/0.9.5/Soft-Code.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - Soft Code — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Soft Code

-

Softcode is a very simple programming language that was created for in-game development on TinyMUD -derivatives such as MUX, PennMUSH, TinyMUSH, and RhostMUSH. The idea is that by providing a stripped -down, minimalistic language for in-game use, you can allow quick and easy building and game -development to happen without having to learn C/C++. There is an added benefit of not having to have -to hand out shell access to all developers, and permissions can be used to alleviate many security -problems.

-

Writing and installing softcode is done through a MUD client. Thus it is not a formatted language. -Each softcode function is a single line of varying size. Some functions can be a half of a page long -or more which is obviously not very readable nor (easily) maintainable over time.

-
-

Examples of Softcode

-

Here is a simple ‘Hello World!’ command:

-
    @set me=HELLO_WORLD.C:$hello:@pemit %#=Hello World!
-
-
-

Pasting this into a MUX/MUSH and typing ‘hello’ will theoretically yield ‘Hello World!’, assuming -certain flags are not set on your account object.

-

Setting attributes is done via @set. Softcode also allows the use of the ampersand (&) symbol. -This shorter version looks like this:

-
    &HELLO_WORLD.C me=$hello:@pemit %#=Hello World!
-
-
-

Perhaps I want to break the Hello World into an attribute which is retrieved when emitting:

-
    &HELLO_VALUE.D me=Hello World
-    &HELLO_WORLD.C me=$hello:@pemit %#=[v(HELLO_VALUE.D)]
-
-
-

The v() function returns the HELLO_VALUE.D attribute on the object that the command resides -(me, which is yourself in this case). This should yield the same output as the first example.

-

If you are still curious about how Softcode works, take a look at some external resources:

- -
-
-

Problems with Softcode

-

Softcode is excellent at what it was intended for: simple things. It is a great tool for making an -interactive object, a room with ambiance, simple global commands, simple economies and coded -systems. However, once you start to try to write something like a complex combat system or a higher -end economy, you’re likely to find yourself buried under a mountain of functions that span multiple -objects across your entire code.

-

Not to mention, softcode is not an inherently fast language. It is not compiled, it is parsed with -each calling of a function. While MUX and MUSH parsers have jumped light years ahead of where they -once were they can still stutter under the weight of more complex systems if not designed properly.

-
-
-

Changing Times

-

Now that starting text-based games is easy and an option for even the most technically inarticulate, -new projects are a dime a dozen. People are starting new MUDs every day with varying levels of -commitment and ability. Because of this shift from fewer, larger, well-staffed games to a bunch of -small, one or two developer games, some of the benefit of softcode fades.

-

Softcode is great in that it allows a mid to large sized staff all work on the same game without -stepping on one another’s toes. As mentioned before, shell access is not necessary to develop a MUX -or a MUSH. However, now that we are seeing a lot more small, one or two-man shops, the issue of -shell access and stepping on each other’s toes is a lot less.

-
-
-

Our Solution

-

Evennia shuns in-game softcode for on-disk Python modules. Python is a popular, mature and -professional programming language. You code it using the conveniences of modern text editors. -Evennia developers have access to the entire library of Python modules out there in the wild - not -to mention the vast online help resources available. Python code is not bound to one-line functions -on objects but complex systems may be organized neatly into real source code modules, sub-modules, -or even broken out into entire Python packages as desired.

-

So what is not included in Evennia is a MUX/MOO-like online player-coding system. Advanced coding -in Evennia is primarily intended to be done outside the game, in full-fledged Python modules. -Advanced building is best handled by extending Evennia’s command system with your own sophisticated -building commands. We feel that with a small development team you are better off using a -professional source-control system (svn, git, bazaar, mercurial etc) anyway.

-
-
-

Your Solution

-

Adding advanced and flexible building commands to your game is easy and will probably be enough to -satisfy most creative builders. However, if you really, really want to offer online coding, there -is of course nothing stopping you from adding that to Evennia, no matter our recommendations. You -could even re-implement MUX’ softcode in Python should you be very ambitious. The -in-game-python is an optional -pseudo-softcode plugin aimed at developers wanting to script their game from inside it.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Spawner-and-Prototypes.html b/docs/0.9.5/Spawner-and-Prototypes.html deleted file mode 100644 index 9c985585ce..0000000000 --- a/docs/0.9.5/Spawner-and-Prototypes.html +++ /dev/null @@ -1,441 +0,0 @@ - - - - - - - - - Spawner and Prototypes — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Spawner and Prototypes

-

The spawner is a system for defining and creating individual objects from a base template called a -prototype. It is only designed for use with in-game Objects, not any other type of -entity.

-

The normal way to create a custom object in Evennia is to make a Typeclass. If you -haven’t read up on Typeclasses yet, think of them as normal Python classes that save to the database -behind the scenes. Say you wanted to create a “Goblin” enemy. A common way to do this would be to -first create a Mobile typeclass that holds everything common to mobiles in the game, like generic -AI, combat code and various movement methods. A Goblin subclass is then made to inherit from -Mobile. The Goblin class adds stuff unique to goblins, like group-based AI (because goblins are -smarter in a group), the ability to panic, dig for gold etc.

-

But now it’s time to actually start to create some goblins and put them in the world. What if we -wanted those goblins to not all look the same? Maybe we want grey-skinned and green-skinned goblins -or some goblins that can cast spells or which wield different weapons? We could make subclasses of -Goblin, like GreySkinnedGoblin and GoblinWieldingClub. But that seems a bit excessive (and a -lot of Python code for every little thing). Using classes can also become impractical when wanting -to combine them - what if we want a grey-skinned goblin shaman wielding a spear - setting up a web -of classes inheriting each other with multiple inheritance can be tricky.

-

This is what the prototype is for. It is a Python dictionary that describes these per-instance -changes to an object. The prototype also has the advantage of allowing an in-game builder to -customize an object without access to the Python backend. Evennia also allows for saving and -searching prototypes so other builders can find and use (and tweak) them later. Having a library of -interesting prototypes is a good reasource for builders. The OLC system allows for creating, saving, -loading and manipulating prototypes using a menu system.

-

The spawner takes a prototype and uses it to create (spawn) new, custom objects.

-
-

Using the OLC

-

Enter the olc command or @spawn/olc to enter the prototype wizard. This is a menu system for -creating, loading, saving and manipulating prototypes. It’s intended to be used by in-game builders -and will give a better understanding of prototypes in general. Use help on each node of the menu -for more information. Below are further details about how prototypes work and how they are used.

-
-
-

The prototype

-

The prototype dictionary can either be created for you by the OLC (see above), be written manually -in a Python module (and then referenced by the @spawn command/OLC), or created on-the-fly and -manually loaded into the spawner function or @spawn command.

-

The dictionary defines all possible database-properties of an Object. It has a fixed set of allowed -keys. When preparing to store the prototype in the database (or when using the OLC), some -of these keys are mandatory. When just passing a one-time prototype-dict to the spawner the system -is -more lenient and will use defaults for keys not explicitly provided.

-

In dictionary form, a prototype can look something like this:

-
{
-   "prototype_key": "house"
-   "key": "Large house"
-   "typeclass": "typeclasses.rooms.house.House"
- }
-
-
-

If you wanted to load it into the spawner in-game you could just put all on one line:

-
@spawn {"prototype_key="house", "key": "Large house", ...}
-
-
-
-

Note that the prototype dict as given on the command line must be a valid Python structure - -so you need to put quotes around strings etc. For security reasons, a dict inserted from-in game -cannot have any -other advanced Python functionality, such as executable code, lambda etc. If builders are supposed -to be able to use such features, you need to offer them through [$protfuncs](Spawner-and- -Prototypes#protfuncs), embedded runnable functions that you have full control to check and vet -before running.

-
-
-

Prototype keys

-

All keys starting with prototype_ are for book keeping.

-
    -
  • prototype_key - the ‘name’ of the prototype. While this can sometimes be skipped (such as when -defining a prototype in a module or feeding a prototype-dict manually to the spawner function), -it’s good -practice to try to include this. It is used for book-keeping and storing of the prototype so you -can find it later.

  • -
  • prototype_parent - If given, this should be the prototype_key of another prototype stored in -the system or available in a module. This makes this prototype inherit the keys from the -parent and only override what is needed. Give a tuple (parent1, parent2, ...) for multiple -left-right inheritance. If this is not given, a typeclass should usually be defined (below).

  • -
  • prototype_desc - this is optional and used when listing the prototype in in-game listings.

  • -
  • protototype_tags - this is optional and allows for tagging the prototype in order to find it -easier later.

  • -
  • prototype_locks - two lock types are supported: edit and spawn. The first lock restricts -the copying and editing of the prototype when loaded through the OLC. The second determines who -may use the prototype to create new objects.

  • -
-

The remaining keys determine actual aspects of the objects to spawn from this prototype:

-
    -
  • key - the main object identifier. Defaults to “Spawned Object X”, where X is a random -integer.

  • -
  • typeclass - A full python-path (from your gamedir) to the typeclass you want to use. If not -set, the prototype_parent should be -defined, with typeclass defined somewhere in the parent chain. When creating a one-time -prototype -dict just for spawning, one could omit this - settings.BASE_OBJECT_TYPECLASS will be used -instead.

  • -
  • location - this should be a #dbref.

  • -
  • home - a valid #dbref. Defaults to location or settings.DEFAULT_HOME if location does not -exist.

  • -
  • destination - a valid #dbref. Only used by exits.

  • -
  • permissions - list of permission strings, like ["Accounts", "may_use_red_door"]

  • -
  • locks - a lock-string like "edit:all();control:perm(Builder)"

  • -
  • aliases - list of strings for use as aliases

  • -
  • tags - list Tags. These are given as tuples (tag, category, data).

  • -
  • attrs - list of Attributes. These are given as tuples (attrname, value, category, lockstring)

  • -
  • Any other keywords are interpreted as non-category Attributes and their values. -This is -convenient for simple Attributes - use attrs for full control of Attributes.

  • -
-

Deprecated as of Evennia 0.8:

-
    -
  • ndb_<name> - sets the value of a non-persistent attribute ("ndb_" is stripped from the name). -This is simply not useful in a prototype and is deprecated.

  • -
  • exec - This accepts a code snippet or a list of code snippets to run. This should not be used - -use callables or $protfuncs instead (see below).

  • -
-
-
-

Prototype values

-

The prototype supports values of several different types.

-

It can be a hard-coded value:

-
    {"key": "An ugly goblin", ...}
-
-
-
-

It can also be a callable. This callable is called without arguments whenever the prototype is -used to -spawn a new object:

-
    {"key": _get_a_random_goblin_name, ...}
-
-
-
-

By use of Python lambda one can wrap the callable so as to make immediate settings in the -prototype:

-
    {"key": lambda: random.choice(("Urfgar", "Rick the smelly", "Blargh the foul", ...)), ...}
-
-
-
-
-

Protfuncs

-

Finally, the value can be a prototype function (Protfunc). These look like simple function calls -that you embed in strings and that has a $ in front, like

-
    {"key": "$choice(Urfgar, Rick the smelly, Blargh the foul)",
-     "attrs": {"desc": "This is a large $red(and very red) demon. "
-                       "He has $randint(2,5) skulls in a chain around his neck."}
-
-
-

At execution time, the place of the protfunc will be replaced with the result of that protfunc being -called (this is always a string). A protfunc works in much the same way as an -InlineFunc - they are actually -parsed using the same parser - except protfuncs are run every time the prototype is used to spawn a -new object (whereas an inlinefunc is called when a text is returned to the user).

-

Here is how a protfunc is defined (same as an inlinefunc).

-
# this is a silly example, you can just color the text red with |r directly!
-def red(*args, **kwargs):
-   """
-   Usage: $red(<text>)
-   Returns the same text you entered, but red.
-   """
-   if not args or len(args) > 1:
-      raise ValueError("Must have one argument, the text to color red!")
-   return "|r{}|n".format(args[0])
-
-
-
-

Note that we must make sure to validate input and raise ValueError if that fails. Also, it is -not possible to use keywords in the call to the protfunc (so something like $echo(text, align=left) is invalid). The kwargs requred is for internal evennia use and not used at all for -protfuncs (only by inlinefuncs).

-
-

To make this protfunc available to builders in-game, add it to a new module and add the path to that -module to settings.PROT_FUNC_MODULES:

-
# in mygame/server/conf/settings.py
-
-PROT_FUNC_MODULES += ["world.myprotfuncs"]
-
-
-
-

All global callables in your added module will be considered a new protfunc. To avoid this (e.g. -to have helper functions that are not protfuncs on their own), name your function something starting -with _.

-

The default protfuncs available out of the box are defined in evennia/prototypes/profuncs.py. To -override the ones available, just add the same-named function in your own protfunc module.

-

| Protfunc | Description |

-

| $random() | Returns random value in range [0, 1) | -| $randint(start, end) | Returns random value in range [start, end] | -| $left_justify(<text>) | Left-justify text | -| $right_justify(<text>) | Right-justify text to screen width | -| $center_justify(<text>) | Center-justify text to screen width | -| $full_justify(<text>) | Spread text across screen width by adding spaces | -| $protkey(<name>) | Returns value of another key in this prototype (self-reference) | -| $add(<value1>, <value2>) | Returns value1 + value2. Can also be lists, dicts etc | -| $sub(<value1>, <value2>) | Returns value1 - value2 | -| $mult(<value1>, <value2>) | Returns value1 * value2 | -| $div(<value1>, <value2>) | Returns value2 / value1 | -| $toint(<value>) | Returns value converted to integer (or value if not possible) | -| $eval(<code>) | Returns result of literal- -eval of code string. Only simple -python expressions. | -| $obj(<query>) | Returns object #dbref searched globally by key, tag or #dbref. Error if more -than one found.” | -| $objlist(<query>) | Like $obj, except always returns a list of zero, one or more results. | -| $dbref(dbref) | Returns argument if it is formed as a #dbref (e.g. #1234), otherwise error.

-

For developers with access to Python, using protfuncs in prototypes is generally not useful. Passing -real Python functions is a lot more powerful and flexible. Their main use is to allow in-game -builders to -do limited coding/scripting for their prototypes without giving them direct access to raw Python.

-
-
-
-
-

Storing prototypes

-

A prototype can be defined and stored in two ways, either in the database or as a dict in a module.

-
-

Database prototypes

-

Stored as Scripts in the database. These are sometimes referred to as database- -prototypes This is the only way for in-game builders to modify and add prototypes. They have the -advantage of being easily modifiable and sharable between builders but you need to work with them -using in-game tools.

-
-
-

Module-based prototypes

-

These prototypes are defined as dictionaries assigned to global variables in one of the modules -defined in settings.PROTOTYPE_MODULES. They can only be modified from outside the game so they are -are necessarily “read-only” from in-game and cannot be modified (but copies of them could be made -into database-prototypes). These were the only prototypes available before Evennia 0.8. Module based -prototypes can be useful in order for developers to provide read-only “starting” or “base” -prototypes to build from or if they just prefer to work offline in an external code editor.

-

By default mygame/world/prototypes.py is set up for you to add your own prototypes. All global -dicts in this module will be considered by Evennia to be a prototype. You could also tell Evennia -to look for prototypes in more modules if you want:

-
# in mygame/server/conf/settings.py
-
-PROTOTYPE_MODULES += ["world.myownprototypes", "combat.prototypes"]
-
-
-
-
-

Note the += operator in the above example. This will extend the already defined world.prototypes -definition in the settings_default.py file in Evennia. If you would like to completely override the -location of your PROTOTYPE_MODULES then set this to just = without the addition operator.

-
-

Here is an example of a prototype defined in a module:

-
    # in a module Evennia looks at for prototypes,
-    # (like mygame/world/prototypes.py)
-
-    ORC_SHAMAN = {"key":"Orc shaman",
-                  "typeclass": "typeclasses.monsters.Orc",
-                  "weapon": "wooden staff",
-                  "health": 20}
-
-
-
-

Note that in the example above, "ORC_SHAMAN" will become the prototype_key of this prototype. -It’s the only case when prototype_key can be skipped in a prototype. However, if prototype_key -was given explicitly, that would take precedence. This is a legacy behavior and it’s recommended -that you always add prototype_key to be consistent.

-
-
-
-
-

Using @spawn

-

The spawner can be used from inside the game through the Builder-only @spawn command. Assuming the -“goblin” typeclass is available to the system (either as a database-prototype or read from module), -you can spawn a new goblin with

-
@spawn goblin
-
-
-

You can also specify the prototype directly as a valid Python dictionary:

-
@spawn {"prototype_key": "shaman", \
-        "key":"Orc shaman", \
-        "prototype_parent": "goblin", \
-        "weapon": "wooden staff", \
-        "health": 20}
-
-
-
-

Note: The @spawn command is more lenient about the prototype dictionary than shown here. So you -can for example skip the prototype_key if you are just testing a throw-away prototype. A random -hash will be used to please the validation. You could also skip prototype_parent/typeclass - then -the typeclass given by settings.BASE_OBJECT_TYPECLASS will be used.

-
-
-
-

Using evennia.prototypes.spawner()

-

In code you access the spawner mechanism directly via the call

-
    new_objects = evennia.prototypes.spawner.spawn(*prototypes)
-
-
-

All arguments are prototype dictionaries or the unique prototype_keys of prototypes -known to the system (either database- or module-based). The function will return a matching list of -created objects. Example:

-
    obj1, obj2, obj3 = evennia.prototypes.spawner.spawn({"key": "Obj1", "desc": "A test"},
-                                                        {"key": "Obj2", "desc": "Another test"},
-                                                         "GOBLIN_SHAMAN")
-
-
-
-

Hint: Same as when using @spawn, when spawning from a one-time prototype dict like this, you can -skip otherwise required keys, like prototype_key or typeclass/prototype_parent. Defaults will -be used.

-
-

Note that no location will be set automatically when using evennia.prototypes.spawner.spawn(), -you -have to specify location explicitly in the prototype dict.

-

If the prototypes you supply are using prototype_parent keywords, the spawner will read prototypes -from modules -in settings.PROTOTYPE_MODULES as well as those saved to the database to determine the body of -available parents. The spawn command takes many optional keywords, you can find its definition in -the api docs.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Start-Stop-Reload.html b/docs/0.9.5/Start-Stop-Reload.html deleted file mode 100644 index aab434f7dd..0000000000 --- a/docs/0.9.5/Start-Stop-Reload.html +++ /dev/null @@ -1,315 +0,0 @@ - - - - - - - - - Start Stop Reload — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Start Stop Reload

-

You control Evennia from your game folder (we refer to it as mygame/ here), using the evennia -program. If the evennia program is not available on the command line you must first install -Evennia as described in the Getting Started page.

-
-

Hint: If you ever try the evennia command and get an error complaining that the command is not -available, make sure your virtualenv is active.

-
-

Below are described the various management options. Run

-
evennia -h
-
-
-

to give you a brief help and

-
evennia menu
-
-
-

to give you a menu with options.

-
-

Starting Evennia

-

Evennia consists of two components, the Evennia Server and Portal. Briefly, -the Server is what is running the mud. It handles all game-specific things but doesn’t care -exactly how players connect, only that they have. The Portal is a gateway to which players -connect. It knows everything about telnet, ssh, webclient protocols etc but very little about the -game. Both are required for a functioning mud.

-
 evennia start
-
-
-

The above command will start the Portal, which in turn will boot up the Server. The command will -print a summary of the process and unless there is an error you will see no further output. Both -components will instead log to log files in mygame/server/logs/. For convenience you can follow -those logs directly in your terminal by attaching -l to commands:

-
 evennia -l
-
-
-

Will start following the logs of an already running server. When starting Evennia you can also do

-
 evennia start -l
-
-
-
-

To stop viewing the log files, press Ctrl-C.

-
-
-
-

Foreground mode

-

Normally, Evennia runs as a ‘daemon’, in the background. If you want you can start either of the -processes (but not both) as foreground processes in interactive mode. This means they will log -directly to the terminal (rather than to log files that we then echo to the terminal) and you can -kill the process (not just the log-file view) with Ctrl-C.

-
evennia istart
-
-
-

will start/restart the Server in interactive mode. This is required if you want to run a -debugger. Next time you reload the server, it will return to normal mode.

-
evennia ipstart
-
-
-

will start the Portal in interactive mode. This is usually only necessary if you want to run -Evennia under the control of some other type of process.

-
-
-

Reloading

-

The act of reloading means the Portal will tell the Server to shut down and then boot it back up -again. Everyone will get a message and the game will be briefly paused for all accounts as the -server -reboots. Since they are connected to the Portal, their connections are not lost.

-

Reloading is as close to a “warm reboot” you can get. It reinitializes all code of Evennia, but -doesn’t kill “persistent” Scripts. It also calls at_server_reload() hooks on all -objects so you -can save eventual temporary properties you want.

-

From in-game the @reload command is used. You can also reload the server from outside the game:

-
 evennia reload
-
-
-

Sometimes reloading from “the outside” is necessary in case you have added some sort of bug that -blocks in-game input.

-
-
-

Resetting

-

Resetting is the equivalent of a “cold reboot” - the Server will shut down and then restarted -again, but will behave as if it was fully shut down. As opposed to a “real” shutdown, no accounts -will be disconnected during a -reset. A reset will however purge all non-persistent scripts and will call at_server_shutdown() -hooks. It can be a good way to clean unsafe scripts during development, for example.

-

From in-game the @reset command is used. From the terminal:

-
evennia reset
-
-
-
-
-

Rebooting

-

This will shut down both Server and Portal, which means all connected players will lose their -connection. It can only be initiated from the terminal:

-
evennia reboot
-
-
-

This is identical to doing these two commands:

-
 evennia stop
- evennia start
-
-
-
-
-

Shutting down

-

A full shutdown closes Evennia completely, both Server and Portal. All accounts will be booted and -systems saved and turned off cleanly.

-

From inside the game you initiate a shutdown with the @shutdown command. From command line you do

-
 evennia stop
-
-
-

You will see messages of both Server and Portal closing down. All accounts will see the shutdown -message and then be disconnected. The same effect happens if you press Ctrl+C while the server -runs in interactive mode.

-
-
-

Status and info

-

To check basic Evennia settings, such as which ports and services are active, this will repeat the -initial return given when starting the server:

-
evennia info
-
-
-

You can also get a briefer run-status from both components with this command

-
evennia status
-
-
-

This can be useful for automating checks to make sure the game is running and is responding.

-
-
-

Killing (Linux/Mac only)

-

In the extreme case that neither of the server processes locks up and does not respond to commands, -you can send them kill-signals to force them to shut down. To kill only the Server:

-
evennia skill
-
-
-

To kill both Server and Portal:

-
evennia kill
-
-
-

Note that this functionality is not supported on Windows.

-
-
-

Django options

-

The evennia program will also pass-through options used by the django-admin. These operate on -the database in various ways.

-
 evennia migrate # migrate the database
- evennia shell   # launch an interactive, django-aware python shell
- evennia dbshell # launch database shell
-
-
-

For (many) more options, see the django-admin -docs.

-
-
-

Advanced handling of Evennia processes

-

If you should need to manually manage Evennia’s processors (or view them in a task manager program -such as Linux’ top or the more advanced htop), you will find the following processes to be -related to Evennia:

-
    -
  • 1 x twistd ... evennia/server/portal/portal.py - this is the Portal process.

  • -
  • 3 x twistd ... server.py - One of these processes manages Evennia’s Server component, the main -game. The other processes (with the same name but different process id) handle’s Evennia’s -internal web server threads. You can look at mygame/server/server.pid to determine which is the -main process.

  • -
-
-

Syntax errors during live development

-

During development, you will usually modify code and then reload the server to see your changes. -This is done by Evennia re-importing your custom modules from disk. Usually bugs in a module will -just have you see a traceback in the game, in the log or on the command line. For some really -serious syntax errors though, your module might not even be recognized as valid Python. Evennia may -then fail to restart correctly.

-

From inside the game you see a text about the Server restarting followed by an ever growing list of -“…”. Usually this only lasts a very short time (up to a few seconds). If it seems to go on, it -means the Portal is still running (you are still connected to the game) but the Server-component of -Evennia failed to restart (that is, it remains in a shut-down state). Look at your log files or -terminal to see what the problem is - you will usually see a clear traceback showing what went -wrong.

-

Fix your bug then run

-
evennia start
-
-
-

Assuming the bug was fixed, this will start the Server manually (while not restarting the Portal). -In-game you should now get the message that the Server has successfully restarted.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Static-In-Game-Map.html b/docs/0.9.5/Static-In-Game-Map.html deleted file mode 100644 index cbee85d664..0000000000 --- a/docs/0.9.5/Static-In-Game-Map.html +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - - - - Static In Game Map — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Static In Game Map

-
-

Introduction

-

This tutorial describes the creation of an in-game map display based on a pre-drawn map. It also -details how to use the Batch code processor for advanced building. There is -also the Dynamic in-game map tutorial that works in the opposite direction, -by generating a map from an existing grid of rooms.

-

Evennia does not require its rooms to be positioned in a “logical” way. Your exits could be named -anything. You could make an exit “west” that leads to a room described to be in the far north. You -could have rooms inside one another, exits leading back to the same room or describing spatial -geometries impossible in the real world.

-

That said, most games do organize their rooms in a logical fashion, if nothing else to retain the -sanity of their players. And when they do, the game becomes possible to map. This tutorial will give -an example of a simple but flexible in-game map system to further help player’s to navigate. We will

-

To simplify development and error-checking we’ll break down the work into bite-size chunks, each -building on what came before. For this we’ll make extensive use of the Batch code processor, so you may want to familiarize yourself with that.

-
    -
  1. Planning the map - Here we’ll come up with a small example map to use for the rest of the -tutorial.

  2. -
  3. Making a map object - This will showcase how to make a static in-game “map” object a -Character could pick up and look at.

  4. -
  5. Building the map areas - Here we’ll actually create the small example area according to the -map we designed before.

  6. -
  7. Map code - This will link the map to the location so our output looks something like this:

    -
    crossroads(#3)
    -↑╚∞╝↑
    -≈↑│↑∩  The merger of two roads. To the north looms a mighty castle.
    -O─O─O  To the south, the glow of a campfire can be seen. To the east lie
    -≈↑│↑∩  the vast mountains and to the west is heard the waves of the sea.
    -↑▲O▲↑
    -
    -Exits: north(#8), east(#9), south(#10), west(#11)
    -
    -
    -
  8. -
-

We will henceforth assume your game folder is name named mygame and that you haven’t modified the -default commands. We will also not be using Colors for our map since they -don’t show in the documentation wiki.

-
-
-

Planning the Map

-

Let’s begin with the fun part! Maps in MUDs come in many different shapes and sizes. Some appear as just boxes connected by lines. Others have complex graphics that are -external to the game itself.

-

Our map will be in-game text but that doesn’t mean we’re restricted to the normal alphabet! If -you’ve ever selected the Wingdings font in Microsoft Word -you will know there are a multitude of other characters around to use. When creating your game with -Evennia you have access to the UTF-8 character encoding which -put at your disposal thousands of letters, number and geometric shapes.

-

For this exercise, we’ve copy-and-pasted from the pallet of special characters used over at Dwarf -Fortress to create what is hopefully a -pleasing and easy to understood landscape:

-
≈≈↑↑↑↑↑∩∩
-≈≈↑╔═╗↑∩∩   Places the account can visit are indicated by "O".
-≈≈↑║O║↑∩∩   Up the top is a castle visitable by the account.
-≈≈↑╚∞╝↑∩∩   To the right is a cottage and to the left the beach.
-≈≈≈↑│↑∩∩∩   And down the bottom is a camp site with tents.
-≈≈O─O─O⌂∩   In the center is the starting location, a crossroads
-≈≈≈↑│↑∩∩∩   which connect the four other areas.
-≈≈↑▲O▲↑∩∩
-≈≈↑↑▲↑↑∩∩
-≈≈↑↑↑↑↑∩∩
-
-
-

There are many considerations when making a game map depending on the play style and requirements -you intend to implement. Here we will display a 5x5 character map of the area surrounding the -account. This means making sure to account for 2 characters around every visitable location. Good -planning at this stage can solve many problems before they happen.

-
-
-

Creating a Map Object

-

In this section we will try to create an actual “map” object that an account can pick up and look -at.

-

Evennia offers a range of default commands for creating objects and rooms -in-game. While readily accessible, these commands are made to do very -specific, restricted things and will thus not offer as much flexibility to experiment (for an -advanced exception see in-line functions). Additionally, entering long -descriptions and properties over and over in the game client can become tedious; especially when -testing and you may want to delete and recreate things over and over.

-

To overcome this, Evennia offers batch processors that work as input-files -created out-of-game. In this tutorial we’ll be using the more powerful of the two available batch -processors, the Batch Code Processor , called with the @batchcode command. -This is a very powerful tool. It allows you to craft Python files to act as blueprints of your -entire game world. These files have access to use Evennia’s Python API directly. Batchcode allows -for easy editing and creation in whatever text editor you prefer, avoiding having to manually build -the world line-by-line inside the game.

-
-

Important warning: @batchcode’s power is only rivaled by the @py command. Batchcode is so -powerful it should be reserved only for the superuser. Think carefully -before you let others (such as Developer- level staff) run @batchcode on their own - make sure -you are okay with them running arbitrary Python code on your server.

-
-

While a simple example, the map object it serves as good way to try out @batchcode. Go to -mygame/world and create a new file there named batchcode_map.py:

-
# mygame/world/batchcode_map.py
-
-from evennia import create_object
-from evennia import DefaultObject
-
-# We use the create_object function to call into existence a
-# DefaultObject named "Map" wherever you are standing.
-
-map = create_object(DefaultObject, key="Map", location=caller.location)
-
-# We then access its description directly to make it our map.
-
-map.db.desc = """
-≈≈↑↑↑↑↑∩∩
-≈≈↑╔═╗↑∩∩
-≈≈↑║O║↑∩∩
-≈≈↑╚∞╝↑∩∩
-≈≈≈↑│↑∩∩∩
-≈≈O─O─O⌂∩
-≈≈≈↑│↑∩∩∩
-≈≈↑▲O▲↑∩∩
-≈≈↑↑▲↑↑∩∩
-≈≈↑↑↑↑↑∩∩
-"""
-
-# This message lets us know our map was created successfully.
-caller.msg("A map appears out of thin air and falls to the ground.")
-
-
-

Log into your game project as the superuser and run the command

-
@batchcode batchcode_map
-
-
-

This will load your batchcode_map.py file and execute the code (Evennia will look in your world/ -folder automatically so you don’t need to specify it).

-

A new map object should have appeared on the ground. You can view the map by using look map. Let’s -take it with the get map command. We’ll need it in case we get lost!

-
-
-

Building the map areas

-

We’ve just used batchcode to create an object useful for our adventures. But the locations on that -map does not actually exist yet - we’re all mapped up with nowhere to go! Let’s use batchcode to -build a game area based on our map. We have five areas outlined: a castle, a cottage, a campsite, a -coastal beach and the crossroads which connects them. Create a new batchcode file for this in -mygame/world, named batchcode_world.py.

-
# mygame/world/batchcode_world.py
-
-from evennia import create_object, search_object
-from typeclasses import rooms, exits
-
-# We begin by creating our rooms so we can detail them later.
-
-centre = create_object(rooms.Room, key="crossroads")
-north = create_object(rooms.Room, key="castle")
-east = create_object(rooms.Room, key="cottage")
-south = create_object(rooms.Room, key="camp")
-west = create_object(rooms.Room, key="coast")
-
-# This is where we set up the cross roads.
-# The rooms description is what we see with the 'look' command.
-
-centre.db.desc = """
-The merger of two roads. A single lamp post dimly illuminates the lonely crossroads.
-To the north looms a mighty castle. To the south the glow of a campfire can be seen.
-To the east lie a wall of mountains and to the west the dull roar of the open sea.
-"""
-
-# Here we are creating exits from the centre "crossroads" location to
-# destinations to the north, east, south, and west. We will be able
-# to use the exit by typing it's key e.g. "north" or an alias e.g. "n".
-
-centre_north = create_object(exits.Exit, key="north",
-                            aliases=["n"], location=centre, destination=north)
-centre_east = create_object(exits.Exit, key="east",
-                            aliases=["e"], location=centre, destination=east)
-centre_south = create_object(exits.Exit, key="south",
-                            aliases=["s"], location=centre, destination=south)
-centre_west = create_object(exits.Exit, key="west",
-                            aliases=["w"], location=centre, destination=west)
-
-# Now we repeat this for the other rooms we'll be implementing.
-# This is where we set up the northern castle.
-
-north.db.desc = "An impressive castle surrounds you. " \
-                "There might be a princess in one of these towers."
-north_south = create_object(exits.Exit, key="south",
-                            aliases=["s"], location=north, destination=centre)
-
-# This is where we set up the eastern cottage.
-
-east.db.desc = "A cosy cottage nestled among mountains " \
-               "stretching east as far as the eye can see."
-east_west = create_object(exits.Exit, key="west",
-                            aliases=["w"], location=east, destination=centre)
-
-# This is where we set up the southern camp.
-
-south.db.desc = "Surrounding a clearing are a number of " \
-                "tribal tents and at their centre a roaring fire."
-south_north = create_object(exits.Exit, key="north",
-                            aliases=["n"], location=south, destination=centre)
-
-# This is where we set up the western coast.
-
-west.db.desc = "The dark forest halts to a sandy beach. " \
-               "The sound of crashing waves calms the soul."
-west_east = create_object(exits.Exit, key="east",
-                            aliases=["e"], location=west, destination=centre)
-
-# Lastly, lets make an entrance to our world from the default Limbo room.
-
-limbo = search_object('Limbo')[0]
-limbo_exit = create_object(exits.Exit, key="enter world",
-                            aliases=["enter"], location=limbo, destination=centre)
-
-
-

Apply this new batch code with @batchcode batchcode_world. If there are no errors in the code we -now have a nice mini-world to explore. Remember that if you get lost you can look at the map we -created!

-
-
-

In-game minimap

-

Now we have a landscape and matching map, but what we really want is a mini-map that displays -whenever we move to a room or use the look command.

-

We could manually enter a part of the map into the description of every room like we did our map -object description. But some MUDs have tens of thousands of rooms! Besides, if we ever changed our -map we would have to potentially alter a lot of those room descriptions manually to match the -change. So instead we will make one central module to hold our map. Rooms will reference this -central location on creation and the map changes will thus come into effect when next running our -batchcode.

-

To make our mini-map we need to be able to cut our full map into parts. To do this we need to put it -in a format which allows us to do that easily. Luckily, python allows us to treat strings as lists -of characters allowing us to pick out the characters we need.

-

mygame/world/map_module.py

-
# We place our map into a sting here.
-world_map = """\
-≈≈↑↑↑↑↑∩∩
-≈≈↑╔═╗↑∩∩
-≈≈↑║O║↑∩∩
-≈≈↑╚∞╝↑∩∩
-≈≈≈↑│↑∩∩∩
-≈≈O─O─O⌂∩
-≈≈≈↑│↑∩∩∩
-≈≈↑▲O▲↑∩∩
-≈≈↑↑▲↑↑∩∩
-≈≈↑↑↑↑↑∩∩
-"""
-
-# This turns our map string into a list of rows. Because python
-# allows us to treat strings as a list of characters, we can access
-# those characters with world_map[5][5] where world_map[row][column].
-world_map = world_map.split('\n')
-
-def return_map():
-    """
-    This function returns the whole map
-    """
-    map = ""
-
-    #For each row in our map, add it to map
-    for valuey in world_map:
-        map += valuey
-        map += "\n"
-
-    return map
-
-def return_minimap(x, y, radius = 2):
-    """
-    This function returns only part of the map.
-    Returning all chars in a 2 char radius from (x,y)
-    """
-    map = ""
-
-    #For each row we need, add the characters we need.
-    for valuey in world_map[y-radius:y+radius+1]:         for valuex in valuey[x-radius:x+radius+1]:
-            map += valuex
-        map += "\n"
-
-    return map
-
-
-

With our map_module set up, let’s replace our hardcoded map in mygame/world/batchcode_map.py with -a reference to our map module. Make sure to import our map_module!

-
# mygame/world/batchcode_map.py
-
-from evennia import create_object
-from evennia import DefaultObject
-from world import map_module
-
-map = create_object(DefaultObject, key="Map", location=caller.location)
-
-map.db.desc = map_module.return_map()
-
-caller.msg("A map appears out of thin air and falls to the ground.")
-
-
-

Log into Evennia as the superuser and run this batchcode. If everything worked our new map should -look exactly the same as the old map - you can use @delete to delete the old one (use a number to -pick which to delete).

-

Now, lets turn our attention towards our game’s rooms. Let’s use the return_minimap method we -created above in order to include a minimap in our room descriptions. This is a little more -complicated.

-

By itself we would have to settle for either the map being above the description with -room.db.desc = map_string + description_string, or the map going below by reversing their order. -Both options are rather unsatisfactory - we would like to have the map next to the text! For this -solution we’ll explore the utilities that ship with Evennia. Tucked away in evennia\evennia\utils -is a little module called EvTable . This is an advanced ASCII table -creator for you to utilize in your game. We’ll use it by creating a basic table with 1 row and two -columns (one for our map and one for our text) whilst also hiding the borders. Open the batchfile -again

-
# mygame\world\batchcode_world.py
-
-# Add to imports
-from evennia.utils import evtable
-from world import map_module
-
-# [...]
-
-# Replace the descriptions with the below code.
-
-# The cross roads.
-# We pass what we want in our table and EvTable does the rest.
-# Passing two arguments will create two columns but we could add more.
-# We also specify no border.
-centre.db.desc = evtable.EvTable(map_module.return_minimap(4,5),
-                 "The merger of two roads. A single lamp post dimly " \
-                 "illuminates the lonely crossroads. To the north " \
-                 "looms a mighty castle. To the south the glow of " \
-                 "a campfire can be seen. To the east lie a wall of " \
-                 "mountains and to the west the dull roar of the open sea.",
-                 border=None)
-# EvTable allows formatting individual columns and cells. We use that here
-# to set a maximum width for our description, but letting the map fill
-# whatever space it needs.
-centre.db.desc.reformat_column(1, width=70)
-
-# [...]
-
-# The northern castle.
-north.db.desc = evtable.EvTable(map_module.return_minimap(4,2),
-                "An impressive castle surrounds you. There might be " \
-                "a princess in one of these towers.",
-                border=None)
-north.db.desc.reformat_column(1, width=70)
-
-# [...]
-
-# The eastern cottage.
-east.db.desc = evtable.EvTable(map_module.return_minimap(6,5),
-               "A cosy cottage nestled among mountains stretching " \
-               "east as far as the eye can see.",
-               border=None)
-east.db.desc.reformat_column(1, width=70)
-
-# [...]
-
-# The southern camp.
-south.db.desc = evtable.EvTable(map_module.return_minimap(4,7),
-                "Surrounding a clearing are a number of tribal tents " \
-                "and at their centre a roaring fire.",
-                border=None)
-south.db.desc.reformat_column(1, width=70)
-
-# [...]
-
-# The western coast.
-west.db.desc = evtable.EvTable(map_module.return_minimap(2,5),
-               "The dark forest halts to a sandy beach. The sound of " \
-               "crashing waves calms the soul.",
-               border=None)
-west.db.desc.reformat_column(1, width=70)
-
-
-

Before we run our new batchcode, if you are anything like me you would have something like 100 maps -lying around and 3-4 different versions of our rooms extending from limbo. Let’s wipe it all and -start with a clean slate. In Command Prompt you can run evennia flush to clear the database and -start anew. It won’t reset dbref values however, so if you are at #100 it will start from there. -Alternatively you can navigate to mygame/server and delete the evennia.db3 file. Now in Command -Prompt use evennia migrate to have a completely freshly made database.

-

Log in to evennia and run @batchcode batchcode_world and you’ll have a little world to explore.

-
-
-

Conclusions

-

You should now have a mapped little world and a basic understanding of batchcode, EvTable and how -easily new game defining features can be added to Evennia.

-

You can easily build from this tutorial by expanding the map and creating more rooms to explore. Why -not add more features to your game by trying other tutorials: Add weather to your world, -fill your world with NPC’s or implement a combat system.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tags.html b/docs/0.9.5/Tags.html deleted file mode 100644 index 9a8eed3179..0000000000 --- a/docs/0.9.5/Tags.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - - - Tags — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tags

-

A common task of a game designer is to organize and find groups of objects and do operations on -them. A classic example is to have a weather script affect all “outside” rooms. Another would be for -a player casting a magic spell that affects every location “in the dungeon”, but not those -“outside”. Another would be to quickly find everyone joined with a particular guild or everyone -currently dead.

-

Tags are short text labels that you attach to objects so as to easily be able to retrieve and -group them. An Evennia entity can be tagged with any number of Tags. On the database side, Tag -entities are shared between all objects with that tag. This makes them very efficient but also -fundamentally different from Attributes, each of which always belongs to one single -object.

-

In Evennia, Tags are technically also used to implement Aliases (alternative names for objects) -and Permissions (simple strings for Locks to check for).

-
-

Properties of Tags (and Aliases and Permissions)

-

Tags are unique per object model. This means that for each object model (Objects, Scripts, -Msgs, etc.) there is only ever one Tag object with a given key and category.

-
-

Not specifying a category (default) gives the tag a category of None, which is also considered a -unique key + category combination.

-
-

When Tags are assigned to game entities, these entities are actually sharing the same Tag. This -means that Tags are not suitable for storing information about a single object - use an -Attribute for this instead. Tags are a lot more limited than Attributes but this also -makes them very quick to lookup in the database - this is the whole point.

-

Tags have the following properties, stored in the database:

-
    -
  • key - the name of the Tag. This is the main property to search for when looking up a Tag.

  • -
  • category - this category allows for retrieving only specific subsets of tags used for -different purposes. You could have one category of tags for “zones”, another for “outdoor -locations”, for example. If not given, the category will be None, which is also considered a -separate, default, category.

  • -
  • data - this is an optional text field with information about the tag. Remember that Tags are -shared between entities, so this field cannot hold any object-specific information. Usually it would -be used to hold info about the group of entities the Tag is tagging - possibly used for contextual -help like a tool tip. It is not used by default.

  • -
-

There are also two special properties. These should usually not need to be changed or set, it is -used internally by Evennia to implement various other uses it makes of the Tag object:

-
    -
  • model - this holds a natural-key description of the model object that this tag deals with, -on the form application.modelclass, for example objects.objectdb. It used by the TagHandler of -each entity type for correctly storing the data behind the scenes.

  • -
  • tagtype - this is a “top-level category” of sorts for the inbuilt children of Tags, namely -Aliases and Permissions. The Taghandlers using this special field are especially intended to -free up the category property for any use you desire.

  • -
-
-
-

Adding/Removing Tags

-

You can tag any typeclassed object, namely Objects, Accounts, -Scripts and Channels. General tags are added by the Taghandler. The -tag handler is accessed as a property tags on the relevant entity:

-
     mychair.tags.add("furniture")
-     mychair.tags.add("furniture", category="luxurious")
-     myroom.tags.add("dungeon#01")
-     myscript.tags.add("weather", category="climate")
-     myaccount.tags.add("guestaccount")
-
-     mychair.tags.all()  # returns a list of Tags
-     mychair.tags.remove("furniture")
-     mychair.tags.clear()
-
-
-

Adding a new tag will either create a new Tag or re-use an already existing one. Note that there are -two “furniture” tags, one with a None category, and one with the “luxurious” category.

-

When using remove, the Tag is not deleted but are just disconnected from the tagged object. This -makes for very quick operations. The clear method removes (disconnects) all Tags from the object. -You can also use the default @tag command:

-
 @tag mychair = furniture
-
-
-

This tags the chair with a ‘furniture’ Tag (the one with a None category).

-
-
-

Searching for objects with a given tag

-

Usually tags are used as a quick way to find tagged database entities. You can retrieve all objects -with a given Tag like this in code:

-
     import evennia
-     
-     # all methods return Querysets
-
-     # search for objects
-     objs = evennia.search_tag("furniture")
-     objs2 = evennia.search_tag("furniture", category="luxurious")
-     dungeon = evennia.search_tag("dungeon#01")
-     forest_rooms = evennia.search_tag(category="forest")
-     forest_meadows = evennia.search_tag("meadow", category="forest")
-     magic_meadows = evennia.search_tag("meadow", category="magical")
-
-     # search for scripts
-     weather = evennia.search_tag_script("weather")
-     climates = evennia.search_tag_script(category="climate")
-
-     # search for accounts
-     accounts = evennia.search_tag_account("guestaccount")
-
-
-
-

Note that searching for just “furniture” will only return the objects tagged with the “furniture” -tag that -has a category of None. We must explicitly give the category to get the “luxurious” furniture.

-
-

Using any of the search_tag variants will all return Django -Querysets, including if you only have -one match. You can treat querysets as lists and iterate over them, or continue building search -queries with them.

-

Remember when searching that not setting a category means setting it to None - this does not -mean that category is undefined, rather None is considered the default, unnamed category.

-
import evennia
-
-myobj1.tags.add("foo")  # implies category=None
-myobj2.tags.add("foo", category="bar")
-
-# this returns a queryset with *only* myobj1
-objs = evennia.search_tag("foo")
-
-# these return a queryset with *only* myobj2
-objs = evennia.search_tag("foo", category="bar")
-# or
-objs = evennia.search_tag(category="bar")
-
-
-
-

There is also an in-game command that deals with assigning and using (Object-) tags:

-
 @tag/search furniture
-
-
-
-
-

Using Aliases and Permissions

-

Aliases and Permissions are implemented using normal TagHandlers that simply save Tags with a -different tagtype. These handlers are named aliases and permissions on all Objects. They are -used in the same way as Tags above:

-
    boy.aliases.add("rascal")
-    boy.permissions.add("Builders")
-    boy.permissions.remove("Builders")
-
-    all_aliases = boy.aliases.all()
-
-
-

and so on. Similarly to how @tag works in-game, there is also the @perm command for assigning -permissions and @alias command for aliases.

-
-
-

Assorted notes

-

Generally, tags are enough on their own for grouping objects. Having no tag category is perfectly -fine and the normal operation. Simply adding a new Tag for grouping objects is often better than -making a new category. So think hard before deciding you really need to categorize your Tags.

-

That said, tag categories can be useful if you build some game system that uses tags. You can then -use tag categories to make sure to separate tags created with this system from any other tags -created elsewhere. You can then supply custom search methods that only find objects tagged with -tags of that category. An example of this -is found in the Zone tutorial.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Text-Encodings.html b/docs/0.9.5/Text-Encodings.html deleted file mode 100644 index 8262d90cb3..0000000000 --- a/docs/0.9.5/Text-Encodings.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - Text Encodings — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Text Encodings

-

Evennia is a text-based game server. This makes it important to understand how -it actually deals with data in the form of text.

-

Text byte encodings describe how a string of text is actually stored in the -computer - that is, the particular sequence of bytes used to represent the -letters of your particular alphabet. A common encoding used in English-speaking -languages is the ASCII encoding. This describes the letters in the English -alphabet (Aa-Zz) as well as a bunch of special characters. For describing other -character sets (such as that of other languages with other letters than -English), sets with names such as Latin-1, ISO-8859-3 and ARMSCII-8 -are used. There are hundreds of different byte encodings in use around the -world.

-

A string of letters in a byte encoding is represented with the bytes type. -In contrast to the byte encoding is the unicode representation. In Python -this is the str type. The unicode is an internationally agreed-upon table -describing essentially all available letters you could ever want to print. -Everything from English to Chinese alphabets and all in between. So what -Evennia (as well as Python and Django) does is to store everything in Unicode -internally, but then converts the data to one of the encodings whenever -outputting data to the user.

-

An easy memory aid is that bytes are what are sent over the network wire. At -all other times, str (unicode) is used. This means that we must convert -between the two at the points where we send/receive network data.

-

The problem is that when receiving a string of bytes over the network it’s -impossible for Evennia to guess which encoding was used - it’s just a bunch of -bytes! Evennia must know the encoding in order to convert back and from the -correct unicode representation.

-
-

How to customize encodings

-

As long as you stick to the standard ASCII character set (which means the -normal English characters, basically) you should not have to worry much -about this section.

-

If you want to build your game in another language however, or expect your -users to want to use special characters not in ASCII, you need to consider -which encodings you want to support.

-

As mentioned, there are many, many byte-encodings used around the world. It -should be clear at this point that Evennia can’t guess but has to assume or -somehow be told which encoding you want to use to communicate with the server. -Basically the encoding used by your client must be the same encoding used by -the server. This can be customized in two complementary ways.

-
    -
  1. Point users to the default @encoding command or the @options command. -This allows them to themselves set which encoding they (and their client of -choice) uses. Whereas data will remain stored as unicode strings internally in -Evennia, all data received from and sent to this particular player will be -converted to the given format before transmitting.

  2. -
  3. As a back-up, in case the user-set encoding translation is erroneous or -fails in some other way, Evennia will fall back to trying with the names -defined in the settings variable ENCODINGS. This is a list of encoding -names Evennia will try, in order, before giving up and giving an encoding -error message.

  4. -
-

Note that having to try several different encodings every input/output adds -unneccesary overhead. Try to guess the most common encodings you players will -use and make sure these are tried first. The International UTF-8 encoding is -what Evennia assumes by default (and also what Python/Django use normally). See -the Wikipedia article here for more help.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/TextTags.html b/docs/0.9.5/TextTags.html deleted file mode 100644 index f64e0c6bce..0000000000 --- a/docs/0.9.5/TextTags.html +++ /dev/null @@ -1,449 +0,0 @@ - - - - - - - - - TextTags — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

TextTags

-

This documentation details the various text tags supported by Evennia, namely colours, command -links and inline functions.

-

There is also an Understanding Color Tags tutorial which expands on the -use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.

-
-

Coloured text

-

Note that the Documentation does not display colour the way it would look on the screen.

-

Color can be a very useful tool for your game. It can be used to increase readability and make your -game more appealing visually.

-

Remember however that, with the exception of the webclient, you generally don’t control the client -used to connect to the game. There is, for example, one special tag meaning “yellow”. But exactly -which hue of yellow is actually displayed on the user’s screen depends on the settings of their -particular mud client. They could even swap the colours around or turn them off altogether if so -desired. Some clients don’t even support color - text games are also played with special reading -equipment by people who are blind or have otherwise diminished eyesight.

-

So a good rule of thumb is to use colour to enhance your game but don’t rely on it to display -critical information. If you are coding the game, you can add functionality to let users disable -colours as they please, as described here.

-

To see which colours your client support, use the default @color command. This will list all -available colours for ANSI and Xterm256 along with the codes you use for them. You can find a list -of all the parsed ANSI-colour codes in evennia/utils/ansi.py.

-
-

ANSI colours

-

Evennia supports the ANSI standard for text. This is by far the most supported MUD-color standard, -available in all but the most ancient mud clients. The ANSI colours are red, green, -yellow, blue, magenta, cyan, white and black. They are abbreviated by their -first letter except for black which is abbreviated with the letter x. In ANSI there are “bright” -and “normal” (darker) versions of each color, adding up to a total of 16 colours to use for -foreground text. There are also 8 “background” colours. These have no bright alternative in ANSI -(but Evennia uses the Xterm256 extension behind the scenes to offer -them anyway).

-

To colour your text you put special tags in it. Evennia will parse these and convert them to the -correct markup for the client used. If the user’s client/console/display supports ANSI colour, they -will see the text in the specified colour, otherwise the tags will be stripped (uncolored text). -This works also for non-terminal clients, such as the webclient. For the webclient, Evennia will -translate the codes to HTML RGB colors.

-

Here is an example of the tags in action:

-
 |rThis text is bright red.|n This is normal text.
- |RThis is a dark red text.|n This is normal text.
- |[rThis text has red background.|n This is normal text.
- |b|[yThis is bright blue text on yellow background.|n This is normal text.
-
-
-
    -
  • |n - this tag will turn off all color formatting, including background colors.

  • -
  • |#- markup marks the start of foreground color. The case defines if the text is “bright” or -“normal”. So |g is a bright green and |G is “normal” (darker) green.

  • -
  • |[# is used to add a background colour to the text. The case again specifies if it is “bright” -or “normal”, so |[c starts a bright cyan background and |[C a darker cyan background.

  • -
  • |!# is used to add foreground color without any enforced brightness/normal information. -These are normal-intensity and are thus always given as uppercase, such as -|!R for red. The difference between e.g. |!R and |R is that -|!R will “inherit” the brightness setting from previously set color tags, whereas |R will -always reset to the normal-intensity red. The |# format contains an implicit |h/|H tag in it: -disabling highlighting when switching to a normal color, and enabling it for bright ones. So |btest |!Rtest2 will result in a bright red test2 since the brightness setting from |b “bleeds over”. -You could use this to for example quickly switch the intensity of a multitude of color tags. There -is no background-color equivalent to |! style tags.

  • -
  • |h is used to make any following foreground ANSI colors bright (it has no effect on Xterm -colors). This is only relevant to use with |! type tags and will be valid until the next |n, -|H or normal (upper-case) |# tag. This tag will never affect background colors, those have to be -set bright/normal explicitly. Technically, |h|!G is identical to |g.

  • -
  • |H negates the effects |h and returns all ANSI foreground colors (|! and | types) to -‘normal’ intensity. It has no effect on background and Xterm colors.

  • -
-
-

Note: The ANSI standard does not actually support bright backgrounds like |[r - the standard -only supports “normal” intensity backgrounds. To get around this Evennia instead implements these -as Xterm256 colours behind the scenes. If the client does not support -Xterm256 the ANSI colors will be used instead and there will be no visible difference between using -upper- and lower-case background tags.

-
-

If you want to display an ANSI marker as output text (without having any effect), you need to escape -it by preceding its | with another |:

-
say The ||r ANSI marker changes text color to bright red.
-
-
-

This will output the raw |r without any color change. This can also be necessary if you are doing -ansi art that uses | with a letter directly following it.

-

Use the command

-
@color ansi
-
-
-

to get a list of all supported ANSI colours and the tags used to produce them.

-

A few additional ANSI codes are supported:

-
    -
  • |/ A line break. You cannot put the normal Python \n line breaks in text entered inside the -game (Evennia will filter this for security reasons). This is what you use instead: use the |/ -marker to format text with line breaks from the game command line.

  • -
  • `` This will translate into a TAB character. This will not always show (or show differently) to -the client since it depends on their local settings. It’s often better to use multiple spaces.

  • -
  • |_ This is a space. You can usually use the normal space character, but if the space is at the -end of the line, Evennia will likely crop it. This tag will not be cropped but always result in a -space.

  • -
  • |* This will invert the current text/background colours. Can be useful to mark things (but see -below).

  • -
-
-

Caveats of |*

-

The |* tag (inverse video) is an old ANSI standard and should usually not be used for more than to -mark short snippets of text. If combined with other tags it comes with a series of potentially -confusing behaviors:

-
    -
  • The |* tag will only work once in a row:, ie: after using it once it won’t have an effect again -until you declare another tag. This is an example:

    -
    Normal text, |*reversed text|*, still reversed text.
    -
    -
    -

    that is, it will not reverse to normal at the second |*. You need to reset it manually:

    -
    Normal text, |*reversed text|n, normal again.
    -
    -
    -
  • -
  • The |* tag does not take “bright” colors into account:

    -
    |RNormal red, |hnow brightened. |*BG is normal red.
    -
    -
    -
  • -
-

So |* only considers the ‘true’ foreground color, ignoring any highlighting. Think of the bright -state (|h) as something like like <strong> in HTML: it modifies the appearance of a normal -foreground color to match its bright counterpart, without changing its normal color.

-
    -
  • Finally, after a |*, if the previous background was set to a dark color (via |[), |!#) will -actually change the background color instead of the foreground:

    -
    |*reversed text |!R now BG is red.
    -
    -
    -
  • -
-

For a detailed explanation of these caveats, see the [Understanding Color Tags](Understanding-Color- -Tags) tutorial. But most of the time you might be better off to simply avoid |* and mark your text -manually instead.

-
-
-
-

Xterm256 Colours

-

The Xterm256 standard is a colour scheme that supports 256 colours for text and/or background. -While this offers many more possibilities than traditional ANSI colours, be wary that too many text -colors will be confusing to the eye. Also, not all clients support Xterm256 - these will instead see -the closest equivalent ANSI color. You can mix Xterm256 tags with ANSI tags as you please.

-
|555 This is pure white text.|n This is normal text.
-|230 This is olive green text.
-|[300 This text has a dark red background.
-|005|[054 This is dark blue text on a bright cyan background.
-|=a This is a greyscale value, equal to black.
-|=m This is a greyscale value, midway between white and black.
-|=z This is a greyscale value, equal to white.
-|[=m This is a background greyscale value.
-
-
-
    -
  • |### - markup consists of three digits, each an integer from 0 to 5. The three digits describe -the amount of red, green and blue (RGB) components used in the colour. So |500 means -maximum red and none of the other colours - the result is a bright red. |520 is red with a touch -of green - the result is orange. As opposed to ANSI colors, Xterm256 syntax does not worry about -bright/normal intensity, a brighter (lighter) color is just achieved by upping all RGB values with -the same amount.

  • -
  • |[### - this works the same way but produces a coloured background.

  • -
  • |=# - markup produces the xterm256 gray scale tones, where # is a letter from a (black) to -z (white). This offers many more nuances of gray than the normal |### markup (which only has -four gray tones between solid black and white (|000, |111, |222, |333 and |444)).

  • -
  • |[=# - this works in the same way but produces background gray scale tones.

  • -
-

If you have a client that supports Xterm256, you can use

-
@color xterm256
-
-
-

to get a table of all the 256 colours and the codes that produce them. If the table looks broken up -into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement. -You can use the @options command to see if xterm256 is active for you. This depends on if your -client told Evennia what it supports - if not, and you know what your client supports, you may have -to activate some features manually.

-
-
- -
-

Inline functions

-
-

Note: Inlinefuncs are not activated by default. To use them you need to add -INLINEFUNC_ENABLED=True to your settings file.

-
-

Evennia has its own inline text formatting language, known as inlinefuncs. It allows the builder -to include special function calls in code. They are executed dynamically by each session that -receives them.

-

To add an inlinefunc, you embed it in a text string like this:

-
"A normal string with $funcname(arg, arg, ...) embedded inside it."
-
-
-

When this string is sent to a session (with the msg() method), these embedded inlinefuncs will be -parsed. Their return value (which always is a string) replace their call location in the finalized -string. The interesting thing with this is that the function called will have access to which -session is seeing the string, meaning the string can end up looking different depending on who is -looking. It could of course also vary depending on other factors like game time.

-

Any number of comma-separated arguments can be given (or none). No keywords are supported. You can -also nest inlinefuncs by letting an argument itself also be another $funcname(arg, arg, ...) call -(down to a depth of nesting given by settings.INLINEFUNC_STACK_MAXSIZE). Function call resolution -happens as in all programming languages inside-out, with the nested calls replacing the argument -with their return strings before calling he parent.

-
   > say  "This is $pad(a center-padded text, 30,c,-) of width 30."
-   You say, "This is ---- a center-padded text----- of width 30."
-   > say "I roll a die and the result is ... a $random(1,6)!"
-   You say, "I roll a die and the result is ... a 5!"
-
-
-

A special case happens if wanting to use an inlinefunc argument that itself includes a comma - this -would be parsed as an argument separator. To escape commas you can either escape each comma manually -with a backslash \,, or you can embed the entire string in python triple-quotes """ or ''' - -this will escape the entire argument, including commas and any nested inlinefunc calls within.

-

Only certain functions are available to use as inlinefuncs and the game developer may add their own -functions as needed.

-
-

New inlinefuncs

-

To add new inlinefuncs, edit the file mygame/server/conf/inlinefuncs.py.

-

All globally defined functions in this module are considered inline functions by the system. The -only exception is functions whose name starts with an underscore _. An inlinefunc must be of the -following form:

-
def funcname(*args, **kwargs):
-    # ...
-    return modified_text
-
-
-

where *args denotes all the arguments this function will accept as an $inlinefunc. The inline -function is expected to clean arguments and check that they are valid. If needed arguments are not -given, default values should be used. The function should always return a string (even if it’s -empty). An inlinefunc should never cause a traceback regardless of the input (but it could log -errors if desired).

-

Note that whereas the function should accept **kwargs, keyword inputs are not usable in the call -to the inlinefunction. The kwargs part is instead intended for Evennia to be able to supply extra -information. Currently Evennia sends a single keyword to every inline function and that is -session, which holds the serversession this text is targeted at. Through the session -object, a lot of dynamic possibilities are opened up for your inline functions.

-

The settings.INLINEFUNC_MODULES configuration option is a list that decides which modules should -be parsed for inline function definitions. This will include mygame/server/conf/inlinefuncs.py but -more could be added. The list is read from left to right so if you want to overload default -functions you just have to put your custom module-paths later in the list and name your functions -the same as default ones.

-

Here is an example, the crop default inlinefunction:

-
from evennia.utils import utils
-
-def crop(*args, **kwargs):
-    """
-    Inlinefunc. Crops ingoing text to given widths.
-    Args:
-        text (str, optional): Text to crop.
-        width (str, optional): Will be converted to an integer. Width of
-            crop in characters.
-        suffix (str, optional): End string to mark the fact that a part
-            of the string was cropped. Defaults to `[...]`.
-    Kwargs:
-        session (Session): Session performing the crop.
-    Example:
-        `$crop(text, 50, [...])`
-
-    """
-    text, width, suffix = "", 78, "[...]"
-    nargs = len(args)
-    if nargs > 0:
-        text = args[0]
-    if nargs > 1:
-        width = int(args[1]) if args[1].strip().isdigit() else 78
-    if nargs > 2:
-        suffix = args[2]
-    return utils.crop(text, width=width, suffix=suffix)
-
-
-

Another example, making use of the Session:

-
def charactername(*args, **kwargs):
-    """
-    Inserts the character name of whomever sees the string
-    (so everyone will see their own name). Uses the account
-    name for OOC communications.
-
-    Example:
-        say "This means YOU, $charactername()!"
-
-    """
-    session = kwargs["session"]
-    if session.puppet:
-        return kwargs["session"].puppet.key
-    else:
-        return session.account.key
-
-
-

Evennia itself offers the following default inline functions (mostly as examples):

-
    -
  • crop(text, width, suffix) - See above.

  • -
  • pad(text, width, align, fillchar) - this pads the text to width (default 78), alignment (“c”, -“l” or “r”, defaulting to “c”) and fill-in character (defaults to space). Example: $pad(40,l,-)

  • -
  • clr(startclr, text, endclr) - A programmatic way to enter colored text for those who don’t want -to use the normal |c type color markers for some reason. The color argument is the same as the -color markers except without the actual pre-marker, so |r would be just r. If endclr is not -given, it defaults to resetting the color (n). Example: $clr(b, A blue text)

  • -
  • space(number) - Inserts the given number of spaces. If no argument is given, use 4 spaces.

  • -
  • random(), random(max), random(min, max) - gives a random value between min and max. With no -arguments, give 0 or 1 (on/off). If one argument, returns 0…max. If values are floats, random -value will be a float (so random(1.0) replicates the normal Python random function).

  • -
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/TickerHandler.html b/docs/0.9.5/TickerHandler.html deleted file mode 100644 index 4d31123d60..0000000000 --- a/docs/0.9.5/TickerHandler.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - - TickerHandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

TickerHandler

-

One way to implement a dynamic MUD is by using “tickers”, also known as “heartbeats”. A ticker is a -timer that fires (“ticks”) at a given interval. The tick triggers updates in various game systems.

-
-

About Tickers

-

Tickers are very common or even unavoidable in other mud code bases. Certain code bases are even -hard-coded to rely on the concept of the global ‘tick’. Evennia has no such notion - the decision to -use tickers is very much up to the need of your game and which requirements you have. The “ticker -recipe” is just one way of cranking the wheels.

-

The most fine-grained way to manage the flow of time is of course to use Scripts. Many -types of operations (weather being the classic example) are however done on multiple objects in the -same way at regular intervals, and for this, storing separate Scripts on each object is inefficient. -The way to do this is to use a ticker with a “subscription model” - let objects sign up to be -triggered at the same interval, unsubscribing when the updating is no longer desired.

-

Evennia offers an optimized implementation of the subscription model - the TickerHandler. This is -a singleton global handler reachable from evennia.TICKER_HANDLER. You can assign any callable (a -function or, more commonly, a method on a database object) to this handler. The TickerHandler will -then call this callable at an interval you specify, and with the arguments you supply when adding -it. This continues until the callable un-subscribes from the ticker. The handler survives a reboot -and is highly optimized in resource usage.

-

Here is an example of importing TICKER_HANDLER and using it:

-
    # we assume that obj has a hook "at_tick" defined on itself
-    from evennia import TICKER_HANDLER as tickerhandler
-
-    tickerhandler.add(20, obj.at_tick)
-
-
-

That’s it - from now on, obj.at_tick() will be called every 20 seconds.

-

You can also import function and tick that:

-
    from evennia import TICKER_HANDLER as tickerhandler
-    from mymodule import myfunc
-
-    tickerhandler.add(30, myfunc)
-
-
-

Removing (stopping) the ticker works as expected:

-
    tickerhandler.remove(20, obj.at_tick)
-    tickerhandler.remove(30, myfunc)
-
-
-

Note that you have to also supply interval to identify which subscription to remove. This is -because the TickerHandler maintains a pool of tickers and a given callable can subscribe to be -ticked at any number of different intervals.

-

The full definition of the tickerhandler.add method is

-
    tickerhandler.add(interval, callback,
-                      idstring="", persistent=True, *args, **kwargs)
-
-
-

Here *args and **kwargs will be passed to callback every interval seconds. If persistent -is False, this subscription will not survive a server reload.

-

Tickers are identified and stored by making a key of the callable itself, the ticker-interval, the -persistent flag and the idstring (the latter being an empty string when not given explicitly).

-

Since the arguments are not included in the ticker’s identification, the idstring must be used to -have a specific callback triggered multiple times on the same interval but with different arguments:

-
    tickerhandler.add(10, obj.update, "ticker1", True, 1, 2, 3)
-    tickerhandler.add(10, obj.update, "ticker2", True, 4, 5)
-
-
-
-

Note that, when we want to send arguments to our callback within a ticker handler, we need to -specify idstring and persistent before, unless we call our arguments as keywords, which would -often be more readable:

-
-
    tickerhandler.add(10, obj.update, caller=self, value=118)
-
-
-

If you add a ticker with exactly the same combination of callback, interval and idstring, it will -overload the existing ticker. This identification is also crucial for later removing (stopping) the -subscription:

-
    tickerhandler.remove(10, obj.update, idstring="ticker1")
-    tickerhandler.remove(10, obj.update, idstring="ticker2")
-
-
-

The callable can be on any form as long as it accepts the arguments you give to send to it in -TickerHandler.add.

-
-

Note that everything you supply to the TickerHandler will need to be pickled at some point to be -saved into the database. Most of the time the handler will correctly store things like database -objects, but the same restrictions as for Attributes apply to what the TickerHandler -may store.

-
-

When testing, you can stop all tickers in the entire game with tickerhandler.clear(). You can also -view the currently subscribed objects with tickerhandler.all().

-

See the Weather Tutorial for an example of using the TickerHandler.

-
-

When not to use TickerHandler

-

Using the TickerHandler may sound very useful but it is important to consider when not to use it. -Even if you are used to habitually relying on tickers for everything in other code bases, stop and -think about what you really need it for. This is the main point:

-
-

You should never use a ticker to catch changes.

-
-

Think about it - you might have to run the ticker every second to react to the change fast enough. -Most likely nothing will have changed at a given moment. So you are doing pointless calls (since -skipping the call gives the same result as doing it). Making sure nothing’s changed might even be -computationally expensive depending on the complexity of your system. Not to mention that you might -need to run the check on every object in the database. Every second. Just to maintain status quo -…

-

Rather than checking over and over on the off-chance that something changed, consider a more -proactive approach. Could you implement your rarely changing system to itself report when its -status changes? It’s almost always much cheaper/efficient if you can do things “on demand”. Evennia -itself uses hook methods for this very reason.

-

So, if you consider a ticker that will fire very often but which you expect to have no effect 99% of -the time, consider handling things things some other way. A self-reporting on-demand solution is -usually cheaper also for fast-updating properties. Also remember that some things may not need to be -updated until someone actually is examining or using them - any interim changes happening up to that -moment are pointless waste of computing time.

-

The main reason for needing a ticker is when you want things to happen to multiple objects at the -same time without input from something else.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Turn-based-Combat-System.html b/docs/0.9.5/Turn-based-Combat-System.html deleted file mode 100644 index be29183daa..0000000000 --- a/docs/0.9.5/Turn-based-Combat-System.html +++ /dev/null @@ -1,621 +0,0 @@ - - - - - - - - - Turn based Combat System — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Turn based Combat System

-

This tutorial gives an example of a full, if simplified, combat system for Evennia. It was inspired -by the discussions held on the mailing list.

-
-

Overview of combat system concepts

-

Most MUDs will use some sort of combat system. There are several main variations:

-
    -
  • Freeform - the simplest form of combat to implement, common to MUSH-style roleplaying games. -This means the system only supplies dice rollers or maybe commands to compare skills and spit out -the result. Dice rolls are done to resolve combat according to the rules of the game and to direct -the scene. A game master may be required to resolve rule disputes.

  • -
  • Twitch - This is the traditional MUD hack&slash style combat. In a twitch system there is often -no difference between your normal “move-around-and-explore mode” and the “combat mode”. You enter an -attack command and the system will calculate if the attack hits and how much damage was caused. -Normally attack commands have some sort of timeout or notion of recovery/balance to reduce the -advantage of spamming or client scripting. Whereas the simplest systems just means entering kill <target> over and over, more sophisticated twitch systems include anything from defensive stances -to tactical positioning.

  • -
  • Turn-based - a turn based system means that the system pauses to make sure all combatants can -choose their actions before continuing. In some systems, such entered actions happen immediately -(like twitch-based) whereas in others the resolution happens simultaneously at the end of the turn. -The disadvantage of a turn-based system is that the game must switch to a “combat mode” and one also -needs to take special care of how to handle new combatants and the passage of time. The advantage is -that success is not dependent on typing speed or of setting up quick client macros. This potentially -allows for emoting as part of combat which is an advantage for roleplay-heavy games.

  • -
-

To implement a freeform combat system all you need is a dice roller and a roleplaying rulebook. See -contrib/dice.py for an -example dice roller. To implement at twitch-based system you basically need a few combat -commands, possibly ones with a cooldown. You also need a game rule -module that makes use of it. We will focus on the turn-based -variety here.

-
-
-

Tutorial overview

-

This tutorial will implement the slightly more complex turn-based combat system. Our example has the -following properties:

-
    -
  • Combat is initiated with attack <target>, this initiates the combat mode.

  • -
  • Characters may join an ongoing battle using attack <target> against a character already in -combat.

  • -
  • Each turn every combating character will get to enter two commands, their internal order matters -and they are compared one-to-one in the order given by each combatant. Use of say and pose is -free.

  • -
  • The commands are (in our example) simple; they can either hit <target>, feint <target> or -parry <target>. They can also defend, a generic passive defense. Finally they may choose to -disengage/flee.

  • -
  • When attacking we use a classic rock-paper-scissors mechanic to determine success: hit defeats feint, which defeats parry which defeats -hit. defend is a general passive action that has a percentage chance to win against hit -(only).

  • -
  • disengage/flee must be entered two times in a row and will only succeed if there is no hit -against them in that time. If so they will leave combat mode.

  • -
  • Once every player has entered two commands, all commands are resolved in order and the result is -reported. A new turn then begins.

  • -
  • If players are too slow the turn will time out and any unset commands will be set to defend.

  • -
-

For creating the combat system we will need the following components:

-
    -
  • A combat handler. This is the main mechanic of the system. This is a Script object -created for each combat. It is not assigned to a specific object but is shared by the combating -characters and handles all the combat information. Since Scripts are database entities it also means -that the combat will not be affected by a server reload.

  • -
  • A combat command set with the relevant commands needed for combat, such as the -various attack/defend options and the flee/disengage command to leave the combat mode.

  • -
  • A rule resolution system. The basics of making such a module is described in the rule system tutorial. We will only sketch such a module here for our end-turn -combat resolution.

  • -
  • An attack command for initiating the combat mode. This is added to the default -command set. It will create the combat handler and add the character(s) to it. It will also assign -the combat command set to the characters.

  • -
-
-
-

The combat handler

-

The combat handler is implemented as a stand-alone Script. This Script is created when -the first Character decides to attack another and is deleted when no one is fighting any more. Each -handler represents one instance of combat and one combat only. Each instance of combat can hold any -number of characters but each character can only be part of one combat at a time (a player would -need to disengage from the first combat before they could join another).

-

The reason we don’t store this Script “on” any specific character is because any character may leave -the combat at any time. Instead the script holds references to all characters involved in the -combat. Vice-versa, all characters holds a back-reference to the current combat handler. While we -don’t use this very much here this might allow the combat commands on the characters to access and -update the combat handler state directly.

-

Note: Another way to implement a combat handler would be to use a normal Python object and handle -time-keeping with the TickerHandler. This would require either adding custom hook -methods on the character or to implement a custom child of the TickerHandler class to track turns. -Whereas the TickerHandler is easy to use, a Script offers more power in this case.

-

Here is a basic combat handler. Assuming our game folder is named mygame, we store it in -mygame/typeclasses/combat_handler.py:

-
# mygame/typeclasses/combat_handler.py
-
-import random
-from evennia import DefaultScript
-from world.rules import resolve_combat
-
-class CombatHandler(DefaultScript):
-    """
-    This implements the combat handler.
-    """
-
-    # standard Script hooks
-
-    def at_script_creation(self):
-        "Called when script is first created"
-
-        self.key = "combat_handler_%i" % random.randint(1, 1000)
-        self.desc = "handles combat"
-        self.interval = 60 * 2  # two minute timeout
-        self.start_delay = True
-        self.persistent = True
-
-        # store all combatants
-        self.db.characters = {}
-        # store all actions for each turn
-        self.db.turn_actions = {}
-        # number of actions entered per combatant
-        self.db.action_count = {}
-
-    def _init_character(self, character):
-        """
-        This initializes handler back-reference
-        and combat cmdset on a character
-        """
-        character.ndb.combat_handler = self
-        character.cmdset.add("commands.combat.CombatCmdSet")
-
-    def _cleanup_character(self, character):
-        """
-        Remove character from handler and clean
-        it of the back-reference and cmdset
-        """
-        dbref = character.id
-        del self.db.characters[dbref]
-        del self.db.turn_actions[dbref]
-        del self.db.action_count[dbref]
-        del character.ndb.combat_handler
-        character.cmdset.delete("commands.combat.CombatCmdSet")
-
-    def at_start(self):
-        """
-        This is called on first start but also when the script is restarted
-        after a server reboot. We need to re-assign this combat handler to
-        all characters as well as re-assign the cmdset.
-        """
-        for character in self.db.characters.values():
-            self._init_character(character)
-
-    def at_stop(self):
-        "Called just before the script is stopped/destroyed."
-        for character in list(self.db.characters.values()):
-            # note: the list() call above disconnects list from database
-            self._cleanup_character(character)
-
-    def at_repeat(self):
-        """
-        This is called every self.interval seconds (turn timeout) or
-        when force_repeat is called (because everyone has entered their
-        commands). We know this by checking the existence of the
-        `normal_turn_end` NAttribute, set just before calling
-        force_repeat.
-
-        """
-        if self.ndb.normal_turn_end:
-            # we get here because the turn ended normally
-            # (force_repeat was called) - no msg output
-            del self.ndb.normal_turn_end
-        else:
-            # turn timeout
-            self.msg_all("Turn timer timed out. Continuing.")
-        self.end_turn()
-
-    # Combat-handler methods
-
-    def add_character(self, character):
-        "Add combatant to handler"
-        dbref = character.id
-        self.db.characters[dbref] = character
-        self.db.action_count[dbref] = 0
-        self.db.turn_actions[dbref] = [("defend", character, None),
-                                       ("defend", character, None)]
-        # set up back-reference
-        self._init_character(character)
-
-    def remove_character(self, character):
-        "Remove combatant from handler"
-        if character.id in self.db.characters:
-            self._cleanup_character(character)
-        if not self.db.characters:
-            # if no more characters in battle, kill this handler
-            self.stop()
-
-    def msg_all(self, message):
-        "Send message to all combatants"
-        for character in self.db.characters.values():
-            character.msg(message)
-
-    def add_action(self, action, character, target):
-        """
-        Called by combat commands to register an action with the handler.
-
-         action - string identifying the action, like "hit" or "parry"
-         character - the character performing the action
-         target - the target character or None
-
-        actions are stored in a dictionary keyed to each character, each
-        of which holds a list of max 2 actions. An action is stored as
-        a tuple (character, action, target).
-        """
-        dbref = character.id
-        count = self.db.action_count[dbref]
-        if 0 <= count <= 1: # only allow 2 actions
-            self.db.turn_actions[dbref][count] = (action, character, target)
-        else:
-            # report if we already used too many actions
-            return False
-        self.db.action_count[dbref] += 1
-        return True
-
-    def check_end_turn(self):
-        """
-        Called by the command to eventually trigger
-        the resolution of the turn. We check if everyone
-        has added all their actions; if so we call force the
-        script to repeat immediately (which will call
-        `self.at_repeat()` while resetting all timers).
-        """
-        if all(count > 1 for count in self.db.action_count.values()):
-            self.ndb.normal_turn_end = True
-            self.force_repeat()
-
-    def end_turn(self):
-        """
-        This resolves all actions by calling the rules module.
-        It then resets everything and starts the next turn. It
-        is called by at_repeat().
-        """
-        resolve_combat(self, self.db.turn_actions)
-
-        if len(self.db.characters) < 2:
-            # less than 2 characters in battle, kill this handler
-            self.msg_all("Combat has ended")
-            self.stop()
-        else:
-            # reset counters before next turn
-            for character in self.db.characters.values():
-                self.db.characters[character.id] = character
-                self.db.action_count[character.id] = 0
-                self.db.turn_actions[character.id] = [("defend", character, None),
-                                                  ("defend", character, None)]
-            self.msg_all("Next turn begins ...")
-
-
-

This implements all the useful properties of our combat handler. This Script will survive a reboot -and will automatically re-assert itself when it comes back online. Even the current state of the -combat should be unaffected since it is saved in Attributes at every turn. An important part to note -is the use of the Script’s standard at_repeat hook and the force_repeat method to end each turn. -This allows for everything to go through the same mechanisms with minimal repetition of code.

-

What is not present in this handler is a way for players to view the actions they set or to change -their actions once they have been added (but before the last one has added theirs). We leave this as -an exercise.

-
-
-

Combat commands

-

Our combat commands - the commands that are to be available to us during the combat - are (in our -example) very simple. In a full implementation the commands available might be determined by the -weapon(s) held by the player or by which skills they know.

-

We create them in mygame/commands/combat.py.

-
# mygame/commands/combat.py
-
-from evennia import Command
-
-class CmdHit(Command):
-    """
-    hit an enemy
-
-    Usage:
-      hit <target>
-
-    Strikes the given enemy with your current weapon.
-    """
-    key = "hit"
-    aliases = ["strike", "slash"]
-    help_category = "combat"
-
-    def func(self):
-        "Implements the command"
-        if not self.args:
-            self.caller.msg("Usage: hit <target>")
-            return
-        target = self.caller.search(self.args)
-        if not target:
-            return
-        ok = self.caller.ndb.combat_handler.add_action("hit",
-                                                       self.caller,
-                                                       target)
-        if ok:
-            self.caller.msg("You add 'hit' to the combat queue")
-        else:
-            self.caller.msg("You can only queue two actions per turn!")
-
-        # tell the handler to check if turn is over
-        self.caller.ndb.combat_handler.check_end_turn()
-
-
-

The other commands CmdParry, CmdFeint, CmdDefend and CmdDisengage look basically the same. -We should also add a custom help command to list all the available combat commands and what they -do.

-

We just need to put them all in a cmdset. We do this at the end of the same module:

-
# mygame/commands/combat.py
-
-from evennia import CmdSet
-from evennia import default_cmds
-
-class CombatCmdSet(CmdSet):
-    key = "combat_cmdset"
-    mergetype = "Replace"
-    priority = 10
-    no_exits = True
-
-    def at_cmdset_creation(self):
-        self.add(CmdHit())
-        self.add(CmdParry())
-        self.add(CmdFeint())
-        self.add(CmdDefend())
-        self.add(CmdDisengage())
-        self.add(CmdHelp())
-        self.add(default_cmds.CmdPose())
-        self.add(default_cmds.CmdSay())
-
-
-
-
-

Rules module

-

A general way to implement a rule module is found in the rule system tutorial. Proper resolution would likely require us to change our Characters to store things -like strength, weapon skills and so on. So for this example we will settle for a very simplistic -rock-paper-scissors kind of setup with some randomness thrown in. We will not deal with damage here -but just announce the results of each turn. In a real system the Character objects would hold stats -to affect their skills, their chosen weapon affect the choices, they would be able to lose health -etc.

-

Within each turn, there are “sub-turns”, each consisting of one action per character. The actions -within each sub-turn happens simultaneously and only once they have all been resolved we move on to -the next sub-turn (or end the full turn).

-

Note: In our simple example the sub-turns don’t affect each other (except for disengage/flee), -nor do any effects carry over between turns. The real power of a turn-based system would be to add -real tactical possibilities here though; For example if your hit got parried you could be out of -balance and your next action would be at a disadvantage. A successful feint would open up for a -subsequent attack and so on …

-

Our rock-paper-scissor setup works like this:

-
    -
  • hit beats feint and flee/disengage. It has a random chance to fail against defend.

  • -
  • parry beats hit.

  • -
  • feint beats parry and is then counted as a hit.

  • -
  • defend does nothing but has a chance to beat hit.

  • -
  • flee/disengage must succeed two times in a row (i.e. not beaten by a hit once during the -turn). If so the character leaves combat.

  • -
-
# mygame/world/rules.py
-
-import random
-
-# messages
-
-def resolve_combat(combat_handler, actiondict):
-    """
-    This is called by the combat handler
-    actiondict is a dictionary with a list of two actions
-    for each character:
-    {char.id:[(action1, char, target), (action2, char, target)], ...}
-    """
-    flee = {} # track number of flee commands per character
-    for isub in range(2):
-        # loop over sub-turns
-        messages = []
-        for subturn in (sub[isub] for sub in actiondict.values()):
-            # for each character, resolve the sub-turn
-            action, char, target = subturn
-            if target:
-                taction, tchar, ttarget = actiondict[target.id][isub]
-            if action == "hit":
-                if taction == "parry" and ttarget == char:
-                    msg = "%s tries to hit %s, but %s parries the attack!"
-                    messages.append(msg % (char, tchar, tchar))
-                elif taction == "defend" and random.random() < 0.5:
-                    msg = "%s defends against the attack by %s."
-                    messages.append(msg % (tchar, char))
-                elif taction == "flee":
-                    msg = "%s stops %s from disengaging, with a hit!"
-                    flee[tchar] = -2
-                    messages.append(msg % (char, tchar))
-                else:
-                    msg = "%s hits %s, bypassing their %s!"
-                    messages.append(msg % (char, tchar, taction))
-            elif action == "parry":
-                if taction == "hit":
-                    msg = "%s parries the attack by %s."
-                    messages.append(msg % (char, tchar))
-                elif taction == "feint":
-                    msg = "%s tries to parry, but %s feints and hits!"
-                    messages.append(msg % (char, tchar))
-                else:
-                    msg = "%s parries to no avail."
-                    messages.append(msg % char)
-            elif action == "feint":
-                if taction == "parry":
-                    msg = "%s feints past %s's parry, landing a hit!"
-                    messages.append(msg % (char, tchar))
-                elif taction == "hit":
-                    msg = "%s feints but is defeated by %s hit!"
-                    messages.append(msg % (char, tchar))
-                else:
-                    msg = "%s feints to no avail."
-                    messages.append(msg % char)
-            elif action == "defend":
-                msg = "%s defends."
-                messages.append(msg % char)
-            elif action == "flee":
-                if char in flee:
-                    flee[char] += 1
-                else:
-                    flee[char] = 1
-                    msg = "%s tries to disengage (two subsequent turns needed)"
-                    messages.append(msg % char)
-
-        # echo results of each subturn
-        combat_handler.msg_all("\n".join(messages))
-
-    # at the end of both sub-turns, test if anyone fled
-    msg = "%s withdraws from combat."
-    for (char, fleevalue) in flee.items():
-        if fleevalue == 2:
-            combat_handler.msg_all(msg % char)
-            combat_handler.remove_character(char)
-
-
-

To make it simple (and to save space), this example rule module actually resolves each interchange -twice - first when it gets to each character and then again when handling the target. Also, since we -use the combat handler’s msg_all method here, the system will get pretty spammy. To clean it up, -one could imagine tracking all the possible interactions to make sure each pair is only handled and -reported once.

-
-
-

Combat initiator command

-

This is the last component we need, a command to initiate combat. This will tie everything together. -We store this with the other combat commands.

-
# mygame/commands/combat.py
-
-from evennia import create_script
-
-class CmdAttack(Command):
-    """
-    initiates combat
-
-    Usage:
-      attack <target>
-
-    This will initiate combat with <target>. If <target is
-    already in combat, you will join the combat.
-    """
-    key = "attack"
-    help_category = "General"
-
-    def func(self):
-        "Handle command"
-        if not self.args:
-            self.caller.msg("Usage: attack <target>")
-            return
-        target = self.caller.search(self.args)
-        if not target:
-            return
-        # set up combat
-        if target.ndb.combat_handler:
-            # target is already in combat - join it
-            target.ndb.combat_handler.add_character(self.caller)
-            target.ndb.combat_handler.msg_all("%s joins combat!" % self.caller)
-        else:
-            # create a new combat handler
-            chandler = create_script("combat_handler.CombatHandler")
-            chandler.add_character(self.caller)
-            chandler.add_character(target)
-            self.caller.msg("You attack %s! You are in combat." % target)
-            target.msg("%s attacks you! You are in combat." % self.caller)
-
-
-

The attack command will not go into the combat cmdset but rather into the default cmdset. See e.g. -the Adding Command Tutorial if you are unsure about how to do this.

-
-
-

Expanding the example

-

At this point you should have a simple but flexible turn-based combat system. We have taken several -shortcuts and simplifications in this example. The output to the players is likely too verbose -during combat and too limited when it comes to informing about things surrounding it. Methods for -changing your commands or list them, view who is in combat etc is likely needed - this will require -play testing for each game and style. There is also currently no information displayed for other -people happening to be in the same room as the combat - some less detailed information should -probably be echoed to the room to -show others what’s going on.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tutorial-Aggressive-NPCs.html b/docs/0.9.5/Tutorial-Aggressive-NPCs.html deleted file mode 100644 index 2fe5b0b090..0000000000 --- a/docs/0.9.5/Tutorial-Aggressive-NPCs.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - Tutorial Aggressive NPCs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tutorial Aggressive NPCs

-

This tutorial shows the implementation of an NPC object that responds to characters entering their -location. In this example the NPC has the option to respond aggressively or not, but any actions -could be triggered this way.

-

One could imagine using a Script that is constantly checking for newcomers. This would be -highly inefficient (most of the time its check would fail). Instead we handle this on-demand by -using a couple of existing object hooks to inform the NPC that a Character has entered.

-

It is assumed that you already know how to create custom room and character typeclasses, please see -the Basic Game tutorial if you haven’t already done this.

-

What we will need is the following:

-
    -
  • An NPC typeclass that can react when someone enters.

  • -
  • A custom Room typeclass that can tell the NPC that someone entered.

  • -
  • We will also tweak our default Character typeclass a little.

  • -
-

To begin with, we need to create an NPC typeclass. Create a new file inside of your typeclasses -folder and name it npcs.py and then add the following code:

-
from typeclasses.characters import Character
-
-class NPC(Character):
-    """
-    A NPC typeclass which extends the character class.
-    """
-    def at_char_entered(self, character):
-        """
-         A simple is_aggressive check.
-         Can be expanded upon later.
-        """
-        if self.db.is_aggressive:
-            self.execute_cmd(f"say Graaah, die {character}!")
-        else:
-            self.execute_cmd(f"say Greetings, {character}!")
-
-
-

We will define our custom Character typeclass below. As for the new at_char_entered method we’ve -just defined, we’ll ensure that it will be called by the room where the NPC is located, when a -player enters that room. You’ll notice that right now, the NPC merely speaks. You can expand this -part as you like and trigger all sorts of effects here (like combat code, fleeing, bartering or -quest-giving) as your game design dictates.

-

Now your typeclasses.rooms module needs to have the following added:

-
# Add this import to the top of your file.
-from evennia import utils
-
-    # Add this hook in any empty area within your Room class.
-    def at_object_receive(self, obj, source_location):
-        if utils.inherits_from(obj, 'typeclasses.npcs.NPC'): # An NPC has entered
-            return
-        elif utils.inherits_from(obj, 'typeclasses.characters.Character'):
-            # A PC has entered.
-            # Cause the player's character to look around.
-            obj.execute_cmd('look')
-            for item in self.contents:
-                if utils.inherits_from(item, 'typeclasses.npcs.NPC'):
-                    # An NPC is in the room
-                    item.at_char_entered(obj)
-
-
-

inherits_from must be given the full path of the class. If the object inherited a class from your -world.races module, then you would check inheritance with world.races.Human, for example. There -is no need to import these prior, as we are passing in the full path. As a matter of a fact, -inherits_from does not properly work if you import the class and only pass in the name of the -class.

-
-

Note: -at_object_receive -is a default hook of the DefaultObject typeclass (and its children). Here we are overriding this -hook in our customized room typeclass to suit our needs.

-
-

This room checks the typeclass of objects entering it (using utils.inherits_from and responds to -Characters, ignoring other NPCs or objects. When triggered the room will look through its -contents and inform any NPCs inside by calling their at_char_entered` method.

-

You’ll also see that we have added a ‘look’ into this code. This is because, by default, the -at_object_receive is carried out before the character’s at_after_move which, we will now -overload. This means that a character entering would see the NPC perform its actions before the -‘look’ command. Deactivate the look command in the default Character class within the -typeclasses.characters module:

-
    # Add this hook in any blank area within your Character class.
-    def at_after_move(self, source_location):
-        """
-        Default is to look around after a move
-        Note:  This has been moved to Room.at_object_receive
-        """
-        #self.execute_cmd('look')
-        pass
-
-
-

Now let’s create an NPC and make it aggressive. Type the following commands into your MUD client:

-
reload
-create/drop Orc:npcs.NPC
-
-
-
-

Note: You could also give the path as typeclasses.npcs.NPC, but Evennia will look into the -typeclasses folder automatically, so this is a little shorter.

-
-

When you enter the aggressive NPC’s location, it will default to using its peaceful action (say your -name is Anna):

-
Orc says, "Greetings, Anna!"
-
-
-

Now we turn on the aggressive mode (we do it manually but it could also be triggered by some sort of -AI code).

-
set orc/is_aggressive = True
-
-
-

Now it will perform its aggressive action whenever a character enters.

-
Orc says, "Graaah, die, Anna!"
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tutorial-NPCs-listening.html b/docs/0.9.5/Tutorial-NPCs-listening.html deleted file mode 100644 index a150839c45..0000000000 --- a/docs/0.9.5/Tutorial-NPCs-listening.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - Tutorial NPCs listening — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tutorial NPCs listening

-

This tutorial shows the implementation of an NPC object that responds to characters speaking in -their location. In this example the NPC parrots what is said, but any actions could be triggered -this way.

-

It is assumed that you already know how to create custom room and character typeclasses, please see -the Basic Game tutorial if you haven’t already done this.

-

What we will need is simply a new NPC typeclass that can react when someone speaks.

-
# mygame/typeclasses/npc.py
-
-from characters import Character
-class Npc(Character):
-    """
-    A NPC typeclass which extends the character class.
-    """
-    def at_heard_say(self, message, from_obj):
-        """
-        A simple listener and response. This makes it easy to change for
-        subclasses of NPCs reacting differently to says.
-
-        """
-        # message will be on the form `<Person> says, "say_text"`
-        # we want to get only say_text without the quotes and any spaces
-        message = message.split('says, ')[1].strip(' "')
-
-        # we'll make use of this in .msg() below
-        return "%s said: '%s'" % (from_obj, message)
-
-
-

When someone in the room speaks to this NPC, its msg method will be called. We will modify the -NPCs .msg method to catch says so the NPC can respond.

-
# mygame/typeclasses/npc.py
-
-from characters import Character
-class Npc(Character):
-
-    # [at_heard_say() goes here]
-
-    def msg(self, text=None, from_obj=None, **kwargs):
-        "Custom msg() method reacting to say."
-
-        if from_obj != self:
-            # make sure to not repeat what we ourselves said or we'll create a loop
-            try:
-                # if text comes from a say, `text` is `('say_text', {'type': 'say'})`
-                say_text, is_say = text[0], text[1]['type'] == 'say'
-            except Exception:
-                is_say = False
-            if is_say:
-                # First get the response (if any)
-                response = self.at_heard_say(say_text, from_obj)
-                # If there is a response
-                if response != None:
-                    # speak ourselves, using the return
-                    self.execute_cmd("say %s" % response)
-    
-        # this is needed if anyone ever puppets this NPC - without it you would never
-        # get any feedback from the server (not even the results of look)
-        super().msg(text=text, from_obj=from_obj, **kwargs)
-
-
-

So if the NPC gets a say and that say is not coming from the NPC itself, it will echo it using the -at_heard_say hook. Some things of note in the above example:

-
    -
  • The text input can be on many different forms depending on where this msg is called from. -Instead of trying to analyze text in detail with a range of if statements we just assume the -form we want and catch the error if it does not match. This simplifies the code considerably. It’s -called ‘leap before you look’ and is a Python paradigm that may feel unfamiliar if you are used to -other languages. Here we ‘swallow’ the error silently, which is fine when the code checked is -simple. If not we may want to import evennia.logger.log_trace and add log_trace() in the -except clause.
    -If you would like to learn more about the text list used above refer to the Out-Of-Band -documentation.

  • -
  • We use execute_cmd to fire the say command back. We could also have called -self.location.msg_contents directly but using the Command makes sure all hooks are called (so -those seeing the NPC’s say can in turn react if they want).

  • -
  • Note the comments about super at the end. This will trigger the ‘default’ msg (in the parent -class) as well. It’s not really necessary as long as no one puppets the NPC (by @ic <npcname>) but -it’s wise to keep in there since the puppeting player will be totally blind if msg() is never -returning anything to them!

  • -
-

Now that’s done, let’s create an NPC and see what it has to say for itself.

-
@reload
-@create/drop Guild Master:npc.Npc
-
-
-

(you could also give the path as typeclasses.npc.Npc, but Evennia will look into the typeclasses -folder automatically so this is a little shorter).

-
> say hi
-You say, "hi"
-Guild Master says, "Anna said: 'hi'"
-
-
-
-

Assorted notes

-

There are many ways to implement this kind of functionality. An alternative example to overriding -msg would be to modify the at_say hook on the Character instead. It could detect that it’s -sending to an NPC and call the at_heard_say hook directly.

-

While the tutorial solution has the advantage of being contained only within the NPC class, -combining this with using the Character class gives more direct control over how the NPC will react. -Which way to go depends on the design requirements of your particular game.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tutorial-Searching-For-Objects.html b/docs/0.9.5/Tutorial-Searching-For-Objects.html deleted file mode 100644 index 234173394e..0000000000 --- a/docs/0.9.5/Tutorial-Searching-For-Objects.html +++ /dev/null @@ -1,528 +0,0 @@ - - - - - - - - - Tutorial Searching For Objects — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tutorial Searching For Objects

-

You will often want to operate on a specific object in the database. For example when a player -attacks a named target you’ll need to find that target so it can be attacked. Or when a rain storm -draws in you need to find all outdoor-rooms so you can show it raining in them. This tutorial -explains Evennia’s tools for searching.

-
-

Things to search for

-

The first thing to consider is the base type of the thing you are searching for. Evennia organizes -its database into a few main tables: Objects, Accounts, Scripts, -Channels, Messages and Help Entries. -Most of the time you’ll likely spend your time searching for Objects and the occasional Accounts.

-

So to find an entity, what can be searched for?

-
    -
  • The key is the name of the entity. While you can get this from obj.key the database field -is actually named obj.db_key - this is useful to know only when you do direct database -queries. The one exception is Accounts, where -the database field for .key is instead named username (this is a Django requirement). When you -don’t specify search-type, you’ll usually search based on key. Aliases are extra names given to -Objects using something like @alias or obj.aliases.add('name'). The main search functions (see -below) will automatically search for aliases whenever you search by-key.

  • -
  • Tags are the main way to group and identify objects in Evennia. Tags can most often be -used (sometimes together with keys) to uniquely identify an object. For example, even though you -have two locations with the same name, you can separate them by their tagging (this is how Evennia -implements ‘zones’ seen in other systems). Tags can also have categories, to further organize your -data for quick lookups.

  • -
  • An object’s Attributes can also used to find an object. This can be very useful but -since Attributes can store almost any data they are far less optimized to search for than Tags or -keys.

  • -
  • The object’s Typeclass indicate the sub-type of entity. A Character, Flower or -Sword are all types of Objects. A Bot is a kind of Account. The database field is called -typeclass_path and holds the full Python-path to the class. You can usually specify the -typeclass as an argument to Evennia’s search functions as well as use the class directly to limit -queries.

  • -
  • The location is only relevant for Objects but is a very common way to weed down the -number of candidates before starting to search. The reason is that most in-game commands tend to -operate on things nearby (in the same room) so the choices can be limited from the start.

  • -
  • The database id or the ‘#dbref’ is unique (and never re-used) within each database table. So while -there is one and only one Object with dbref #42 there could also be an Account or Script with the -dbref #42 at the same time. In almost all search methods you can replace the “key” search -criterion with "#dbref" to search for that id. This can occasionally be practical and may be what -you are used to from other code bases. But it is considered bad practice in Evennia to rely on -hard-coded #dbrefs to do your searches. It makes your code tied to the exact layout of the database. -It’s also not very maintainable to have to remember abstract numbers. Passing the actual objects -around and searching by Tags and/or keys will usually get you what you need.

  • -
-
-
-

Getting objects inside another

-

All in-game Objects have a .contents property that returns all objects ‘inside’ them -(that is, all objects which has its .location property set to that object. This is a simple way to -get everything in a room and is also faster since this lookup is cached and won’t hit the database.

-
    -
  • roomobj.contents returns a list of all objects inside roomobj.

  • -
  • obj.contents same as for a room, except this usually represents the object’s inventory

  • -
  • obj.location.contents gets everything in obj’s location (including obj itself).

  • -
  • roomobj.exits returns all exits starting from roomobj (Exits are here defined as Objects with -their destination field set).

  • -
  • obj.location.contents_get(exclude=obj) - this helper method returns all objects in obj’s -location except obj.

  • -
-
- - -
-

Queries in Django

-

This is an advanced topic.

-

Evennia’s search methods should be sufficient for the vast majority of situations. But eventually -you might find yourself trying to figure out how to get searches for unusual circumstances: Maybe -you want to find all characters who are not in rooms tagged as hangouts and have the lycanthrope -tag and whose names start with a vowel, but not with ‘Ab’, and only if they have 3 or more -objects in their inventory … You could in principle use one of the earlier search methods to find -all candidates and then loop over them with a lot of if statements in raw Python. But you can do -this much more efficiently by querying the database directly.

-

Enter django’s querysets. A QuerySet -is the representation of a database query and can be modified as desired. Only once one tries to -retrieve the data of that query is it evaluated and does an actual database request. This is -useful because it means you can modify a query as much as you want (even pass it around) and only -hit the database once you are happy with it. -Evennia’s search functions are themselves an even higher level wrapper around Django’s queries, and -many search methods return querysets. That means that you could get the result from a search -function and modify the resulting query to your own ends to further tweak what you search for.

-

Evaluated querysets can either contain objects such as Character objects, or lists of values derived -from the objects. Queries usually use the ‘manager’ object of a class, which by convention is the -.objects attribute of a class. For example, a query of Accounts that contain the letter ‘a’ could -be:

-
    from typeclasses.accounts import Account
-
-queryset = Account.objects.filter(username__contains='a')
-
-
-
-

The filter method of a manager takes arguments that allow you to define the query, and you can -continue to refine the query by calling additional methods until you evaluate the queryset, causing -the query to be executed and return a result. For example, if you have the result above, you could, -without causing the queryset to be evaluated yet, get rid of matches that contain the letter ‘e by -doing this:

-
queryset = result.exclude(username__contains='e')
-
-
-
-
-

You could also have chained .exclude directly to the end of the previous line.

-
-

Once you try to access the result, the queryset will be evaluated automatically under the hood:

-
accounts = list(queryset)  # this fills list with matches
-
-for account in queryset:
-    # do something with account
-
-accounts = queryset[:4]  # get first four matches
-account = queryset[0]  # get first match
-# etc
-
-
-
-
-

Limiting by typeclass

-

Although Characters, Exits, Rooms, and other children of DefaultObject all shares the same -underlying database table, Evennia provides a shortcut to do more specific queries only for those -typeclasses. For example, to find only Characters whose names start with ‘A’, you might do:

-
Character.objects.filter(db_key__startswith="A")
-
-
-
-

If Character has a subclass Npc and you wanted to find only Npc’s you’d instead do

-
Npc.objects.filter(db_key__startswith="A")
-
-
-
-

If you wanted to search both Characters and all its subclasses (like Npc) you use the *_family -method which is added by Evennia:

-
Character.objects.filter_family(db_key__startswith="A")
-
-
-

The higher up in the inheritance hierarchy you go the more objects will be included in these -searches. There is one special case, if you really want to include everything from a given -database table. You do that by searching on the database model itself. These are named ObjectDB, -AccountDB, ScriptDB etc.

-
from evennia import AccountDB
-
-# all Accounts in the database, regardless of typeclass
-all = AccountDB.objects.all()
-
-
-
-

Here are the most commonly used methods to use with the objects managers:

-
    -
  • filter - query for a listing of objects based on search criteria. Gives empty queryset if none -were found.

  • -
  • get - query for a single match - raises exception if none were found, or more than one was -found.

  • -
  • all - get all instances of the particular type.

  • -
  • filter_family - like filter, but search all sub classes as well.

  • -
  • get_family - like get, but search all sub classes as well.

  • -
  • all_family - like all, but return entities of all subclasses as well.

  • -
-
-
-
-

Multiple conditions

-

If you pass more than one keyword argument to a query method, the query becomes an AND -relationship. For example, if we want to find characters whose names start with “A” and are also -werewolves (have the lycanthrope tag), we might do:

-
queryset = Character.objects.filter(db_key__startswith="A", db_tags__db_key="lycanthrope")
-
-
-

To exclude lycanthropes currently in rooms tagged as hangouts, we might tack on an .exclude as -before:

-
queryset = quersyet.exclude(db_location__db_tags__db_key="hangout")
-
-
-

Note the syntax of the keywords in building the queryset. For example, db_location is the name of -the database field sitting on (in this case) the Character (Object). Double underscore __ works -like dot-notation in normal Python (it’s used since dots are not allowed in keyword names). So the -instruction db_location__db_tags__db_key="hangout" should be read as such:

-
    -
  1. “On the Character object … (this comes from us building this queryset using the -Character.objects manager)

  2. -
  3. … get the value of the db_location field … (this references a Room object, normally)

  4. -
  5. … on that location, get the value of the db_tags field … (this is a many-to-many field that -can be treated like an object for this purpose. It references all tags on the location)

  6. -
  7. … through the db_tag manager, find all Tags having a field db_key set to the value -“hangout”.”

  8. -
-

This may seem a little complex at first, but this syntax will work the same for all queries. Just -remember that all database-fields in Evennia are prefaced with db_. So even though Evennia is -nice enough to alias the db_key field so you can normally just do char.key to get a character’s -name, the database field is actually called db_key and the real name must be used for the purpose -of building a query.

-
-

Don’t confuse database fields with Attributes you set via obj.db.attr = 'foo' or -obj.attributes.add(). Attributes are custom database entities linked to an object. They are not -separate fields on that object like db_key or db_location are. You can get attached Attributes -manually through the db_attributes many-to-many field in the same way as db_tags above.

-
-
-

Complex queries

-

What if you want to have a query with with OR conditions or negated requirements (NOT)? Enter -Django’s Complex Query object, -Q. Q() -objects take a normal django keyword query as its arguments. The special thing is that these Q -objects can then be chained together with set operations: | for OR, & for AND, and preceded with -~ for NOT to build a combined, complex query.

-

In our original Lycanthrope example we wanted our werewolves to have names that could start with any -vowel except for the specific beginning “ab”.

-
from django.db.models import Q
-from typeclasses.characters import Character
-
-query = Q()
-for letter in ("aeiouy"):
-    query |= Q(db_key__istartswith=letter)
-query &= ~Q(db_key__istartswith="ab")
-query = Character.objects.filter(query)
-
-list_of_lycanthropes = list(query)
-
-
-

In the above example, we construct our query our of several Q objects that each represent one part -of the query. We iterate over the list of vowels, and add an OR condition to the query using |= -(this is the same idea as using += which may be more familiar). Each OR condition checks that -the name starts with one of the valid vowels. Afterwards, we add (using &=) an AND condition -that is negated with the ~ symbol. In other words we require that any match should not start -with the string “ab”. Note that we don’t actually hit the database until we convert the query to a -list at the end (we didn’t need to do that either, but could just have kept the query until we -needed to do something with the matches).

-
-
-

Annotations and F objects

-

What if we wanted to filter on some condition that isn’t represented easily by a field on the -object? Maybe we want to find rooms only containing five or more objects?

-

We could retrieve all interesting candidates and run them through a for-loop to get and count -their .content properties. We’d then just return a list of only those objects with enough -contents. It would look something like this (note: don’t actually do this!):

-
# probably not a good idea to do it this way
-
-from typeclasses.rooms import Room
-
-queryset = Room.objects.all()  # get all Rooms
-rooms = [room for room in queryset if len(room.contents) >= 5]
-
-
-
-

Once the number of rooms in your game increases, this could become quite expensive. Additionally, in -some particular contexts, like when using the web features of Evennia, you must have the result as a -queryset in order to use it in operations, such as in Django’s admin interface when creating list -filters.

-

Enter F objects and -annotations. So-called F expressions allow you to do a query that looks at a value of each object -in the database, while annotations allow you to calculate and attach a value to a query. So, let’s -do the same example as before directly in the database:

-
from typeclasses.rooms import Room
-from django.db.models import Count
-
-room_count = Room.objects.annotate(num_objects=Count('locations_set'))
-queryset = room_count.filter(num_objects__gte=5)
-
-rooms = (Room.objects.annotate(num_objects=Count('locations_set'))
-                     .filter(num_objects__gte=5))
-
-rooms = list(rooms)
-
-
-
-

Here we first create an annotation num_objects of type Count, which is a Django class. Note that -use of location_set in that Count. The *_set is a back-reference automatically created by -Django. In this case it allows you to find all objects that has the current object as location. -Once we have those, they are counted. -Next we filter on this annotation, using the name num_objects as something we can filter for. We -use num_objects__gte=5 which means that num_objects should be greater than 5. This is a little -harder to get one’s head around but much more efficient than lopping over all objects in Python.

-

What if we wanted to compare two parameters against one another in a query? For example, what if -instead of having 5 or more objects, we only wanted objects that had a bigger inventory than they -had tags? Here an F-object comes in handy:

-
from django.db.models import Count, F
-from typeclasses.rooms import Room
-
-result = (Room.objects.annotate(num_objects=Count('locations_set'),
-                                num_tags=Count('db_tags'))
-                      .filter(num_objects__gt=F('num_tags')))
-
-
-

F-objects allows for wrapping an annotated structure on the right-hand-side of the expression. It -will be evaluated on-the-fly as needed.

-
-
-

Grouping By and Values

-

Suppose you used tags to mark someone belonging an organization. Now you want to make a list and -need to get the membership count of every organization all at once. That’s where annotations and the -.values_list queryset method come in. Values/Values Lists are an alternate way of returning a -queryset - instead of objects, you get a list of dicts or tuples that hold selected properties from -the the matches. It also allows you a way to ‘group up’ queries for returning information. For -example, to get a display about each tag per Character and the names of the tag:

-
result = (Character.objects.filter(db_tags__db_category="organization")
-                           .values_list('db_tags__db_key')
-                           .annotate(cnt=Count('id'))
-                           .order_by('-cnt'))
-
-
-

The result queryset will be a list of tuples ordered in descending order by the number of matches, -in a format like the following:

-
[('Griatch Fanclub', 3872), ("Chainsol's Ainneve Testers", 2076), ("Blaufeuer's Whitespace Fixers",
-1903),
- ("Volund's Bikeshed Design Crew", 1764), ("Tehom's Misanthropes", 1)]
-
-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tutorial-Tweeting-Game-Stats.html b/docs/0.9.5/Tutorial-Tweeting-Game-Stats.html deleted file mode 100644 index b6650e53c8..0000000000 --- a/docs/0.9.5/Tutorial-Tweeting-Game-Stats.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - - Tutorial Tweeting Game Stats — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tutorial Tweeting Game Stats

-

This tutorial will create a simple script that will send a tweet to your already configured twitter -account. Please see: How to connect Evennia to Twitter if you -haven’t already done so.

-

The script could be expanded to cover a variety of statistics you might wish to tweet about -regularly, from player deaths to how much currency is in the economy etc.

-
# evennia/typeclasses/tweet_stats.py
-
-import twitter
-from random import randint
-from django.conf import settings
-from evennia import ObjectDB
-from evennia import spawner
-from evennia import logger
-from evennia import DefaultScript
-
-class TweetStats(DefaultScript):
-    """
-    This implements the tweeting of stats to a registered twitter account
-    """
-
-    # standard Script hooks
-
-    def at_script_creation(self):
-        "Called when script is first created"
-
-        self.key = "tweet_stats"
-        self.desc = "Tweets interesting stats about the game"
-        self.interval = 86400  # 1 day timeout
-        self.start_delay = False
-        
-    def at_repeat(self):
-        """
-        This is called every self.interval seconds to tweet interesting stats about the game.
-        """
-        
-        api = twitter.Api(consumer_key='consumer_key',
-          consumer_secret='consumer_secret',
-          access_token_key='access_token_key',
-          access_token_secret='access_token_secret')
-        
-        number_tweet_outputs = 2
-
-        tweet_output = randint(1, number_tweet_outputs)
-
-        if tweet_output == 1:
-        ##Game Chars, Rooms, Objects taken from @stats command
-            nobjs = ObjectDB.objects.count()
-            base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS
-            nchars = ObjectDB.objects.filter(db_typeclass_path=base_char_typeclass).count()
-            nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=bas
-e_char_typeclass).count()
-            nexits = ObjectDB.objects.filter(db_location__isnull=False,
-db_destination__isnull=False).count()
-            nother = nobjs - nchars - nrooms - nexits
-            tweet = "Chars: %s, Rooms: %s, Objects: %s" %(nchars, nrooms, nother)
-        else:
-            if tweet_output == 2: ##Number of prototypes and 3 random keys - taken from @spawn
-command
-                prototypes = spawner.spawn(return_prototypes=True)
-            
-                keys = prototypes.keys()
-                nprots = len(prototypes)
-                tweet = "Prototype Count: %s  Random Keys: " % nprots
-
-                tweet += " %s" % keys[randint(0,len(keys)-1)]
-                for x in range(0,2): ##tweet 3
-                    tweet += ", %s" % keys[randint(0,len(keys)-1)]
-        # post the tweet
-        try:
-            response = api.PostUpdate(tweet)
-        except:
-            logger.log_trace("Tweet Error: When attempting to tweet %s" % tweet)
-
-
-

In the at_script_creation method, we configure the script to fire immediately (useful for testing) -and setup the delay (1 day) as well as script information seen when you use @scripts

-

In the at_repeat method (which is called immediately and then at interval seconds later) we setup -the Twitter API (just like in the initial configuration of twitter). numberTweetOutputs is used to -show how many different types of outputs we have (in this case 2). We then build the tweet based on -randomly choosing between these outputs.

-
    -
  1. Shows the number of Player Characters, Rooms and Other/Objects

  2. -
  3. Shows the number of prototypes currently in the game and then selects 3 random keys to show

  4. -
-

Scripts Information will show you how to add it as a Global script, however, for testing -it may be useful to start/stop it quickly from within the game. Assuming that you create the file -as mygame/typeclasses/tweet_stats.py it can be started by using the following command

-
@script Here = tweet_stats.TweetStats
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tutorial-Vehicles.html b/docs/0.9.5/Tutorial-Vehicles.html deleted file mode 100644 index dfd9f2b286..0000000000 --- a/docs/0.9.5/Tutorial-Vehicles.html +++ /dev/null @@ -1,504 +0,0 @@ - - - - - - - - - Tutorial Vehicles — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tutorial Vehicles

-

This tutorial explains how you can create vehicles that can move around in your world. The tutorial -will explain how to create a train, but this can be equally applied to create other kind of vehicles -(cars, planes, boats, spaceships, submarines, …).

-
-

How it works

-

Objects in Evennia have an interesting property: you can put any object inside another object. This -is most obvious in rooms: a room in Evennia is just like any other game object (except rooms tend to -not themselves be inside anything else).

-

Our train will be similar: it will be an object that other objects can get inside. We then simply -move the Train, which brings along everyone inside it.

-
-
-

Creating our train object

-

The first step we need to do is create our train object, including a new typeclass. To do this, -create a new file, for instance in mygame/typeclasses/train.py with the following content:

-
# file mygame/typeclasses/train.py
-
-from evennia import DefaultObject
-
-class TrainObject(DefaultObject):
-
-    def at_object_creation(self):
-        # We'll add in code here later.
-        pass
-
-
-
-

Now we can create our train in our game:

-
@create/drop train:train.TrainObject
-
-
-

Now this is just an object that doesn’t do much yet… but we can already force our way inside it -and back (assuming we created it in limbo).

-
@tel train
-@tel limbo
-
-
-
-
-

Entering and leaving the train

-

Using the @telcommand like shown above is obviously not what we want. @tel is an admin command -and normal players will thus never be able to enter the train! It is also not really a good idea to -use Exits to get in and out of the train - Exits are (at least by default) objects -too. They point to a specific destination. If we put an Exit in this room leading inside the train -it would stay here when the train moved away (still leading into the train like a magic portal!). In -the same way, if we put an Exit object inside the train, it would always point back to this room, -regardless of where the Train has moved. Now, one could define custom Exit types that move with -the train or change their destination in the right way - but this seems to be a pretty cumbersome -solution.

-

What we will do instead is to create some new commands: one for entering the train and -another for leaving it again. These will be stored on the train object and will thus be made -available to whomever is either inside it or in the same room as the train.

-

Let’s create a new command module as mygame/commands/train.py:

-
# mygame/commands/train.py
-
-from evennia import Command, CmdSet
-
-class CmdEnterTrain(Command):
-    """
-    entering the train
-    
-    Usage:
-      enter train
-
-    This will be available to players in the same location
-    as the train and allows them to embark.
-    """
-
-    key = "enter train"
-    locks = "cmd:all()"
-
-    def func(self):
-        train = self.obj
-        self.caller.msg("You board the train.")
-        self.caller.move_to(train)
-
-
-class CmdLeaveTrain(Command):
-    """
-    leaving the train
- 
-    Usage:
-      leave train
-
-    This will be available to everyone inside the
-    train. It allows them to exit to the train's
-    current location.
-    """
-
-    key = "leave train"
-    locks = "cmd:all()"
-
-    def func(self):
-        train = self.obj
-        parent = train.location
-        self.caller.move_to(parent)
-
-
-class CmdSetTrain(CmdSet):
-
-    def at_cmdset_creation(self):
-        self.add(CmdEnterTrain())
-        self.add(CmdLeaveTrain())
-
-
-

Note that while this seems like a lot of text, the majority of lines here are taken up by -documentation.

-

These commands are work in a pretty straightforward way: CmdEnterTrain moves the location of the -player to inside the train and CmdLeaveTrain does the opposite: it moves the player back to the -current location of the train (back outside to its current location). We stacked them in a -cmdset CmdSetTrain so they can be used.

-

To make the commands work we need to add this cmdset to our train typeclass:

-
# file mygame/typeclasses/train.py
-
-from evennia import DefaultObject
-from commands.train import CmdSetTrain
-
-class TrainObject(DefaultObject):
-
-    def at_object_creation(self):
-        self.cmdset.add_default(CmdSetTrain)
-
-
-
-

If we now @reload our game and reset our train, those commands should work and we can now enter -and leave the train:

-
@reload
-@typeclass/force/reset train = train.TrainObject
-enter train
-leave train
-
-
-

Note the switches used with the @typeclass command: The /force switch is necessary to assign our -object the same typeclass we already have. The /reset re-triggers the typeclass’ -at_object_creation() hook (which is otherwise only called the very first an instance is created). -As seen above, when this hook is called on our train, our new cmdset will be loaded.

-
-
-

Locking down the commands

-

If you have played around a bit, you’ve probably figured out that you can use leave train when -outside the train and enter train when inside. This doesn’t make any sense … so let’s go ahead -and fix that. We need to tell Evennia that you can not enter the train when you’re already inside -or leave the train when you’re outside. One solution to this is locks: we will lock down -the commands so that they can only be called if the player is at the correct location.

-

Right now commands defaults to the lock cmd:all(). The cmd lock type in combination with the -all() lock function means that everyone can run those commands as long as they are in the same -room as the train or inside the train. We’re going to change this to check the location of the -player and only allow access if they are inside the train.

-

First of all we need to create a new lock function. Evennia comes with many lock functions built-in -already, but none that we can use for locking a command in this particular case. Create a new entry -in mygame/server/conf/lockfuncs.py:

-

-# file mygame/server/conf/lockfuncs.py
-
-def cmdinside(accessing_obj, accessed_obj, *args, **kwargs):
-    """
-    Usage: cmdinside()
-    Used to lock commands and only allows access if the command
-    is defined on an object which accessing_obj is inside of.
-    """
-    return accessed_obj.obj == accessing_obj.location
-
-
-
-

If you didn’t know, Evennia is by default set up to use all functions in this module as lock -functions (there is a setting variable that points to it).

-

Our new lock function, cmdinside, is to be used by Commands. The accessed_obj is the Command -object (in our case this will be CmdEnterTrain and CmdLeaveTrain) — Every command has an obj -property: this is the the object on which the command “sits”. Since we added those commands to our -train object, the .obj property will be set to the train object. Conversely, accessing_obj is -the object that called the command: in our case it’s the Character trying to enter or leave the -train.

-

What this function does is to check that the player’s location is the same as the train object. If -it is, it means the player is inside the train. Otherwise it means the player is somewhere else and -the check will fail.

-

The next step is to actually use this new lock function to create a lock of type cmd:

-
# file commands/train.py
-...
-class CmdEnterTrain(Command):
-    key = "enter train"
-    locks = "cmd:not cmdinside()"
-    # ...
-
-class CmdLeaveTrain(Command):
-    key = "leave train"
-    locks = "cmd:cmdinside()"
-    # ...
-
-
-

Notice how we use the not here so that we can use the same cmdinside to check if we are inside -and outside, without having to create two separate lock functions. After a @reload our commands -should be locked down appropriately and you should only be able to use them at the right places.

-
-

Note: If you’re logged in as the super user (user #1) then this lock will not work: the super -user ignores lock functions. In order to use this functionality you need to @quell first.

-
-
-
-

Making our train move

-

Now that we can enter and leave the train correctly, it’s time to make it move. There are different -things we need to consider for this:

-
    -
  • Who can control your vehicle? The first player to enter it, only players that have a certain -“drive” skill, automatically?

  • -
  • Where should it go? Can the player steer the vehicle to go somewhere else or will it always follow -the same route?

  • -
-

For our example train we’re going to go with automatic movement through a predefined route (its -track). The train will stop for a bit at the start and end of the route to allow players to enter -and leave it.

-

Go ahead and create some rooms for our train. Make a list of the room ids along the route (using the -@ex command).

-
@dig/tel South station
-@ex              # note the id of the station
-@tunnel/tel n = Following a railroad
-@ex              # note the id of the track
-@tunnel/tel n = Following a railroad
-...
-@tunnel/tel n = North Station
-
-
-

Put the train onto the tracks:

-
@tel south station
-@tel train = here
-
-
-

Next we will tell the train how to move and which route to take.

-
# file typeclasses/train.py
-
-from evennia import DefaultObject, search_object
-
-from commands.train import CmdSetTrain
-
-class TrainObject(DefaultObject):
-
-    def at_object_creation(self):
-        self.cmdset.add_default(CmdSetTrain)
-        self.db.driving = False
-        # The direction our train is driving (1 for forward, -1 for backwards)
-        self.db.direction = 1
-        # The rooms our train will pass through (change to fit your game)
-        self.db.rooms = ["#2", "#47", "#50", "#53", "#56", "#59"]
-
-    def start_driving(self):
-        self.db.driving = True
-
-    def stop_driving(self):
-        self.db.driving = False
-
-    def goto_next_room(self):
-        currentroom = self.location.dbref
-        idx = self.db.rooms.index(currentroom) + self.db.direction
-
-        if idx < 0 or idx >= len(self.db.rooms):
-            # We reached the end of our path
-            self.stop_driving()
-            # Reverse the direction of the train
-            self.db.direction *= -1
-        else:
-            roomref = self.db.rooms[idx]
-            room = search_object(roomref)[0]
-            self.move_to(room)
-            self.msg_contents("The train is moving forward to %s." % (room.name, ))
-
-
-

We added a lot of code here. Since we changed the at_object_creation to add in variables we will -have to reset our train object like earlier (using the @typeclass/force/reset command).

-

We are keeping track of a few different things now: whether the train is moving or standing still, -which direction the train is heading to and what rooms the train will pass through.

-

We also added some methods: one to start moving the train, another to stop and a third that actually -moves the train to the next room in the list. Or makes it stop driving if it reaches the last stop.

-

Let’s try it out, using @py to call the new train functionality:

-
@reload
-@typeclass/force/reset train = train.TrainObject
-enter train
-@py here.goto_next_room()
-
-
-

You should see the train moving forward one step along the rail road.

-
-
-

Adding in scripts

-

If we wanted full control of the train we could now just add a command to step it along the track -when desired. We want the train to move on its own though, without us having to force it by manually -calling the goto_next_room method.

-

To do this we will create two scripts: one script that runs when the train has stopped at -a station and is responsible for starting the train again after a while. The other script will take -care of the driving.

-

Let’s make a new file in mygame/typeclasses/trainscript.py

-
# file mygame/typeclasses/trainscript.py
-
-from evennia import DefaultScript
-
-class TrainStoppedScript(DefaultScript):
-
-    def at_script_creation(self):
-        self.key = "trainstopped"
-        self.interval = 30
-        self.persistent = True
-        self.repeats = 1
-        self.start_delay = True
-
-    def at_repeat(self):
-        self.obj.start_driving()
-
-    def at_stop(self):
-        self.obj.scripts.add(TrainDrivingScript)
-
-
-class TrainDrivingScript(DefaultScript):
-
-    def at_script_creation(self):
-        self.key = "traindriving"
-        self.interval = 1
-        self.persistent = True
-
-    def is_valid(self):
-        return self.obj.db.driving
-
-    def at_repeat(self):
-        if not self.obj.db.driving:
-            self.stop()
-        else:
-            self.obj.goto_next_room()
-
-    def at_stop(self):
-        self.obj.scripts.add(TrainStoppedScript)
-
-
-

Those scripts work as a state system: when the train is stopped, it waits for 30 seconds and then -starts again. When the train is driving, it moves to the next room every second. The train is always -in one of those two states - both scripts take care of adding the other one once they are done.

-

As a last step we need to link the stopped-state script to our train, reload the game and reset our -train again., and we’re ready to ride it around!

-
# file typeclasses/train.py
-
-from typeclasses.trainscript import TrainStoppedScript
-
-class TrainObject(DefaultObject):
-
-    def at_object_creation(self):
-        # ...
-        self.scripts.add(TrainStoppedScript)
-
-
-
@reload
-@typeclass/force/reset train = train.TrainObject
-enter train
-
-# output:
-< The train is moving forward to Following a railroad.
-< The train is moving forward to Following a railroad.
-< The train is moving forward to Following a railroad.
-...
-< The train is moving forward to Following a railroad.
-< The train is moving forward to North station.
-
-leave train
-
-
-

Our train will stop 30 seconds at each end station and then turn around to go back to the other end.

-
-
-

Expanding

-

This train is very basic and still has some flaws. Some more things to do:

-
    -
  • Make it look like a train.

  • -
  • Make it impossible to exit and enter the train mid-ride. This could be made by having the -enter/exit commands check so the train is not moving before allowing the caller to proceed.

  • -
  • Have train conductor commands that can override the automatic start/stop.

  • -
  • Allow for in-between stops between the start- and end station

  • -
  • Have a rail road track instead of hard-coding the rooms in the train object. This could for -example be a custom Exit only traversable by trains. The train will follow the -track. Some track segments can split to lead to two different rooms and a player can switch the -direction to which room it goes.

  • -
  • Create another kind of vehicle!

  • -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tutorial-World-Introduction.html b/docs/0.9.5/Tutorial-World-Introduction.html deleted file mode 100644 index 8518cdfd5e..0000000000 --- a/docs/0.9.5/Tutorial-World-Introduction.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - Tutorial World Introduction — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tutorial World Introduction

-

The Tutorial World is a small and functioning MUD-style game world. It is intended to be -deconstructed and used as a way to learn Evennia. The game consists of a single-player quest and -has some 20 rooms that you can explore as you seek to discover the whereabouts of a mythical weapon.

-

The source code is fully documented. You can find the whole thing in -evennia/contrib/tutorial_world/.

-

Some features exemplified by the tutorial world:

-
    -
  • Tutorial command, giving “behind-the-scenes” help for every room and some of the special objects

  • -
  • Rooms with custom return_appearance to show details.

  • -
  • Hidden exits

  • -
  • Objects with multiple custom interactions

  • -
  • Large-area rooms

  • -
  • Outdoor weather rooms

  • -
  • Dark room, needing light source

  • -
  • Puzzle object

  • -
  • Multi-room puzzle

  • -
  • Aggressive mobile with roam, pursue and battle state-engine AI

  • -
  • Weapons, also used by mobs

  • -
  • Simple combat system with attack/defend commands

  • -
  • Object spawning

  • -
  • Teleporter trap rooms

  • -
-
-

Install

-

The tutorial world consists of a few modules in evennia/contrib/tutorial_world/ containing custom -Typeclasses for rooms and objects and associated Commands.

-

These reusable bits and pieces are then put together into a functioning game area (“world” is maybe -too big a word for such a small zone) using a batch script called build.ev. To -install, log into the server as the superuser (user #1) and run:

-
@batchcommand tutorial_world.build
-
-
-

The world will be built (this might take a while, so don’t rerun the command even if it seems the -system has frozen). After finishing you will end up back in Limbo with a new exit called tutorial.

-

An alternative is

-
@batchcommand/interactive tutorial_world.build
-
-
-

with the /interactive switch you are able to step through the building process at your own pace to -see what happens in detail.

-
-
-

Quelling and permissions in the tutorial-world

-

Non-superusers entering the tutorial will be auto-quelled so they play with their Character’s -permission. As superuser you will not be auto-quelled, but it’s recommended that you still quell -manually to play the tutorial “correctly”. The reason for this is that many game systems ignore the -presence of a superuser and will thus not work as normal.

-

Use unquell if you want to get back your main account-level permissions to examine things under -the hood. When you exit the tutorial (either by winning or using the abort/give up command) you -will automatically be unquelled.

-
-
-

Gameplay

-

the castle off the moor

-

To get into the mood of this miniature quest, imagine you are an adventurer out to find fame and -fortune. You have heard rumours of an old castle ruin by the coast. In its depth a warrior princess -was buried together with her powerful magical weapon - a valuable prize, if it’s true. Of course -this is a chance to adventure that you cannot turn down!

-

You reach the ocean in the midst of a raging thunderstorm. With wind and rain screaming in your -face you stand where the moor meets the sea along a high, rocky coast …

-
    -
  • Look at everything.

  • -
  • Some objects are interactive in more than one way. Use the normal help command to get a feel for -which commands are available at any given time. (use the command tutorial to get insight behind -the scenes of the tutorial).

  • -
  • In order to fight, you need to first find some type of weapon.

  • -
  • slash is a normal attack

  • -
  • stab launches an attack that makes more damage but has a lower chance to hit.

  • -
  • defend will lower the chance to taking damage on your enemy’s next attack.

  • -
  • You can run from a fight that feels too deadly. Expect to be chased though.

  • -
  • Being defeated is a part of the experience …

  • -
-
-
-

Uninstall

-

Uninstalling the tutorial world basically means deleting all the rooms and objects it consists of. -First, move out of the tutorial area.

-
 @find tut#01
- @find tut#16
-
-
-

This should locate the first and last rooms created by build.ev - Intro and Outro. If you -installed normally, everything created between these two numbers should be part of the tutorial. -Note their dbref numbers, for example 5 and 80. Next we just delete all objects in that range:

-
 @del 5-80
-
-
-

You will see some errors since some objects are auto-deleted and so cannot be found when the delete -mechanism gets to them. That’s fine. You should have removed the tutorial completely once the -command finishes.

-
-
-

Notes

-

When reading and learning from the code, keep in mind that Tutorial World was created with a very -specific goal: to install easily and to not permanently modify the rest of the server. It therefore -goes to some length to use only temporary solutions and to clean up after -itself.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tutorial-for-basic-MUSH-like-game.html b/docs/0.9.5/Tutorial-for-basic-MUSH-like-game.html deleted file mode 100644 index e9b5c45165..0000000000 --- a/docs/0.9.5/Tutorial-for-basic-MUSH-like-game.html +++ /dev/null @@ -1,739 +0,0 @@ - - - - - - - - - Tutorial for basic MUSH like game — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tutorial for basic MUSH like game

-

This tutorial lets you code a small but complete and functioning MUSH-like game in Evennia. A -MUSH is, for our purposes, a class of roleplay-centric games -focused on free form storytelling. Even if you are not interested in MUSH:es, this is still a good -first game-type to try since it’s not so code heavy. You will be able to use the same principles for -building other types of games.

-

The tutorial starts from scratch. If you did the First Steps Coding tutorial -already you should have some ideas about how to do some of the steps already.

-

The following are the (very simplistic and cut-down) features we will implement (this was taken from -a feature request from a MUSH user new to Evennia). A Character in this system should:

-
    -
  • Have a “Power” score from 1 to 10 that measures how strong they are (stand-in for the stat -system).

  • -
  • Have a command (e.g. +setpower 4) that sets their power (stand-in for character generation -code).

  • -
  • Have a command (e.g. +attack) that lets them roll their power and produce a “Combat Score” -between 1 and 10*Power, displaying the result and editing their object to record this number -(stand-in for +actions in the command code).

  • -
  • Have a command that displays everyone in the room and what their most recent “Combat Score” roll -was (stand-in for the combat code).

  • -
  • Have a command (e.g. +createNPC Jenkins) that creates an NPC with full abilities.

  • -
  • Have a command to control NPCs, such as +npc/cmd (name)=(command) (stand-in for the NPC -controlling code).

  • -
-

In this tutorial we will assume you are starting from an empty database without any previous -modifications.

-
-

Server Settings

-

To emulate a MUSH, the default MULTISESSION_MODE=0 is enough (one unique session per -account/character). This is the default so you don’t need to change anything. You will still be able -to puppet/unpuppet objects you have permission to, but there is no character selection out of the -box in this mode.

-

We will assume our game folder is called mygame henceforth. You should be fine with the default -SQLite3 database.

-
-
-

Creating the Character

-

First thing is to choose how our Character class works. We don’t need to define a special NPC object -– an NPC is after all just a Character without an Account currently controlling them.

-

Make your changes in the mygame/typeclasses/characters.py file:

-
# mygame/typeclasses/characters.py
-
-from evennia import DefaultCharacter
-
-class Character(DefaultCharacter):
-    """
-     [...]
-    """
-    def at_object_creation(self):
-        "This is called when object is first created, only."
-        self.db.power = 1
-        self.db.combat_score = 1
-
-
-

We defined two new Attributes power and combat_score and set them to default -values. Make sure to @reload the server if you had it already running (you need to reload every -time you update your python code, don’t worry, no accounts will be disconnected by the reload).

-

Note that only new characters will see your new Attributes (since the at_object_creation hook is -called when the object is first created, existing Characters won’t have it). To update yourself, -run

-
 @typeclass/force self
-
-
-

This resets your own typeclass (the /force switch is a safety measure to not do this -accidentally), this means that at_object_creation is re-run.

-
 examine self
-
-
-

Under the “Persistent attributes” heading you should now find the new Attributes power and score -set on yourself by at_object_creation. If you don’t, first make sure you @reloaded into the new -code, next look at your server log (in the terminal/console) to see if there were any syntax errors -in your code that may have stopped your new code from loading correctly.

-
-
-

Character Generation

-

We assume in this example that Accounts first connect into a “character generation area”. Evennia -also supports full OOC menu-driven character generation, but for this example, a simple start room -is enough. When in this room (or rooms) we allow character generation commands. In fact, character -generation commands will only be available in such rooms.

-

Note that this again is made so as to be easy to expand to a full-fledged game. With our simple -example, we could simply set an is_in_chargen flag on the account and have the +setpower command -check it. Using this method however will make it easy to add more functionality later.

-

What we need are the following:

-
    -
  • One character generation Command to set the “Power” on the Character.

  • -
  • A chargen CmdSet to hold this command. Lets call it ChargenCmdset.

  • -
  • A custom ChargenRoom type that makes this set of commands available to players in such rooms.

  • -
  • One such room to test things in.

  • -
-
-

The +setpower command

-

For this tutorial we will add all our new commands to mygame/commands/command.py but you could -split your commands into multiple module if you prefered.

-

For this tutorial character generation will only consist of one Command to set the -Character s “power” stat. It will be called on the following MUSH-like form:

-
 +setpower 4
-
-
-

Open command.py file. It contains documented empty templates for the base command and the -“MuxCommand” type used by default in Evennia. We will use the plain Command type here, the -MuxCommand class offers some extra features like stripping whitespace that may be useful - if so, -just import from that instead.

-

Add the following to the end of the command.py file:

-
# end of command.py
-from evennia import Command # just for clarity; already imported above
-
-class CmdSetPower(Command):
-    """
-    set the power of a character
-
-    Usage:
-      +setpower <1-10>
-
-    This sets the power of the current character. This can only be
-    used during character generation.
-    """
-    
-    key = "+setpower"
-    help_category = "mush"
-
-    def func(self):
-        "This performs the actual command"
-        errmsg = "You must supply a number between 1 and 10."
-        if not self.args:
-            self.caller.msg(errmsg)
-            return
-        try:
-            power = int(self.args)
-        except ValueError:
-            self.caller.msg(errmsg)
-            return
-        if not (1 <= power <= 10):
-            self.caller.msg(errmsg)
-            return
-        # at this point the argument is tested as valid. Let's set it.
-        self.caller.db.power = power
-        self.caller.msg("Your Power was set to %i." % power)
-
-
-

This is a pretty straightforward command. We do some error checking, then set the power on ourself. -We use a help_category of “mush” for all our commands, just so they are easy to find and separate -in the help list.

-

Save the file. We will now add it to a new CmdSet so it can be accessed (in a full -chargen system you would of course have more than one command here).

-

Open mygame/commands/default_cmdsets.py and import your command.py module at the top. We also -import the default CmdSet class for the next step:

-
from evennia import CmdSet
-from commands import command
-
-
-

Next scroll down and define a new command set (based on the base CmdSet class we just imported at -the end of this file, to hold only our chargen-specific command(s):

-
# end of default_cmdsets.py
-
-class ChargenCmdset(CmdSet):
-    """
-    This cmdset it used in character generation areas.
-    """
-    key = "Chargen"
-    def at_cmdset_creation(self):
-        "This is called at initialization"
-        self.add(command.CmdSetPower())
-
-
-

In the future you can add any number of commands to this cmdset, to expand your character generation -system as you desire. Now we need to actually put that cmdset on something so it’s made available to -users. We could put it directly on the Character, but that would make it available all the time. -It’s cleaner to put it on a room, so it’s only available when players are in that room.

-
-
-

Chargen areas

-

We will create a simple Room typeclass to act as a template for all our Chargen areas. Edit -mygame/typeclasses/rooms.py next:

-
from commands.default_cmdsets import ChargenCmdset
-
-# ...
-# down at the end of rooms.py
-
-class ChargenRoom(Room):
-    """
-    This room class is used by character-generation rooms. It makes
-    the ChargenCmdset available.
-    """
-    def at_object_creation(self):
-        "this is called only at first creation"
-        self.cmdset.add(ChargenCmdset, permanent=True)
-
-
-

Note how new rooms created with this typeclass will always start with ChargenCmdset on themselves. -Don’t forget the permanent=True keyword or you will lose the cmdset after a server reload. For -more information about Command Sets and Commands, see the respective -links.

-
-
-

Testing chargen

-

First, make sure you have @reloaded the server (or use evennia reload from the terminal) to have -your new python code added to the game. Check your terminal and fix any errors you see - the error -traceback lists exactly where the error is found - look line numbers in files you have changed.

-

We can’t test things unless we have some chargen areas to test. Log into the game (you should at -this point be using the new, custom Character class). Let’s dig a chargen area to test.

-
 @dig chargen:rooms.ChargenRoom = chargen,finish
-
-
-

If you read the help for @dig you will find that this will create a new room named chargen. The -part after the : is the python-path to the Typeclass you want to use. Since Evennia will -automatically try the typeclasses folder of our game directory, we just specify -rooms.ChargenRoom, meaning it will look inside the module rooms.py for a class named -ChargenRoom (which is what we created above). The names given after = are the names of exits to -and from the room from your current location. You could also append aliases to each one name, such -as chargen;character generation.

-

So in summary, this will create a new room of type ChargenRoom and open an exit chargen to it and -an exit back here named finish. If you see errors at this stage, you must fix them in your code. -@reload -between fixes. Don’t continue until the creation seems to have worked okay.

-
 chargen
-
-
-

This should bring you to the chargen room. Being in there you should now have the +setpower -command available, so test it out. When you leave (via the finish exit), the command will go away -and trying +setpower should now give you a command-not-found error. Use ex me (as a privileged -user) to check so the Power Attribute has been set correctly.

-

If things are not working, make sure your typeclasses and commands are free of bugs and that you -have entered the paths to the various command sets and commands correctly. Check the logs or command -line for tracebacks and errors.

-
-
-
-

Combat System

-

We will add our combat command to the default command set, meaning it will be available to everyone -at all times. The combat system consists of a +attack command to get how successful our attack is. -We also change the default look command to display the current combat score.

-
-

Attacking with the +attack command

-

Attacking in this simple system means rolling a random “combat score” influenced by the power stat -set during Character generation:

-
> +attack
-You +attack with a combat score of 12!
-
-
-

Go back to mygame/commands/command.py and add the command to the end like this:

-
import random
-
-# ...
-
-class CmdAttack(Command):
-    """
-    issues an attack
-
-    Usage:
-        +attack
-
-    This will calculate a new combat score based on your Power.
-    Your combat score is visible to everyone in the same location.
-    """
-    key = "+attack"
-    help_category = "mush"
-
-    def func(self):
-        "Calculate the random score between 1-10*Power"
-        caller = self.caller
-        power = caller.db.power
-        if not power:
-            # this can happen if caller is not of
-            # our custom Character typeclass
-            power = 1
-        combat_score = random.randint(1, 10 * power)
-        caller.db.combat_score = combat_score
-
-        # announce
-        message = "%s +attack%s with a combat score of %s!"
-        caller.msg(message % ("You", "", combat_score))
-        caller.location.msg_contents(message %
-                                     (caller.key, "s", combat_score),
-                                     exclude=caller)
-
-
-

What we do here is simply to generate a “combat score” using Python’s inbuilt random.randint() -function. We then store that and echo the result to everyone involved.

-

To make the +attack command available to you in game, go back to -mygame/commands/default_cmdsets.py and scroll down to the CharacterCmdSet class. At the correct -place add this line:

-
self.add(command.CmdAttack())
-
-
-

@reload Evennia and the +attack command should be available to you. Run it and use e.g. @ex to -make sure the combat_score attribute is saved correctly.

-
-
-

Have “look” show combat scores

-

Players should be able to view all current combat scores in the room. We could do this by simply -adding a second command named something like +combatscores, but we will instead let the default -look command do the heavy lifting for us and display our scores as part of its normal output, like -this:

-
>  look Tom
-Tom (combat score: 3)
-This is a great warrior.
-
-
-

We don’t actually have to modify the look command itself however. To understand why, take a look -at how the default look is actually defined. It sits in evennia/commands/default/general.py (or -browse it online -here). -You will find that the actual return text is done by the look command calling a hook method -named return_appearance on the object looked at. All the look does is to echo whatever this hook -returns. So what we need to do is to edit our custom Character typeclass and overload its -return_appearance to return what we want (this is where the advantage of having a custom typeclass -comes into play for real).

-

Go back to your custom Character typeclass in mygame/typeclasses/characters.py. The default -implementation of return appearance is found in evennia.DefaultCharacter (or online -here). If you -want to make bigger changes you could copy & paste the whole default thing into our overloading -method. In our case the change is small though:

-
class Character(DefaultCharacter):
-    """
-     [...]
-    """
-    def at_object_creation(self):
-        "This is called when object is first created, only."
-        self.db.power = 1
-        self.db.combat_score = 1
-
-    def return_appearance(self, looker):
-        """
-        The return from this method is what
-        looker sees when looking at this object.
-        """
-        text = super().return_appearance(looker)
-        cscore = " (combat score: %s)" % self.db.combat_score
-        if "\n" in text:
-            # text is multi-line, add score after first line
-            first_line, rest = text.split("\n", 1)
-            text = first_line + cscore + "\n" + rest
-        else:
-            # text is only one line; add score to end
-            text += cscore
-        return text
-
-
-

What we do is to simply let the default return_appearance do its thing (super will call the -parent’s version of the same method). We then split out the first line of this text, append our -combat_score and put it back together again.

-

@reload the server and you should be able to look at other Characters and see their current combat -scores.

-
-

Note: A potentially more useful way to do this would be to overload the entire return_appearance -of the Rooms of your mush and change how they list their contents; in that way one could see all -combat scores of all present Characters at the same time as looking at the room. We leave this as an -exercise.

-
-
-
-
-

NPC system

-

Here we will re-use the Character class by introducing a command that can create NPC objects. We -should also be able to set its Power and order it around.

-

There are a few ways to define the NPC class. We could in theory create a custom typeclass for it -and put a custom NPC-specific cmdset on all NPCs. This cmdset could hold all manipulation commands. -Since we expect NPC manipulation to be a common occurrence among the user base however, we will -instead put all relevant NPC commands in the default command set and limit eventual access with -Permissions and Locks.

-
-

Creating an NPC with +createNPC

-

We need a command for creating the NPC, this is a very straightforward command:

-
> +createnpc Anna
-You created the NPC 'Anna'.
-
-
-

At the end of command.py, create our new command:

-
from evennia import create_object
-    
-class CmdCreateNPC(Command):
-    """
-    create a new npc
-
-    Usage:
-        +createNPC <name>
-
-    Creates a new, named NPC. The NPC will start with a Power of 1.
-    """
-    key = "+createnpc"
-    aliases = ["+createNPC"]
-    locks = "call:not perm(nonpcs)"
-    help_category = "mush"
-    
-    def func(self):
-        "creates the object and names it"
-        caller = self.caller
-        if not self.args:
-            caller.msg("Usage: +createNPC <name>")
-            return
-        if not caller.location:
-            # may not create npc when OOC
-            caller.msg("You must have a location to create an npc.")
-            return
-        # make name always start with capital letter
-        name = self.args.strip().capitalize()
-        # create npc in caller's location
-        npc = create_object("characters.Character",
-                      key=name,
-                      location=caller.location,
-                      locks="edit:id(%i) and perm(Builders);call:false()" % caller.id)
-        # announce
-        message = "%s created the NPC '%s'."
-        caller.msg(message % ("You", name))
-        caller.location.msg_contents(message % (caller.key, name),
-                                                exclude=caller)
-
-
-

Here we define a +createnpc (+createNPC works too) that is callable by everyone not having the -nonpcspermission” (in Evennia, a “permission” can just as well be used to -block access, it depends on the lock we define). We create the NPC object in the caller’s current -location, using our custom Character typeclass to do so.

-

We set an extra lock condition on the NPC, which we will use to check who may edit the NPC later – -we allow the creator to do so, and anyone with the Builders permission (or higher). See -Locks for more information about the lock system.

-

Note that we just give the object default permissions (by not specifying the permissions keyword -to the create_object() call). In some games one might want to give the NPC the same permissions -as the Character creating them, this might be a security risk though.

-

Add this command to your default cmdset the same way you did the +attack command earlier. -@reload and it will be available to test.

-
-
-

Editing the NPC with +editNPC

-

Since we re-used our custom character typeclass, our new NPC already has a Power value - it -defaults to 1. How do we change this?

-

There are a few ways we can do this. The easiest is to remember that the power attribute is just a -simple Attribute stored on the NPC object. So as a Builder or Admin we could set this -right away with the default @set command:

-
 @set mynpc/power = 6
-
-
-

The @set command is too generally powerful though, and thus only available to staff. We will add a -custom command that only changes the things we want players to be allowed to change. We could in -principle re-work our old +setpower command, but let’s try something more useful. Let’s make a -+editNPC command.

-
> +editNPC Anna/power = 10
-Set Anna's property 'power' to 10.
-
-
-

This is a slightly more complex command. It goes at the end of your command.py file as before.

-
class CmdEditNPC(Command):
-    """
-    edit an existing NPC
-
-    Usage:
-      +editnpc <name>[/<attribute> [= value]]
-
-    Examples:
-      +editnpc mynpc/power = 5
-      +editnpc mynpc/power    - displays power value
-      +editnpc mynpc          - shows all editable
-                                attributes and values
-
-    This command edits an existing NPC. You must have
-    permission to edit the NPC to use this.
-    """
-    key = "+editnpc"
-    aliases = ["+editNPC"]
-    locks = "cmd:not perm(nonpcs)"
-    help_category = "mush"
-
-    def parse(self):
-        "We need to do some parsing here"
-        args = self.args
-        propname, propval = None, None
-        if "=" in args:
-            args, propval = [part.strip() for part in args.rsplit("=", 1)]
-        if "/" in args:
-            args, propname = [part.strip() for part in args.rsplit("/", 1)]
-        # store, so we can access it below in func()
-        self.name = args
-        self.propname = propname
-        # a propval without a propname is meaningless
-        self.propval = propval if propname else None
-
-    def func(self):
-        "do the editing"
-
-        allowed_propnames = ("power", "attribute1", "attribute2")
-
-        caller = self.caller
-        if not self.args or not self.name:
-            caller.msg("Usage: +editnpc name[/propname][=propval]")
-            return
-        npc = caller.search(self.name)
-        if not npc:
-            return
-        if not npc.access(caller, "edit"):
-            caller.msg("You cannot change this NPC.")
-            return
-        if not self.propname:
-            # this means we just list the values
-            output = f"Properties of {npc.key}:"
-            for propname in allowed_propnames:
-                output += f"\n {propname} = {npc.attributes.get(propname, default='N/A')}"
-            caller.msg(output)
-        elif self.propname not in allowed_propnames:
-            caller.msg(f"You may only change {', '.join(allowed_propnames)}.")
-        elif self.propval:
-            # assigning a new propvalue
-            # in this example, the properties are all integers...
-            intpropval = int(self.propval)
-            npc.attributes.add(self.propname, intpropval)
-            caller.msg(f"Set {npc.key}'s property {self.propname} to {self.propval}")
-        else:
-            # propname set, but not propval - show current value
-            caller.msg(f"{npc.key} has property {self.propname} = {npc.attributes.get(self.propname,
-default='N/A')}")
-
-
-

This command example shows off the use of more advanced parsing but otherwise it’s mostly error -checking. It searches for the given npc in the same room, and checks so the caller actually has -permission to “edit” it before continuing. An account without the proper permission won’t even be -able to view the properties on the given NPC. It’s up to each game if this is the way it should be.

-

Add this to the default command set like before and you should be able to try it out.

-

Note: If you wanted a player to use this command to change an on-object property like the NPC’s -name (the key property), you’d need to modify the command since “key” is not an Attribute (it is -not retrievable via npc.attributes.get but directly via npc.key). We leave this as an optional -exercise.

-
-
-

Making the NPC do stuff - the +npc command

-

Finally, we will make a command to order our NPC around. For now, we will limit this command to only -be usable by those having the “edit” permission on the NPC. This can be changed if it’s possible for -anyone to use the NPC.

-

The NPC, since it inherited our Character typeclass has access to most commands a player does. What -it doesn’t have access to are Session and Player-based cmdsets (which means, among other things that -they cannot chat on channels, but they could do that if you just added those commands). This makes -the +npc command simple:

-
+npc Anna = say Hello!
-Anna says, 'Hello!'
-
-
-

Again, add to the end of your command.py module:

-
class CmdNPC(Command):
-    """
-    controls an NPC
-
-    Usage:
-        +npc <name> = <command>
-
-    This causes the npc to perform a command as itself. It will do so
-    with its own permissions and accesses.
-    """
-    key = "+npc"
-    locks = "call:not perm(nonpcs)"
-    help_category = "mush"
-
-    def parse(self):
-        "Simple split of the = sign"
-        name, cmdname = None, None
-        if "=" in self.args:
-            name, cmdname = self.args.rsplit("=", 1)
-            name = name.strip()
-            cmdname = cmdname.strip()
-        self.name, self.cmdname = name, cmdname
-
-    def func(self):
-        "Run the command"
-        caller = self.caller
-        if not self.cmdname:
-            caller.msg("Usage: +npc <name> = <command>")
-            return
-        npc = caller.search(self.name)
-        if not npc:
-            return
-        if not npc.access(caller, "edit"):
-            caller.msg("You may not order this NPC to do anything.")
-            return
-        # send the command order
-        npc.execute_cmd(self.cmdname)
-        caller.msg(f"You told {npc.key} to do '{self.cmdname}'.")
-
-
-

Note that if you give an erroneous command, you will not see any error message, since that error -will be returned to the npc object, not to you. If you want players to see this, you can give the -caller’s session ID to the execute_cmd call, like this:

-
npc.execute_cmd(self.cmdname, sessid=self.caller.sessid)
-
-
-

Another thing to remember is however that this is a very simplistic way to control NPCs. Evennia -supports full puppeting very easily. An Account (assuming the “puppet” permission was set correctly) -could simply do @ic mynpc and be able to play the game “as” that NPC. This is in fact just what -happens when an Account takes control of their normal Character as well.

-
-
-
-

Concluding remarks

-

This ends the tutorial. It looks like a lot of text but the amount of code you have to write is -actually relatively short. At this point you should have a basic skeleton of a game and a feel for -what is involved in coding your game.

-

From here on you could build a few more ChargenRooms and link that to a bigger grid. The +setpower -command can either be built upon or accompanied by many more to get a more elaborate character -generation.

-

The simple “Power” game mechanic should be easily expandable to something more full-fledged and -useful, same is true for the combat score principle. The +attack could be made to target a -specific player (or npc) and automatically compare their relevant attributes to determine a result.

-

To continue from here, you can take a look at the Tutorial World. For -more specific ideas, see the other tutorials and hints as well -as the Developer Central.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Tutorials.html b/docs/0.9.5/Tutorials.html deleted file mode 100644 index a7db0a8025..0000000000 --- a/docs/0.9.5/Tutorials.html +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - - - - Tutorials — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - -
-
-
-
- -
-

Tutorials

-

Before continuing to read these tutorials (and especially before you start to code or build your -game in earnest) it’s strongly recommended that you read the -Evennia coding introduction as well as the Planning your own game pages first.

-

Please note that it’s not within the scope of our tutorials to teach you basic Python. If you are -new to the language, expect to have to look up concepts you are unfamiliar with. Usually a quick -internet search will give you all info you need. Furthermore, our tutorials tend to focus on -implementation and concepts. As such they give only brief explanations to use Evennia features while -providing ample links to the relevant detailed documentation.

-

The main information resource for builders is the Builder Documentation. Coders -should refer to the Developer Central for further information.

-
-

Building

-

Help with populating your game world.

- -
-
-

General Development tutorials

-

General code practices for newbie game developers.

-

To use Evennia, you will need basic understanding of Python -modules, -variables, conditional statements, -loops, -functions, lists, dictionaries, list comprehensions and string formatting. You should also have a basic -understanding of object-oriented programming and what Python -Classes are.

- -
-
-

Coding - First Step tutorials

-

Starting tutorials for you who are new to developing with Evennia.

- -
-
-

Custom objects and typeclasses

-

Examples of designing new objects for your game world

- -
-
-

Game mechanics tutorials

-

Creating the underlying game mechanics of game play.

- -
-
-

Miscellaneous system tutorials

-

Design various game systems and achieve particular effects.

- -
-
-

Contrib

-

This section contains tutorials linked with contribs. These contribs can be used in your game, but -you’ll need to install them explicitly. They add common features that can earn you time in -implementation.

- -
-
-

Web tutorials

-

Expanding Evennia’s web presence.

- -
-
-

Evennia for [Engine]-Users

-

Hints for new users more familiar with other game engines.

- -
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Typeclasses.html b/docs/0.9.5/Typeclasses.html deleted file mode 100644 index fdaad466ad..0000000000 --- a/docs/0.9.5/Typeclasses.html +++ /dev/null @@ -1,441 +0,0 @@ - - - - - - - - - Typeclasses — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Typeclasses

-

Typeclasses form the core of Evennia data storage. It allows Evennia to represent any number of -different game entities as Python classes, without having to modify the database schema for every -new type.

-

In Evennia the most important game entities, Accounts, Objects, -Scripts and Channels are all Python classes inheriting, at -varying distance, from evennia.typeclasses.models.TypedObject. In the documentation we refer to -these objects as being “typeclassed” or even “being a typeclass”.

-

This is how the inheritance looks for the typeclasses in Evennia:

-
                  TypedObject
-      _________________|_________________________________
-     |                 |                 |               |
-1: AccountDB        ObjectDB           ScriptDB         ChannelDB
-     |                 |                 |               |
-2: DefaultAccount   DefaultObject      DefaultScript    DefaultChannel
-     |              DefaultCharacter     |               |
-     |              DefaultRoom          |               |
-     |              DefaultExit          |               |
-     |                 |                 |               |
-3: Account          Object              Script           Channel
-                   Character
-                   Room
-                   Exit
-
-
-
    -
  • Level 1 above is the “database model” level. This describes the database tables and fields -(this is technically a Django model).

  • -
  • Level 2 is where we find Evennia’s default implementations of the various game entities, on -top of the database. These classes define all the hook methods that Evennia calls in various -situations. DefaultObject is a little special since it’s the parent for DefaultCharacter, -DefaultRoom and DefaultExit. They are all grouped under level 2 because they all represents -defaults to build from.

  • -
  • Level 3, finally, holds empty template classes created in your game directory. This is the -level you are meant to modify and tweak as you please, overloading the defaults as befits your game. -The templates inherit directly from their defaults, so Object inherits from DefaultObject and -Room inherits from DefaultRoom.

  • -
-

The typeclass/list command will provide a list of all typeclasses known to -Evennia. This can be useful for getting a feel for what is available. Note -however that if you add a new module with a class in it but do not import that -module from anywhere, the typeclass/list will not find it. To make it known -to Evennia you must import that module from somewhere.

-
-

Difference between typeclasses and classes

-

All Evennia classes inheriting from class in the table above share one important feature and two -important limitations. This is why we don’t simply call them “classes” but “typeclasses”.

-
    -
  1. A typeclass can save itself to the database. This means that some properties (actually not that -many) on the class actually represents database fields and can only hold very specific data types. -This is detailed below.

  2. -
  3. Due to its connection to the database, the typeclass’ name must be unique across the entire -server namespace. That is, there must never be two same-named classes defined anywhere. So the below -code would give an error (since DefaultObject is now globally found both in this module and in the -default library):

    -
    from evennia import DefaultObject as BaseObject
    -class DefaultObject(BaseObject):
    -     pass
    -
    -
    -
  4. -
  5. A typeclass’ __init__ method should normally not be overloaded. This has mostly to do with the -fact that the __init__ method is not called in a predictable way. Instead Evennia suggest you use -the at_*_creation hooks (like at_object_creation for Objects) for setting things the very first -time the typeclass is saved to the database or the at_init hook which is called every time the -object is cached to memory. If you know what you are doing and want to use __init__, it must -both accept arbitrary keyword arguments and use super to call its parent::

    -
    def __init__(self, **kwargs):
    -    # my content
    -    super().__init__(**kwargs)
    -    # my content
    -
    -
    -
  6. -
-

Apart from this, a typeclass works like any normal Python class and you can -treat it as such.

-
-
-

Creating a new typeclass

-

It’s easy to work with Typeclasses. Either you use an existing typeclass or you create a new Python -class inheriting from an existing typeclass. Here is an example of creating a new type of Object:

-
    from evennia import DefaultObject
-
-    class Furniture(DefaultObject):
-        # this defines what 'furniture' is, like
-        # storing who sits on it or something.
-        pass
-
-
-
-

You can now create a new Furniture object in two ways. First (and usually not the most -convenient) way is to create an instance of the class and then save it manually to the database:

-
chair = Furniture(db_key="Chair")
-chair.save()
-
-
-
-

To use this you must give the database field names as keywords to the call. Which are available -depends on the entity you are creating, but all start with db_* in Evennia. This is a method you -may be familiar with if you know Django from before.

-

It is recommended that you instead use the create_* functions to create typeclassed entities:

-
from evennia import create_object
-
-chair = create_object(Furniture, key="Chair")
-# or (if your typeclass is in a module furniture.py)
-chair = create_object("furniture.Furniture", key="Chair")
-
-
-

The create_object (create_account, create_script etc) takes the typeclass as its first -argument; this can both be the actual class or the python path to the typeclass as found under your -game directory. So if your Furniture typeclass sits in mygame/typeclasses/furniture.py, you -could point to it as typeclasses.furniture.Furniture. Since Evennia will itself look in -mygame/typeclasses, you can shorten this even further to just furniture.Furniture. The create- -functions take a lot of extra keywords allowing you to set things like Attributes and -Tags all in one go. These keywords don’t use the db_* prefix. This will also automatically -save the new instance to the database, so you don’t need to call save() explicitly.

-
-

About typeclass properties

-

An example of a database field is db_key. This stores the “name” of the entity you are modifying -and can thus only hold a string. This is one way of making sure to update the db_key:

-
chair.db_key = "Table"
-chair.save()
-
-print(chair.db_key)
-<<< Table
-
-
-

That is, we change the chair object to have the db_key “Table”, then save this to the database. -However, you almost never do things this way; Evennia defines property wrappers for all the database -fields. These are named the same as the field, but without the db_ part:

-
chair.key = "Table"
-
-print(chair.key)
-<<< Table
-
-
-
-

The key wrapper is not only shorter to write, it will make sure to save the field for you, and -does so more efficiently by levering sql update mechanics under the hood. So whereas it is good to -be aware that the field is named db_key you should use key as much as you can.

-

Each typeclass entity has some unique fields relevant to that type. But all also share the -following fields (the wrapper name without db_ is given):

-
    -
  • key (str): The main identifier for the entity, like “Rose”, “myscript” or “Paul”. name is an -alias.

  • -
  • date_created (datetime): Time stamp when this object was created.

  • -
  • typeclass_path (str): A python path pointing to the location of this (type)class

  • -
-

There is one special field that doesn’t use the db_ prefix (it’s defined by Django):

-
    -
  • id (int): the database id (database ref) of the object. This is an ever-increasing, unique -integer. It can also be accessed as dbid (database ID) or pk (primary key). The dbref property -returns the string form “#id”.

  • -
-

The typeclassed entity has several common handlers:

-
    -
  • tags - the TagHandler that handles tagging. Use tags.add() , tags.get() etc.

  • -
  • locks - the LockHandler that manages access restrictions. Use locks.add(), -locks.get() etc.

  • -
  • attributes - the AttributeHandler that manages Attributes on the object. Use -attributes.add() -etc.

  • -
  • db (DataBase) - a shortcut property to the AttributeHandler; allowing obj.db.attrname = value

  • -
  • nattributes - the Non-persistent AttributeHandler for attributes not saved in the -database.

  • -
  • ndb (NotDataBase) - a shortcut property to the Non-peristent AttributeHandler. Allows -obj.ndb.attrname = value

  • -
-

Each of the typeclassed entities then extend this list with their own properties. Go to the -respective pages for Objects, Scripts, Accounts and -Channels for more info. It’s also recommended that you explore the available -entities using Evennia’s flat API to explore which properties and methods they have -available.

-
-
-

Overloading hooks

-

The way to customize typeclasses is usually to overload hook methods on them. Hooks are methods -that Evennia call in various situations. An example is the at_object_creation hook on Objects, -which is only called once, the very first time this object is saved to the database. Other examples -are the at_login hook of Accounts and the at_repeat hook of Scripts.

-
-
-

Querying for typeclasses

-

Most of the time you search for objects in the database by using convenience methods like the -caller.search() of Commands or the search functions like evennia.search_objects.

-

You can however also query for them directly using Django’s query -language. This makes use of a database -manager that sits on all typeclasses, named objects. This manager holds methods that allow -database searches against that particular type of object (this is the way Django normally works -too). When using Django queries, you need to use the full field names (like db_key) to search:

-
matches = Furniture.objects.get(db_key="Chair")
-
-
-
-

It is important that this will only find objects inheriting directly from Furniture in your -database. If there was a subclass of Furniture named Sitables you would not find any chairs -derived from Sitables with this query (this is not a Django feature but special to Evennia). To -find objects from subclasses Evennia instead makes the get_family and filter_family query -methods available:

-
# search for all furnitures and subclasses of furnitures
-# whose names starts with "Chair"
-matches = Furniture.objects.filter_family(db_key__startswith="Chair")
-
-
-
-

To make sure to search, say, all Scripts regardless of typeclass, you need to query from the -database model itself. So for Objects, this would be ObjectDB in the diagram above. Here’s an -example for Scripts:

-
from evennia import ScriptDB
-matches = ScriptDB.objects.filter(db_key__contains="Combat")
-
-
-

When querying from the database model parent you don’t need to use filter_family or get_family - -you will always query all children on the database model.

-
-
-
-

Updating existing typeclass instances

-

If you already have created instances of Typeclasses, you can modify the Python code at any time - -due to how Python inheritance works your changes will automatically be applied to all children once -you have reloaded the server.

-

However, database-saved data, like db_* fields, Attributes, Tags etc, are -not themselves embedded into the class and will not be updated automatically. This you need to -manage yourself, by searching for all relevant objects and updating or adding the data:

-
# add a worth Attribute to all existing Furniture
-for obj in Furniture.objects.all():
-    # this will loop over all Furniture instances
-    obj.db.worth = 100
-
-
-

A common use case is putting all Attributes in the at_*_creation hook of the entity, such as -at_object_creation for Objects. This is called every time an object is created - and only then. -This is usually what you want but it does mean already existing objects won’t get updated if you -change the contents of at_object_creation later. You can fix this in a similar way as above -(manually setting each Attribute) or with something like this:

-
# Re-run at_object_creation only on those objects not having the new Attribute
-for obj in Furniture.objects.all():
-    if not obj.db.worth:
-        obj.at_object_creation()
-
-
-

The above examples can be run in the command prompt created by evennia shell. You could also run -it all in-game using @py. That however requires you to put the code (including imports) as one -single line using ; and list -comprehensions, like this (ignore the -line break, that’s only for readability in the wiki):

-
@py from typeclasses.furniture import Furniture;
-[obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth]
-
-
-

It is recommended that you plan your game properly before starting to build, to avoid having to -retroactively update objects more than necessary.

-
-
-

Swap typeclass

-

If you want to swap an already existing typeclass, there are two ways to do so: From in-game and via -code. From inside the game you can use the default @typeclass command:

-
@typeclass objname = path.to.new.typeclass
-
-
-

There are two important switches to this command:

-
    -
  • /reset - This will purge all existing Attributes on the object and re-run the creation hook -(like at_object_creation for Objects). This assures you get an object which is purely of this new -class.

  • -
  • /force - This is required if you are changing the class to be the same class the object -already has - it’s a safety check to avoid user errors. This is usually used together with /reset -to re-run the creation hook on an existing class.

  • -
-

In code you instead use the swap_typeclass method which you can find on all typeclassed entities:

-
obj_to_change.swap_typeclass(new_typeclass_path, clean_attributes=False,
-                   run_start_hooks="all", no_default=True, clean_cmdsets=False)
-
-
-

The arguments to this method are described in the API docs -here.

-
-
-

How typeclasses actually work

-

This is considered an advanced section.

-

Technically, typeclasses are Django proxy -models. The only database -models that are “real” in the typeclass system (that is, are represented by actual tables in the -database) are AccountDB, ObjectDB, ScriptDB and ChannelDB (there are also -Attributes and Tags but they are not typeclasses themselves). All the -subclasses of them are “proxies”, extending them with Python code without actually modifying the -database layout.

-

Evennia modifies Django’s proxy model in various ways to allow them to work without any boiler plate -(for example you don’t need to set the Django “proxy” property in the model Meta subclass, Evennia -handles this for you using metaclasses). Evennia also makes sure you can query subclasses as well as -patches django to allow multiple inheritance from the same base class.

-
-

Caveats

-

Evennia uses the idmapper to cache its typeclasses (Django proxy models) in memory. The idmapper -allows things like on-object handlers and properties to be stored on typeclass instances and to not -get lost as long as the server is running (they will only be cleared on a Server reload). Django -does not work like this by default; by default every time you search for an object in the database -you’ll get a different instance of that object back and anything you stored on it that was not in -the database would be lost. The bottom line is that Evennia’s Typeclass instances subside in memory -a lot longer than vanilla Django model instance do.

-

There is one caveat to consider with this, and that relates to [making your own models](New- -Models): Foreign relationships to typeclasses are cached by Django and that means that if you were -to change an object in a foreign relationship via some other means than via that relationship, the -object seeing the relationship may not reliably update but will still see its old cached version. -Due to typeclasses staying so long in memory, stale caches of such relationships could be more -visible than common in Django. See the closed issue #1098 and its -comments for examples and solutions.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Understanding-Color-Tags.html b/docs/0.9.5/Understanding-Color-Tags.html deleted file mode 100644 index 3789846568..0000000000 --- a/docs/0.9.5/Understanding-Color-Tags.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - Understanding Color Tags — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Understanding Color Tags

-

This tutorial aims at dispelling confusions regarding the use of color tags within Evennia.

-

Correct understanding of this topic requires having read the TextTags page and learned -Evennia’s color tags. Here we’ll explain by examples the reasons behind the unexpected (or -apparently incoherent) behaviors of some color tags, as mentioned en passant in the -TextTags page.

-

All you’ll need for this tutorial is access to a running instance of Evennia via a color-enabled -client. The examples provided are just commands that you can type in your client.

-
-
-

Evennia, ANSI and Xterm256

-

All modern MUD clients support colors; nevertheless, the standards to which all clients abide dates -back to old day of terminals, and when it comes to colors we are dealing with ANSI and Xterm256 -standards.

-

Evennia handles transparently, behind the scenes, all the code required to enforce these -standards—so, if a user connects with a client which doesn’t support colors, or supports only ANSI -(16 colors), Evennia will take all due steps to ensure that the output will be adjusted to look -right at the client side.

-

As for you, the developer, all you need to care about is knowing how to correctly use the color tags -within your MUD. Most likely, you’ll be adding colors to help pages, descriptions, automatically -generated text, etc.

-

You are free to mix together ANSI and Xterm256 color tags, but you should be aware of a few -pitfalls. ANSI and Xterm256 coexist without conflicts in Evennia, but in many ways they don’t «see» -each other: ANSI-specific color tags will have no effect on Xterm-defined colors, as we shall see -here.

-
-
-

ANSI

-

ANSI has a set of 16 colors, to be more precise: ANSI has 8 basic colors which come in dark and -bright flavours—with dark being normal. The colors are: red, green, yellow, blue, magenta, -cyan, white and black. White in its dark version is usually referred to as gray, and black in its -bright version as darkgray. Here, for sake of simplicity they’ll be referred to as dark and bright: -bright/dark black, bright/dark white.

-

The default colors of MUD clients is normal (dark) white on normal black (ie: gray on black).

-

It’s important to grasp that in the ANSI standard bright colors apply only to text (foreground), not -to background. Evennia allows to bypass this limitation via Xterm256, but doing so will impact the -behavior of ANSI tags, as we shall see.

-

Also, it’s important to remember that the 16 ANSI colors are a convention, and the final user can -always customize their appearance—he might decide to have green show as red, and dark green as blue, -etc.

-
-
-

Xterm256

-

The 16 colors of ANSI should be more than enough to handle simple coloring of text. But when an -author wants to be sure that a given color will show as he intended it, she might choose to rely on -Xterm256 colors.

-

Xterm256 doesn’t rely on a palette of named colors, it instead represent colors by their values. So, -a red color could be |[500 (bright and pure red), or |[300 (darker red), and so on.

-
-
-

ANSI Color Tags in Evennia

-
-

NOTE: for ease of reading, the examples contain extra white spaces after the -color tags (eg: |g green |b blue ). This is done only so that it’s easier -to see the tags separated from their context; it wouldn’t be good practice -in real-life coding.

-
-

Let’s proceed by examples. In your MUD client type:

-
say Normal |* Negative
-
-
-

Evennia should output the word “Normal” normally (ie: gray on black) and “Negative” in reversed -colors (ie: black on gray).

-

This is pretty straight forward, the |* ANSI invert tag switches between foreground and -background—from now on, FG and BG shorthands will be used to refer to foreground and -background.

-

But take mental note of this: |* has switched dark white and dark black.

-

Now try this:

-
say |w Bright white FG |* Negative
-
-
-

You’ll notice that the word “Negative” is not black on white, it’s darkgray on gray. Why is this? -Shouldn’t it be black text on a white BG? Two things are happening here.

-

As mentioned, ANSI has 8 base colors, the dark ones. The bright ones are achieved by means of -highlighting the base/dark/normal colors, and they only apply to FG.

-

What happened here is that when we set the bright white FG with |w, Evennia translated this into -the ANSI sequence of Highlight On + White FG. In terms of Evennia’s color tags, it’s as if we typed:

-
say |h|!W Bright white FG |* Negative
-
-
-

Furthermore, the Highlight-On property (which only works for BG!) is preserved after the FG/BG -switch, this being the reason why we see black as darkgray: highlighting makes it bright black -(ie: darkgray).

-

As for the BG being also grey, that is normal—ie: you are seeing normal white (ie: dark white = -gray). Remember that since there are no bright BG colors, the ANSI |* tag will transpose any FG -color in its normal/dark version. So here the FG’s bright white became dark white in the BG! In -reality, it was always normal/dark white, except that in the FG is seen as bright because of the -highlight tag behind the scenes.

-

Let’s try the same thing with some color:

-
say |m |[G Bright Magenta on Dark Green |* Negative
-
-
-

Again, the BG stays dark because of ANSI rules, and the FG stays bright because of the implicit |h -in |m.

-

Now, let’s see what happens if we set a bright BG and then invert—yes, Evennia kindly allows us to -do it, even if it’s not within ANSI expectations.

-
say |[b Dark White on Bright Blue |* Negative
-
-
-

Before color inversion, the BG does show in bright blue, and after inversion (as expected) it’s -dark white (gray). The bright blue of the BG survived the inversion and gave us a bright blue FG. -This behavior is tricky though, and not as simple as it might look.

-

If the inversion were to be pure ANSI, the bright blue would have been accounted just as normal -blue, and should have converted to normal blue in the FG (after all, there was no highlighting on). -The fact is that in reality this color is not bright blue at all, it just an Xterm version of it!

-

To demonstrate this, type:

-
say |[b Dark White on Bright Blue |* Negative |H un-bright
-
-
-

The |H Highlight-Off tag should have turned dark blue the last word; but it didn’t because it -couldn’t: in order to enforce the non-ANSI bright BG Evennia turned to Xterm, and Xterm entities are -not affected by ANSI tags!

-

So, we are getting at the heart of all confusions and possible odd-behaviors pertaining color tags -in Evennia: apart from Evennia’s translations from- and to- ANSI/Xterm, the two systems are -independent and transparent to each other.

-

The bright blue of the previous example was just an Xterm representation of the ANSI standard blue. -Try to change the default settings of your client, so that blue shows as some other color, you’ll -then realize the difference when Evennia is sending a true ANSI color (which will show up according -to your settings) and when instead it’s sending an Xterm representation of that color (which will -show up always as defined by Evennia).

-

You’ll have to keep in mind that the presence of an Xterm BG or FG color might affect the way your -tags work on the text. For example:

-
say |[b Bright Blue BG |* Negative |!Y Dark Yellow |h not bright
-
-
-

Here the |h tag no longer affects the FG color. Even though it was changed via the |! tag, the -ANSI system is out-of-tune because of the intrusion of an Xterm color (bright blue BG, then moved to -FG with |*).

-

All unexpected ANSI behaviours are the result of mixing Xterm colors (either on purpose or either -via bright BG colors). The |n tag will restore things in place and ANSI tags will respond properly -again. So, at the end is just an issue of being mindful when using Xterm colors or bright BGs, and -avoid wild mixing them with ANSI tags without normalizing (|n) things again.

-

Try this:

-
say |[b Bright Blue BG |* Negative |!R Red FG
-
-
-

And then:

-
say |[B Dark Blue BG |* Negative |!R Red BG??
-
-
-

In this second example the |! changes the BG color instead of the FG! In fact, the odd behavior is -the one from the former example, non the latter. When you invert FG and BG with |* you actually -inverting their references. This is why the last example (which has a normal/dark BG!) allows |! -to change the BG color. In the first example, it’s again the presence of an Xterm color (bright blue -BG) which changes the default behavior.

-

Try this:

-

say Normal |* Negative |!R Red BG

-

This is the normal behavior, and as you can see it allows |! to change BG color after the -inversion of FG and BG.

-

As long as you have an understanding of how ANSI works, it should be easy to handle color tags -avoiding the pitfalls of Xterm-ANSI promisquity.

-

One last example:

-

say Normal |* Negative |* still Negative

-

Shows that |* only works once in a row and will not (and should not!) revert back if used again. -Nor it will have any effect until the |n tag is called to “reset” ANSI back to normal. This is how -it is meant to work.

-

ANSI operates according to a simple states-based mechanism, and it’s important to understand the -positive effect of resetting with the |n tag, and not try to -push it over the limit, so to speak.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Unit-Testing.html b/docs/0.9.5/Unit-Testing.html deleted file mode 100644 index 63e6288a44..0000000000 --- a/docs/0.9.5/Unit-Testing.html +++ /dev/null @@ -1,537 +0,0 @@ - - - - - - - - - Unit Testing — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Unit Testing

-

Unit testing means testing components of a program in isolation from each other to make sure every -part works on its own before using it with others. Extensive testing helps avoid new updates causing -unexpected side effects as well as alleviates general code rot (a more comprehensive wikipedia -article on unit testing can be found here).

-

A typical unit test set calls some function or method with a given input, looks at the result and -makes sure that this result looks as expected. Rather than having lots of stand-alone test programs, -Evennia makes use of a central test runner. This is a program that gathers all available tests all -over the Evennia source code (called test suites) and runs them all in one go. Errors and -tracebacks are reported.

-

By default Evennia only tests itself. But you can also add your own tests to your game code and have -Evennia run those for you.

-
-

Running the Evennia test suite

-

To run the full Evennia test suite, go to your game folder and issue the command

-
evennia test evennia
-
-
-

This will run all the evennia tests using the default settings. You could also run only a subset of -all tests by specifying a subpackage of the library:

-
evennia test evennia.commands.default
-
-
-

A temporary database will be instantiated to manage the tests. If everything works out you will see -how many tests were run and how long it took. If something went wrong you will get error messages. -If you contribute to Evennia, this is a useful sanity check to see you haven’t introduced an -unexpected bug.

-
-
-

Running tests with custom settings file

-

If you have implemented your own tests for your game (see below) you can run them from your game dir -with

-
evennia test .
-
-
-

The period (.) means to run all tests found in the current directory and all subdirectories. You -could also specify, say, typeclasses or world if you wanted to just run tests in those subdirs.

-

Those tests will all be run using the default settings. To run the tests with your own settings file -you must use the --settings option:

-
evennia test --settings settings.py .
-
-
-

The --settings option of Evennia takes a file name in the mygame/server/conf folder. It is -normally used to swap settings files for testing and development. In combination with test, it -forces Evennia to use this settings file over the default one.

-
-
-

Writing new tests

-

Evennia’s test suite makes use of Django unit test system, which in turn relies on Python’s -unittest module.

-
-

If you want to help out writing unittests for Evennia, take a look at Evennia’s coveralls.io page. There you see which modules have any form of -test coverage and which does not.

-
-

To make the test runner find the tests, they must be put in a module named test*.py (so test.py, -tests.py etc). Such a test module will be found wherever it is in the package. It can be a good -idea to look at some of Evennia’s tests.py modules to see how they look.

-

Inside a testing file, a unittest.TestCase class is used to test a single aspect or component in -various ways. Each test case contains one or more test methods - these define the actual tests to -run. You can name the test methods anything you want as long as the name starts with “test_”. -Your TestCase class can also have a method setUp(self):. This is run before each test, setting -up and storing whatever preparations the test methods need. Conversely, a tearDown(self): method -can optionally do cleanup after each test.

-

To test the results, you use special methods of the TestCase class. Many of those start with -“assert”, such as assertEqual or assertTrue.

-

Example of a TestCase class:

-
    import unittest
-
-    # the function we want to test
-    from mypath import myfunc
-
-    class TestObj(unittest.TestCase):
-       "This tests a function myfunc."
-
-       def test_return_value(self):
-           "test method. Makes sure return value is as expected."
-           expected_return = "This is me being nice."
-           actual_return = myfunc()
-           # test
-           self.assertEqual(expected_return, actual_return)
-       def test_alternative_call(self):
-           "test method. Calls with a keyword argument."
-           expected_return = "This is me being baaaad."
-           actual_return = myfunc(bad=True)
-           # test
-           self.assertEqual(expected_return, actual_return)
-
-
-

You might also want to read the documentation for the unittest module.

-
-

Using the EvenniaTest class

-

Evennia offers a custom TestCase, the evennia.utils.test_resources.EvenniaTest class. This class -initiates a range of useful properties on themselves for testing Evennia systems. Examples are -.account and .session representing a mock connected Account and its Session and .char1 and -.char2 representing Characters complete with a location in the test database. These are all useful -when testing Evennia system requiring any of the default Evennia typeclasses as inputs. See the full -definition of the EvenniaTest class in evennia/utils/test_resources.py.

-
# in a test module
-
-from evennia.utils.test_resources import EvenniaTest
-
-class TestObject(EvenniaTest):
-    def test_object_search(self):
-        # char1 and char2 are both created in room1
-        self.assertEqual(self.char1.search(self.char2.key), self.char2)
-        self.assertEqual(self.char1.search(self.char1.location.key), self.char1.location)
-        # ...
-
-
-
-
-

Testing in-game Commands

-

In-game Commands are a special case. Tests for the default commands are put in -evennia/commands/default/tests.py. This uses a custom CommandTest class that inherits from -evennia.utils.test_resources.EvenniaTest described above. CommandTest supplies extra convenience -functions for executing commands and check that their return values (calls of msg() returns -expected values. It uses Characters and Sessions generated on the EvenniaTest class to call each -class).

-

Each command tested should have its own TestCase class. Inherit this class from the CommandTest -class in the same module to get access to the command-specific utilities mentioned.

-
    from evennia.commands.default.tests import CommandTest
-    from evennia.commands.default import general
-    class TestSet(CommandTest):
-        "tests the look command by simple call, using Char2 as a target"
-        def test_mycmd_char(self):
-            self.call(general.CmdLook(), "Char2", "Char2(#7)")
-        "tests the look command by simple call, with target as room"
-        def test_mycmd_room(self):
-            self.call(general.CmdLook(), "Room",
-                      "Room(#1)\nroom_desc\nExits: out(#3)\n"
-                      "You see: Obj(#4), Obj2(#5), Char2(#7)")
-
-
-
-
-

Unit testing contribs with custom models

-

A special case is if you were to create a contribution to go to the evennia/contrib folder that -uses its own database models. The problem with this is that Evennia (and Django) will -only recognize models in settings.INSTALLED_APPS. If a user wants to use your contrib, they will -be required to add your models to their settings file. But since contribs are optional you cannot -add the model to Evennia’s central settings_default.py file - this would always create your -optional models regardless of if the user wants them. But at the same time a contribution is a part -of the Evennia distribution and its unit tests should be run with all other Evennia tests using -evennia test evennia.

-

The way to do this is to only temporarily add your models to the INSTALLED_APPS directory when the -test runs. here is an example of how to do it.

-
-

Note that this solution, derived from this stackexchange answer is currently untested! Please report your findings.

-
-
# a file contrib/mycontrib/tests.py
-
-from django.conf import settings
-import django
-from evennia.utils.test_resources import EvenniaTest
-
-OLD_DEFAULT_SETTINGS = settings.INSTALLED_APPS
-DEFAULT_SETTINGS = dict(
-    INSTALLED_APPS=(
-        'contrib.mycontrib.tests',
-        ),
-    DATABASES={
-        "default": {
-            "ENGINE": "django.db.backends.sqlite3"
-            }
-        },
-    SILENCED_SYSTEM_CHECKS=["1_7.W001"],
-    )
-
-class TestMyModel(EvenniaTest):
-    def setUp(self):
-
-        if not settings.configured:
-           settings.configure(**DEFAULT_SETTINGS)
-        django.setup()
-
-        from django.core.management import call_command
-        from django.db.models import loading
-        loading.cache.loaded = False
-        call_command('syncdb', verbosity=0)
-
-    def tearDown(self):
-        settings.configure(**OLD_DEFAULT_SETTINGS)
-        django.setup()
-
-        from django.core.management import call_command
-        from django.db.models import loading
-        loading.cache.loaded = False
-        call_command('syncdb', verbosity=0)
-
-    # test cases below ...
-
-    def test_case(self):
-        # test case here
-
-
-
-
-

A note on adding new tests

-

Having an extensive tests suite is very important for avoiding code degradation as Evennia is -developed. Only a small fraction of the Evennia codebase is covered by test suites at this point. -Writing new tests is not hard, it’s more a matter of finding the time to do so. So adding new tests -is really an area where everyone can contribute, also with only limited Python skills.

-
-
-

A note on making the test runner faster

-

If you have custom models with a large number of migrations, creating the test database can take a -very long time. If you don’t require migrations to run for your tests, you can disable them with the -django-test-without-migrations package. To install it, simply:

-
$ pip install django-test-without-migrations
-
-
-

Then add it to your INSTALLED_APPS in your server.conf.settings.py:

-
INSTALLED_APPS = (
-    # ...
-    'test_without_migrations',
-)
-
-
-

After doing so, you can then run tests without migrations by adding the --nomigrations argument:

-
evennia test --settings settings.py --nomigrations .
-
-
-
-
-
-

Testing for Game development (mini-tutorial)

-

Unit testing can be of paramount importance to game developers. When starting with a new game, it is -recommended to look into unit testing as soon as possible; an already huge game is much harder to -write tests for. The benefits of testing a game aren’t different from the ones regarding library -testing. For example it is easy to introduce bugs that affect previously working code. Testing is -there to ensure your project behaves the way it should and continue to do so.

-

If you have never used unit testing (with Python or another language), you might want to check the -official Python documentation about unit testing, -particularly the first section dedicated to a basic example.

-
-

Basic testing using Evennia

-

Evennia’s test runner can be used to launch tests in your game directory (let’s call it ‘mygame’). -Evennia’s test runner does a few useful things beyond the normal Python unittest module:

-
    -
  • It creates and sets up an empty database, with some useful objects (accounts, characters and -rooms, among others).

  • -
  • It provides simple ways to test commands, which can be somewhat tricky at times, if not tested -properly.

  • -
-

Therefore, you should use the command-line to execute the test runner, while specifying your own -game directories (not the one containing evennia). Go to your game directory (referred as ‘mygame’ -in this section) and execute the test runner:

-
evennia test --settings settings.py commands
-
-
-

This command will execute Evennia’s test runner using your own settings file. It will set up a dummy -database of your choice and look into the ‘commands’ package defined in your game directory -(mygame/commands in this example) to find tests. The test module’s name should begin with ‘test’ -and contain one or more TestCase. A full example can be found below.

-
-
-

A simple example

-

In your game directory, go to commands and create a new file tests.py inside (it could be named -anything starting with test). We will start by making a test that has nothing to do with Commands, -just to show how unit testing works:

-
    # mygame/commands/tests.py
-
-    import unittest
-
-    class TestString(unittest.TestCase):
-
-        """Unittest for strings (just a basic example)."""
-
-        def test_upper(self):
-            """Test the upper() str method."""
-            self.assertEqual('foo'.upper(), 'FOO')
-
-
-

This example, inspired from the Python documentation, is used to test the ‘upper()’ method of the -‘str’ class. Not very useful, but it should give you a basic idea of how tests are used.

-

Let’s execute that test to see if it works.

-
> evennia test --settings settings.py commands
-
-TESTING: Using specified settings file 'server.conf.settings'.
-
-(Obs: Evennia's full test suite may not pass if the settings are very
-different from the default. Use 'test .' as arguments to run only tests
-on the game dir.)
-
-Creating test database for alias 'default'...
-.
-----------------------------------------------------------------------
-Ran 1 test in 0.001s
-
-OK
-Destroying test database for alias 'default'...
-
-
-

We specified the commands package to the evennia test command since that’s where we put our test -file. In this case we could just as well just said . to search all of mygame for testing files. -If we have a lot of tests it may be useful to test only a single set at a time though. We get an -information text telling us we are using our custom settings file (instead of Evennia’s default -file) and then the test runs. The test passes! Change the “FOO” string to something else in the test -to see how it looks when it fails.

-
-
-

Testing commands

-

This section will test the proper execution of the ‘abilities’ command, as described in the -First Steps Coding page. Follow this tutorial to create the ‘abilities’ command, we -will need it to test it.

-

Testing commands in Evennia is a bit more complex than the simple testing example we have seen. -Luckily, Evennia supplies a special test class to do just that … we just need to inherit from it -and use it properly. This class is called ‘CommandTest’ and is defined in the -‘evennia.commands.default.tests’ package. To create a test for our ‘abilities’ command, we just -need to create a class that inherits from ‘CommandTest’ and add methods.

-

We could create a new test file for this but for now we just append to the tests.py file we -already have in commands from before.

-
    # bottom of mygame/commands/tests.py
-
-    from evennia.commands.default.tests import CommandTest
-
-    from commands.command import CmdAbilities
-    from typeclasses.characters import Character
-
-    class TestAbilities(CommandTest):
-
-        character_typeclass = Character
-
-        def test_simple(self):
-            self.call(CmdAbilities(), "", "STR: 5, AGI: 4, MAG: 2")
-
-
-
    -
  • Line 1-4: we do some importing. ‘CommandTest’ is going to be our base class for our test, so we -need it. We also import our command (‘CmdAbilities’ in this case). Finally we import the -‘Character’ typeclass. We need it, since ‘CommandTest’ doesn’t use ‘Character’, but -‘DefaultCharacter’, which means the character calling the command won’t have the abilities we have -written in the ‘Character’ typeclass.

  • -
  • Line 6-8: that’s the body of our test. Here, a single command is tested in an entire class. -Default commands are usually grouped by category in a single class. There is no rule, as long as -you know where you put your tests. Note that we set the ‘character_typeclass’ class attribute to -Character. As explained above, if you didn’t do that, the system would create a ‘DefaultCharacter’ -object, not a ‘Character’. You can try to remove line 4 and 8 to see what happens when running the -test.

  • -
  • Line 10-11: our unique testing method. Note its name: it should begin by ‘test_’. Apart from -that, the method is quite simple: it’s an instance method (so it takes the ‘self’ argument) but no -other arguments are needed. Line 11 uses the ‘call’ method, which is defined in ‘CommandTest’. -It’s a useful method that compares a command against an expected result. It would be like comparing -two strings with ‘assertEqual’, but the ‘call’ method does more things, including testing the -command in a realistic way (calling its hooks in the right order, so you don’t have to worry about -that).

  • -
-

Line 11 can be understood as: test the ‘abilities’ command (first parameter), with no argument -(second parameter), and check that the character using it receives his/her abilities (third -parameter).

-

Let’s run our new test:

-
> evennia test --settings settings.py commands
-[...]
-Creating test database for alias 'default'...
-..
-----------------------------------------------------------------------
-Ran 2 tests in 0.156s
-
-OK
-Destroying test database for alias 'default'...
-
-
-

Two tests were executed, since we have kept ‘TestString’ from last time. In case of failure, you -will get much more information to help you fix the bug.

-
-
-

Testing Dynamic Output

-

Having read the unit test tutorial on Testing commands we can see -the code expects static unchanging numbers. While very good for learning it is unlikely a project -will have nothing but static output to test. Here we are going to learn how to test against dynamic -output.

-

This tutorial assumes you have a basic understanding of what regular expressions are. If you do not -I recommend reading the Introduction and Simple Pattern sections at -Python regular expressions tutorial. If you do plan on making a complete Evennia -project learning regular expressions will save a great deal of time.

-

Append the code below to your tests.py file.

-
    # bottom of mygame/commands/tests.py
-
-    class TestDynamicAbilities(CommandTest):
-
-        character_typeclass = Character
-
-        def test_simple(self):
-            cmd_abil_result = self.call(CmdAbilities(), "")
-            self.assertRegex(cmd_abil_result, "STR: \d+, AGI: \d+, MAG: \d+")
-
-
-

Noticed that we removed the test string from self.call. That method always returns the string it -found from the commands output. If we remove the string to test against, all self.call will do is -return the screen output from the command object passed to it.

-

We are instead using the next line to test our command’s output. -assertRegex is a -method of unittest.TestCase this is inherited to TestDynamicAbilities from CommandTest who -inherited it from EvenniaTest.

-

What we are doing is testing the result of the CmdAbilities method or command against a regular -expression pattern. In this case, "STR: \d+, AGI: \d+, MAG: \d+". \d in regular expressions or -regex are digits (numbers), the + is to state we want 1 or more of that pattern. Together \d+ is -telling assertRegex -that in that position of the string we expect 1 or more digits (numbers) to appear in the -string.

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Updating-Your-Game.html b/docs/0.9.5/Updating-Your-Game.html deleted file mode 100644 index bc7c55da89..0000000000 --- a/docs/0.9.5/Updating-Your-Game.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - - - - - - Updating Your Game — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Updating Your Game

-

Fortunately, it’s extremely easy to keep your Evennia server up-to-date. If you haven’t already, see -the Getting Started guide and get everything running.

-
-

Updating with the latest Evennia code changes

-

Very commonly we make changes to the Evennia code to improve things. There are many ways to get told -when to update: You can subscribe to the RSS feed or manually check up on the feeds from -http://www.evennia.com. You can also simply fetch the latest regularly.

-

When you’re wanting to apply updates, simply cd to your cloned evennia root directory and type:

-
 git pull
-
-
-

assuming you’ve got the command line client. If you’re using a graphical client, you will probably -want to navigate to the evennia directory and either right click and find your client’s pull -function, or use one of the menus (if applicable).

-

You can review the latest changes with

-
 git log
-
-
-

or the equivalent in the graphical client. You can also see the latest changes online -here.

-

You will always need to do evennia reload (or reload from -in-game) from your game-dir to have -the new code affect your game. If you want to be really sure you should run a full evennia reboot -so that both Server and Portal can restart (this will disconnect everyone though, so if you know the -Portal has had no updates you don’t have to do that).

-
-
-

Upgrading Evennia dependencies

-

On occasion we update the versions of third-party libraries Evennia depend on (or we may add a new -dependency). This will be announced on the mailing list/forum. If you run into errors when starting -Evennia, always make sure you have the latest versions of everything. In some cases, like for -Django, starting the server may also give warning saying that you are using a working, but too-old -version that should not be used in production.

-

Upgrading evennia will automatically fetch all the latest packages that it now need. First cd to -your cloned evennia folder. Make sure your virtualenv is active and use

-
pip install --upgrade -e .
-
-
-

Remember the period (.) at the end - that applies the upgrade to the current location (your -evennia dir).

-
-

The -e means that we are linking the evennia sources rather than copying them into the -environment. This means we can most of the time just update the sources (with git pull) and see -those changes directly applied to our installed evennia package. Without installing/upgrading the -package with -e, we would have to remember to upgrade the package every time we downloaded any new -source-code changes.

-
-

Follow the upgrade output to make sure it finishes without errors. To check what packages are -currently available in your python environment after the upgrade, use

-
pip list
-
-
-

This will show you the version of all installed packages. The evennia package will also show the -location of its source code.

-
-
-

Migrating the Database Schema

-

Whenever we change the database layout of Evennia upstream (such as when we add new features) you -will need to migrate your existing database. When this happens it will be clearly noted in the -git log (it will say something to the effect of “Run migrations”). Database changes will also be -announced on the Evennia mailing list.

-

When the database schema changes, you just go to your game folder and run

-
 evennia migrate
-
-
-
-

Hint: If the evennia command is not found, you most likely need to activate your -virtualenv.

-
-
-
-

Resetting your database

-

Should you ever want to start over completely from scratch, there is no need to re-download Evennia -or anything like that. You just need to clear your database. Once you are done, you just rebuild it -from scratch as described in the second step of the Getting Started guide.

-

First stop a running server with

-
evennia stop
-
-
-

If you run the default SQlite3 database (to change this you need to edit your settings.py file), -the database is actually just a normal file in mygame/server/ called evennia.db3. Simply delete -that file - that’s it. Now run evennia migrate to recreate a new, fresh one.

-

If you run some other database system you can instead flush the database:

-
evennia flush
-
-
-

This will empty the database. However, it will not reset the internal counters of the database, so -you will start with higher dbref values. If this is okay, this is all you need.

-

Django also offers an easy way to start the database’s own management should we want more direct -control:

-
 evennia dbshell
-
-
-

In e.g. MySQL you can then do something like this (assuming your MySQL database is named “Evennia”:

-
mysql> DROP DATABASE Evennia;
-mysql> exit
-
-
-
-

NOTE: Under Windows OS, in order to access SQLite dbshell you need to download the SQLite -command-line shell program. It’s a single executable file -(sqlite3.exe) that you should place in the root of either your MUD folder or Evennia’s (it’s the -same, in both cases Django will find it).

-
-
-
-

More about schema migrations

-

If and when an Evennia update modifies the database schema (that is, the under-the-hood details as -to how data is stored in the database), you must update your existing database correspondingly to -match the change. If you don’t, the updated Evennia will complain that it cannot read the database -properly. Whereas schema changes should become more and more rare as Evennia matures, it may still -happen from time to time.

-

One way one could handle this is to apply the changes manually to your database using the database’s -command line. This often means adding/removing new tables or fields as well as possibly convert -existing data to match what the new Evennia version expects. It should be quite obvious that this -quickly becomes cumbersome and error-prone. If your database doesn’t contain anything critical yet -it’s probably easiest to simply reset it and start over rather than to bother converting.

-

Enter migrations. Migrations keeps track of changes in the database schema and applies them -automatically for you. Basically, whenever the schema changes we distribute small files called -“migrations” with the source. Those tell the system exactly how to implement the change so you don’t -have to do so manually. When a migration has been added we will tell you so on Evennia’s mailing -lists and in commit messages - -you then just run evennia migrate to be up-to-date again.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Using-MUX-as-a-Standard.html b/docs/0.9.5/Using-MUX-as-a-Standard.html deleted file mode 100644 index f96f59be87..0000000000 --- a/docs/0.9.5/Using-MUX-as-a-Standard.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - Using MUX as a Standard — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Using MUX as a Standard

-

Evennia allows for any command syntax. If you like the way DikuMUDs, LPMuds or MOOs handle things, -you could emulate that with Evennia. If you are ambitious you could even design a whole new style, -perfectly fitting your own dreams of the ideal game.

-

We do offer a default however. The default Evennia setup tends to resemble -MUX2, and its cousins PennMUSH, -TinyMUSH, and RhostMUSH. While the -reason for this similarity is partly historical, these codebases offer very mature feature sets for -administration and building.

-

Evennia is not a MUX system though. It works very differently in many ways. For example, Evennia -deliberately lacks an online softcode language (a policy explained on our softcode policy -page). Evennia also does not shy from using its own syntax when deemed appropriate: the -MUX syntax has grown organically over a long time and is, frankly, rather arcane in places. All in -all the default command syntax should at most be referred to as “MUX-like” or “MUX-inspired”.

-
-

Documentation policy

-

All the commands in the default command sets should have their doc-strings formatted on a similar -form:

-
      """
-      Short header
-    
-      Usage:
-        key[/switches, if any] <mandatory args> [optional] choice1||choice2||choice3
-    
-      Switches:
-        switch1    - description
-        switch2    - description
-    
-      Examples:
-        usage example and output
-    
-      Longer documentation detailing the command.
-    
-      """
-
-
-
    -
  • Two spaces are used for indentation in all default commands.

  • -
  • Square brackets [ ] surround optional, skippable arguments.

  • -
  • Angled brackets < > surround a description of what to write rather than the exact syntax.

  • -
  • *Explicit choices are separated by |. To avoid this being parsed as a color code, use || (this -will come out as a single |) or put spaces around the character (“|”) if there’s plenty of -room.

  • -
  • The Switches and Examples blocks are optional based on the Command.

  • -
-

Here is the nick command as an example:

-
      """
-      Define a personal alias/nick
-    
-      Usage:
-        nick[/switches] <nickname> = [<string>]
-        alias             ''
-    
-      Switches:
-        object   - alias an object
-        account   - alias an account
-        clearall - clear all your aliases
-        list     - show all defined aliases (also "nicks" works)
-    
-      Examples:
-        nick hi = say Hello, I'm Sarah!
-        nick/object tom = the tall man
-    
-      A 'nick' is a personal shortcut you create for your own use [...]
-    
-        """
-
-
-

For commands that require arguments, the policy is for it to return a Usage: string if the -command is entered without any arguments. So for such commands, the Command body should contain -something to the effect of

-
      if not self.args:
-          self.caller.msg("Usage: nick[/switches] <nickname> = [<string>]")
-          return
-
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Using-Travis.html b/docs/0.9.5/Using-Travis.html deleted file mode 100644 index a27660fe7f..0000000000 --- a/docs/0.9.5/Using-Travis.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - Using Travis — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Using Travis

-

Evennia uses Travis CI to check that it’s building successfully after every -commit to its Github repository (you can for example see the build: passing badge at the top of -Evennia’s Readme file). If your game is open source on Github -you may use Travis for free. See the Travis docs -for how to get started.

-

After logging in you need to point Travis to your repository on github. One further thing you need -to set up yourself is a Travis config file named .travis.yml (note the initial period .). This -should be created in the root of your game directory.

-
dist: xenial
-language: python
-cache: pip
-
-python:
-  - "3.7"
-  - "3.8"
-
-install:
-  - git clone https://github.com/evennia/evennia.git ../evennia
-  - pip install -e ../evennia
-
-script:
-  - evennia test --settings settings.py
-  
-
-
-

Here we tell Travis how to download and install Evennia into a folder a level up from your game dir. -It will then install the server (so the evennia command is available) and run the tests only for -your game dir (based on your settings.py file in server/conf/).

-

Running this will not actually do anything though, because there are no unit tests in your game dir -yet. We have a page on how we set those up for Evennia, you should be able to refer -to that for making tests fitting your game.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Version-Control.html b/docs/0.9.5/Version-Control.html deleted file mode 100644 index e28251684b..0000000000 --- a/docs/0.9.5/Version-Control.html +++ /dev/null @@ -1,569 +0,0 @@ - - - - - - - - - Version Control — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Version Control

-

Version control software allows you to track the changes you make to your code, as well as being -able to easily backtrack these changes, share your development efforts and more. Even if you are not -contributing to Evennia itself, and only wish to develop your own MU* using Evennia, having a -version control system in place is a good idea (and standard coding practice). For an introduction -to the concept, start with the Wikipedia article -here. Evennia uses the version control system -Git and this is what will be covered henceforth. Note that this page also -deals with commands for Linux operating systems, and the steps below may vary for other systems, -however where possible links will be provided for alternative instructions.

-

For more help on using Git, please refer to the Official GitHub -documentation.

-
-

Setting up Git

-

If you have gotten Evennia installed, you will have Git already and can skip to Step 2 below. -Otherwise you will need to install Git on your platform. You can find expanded instructions for -installation here.

-
-

Step 1: Install Git

-
    -
  • Fedora Linux

    -
      yum install git-core
    -
    -
    -
  • -
  • Debian Linux (Ubuntu, Linux Mint, etc.)

    -
      apt-get install git
    -
    -
    -
  • -
  • Windows: It is recommended to use Git for Windows.

  • -
  • Mac: Mac platforms offer two methods for installation, one via MacPorts, which you can find -out about here, or -you can use the Git OSX Installer.

  • -
-
-
-

Step 2: Define user/e-mail Settings for Git

-

To avoid a common issue later, you will need to set a couple of settings; first you will need to -tell Git your username, followed by your e-mail address, so that when you commit code later you will -be properly credited.

-
-

Note that your commit information will be visible to everyone if you ever contribute to Evennia or -use an online service like github to host your code. So if you are not comfortable with using your -real, full name online, put a nickname here.

-
-
    -
  1. Set the default name for git to use when you commit:

    -
     git config --global user.name "Your Name Here"
    -
    -
    -
  2. -
  3. Set the default email for git to use when you commit:

    -
     git config --global user.email "your_email@example.com"
    -
    -
    -
  4. -
-
-
-
-

Putting your game folder under version control

-
-

Note: The game folder’s version control is completely separate from Evennia’s repository.

-
-

After you have set up your game you will have created a new folder to host your particular game -(let’s call this folder mygame for now).

-

This folder is not under version control at this point.

-
git init mygame
-
-
-

Your mygame folder is now ready for version control! Now add all the content and make a first -commit:

-
cd mygame
-git add *
-git commit -m "Initial commit"
-
-
-

Read on for help on what these commands do.

-
-

Tracking files

-

When working on your code or fix bugs in your local branches you may end up creating new files. If -you do you must tell Git to track them by using the add command:

-
git add <filename>
-
-
-

You can check the current status of version control with git status. This will show if you have -any modified, added or otherwise changed files. Some files, like database files, logs and temporary -PID files are usually not tracked in version control. These should either not show up or have a -question mark in front of them.

-
-
-

Controlling tracking

-

You will notice that some files are not covered by your git version control, notably your settings -file (mygame/server/conf/settings.py) and your sqlite3 database file mygame/server/evennia.db3. -This is controlled by the hidden file mygame/.gitignore. Evennia creates this file as part of the -creation of your game directory. Everything matched in this file will be ignored by GIT. If you want -to, for example, include your settings file for collaborators to access, remove that entry in -.gitignore.

-
-

Note: You should never put your sqlite3 database file into git by removing its entry in -.gitignore. GIT is for backing up your code, not your database. That way lies madness and a good -chance you’ll confuse yourself so that after a few commits and reverts don’t know what is in your -database or not. If you want to backup your database, do so by simply copying the file on your hard -drive to a backup-name.

-
-
-
-

Committing your Code

-
-

Committing means storing the current snapshot of your code within git. This creates a “save point” -or “history” of your development process. You can later jump back and forth in your history, for -example to figure out just when a bug was introduced or see what results the code used to produce -compared to now.

-
-

It’s usually a good idea to commit your changes often. Committing is fast and local only - you will -never commit anything online at this point. To commit your changes, use

-
git commit --all
-
-
-

This will save all changes you made since last commit. The command will open a text editor where you -can add a message detailing the changes you’ve made. Make it brief but informative. You can see the -history of commits with git log. If you don’t want to use the editor you can set the message -directly by using the -m flag:

-
git commit --all -m "This fixes a bug in the combat code."
-
-
-
-
-

Changing your mind

-

If you have non-committed changes that you realize you want to throw away, you can do the following:

-
git checkout <file to revert>
-
-
-

This will revert the file to the state it was in at your last commit, throwing away the changes -you did to it since. It’s a good way to make wild experiments without having to remember just what -you changed. If you do git checkout . you will throw away all changes since the last commit.

-
-
-

Pushing your code online

-

So far your code is only located on your private machine. A good idea is to back it up online. The -easiest way to do this is to push it to your own remote repository on GitHub.

-
    -
  1. Make sure you have your game directory setup under git version control as described above. Make -sure to commit any changes.

  2. -
  3. Create a new, empty repository on Github. Github explains how -here (do not “Initialize the repository with a -README” or else you’ll create unrelated histories).

  4. -
  5. From your local game dir, do git remote add origin <github URL> where <github URL> is the URL -to your online repo. This tells your game dir that it should be pushing to the remote online dir.

  6. -
  7. git remote -v to verify the online dir.

  8. -
  9. git push origin master now pushes your game dir online so you can see it on github.com.

  10. -
-

You can commit your work locally (git commit --all -m "Make a change that ...") as many times as -you want. When you want to push those changes to your online repo, you do git push. You can also -git clone <url_to_online_repo> from your online repo to somewhere else (like your production -server) and henceforth do git pull to update that to the latest thing you pushed.

-

Note that GitHub’s repos are, by default publicly visible by all. Creating a publicly visible online -clone might not be what you want for all parts of your development process - you may prefer a more -private venue when sharing your revolutionary work with your team. If that’s the case you can change -your repository to “Private” in the github settings. Then your code will only be visible to those -you specifically grant access.

-
-
-
-

Forking Evennia

-

This helps you set up an online fork of Evennia so you can easily commit fixes and help with -upstream development.

-
-

Step 1: Fork the evennia/master repository

-
-

Before proceeding with the following step, make sure you have registered and created an account on -GitHub.com. This is necessary in order to create a fork of Evennia’s master -repository, and to push your commits to your fork either for yourself or for contributing to -Evennia.

-
-

A fork is a clone of the master repository that you can make your own commits and changes to. At -the top of this page, click the “Fork” button, as it appears -below.

-
-
-

Step 2: Clone your fork

-

The fork only exists online as of yet. In a terminal, change your directory to the folder you wish -to develop in. From this directory run the following command:

-
git clone https://github.com/yourusername/evennia.git
-
-
-

This will download your fork to your computer. It creates a new folder evennia/ at your current -location.

-
-
-

Step 3: Configure remotes

-

A remote is a repository stored on another computer, in this case on GitHub’s server. When a -repository is cloned, it has a default remote called origin. This points to your fork on GitHub, -not the original repository it was forked from. To easily keep track of the original repository -(that is, Evennia’s official repository), you need to add another remote. The standard name for this -remote is “upstream”.

-

Below we change the active directory to the newly cloned “evennia” directory and then assign the -original Evennia repository to a remote called “upstream”:

-
cd evennia
-git remote add upstream https://github.com/evennia/evennia.git
-
-
-

If you also want to access Evennia’s develop branch (the bleeding edge development branch) do the -following:

-
git fetch upstream develop
-git checkout develop
-
-
-

You should now have the upstream branch available locally. You can use this instead of master -below if you are contributing new features rather than bug fixes.

-
-
-
-

Working with your fork

-
-

A branch is a separate instance of your code. Changes you do to code in a branch does not affect -that in other branches (so if you for example add/commit a file to one branch and then switches to -another branch, that file will be gone until you switch back to the first branch again). One can -switch between branches at will and create as many branches as one needs for a given project. The -content of branches can also be merged together or deleted without affecting other branches. This is -not only a common way to organize development but also to test features without messing with -existing code.

-
-

The default branch of git is called the “master” branch. As a rule of thumb, you should never -make modifications directly to your local copy of the master branch. Rather keep the master clean -and only update it by pulling our latest changes to it. Any work you do should instead happen in a -local, other branches.

-
-

Making a work branch

-
git checkout -b myfixes
-
-
-

This command will checkout and automatically create the new branch myfixes on your machine. If you -stared out in the master branch, myfixes will be a perfect copy of the master branch. You can see -which branch you are on with git branch and change between different branches with git checkout <branchname>.

-

Branches are fast and cheap to create and manage. It is common practice to create a new branch for -every bug you want to work on or feature you want to create, then create a pull request for that -branch to be merged upstream (see below). Not only will this organize your work, it will also make -sure that your master branch version of Evennia is always exactly in sync with the upstream -version’s master branch.

-
-
-

Updating with upstream changes

-

When Evennia’s official repository updates, first make sure to commit all your changes to your -branch and then checkout the “clean” master branch:

-
git commit --all
-git checkout master
-
-
-

Pull the latest changes from upstream:

-
git pull upstream master
-
-
-

This should sync your local master branch with upstream Evennia’s master branch. Now we go back to -our own work-branch (let’s say it’s still called “myfixes”) and merge the updated master into our -branch.

-
git checkout myfixes
-git merge master
-
-
-

If everything went well, your myfixes branch will now have the latest version of Evennia merged -with whatever changes you have done. Use git log to see what has changed. You may need to restart -the server or run manage.py migrate if the database schema changed (this will be seen in the -commit log and on the mailing list). See the Git manuals for -learning more about useful day-to-day commands, and special situations such as dealing with merge -collisions.

-
-
-
-

Sharing your Code Publicly

-

Up to this point your myfixes branch only exists on your local computer. No one else can see it. -If you want a copy of this branch to also appear in your online fork on GitHub, make sure to have -checked out your “myfixes” branch and then run the following:

-
git push -u origin myfixes
-
-
-

This will create a new remote branch named “myfixes” in your online repository (which is refered -to as “origin” by default); the -u flag makes sure to set this to the default push location. -Henceforth you can just use git push from your myfixes branch to push your changes online. This is -a great way to keep your source backed-up and accessible. Remember though that by default your -repository will be public so everyone will be able to browse and download your code (same way as you -can with Evennia itself). If you want secrecy you can change your repository to “Private” in the -Github settings. Note though that if you do, you might have trouble contributing to Evennia (since -we can’t see the code you want to share).

-

Note: If you hadn’t setup a public key on GitHub or aren’t asked for a username/password, you might -get an error 403: Forbidden Access at this stage. In that case, some users have reported that the -workaround is to create a file .netrc under your home directory and add your credentials there:

-
machine github.com
-login <my_github_username>
-password <my_github_password>
-
-
-
-
-

Committing fixes to Evennia

-

Contributing can mean both bug-fixes or adding new features to Evennia. Please note that if your -change is not already listed and accepted in the Issue -Tracker, it is recommended that you first hit the -developer mailing list or IRC chat to see beforehand if your feature is deemed suitable to include -as a core feature in the engine. When it comes to bug-fixes, other developers may also have good -input on how to go about resolving the issue.

-

To contribute you need to have forked Evennia first. As described -above you should do your modification in a separate local branch (not in the master branch). This -branch is what you then present to us (as a Pull request, PR, see below). We can then merge your -change into the upstream master and you then do git pull to update master usual. Now that the -master is updated with your fixes, you can safely delete your local work branch. Below we describe -this work flow.

-

First update the Evennia master branch to the latest Evennia version:

-
git checkout master
-git pull upstream master
-
-
-

Next, create a new branch to hold your contribution. Let’s call it the “fixing_strange_bug” branch:

-
git checkout -b fixing_strange_bug
-
-
-

It is wise to make separate branches for every fix or series of fixes you want to contribute. You -are now in your new fixing_strange_bug branch. You can list all branches with git branch and -jump between branches with git checkout <branchname>. Code and test things in here, committing as -you go:

-
git commit --all -m "Fix strange bug in look command. Resolves #123."
-
-
-

You can make multiple commits if you want, depending on your work flow and progress. Make sure to -always make clear and descriptive commit messages so it’s easy to see what you intended. To refer -to, say, issue number 123, write #123, it will turn to a link on GitHub. If you include the text -“Resolves #123”, that issue will be auto-closed on GitHub if your commit gets merged into main -Evennia.

-
-

If you refer to in-game commands that start with @(such as @examine), please put them in -backticks `, for example `@examine`. The reason for this is that GitHub uses @username to refer -to GitHub users, so if you forget the ticks, any user happening to be named examine will get a -notification …

-
-

If you implement multiple separate features/bug-fixes, split them into different branches if they -are very different and should be handled as separate PRs. You can do any number of commits to your -branch as you work. Once you are at a stage where you want to show the world what you did you might -want to consider making it clean for merging into Evennia’s master branch by using git -rebase (this is not always necessary, -and if it sounds too hard, say so and we’ll handle it on our end).

-

Once you are ready, push your work to your online Evennia fork on github, in a new remote branch:

-
git push -u origin fixing_strange_bug
-
-
-

The -u flag is only needed the first time - this tells GIT to create a remote branch. If you -already created the remote branch earlier, just stand in your fixing_strange_bug branch and do -git push.

-

Now you should tell the Evennia developers that they should consider merging your brilliant changes -into Evennia proper. Create a pull request and follow -the instructions. Make sure to specifically select your fixing_strange_bug branch to be the source -of the merge. Evennia developers will then be able to examine your request and merge it if it’s -deemed suitable.

-

Once your changes have been merged into Evennia your local fixing_strange_bug can be deleted -(since your changes are now available in the “clean” Evennia repository). Do

-
git branch -D fixing_strange_bug
-
-
-

to delete your work branch. Update your master branch (checkout master and then git pull) and -you should get your fix back, now as a part of official Evennia!

-
-
-

GIT tips and tricks

-

Some of the GIT commands can feel a little long and clunky if you need to do them often. Luckily you -can create aliases for those. Here are some useful commands to run:

-
# git st
-# - view brief status info
-git config --global alias.st 'status -s'
-
-
-

Above, you only need to ever enter the git config ... command once - you have then added the new -alias. Afterwards, just do git st to get status info. All the examples below follow the same -template.

-
# git cl
-# - clone a repository
-git config --global alias.cl clone
-
-
-
# git cma "commit message"
-# - commit all changes without opening editor for message
-git config --global alias.cma 'commit -a -m'
-
-
-
# git ca
-# - amend text to your latest commit message
-git config --global alias.ca 'commit --amend'
-
-
-
# git fl
-# - file log; shows diffs of files in latest commits
-git config --global alias.fl 'log -u'
-
-
-
# git co [branchname]
-# - checkout
-git config --global alias.co checkout
-
-
-
# git br <branchname>
-# - create branch
-git config --global alias.br branch
-
-
-
# git ls
-# - view log tree
-git config --global alias.ls 'log --pretty=format:"%C(green)%h\ %C(yellow)[%ad]%Cred%d\
-%Creset%s%Cblue\ [%cn]" --decorate --date=relative --graph'
-
-
-
# git diff
-# - show current uncommitted changes
-git config --global alias.diff 'diff --word-diff'
-
-
-
# git grep <query>
-# - search (grep) codebase for a search criterion
-git config --global alias.grep 'grep -Ii'
-
-
-

To get a further feel for GIT there is also a good YouTube talk about -it - it’s a bit long but it will help you -understand the underlying ideas behind GIT -(which in turn makes it a lot more intuitive to use).

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Weather-Tutorial.html b/docs/0.9.5/Weather-Tutorial.html deleted file mode 100644 index b7e3dc6775..0000000000 --- a/docs/0.9.5/Weather-Tutorial.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - Weather Tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Weather Tutorial

-

This tutorial will have us create a simple weather system for our MUD. The way we want to use this -is to have all outdoor rooms echo weather-related messages to the room at regular and semi-random -intervals. Things like “Clouds gather above”, “It starts to rain” and so on.

-

One could imagine every outdoor room in the game having a script running on themselves that fires -regularly. For this particular example it is however more efficient to do it another way, namely by -using a “ticker-subscription” model. The principle is simple: Instead of having each Object -individually track the time, they instead subscribe to be called by a global ticker who handles time -keeping. Not only does this centralize and organize much of the code in one place, it also has less -computing overhead.

-

Evennia offers the TickerHandler specifically for using the subscription model. We -will use it for our weather system.

-

We will assume you know how to make your own Typeclasses. If not see one of the beginning tutorials. -We will create a new WeatherRoom typeclass that is aware of the day-night cycle.

-

-    import random
-    from evennia import DefaultRoom, TICKER_HANDLER
-    
-    ECHOES = ["The sky is clear.",
-              "Clouds gather overhead.",
-              "It's starting to drizzle.",
-              "A breeze of wind is felt.",
-              "The wind is picking up"] # etc
-
-    class WeatherRoom(DefaultRoom):
-        "This room is ticked at regular intervals"
-       
-        def at_object_creation(self):
-            "called only when the object is first created"
-            TICKER_HANDLER.add(60 * 60, self.at_weather_update)
-
-        def at_weather_update(self, *args, **kwargs):
-            "ticked at regular intervals"
-            echo = random.choice(ECHOES)
-            self.msg_contents(echo)
-
-
-

In the at_object_creation method, we simply added ourselves to the TickerHandler and tell it to -call at_weather_update every hour (60*60 seconds). During testing you might want to play with a -shorter time duration.

-

For this to work we also create a custom hook at_weather_update(*args, **kwargs), which is the -call sign required by TickerHandler hooks.

-

Henceforth the room will inform everyone inside it when the weather changes. This particular example -is of course very simplistic - the weather echoes are just randomly chosen and don’t care what -weather came before it. Expanding it to be more realistic is a useful exercise.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Web-Character-Generation.html b/docs/0.9.5/Web-Character-Generation.html deleted file mode 100644 index bacbb89e78..0000000000 --- a/docs/0.9.5/Web-Character-Generation.html +++ /dev/null @@ -1,751 +0,0 @@ - - - - - - - - - Web Character Generation — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Web Character Generation

-
-

Introduction

-

This tutorial will create a simple web-based interface for generating a new in-game Character. -Accounts will need to have first logged into the website (with their AccountDB account). Once -finishing character generation the Character will be created immediately and the Accounts can then -log into the game and play immediately (the Character will not require staff approval or anything -like that). This guide does not go over how to create an AccountDB on the website with the right -permissions to transfer to their web-created characters.

-

It is probably most useful to set MULTISESSION_MODE = 2 or 3 (which gives you a character- -selection screen when you log into the game later). Other modes can be used with some adaptation to -auto-puppet the new Character.

-

You should have some familiarity with how Django sets up its Model Template View framework. You need -to understand what is happening in the basic Web Character View tutorial. If you don’t understand the listed tutorial or have a grasp of Django basics, please look -at the Django tutorial to get a taste of what Django -does, before throwing Evennia into the mix (Evennia shares its API and attributes with the website -interface). This guide will outline the format of the models, views, urls, and html templates -needed.

-
-
-

Pictures

-

Here are some screenshots of the simple app we will be making.

-

Index page, with no character application yet done:

-
-

Index page, with no character application yet done.

-
-

Having clicked the “create” link you get to create your character (here we will only have name and -background, you can add whatever is needed to fit your game):

-
-

Character creation.

-
-

Back to the index page. Having entered our character application (we called our character “TestApp”) -you see it listed:

-
-

Having entered an application.

-
-

We can also view an already written character application by clicking on it - this brings us to the -detail page:

-
-

Detail view of character application.

-
-
-
-

Installing an App

-

Assuming your game is named “mygame”, navigate to your mygame/ directory, and type:

-
evennia startapp chargen
-
-
-

This will initialize a new Django app we choose to call “chargen”. It is directory containing some -basic starting things Django needs. You will need to move this directory: for the time being, it is -in your mygame directory. Better to move it in your mygame/web directory, so you have -mygame/web/chargen in the end.

-

Next, navigate to mygame/server/conf/settings.py and add or edit the following line to make -Evennia (and Django) aware of our new app:

-
INSTALLED_APPS += ('web.chargen',)
-
-
-

After this, we will get into defining our models (the description of the database storage), -views (the server-side website content generators), urls (how the web browser finds the pages) -and templates (how the web page should be structured).

-
-

Installing - Checkpoint:

-
    -
  • you should have a folder named chargen or whatever you chose in your mygame/web/ directory

  • -
  • you should have your application name added to your INSTALLED_APPS in settings.py

  • -
-
-
-
-

Create Models

-

Models are created in mygame/web/chargen/models.py.

-

A Django database model is a Python class that describes the database storage of the -data you want to manage. Any data you choose to store is stored in the same database as the game and -you have access to all the game’s objects here.

-

We need to define what a character application actually is. This will differ from game to game so -for this tutorial we will define a simple character sheet with the following database fields:

-
    -
  • app_id (AutoField): Primary key for this character application sheet.

  • -
  • char_name (CharField): The new character’s name.

  • -
  • date_applied (DateTimeField): Date that this application was received.

  • -
  • background (TextField): Character story background.

  • -
  • account_id (IntegerField): Which account ID does this application belong to? This is an -AccountID from the AccountDB object.

  • -
  • submitted (BooleanField): True/False depending on if the application has been submitted yet.

  • -
-
-

Note: In a full-fledged game, you’d likely want them to be able to select races, skills, -attributes and so on.

-
-

Our models.py file should look something like this:

-
# in mygame/web/chargen/models.py
-
-from django.db import models
-
-class CharApp(models.Model):
-    app_id = models.AutoField(primary_key=True)
-    char_name = models.CharField(max_length=80, verbose_name='Character Name')
-    date_applied = models.DateTimeField(verbose_name='Date Applied')
-    background = models.TextField(verbose_name='Background')
-    account_id = models.IntegerField(default=1, verbose_name='Account ID')
-    submitted = models.BooleanField(default=False)
-
-
-

You should consider how you are going to link your application to your account. For this tutorial, -we are using the account_id attribute on our character application model in order to keep track of -which characters are owned by which accounts. Since the account id is a primary key in Evennia, it -is a good candidate, as you will never have two of the same IDs in Evennia. You can feel free to use -anything else, but for the purposes of this guide, we are going to use account ID to join the -character applications with the proper account.

-
-

Model - Checkpoint:

-
    -
  • you should have filled out mygame/web/chargen/models.py with the model class shown above -(eventually adding fields matching what you need for your game).

  • -
-
-
-
-

Create Views

-

Views are server-side constructs that make dynamic data available to a web page. We are going to -add them to mygame/web/chargen.views.py. Each view in our example represents the backbone of a -specific web page. We will use three views and three pages here:

-
    -
  • The index (managing index.html). This is what you see when you navigate to -http://yoursite.com/chargen.

  • -
  • The detail display sheet (manages detail.html). A page that passively displays the stats of a -given Character.

  • -
  • Character creation sheet (manages create.html). This is the main form with fields to fill in.

  • -
-
-

Index view

-

Let’s get started with the index first.

-

We’ll want characters to be able to see their created characters so let’s

-
# file mygame/web/chargen.views.py
-
-from .models import CharApp
-
-def index(request):
-    current_user = request.user # current user logged in
-    p_id = current_user.id # the account id
-    # submitted Characters by this account
-    sub_apps = CharApp.objects.filter(account_id=p_id, submitted=True)
-    context = {'sub_apps': sub_apps}
-    # make the variables in 'context' available to the web page template
-    return render(request, 'chargen/index.html', context)
-
-
-
-
-

Detail view

-

Our detail page will have pertinent character application information our users can see. Since this -is a basic demonstration, our detail page will only show two fields:

-
    -
  • Character name

  • -
  • Character background

  • -
-

We will use the account ID again just to double-check that whoever tries to check our character page -is actually the account who owns the application.

-
# file mygame/web/chargen.views.py
-
-def detail(request, app_id):
-    app = CharApp.objects.get(app_id=app_id)
-    name = app.char_name
-    background = app.background
-    submitted = app.submitted
-    p_id = request.user.id
-    context = {'name': name, 'background': background,
-        'p_id': p_id, 'submitted': submitted}
-    return render(request, 'chargen/detail.html', context)
-
-
-
-
-
-

Creating view

-

Predictably, our create function will be the most complicated of the views, as it needs to accept -information from the user, validate the information, and send the information to the server. Once -the form content is validated will actually create a playable Character.

-

The form itself we will define first. In our simple example we are just looking for the Character’s -name and background. This form we create in mygame/web/chargen/forms.py:

-
# file mygame/web/chargen/forms.py
-
-from django import forms
-
-class AppForm(forms.Form):
-    name = forms.CharField(label='Character Name', max_length=80)
-    background = forms.CharField(label='Background')
-
-
-

Now we make use of this form in our view.

-
# file mygame/web/chargen/views.py
-
-from web.chargen.models import CharApp
-from web.chargen.forms import AppForm
-from django.http import HttpResponseRedirect
-from datetime import datetime
-from evennia.objects.models import ObjectDB
-from django.conf import settings
-from evennia.utils import create
-
-def creating(request):
-    user = request.user
-    if request.method == 'POST':
-        form = AppForm(request.POST)
-        if form.is_valid():
-            name = form.cleaned_data['name']
-            background = form.cleaned_data['background']
-            applied_date = datetime.now()
-            submitted = True
-            if 'save' in request.POST:
-                submitted = False
-            app = CharApp(char_name=name, background=background,
-            date_applied=applied_date, account_id=user.id,
-            submitted=submitted)
-            app.save()
-            if submitted:
-                # Create the actual character object
-                typeclass = settings.BASE_CHARACTER_TYPECLASS
-                home = ObjectDB.objects.get_id(settings.GUEST_HOME)
-                # turn the permissionhandler to a string
-                perms = str(user.permissions)
-                # create the character
-                char = create.create_object(typeclass=typeclass, key=name,
-                    home=home, permissions=perms)
-                user.db._playable_characters.append(char)
-                # add the right locks for the character so the account can
-                #  puppet it
-                char.locks.add("puppet:id(%i) or pid(%i) or perm(Developers) "
-                    "or pperm(Developers)" % (char.id, user.id))
-                char.db.background = background # set the character background
-            return HttpResponseRedirect('/chargen')
-    else:
-        form = AppForm()
-    return render(request, 'chargen/create.html', {'form': form})
-
-
-
-

Note also that we basically create the character using the Evennia API, and we grab the proper -permissions from the AccountDB object and copy them to the character object. We take the user -permissions attribute and turn that list of strings into a string object in order for the -create_object function to properly process the permissions.

-
-

Most importantly, the following attributes must be set on the created character object:

-
    -
  • Evennia permissions (copied from the AccountDB).

  • -
  • The right puppet locks so the Account can actually play as this Character later.

  • -
  • The relevant Character typeclass

  • -
  • Character name (key)

  • -
  • The Character’s home room location (#2 by default)

  • -
-

Other attributes are strictly speaking optional, such as the background attribute on our -character. It may be a good idea to decompose this function and create a separate _create_character -function in order to set up your character object the account owns. But with the Evennia API, -setting custom attributes is as easy as doing it in the meat of your Evennia game directory.

-

After all of this, our views.py file should look like something like this:

-
# file mygame/web/chargen/views.py
-
-from django.shortcuts import render
-from web.chargen.models import CharApp
-from web.chargen.forms import AppForm
-from django.http import HttpResponseRedirect
-from datetime import datetime
-from evennia.objects.models import ObjectDB
-from django.conf import settings
-from evennia.utils import create
-
-def index(request):
-    current_user = request.user # current user logged in
-    p_id = current_user.id # the account id
-    # submitted apps under this account
-    sub_apps = CharApp.objects.filter(account_id=p_id, submitted=True)
-    context = {'sub_apps': sub_apps}
-    return render(request, 'chargen/index.html', context)
-
-def detail(request, app_id):
-    app = CharApp.objects.get(app_id=app_id)
-    name = app.char_name
-    background = app.background
-    submitted = app.submitted
-    p_id = request.user.id
-    context = {'name': name, 'background': background,
-        'p_id': p_id, 'submitted': submitted}
-    return render(request, 'chargen/detail.html', context)
-
-def creating(request):
-    user = request.user
-    if request.method == 'POST':
-        form = AppForm(request.POST)
-        if form.is_valid():
-            name = form.cleaned_data['name']
-            background = form.cleaned_data['background']
-            applied_date = datetime.now()
-            submitted = True
-            if 'save' in request.POST:
-                submitted = False
-            app = CharApp(char_name=name, background=background,
-            date_applied=applied_date, account_id=user.id,
-            submitted=submitted)
-            app.save()
-            if submitted:
-                # Create the actual character object
-                typeclass = settings.BASE_CHARACTER_TYPECLASS
-                home = ObjectDB.objects.get_id(settings.GUEST_HOME)
-                # turn the permissionhandler to a string
-                perms = str(user.permissions)
-                # create the character
-                char = create.create_object(typeclass=typeclass, key=name,
-                    home=home, permissions=perms)
-                user.db._playable_characters.append(char)
-                # add the right locks for the character so the account can
-                #  puppet it
-                char.locks.add("puppet:id(%i) or pid(%i) or perm(Developers) "
-                    "or pperm(Developers)" % (char.id, user.id))
-                char.db.background = background # set the character background
-            return HttpResponseRedirect('/chargen')
-    else:
-        form = AppForm()
-    return render(request, 'chargen/create.html', {'form': form})
-
-
-
-

Create Views - Checkpoint:

-
    -
  • you’ve defined a views.py that has an index, detail, and creating functions.

  • -
  • you’ve defined a forms.py with the AppForm class needed by the creating function of -views.py.

  • -
  • your mygame/web/chargen directory should now have a views.py and forms.py file

  • -
-
-
-
-

Create URLs

-

URL patterns helps redirect requests from the web browser to the right views. These patterns are -created in mygame/web/chargen/urls.py.

-
# file mygame/web/chargen/urls.py
-
-from django.conf.urls import url
-from web.chargen import views
-
-urlpatterns = [
-    # ex: /chargen/
-    url(r'^$', views.index, name='index'),
-    # ex: /chargen/5/
-    url(r'^(?P<app_id>[0-9]+)/$', views.detail, name='detail'),
-    # ex: /chargen/create
-    url(r'^create/$', views.creating, name='creating'),
-]
-
-
-

You could change the format as you desire. To make it more secure, you could remove app_id from the -“detail” url, and instead just fetch the account’s applications using a unifying field like -account_id to find all the character application objects to display.

-

We must also update the main mygame/web/urls.py file (that is, one level up from our chargen app), -so the main website knows where our app’s views are located. Find the patterns variable, and -change it to include:

-
# in file mygame/web/urls.py
-
-from django.conf.urls import url, include
-
-# default evennia patterns
-from evennia.web.urls import urlpatterns
-
-# eventual custom patterns
-custom_patterns = [
-    # url(r'/desired/url/', view, name='example'),
-]
-
-# this is required by Django.
-urlpatterns += [
-    url(r'^chargen/', include('web.chargen.urls')),
-]
-
-urlpatterns = custom_patterns + urlpatterns
-
-
-
-

URLs - Checkpoint:

-
    -
  • You’ve created a urls.py file in the mygame/web/chargen directory

  • -
  • You have edited the main mygame/web/urls.py file to include urls to the chargen directory.

  • -
-
-
-
-

HTML Templates

-

So we have our url patterns, views, and models defined. Now we must define our HTML templates that -the actual user will see and interact with. For this tutorial we us the basic prosimii template -that comes with Evennia.

-

Take note that we use user.is_authenticated to make sure that the user cannot create a character -without logging in.

-

These files will all go into the /mygame/web/chargen/templates/chargen/ directory.

-
-

index.html

-

This HTML template should hold a list of all the applications the account currently has active. For -this demonstration, we will only list the applications that the account has submitted. You could -easily adjust this to include saved applications, or other types of applications if you have -different kinds.

-

Please refer back to views.py to see where we define the variables these templates make use of.

-
<!-- file mygame/web/chargen/templates/chargen/index.html-->
-
-{% extends "base.html" %}
-{% block content %}
-{% if user.is_authenticated %}
-    <h1>Character Generation</h1>
-    {% if sub_apps %}
-        <ul>
-        {% for sub_app in sub_apps %}
-            <li><a href="/chargen/{{ sub_app.app_id }}/">{{ sub_app.char_name }}</a></li>
-        {% endfor %}
-        </ul>
-    {% else %}
-        <p>You haven't submitted any character applications.</p>
-    {% endif %}
-  {% else %}
-    <p>Please <a href="{% url 'login'%}">login</a>first.<a/></p>
-{% endif %}
-{% endblock %}
-
-
-
-
-

detail.html

-

This page should show a detailed character sheet of their application. This will only show their -name and character background. You will likely want to extend this to show many more fields for your -game. In a full-fledged character generation, you may want to extend the boolean attribute of -submitted to allow accounts to save character applications and submit them later.

-
<!-- file mygame/web/chargen/templates/chargen/detail.html-->
-
-{% extends "base.html" %}
-{% block content %}
-<h1>Character Information</h1>
-{% if user.is_authenticated %}
-    {% if user.id == p_id %}
-        <h2>{{name}}</h2>
-        <h2>Background</h2>
-        <p>{{background}}</p>
-        <p>Submitted: {{submitted}}</p>
-    {% else %}
-        <p>You didn't submit this character.</p>
-    {% endif %}
-{% else %}
-<p>You aren't logged in.</p>
-{% endif %}
-{% endblock %}
-
-
-
-
-

create.html

-

Our create HTML template will use the Django form we defined back in views.py/forms.py to drive the -majority of the application process. There will be a form input for every field we defined in -forms.py, which is handy. We have used POST as our method because we are sending information to the -server that will update the database. As an alternative, GET would be much less secure. You can read -up on documentation elsewhere on the web for GET vs. POST.

-
<!-- file mygame/web/chargen/templates/chargen/create.html-->
-
-{% extends "base.html" %}
-{% block content %}
-<h1>Character Creation</h1>
-{% if user.is_authenticated %}
-<form action="/chargen/create/" method="post">
-    {% csrf_token %}
-    {{ form }}
-    <input type="submit" name="submit" value="Submit"/>
-</form>
-{% else %}
-<p>You aren't logged in.</p>
-{% endif %}
-{% endblock %}
-
-
-
-
-

Templates - Checkpoint:

-
    -
  • Create a index.html, detail.html and create.html template in your -mygame/web/chargen/templates/chargen directory

  • -
-
-
-
-

Activating your new character generation

-

After finishing this tutorial you should have edited or created the following files:

-
mygame/web/urls.py
-mygame/web/chargen/models.py
-mygame/web/chargen/views.py
-mygame/web/chargen/urls.py
-mygame/web/chargen/templates/chargen/index.html
-mygame/web/chargen/templates/chargen/create.html
-mygame/web/chargen/templates/chargen/detail.html
-
-
-

Once you have all these files stand in your mygame/folder and run:

-
evennia makemigrations
-evennia migrate
-
-
-

This will create and update the models. If you see any errors at this stage, read the traceback -carefully, it should be relatively easy to figure out where the error is.

-

Login to the website (you need to have previously registered an Player account with the game to do -this). Next you navigate to http://yourwebsite.com/chargen (if you are running locally this will -be something like http://localhost:4001/chargen and you will see your new app in action.

-

This should hopefully give you a good starting point in figuring out how you’d like to approach your -own web generation. The main difficulties are in setting the appropriate settings on your newly -created character object. Thankfully, the Evennia API makes this easy.

-
-
-

Adding a no CAPCHA reCAPCHA on your character generation

-

As sad as it is, if your server is open to the web, bots might come to visit and take advantage of -your open form to create hundreds, thousands, millions of characters if you give them the -opportunity. This section shows you how to use the No CAPCHA reCAPCHA designed by Google. Not only is it -easy to use, it is user-friendly… for humans. A simple checkbox to check, except if Google has -some suspicion, in which case you will have a more difficult test with an image and the usual text -inside. It’s worth pointing out that, as long as Google doesn’t suspect you of being a robot, this -is quite useful, not only for common users, but to screen-reader users, to which reading inside of -an image is pretty difficult, if not impossible. And to top it all, it will be so easy to add in -your website.

-
-

Step 1: Obtain a SiteKey and secret from Google

-

The first thing is to ask Google for a way to safely authenticate your website to their service. To -do it, we need to create a site key and a secret. Go to -https://www.google.com/recaptcha/admin to create such a -site key. It’s quite easy when you have a Google account.

-

When you have created your site key, save it safely. Also copy your secret key as well. You should -find both information on the web page. Both would contain a lot of letters and figures.

-
-
-

Step 2: installing and configuring the dedicated Django app

-

Since Evennia runs on Django, the easiest way to add our CAPCHA and perform the proper check is to -install the dedicated Django app. Quite easy:

-
pip install django-nocaptcha-recaptcha
-
-
-

And add it to the installed apps in your settings. In your mygame/server/conf/settings.py, you -might have something like this:

-
# ...
-INSTALLED_APPS += (
-    'web.chargen',
-    'nocaptcha_recaptcha',
-)
-
-
-

Don’t close the setting file just yet. We have to add in the site key and secret key. You can add -them below:

-
# NoReCAPCHA site key
-NORECAPTCHA_SITE_KEY = "PASTE YOUR SITE KEY HERE"
-# NoReCAPCHA secret key
-NORECAPTCHA_SECRET_KEY = "PUT YOUR SECRET KEY HERE"
-
-
-
-
-

Step 3: Adding the CAPCHA to our form

-

Finally we have to add the CAPCHA to our form. It will be pretty easy too. First, open your -web/chargen/forms.py file. We’re going to add a new field, but hopefully, all the hard work has -been done for us. Update at your convenience, You might end up with something like this:

-
from django import forms
-from nocaptcha_recaptcha.fields import NoReCaptchaField
-
-class AppForm(forms.Form):
-    name = forms.CharField(label='Character Name', max_length=80)
-    background = forms.CharField(label='Background')
-    captcha = NoReCaptchaField()
-
-
-

As you see, we added a line of import (line 2) and a field in our form.

-

And lastly, we need to update our HTML file to add in the Google library. You can open -web/chargen/templates/chargen/create.html. There’s only one line to add:

-
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
-
-
-

And you should put it at the bottom of the page. Just before the closing body would be good, but -for the time being, the base page doesn’t provide a footer block, so we’ll put it in the content -block. Note that it’s not the best place, but it will work. In the end, your -web/chargen/templates/chargen/create.html file should look like this:

-
{% extends "base.html" %}
-{% block content %}
-<h1>Character Creation</h1>
-{% if user.is_authenticated %}
-<form action="/chargen/create/" method="post">
-    {% csrf_token %}
-    {{ form }}
-    <input type="submit" name="submit" value="Submit"/>
-</form>
-{% else %}
-<p>You aren't logged in.</p>
-{% endif %}
-<script src="https://www.google.com/recaptcha/api.js" async defer></script>
-{% endblock %}
-
-
-

Reload and open http://localhost:4001/chargen/create and -you should see your beautiful CAPCHA just before the “submit” button. Try not to check the checkbox -to see what happens. And do the same while checking the checkbox!

-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Web-Character-View-Tutorial.html b/docs/0.9.5/Web-Character-View-Tutorial.html deleted file mode 100644 index da52280643..0000000000 --- a/docs/0.9.5/Web-Character-View-Tutorial.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - - - - Web Character View Tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Web Character View Tutorial

-

Before doing this tutorial you will probably want to read the intro in Basic Web tutorial.

-

In this tutorial we will create a web page that displays the stats of a game character. For this, -and all other pages we want to make specific to our game, we’ll need to create our own Django “app”

-

We’ll call our app character, since it will be dealing with character information. From your game -dir, run

-
evennia startapp character
-
-
-

This will create a directory named character in the root of your game dir. It contains all basic -files that a Django app needs. To keep mygame well ordered, move it to your mygame/web/ -directory instead:

-
mv character web/
-
-
-

Note that we will not edit all files in this new directory, many of the generated files are outside -the scope of this tutorial.

-

In order for Django to find our new web app, we’ll need to add it to the INSTALLED_APPS setting. -Evennia’s default installed apps are already set, so in server/conf/settings.py, we’ll just extend -them:

-
INSTALLED_APPS += ('web.character',)
-
-
-
-

Note: That end comma is important. It makes sure that Python interprets the addition as a tuple -instead of a string.

-
-

The first thing we need to do is to create a view and an URL pattern to point to it. A view is a -function that generates the web page that a visitor wants to see, while the URL pattern lets Django -know what URL should trigger the view. The pattern may also provide some information of its own as -we shall see.

-

Here is our character/urls.py file (Note: you may have to create this file if a blank one -wasn’t generated for you):

-
# URL patterns for the character app
-
-from django.conf.urls import url
-from web.character.views import sheet
-
-urlpatterns = [
-    url(r'^sheet/(?P<object_id>\d+)/$', sheet, name="sheet")
-]
-
-
-

This file contains all of the URL patterns for the application. The url function in the -urlpatterns list are given three arguments. The first argument is a pattern-string used to -identify which URLs are valid. Patterns are specified as regular expressions. Regular expressions -are used to match strings and are written in a special, very compact, syntax. A detailed description -of regular expressions is beyond this tutorial but you can learn more about them -here. For now, just accept that this regular -expression requires that the visitor’s URL looks something like this:

-
sheet/123/
-
-
-

That is, sheet/ followed by a number, rather than some other possible URL pattern. We will -interpret this number as object ID. Thanks to how the regular expression is formulated, the pattern -recognizer stores the number in a variable called object_id. This will be passed to the view (see -below). We add the imported view function (sheet) in the second argument. We also add the name -keyword to identify the URL pattern itself. You should always name your URL patterns, this makes -them easy to refer to in html templates using the {% url %} tag (but we won’t get more into that -in this tutorial).

-
-

Security Note: Normally, users do not have the ability to see object IDs within the game (it’s -restricted to superusers only). Exposing the game’s object IDs to the public like this enables -griefers to perform what is known as an account enumeration -attack in the efforts of -hijacking your superuser account. Consider this: in every Evennia installation, there are two -objects that we can always expect to exist and have the same object IDs– Limbo (#2) and the -superuser you create in the beginning (#1). Thus, the griefer can get 50% of the information they -need to hijack the admin account (the admin’s username) just by navigating to sheet/1!

-
-

Next we create views.py, the view file that urls.py refers to.

-
# Views for our character app
-
-from django.http import Http404
-from django.shortcuts import render
-from django.conf import settings
-
-from evennia.utils.search import object_search
-from evennia.utils.utils import inherits_from
-
-def sheet(request, object_id):
-    object_id = '#' + object_id
-    try:
-        character = object_search(object_id)[0]
-    except IndexError:
-        raise Http404("I couldn't find a character with that ID.")
-    if not inherits_from(character, settings.BASE_CHARACTER_TYPECLASS):
-        raise Http404("I couldn't find a character with that ID. "
-                      "Found something else instead.")
-    return render(request, 'character/sheet.html', {'character': character})
-
-
-

As explained earlier, the URL pattern parser in urls.py parses the URL and passes object_id to -our view function sheet. We do a database search for the object using this number. We also make -sure such an object exists and that it is actually a Character. The view function is also handed a -request object. This gives us information about the request, such as if a logged-in user viewed it

-
    -
  • we won’t use that information here but it is good to keep in mind.

  • -
-

On the last line, we call the render function. Apart from the request object, the render -function takes a path to an html template and a dictionary with extra data you want to pass into -said template. As extra data we pass the Character object we just found. In the template it will be -available as the variable “character”.

-

The html template is created as templates/character/sheet.html under your character app folder. -You may have to manually create both template and its subfolder character. Here’s the template -to create:

-
{% extends "base.html" %}
-{% block content %}
-
-    <h1>{{ character.name }}</h1>
-
-    <p>{{ character.db.desc }}</p>
-
-    <h2>Stats</h2>
-    <table>
-      <thead>
-        <tr>
-          <th>Stat</th>
-          <th>Value</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <td>Strength</td>
-          <td>{{ character.db.str }}</td>
-        </tr>
-        <tr>
-          <td>Intelligence</td>
-          <td>{{ character.db.int }}</td>
-        </tr>
-        <tr>
-          <td>Speed</td>
-          <td>{{ character.db.spd }}</td>
-        </tr>
-      </tbody>
-    </table>
-
-    <h2>Skills</h2>
-    <ul>
-      {% for skill in character.db.skills %}
-        <li>{{ skill }}</li>
-      {% empty %}
-        <li>This character has no skills yet.</li>
-      {% endfor %}
-    </ul>
-
-    {% if character.db.approved %}
-      <p class="success">This character has been approved!</p>
-    {% else %}
-      <p class="warning">This character has not yet been approved!</p>
-    {% endif %}
-{% endblock %}
-
-
-

In Django templates, {% ... %} denotes special in-template “functions” that Django understands. -The {{ ... }} blocks work as “slots”. They are replaced with whatever value the code inside the -block returns.

-

The first line, {% extends "base.html" %}, tells Django that this template extends the base -template that Evennia is using. The base template is provided by the theme. Evennia comes with the -open-source third-party theme prosimii. You can find it and its base.html in -evennia/web/templates/prosimii. Like other templates, these can be overwritten.

-

The next line is {% block content %}. The base.html file has blocks, which are placeholders -that templates can extend. The main block, and the one we use, is named content.

-

We can access the character variable anywhere in the template because we passed it in the render -call at the end of view.py. That means we also have access to the Character’s db attributes, -much like you would in normal Python code. You don’t have the ability to call functions with -arguments in the template– in fact, if you need to do any complicated logic, you should do it in -view.py and pass the results as more variables to the template. But you still have a great deal of -flexibility in how you display the data.

-

We can do a little bit of logic here as well. We use the {% for %} ... {% endfor %} and {% if %} ... {% else %} ... {% endif %} structures to change how the template renders depending on how many -skills the user has, or if the user is approved (assuming your game has an approval system).

-

The last file we need to edit is the master URLs file. This is needed in order to smoothly integrate -the URLs from your new character app with the URLs from Evennia’s existing pages. Find the file -web/urls.py and update its patterns list as follows:

-
# web/urls.py
-
-custom_patterns = [
-    url(r'^character/', include('web.character.urls'))
-   ]
-
-
-

Now reload the server with evennia reload and visit the page in your browser. If you haven’t -changed your defaults, you should be able to find the sheet for character #1 at -http://localhost:4001/character/sheet/1/

-

Try updating the stats in-game and refresh the page in your browser. The results should show -immediately.

-

As an optional final step, you can also change your character typeclass to have a method called -‘get_absolute_url’.

-
# typeclasses/characters.py
-
-    # inside Character
-    def get_absolute_url(self):
-        from django.urls import reverse
-        return reverse('character:sheet', kwargs={'object_id':self.id})
-
-
-

Doing so will give you a ‘view on site’ button in the top right of the Django Admin Objects -changepage that links to your new character sheet, and allow you to get the link to a character’s -page by using in any template where you have a given object.

-

Now that you’ve made a basic page and app with Django, you may want to read the full Django -tutorial to get a better idea of what it can do. You can find Django’s tutorial -here.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Web-Features.html b/docs/0.9.5/Web-Features.html deleted file mode 100644 index 4b42737fc3..0000000000 --- a/docs/0.9.5/Web-Features.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - - - - Web Features — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Web Features

-

Evennia is its own webserver and hosts a default website and browser webclient.

-
-

Web site

-

The Evennia website is a Django application that ties in with the MUD database. Since the website -shares this database you could, for example, tell website visitors how many accounts are logged into -the game at the moment, how long the server has been up and any other database information you may -want. During development you can access the website by pointing your browser to -http://localhost:4001.

-
-

You may also want to set DEBUG = True in your settings file for debugging the website. You will -then see proper tracebacks in the browser rather than just error codes. Note however that this will -leak memory a lot (it stores everything all the time) and is not to be used in production. It’s -recommended to only use DEBUG for active web development and to turn it off otherwise.

-
-

A Django (and thus Evennia) website basically consists of three parts, a -view an associated -template and an urls.py file. Think of -the view as the Python back-end and the template as the HTML files you are served, optionally filled -with data from the back-end. The urls file is a sort of mapping that tells Django that if a specific -URL is given in the browser, a particular view should be triggered. You are wise to review the -Django documentation for details on how to use these components.

-

Evennia’s default website is located in -evennia/web/website. In this -folder you’ll find the simple default view as well as subfolders templates and static. Static -files are things like images, CSS files and Javascript.

-
-

Customizing the Website

-

You customize your website from your game directory. In the folder web you’ll find folders -static, templates, static_overrides and templates_overrides. The first two of those are -populated automatically by Django and used to serve the website. You should not edit anything in -them - the change will be lost. To customize the website you’ll need to copy the file you want to -change from the web/website/template/ or web/website/static/ path to the corresponding place -under one of _overrides directories.

-

Example: To override or modify evennia/web/website/template/website/index.html you need to -add/modify mygame/web/template_overrides/website/index.html.

-

The detailed description on how to customize the website is best described in tutorial form. See the -Web Tutorial for more information.

-
-
-

Overloading Django views

-

The Python backend for every HTML page is called a Django -view. A view can do all sorts of -functions, but the main one is to update variables data that the page can display, like how your -out-of-the-box website will display statistics about number of users and database objects.

-

To re-point a given page to a view.py of your own, you need to modify mygame/web/urls.py. An -URL pattern is a regular -expression that you need to enter in the address -field of your web browser to get to the page in question. If you put your own URL pattern before -the default ones, your own view will be used instead. The file urls.py even marks where you should -put your change.

-

Here’s an example:

-
# mygame/web/urls.py
-
-from django.conf.urls import url, include
-# default patterns
-from evennia.web.urls import urlpatterns
-
-# our own view to use as a replacement
-from web.myviews import myview
-
-# custom patterns to add
-patterns = [
-    # overload the main page view
-    url(r'^', myview, name='mycustomview'),
-]
-
-urlpatterns = patterns + urlpatterns
-
-
-
-

Django will always look for a list named urlpatterns which consists of the results of url() -calls. It will use the first match it finds in this list. Above, we add a new URL redirect from -the root of the website. It will now our own function myview from a new module -mygame/web/myviews.py.

-
-

If our game is found on http://mygame.com, the regular expression "^" means we just entered -mygame.com in the address bar. If we had wanted to add a view for http://mygame.com/awesome, the -regular expression would have been ^/awesome.

-
-

Look at evennia/web/website/views.py to see the inputs and outputs you must have to define a view. Easiest may be to -copy the default file to mygame/web to have something to modify and expand on.

-

Restart the server and reload the page in the browser - the website will now use your custom view. -If there are errors, consider turning on settings.DEBUG to see the full tracebacks - in debug mode -you will also log all requests in mygame/server/logs/http_requests.log.

-
-
-
-

Web client

-

Evennia comes with a MUD client accessible from a normal web browser. During -development you can try it at http://localhost:4001/webclient. -See the Webclient page for more details.

-
-
-

The Django ‘Admin’ Page

-

Django comes with a built-in admin -website. This is accessible by clicking -the ‘admin’ button from your game website. The admin site allows you to see, edit and create objects -in your database from a graphical interface.

-

The behavior of default Evennia models are controlled by files admin.py in the Evennia package. -New database models you choose to add yourself (such as in the Web Character View Tutorial) can/will -also have admin.py files. New models are registered to the admin website by a call of -admin.site.register(model class, admin class) inside an admin.py file. It is an error to attempt -to register a model that has already been registered.

-

To overload Evennia’s admin files you don’t need to modify Evennia itself. To customize you can call -admin.site.unregister(model class), then follow that with admin.site.register in one of your own -admin.py files in a new app that you add.

-
-
-

More reading

-

Evennia relies on Django for its web features. For details on expanding your web experience, the -Django documentation or the Django -Book are the main resources to look into. In Django -lingo, the Evennia is a django “project” that consists of Django “applications”. For the sake of web -implementation, the relevant django “applications” in default Evennia are web/website or -web/webclient.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Web-Tutorial.html b/docs/0.9.5/Web-Tutorial.html deleted file mode 100644 index 963bb3b617..0000000000 --- a/docs/0.9.5/Web-Tutorial.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - - - - Web Tutorial — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Web Tutorial

-

Evennia uses the Django web framework as the basis of both its -database configuration and the website it provides. While a full understanding of Django requires -reading the Django documentation, we have provided this tutorial to get you running with the basics -and how they pertain to Evennia. This text details getting everything set up. The Web-based -Character view Tutorial gives a more explicit example of making a -custom web page connected to your game, and you may want to read that after finishing this guide.

-
-

A Basic Overview

-

Django is a web framework. It gives you a set of development tools for building a website quickly -and easily.

-

Django projects are split up into apps and these apps all contribute to one project. For instance, -you might have an app for conducting polls, or an app for showing news posts or, like us, one for -creating a web client.

-

Each of these applications has a urls.py file, which specifies what -URLs are used by the app, a views.py file -for the code that the URLs activate, a templates directory for displaying the results of that code -in HTML for the user, and a static folder that holds assets -like CSS, Javascript, -and Image files (You may note your mygame/web folder does not have a static or template folder. -This is intended and explained further below). Django applications may also have a models.py file -for storing information in the database. We “cmd: attr(locktest, 80, compare=gt)”will not change any -models here, take a look at the New Models page (as well as the Django -docs on models) if you are interested.

-

There is also a root urls.py that determines the URL structure for the entire project. A starter -urls.py is included in the default game template, and automatically imports all of Evennia’s -default URLs for you. This is located in web/urls.py.

-
-
-

Changing the logo on the front page

-

Evennia’s default logo is a fun little googly-eyed snake wrapped around a gear globe. As cute as it -is, it probably doesn’t represent your game. So one of the first things you may wish to do is -replace it with a logo of your own.

-

Django web apps all have static assets: CSS files, Javascript files, and Image files. In order to -make sure the final project has all the static files it needs, the system collects the files from -every app’s static folder and places it in the STATIC_ROOT defined in settings.py. By default, -the Evennia STATIC_ROOT is in web/static.

-

Because Django pulls files from all of those separate places and puts them in one folder, it’s -possible for one file to overwrite another. We will use this to plug in our own files without having -to change anything in the Evennia itself.

-

By default, Evennia is configured to pull files you put in the web/static_overrides after all -other static files. That means that files in static_overrides folder will overwrite any previously -loaded files having the same path under its static folder. This last part is important to repeat: -To overload the static resource from a standard static folder you need to replicate the path of -folders and file names from that static folder in exactly the same way inside static_overrides.

-

Let’s see how this works for our logo. The default web application is in the Evennia library itself, -in evennia/web/. We can see that there is a static folder here. If we browse down, we’ll -eventually find the full path to the Evennia logo file: -evennia/web/website/static/website/images/evennia_logo.png.

-

Inside our static_overrides we must replicate the part of the path inside the website’s static -folder, in other words, we must replicate website/images/evennia_logo.png.

-

So, to change the logo, we need to create the folder path website/images/ in static_overrides. -You may already have this folder structure prepared for you. We then rename our own logo file to -evennia_logo.png and copy it there. The final path for this file would thus be: -mygame/web/static_overrides/website/images/evennia_logo.png.

-

To get this file pulled in, just change to your own game directory and reload the server:

-
evennia reload
-
-
-

This will reload the configuration and bring in the new static file(s). If you didn’t want to reload -the server you could instead use

-
evennia collectstatic
-
-
-

to only update the static files without any other changes.

-
-

Note: Evennia will collect static files automatically during startup. So if evennia collectstatic reports finding 0 files to collect, make sure you didn’t start the engine at some -point - if so the collector has already done its work! To make sure, connect to the website and -check so the logo has actually changed to your own version.

-
-
-

Note: Sometimes the static asset collector can get confused. If no matter what you do, your -overridden files aren’t getting copied over the defaults, try removing the target file (or -everything) in the web/static directory, and re-running collectstatic to gather everything from -scratch.

-
-
-
-

Changing the Front Page’s Text

-

The default front page for Evennia contains information about the Evennia project. You’ll probably -want to replace this information with information about your own project. Changing the page template -is done in a similar way to changing static resources.

-

Like static files, Django looks through a series of template folders to find the file it wants. The -difference is that Django does not copy all of the template files into one place, it just searches -through the template folders until it finds a template that matches what it’s looking for. This -means that when you edit a template, the changes are instant. You don’t have to reload the server or -run any extra commands to see these changes - reloading the web page in your browser is enough.

-

To replace the index page’s text, we’ll need to find the template for it. We’ll go into more detail -about how to determine which template is used for rendering a page in the Web-based Character view -Tutorial. For now, you should know that the template we want to change -is stored in evennia/web/website/templates/website/index.html.

-

To replace this template file, you will put your changed template inside the -web/template_overrides/website directory in your game folder. In the same way as with static -resources you must replicate the path inside the default template directory exactly. So we must -copy our replacement template named index.html there (or create the website directory in -web/template_overridesif it does not exist, first). The final path to the file should thus be:web/template_overrides/website/index.html` within your game directory.

-

Note that it is usually easier to just copy the original template over and edit it in place. The -original file already has all the markup and tags, ready for editing.

-
-
-

Further reading

-

For further hints on working with the web presence, you could now continue to the Web-based -Character view Tutorial where you learn to make a web page that -displays in-game character stats. You can also look at Django’s own -tutorial to get more insight in how Django works and what -possibilities exist.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Webclient-brainstorm.html b/docs/0.9.5/Webclient-brainstorm.html deleted file mode 100644 index 94e63e56d4..0000000000 --- a/docs/0.9.5/Webclient-brainstorm.html +++ /dev/null @@ -1,450 +0,0 @@ - - - - - - - - - Webclient brainstorm — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Webclient brainstorm

-
-
-

Ideas for a future webclient gui

-

This is a brainstorming whitepage. Add your own comments in a named section below.

-
-

From Chat on 2019/09/02

-
  Griatch (IRC)APP  @friarzen: Could one (with goldenlayout) programmatically provide pane positions
-and sizes?
-I recall it was not trivial for the old split.js solution.
-  friarzen  take a look at the goldenlayout_default_config.js
-It is kinda cryptic but that is the layout json.
-  Griatch (IRC)APP  @friarzen: Hm, so dynamically replacing the goldenlayout_config in the global
-scope at the right
- thing would do it?
-  friarzen  yep
-  friarzen  the biggest pain in the butt is that goldenlayout_config needs to be set before the
-goldenlayout init()
-is called, which isn't trivial with the current structure, but it isn't impossible.
-  Griatch (IRC)APP  One could in principle re-run init() at a later date though, right?
-  friarzen  Hmm...not sure I've ever tried it... seems doable off the top of my head...
-right now, that whole file exists to be overridden on page load as a separate <script> HTML tag, so
-it can get
-force-loaded early.
-you could just as easily call the server at that time for the proper config info and store it into
-the variable.
-  Griatch (IRC)APP  @friarzen: Right. I picture the default would either be modified directly in
-that file or even be
-in settings.
-  friarzen  And if you have it call with an authenticated session at that early point, you could
-even have the layout
-be defined as a per-user setting.
-  Griatch (IRC)APP  Yeah, I think that'd be a very important feature.
-So one part of the config would be the golden-layout config blob; then another section with custom
-per-pane
-settings. Things like which tag a pane filters on, its type, and options for that type (like
-replace/scroll for a
-text pane etc).
-And probably one general config section; things like notifications, sounds, popup settings and the
-like
-  friarzen  Actually, that information is already somewhat stored into the data as componentState {}
-  Griatch (IRC)APP  I imagine a pane would need to be identified uniquely for golden-layout to know
-which is which,
-so that'd need to be associated.
-  friarzen  see line 55.
-that's....where it gets tricky...
-goldenlayout is kinda dumb in that it treats the whole json blob as a state object.
-  Griatch (IRC)APP  componentState, is the idea that it takes arbitrary settings then?
-  friarzen  so each sub-dictionary with a type of "component" in it is a window and as you move
-stuff around it
-dynamically creates new dictionaries as needed to keep it all together.
-yep
-right now I'm storing the list of types as a string and the updateMethod type as a string, just to
-be as obvious as
-possible.
-  Griatch (IRC)APP  I wonder if one could populate componentState(s) just before initializing the
-system, and then
-extract them into a more Evennia-friendly storage when saving. Or maybe it's no big deal to just
-store that whole
-blob as-is, with some extra things added?
-  friarzen  if you want to see it in action, take a look at the values in your localstorage after
-you've moved stuff
-around, created new windows, etc.
-I think their preference is to just store the whole blob.
-which is what the localstorage save does, in fact.
-  Griatch (IRC)APP  Yes, I see.
-It allows you to retain the session within your browser as well
-  friarzen  One trick I've been thinking about for the whole interface is to have another pair of
-components, one
-being a "dynamic" config window that displays a series of dropdowns, one for each plugin that
-defines it's own
-settings.
-And another that has an embedded i-frame to allow a split-screen where half of the display is the
-text interface
-and the other loads the local main website page and allows further browsing.
-  Griatch (IRC)APP  I think ideally, each pane should handle its per-pane settings as a dropdown or
-popup triggered
-from its header.
-  friarzen  well, pane != plugin...
-  Griatch (IRC)APP  True, one tends to sometimes make the other available but they are not the same
-  friarzen  think of the history plugin for example...if you want to make keyboard keys dynamically
-configurable, you
-need some place to enter that information.
-yeah.
-  Griatch (IRC)APP  Yes, I buy that - a dynamical option window would be needed. I'd say the
-'global' settings should
-be treated as just another module in this regard though
-So that if you have no modules installed, there is one entry in that option pane, the one that opens
-the global
-options
-  friarzen  Yeah, so that is that component...one pane.
-  Griatch (IRC)APP  Another thing that the config should contain would be the available message
-tags. Waiting for
-them to actually show up before being able to use them is not that useful. This would likely have to
-be a setting
-though I imagine.
-  friarzen  we could remove the current pop-up and just open the new component pane instead, and
-then that pane would
-display the list of plugins that registered a config() callback.
-  Griatch (IRC)APP  Yes
-  friarzen  yeah, the server has to pre-define what tags it is going to send.
-  Griatch (IRC)APP  The process for adding a tag would be to adding to, say, a list in settings,
-restart and then .. profit
-  friarzen  yep, which is kind of how I see spawns working.
-  Griatch (IRC)APP  spawns, meaning stand-alone windows?
-  friarzen  we just have a plugin that defines it's config pane with a "match this text" box and a
-tag type to send
-the data to, or spawn a new pane with that tag type preselected to capture that text.
-wouldn't be stand alone windows.  just new tabs, for now.
-  Griatch (IRC)APP  Ok, so a filter target that filters on text content and directs to tag
-  friarzen  yep.
-  Griatch (IRC)APP  (or re-tags it, I suppose)
-  friarzen  yeah, exactly.
-and opens a new window for that tag if one doesn't already exist.
-  Griatch (IRC)APP  A lot of complex effects could be achieved there. Should the filter extract the
-text and re-tag,
-or should it keep the text with its original tag and make a copy of it with the new tag? O_o;
-  friarzen  yet more options for the user. :slightly_smiling_face:
-baby steps first I think.
-"pages from bob should go to bob's window, not the general chat window"
-  Griatch (IRC)APP  It doesn't sound too hard to do - using tagging like that is really neat since
-then the rerouting
-can just work normally without knowing which pane you are actually rerouting too (and you could have
-multiple panes
-getting the same content too)
-  friarzen  yep.
-and the i-frame component, I think, provides just as much wow facter.
-  Griatch (IRC)APP  Yes, the setting would be something like: If text contains "bob" -> set tag
-"bob" (auto-create
-pane if not exist []?)
-  friarzen  just being able to load a character page from the wiki side in half the screen
-(including the pictures
-and whatnot) while playing/reading the text from the other half is really nice.
-  Griatch (IRC)APP  Could there not be some cross-scripting warnings in modern browsers if trying to
-load another
-website in an iframe?
-I doubt you could open Github like that, for example.
-friarzen  well, assuming it is all from the same origin game server, it will all be from the same
-domain, so should
-avoid that, but it will take some testing to make sure.  I want to get it to the point where you can
-click on
-somebody's name in the general terminal-style window and have it pop up that characters' wiki page.
-  Griatch (IRC)APP  That does sound nice :)
-  friarzen  i-frames are pretty much the only way to handle cross-domain content, but they are a
-pain in the butt to
-get that to work.
-  Griatch (IRC)APP  If it's on the same domain it would be fine, but it should probably give a "you
-are now leaving
-..." message and switch to a new window if going elsewhere.
-  friarzen  (without getting into modern CORS url blessings)
-yeah
-  Griatch (IRC)APP  Just to avoid headaches :)
-  friarzen  So, yeah, two new goldenlayout components that I am working on but they aren't ready
-yet. heh.
-  Griatch (IRC)APP  Oh, you are working on them already?
-  friarzen  yeah, some initial "will this work" structure.
-I haven't found anything yet that will not make it work.
-it's mostly just not going to look very pretty to start with. It'll take a few iterations I bet.
-  Griatch (IRC)APP  Sounds good! We should probably try to formalize our thoughts around a future
-config format as
-well. Depending just how and when the golden-layout blob needs to be in place and if we can
-construct it on-the-
-fly, it affects the format style chosen.
-  friarzen  Yeah, that's new from today's conversation, so I don't really have anything built for
-that.
-  Griatch (IRC)APP  I'm still unsure about how the Evennia/pane specific things (that'd need to be
-serialized to the
-database on a per-account basis) would interact with the golden-layout structure.
-  friarzen  maybe the place to start is to wipe-out/update the old
-https://github.com/evennia/evennia/wiki/Webclient-
-brainstorm entry?
-  Griatch (IRC)APP  I don't think we need to wipe the old, but we could certainly add a new section
-above the old and
-start some new discussions.
-  friarzen  It is really just that per componentState bit.
-anything in that json is treated as a blackbox that goldenlayout just passes around.
-  Griatch (IRC)APP  So would we save the whole blob then, componentState and all, and simply not
-worry about
-ourselves storing per-pane options?
-The drawback with that would be that a dev could not offer an exact default layout/setup for the
-user.
-... unless they set it up and saved the blob I guess
-  friarzen  Yeah, that's exactly how I built mine. :slightly_smiling_face:
-not the most dev-friendly way to do it, but it works.
-  Griatch (IRC)APP  Heh. Ok, so the config would be one section for the golden-layout-blob, one for
-module settings,
-one of which is the global settings.
-and a list of the available tags
-  friarzen  yep
-  Griatch (IRC)APP  And probably an empty misc section for future use ...
-  friarzen  seems reasonable.
-  Griatch (IRC)APP  So, that means that in the future one would need a procedure for the dev to
-easily create and
-save the player-wide default to central storage. Well, one step at a time.
-For now, good night!
-  friarzen  Yep, I expect that would be some kind of admin-approved api/command
-  Griatch (IRC)APP  And thanks for the discussion!
-
-
-
-

Relates to the activity happening relating to the Webclient extensions task -#614.

-
-
-

Griatch Jan 23, 2017 post 2

-

These are my ideas for the functionality of Evennia’s webclient in the (possibly distant) future. It -assumes the webclient options have been implemented as per -#1172.

-

Mockup 1

-

The idea of the GUI is based around panes (a “window” feels like something that pops open). These -are areas of the window of the type you would see from a javascript library like -Split.js. Each pane has a separator with a handle for -resizing it.

-

Each pane has an icon for opening a dropdown menu (see next mockup image).

-

Above image could show the default setup; mimicking the traditional telnet mud client setup. There -should be at least one input pane (but you could add multiple). The Input pane has the icon for -launching the main webclient options window. Alternatively the options icon could hover -transparently in the upper left, “above” all panes.

-

The main webclient options window should have an option for hiding all GUI customization icons (the -draggable handles of panes and the dropdown menu) for users who does not want to have to worry about -changing things accidentally. Devs not wanting to offer players the ability to customize their -client GUIs could maybe just set it up the way they want and then turn the gui controls off on the -javascript level.

-

Mockup 2

-

The dropdown menu allows for splitting each pane horizontally and vertically.

-

Mockup 3

-

Each pane is “tagged” with what data will be sent to it. It could accept more than one type of -tagged data. Just which tags are available is different for each game (and should be reported by the -server).

-

For example, the server could send the return of the “look” command as

-
    msg("you see ...", {"pane": "look"})
-
-
-

If one (or more) of the panes is set to receive “look” data, all such will go there. If no pane is -assigned to “look”, this text will end up in the pane(s) with the “Unassigned” designation. It might -be necessary to enforce that at least one window has the “unassigned” designation in order to not -lose output without a clear pane target. By contrast the pane tagged with “All” receives all output -regardless of if it is also being sent to other panes.

-

Another thing that came to mind is logging. It might be an idea to tie a given log file to a -specific pane (panes?) to limit spam in the log (it might be one reason for having a pane with the -“All” tag, for those wanting to log everything). This could also be set in the dropdown menu or in -the webclient options window, maybe.

-

Comments?

-
-
-

titeuf87 Jan 23, 2017

-

That way of splitting panes seems easy to manage while still being powerful at the same time. -It’s certainly a lot more flexible than what I had in mind.

-

This is a quick sketch of what I was thinking about:

-
=====================
-Options           [x]
-=====================
-help:         [popup]
-map:       [top left]
-channels: [top right]
-look:   [main output]
-
-
-

But that’s not as good as Griatch’s proposal.

-

The panes themselves can work differently depending on the content though:

-
    -
  • channel messages: when someone talks on a channel, the output text needs to be appended to the -text already shown.

  • -
  • inventory: this pane should clear its output every time the inventory is displayed, so old -inventory is not displayed. Also what about dropping items? As long as the player doesn’t check its -inventory then that pane won’t be updated, unless more OOB data is added to track the inventory.

  • -
  • help/look: those pane should probably also clear their content before displaying new content.

  • -
-

logging: do you mean have a “save to log” item in the pane menu?

-
-
-

Griatch Jan 23, 2017, post 1

-

It makes sense that different types of panes would have different functionality. I was thinking that -something like inventory would be very specific to a given game. But titeuf87 has a point - maybe -you can get a rather generalized behavior just by defining if a pane should replace or append to the -existing data.

-

As for the updating of inventory when dropping items, not sure if that can be generalized, but I -guess drop/get commands could be wired to do that.

-

As for logging - yes I picture a “save to log” thing in the menu. The problem is just where to save -the log. I think at least for an initial setup, one could maybe set the logging file path in the -webclient options and just append logs from all the panes marked for logging to that same file.

-
-
-

chainsol 3rd of October, 2017

-

I’ve been messing a little bit with split.js - I’ve managed to split a split dynamically with a data -attribute on a button and jQuery. My current thinking on this issue is to use jQuery -Templates to write the template for horizontal and -vertical splits, then using jQuery to set up the initial split layout by inserting those templates -as appropriate for user settings.

-

We would have a dropdown per split that decides what tag it’s meant to handle - perhaps a data -attribute that we change in the template for easy selection through jQuery - and then send tags for -messages to the webclient. This dropdown would also have a setting per split to either append new -content or replace content - again, perhaps a data attribute.

-

We might also want to store “mobile” and “desktop” layouts separately - or just default to a mobile- -friendly layout at a certain screen size, and turn off the splits.

-

Oh, and embedding Bootstrap containers in splits works perfectly - that could help us too.

-
-
-

chainsol 9th of October, 2017

-

I’ve got a demo of this working at my development box, -if anyone wants to take a look. So far, I’ve got tag handling, dynamic splits, and the ability to -swap splits’ contents. Since this doesn’t have a fancy interface yet, if you want to see a dynamic -split, open your browser’s console and try these commands:

-

SplitHandler.split("#main", "horizontal") will split the top-half into two 50-50 splits, both -receiving content that’s not tagged or is tagged “all” from the server.

-

SplitHandler.swapContent("#input", "#split_2") after doing so will swap the input into the top- -left split.

-

I’m trying to figure out where to put each split’s configuration - maybe a dropdown button that’s -semi-transparent until you hover over it? So far, if you edited the data attributes, you could -change each split to receive only tagged messages ( data-tag=“all” ), and you could change them to -overwrite instead of append data ( data-update-append or data-update-overwrite )

-
-
-

Griatch Oct 13, 2017

-

I would suggest that, assuming the game dev allows the user to modify their GUI in the first place, -that modification is a “mode”. I picture that 99% of the time a user doesn’t need to modify their -interface. They only want to click whatever game-related buttons etc are present in the pane without -risk of resizing things accidentally.

-

So I’d say that normally the panes are all fixed, with minimal spacing between them, no handles etc. -But you can enter the client settings window and choose Customize GUI mode (or something like -that). When you do, then separators get handles and the dropdown menu markers appear (permanently) -in the corner of each pane. This means that if a user wants to easily tweak their window they -could stay in this mode, but they could also “lock” the gui layout at any time.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Webclient.html b/docs/0.9.5/Webclient.html deleted file mode 100644 index d10ab23535..0000000000 --- a/docs/0.9.5/Webclient.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - - - - Webclient — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Webclient

-
-
-

Web client

-

Evennia comes with a MUD client accessible from a normal web browser. During development you can try -it at http://localhost:4001/webclient. The client consists of several parts, all under -evennia/web/webclient/:

-

templates/webclient/webclient.html and templates/webclient/base.html are the very simplistic -django html templates describing the webclient layout.

-

static/webclient/js/evennia.js is the main evennia javascript library. This handles all -communication between Evennia and the client over websockets and via AJAX/COMET if the browser can’t -handle websockets. It will make the Evennia object available to the javascript namespace, which -offers methods for sending and receiving data to/from the server transparently. This is intended to -be used also if swapping out the gui front end.

-

static/webclient/js/webclient_gui.js is the default plugin manager. It adds the plugins and -plugin_manager objects to the javascript namespace, coordinates the GUI operations between the -various plugins, and uses the Evennia object library for all in/out.

-

static/webclient/js/plugins provides a default set of plugins that implement a “telnet-like” -interface.

-

static/webclient/css/webclient.css is the CSS file for the client; it also defines things like how -to display ANSI/Xterm256 colors etc.

-

The server-side webclient protocols are found in evennia/server/portal/webclient.py and -webclient_ajax.py for the two types of connections. You can’t (and should not need to) modify -these.

-
-

Customizing the web client

-

Like was the case for the website, you override the webclient from your game directory. You need to -add/modify a file in the matching directory location within one of the _overrides directories. -These _override directories are NOT directly used by the web server when the game is running, the -server copies everything web related in the Evennia folder over to mygame/web/static/ and then -copies in all of your _overrides. This can cause some cases were you edit a file, but it doesn’t -seem to make any difference in the servers behavior. Before doing anything else, try shutting -down the game and running evennia collectstatic from the command line then start it back up, clear -your browser cache, and see if your edit shows up.

-

Example: To change the utilized plugin list, you need to override base.html by copying -evennia/web/webclient/templates/webclient/base.html to -mygame/web/template_overrides/webclient/base.html and editing it to add your new plugin.

-
-
-
-

Evennia Web Client API (from evennia.js)

-
    -
  • Evennia.init( opts )

  • -
  • Evennia.connect()

  • -
  • Evennia.isConnected()

  • -
  • Evennia.msg( cmdname, args, kwargs, callback )

  • -
  • Evennia.emit( cmdname, args, kwargs )

  • -
  • log()

  • -
-
-
-

Plugin Manager API (from webclient_gui.js)

-
    -
  • options Object, Stores key/value ‘state’ that can be used by plugins to coordinate behavior.

  • -
  • plugins Object, key/value list of the all the loaded plugins.

  • -
  • plugin_handler Object

    -
      -
    • plugin_handler.add("name", plugin)

    • -
    • plugin_handler.onSend(string)

    • -
    -
  • -
-
-
-

Plugin callbacks API

-
    -
  • init() – The only required callback

  • -
  • boolean onKeydown(event) This plugin listens for Keydown events

  • -
  • onBeforeUnload() This plugin does something special just before the webclient page/tab is -closed.

  • -
  • onLoggedIn(args, kwargs) This plugin does something when the webclient first logs in.

  • -
  • onGotOptions(args, kwargs) This plugin does something with options sent from the server.

  • -
  • boolean onText(args, kwargs) This plugin does something with messages sent from the server.

  • -
  • boolean onPrompt(args, kwargs) This plugin does something when the server sends a prompt.

  • -
  • boolean onUnknownCmd(cmdname, args, kwargs) This plugin does something with “unknown commands”.

  • -
  • onConnectionClose(args, kwargs) This plugin does something when the webclient disconnects from -the server.

  • -
  • newstring onSend(string) This plugin examines/alters text that other plugins generate. Use -with caution

  • -
-

The order of the plugins defined in base.html is important. All the callbacks for each plugin -will be executed in that order. Functions marked “boolean” above must return true/false. Returning -true will short-circuit the execution, so no other plugins lower in the base.html list will have -their callback for this event called. This enables things like the up/down arrow keys for the -history.js plugin to always occur before the default_in.js plugin adds that key to the current input -buffer.

-
-
-

Example/Default Plugins (plugins/*.js)

-
    -
  • clienthelp.js Defines onOptionsUI from the options2 plugin. This is a mostly empty plugin to -add some “How To” information for your game.

  • -
  • default_in.js Defines onKeydown. key or mouse clicking the arrow will send the currently -typed text.

  • -
  • default_out.js Defines onText, onPrompt, and onUnknownCmd. Generates HTML output for the user.

  • -
  • default_unload.js Defines onBeforeUnload. Prompts the user to confirm that they meant to -leave/close the game.

  • -
  • font.js Defines onOptionsUI. The plugin adds the ability to select your font and font size.

  • -
  • goldenlayout_default_config.js Not actually a plugin, defines a global variable that -goldenlayout uses to determine its window layout, known tag routing, etc.

  • -
  • goldenlayout.js Defines onKeydown, onText and custom functions. A very powerful “tabbed” window -manager for drag-n-drop windows, text routing and more.

  • -
  • history.js Defines onKeydown and onSend. Creates a history of past sent commands, and uses arrow -keys to peruse.

  • -
  • hotbuttons.js Defines onGotOptions. A Disabled-by-default plugin that defines a button bar with -user-assignable commands.

  • -
  • iframe.js Defines onOptionsUI. A goldenlayout-only plugin to create a restricted browsing sub- -window for a side-by-side web/text interface, mostly an example of how to build new HTML -“components” for goldenlayout.

  • -
  • message_routing.js Defines onOptionsUI, onText, onKeydown. This goldenlayout-only plugin -implements regex matching to allow users to “tag” arbitrary text that matches, so that it gets -routed to proper windows. Similar to “Spawn” functions for other clients.

  • -
  • multimedia.js An basic plugin to allow the client to handle “image” “audio” and “video” messages -from the server and display them as inline HTML.

  • -
  • notifications.js Defines onText. Generates browser notification events for each new message -while the tab is hidden.

  • -
  • oob.js Defines onSend. Allows the user to test/send Out Of Band json messages to the server.

  • -
  • options.js Defines most callbacks. Provides a popup-based UI to coordinate options settings with -the server.

  • -
  • options2.js Defines most callbacks. Provides a goldenlayout-based version of the -options/settings tab. Integrates with other plugins via the custom onOptionsUI callback.

  • -
  • popups.js Provides default popups/Dialog UI for other plugins to use.

  • -
-
-
-

Writing your own Plugins

-

So, you love the functionality of the webclient, but your game has specific types of text that need -to be separated out into their own space, visually. The Goldenlayout plugin framework can help with -this.

-
-

GoldenLayout

-

GoldenLayout is a web framework that allows web developers and their users to create their own -tabbed/windowed layouts. Windows/tabs can be click-and-dragged from location to location by -clicking on their titlebar and dragging until the “frame lines” appear. Dragging a window onto -another window’s titlebar will create a tabbed “Stack”. The Evennia goldenlayout plugin defines 3 -basic types of window: The Main window, input windows and non-main text output windows. The Main -window and the first input window are unique in that they can’t be “closed”.

-

The most basic customization is to provide your users with a default layout other than just one Main -output and the one starting input window. This is done by modifying your server’s -goldenlayout_default_config.js.

-

Start by creating a new -mygame/web/static_overrides/webclient/js/plugins/goldenlayout_default_config.js file, and adding -the following JSON variable:

-
var goldenlayout_config = {
-    content: [{
-        type: 'column',
-        content: [{
-            type: 'row',
-            content: [{
-                type: 'column',
-                content: [{
-                    type: 'component',
-                    componentName: 'Main',
-                    isClosable: false,
-                    tooltip: 'Main - drag to desired position.',
-                    componentState: {
-                        cssClass: 'content',
-                        types: 'untagged',
-                        updateMethod: 'newlines',
-                    },
-                }, {
-                    type: 'component',
-                    componentName: 'input',
-                    id: 'inputComponent',
-                    height: 10,
-                    tooltip: 'Input - The last input in the layout is always the default.',
-                }, {
-                    type: 'component',
-                    componentName: 'input',
-                    id: 'inputComponent',
-                    height: 10,
-                    isClosable: false,
-                    tooltip: 'Input - The last input in the layout is always the default.',
-                }]
-            },{
-                type: 'column',
-                content: [{
-                    type: 'component',
-                    componentName: 'evennia',
-                    componentId: 'evennia',
-                    title: 'example',
-                    height: 60,
-                    isClosable: false,
-                    componentState: {
-                        types: 'some-tag-here another-tag-here',
-                        updateMethod: 'newlines',
-                    },
-                }, {
-                    type: 'component',
-                    componentName: 'evennia',
-                    componentId: 'evennia',
-                    title: 'sheet',
-                    isClosable: false,
-                    componentState: {
-                        types: 'sheet',
-                        updateMethod: 'replace',
-                    },
-                }],
-            }],
-        }]
-    }]
-};
-
-
-

This is a bit ugly, but hopefully, from the indentation, you can see that it creates a side-by-side -(2-column) interface with 3 windows down the left side (The Main and 2 inputs) and a pair of windows -on the right side for extra outputs. Any text tagged with “some-tag-here” will flow to the bottom -of the “example” window, and any text tagged “sheet” will replace the text already in the “sheet” -window.

-

Note: GoldenLayout gets VERY confused and will break if you create two windows with the “Main” -componentName.

-

Now, let’s say you want to display text on each window using different CSS. This is where new -goldenlayout “components” come in. Each component is like a blueprint that gets stamped out when -you create a new instance of that component, once it is defined, it won’t be easily altered. You -will need to define a new component, preferably in a new plugin file, and then add that into your -page (either dynamically to the DOM via javascript, or by including the new plugin file into the -base.html).

-

First up, follow the directions in Customizing the Web Client section above to override the -base.html.

-

Next, add the new plugin to your copy of base.html:

-
<script src={% static "webclient/js/plugins/myplugin.js" %} language="javascript"
-type="text/javascript"></script>
-
-
-

Remember, plugins are load-order dependent, so make sure the new <script> tag comes before the -goldenlayout.js

-

Next, create a new plugin file mygame/web/static_overrides/webclient/js/plugins/myplugin.js and -edit it.

-
let myplugin = (function () {
-    //
-    //
-    var postInit = function() {
-        var myLayout = window.plugins['goldenlayout'].getGL();
-
-        // register our component and replace the default messagewindow
-        myLayout.registerComponent( 'mycomponent', function (container, componentState) {
-            let mycssdiv = $('<div>').addClass('content myCSS');
-            mycssdiv.attr('types', 'mytag1 mytag2');
-            mycssdiv.attr('updateMethod', 'newlines');
-            mycssdiv.appendTo( container.getElement() );
-            container.on("tab", plugins['goldenlayout'].onTabCreate);
-        });
-
-        console.log("MyPlugin Initialized.");
-    }
-
-    return {
-        init: function () {},
-        postInit: postInit,
-    }
-})();
-window.plugin_handler.add("myplugin", myplugin);
-
-
-

You can then switch componentName: 'evennia' with componentName: 'mycomponent' in your -goldenlayout_default_config.js (and update the tags: to match).

-

When you tag text with mytag1, it will then be sent to the mycomponent pane. Example: -caller.msg(("Text with a type.", {"type": "mytag1"}))

-

Make sure to run evennia stop, evennia collectstatic, and evennia start. Then make sure to -clear your browser cache before loading the webclient page.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Wiki-Index.html b/docs/0.9.5/Wiki-Index.html deleted file mode 100644 index 7089897f03..0000000000 --- a/docs/0.9.5/Wiki-Index.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - - - - - Wiki Index — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Wiki Index

-

This wiki index is automatically generated. Do not modify, your changes will be lost.

-
-

A-Z

-

There are 141 entries in the wiki.

- -
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/Zones.html b/docs/0.9.5/Zones.html deleted file mode 100644 index 06bb9ad842..0000000000 --- a/docs/0.9.5/Zones.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - Zones — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

Zones

-

Say you create a room named Meadow in your nice big forest MUD. That’s all nice and dandy, but -what if you, in the other end of that forest want another Meadow? As a game creator, this can -cause all sorts of confusion. For example, teleporting to Meadow will now give you a warning that -there are two Meadow s and you have to select which one. It’s no problem to do that, you just -choose for example to go to 2-meadow, but unless you examine them you couldn’t be sure which of -the two sat in the magical part of the forest and which didn’t.

-

Another issue is if you want to group rooms in geographic regions. Let’s say the “normal” part of -the forest should have separate weather patterns from the magical part. Or maybe a magical -disturbance echoes through all magical-forest rooms. It would then be convenient to be able to -simply find all rooms that are “magical” so you could send messages to them.

-
-

Zones in Evennia

-

Zones try to separate rooms by global location. In our example we would separate the forest into -two parts - the magical and the non-magical part. Each have a Meadow and rooms belonging to each -part should be easy to retrieve.

-

Many MUD codebases hardcode zones as part of the engine and database. Evennia does no such -distinction due to the fact that rooms themselves are meant to be customized to any level anyway. -Below is a suggestion for how to implement zones in Evennia.

-

All objects in Evennia can hold any number of Tags. Tags are short labels that you attach to -objects. They make it very easy to retrieve groups of objects. An object can have any number of -different tags. So let’s attach the relevant tag to our forest:

-
     forestobj.tags.add("magicalforest", category="zone")
-
-
-

You could add this manually, or automatically during creation somehow (you’d need to modify your -@dig command for this, most likely). You can also use the default @tag command during building:

-
 @tag forestobj = magicalforest : zone
-
-
-

Henceforth you can then easily retrieve only objects with a given tag:

-
     import evennia
-     rooms = evennia.search_tag("magicalforest", category="zone")
-
-
-
-
-

Using typeclasses and inheritance for zoning

-

The tagging or aliasing systems above don’t instill any sort of functional difference between a -magical forest room and a normal one - they are just arbitrary ways to mark objects for quick -retrieval later. Any functional differences must be expressed using Typeclasses.

-

Of course, an alternative way to implement zones themselves is to have all rooms/objects in a zone -inherit from a given typeclass parent - and then limit your searches to objects inheriting from that -given parent. The effect would be similar but you’d need to expand the search functionality to -properly search the inheritance tree.

-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/django/conf.html b/docs/0.9.5/_modules/django/conf.html deleted file mode 100644 index cd6949ffec..0000000000 --- a/docs/0.9.5/_modules/django/conf.html +++ /dev/null @@ -1,377 +0,0 @@ - - - - - - - - django.conf — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for django.conf

-"""
-Settings and configuration for Django.
-
-Read values from the module specified by the DJANGO_SETTINGS_MODULE environment
-variable, and then from django.conf.global_settings; see the global_settings.py
-for a list of all possible variables.
-"""
-
-import importlib
-import os
-import time
-import traceback
-import warnings
-from pathlib import Path
-
-import django
-from django.conf import global_settings
-from django.core.exceptions import ImproperlyConfigured
-from django.utils.deprecation import RemovedInDjango40Warning
-from django.utils.functional import LazyObject, empty
-
-ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
-
-PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG = (
-    'The PASSWORD_RESET_TIMEOUT_DAYS setting is deprecated. Use '
-    'PASSWORD_RESET_TIMEOUT instead.'
-)
-
-DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG = (
-    'The DEFAULT_HASHING_ALGORITHM transitional setting is deprecated. '
-    'Support for it and tokens, cookies, sessions, and signatures that use '
-    'SHA-1 hashing algorithm will be removed in Django 4.0.'
-)
-
-
-class SettingsReference(str):
-    """
-    String subclass which references a current settings value. It's treated as
-    the value in memory but serializes to a settings.NAME attribute reference.
-    """
-    def __new__(self, value, setting_name):
-        return str.__new__(self, value)
-
-    def __init__(self, value, setting_name):
-        self.setting_name = setting_name
-
-
-class LazySettings(LazyObject):
-    """
-    A lazy proxy for either global Django settings or a custom settings object.
-    The user can manually configure settings prior to using them. Otherwise,
-    Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
-    """
-    def _setup(self, name=None):
-        """
-        Load the settings module pointed to by the environment variable. This
-        is used the first time settings are needed, if the user hasn't
-        configured settings manually.
-        """
-        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
-        if not settings_module:
-            desc = ("setting %s" % name) if name else "settings"
-            raise ImproperlyConfigured(
-                "Requested %s, but settings are not configured. "
-                "You must either define the environment variable %s "
-                "or call settings.configure() before accessing settings."
-                % (desc, ENVIRONMENT_VARIABLE))
-
-        self._wrapped = Settings(settings_module)
-
-    def __repr__(self):
-        # Hardcode the class name as otherwise it yields 'Settings'.
-        if self._wrapped is empty:
-            return '<LazySettings [Unevaluated]>'
-        return '<LazySettings "%(settings_module)s">' % {
-            'settings_module': self._wrapped.SETTINGS_MODULE,
-        }
-
-    def __getattr__(self, name):
-        """Return the value of a setting and cache it in self.__dict__."""
-        if self._wrapped is empty:
-            self._setup(name)
-        val = getattr(self._wrapped, name)
-
-        # Special case some settings which require further modification.
-        # This is done here for performance reasons so the modified value is cached.
-        if name in {'MEDIA_URL', 'STATIC_URL'} and val is not None:
-            val = self._add_script_prefix(val)
-        elif name == 'SECRET_KEY' and not val:
-            raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")
-
-        self.__dict__[name] = val
-        return val
-
-    def __setattr__(self, name, value):
-        """
-        Set the value of setting. Clear all cached values if _wrapped changes
-        (@override_settings does this) or clear single values when set.
-        """
-        if name == '_wrapped':
-            self.__dict__.clear()
-        else:
-            self.__dict__.pop(name, None)
-        super().__setattr__(name, value)
-
-    def __delattr__(self, name):
-        """Delete a setting and clear it from cache if needed."""
-        super().__delattr__(name)
-        self.__dict__.pop(name, None)
-
-    def configure(self, default_settings=global_settings, **options):
-        """
-        Called to manually configure the settings. The 'default_settings'
-        parameter sets where to retrieve any unspecified values from (its
-        argument must support attribute access (__getattr__)).
-        """
-        if self._wrapped is not empty:
-            raise RuntimeError('Settings already configured.')
-        holder = UserSettingsHolder(default_settings)
-        for name, value in options.items():
-            if not name.isupper():
-                raise TypeError('Setting %r must be uppercase.' % name)
-            setattr(holder, name, value)
-        self._wrapped = holder
-
-    @staticmethod
-    def _add_script_prefix(value):
-        """
-        Add SCRIPT_NAME prefix to relative paths.
-
-        Useful when the app is being served at a subpath and manually prefixing
-        subpath to STATIC_URL and MEDIA_URL in settings is inconvenient.
-        """
-        # Don't apply prefix to absolute paths and URLs.
-        if value.startswith(('http://', 'https://', '/')):
-            return value
-        from django.urls import get_script_prefix
-        return '%s%s' % (get_script_prefix(), value)
-
-    @property
-    def configured(self):
-        """Return True if the settings have already been configured."""
-        return self._wrapped is not empty
-
-    @property
-    def PASSWORD_RESET_TIMEOUT_DAYS(self):
-        stack = traceback.extract_stack()
-        # Show a warning if the setting is used outside of Django.
-        # Stack index: -1 this line, -2 the caller.
-        filename, _, _, _ = stack[-2]
-        if not filename.startswith(os.path.dirname(django.__file__)):
-            warnings.warn(
-                PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG,
-                RemovedInDjango40Warning,
-                stacklevel=2,
-            )
-        return self.__getattr__('PASSWORD_RESET_TIMEOUT_DAYS')
-
-
-class Settings:
-    def __init__(self, settings_module):
-        # update this dict from global settings (but only for ALL_CAPS settings)
-        for setting in dir(global_settings):
-            if setting.isupper():
-                setattr(self, setting, getattr(global_settings, setting))
-
-        # store the settings module in case someone later cares
-        self.SETTINGS_MODULE = settings_module
-
-        mod = importlib.import_module(self.SETTINGS_MODULE)
-
-        tuple_settings = (
-            "INSTALLED_APPS",
-            "TEMPLATE_DIRS",
-            "LOCALE_PATHS",
-        )
-        self._explicit_settings = set()
-        for setting in dir(mod):
-            if setting.isupper():
-                setting_value = getattr(mod, setting)
-
-                if (setting in tuple_settings and
-                        not isinstance(setting_value, (list, tuple))):
-                    raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting)
-                setattr(self, setting, setting_value)
-                self._explicit_settings.add(setting)
-
-        if self.is_overridden('PASSWORD_RESET_TIMEOUT_DAYS'):
-            if self.is_overridden('PASSWORD_RESET_TIMEOUT'):
-                raise ImproperlyConfigured(
-                    'PASSWORD_RESET_TIMEOUT_DAYS/PASSWORD_RESET_TIMEOUT are '
-                    'mutually exclusive.'
-                )
-            setattr(self, 'PASSWORD_RESET_TIMEOUT', self.PASSWORD_RESET_TIMEOUT_DAYS * 60 * 60 * 24)
-            warnings.warn(PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, RemovedInDjango40Warning)
-
-        if self.is_overridden('DEFAULT_HASHING_ALGORITHM'):
-            warnings.warn(DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG, RemovedInDjango40Warning)
-
-        if hasattr(time, 'tzset') and self.TIME_ZONE:
-            # When we can, attempt to validate the timezone. If we can't find
-            # this file, no check happens and it's harmless.
-            zoneinfo_root = Path('/usr/share/zoneinfo')
-            zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split('/'))
-            if zoneinfo_root.exists() and not zone_info_file.exists():
-                raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
-            # Move the time zone info into os.environ. See ticket #2315 for why
-            # we don't do this unconditionally (breaks Windows).
-            os.environ['TZ'] = self.TIME_ZONE
-            time.tzset()
-
-    def is_overridden(self, setting):
-        return setting in self._explicit_settings
-
-    def __repr__(self):
-        return '<%(cls)s "%(settings_module)s">' % {
-            'cls': self.__class__.__name__,
-            'settings_module': self.SETTINGS_MODULE,
-        }
-
-
-class UserSettingsHolder:
-    """Holder for user configured settings."""
-    # SETTINGS_MODULE doesn't make much sense in the manually configured
-    # (standalone) case.
-    SETTINGS_MODULE = None
-
-    def __init__(self, default_settings):
-        """
-        Requests for configuration variables not in this class are satisfied
-        from the module specified in default_settings (if possible).
-        """
-        self.__dict__['_deleted'] = set()
-        self.default_settings = default_settings
-
-    def __getattr__(self, name):
-        if not name.isupper() or name in self._deleted:
-            raise AttributeError
-        return getattr(self.default_settings, name)
-
-    def __setattr__(self, name, value):
-        self._deleted.discard(name)
-        if name == 'PASSWORD_RESET_TIMEOUT_DAYS':
-            setattr(self, 'PASSWORD_RESET_TIMEOUT', value * 60 * 60 * 24)
-            warnings.warn(PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, RemovedInDjango40Warning)
-        if name == 'DEFAULT_HASHING_ALGORITHM':
-            warnings.warn(DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG, RemovedInDjango40Warning)
-        super().__setattr__(name, value)
-
-    def __delattr__(self, name):
-        self._deleted.add(name)
-        if hasattr(self, name):
-            super().__delattr__(name)
-
-    def __dir__(self):
-        return sorted(
-            s for s in [*self.__dict__, *dir(self.default_settings)]
-            if s not in self._deleted
-        )
-
-    def is_overridden(self, setting):
-        deleted = (setting in self._deleted)
-        set_locally = (setting in self.__dict__)
-        set_on_default = getattr(self.default_settings, 'is_overridden', lambda s: False)(setting)
-        return deleted or set_locally or set_on_default
-
-    def __repr__(self):
-        return '<%(cls)s>' % {
-            'cls': self.__class__.__name__,
-        }
-
-
-settings = LazySettings()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/django/db/models/fields/related_descriptors.html b/docs/0.9.5/_modules/django/db/models/fields/related_descriptors.html deleted file mode 100644 index 72083699e2..0000000000 --- a/docs/0.9.5/_modules/django/db/models/fields/related_descriptors.html +++ /dev/null @@ -1,1309 +0,0 @@ - - - - - - - - django.db.models.fields.related_descriptors — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for django.db.models.fields.related_descriptors

-"""
-Accessors for related objects.
-
-When a field defines a relation between two models, each model class provides
-an attribute to access related instances of the other model class (unless the
-reverse accessor has been disabled with related_name='+').
-
-Accessors are implemented as descriptors in order to customize access and
-assignment. This module defines the descriptor classes.
-
-Forward accessors follow foreign keys. Reverse accessors trace them back. For
-example, with the following models::
-
-    class Parent(Model):
-        pass
-
-    class Child(Model):
-        parent = ForeignKey(Parent, related_name='children')
-
- ``child.parent`` is a forward many-to-one relation. ``parent.children`` is a
-reverse many-to-one relation.
-
-There are three types of relations (many-to-one, one-to-one, and many-to-many)
-and two directions (forward and reverse) for a total of six combinations.
-
-1. Related instance on the forward side of a many-to-one relation:
-   ``ForwardManyToOneDescriptor``.
-
-   Uniqueness of foreign key values is irrelevant to accessing the related
-   instance, making the many-to-one and one-to-one cases identical as far as
-   the descriptor is concerned. The constraint is checked upstream (unicity
-   validation in forms) or downstream (unique indexes in the database).
-
-2. Related instance on the forward side of a one-to-one
-   relation: ``ForwardOneToOneDescriptor``.
-
-   It avoids querying the database when accessing the parent link field in
-   a multi-table inheritance scenario.
-
-3. Related instance on the reverse side of a one-to-one relation:
-   ``ReverseOneToOneDescriptor``.
-
-   One-to-one relations are asymmetrical, despite the apparent symmetry of the
-   name, because they're implemented in the database with a foreign key from
-   one table to another. As a consequence ``ReverseOneToOneDescriptor`` is
-   slightly different from ``ForwardManyToOneDescriptor``.
-
-4. Related objects manager for related instances on the reverse side of a
-   many-to-one relation: ``ReverseManyToOneDescriptor``.
-
-   Unlike the previous two classes, this one provides access to a collection
-   of objects. It returns a manager rather than an instance.
-
-5. Related objects manager for related instances on the forward or reverse
-   sides of a many-to-many relation: ``ManyToManyDescriptor``.
-
-   Many-to-many relations are symmetrical. The syntax of Django models
-   requires declaring them on one side but that's an implementation detail.
-   They could be declared on the other side without any change in behavior.
-   Therefore the forward and reverse descriptors can be the same.
-
-   If you're looking for ``ForwardManyToManyDescriptor`` or
-   ``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
-"""
-
-from django.core.exceptions import FieldError
-from django.db import connections, router, transaction
-from django.db.models import Q, signals
-from django.db.models.query import QuerySet
-from django.db.models.query_utils import DeferredAttribute
-from django.db.models.utils import resolve_callables
-from django.utils.functional import cached_property
-
-
-class ForeignKeyDeferredAttribute(DeferredAttribute):
-    def __set__(self, instance, value):
-        if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(instance):
-            self.field.delete_cached_value(instance)
-        instance.__dict__[self.field.attname] = value
-
-
-class ForwardManyToOneDescriptor:
-    """
-    Accessor to the related object on the forward side of a many-to-one or
-    one-to-one (via ForwardOneToOneDescriptor subclass) relation.
-
-    In the example::
-
-        class Child(Model):
-            parent = ForeignKey(Parent, related_name='children')
-
-    ``Child.parent`` is a ``ForwardManyToOneDescriptor`` instance.
-    """
-
-    def __init__(self, field_with_rel):
-        self.field = field_with_rel
-
-    @cached_property
-    def RelatedObjectDoesNotExist(self):
-        # The exception can't be created at initialization time since the
-        # related model might not be resolved yet; `self.field.model` might
-        # still be a string model reference.
-        return type(
-            'RelatedObjectDoesNotExist',
-            (self.field.remote_field.model.DoesNotExist, AttributeError), {
-                '__module__': self.field.model.__module__,
-                '__qualname__': '%s.%s.RelatedObjectDoesNotExist' % (
-                    self.field.model.__qualname__,
-                    self.field.name,
-                ),
-            }
-        )
-
-    def is_cached(self, instance):
-        return self.field.is_cached(instance)
-
-    def get_queryset(self, **hints):
-        return self.field.remote_field.model._base_manager.db_manager(hints=hints).all()
-
-    def get_prefetch_queryset(self, instances, queryset=None):
-        if queryset is None:
-            queryset = self.get_queryset()
-        queryset._add_hints(instance=instances[0])
-
-        rel_obj_attr = self.field.get_foreign_related_value
-        instance_attr = self.field.get_local_related_value
-        instances_dict = {instance_attr(inst): inst for inst in instances}
-        related_field = self.field.foreign_related_fields[0]
-        remote_field = self.field.remote_field
-
-        # FIXME: This will need to be revisited when we introduce support for
-        # composite fields. In the meantime we take this practical approach to
-        # solve a regression on 1.6 when the reverse manager in hidden
-        # (related_name ends with a '+'). Refs #21410.
-        # The check for len(...) == 1 is a special case that allows the query
-        # to be join-less and smaller. Refs #21760.
-        if remote_field.is_hidden() or len(self.field.foreign_related_fields) == 1:
-            query = {'%s__in' % related_field.name: {instance_attr(inst)[0] for inst in instances}}
-        else:
-            query = {'%s__in' % self.field.related_query_name(): instances}
-        queryset = queryset.filter(**query)
-
-        # Since we're going to assign directly in the cache,
-        # we must manage the reverse relation cache manually.
-        if not remote_field.multiple:
-            for rel_obj in queryset:
-                instance = instances_dict[rel_obj_attr(rel_obj)]
-                remote_field.set_cached_value(rel_obj, instance)
-        return queryset, rel_obj_attr, instance_attr, True, self.field.get_cache_name(), False
-
-    def get_object(self, instance):
-        qs = self.get_queryset(instance=instance)
-        # Assuming the database enforces foreign keys, this won't fail.
-        return qs.get(self.field.get_reverse_related_filter(instance))
-
-    def __get__(self, instance, cls=None):
-        """
-        Get the related instance through the forward relation.
-
-        With the example above, when getting ``child.parent``:
-
-        - ``self`` is the descriptor managing the ``parent`` attribute
-        - ``instance`` is the ``child`` instance
-        - ``cls`` is the ``Child`` class (we don't need it)
-        """
-        if instance is None:
-            return self
-
-        # The related instance is loaded from the database and then cached
-        # by the field on the model instance state. It can also be pre-cached
-        # by the reverse accessor (ReverseOneToOneDescriptor).
-        try:
-            rel_obj = self.field.get_cached_value(instance)
-        except KeyError:
-            has_value = None not in self.field.get_local_related_value(instance)
-            ancestor_link = instance._meta.get_ancestor_link(self.field.model) if has_value else None
-            if ancestor_link and ancestor_link.is_cached(instance):
-                # An ancestor link will exist if this field is defined on a
-                # multi-table inheritance parent of the instance's class.
-                ancestor = ancestor_link.get_cached_value(instance)
-                # The value might be cached on an ancestor if the instance
-                # originated from walking down the inheritance chain.
-                rel_obj = self.field.get_cached_value(ancestor, default=None)
-            else:
-                rel_obj = None
-            if rel_obj is None and has_value:
-                rel_obj = self.get_object(instance)
-                remote_field = self.field.remote_field
-                # If this is a one-to-one relation, set the reverse accessor
-                # cache on the related object to the current instance to avoid
-                # an extra SQL query if it's accessed later on.
-                if not remote_field.multiple:
-                    remote_field.set_cached_value(rel_obj, instance)
-            self.field.set_cached_value(instance, rel_obj)
-
-        if rel_obj is None and not self.field.null:
-            raise self.RelatedObjectDoesNotExist(
-                "%s has no %s." % (self.field.model.__name__, self.field.name)
-            )
-        else:
-            return rel_obj
-
-    def __set__(self, instance, value):
-        """
-        Set the related instance through the forward relation.
-
-        With the example above, when setting ``child.parent = parent``:
-
-        - ``self`` is the descriptor managing the ``parent`` attribute
-        - ``instance`` is the ``child`` instance
-        - ``value`` is the ``parent`` instance on the right of the equal sign
-        """
-        # An object must be an instance of the related class.
-        if value is not None and not isinstance(value, self.field.remote_field.model._meta.concrete_model):
-            raise ValueError(
-                'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
-                    value,
-                    instance._meta.object_name,
-                    self.field.name,
-                    self.field.remote_field.model._meta.object_name,
-                )
-            )
-        elif value is not None:
-            if instance._state.db is None:
-                instance._state.db = router.db_for_write(instance.__class__, instance=value)
-            if value._state.db is None:
-                value._state.db = router.db_for_write(value.__class__, instance=instance)
-            if not router.allow_relation(value, instance):
-                raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value)
-
-        remote_field = self.field.remote_field
-        # If we're setting the value of a OneToOneField to None, we need to clear
-        # out the cache on any old related object. Otherwise, deleting the
-        # previously-related object will also cause this object to be deleted,
-        # which is wrong.
-        if value is None:
-            # Look up the previously-related object, which may still be available
-            # since we've not yet cleared out the related field.
-            # Use the cache directly, instead of the accessor; if we haven't
-            # populated the cache, then we don't care - we're only accessing
-            # the object to invalidate the accessor cache, so there's no
-            # need to populate the cache just to expire it again.
-            related = self.field.get_cached_value(instance, default=None)
-
-            # If we've got an old related object, we need to clear out its
-            # cache. This cache also might not exist if the related object
-            # hasn't been accessed yet.
-            if related is not None:
-                remote_field.set_cached_value(related, None)
-
-            for lh_field, rh_field in self.field.related_fields:
-                setattr(instance, lh_field.attname, None)
-
-        # Set the values of the related field.
-        else:
-            for lh_field, rh_field in self.field.related_fields:
-                setattr(instance, lh_field.attname, getattr(value, rh_field.attname))
-
-        # Set the related instance cache used by __get__ to avoid an SQL query
-        # when accessing the attribute we just set.
-        self.field.set_cached_value(instance, value)
-
-        # If this is a one-to-one relation, set the reverse accessor cache on
-        # the related object to the current instance to avoid an extra SQL
-        # query if it's accessed later on.
-        if value is not None and not remote_field.multiple:
-            remote_field.set_cached_value(value, instance)
-
-    def __reduce__(self):
-        """
-        Pickling should return the instance attached by self.field on the
-        model, not a new copy of that descriptor. Use getattr() to retrieve
-        the instance directly from the model.
-        """
-        return getattr, (self.field.model, self.field.name)
-
-
-class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor):
-    """
-    Accessor to the related object on the forward side of a one-to-one relation.
-
-    In the example::
-
-        class Restaurant(Model):
-            place = OneToOneField(Place, related_name='restaurant')
-
-    ``Restaurant.place`` is a ``ForwardOneToOneDescriptor`` instance.
-    """
-
-    def get_object(self, instance):
-        if self.field.remote_field.parent_link:
-            deferred = instance.get_deferred_fields()
-            # Because it's a parent link, all the data is available in the
-            # instance, so populate the parent model with this data.
-            rel_model = self.field.remote_field.model
-            fields = [field.attname for field in rel_model._meta.concrete_fields]
-
-            # If any of the related model's fields are deferred, fallback to
-            # fetching all fields from the related model. This avoids a query
-            # on the related model for every deferred field.
-            if not any(field in fields for field in deferred):
-                kwargs = {field: getattr(instance, field) for field in fields}
-                obj = rel_model(**kwargs)
-                obj._state.adding = instance._state.adding
-                obj._state.db = instance._state.db
-                return obj
-        return super().get_object(instance)
-
-    def __set__(self, instance, value):
-        super().__set__(instance, value)
-        # If the primary key is a link to a parent model and a parent instance
-        # is being set, update the value of the inherited pk(s).
-        if self.field.primary_key and self.field.remote_field.parent_link:
-            opts = instance._meta
-            # Inherited primary key fields from this object's base classes.
-            inherited_pk_fields = [
-                field for field in opts.concrete_fields
-                if field.primary_key and field.remote_field
-            ]
-            for field in inherited_pk_fields:
-                rel_model_pk_name = field.remote_field.model._meta.pk.attname
-                raw_value = getattr(value, rel_model_pk_name) if value is not None else None
-                setattr(instance, rel_model_pk_name, raw_value)
-
-
-class ReverseOneToOneDescriptor:
-    """
-    Accessor to the related object on the reverse side of a one-to-one
-    relation.
-
-    In the example::
-
-        class Restaurant(Model):
-            place = OneToOneField(Place, related_name='restaurant')
-
-    ``Place.restaurant`` is a ``ReverseOneToOneDescriptor`` instance.
-    """
-
-    def __init__(self, related):
-        # Following the example above, `related` is an instance of OneToOneRel
-        # which represents the reverse restaurant field (place.restaurant).
-        self.related = related
-
-    @cached_property
-    def RelatedObjectDoesNotExist(self):
-        # The exception isn't created at initialization time for the sake of
-        # consistency with `ForwardManyToOneDescriptor`.
-        return type(
-            'RelatedObjectDoesNotExist',
-            (self.related.related_model.DoesNotExist, AttributeError), {
-                '__module__': self.related.model.__module__,
-                '__qualname__': '%s.%s.RelatedObjectDoesNotExist' % (
-                    self.related.model.__qualname__,
-                    self.related.name,
-                )
-            },
-        )
-
-    def is_cached(self, instance):
-        return self.related.is_cached(instance)
-
-    def get_queryset(self, **hints):
-        return self.related.related_model._base_manager.db_manager(hints=hints).all()
-
-    def get_prefetch_queryset(self, instances, queryset=None):
-        if queryset is None:
-            queryset = self.get_queryset()
-        queryset._add_hints(instance=instances[0])
-
-        rel_obj_attr = self.related.field.get_local_related_value
-        instance_attr = self.related.field.get_foreign_related_value
-        instances_dict = {instance_attr(inst): inst for inst in instances}
-        query = {'%s__in' % self.related.field.name: instances}
-        queryset = queryset.filter(**query)
-
-        # Since we're going to assign directly in the cache,
-        # we must manage the reverse relation cache manually.
-        for rel_obj in queryset:
-            instance = instances_dict[rel_obj_attr(rel_obj)]
-            self.related.field.set_cached_value(rel_obj, instance)
-        return queryset, rel_obj_attr, instance_attr, True, self.related.get_cache_name(), False
-
-    def __get__(self, instance, cls=None):
-        """
-        Get the related instance through the reverse relation.
-
-        With the example above, when getting ``place.restaurant``:
-
-        - ``self`` is the descriptor managing the ``restaurant`` attribute
-        - ``instance`` is the ``place`` instance
-        - ``cls`` is the ``Place`` class (unused)
-
-        Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
-        """
-        if instance is None:
-            return self
-
-        # The related instance is loaded from the database and then cached
-        # by the field on the model instance state. It can also be pre-cached
-        # by the forward accessor (ForwardManyToOneDescriptor).
-        try:
-            rel_obj = self.related.get_cached_value(instance)
-        except KeyError:
-            related_pk = instance.pk
-            if related_pk is None:
-                rel_obj = None
-            else:
-                filter_args = self.related.field.get_forward_related_filter(instance)
-                try:
-                    rel_obj = self.get_queryset(instance=instance).get(**filter_args)
-                except self.related.related_model.DoesNotExist:
-                    rel_obj = None
-                else:
-                    # Set the forward accessor cache on the related object to
-                    # the current instance to avoid an extra SQL query if it's
-                    # accessed later on.
-                    self.related.field.set_cached_value(rel_obj, instance)
-            self.related.set_cached_value(instance, rel_obj)
-
-        if rel_obj is None:
-            raise self.RelatedObjectDoesNotExist(
-                "%s has no %s." % (
-                    instance.__class__.__name__,
-                    self.related.get_accessor_name()
-                )
-            )
-        else:
-            return rel_obj
-
-    def __set__(self, instance, value):
-        """
-        Set the related instance through the reverse relation.
-
-        With the example above, when setting ``place.restaurant = restaurant``:
-
-        - ``self`` is the descriptor managing the ``restaurant`` attribute
-        - ``instance`` is the ``place`` instance
-        - ``value`` is the ``restaurant`` instance on the right of the equal sign
-
-        Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
-        """
-        # The similarity of the code below to the code in
-        # ForwardManyToOneDescriptor is annoying, but there's a bunch
-        # of small differences that would make a common base class convoluted.
-
-        if value is None:
-            # Update the cached related instance (if any) & clear the cache.
-            # Following the example above, this would be the cached
-            # ``restaurant`` instance (if any).
-            rel_obj = self.related.get_cached_value(instance, default=None)
-            if rel_obj is not None:
-                # Remove the ``restaurant`` instance from the ``place``
-                # instance cache.
-                self.related.delete_cached_value(instance)
-                # Set the ``place`` field on the ``restaurant``
-                # instance to None.
-                setattr(rel_obj, self.related.field.name, None)
-        elif not isinstance(value, self.related.related_model):
-            # An object must be an instance of the related class.
-            raise ValueError(
-                'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
-                    value,
-                    instance._meta.object_name,
-                    self.related.get_accessor_name(),
-                    self.related.related_model._meta.object_name,
-                )
-            )
-        else:
-            if instance._state.db is None:
-                instance._state.db = router.db_for_write(instance.__class__, instance=value)
-            if value._state.db is None:
-                value._state.db = router.db_for_write(value.__class__, instance=instance)
-            if not router.allow_relation(value, instance):
-                raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value)
-
-            related_pk = tuple(getattr(instance, field.attname) for field in self.related.field.foreign_related_fields)
-            # Set the value of the related field to the value of the related object's related field
-            for index, field in enumerate(self.related.field.local_related_fields):
-                setattr(value, field.attname, related_pk[index])
-
-            # Set the related instance cache used by __get__ to avoid an SQL query
-            # when accessing the attribute we just set.
-            self.related.set_cached_value(instance, value)
-
-            # Set the forward accessor cache on the related object to the current
-            # instance to avoid an extra SQL query if it's accessed later on.
-            self.related.field.set_cached_value(value, instance)
-
-    def __reduce__(self):
-        # Same purpose as ForwardManyToOneDescriptor.__reduce__().
-        return getattr, (self.related.model, self.related.name)
-
-
-class ReverseManyToOneDescriptor:
-    """
-    Accessor to the related objects manager on the reverse side of a
-    many-to-one relation.
-
-    In the example::
-
-        class Child(Model):
-            parent = ForeignKey(Parent, related_name='children')
-
-    ``Parent.children`` is a ``ReverseManyToOneDescriptor`` instance.
-
-    Most of the implementation is delegated to a dynamically defined manager
-    class built by ``create_forward_many_to_many_manager()`` defined below.
-    """
-
-    def __init__(self, rel):
-        self.rel = rel
-        self.field = rel.field
-
-    @cached_property
-    def related_manager_cls(self):
-        related_model = self.rel.related_model
-
-        return create_reverse_many_to_one_manager(
-            related_model._default_manager.__class__,
-            self.rel,
-        )
-
-    def __get__(self, instance, cls=None):
-        """
-        Get the related objects through the reverse relation.
-
-        With the example above, when getting ``parent.children``:
-
-        - ``self`` is the descriptor managing the ``children`` attribute
-        - ``instance`` is the ``parent`` instance
-        - ``cls`` is the ``Parent`` class (unused)
-        """
-        if instance is None:
-            return self
-
-        return self.related_manager_cls(instance)
-
-    def _get_set_deprecation_msg_params(self):
-        return (
-            'reverse side of a related set',
-            self.rel.get_accessor_name(),
-        )
-
-    def __set__(self, instance, value):
-        raise TypeError(
-            'Direct assignment to the %s is prohibited. Use %s.set() instead.'
-            % self._get_set_deprecation_msg_params(),
-        )
-
-
-def create_reverse_many_to_one_manager(superclass, rel):
-    """
-    Create a manager for the reverse side of a many-to-one relation.
-
-    This manager subclasses another manager, generally the default manager of
-    the related model, and adds behaviors specific to many-to-one relations.
-    """
-
-    class RelatedManager(superclass):
-        def __init__(self, instance):
-            super().__init__()
-
-            self.instance = instance
-            self.model = rel.related_model
-            self.field = rel.field
-
-            self.core_filters = {self.field.name: instance}
-
-        def __call__(self, *, manager):
-            manager = getattr(self.model, manager)
-            manager_class = create_reverse_many_to_one_manager(manager.__class__, rel)
-            return manager_class(self.instance)
-        do_not_call_in_templates = True
-
-        def _apply_rel_filters(self, queryset):
-            """
-            Filter the queryset for the instance this manager is bound to.
-            """
-            db = self._db or router.db_for_read(self.model, instance=self.instance)
-            empty_strings_as_null = connections[db].features.interprets_empty_strings_as_nulls
-            queryset._add_hints(instance=self.instance)
-            if self._db:
-                queryset = queryset.using(self._db)
-            queryset._defer_next_filter = True
-            queryset = queryset.filter(**self.core_filters)
-            for field in self.field.foreign_related_fields:
-                val = getattr(self.instance, field.attname)
-                if val is None or (val == '' and empty_strings_as_null):
-                    return queryset.none()
-            if self.field.many_to_one:
-                # Guard against field-like objects such as GenericRelation
-                # that abuse create_reverse_many_to_one_manager() with reverse
-                # one-to-many relationships instead and break known related
-                # objects assignment.
-                try:
-                    target_field = self.field.target_field
-                except FieldError:
-                    # The relationship has multiple target fields. Use a tuple
-                    # for related object id.
-                    rel_obj_id = tuple([
-                        getattr(self.instance, target_field.attname)
-                        for target_field in self.field.get_path_info()[-1].target_fields
-                    ])
-                else:
-                    rel_obj_id = getattr(self.instance, target_field.attname)
-                queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}}
-            return queryset
-
-        def _remove_prefetched_objects(self):
-            try:
-                self.instance._prefetched_objects_cache.pop(self.field.remote_field.get_cache_name())
-            except (AttributeError, KeyError):
-                pass  # nothing to clear from cache
-
-        def get_queryset(self):
-            try:
-                return self.instance._prefetched_objects_cache[self.field.remote_field.get_cache_name()]
-            except (AttributeError, KeyError):
-                queryset = super().get_queryset()
-                return self._apply_rel_filters(queryset)
-
-        def get_prefetch_queryset(self, instances, queryset=None):
-            if queryset is None:
-                queryset = super().get_queryset()
-
-            queryset._add_hints(instance=instances[0])
-            queryset = queryset.using(queryset._db or self._db)
-
-            rel_obj_attr = self.field.get_local_related_value
-            instance_attr = self.field.get_foreign_related_value
-            instances_dict = {instance_attr(inst): inst for inst in instances}
-            query = {'%s__in' % self.field.name: instances}
-            queryset = queryset.filter(**query)
-
-            # Since we just bypassed this class' get_queryset(), we must manage
-            # the reverse relation manually.
-            for rel_obj in queryset:
-                instance = instances_dict[rel_obj_attr(rel_obj)]
-                setattr(rel_obj, self.field.name, instance)
-            cache_name = self.field.remote_field.get_cache_name()
-            return queryset, rel_obj_attr, instance_attr, False, cache_name, False
-
-        def add(self, *objs, bulk=True):
-            self._remove_prefetched_objects()
-            db = router.db_for_write(self.model, instance=self.instance)
-
-            def check_and_update_obj(obj):
-                if not isinstance(obj, self.model):
-                    raise TypeError("'%s' instance expected, got %r" % (
-                        self.model._meta.object_name, obj,
-                    ))
-                setattr(obj, self.field.name, self.instance)
-
-            if bulk:
-                pks = []
-                for obj in objs:
-                    check_and_update_obj(obj)
-                    if obj._state.adding or obj._state.db != db:
-                        raise ValueError(
-                            "%r instance isn't saved. Use bulk=False or save "
-                            "the object first." % obj
-                        )
-                    pks.append(obj.pk)
-                self.model._base_manager.using(db).filter(pk__in=pks).update(**{
-                    self.field.name: self.instance,
-                })
-            else:
-                with transaction.atomic(using=db, savepoint=False):
-                    for obj in objs:
-                        check_and_update_obj(obj)
-                        obj.save()
-        add.alters_data = True
-
-        def create(self, **kwargs):
-            kwargs[self.field.name] = self.instance
-            db = router.db_for_write(self.model, instance=self.instance)
-            return super(RelatedManager, self.db_manager(db)).create(**kwargs)
-        create.alters_data = True
-
-        def get_or_create(self, **kwargs):
-            kwargs[self.field.name] = self.instance
-            db = router.db_for_write(self.model, instance=self.instance)
-            return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
-        get_or_create.alters_data = True
-
-        def update_or_create(self, **kwargs):
-            kwargs[self.field.name] = self.instance
-            db = router.db_for_write(self.model, instance=self.instance)
-            return super(RelatedManager, self.db_manager(db)).update_or_create(**kwargs)
-        update_or_create.alters_data = True
-
-        # remove() and clear() are only provided if the ForeignKey can have a value of null.
-        if rel.field.null:
-            def remove(self, *objs, bulk=True):
-                if not objs:
-                    return
-                val = self.field.get_foreign_related_value(self.instance)
-                old_ids = set()
-                for obj in objs:
-                    if not isinstance(obj, self.model):
-                        raise TypeError("'%s' instance expected, got %r" % (
-                            self.model._meta.object_name, obj,
-                        ))
-                    # Is obj actually part of this descriptor set?
-                    if self.field.get_local_related_value(obj) == val:
-                        old_ids.add(obj.pk)
-                    else:
-                        raise self.field.remote_field.model.DoesNotExist(
-                            "%r is not related to %r." % (obj, self.instance)
-                        )
-                self._clear(self.filter(pk__in=old_ids), bulk)
-            remove.alters_data = True
-
-            def clear(self, *, bulk=True):
-                self._clear(self, bulk)
-            clear.alters_data = True
-
-            def _clear(self, queryset, bulk):
-                self._remove_prefetched_objects()
-                db = router.db_for_write(self.model, instance=self.instance)
-                queryset = queryset.using(db)
-                if bulk:
-                    # `QuerySet.update()` is intrinsically atomic.
-                    queryset.update(**{self.field.name: None})
-                else:
-                    with transaction.atomic(using=db, savepoint=False):
-                        for obj in queryset:
-                            setattr(obj, self.field.name, None)
-                            obj.save(update_fields=[self.field.name])
-            _clear.alters_data = True
-
-        def set(self, objs, *, bulk=True, clear=False):
-            # Force evaluation of `objs` in case it's a queryset whose value
-            # could be affected by `manager.clear()`. Refs #19816.
-            objs = tuple(objs)
-
-            if self.field.null:
-                db = router.db_for_write(self.model, instance=self.instance)
-                with transaction.atomic(using=db, savepoint=False):
-                    if clear:
-                        self.clear(bulk=bulk)
-                        self.add(*objs, bulk=bulk)
-                    else:
-                        old_objs = set(self.using(db).all())
-                        new_objs = []
-                        for obj in objs:
-                            if obj in old_objs:
-                                old_objs.remove(obj)
-                            else:
-                                new_objs.append(obj)
-
-                        self.remove(*old_objs, bulk=bulk)
-                        self.add(*new_objs, bulk=bulk)
-            else:
-                self.add(*objs, bulk=bulk)
-        set.alters_data = True
-
-    return RelatedManager
-
-
-class ManyToManyDescriptor(ReverseManyToOneDescriptor):
-    """
-    Accessor to the related objects manager on the forward and reverse sides of
-    a many-to-many relation.
-
-    In the example::
-
-        class Pizza(Model):
-            toppings = ManyToManyField(Topping, related_name='pizzas')
-
-    ``Pizza.toppings`` and ``Topping.pizzas`` are ``ManyToManyDescriptor``
-    instances.
-
-    Most of the implementation is delegated to a dynamically defined manager
-    class built by ``create_forward_many_to_many_manager()`` defined below.
-    """
-
-    def __init__(self, rel, reverse=False):
-        super().__init__(rel)
-
-        self.reverse = reverse
-
-    @property
-    def through(self):
-        # through is provided so that you have easy access to the through
-        # model (Book.authors.through) for inlines, etc. This is done as
-        # a property to ensure that the fully resolved value is returned.
-        return self.rel.through
-
-    @cached_property
-    def related_manager_cls(self):
-        related_model = self.rel.related_model if self.reverse else self.rel.model
-
-        return create_forward_many_to_many_manager(
-            related_model._default_manager.__class__,
-            self.rel,
-            reverse=self.reverse,
-        )
-
-    def _get_set_deprecation_msg_params(self):
-        return (
-            '%s side of a many-to-many set' % ('reverse' if self.reverse else 'forward'),
-            self.rel.get_accessor_name() if self.reverse else self.field.name,
-        )
-
-
-def create_forward_many_to_many_manager(superclass, rel, reverse):
-    """
-    Create a manager for the either side of a many-to-many relation.
-
-    This manager subclasses another manager, generally the default manager of
-    the related model, and adds behaviors specific to many-to-many relations.
-    """
-
-    class ManyRelatedManager(superclass):
-        def __init__(self, instance=None):
-            super().__init__()
-
-            self.instance = instance
-
-            if not reverse:
-                self.model = rel.model
-                self.query_field_name = rel.field.related_query_name()
-                self.prefetch_cache_name = rel.field.name
-                self.source_field_name = rel.field.m2m_field_name()
-                self.target_field_name = rel.field.m2m_reverse_field_name()
-                self.symmetrical = rel.symmetrical
-            else:
-                self.model = rel.related_model
-                self.query_field_name = rel.field.name
-                self.prefetch_cache_name = rel.field.related_query_name()
-                self.source_field_name = rel.field.m2m_reverse_field_name()
-                self.target_field_name = rel.field.m2m_field_name()
-                self.symmetrical = False
-
-            self.through = rel.through
-            self.reverse = reverse
-
-            self.source_field = self.through._meta.get_field(self.source_field_name)
-            self.target_field = self.through._meta.get_field(self.target_field_name)
-
-            self.core_filters = {}
-            self.pk_field_names = {}
-            for lh_field, rh_field in self.source_field.related_fields:
-                core_filter_key = '%s__%s' % (self.query_field_name, rh_field.name)
-                self.core_filters[core_filter_key] = getattr(instance, rh_field.attname)
-                self.pk_field_names[lh_field.name] = rh_field.name
-
-            self.related_val = self.source_field.get_foreign_related_value(instance)
-            if None in self.related_val:
-                raise ValueError('"%r" needs to have a value for field "%s" before '
-                                 'this many-to-many relationship can be used.' %
-                                 (instance, self.pk_field_names[self.source_field_name]))
-            # Even if this relation is not to pk, we require still pk value.
-            # The wish is that the instance has been already saved to DB,
-            # although having a pk value isn't a guarantee of that.
-            if instance.pk is None:
-                raise ValueError("%r instance needs to have a primary key value before "
-                                 "a many-to-many relationship can be used." %
-                                 instance.__class__.__name__)
-
-        def __call__(self, *, manager):
-            manager = getattr(self.model, manager)
-            manager_class = create_forward_many_to_many_manager(manager.__class__, rel, reverse)
-            return manager_class(instance=self.instance)
-        do_not_call_in_templates = True
-
-        def _build_remove_filters(self, removed_vals):
-            filters = Q(**{self.source_field_name: self.related_val})
-            # No need to add a subquery condition if removed_vals is a QuerySet without
-            # filters.
-            removed_vals_filters = (not isinstance(removed_vals, QuerySet) or
-                                    removed_vals._has_filters())
-            if removed_vals_filters:
-                filters &= Q(**{'%s__in' % self.target_field_name: removed_vals})
-            if self.symmetrical:
-                symmetrical_filters = Q(**{self.target_field_name: self.related_val})
-                if removed_vals_filters:
-                    symmetrical_filters &= Q(
-                        **{'%s__in' % self.source_field_name: removed_vals})
-                filters |= symmetrical_filters
-            return filters
-
-        def _apply_rel_filters(self, queryset):
-            """
-            Filter the queryset for the instance this manager is bound to.
-            """
-            queryset._add_hints(instance=self.instance)
-            if self._db:
-                queryset = queryset.using(self._db)
-            queryset._defer_next_filter = True
-            return queryset._next_is_sticky().filter(**self.core_filters)
-
-        def _remove_prefetched_objects(self):
-            try:
-                self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
-            except (AttributeError, KeyError):
-                pass  # nothing to clear from cache
-
-        def get_queryset(self):
-            try:
-                return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
-            except (AttributeError, KeyError):
-                queryset = super().get_queryset()
-                return self._apply_rel_filters(queryset)
-
-        def get_prefetch_queryset(self, instances, queryset=None):
-            if queryset is None:
-                queryset = super().get_queryset()
-
-            queryset._add_hints(instance=instances[0])
-            queryset = queryset.using(queryset._db or self._db)
-
-            query = {'%s__in' % self.query_field_name: instances}
-            queryset = queryset._next_is_sticky().filter(**query)
-
-            # M2M: need to annotate the query in order to get the primary model
-            # that the secondary model was actually related to. We know that
-            # there will already be a join on the join table, so we can just add
-            # the select.
-
-            # For non-autocreated 'through' models, can't assume we are
-            # dealing with PK values.
-            fk = self.through._meta.get_field(self.source_field_name)
-            join_table = fk.model._meta.db_table
-            connection = connections[queryset.db]
-            qn = connection.ops.quote_name
-            queryset = queryset.extra(select={
-                '_prefetch_related_val_%s' % f.attname:
-                '%s.%s' % (qn(join_table), qn(f.column)) for f in fk.local_related_fields})
-            return (
-                queryset,
-                lambda result: tuple(
-                    getattr(result, '_prefetch_related_val_%s' % f.attname)
-                    for f in fk.local_related_fields
-                ),
-                lambda inst: tuple(
-                    f.get_db_prep_value(getattr(inst, f.attname), connection)
-                    for f in fk.foreign_related_fields
-                ),
-                False,
-                self.prefetch_cache_name,
-                False,
-            )
-
-        def add(self, *objs, through_defaults=None):
-            self._remove_prefetched_objects()
-            db = router.db_for_write(self.through, instance=self.instance)
-            with transaction.atomic(using=db, savepoint=False):
-                self._add_items(
-                    self.source_field_name, self.target_field_name, *objs,
-                    through_defaults=through_defaults,
-                )
-                # If this is a symmetrical m2m relation to self, add the mirror
-                # entry in the m2m table.
-                if self.symmetrical:
-                    self._add_items(
-                        self.target_field_name,
-                        self.source_field_name,
-                        *objs,
-                        through_defaults=through_defaults,
-                    )
-        add.alters_data = True
-
-        def remove(self, *objs):
-            self._remove_prefetched_objects()
-            self._remove_items(self.source_field_name, self.target_field_name, *objs)
-        remove.alters_data = True
-
-        def clear(self):
-            db = router.db_for_write(self.through, instance=self.instance)
-            with transaction.atomic(using=db, savepoint=False):
-                signals.m2m_changed.send(
-                    sender=self.through, action="pre_clear",
-                    instance=self.instance, reverse=self.reverse,
-                    model=self.model, pk_set=None, using=db,
-                )
-                self._remove_prefetched_objects()
-                filters = self._build_remove_filters(super().get_queryset().using(db))
-                self.through._default_manager.using(db).filter(filters).delete()
-
-                signals.m2m_changed.send(
-                    sender=self.through, action="post_clear",
-                    instance=self.instance, reverse=self.reverse,
-                    model=self.model, pk_set=None, using=db,
-                )
-        clear.alters_data = True
-
-        def set(self, objs, *, clear=False, through_defaults=None):
-            # Force evaluation of `objs` in case it's a queryset whose value
-            # could be affected by `manager.clear()`. Refs #19816.
-            objs = tuple(objs)
-
-            db = router.db_for_write(self.through, instance=self.instance)
-            with transaction.atomic(using=db, savepoint=False):
-                if clear:
-                    self.clear()
-                    self.add(*objs, through_defaults=through_defaults)
-                else:
-                    old_ids = set(self.using(db).values_list(self.target_field.target_field.attname, flat=True))
-
-                    new_objs = []
-                    for obj in objs:
-                        fk_val = (
-                            self.target_field.get_foreign_related_value(obj)[0]
-                            if isinstance(obj, self.model)
-                            else self.target_field.get_prep_value(obj)
-                        )
-                        if fk_val in old_ids:
-                            old_ids.remove(fk_val)
-                        else:
-                            new_objs.append(obj)
-
-                    self.remove(*old_ids)
-                    self.add(*new_objs, through_defaults=through_defaults)
-        set.alters_data = True
-
-        def create(self, *, through_defaults=None, **kwargs):
-            db = router.db_for_write(self.instance.__class__, instance=self.instance)
-            new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs)
-            self.add(new_obj, through_defaults=through_defaults)
-            return new_obj
-        create.alters_data = True
-
-        def get_or_create(self, *, through_defaults=None, **kwargs):
-            db = router.db_for_write(self.instance.__class__, instance=self.instance)
-            obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
-            # We only need to add() if created because if we got an object back
-            # from get() then the relationship already exists.
-            if created:
-                self.add(obj, through_defaults=through_defaults)
-            return obj, created
-        get_or_create.alters_data = True
-
-        def update_or_create(self, *, through_defaults=None, **kwargs):
-            db = router.db_for_write(self.instance.__class__, instance=self.instance)
-            obj, created = super(ManyRelatedManager, self.db_manager(db)).update_or_create(**kwargs)
-            # We only need to add() if created because if we got an object back
-            # from get() then the relationship already exists.
-            if created:
-                self.add(obj, through_defaults=through_defaults)
-            return obj, created
-        update_or_create.alters_data = True
-
-        def _get_target_ids(self, target_field_name, objs):
-            """
-            Return the set of ids of `objs` that the target field references.
-            """
-            from django.db.models import Model
-            target_ids = set()
-            target_field = self.through._meta.get_field(target_field_name)
-            for obj in objs:
-                if isinstance(obj, self.model):
-                    if not router.allow_relation(obj, self.instance):
-                        raise ValueError(
-                            'Cannot add "%r": instance is on database "%s", '
-                            'value is on database "%s"' %
-                            (obj, self.instance._state.db, obj._state.db)
-                        )
-                    target_id = target_field.get_foreign_related_value(obj)[0]
-                    if target_id is None:
-                        raise ValueError(
-                            'Cannot add "%r": the value for field "%s" is None' %
-                            (obj, target_field_name)
-                        )
-                    target_ids.add(target_id)
-                elif isinstance(obj, Model):
-                    raise TypeError(
-                        "'%s' instance expected, got %r" %
-                        (self.model._meta.object_name, obj)
-                    )
-                else:
-                    target_ids.add(target_field.get_prep_value(obj))
-            return target_ids
-
-        def _get_missing_target_ids(self, source_field_name, target_field_name, db, target_ids):
-            """
-            Return the subset of ids of `objs` that aren't already assigned to
-            this relationship.
-            """
-            vals = self.through._default_manager.using(db).values_list(
-                target_field_name, flat=True
-            ).filter(**{
-                source_field_name: self.related_val[0],
-                '%s__in' % target_field_name: target_ids,
-            })
-            return target_ids.difference(vals)
-
-        def _get_add_plan(self, db, source_field_name):
-            """
-            Return a boolean triple of the way the add should be performed.
-
-            The first element is whether or not bulk_create(ignore_conflicts)
-            can be used, the second whether or not signals must be sent, and
-            the third element is whether or not the immediate bulk insertion
-            with conflicts ignored can be performed.
-            """
-            # Conflicts can be ignored when the intermediary model is
-            # auto-created as the only possible collision is on the
-            # (source_id, target_id) tuple. The same assertion doesn't hold for
-            # user-defined intermediary models as they could have other fields
-            # causing conflicts which must be surfaced.
-            can_ignore_conflicts = (
-                connections[db].features.supports_ignore_conflicts and
-                self.through._meta.auto_created is not False
-            )
-            # Don't send the signal when inserting duplicate data row
-            # for symmetrical reverse entries.
-            must_send_signals = (self.reverse or source_field_name == self.source_field_name) and (
-                signals.m2m_changed.has_listeners(self.through)
-            )
-            # Fast addition through bulk insertion can only be performed
-            # if no m2m_changed listeners are connected for self.through
-            # as they require the added set of ids to be provided via
-            # pk_set.
-            return can_ignore_conflicts, must_send_signals, (can_ignore_conflicts and not must_send_signals)
-
-        def _add_items(self, source_field_name, target_field_name, *objs, through_defaults=None):
-            # source_field_name: the PK fieldname in join table for the source object
-            # target_field_name: the PK fieldname in join table for the target object
-            # *objs - objects to add. Either object instances, or primary keys of object instances.
-            if not objs:
-                return
-
-            through_defaults = dict(resolve_callables(through_defaults or {}))
-            target_ids = self._get_target_ids(target_field_name, objs)
-            db = router.db_for_write(self.through, instance=self.instance)
-            can_ignore_conflicts, must_send_signals, can_fast_add = self._get_add_plan(db, source_field_name)
-            if can_fast_add:
-                self.through._default_manager.using(db).bulk_create([
-                    self.through(**{
-                        '%s_id' % source_field_name: self.related_val[0],
-                        '%s_id' % target_field_name: target_id,
-                    })
-                    for target_id in target_ids
-                ], ignore_conflicts=True)
-                return
-
-            missing_target_ids = self._get_missing_target_ids(
-                source_field_name, target_field_name, db, target_ids
-            )
-            with transaction.atomic(using=db, savepoint=False):
-                if must_send_signals:
-                    signals.m2m_changed.send(
-                        sender=self.through, action='pre_add',
-                        instance=self.instance, reverse=self.reverse,
-                        model=self.model, pk_set=missing_target_ids, using=db,
-                    )
-                # Add the ones that aren't there already.
-                self.through._default_manager.using(db).bulk_create([
-                    self.through(**through_defaults, **{
-                        '%s_id' % source_field_name: self.related_val[0],
-                        '%s_id' % target_field_name: target_id,
-                    })
-                    for target_id in missing_target_ids
-                ], ignore_conflicts=can_ignore_conflicts)
-
-                if must_send_signals:
-                    signals.m2m_changed.send(
-                        sender=self.through, action='post_add',
-                        instance=self.instance, reverse=self.reverse,
-                        model=self.model, pk_set=missing_target_ids, using=db,
-                    )
-
-        def _remove_items(self, source_field_name, target_field_name, *objs):
-            # source_field_name: the PK colname in join table for the source object
-            # target_field_name: the PK colname in join table for the target object
-            # *objs - objects to remove. Either object instances, or primary
-            # keys of object instances.
-            if not objs:
-                return
-
-            # Check that all the objects are of the right type
-            old_ids = set()
-            for obj in objs:
-                if isinstance(obj, self.model):
-                    fk_val = self.target_field.get_foreign_related_value(obj)[0]
-                    old_ids.add(fk_val)
-                else:
-                    old_ids.add(obj)
-
-            db = router.db_for_write(self.through, instance=self.instance)
-            with transaction.atomic(using=db, savepoint=False):
-                # Send a signal to the other end if need be.
-                signals.m2m_changed.send(
-                    sender=self.through, action="pre_remove",
-                    instance=self.instance, reverse=self.reverse,
-                    model=self.model, pk_set=old_ids, using=db,
-                )
-                target_model_qs = super().get_queryset()
-                if target_model_qs._has_filters():
-                    old_vals = target_model_qs.using(db).filter(**{
-                        '%s__in' % self.target_field.target_field.attname: old_ids})
-                else:
-                    old_vals = old_ids
-                filters = self._build_remove_filters(old_vals)
-                self.through._default_manager.using(db).filter(filters).delete()
-
-                signals.m2m_changed.send(
-                    sender=self.through, action="post_remove",
-                    instance=self.instance, reverse=self.reverse,
-                    model=self.model, pk_set=old_ids, using=db,
-                )
-
-    return ManyRelatedManager
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/django/db/models/manager.html b/docs/0.9.5/_modules/django/db/models/manager.html deleted file mode 100644 index 7fab905bd8..0000000000 --- a/docs/0.9.5/_modules/django/db/models/manager.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - - django.db.models.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for django.db.models.manager

-import copy
-import inspect
-from importlib import import_module
-
-from django.db import router
-from django.db.models.query import QuerySet
-
-
-class BaseManager:
-    # To retain order, track each time a Manager instance is created.
-    creation_counter = 0
-
-    # Set to True for the 'objects' managers that are automatically created.
-    auto_created = False
-
-    #: If set to True the manager will be serialized into migrations and will
-    #: thus be available in e.g. RunPython operations.
-    use_in_migrations = False
-
-    def __new__(cls, *args, **kwargs):
-        # Capture the arguments to make returning them trivial.
-        obj = super().__new__(cls)
-        obj._constructor_args = (args, kwargs)
-        return obj
-
-    def __init__(self):
-        super().__init__()
-        self._set_creation_counter()
-        self.model = None
-        self.name = None
-        self._db = None
-        self._hints = {}
-
-    def __str__(self):
-        """Return "app_label.model_label.manager_name"."""
-        return '%s.%s' % (self.model._meta.label, self.name)
-
-    def __class_getitem__(cls, *args, **kwargs):
-        return cls
-
-    def deconstruct(self):
-        """
-        Return a 5-tuple of the form (as_manager (True), manager_class,
-        queryset_class, args, kwargs).
-
-        Raise a ValueError if the manager is dynamically generated.
-        """
-        qs_class = self._queryset_class
-        if getattr(self, '_built_with_as_manager', False):
-            # using MyQuerySet.as_manager()
-            return (
-                True,  # as_manager
-                None,  # manager_class
-                '%s.%s' % (qs_class.__module__, qs_class.__name__),  # qs_class
-                None,  # args
-                None,  # kwargs
-            )
-        else:
-            module_name = self.__module__
-            name = self.__class__.__name__
-            # Make sure it's actually there and not an inner class
-            module = import_module(module_name)
-            if not hasattr(module, name):
-                raise ValueError(
-                    "Could not find manager %s in %s.\n"
-                    "Please note that you need to inherit from managers you "
-                    "dynamically generated with 'from_queryset()'."
-                    % (name, module_name)
-                )
-            return (
-                False,  # as_manager
-                '%s.%s' % (module_name, name),  # manager_class
-                None,  # qs_class
-                self._constructor_args[0],  # args
-                self._constructor_args[1],  # kwargs
-            )
-
-    def check(self, **kwargs):
-        return []
-
-    @classmethod
-    def _get_queryset_methods(cls, queryset_class):
-        def create_method(name, method):
-            def manager_method(self, *args, **kwargs):
-                return getattr(self.get_queryset(), name)(*args, **kwargs)
-            manager_method.__name__ = method.__name__
-            manager_method.__doc__ = method.__doc__
-            return manager_method
-
-        new_methods = {}
-        for name, method in inspect.getmembers(queryset_class, predicate=inspect.isfunction):
-            # Only copy missing methods.
-            if hasattr(cls, name):
-                continue
-            # Only copy public methods or methods with the attribute `queryset_only=False`.
-            queryset_only = getattr(method, 'queryset_only', None)
-            if queryset_only or (queryset_only is None and name.startswith('_')):
-                continue
-            # Copy the method onto the manager.
-            new_methods[name] = create_method(name, method)
-        return new_methods
-
-    @classmethod
-    def from_queryset(cls, queryset_class, class_name=None):
-        if class_name is None:
-            class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)
-        return type(class_name, (cls,), {
-            '_queryset_class': queryset_class,
-            **cls._get_queryset_methods(queryset_class),
-        })
-
-    def contribute_to_class(self, cls, name):
-        self.name = self.name or name
-        self.model = cls
-
-        setattr(cls, name, ManagerDescriptor(self))
-
-        cls._meta.add_manager(self)
-
-    def _set_creation_counter(self):
-        """
-        Set the creation counter value for this instance and increment the
-        class-level copy.
-        """
-        self.creation_counter = BaseManager.creation_counter
-        BaseManager.creation_counter += 1
-
-    def db_manager(self, using=None, hints=None):
-        obj = copy.copy(self)
-        obj._db = using or self._db
-        obj._hints = hints or self._hints
-        return obj
-
-    @property
-    def db(self):
-        return self._db or router.db_for_read(self.model, **self._hints)
-
-    #######################
-    # PROXIES TO QUERYSET #
-    #######################
-
-    def get_queryset(self):
-        """
-        Return a new QuerySet object. Subclasses can override this method to
-        customize the behavior of the Manager.
-        """
-        return self._queryset_class(model=self.model, using=self._db, hints=self._hints)
-
-    def all(self):
-        # We can't proxy this method through the `QuerySet` like we do for the
-        # rest of the `QuerySet` methods. This is because `QuerySet.all()`
-        # works by creating a "copy" of the current queryset and in making said
-        # copy, all the cached `prefetch_related` lookups are lost. See the
-        # implementation of `RelatedManager.get_queryset()` for a better
-        # understanding of how this comes into play.
-        return self.get_queryset()
-
-    def __eq__(self, other):
-        return (
-            isinstance(other, self.__class__) and
-            self._constructor_args == other._constructor_args
-        )
-
-    def __hash__(self):
-        return id(self)
-
-
-class Manager(BaseManager.from_queryset(QuerySet)):
-    pass
-
-
-class ManagerDescriptor:
-
-    def __init__(self, manager):
-        self.manager = manager
-
-    def __get__(self, instance, cls=None):
-        if instance is not None:
-            raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
-
-        if cls._meta.abstract:
-            raise AttributeError("Manager isn't available; %s is abstract" % (
-                cls._meta.object_name,
-            ))
-
-        if cls._meta.swapped:
-            raise AttributeError(
-                "Manager isn't available; '%s' has been swapped for '%s'" % (
-                    cls._meta.label,
-                    cls._meta.swapped,
-                )
-            )
-
-        return cls._meta.managers_map[self.manager.name]
-
-
-class EmptyManager(Manager):
-    def __init__(self, model):
-        super().__init__()
-        self.model = model
-
-    def get_queryset(self):
-        return super().get_queryset().none()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/django/db/models/query_utils.html b/docs/0.9.5/_modules/django/db/models/query_utils.html deleted file mode 100644 index 01ced5e947..0000000000 --- a/docs/0.9.5/_modules/django/db/models/query_utils.html +++ /dev/null @@ -1,450 +0,0 @@ - - - - - - - - django.db.models.query_utils — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for django.db.models.query_utils

-"""
-Various data structures used in query construction.
-
-Factored out from django.db.models.query to avoid making the main module very
-large and/or so that they can be used by other modules without getting into
-circular import difficulties.
-"""
-import copy
-import functools
-import inspect
-import warnings
-from collections import namedtuple
-
-from django.core.exceptions import FieldDoesNotExist, FieldError
-from django.db.models.constants import LOOKUP_SEP
-from django.utils import tree
-from django.utils.deprecation import RemovedInDjango40Warning
-
-# PathInfo is used when converting lookups (fk__somecol). The contents
-# describe the relation in Model terms (model Options and Fields for both
-# sides of the relation. The join_field is the field backing the relation.
-PathInfo = namedtuple('PathInfo', 'from_opts to_opts target_fields join_field m2m direct filtered_relation')
-
-
-class InvalidQueryType(type):
-    @property
-    def _subclasses(self):
-        return (FieldDoesNotExist, FieldError)
-
-    def __warn(self):
-        warnings.warn(
-            'The InvalidQuery exception class is deprecated. Use '
-            'FieldDoesNotExist or FieldError instead.',
-            category=RemovedInDjango40Warning,
-            stacklevel=4,
-        )
-
-    def __instancecheck__(self, instance):
-        self.__warn()
-        return isinstance(instance, self._subclasses) or super().__instancecheck__(instance)
-
-    def __subclasscheck__(self, subclass):
-        self.__warn()
-        return issubclass(subclass, self._subclasses) or super().__subclasscheck__(subclass)
-
-
-class InvalidQuery(Exception, metaclass=InvalidQueryType):
-    pass
-
-
-def subclasses(cls):
-    yield cls
-    for subclass in cls.__subclasses__():
-        yield from subclasses(subclass)
-
-
-class Q(tree.Node):
-    """
-    Encapsulate filters as objects that can then be combined logically (using
-    `&` and `|`).
-    """
-    # Connection types
-    AND = 'AND'
-    OR = 'OR'
-    default = AND
-    conditional = True
-
-    def __init__(self, *args, _connector=None, _negated=False, **kwargs):
-        super().__init__(children=[*args, *sorted(kwargs.items())], connector=_connector, negated=_negated)
-
-    def _combine(self, other, conn):
-        if not(isinstance(other, Q) or getattr(other, 'conditional', False) is True):
-            raise TypeError(other)
-
-        if not self:
-            return other.copy() if hasattr(other, 'copy') else copy.copy(other)
-        elif isinstance(other, Q) and not other:
-            _, args, kwargs = self.deconstruct()
-            return type(self)(*args, **kwargs)
-
-        obj = type(self)()
-        obj.connector = conn
-        obj.add(self, conn)
-        obj.add(other, conn)
-        return obj
-
-    def __or__(self, other):
-        return self._combine(other, self.OR)
-
-    def __and__(self, other):
-        return self._combine(other, self.AND)
-
-    def __invert__(self):
-        obj = type(self)()
-        obj.add(self, self.AND)
-        obj.negate()
-        return obj
-
-    def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
-        # We must promote any new joins to left outer joins so that when Q is
-        # used as an expression, rows aren't filtered due to joins.
-        clause, joins = query._add_q(
-            self, reuse, allow_joins=allow_joins, split_subq=False,
-            check_filterable=False,
-        )
-        query.promote_joins(joins)
-        return clause
-
-    def deconstruct(self):
-        path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
-        if path.startswith('django.db.models.query_utils'):
-            path = path.replace('django.db.models.query_utils', 'django.db.models')
-        args = tuple(self.children)
-        kwargs = {}
-        if self.connector != self.default:
-            kwargs['_connector'] = self.connector
-        if self.negated:
-            kwargs['_negated'] = True
-        return path, args, kwargs
-
-
-class DeferredAttribute:
-    """
-    A wrapper for a deferred-loading field. When the value is read from this
-    object the first time, the query is executed.
-    """
-    def __init__(self, field):
-        self.field = field
-
-    def __get__(self, instance, cls=None):
-        """
-        Retrieve and caches the value from the datastore on the first lookup.
-        Return the cached value.
-        """
-        if instance is None:
-            return self
-        data = instance.__dict__
-        field_name = self.field.attname
-        if field_name not in data:
-            # Let's see if the field is part of the parent chain. If so we
-            # might be able to reuse the already loaded value. Refs #18343.
-            val = self._check_parent_chain(instance)
-            if val is None:
-                instance.refresh_from_db(fields=[field_name])
-            else:
-                data[field_name] = val
-        return data[field_name]
-
-    def _check_parent_chain(self, instance):
-        """
-        Check if the field value can be fetched from a parent field already
-        loaded in the instance. This can be done if the to-be fetched
-        field is a primary key field.
-        """
-        opts = instance._meta
-        link_field = opts.get_ancestor_link(self.field.model)
-        if self.field.primary_key and self.field != link_field:
-            return getattr(instance, link_field.attname)
-        return None
-
-
-class RegisterLookupMixin:
-
-    @classmethod
-    def _get_lookup(cls, lookup_name):
-        return cls.get_lookups().get(lookup_name, None)
-
-    @classmethod
-    @functools.lru_cache(maxsize=None)
-    def get_lookups(cls):
-        class_lookups = [parent.__dict__.get('class_lookups', {}) for parent in inspect.getmro(cls)]
-        return cls.merge_dicts(class_lookups)
-
-    def get_lookup(self, lookup_name):
-        from django.db.models.lookups import Lookup
-        found = self._get_lookup(lookup_name)
-        if found is None and hasattr(self, 'output_field'):
-            return self.output_field.get_lookup(lookup_name)
-        if found is not None and not issubclass(found, Lookup):
-            return None
-        return found
-
-    def get_transform(self, lookup_name):
-        from django.db.models.lookups import Transform
-        found = self._get_lookup(lookup_name)
-        if found is None and hasattr(self, 'output_field'):
-            return self.output_field.get_transform(lookup_name)
-        if found is not None and not issubclass(found, Transform):
-            return None
-        return found
-
-    @staticmethod
-    def merge_dicts(dicts):
-        """
-        Merge dicts in reverse to preference the order of the original list. e.g.,
-        merge_dicts([a, b]) will preference the keys in 'a' over those in 'b'.
-        """
-        merged = {}
-        for d in reversed(dicts):
-            merged.update(d)
-        return merged
-
-    @classmethod
-    def _clear_cached_lookups(cls):
-        for subclass in subclasses(cls):
-            subclass.get_lookups.cache_clear()
-
-    @classmethod
-    def register_lookup(cls, lookup, lookup_name=None):
-        if lookup_name is None:
-            lookup_name = lookup.lookup_name
-        if 'class_lookups' not in cls.__dict__:
-            cls.class_lookups = {}
-        cls.class_lookups[lookup_name] = lookup
-        cls._clear_cached_lookups()
-        return lookup
-
-    @classmethod
-    def _unregister_lookup(cls, lookup, lookup_name=None):
-        """
-        Remove given lookup from cls lookups. For use in tests only as it's
-        not thread-safe.
-        """
-        if lookup_name is None:
-            lookup_name = lookup.lookup_name
-        del cls.class_lookups[lookup_name]
-
-
-def select_related_descend(field, restricted, requested, load_fields, reverse=False):
-    """
-    Return True if this field should be used to descend deeper for
-    select_related() purposes. Used by both the query construction code
-    (sql.query.fill_related_selections()) and the model instance creation code
-    (query.get_klass_info()).
-
-    Arguments:
-     * field - the field to be checked
-     * restricted - a boolean field, indicating if the field list has been
-       manually restricted using a requested clause)
-     * requested - The select_related() dictionary.
-     * load_fields - the set of fields to be loaded on this model
-     * reverse - boolean, True if we are checking a reverse select related
-    """
-    if not field.remote_field:
-        return False
-    if field.remote_field.parent_link and not reverse:
-        return False
-    if restricted:
-        if reverse and field.related_query_name() not in requested:
-            return False
-        if not reverse and field.name not in requested:
-            return False
-    if not restricted and field.null:
-        return False
-    if load_fields:
-        if field.attname not in load_fields:
-            if restricted and field.name in requested:
-                msg = (
-                    'Field %s.%s cannot be both deferred and traversed using '
-                    'select_related at the same time.'
-                ) % (field.model._meta.object_name, field.name)
-                raise FieldError(msg)
-    return True
-
-
-def refs_expression(lookup_parts, annotations):
-    """
-    Check if the lookup_parts contains references to the given annotations set.
-    Because the LOOKUP_SEP is contained in the default annotation names, check
-    each prefix of the lookup_parts for a match.
-    """
-    for n in range(1, len(lookup_parts) + 1):
-        level_n_lookup = LOOKUP_SEP.join(lookup_parts[0:n])
-        if level_n_lookup in annotations and annotations[level_n_lookup]:
-            return annotations[level_n_lookup], lookup_parts[n:]
-    return False, ()
-
-
-def check_rel_lookup_compatibility(model, target_opts, field):
-    """
-    Check that self.model is compatible with target_opts. Compatibility
-    is OK if:
-      1) model and opts match (where proxy inheritance is removed)
-      2) model is parent of opts' model or the other way around
-    """
-    def check(opts):
-        return (
-            model._meta.concrete_model == opts.concrete_model or
-            opts.concrete_model in model._meta.get_parent_list() or
-            model in opts.get_parent_list()
-        )
-    # If the field is a primary key, then doing a query against the field's
-    # model is ok, too. Consider the case:
-    # class Restaurant(models.Model):
-    #     place = OneToOneField(Place, primary_key=True):
-    # Restaurant.objects.filter(pk__in=Restaurant.objects.all()).
-    # If we didn't have the primary key check, then pk__in (== place__in) would
-    # give Place's opts as the target opts, but Restaurant isn't compatible
-    # with that. This logic applies only to primary keys, as when doing __in=qs,
-    # we are going to turn this into __in=qs.values('pk') later on.
-    return (
-        check(target_opts) or
-        (getattr(field, 'primary_key', False) and check(field.model._meta))
-    )
-
-
-class FilteredRelation:
-    """Specify custom filtering in the ON clause of SQL joins."""
-
-    def __init__(self, relation_name, *, condition=Q()):
-        if not relation_name:
-            raise ValueError('relation_name cannot be empty.')
-        self.relation_name = relation_name
-        self.alias = None
-        if not isinstance(condition, Q):
-            raise ValueError('condition argument must be a Q() instance.')
-        self.condition = condition
-        self.path = []
-
-    def __eq__(self, other):
-        if not isinstance(other, self.__class__):
-            return NotImplemented
-        return (
-            self.relation_name == other.relation_name and
-            self.alias == other.alias and
-            self.condition == other.condition
-        )
-
-    def clone(self):
-        clone = FilteredRelation(self.relation_name, condition=self.condition)
-        clone.alias = self.alias
-        clone.path = self.path[:]
-        return clone
-
-    def resolve_expression(self, *args, **kwargs):
-        """
-        QuerySet.annotate() only accepts expression-like arguments
-        (with a resolve_expression() method).
-        """
-        raise NotImplementedError('FilteredRelation.resolve_expression() is unused.')
-
-    def as_sql(self, compiler, connection):
-        # Resolve the condition in Join.filtered_relation.
-        query = compiler.query
-        where = query.build_filtered_relation_q(self.condition, reuse=set(self.path))
-        return compiler.compile(where)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/django/utils/functional.html b/docs/0.9.5/_modules/django/utils/functional.html deleted file mode 100644 index 0e7ba03b5b..0000000000 --- a/docs/0.9.5/_modules/django/utils/functional.html +++ /dev/null @@ -1,527 +0,0 @@ - - - - - - - - django.utils.functional — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for django.utils.functional

-import copy
-import itertools
-import operator
-from functools import total_ordering, wraps
-
-
-class cached_property:
-    """
-    Decorator that converts a method with a single self argument into a
-    property cached on the instance.
-
-    A cached property can be made out of an existing method:
-    (e.g. ``url = cached_property(get_absolute_url)``).
-    The optional ``name`` argument is obsolete as of Python 3.6 and will be
-    deprecated in Django 4.0 (#30127).
-    """
-    name = None
-
-    @staticmethod
-    def func(instance):
-        raise TypeError(
-            'Cannot use cached_property instance without calling '
-            '__set_name__() on it.'
-        )
-
-    def __init__(self, func, name=None):
-        self.real_func = func
-        self.__doc__ = getattr(func, '__doc__')
-
-    def __set_name__(self, owner, name):
-        if self.name is None:
-            self.name = name
-            self.func = self.real_func
-        elif name != self.name:
-            raise TypeError(
-                "Cannot assign the same cached_property to two different names "
-                "(%r and %r)." % (self.name, name)
-            )
-
-    def __get__(self, instance, cls=None):
-        """
-        Call the function and put the return value in instance.__dict__ so that
-        subsequent attribute access on the instance returns the cached value
-        instead of calling cached_property.__get__().
-        """
-        if instance is None:
-            return self
-        res = instance.__dict__[self.name] = self.func(instance)
-        return res
-
-
-class classproperty:
-    """
-    Decorator that converts a method with a single cls argument into a property
-    that can be accessed directly from the class.
-    """
-    def __init__(self, method=None):
-        self.fget = method
-
-    def __get__(self, instance, cls=None):
-        return self.fget(cls)
-
-    def getter(self, method):
-        self.fget = method
-        return self
-
-
-class Promise:
-    """
-    Base class for the proxy class created in the closure of the lazy function.
-    It's used to recognize promises in code.
-    """
-    pass
-
-
-def lazy(func, *resultclasses):
-    """
-    Turn any callable into a lazy evaluated callable. result classes or types
-    is required -- at least one is needed so that the automatic forcing of
-    the lazy evaluation code is triggered. Results are not memoized; the
-    function is evaluated on every access.
-    """
-
-    @total_ordering
-    class __proxy__(Promise):
-        """
-        Encapsulate a function call and act as a proxy for methods that are
-        called on the result of that function. The function is not evaluated
-        until one of the methods on the result is called.
-        """
-        __prepared = False
-
-        def __init__(self, args, kw):
-            self.__args = args
-            self.__kw = kw
-            if not self.__prepared:
-                self.__prepare_class__()
-            self.__class__.__prepared = True
-
-        def __reduce__(self):
-            return (
-                _lazy_proxy_unpickle,
-                (func, self.__args, self.__kw) + resultclasses
-            )
-
-        def __repr__(self):
-            return repr(self.__cast())
-
-        @classmethod
-        def __prepare_class__(cls):
-            for resultclass in resultclasses:
-                for type_ in resultclass.mro():
-                    for method_name in type_.__dict__:
-                        # All __promise__ return the same wrapper method, they
-                        # look up the correct implementation when called.
-                        if hasattr(cls, method_name):
-                            continue
-                        meth = cls.__promise__(method_name)
-                        setattr(cls, method_name, meth)
-            cls._delegate_bytes = bytes in resultclasses
-            cls._delegate_text = str in resultclasses
-            assert not (cls._delegate_bytes and cls._delegate_text), (
-                "Cannot call lazy() with both bytes and text return types.")
-            if cls._delegate_text:
-                cls.__str__ = cls.__text_cast
-            elif cls._delegate_bytes:
-                cls.__bytes__ = cls.__bytes_cast
-
-        @classmethod
-        def __promise__(cls, method_name):
-            # Builds a wrapper around some magic method
-            def __wrapper__(self, *args, **kw):
-                # Automatically triggers the evaluation of a lazy value and
-                # applies the given magic method of the result type.
-                res = func(*self.__args, **self.__kw)
-                return getattr(res, method_name)(*args, **kw)
-            return __wrapper__
-
-        def __text_cast(self):
-            return func(*self.__args, **self.__kw)
-
-        def __bytes_cast(self):
-            return bytes(func(*self.__args, **self.__kw))
-
-        def __bytes_cast_encoded(self):
-            return func(*self.__args, **self.__kw).encode()
-
-        def __cast(self):
-            if self._delegate_bytes:
-                return self.__bytes_cast()
-            elif self._delegate_text:
-                return self.__text_cast()
-            else:
-                return func(*self.__args, **self.__kw)
-
-        def __str__(self):
-            # object defines __str__(), so __prepare_class__() won't overload
-            # a __str__() method from the proxied class.
-            return str(self.__cast())
-
-        def __eq__(self, other):
-            if isinstance(other, Promise):
-                other = other.__cast()
-            return self.__cast() == other
-
-        def __lt__(self, other):
-            if isinstance(other, Promise):
-                other = other.__cast()
-            return self.__cast() < other
-
-        def __hash__(self):
-            return hash(self.__cast())
-
-        def __mod__(self, rhs):
-            if self._delegate_text:
-                return str(self) % rhs
-            return self.__cast() % rhs
-
-        def __add__(self, other):
-            return self.__cast() + other
-
-        def __radd__(self, other):
-            return other + self.__cast()
-
-        def __deepcopy__(self, memo):
-            # Instances of this class are effectively immutable. It's just a
-            # collection of functions. So we don't need to do anything
-            # complicated for copying.
-            memo[id(self)] = self
-            return self
-
-    @wraps(func)
-    def __wrapper__(*args, **kw):
-        # Creates the proxy object, instead of the actual value.
-        return __proxy__(args, kw)
-
-    return __wrapper__
-
-
-def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses):
-    return lazy(func, *resultclasses)(*args, **kwargs)
-
-
-def lazystr(text):
-    """
-    Shortcut for the common case of a lazy callable that returns str.
-    """
-    return lazy(str, str)(text)
-
-
-def keep_lazy(*resultclasses):
-    """
-    A decorator that allows a function to be called with one or more lazy
-    arguments. If none of the args are lazy, the function is evaluated
-    immediately, otherwise a __proxy__ is returned that will evaluate the
-    function when needed.
-    """
-    if not resultclasses:
-        raise TypeError("You must pass at least one argument to keep_lazy().")
-
-    def decorator(func):
-        lazy_func = lazy(func, *resultclasses)
-
-        @wraps(func)
-        def wrapper(*args, **kwargs):
-            if any(isinstance(arg, Promise) for arg in itertools.chain(args, kwargs.values())):
-                return lazy_func(*args, **kwargs)
-            return func(*args, **kwargs)
-        return wrapper
-    return decorator
-
-
-def keep_lazy_text(func):
-    """
-    A decorator for functions that accept lazy arguments and return text.
-    """
-    return keep_lazy(str)(func)
-
-
-empty = object()
-
-
-def new_method_proxy(func):
-    def inner(self, *args):
-        if self._wrapped is empty:
-            self._setup()
-        return func(self._wrapped, *args)
-    return inner
-
-
-class LazyObject:
-    """
-    A wrapper for another class that can be used to delay instantiation of the
-    wrapped class.
-
-    By subclassing, you have the opportunity to intercept and alter the
-    instantiation. If you don't need to do that, use SimpleLazyObject.
-    """
-
-    # Avoid infinite recursion when tracing __init__ (#19456).
-    _wrapped = None
-
-    def __init__(self):
-        # Note: if a subclass overrides __init__(), it will likely need to
-        # override __copy__() and __deepcopy__() as well.
-        self._wrapped = empty
-
-    __getattr__ = new_method_proxy(getattr)
-
-    def __setattr__(self, name, value):
-        if name == "_wrapped":
-            # Assign to __dict__ to avoid infinite __setattr__ loops.
-            self.__dict__["_wrapped"] = value
-        else:
-            if self._wrapped is empty:
-                self._setup()
-            setattr(self._wrapped, name, value)
-
-    def __delattr__(self, name):
-        if name == "_wrapped":
-            raise TypeError("can't delete _wrapped.")
-        if self._wrapped is empty:
-            self._setup()
-        delattr(self._wrapped, name)
-
-    def _setup(self):
-        """
-        Must be implemented by subclasses to initialize the wrapped object.
-        """
-        raise NotImplementedError('subclasses of LazyObject must provide a _setup() method')
-
-    # Because we have messed with __class__ below, we confuse pickle as to what
-    # class we are pickling. We're going to have to initialize the wrapped
-    # object to successfully pickle it, so we might as well just pickle the
-    # wrapped object since they're supposed to act the same way.
-    #
-    # Unfortunately, if we try to simply act like the wrapped object, the ruse
-    # will break down when pickle gets our id(). Thus we end up with pickle
-    # thinking, in effect, that we are a distinct object from the wrapped
-    # object, but with the same __dict__. This can cause problems (see #25389).
-    #
-    # So instead, we define our own __reduce__ method and custom unpickler. We
-    # pickle the wrapped object as the unpickler's argument, so that pickle
-    # will pickle it normally, and then the unpickler simply returns its
-    # argument.
-    def __reduce__(self):
-        if self._wrapped is empty:
-            self._setup()
-        return (unpickle_lazyobject, (self._wrapped,))
-
-    def __copy__(self):
-        if self._wrapped is empty:
-            # If uninitialized, copy the wrapper. Use type(self), not
-            # self.__class__, because the latter is proxied.
-            return type(self)()
-        else:
-            # If initialized, return a copy of the wrapped object.
-            return copy.copy(self._wrapped)
-
-    def __deepcopy__(self, memo):
-        if self._wrapped is empty:
-            # We have to use type(self), not self.__class__, because the
-            # latter is proxied.
-            result = type(self)()
-            memo[id(self)] = result
-            return result
-        return copy.deepcopy(self._wrapped, memo)
-
-    __bytes__ = new_method_proxy(bytes)
-    __str__ = new_method_proxy(str)
-    __bool__ = new_method_proxy(bool)
-
-    # Introspection support
-    __dir__ = new_method_proxy(dir)
-
-    # Need to pretend to be the wrapped class, for the sake of objects that
-    # care about this (especially in equality tests)
-    __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
-    __eq__ = new_method_proxy(operator.eq)
-    __lt__ = new_method_proxy(operator.lt)
-    __gt__ = new_method_proxy(operator.gt)
-    __ne__ = new_method_proxy(operator.ne)
-    __hash__ = new_method_proxy(hash)
-
-    # List/Tuple/Dictionary methods support
-    __getitem__ = new_method_proxy(operator.getitem)
-    __setitem__ = new_method_proxy(operator.setitem)
-    __delitem__ = new_method_proxy(operator.delitem)
-    __iter__ = new_method_proxy(iter)
-    __len__ = new_method_proxy(len)
-    __contains__ = new_method_proxy(operator.contains)
-
-
-def unpickle_lazyobject(wrapped):
-    """
-    Used to unpickle lazy objects. Just return its argument, which will be the
-    wrapped object.
-    """
-    return wrapped
-
-
-class SimpleLazyObject(LazyObject):
-    """
-    A lazy object initialized from any function.
-
-    Designed for compound objects of unknown type. For builtins or objects of
-    known type, use django.utils.functional.lazy.
-    """
-    def __init__(self, func):
-        """
-        Pass in a callable that returns the object to be wrapped.
-
-        If copies are made of the resulting SimpleLazyObject, which can happen
-        in various circumstances within Django, then you must ensure that the
-        callable can be safely run more than once and will return the same
-        value.
-        """
-        self.__dict__['_setupfunc'] = func
-        super().__init__()
-
-    def _setup(self):
-        self._wrapped = self._setupfunc()
-
-    # Return a meaningful representation of the lazy object for debugging
-    # without evaluating the wrapped object.
-    def __repr__(self):
-        if self._wrapped is empty:
-            repr_attr = self._setupfunc
-        else:
-            repr_attr = self._wrapped
-        return '<%s: %r>' % (type(self).__name__, repr_attr)
-
-    def __copy__(self):
-        if self._wrapped is empty:
-            # If uninitialized, copy the wrapper. Use SimpleLazyObject, not
-            # self.__class__, because the latter is proxied.
-            return SimpleLazyObject(self._setupfunc)
-        else:
-            # If initialized, return a copy of the wrapped object.
-            return copy.copy(self._wrapped)
-
-    def __deepcopy__(self, memo):
-        if self._wrapped is empty:
-            # We have to use SimpleLazyObject, not self.__class__, because the
-            # latter is proxied.
-            result = SimpleLazyObject(self._setupfunc)
-            memo[id(self)] = result
-            return result
-        return copy.deepcopy(self._wrapped, memo)
-
-
-def partition(predicate, values):
-    """
-    Split the values into two sets, based on the return value of the function
-    (True/False). e.g.:
-
-        >>> partition(lambda x: x > 3, range(5))
-        [0, 1, 2, 3], [4]
-    """
-    results = ([], [])
-    for item in values:
-        results[predicate(item)].append(item)
-    return results
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia.html b/docs/0.9.5/_modules/evennia.html deleted file mode 100644 index f67d798380..0000000000 --- a/docs/0.9.5/_modules/evennia.html +++ /dev/null @@ -1,543 +0,0 @@ - - - - - - - - evennia — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia

-"""
-Evennia MUD/MUX/MU* creation system
-
-This is the main top-level API for Evennia. You can explore the evennia library
-by accessing evennia.<subpackage> directly. From inside the game you can read
-docs of all object by viewing its `__doc__` string, such as through
-
-    py evennia.ObjectDB.__doc__
-
-For full functionality you should explore this module via a django-
-aware shell. Go to your game directory and use the command
-
-   evennia shell
-
-to launch such a shell (using python or ipython depending on your install).
-See www.evennia.com for full documentation.
-
-"""
-
-# docstring header
-
-DOCSTRING = """
-Evennia MU* creation system.
-
-Online manual and API docs are found at http://www.evennia.com.
-
-Flat-API shortcut names:
-{}
-"""
-
-# Delayed loading of properties
-
-# Typeclasses
-
-DefaultAccount = None
-DefaultGuest = None
-DefaultObject = None
-DefaultCharacter = None
-DefaultRoom = None
-DefaultExit = None
-DefaultChannel = None
-DefaultScript = None
-
-# Database models
-ObjectDB = None
-AccountDB = None
-ScriptDB = None
-ChannelDB = None
-Msg = None
-
-# commands
-Command = None
-CmdSet = None
-default_cmds = None
-syscmdkeys = None
-InterruptCommand = None
-
-# search functions
-search_object = None
-search_script = None
-search_account = None
-search_channel = None
-search_message = None
-search_help = None
-search_tag = None
-
-# create functions
-create_object = None
-create_script = None
-create_account = None
-create_channel = None
-create_message = None
-create_help_entry = None
-
-# utilities
-settings = None
-lockfuncs = None
-inputhandler = None
-logger = None
-gametime = None
-ansi = None
-spawn = None
-managers = None
-contrib = None
-EvMenu = None
-EvTable = None
-EvForm = None
-EvEditor = None
-EvMore = None
-ANSIString = None
-signals = None
-
-# Handlers
-SESSION_HANDLER = None
-TASK_HANDLER = None
-TICKER_HANDLER = None
-MONITOR_HANDLER = None
-CHANNEL_HANDLER = None
-
-# Containers
-GLOBAL_SCRIPTS = None
-OPTION_CLASSES = None
-
-def _create_version():
-    """
-    Helper function for building the version string
-    """
-    import os
-    from subprocess import check_output, CalledProcessError, STDOUT
-
-    version = "Unknown"
-    root = os.path.dirname(os.path.abspath(__file__))
-    try:
-        with open(os.path.join(root, "VERSION.txt"), "r") as f:
-            version = f.read().strip()
-    except IOError as err:
-        print(err)
-    try:
-        rev = (
-            check_output("git rev-parse --short HEAD", shell=True, cwd=root, stderr=STDOUT)
-            .strip()
-            .decode()
-        )
-        version = "%s (rev %s)" % (version, rev)
-    except (IOError, CalledProcessError, OSError):
-        # ignore if we cannot get to git
-        pass
-    return version
-
-
-__version__ = _create_version()
-del _create_version
-
-
-def _init():
-    """
-    This function is called automatically by the launcher only after
-    Evennia has fully initialized all its models. It sets up the API
-    in a safe environment where all models are available already.
-    """
-    global DefaultAccount, DefaultObject, DefaultGuest, DefaultCharacter
-    global DefaultRoom, DefaultExit, DefaultChannel, DefaultScript
-    global ObjectDB, AccountDB, ScriptDB, ChannelDB, Msg
-    global Command, CmdSet, default_cmds, syscmdkeys, InterruptCommand
-    global search_object, search_script, search_account, search_channel
-    global search_help, search_tag, search_message
-    global create_object, create_script, create_account, create_channel
-    global create_message, create_help_entry
-    global signals
-    global settings, lockfuncs, logger, utils, gametime, ansi, spawn, managers
-    global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER
-    global CHANNEL_HANDLER, TASK_HANDLER
-    global GLOBAL_SCRIPTS, OPTION_CLASSES
-    global EvMenu, EvTable, EvForm, EvMore, EvEditor
-    global ANSIString
-
-    # Parent typeclasses
-    from .accounts.accounts import DefaultAccount
-    from .accounts.accounts import DefaultGuest
-    from .objects.objects import DefaultObject
-    from .objects.objects import DefaultCharacter
-    from .objects.objects import DefaultRoom
-    from .objects.objects import DefaultExit
-    from .comms.comms import DefaultChannel
-    from .scripts.scripts import DefaultScript
-
-    # Database models
-    from .objects.models import ObjectDB
-    from .accounts.models import AccountDB
-    from .scripts.models import ScriptDB
-    from .comms.models import ChannelDB
-    from .comms.models import Msg
-
-    # commands
-    from .commands.command import Command, InterruptCommand
-    from .commands.cmdset import CmdSet
-
-    # search functions
-    from .utils.search import search_object
-    from .utils.search import search_script
-    from .utils.search import search_account
-    from .utils.search import search_message
-    from .utils.search import search_channel
-    from .utils.search import search_help
-    from .utils.search import search_tag
-
-    # create functions
-    from .utils.create import create_object
-    from .utils.create import create_script
-    from .utils.create import create_account
-    from .utils.create import create_channel
-    from .utils.create import create_message
-    from .utils.create import create_help_entry
-
-    # utilities
-    from django.conf import settings
-    from .locks import lockfuncs
-    from .utils import logger
-    from .utils import gametime
-    from .utils import ansi
-    from .prototypes.spawner import spawn
-    from . import contrib
-    from .utils.evmenu import EvMenu
-    from .utils.evtable import EvTable
-    from .utils.evmore import EvMore
-    from .utils.evform import EvForm
-    from .utils.eveditor import EvEditor
-    from .utils.ansi import ANSIString
-    from .server import signals
-
-    # handlers
-    from .scripts.tickerhandler import TICKER_HANDLER
-    from .scripts.taskhandler import TASK_HANDLER
-    from .server.sessionhandler import SESSION_HANDLER
-    from .comms.channelhandler import CHANNEL_HANDLER
-    from .scripts.monitorhandler import MONITOR_HANDLER
-
-    # containers
-    from .utils.containers import GLOBAL_SCRIPTS
-    from .utils.containers import OPTION_CLASSES
-
-    # API containers
-
-    class _EvContainer(object):
-        """
-        Parent for other containers
-
-        """
-
-        def _help(self):
-            "Returns list of contents"
-            names = [name for name in self.__class__.__dict__ if not name.startswith("_")]
-            names += [name for name in self.__dict__ if not name.startswith("_")]
-            print(self.__doc__ + "-" * 60 + "\n" + ", ".join(names))
-
-        help = property(_help)
-
-    class DBmanagers(_EvContainer):
-        """
-        Links to instantiated Django database managers. These are used
-        to perform more advanced custom database queries than the standard
-        search functions allow.
-
-        helpentries - HelpEntry.objects
-        accounts - AccountDB.objects
-        scripts - ScriptDB.objects
-        msgs    - Msg.objects
-        channels - Channel.objects
-        objects - ObjectDB.objects
-        serverconfigs - ServerConfig.objects
-        tags - Tags.objects
-        attributes - Attributes.objects
-
-        """
-
-        from .help.models import HelpEntry
-        from .accounts.models import AccountDB
-        from .scripts.models import ScriptDB
-        from .comms.models import Msg, ChannelDB
-        from .objects.models import ObjectDB
-        from .server.models import ServerConfig
-        from .typeclasses.attributes import Attribute
-        from .typeclasses.tags import Tag
-
-        # create container's properties
-        helpentries = HelpEntry.objects
-        accounts = AccountDB.objects
-        scripts = ScriptDB.objects
-        msgs = Msg.objects
-        channels = ChannelDB.objects
-        objects = ObjectDB.objects
-        serverconfigs = ServerConfig.objects
-        attributes = Attribute.objects
-        tags = Tag.objects
-        # remove these so they are not visible as properties
-        del HelpEntry, AccountDB, ScriptDB, Msg, ChannelDB
-        # del ExternalChannelConnection
-        del ObjectDB, ServerConfig, Tag, Attribute
-
-    managers = DBmanagers()
-    del DBmanagers
-
-    class DefaultCmds(_EvContainer):
-        """
-        This container holds direct shortcuts to all default commands in Evennia.
-
-        To access in code, do 'from evennia import default_cmds' then
-        access the properties on the imported default_cmds object.
-
-        """
-
-        from .commands.default.cmdset_character import CharacterCmdSet
-        from .commands.default.cmdset_account import AccountCmdSet
-        from .commands.default.cmdset_unloggedin import UnloggedinCmdSet
-        from .commands.default.cmdset_session import SessionCmdSet
-        from .commands.default.muxcommand import MuxCommand, MuxAccountCommand
-
-        def __init__(self):
-            "populate the object with commands"
-
-            def add_cmds(module):
-                "helper method for populating this object with cmds"
-                from evennia.utils import utils
-
-                cmdlist = utils.variable_from_module(module, module.__all__)
-                self.__dict__.update(dict([(c.__name__, c) for c in cmdlist]))
-
-            from .commands.default import (
-                admin,
-                batchprocess,
-                building,
-                comms,
-                general,
-                account,
-                help,
-                system,
-                unloggedin,
-            )
-
-            add_cmds(admin)
-            add_cmds(building)
-            add_cmds(batchprocess)
-            add_cmds(building)
-            add_cmds(comms)
-            add_cmds(general)
-            add_cmds(account)
-            add_cmds(help)
-            add_cmds(system)
-            add_cmds(unloggedin)
-
-    default_cmds = DefaultCmds()
-    del DefaultCmds
-
-    class SystemCmds(_EvContainer):
-        """
-        Creating commands with keys set to these constants will make
-        them system commands called as a replacement by the parser when
-        special situations occur. If not defined, the hard-coded
-        responses in the server are used.
-
-        CMD_NOINPUT - no input was given on command line
-        CMD_NOMATCH - no valid command key was found
-        CMD_MULTIMATCH - multiple command matches were found
-        CMD_CHANNEL - the command name is a channel name
-        CMD_LOGINSTART - this command will be called as the very
-                         first command when an account connects to
-                         the server.
-
-        To access in code, do 'from evennia import syscmdkeys' then
-        access the properties on the imported syscmdkeys object.
-
-        """
-
-        from .commands import cmdhandler
-
-        CMD_NOINPUT = cmdhandler.CMD_NOINPUT
-        CMD_NOMATCH = cmdhandler.CMD_NOMATCH
-        CMD_MULTIMATCH = cmdhandler.CMD_MULTIMATCH
-        CMD_CHANNEL = cmdhandler.CMD_CHANNEL
-        CMD_LOGINSTART = cmdhandler.CMD_LOGINSTART
-        del cmdhandler
-
-    syscmdkeys = SystemCmds()
-    del SystemCmds
-    del _EvContainer
-
-    # delayed starts - important so as to not back-access evennia before it has
-    # finished initializing
-    GLOBAL_SCRIPTS.start()
-    from .prototypes import prototypes
-    prototypes.load_module_prototypes()
-    del prototypes
-
-
-
[docs]def set_trace(term_size=(140, 80), debugger="auto"): - """ - Helper function for running a debugger inside the Evennia event loop. - - Args: - term_size (tuple, optional): Only used for Pudb and defines the size of the terminal - (width, height) in number of characters. - debugger (str, optional): One of 'auto', 'pdb' or 'pudb'. Pdb is the standard debugger. Pudb - is an external package with a different, more 'graphical', ncurses-based UI. With - 'auto', will use pudb if possible, otherwise fall back to pdb. Pudb is available through - `pip install pudb`. - - Notes: - To use: - - 1) add this to a line to act as a breakpoint for entering the debugger: - - from evennia import set_trace; set_trace() - - 2) restart evennia in interactive mode - - evennia istart - - 3) debugger will appear in the interactive terminal when breakpoint is reached. Exit - with 'q', remove the break line and restart server when finished. - - """ - import sys - - dbg = None - - if debugger in ("auto", "pudb"): - try: - from pudb import debugger - - dbg = debugger.Debugger(stdout=sys.__stdout__, term_size=term_size) - except ImportError: - if debugger == "pudb": - raise - pass - - if not dbg: - import pdb - - dbg = pdb.Pdb(stdout=sys.__stdout__) - - try: - # Start debugger, forcing it up one stack frame (otherwise `set_trace` - # will start debugger this point, not the actual code location) - dbg.set_trace(sys._getframe().f_back) - except Exception: - # Stopped at breakpoint. Press 'n' to continue into the code. - dbg.set_trace()
- - -# initialize the doc string -global __doc__ -__doc__ = DOCSTRING.format( - "\n- " - + "\n- ".join( - f"evennia.{key}" - for key in sorted(globals()) - if not key.startswith("_") and key not in ("DOCSTRING",) - ) -) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/accounts/accounts.html b/docs/0.9.5/_modules/evennia/accounts/accounts.html deleted file mode 100644 index 813c8439bb..0000000000 --- a/docs/0.9.5/_modules/evennia/accounts/accounts.html +++ /dev/null @@ -1,1790 +0,0 @@ - - - - - - - - evennia.accounts.accounts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.accounts.accounts

-"""
-Typeclass for Account objects
-
-Note that this object is primarily intended to
-store OOC information, not game info! This
-object represents the actual user (not their
-character) and has NO actual presence in the
-game world (this is handled by the associated
-character object, so you should customize that
-instead for most things).
-
-"""
-import re
-import time
-from django.conf import settings
-from django.contrib.auth import authenticate, password_validation
-from django.core.exceptions import ImproperlyConfigured, ValidationError
-from django.utils import timezone
-from django.utils.module_loading import import_string
-from evennia.typeclasses.models import TypeclassBase
-from evennia.accounts.manager import AccountManager
-from evennia.accounts.models import AccountDB
-from evennia.objects.models import ObjectDB
-from evennia.comms.models import ChannelDB
-from evennia.server.models import ServerConfig
-from evennia.server.throttle import Throttle
-from evennia.utils import class_from_module, create, logger
-from evennia.utils.utils import lazy_property, to_str, make_iter, is_iter, variable_from_module
-from evennia.server.signals import (
-    SIGNAL_ACCOUNT_POST_CREATE,
-    SIGNAL_OBJECT_POST_PUPPET,
-    SIGNAL_OBJECT_POST_UNPUPPET,
-)
-from evennia.typeclasses.attributes import NickHandler
-from evennia.scripts.scripthandler import ScriptHandler
-from evennia.commands.cmdsethandler import CmdSetHandler
-from evennia.utils.optionhandler import OptionHandler
-
-from django.utils.translation import gettext as _
-from random import getrandbits
-
-__all__ = ("DefaultAccount", "DefaultGuest")
-
-_SESSIONS = None
-
-_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
-_MULTISESSION_MODE = settings.MULTISESSION_MODE
-_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
-_CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT
-_MUDINFO_CHANNEL = None
-_CMDHANDLER = None
-
-# Create throttles for too many account-creations and login attempts
-CREATION_THROTTLE = Throttle(
-    limit=settings.CREATION_THROTTLE_LIMIT, timeout=settings.CREATION_THROTTLE_TIMEOUT
-)
-LOGIN_THROTTLE = Throttle(
-    limit=settings.LOGIN_THROTTLE_LIMIT, timeout=settings.LOGIN_THROTTLE_TIMEOUT
-)
-
-
-class AccountSessionHandler(object):
-    """
-    Manages the session(s) attached to an account.
-
-    """
-
-    def __init__(self, account):
-        """
-        Initializes the handler.
-
-        Args:
-            account (Account): The Account on which this handler is defined.
-
-        """
-        self.account = account
-
-    def get(self, sessid=None):
-        """
-        Get the sessions linked to this object.
-
-        Args:
-            sessid (int, optional): Specify a given session by
-                session id.
-
-        Returns:
-            sessions (list): A list of Session objects. If `sessid`
-                is given, this is a list with one (or zero) elements.
-
-        """
-        global _SESSIONS
-        if not _SESSIONS:
-            from evennia.server.sessionhandler import SESSIONS as _SESSIONS
-        if sessid:
-            return make_iter(_SESSIONS.session_from_account(self.account, sessid))
-        else:
-            return _SESSIONS.sessions_from_account(self.account)
-
-    def all(self):
-        """
-        Alias to get(), returning all sessions.
-
-        Returns:
-            sessions (list): All sessions.
-
-        """
-        return self.get()
-
-    def count(self):
-        """
-        Get amount of sessions connected.
-
-        Returns:
-            sesslen (int): Number of sessions handled.
-
-        """
-        return len(self.get())
-
-
-
[docs]class DefaultAccount(AccountDB, metaclass=TypeclassBase): - """ - This is the base Typeclass for all Accounts. Accounts represent - the person playing the game and tracks account info, password - etc. They are OOC entities without presence in-game. An Account - can connect to a Character Object in order to "enter" the - game. - - Account Typeclass API: - - * Available properties (only available on initiated typeclass objects) - - - key (string) - name of account - - name (string)- wrapper for user.username - - aliases (list of strings) - aliases to the object. Will be saved to - database as AliasDB entries but returned as strings. - - dbref (int, read-only) - unique #id-number. Also "id" can be used. - - date_created (string) - time stamp of object creation - - permissions (list of strings) - list of permission strings - - user (User, read-only) - django User authorization object - - obj (Object) - game object controlled by account. 'character' can also - be used. - - sessions (list of Sessions) - sessions connected to this account - - is_superuser (bool, read-only) - if the connected user is a superuser - - * Handlers - - - locks - lock-handler: use locks.add() to add new lock strings - - db - attribute-handler: store/retrieve database attributes on this - self.db.myattr=val, val=self.db.myattr - - ndb - non-persistent attribute handler: same as db but does not - create a database entry when storing data - - scripts - script-handler. Add new scripts to object with scripts.add() - - cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object - - nicks - nick-handler. New nicks with nicks.add(). - - * Helper methods - - - msg(text=None, from_obj=None, session=None, options=None, **kwargs) - - execute_cmd(raw_string) - - search(ostring, global_search=False, attribute_name=None, - use_nicks=False, location=None, - ignore_errors=False, account=False) - - is_typeclass(typeclass, exact=False) - - swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) - - access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False) - - check_permstring(permstring) - - * Hook methods - - basetype_setup() - at_account_creation() - - > note that the following hooks are also found on Objects and are - usually handled on the character level: - - - at_init() - - at_access() - - at_cmdset_get(**kwargs) - - at_first_login() - - at_post_login(session=None) - - at_disconnect() - - at_message_receive() - - at_message_send() - - at_server_reload() - - at_server_shutdown() - - """ - - objects = AccountManager() - - # properties -
[docs] @lazy_property - def cmdset(self): - return CmdSetHandler(self, True)
- -
[docs] @lazy_property - def scripts(self): - return ScriptHandler(self)
- -
[docs] @lazy_property - def nicks(self): - return NickHandler(self)
- -
[docs] @lazy_property - def sessions(self): - return AccountSessionHandler(self)
- -
[docs] @lazy_property - def options(self): - return OptionHandler( - self, - options_dict=settings.OPTIONS_ACCOUNT_DEFAULT, - savefunc=self.attributes.add, - loadfunc=self.attributes.get, - save_kwargs={"category": "option"}, - load_kwargs={"category": "option"}, - )
- - # Do not make this a lazy property; the web UI will not refresh it! - @property - def characters(self): - # Get playable characters list - objs = self.db._playable_characters or [] - - # Rebuild the list if legacy code left null values after deletion - try: - if None in objs: - objs = [x for x in self.db._playable_characters if x] - self.db._playable_characters = objs - except Exception as e: - logger.log_trace(e) - logger.log_err(e) - - return objs - - # session-related methods - -
[docs] def disconnect_session_from_account(self, session, reason=None): - """ - Access method for disconnecting a given session from the - account (connection happens automatically in the - sessionhandler) - - Args: - session (Session): Session to disconnect. - reason (str, optional): Eventual reason for the disconnect. - - """ - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - _SESSIONS.disconnect(session, reason)
- - # puppeting operations - -
[docs] def puppet_object(self, session, obj): - """ - Use the given session to control (puppet) the given object (usually - a Character type). - - Args: - session (Session): session to use for puppeting - obj (Object): the object to start puppeting - - Raises: - RuntimeError: If puppeting is not possible, the - `exception.msg` will contain the reason. - - - """ - # safety checks - if not obj: - raise RuntimeError("Object not found") - if not session: - raise RuntimeError("Session not found") - if self.get_puppet(session) == obj: - # already puppeting this object - self.msg(_("You are already puppeting this object.")) - return - if not obj.access(self, "puppet"): - # no access - self.msg(_("You don't have permission to puppet '{key}'.").format(key=obj.key)) - return - if obj.account: - # object already puppeted - if obj.account == self: - if obj.sessions.count(): - # we may take over another of our sessions - # output messages to the affected sessions - if _MULTISESSION_MODE in (1, 3): - txt1 = f"Sharing |c{obj.name}|n with another of your sessions." - txt2 = f"|c{obj.name}|n|G is now shared from another of your sessions.|n" - self.msg(txt1, session=session) - self.msg(txt2, session=obj.sessions.all()) - else: - txt1 = f"Taking over |c{obj.name}|n from another of your sessions." - txt2 = f"|c{obj.name}|n|R is now acted from another of your sessions.|n" - self.msg(_(txt1), session=session) - self.msg(_(txt2), session=obj.sessions.all()) - self.unpuppet_object(obj.sessions.get()) - elif obj.account.is_connected: - # controlled by another account - self.msg(_("|c{key}|R is already puppeted by another Account.").format(key=obj.key)) - return - - # do the puppeting - if session.puppet: - # cleanly unpuppet eventual previous object puppeted by this session - self.unpuppet_object(session) - # if we get to this point the character is ready to puppet or it - # was left with a lingering account/session reference from an unclean - # server kill or similar - - obj.at_pre_puppet(self, session=session) - - # do the connection - obj.sessions.add(session) - obj.account = self - session.puid = obj.id - session.puppet = obj - # validate/start persistent scripts on object - obj.scripts.validate() - - # re-cache locks to make sure superuser bypass is updated - obj.locks.cache_lock_bypass(obj) - # final hook - obj.at_post_puppet() - SIGNAL_OBJECT_POST_PUPPET.send(sender=obj, account=self, session=session)
- -
[docs] def unpuppet_object(self, session): - """ - Disengage control over an object. - - Args: - session (Session or list): The session or a list of - sessions to disengage from their puppets. - - Raises: - RuntimeError With message about error. - - """ - for session in make_iter(session): - obj = session.puppet - if obj: - # do the disconnect, but only if we are the last session to puppet - obj.at_pre_unpuppet() - obj.sessions.remove(session) - if not obj.sessions.count(): - del obj.account - obj.at_post_unpuppet(self, session=session) - SIGNAL_OBJECT_POST_UNPUPPET.send(sender=obj, session=session, account=self) - # Just to be sure we're always clear. - session.puppet = None - session.puid = None
- -
[docs] def unpuppet_all(self): - """ - Disconnect all puppets. This is called by server before a - reset/shutdown. - """ - self.unpuppet_object(self.sessions.all())
- -
[docs] def get_puppet(self, session): - """ - Get an object puppeted by this session through this account. This is - the main method for retrieving the puppeted object from the - account's end. - - Args: - session (Session): Find puppeted object based on this session - - Returns: - puppet (Object): The matching puppeted object, if any. - - """ - return session.puppet if session else None
- -
[docs] def get_all_puppets(self): - """ - Get all currently puppeted objects. - - Returns: - puppets (list): All puppeted objects currently controlled - by this Account. - - """ - return list(set(session.puppet for session in self.sessions.all() if session.puppet))
- - def __get_single_puppet(self): - """ - This is a legacy convenience link for use with `MULTISESSION_MODE`. - - Returns: - puppets (Object or list): Users of `MULTISESSION_MODE` 0 or 1 will - always get the first puppet back. Users of higher `MULTISESSION_MODE`s will - get a list of all puppeted objects. - - """ - puppets = self.get_all_puppets() - if _MULTISESSION_MODE in (0, 1): - return puppets and puppets[0] or None - return puppets - - character = property(__get_single_puppet) - puppet = property(__get_single_puppet) - - # utility methods -
[docs] @classmethod - def is_banned(cls, **kwargs): - """ - Checks if a given username or IP is banned. - - Keyword Args: - ip (str, optional): IP address. - username (str, optional): Username. - - Returns: - is_banned (bool): Whether either is banned or not. - - """ - - ip = kwargs.get("ip", "").strip() - username = kwargs.get("username", "").lower().strip() - - # Check IP and/or name bans - bans = ServerConfig.objects.conf("server_bans") - if bans and ( - any(tup[0] == username for tup in bans if username) - or any(tup[2].match(ip) for tup in bans if ip and tup[2]) - ): - return True - - return False
- -
[docs] @classmethod - def get_username_validators( - cls, validator_config=getattr(settings, "AUTH_USERNAME_VALIDATORS", []) - ): - """ - Retrieves and instantiates validators for usernames. - - Args: - validator_config (list): List of dicts comprising the battery of - validators to apply to a username. - - Returns: - validators (list): List of instantiated Validator objects. - """ - - objs = [] - for validator in validator_config: - try: - klass = import_string(validator["NAME"]) - except ImportError: - msg = ( - f"The module in NAME could not be imported: {validator['NAME']}. " - "Check your AUTH_USERNAME_VALIDATORS setting." - ) - raise ImproperlyConfigured(msg) - objs.append(klass(**validator.get("OPTIONS", {}))) - return objs
- -
[docs] @classmethod - def authenticate(cls, username, password, ip="", **kwargs): - """ - Checks the given username/password against the database to see if the - credentials are valid. - - Note that this simply checks credentials and returns a valid reference - to the user-- it does not log them in! - - To finish the job: - After calling this from a Command, associate the account with a Session: - - session.sessionhandler.login(session, account) - - ...or after calling this from a View, associate it with an HttpRequest: - - django.contrib.auth.login(account, request) - - Args: - username (str): Username of account - password (str): Password of account - ip (str, optional): IP address of client - - Keyword Args: - session (Session, optional): Session requesting authentication - - Returns: - account (DefaultAccount, None): Account whose credentials were - provided if not banned. - errors (list): Error messages of any failures. - - """ - errors = [] - if ip: - ip = str(ip) - - # See if authentication is currently being throttled - if ip and LOGIN_THROTTLE.check(ip): - errors.append(_("Too many login failures; please try again in a few minutes.")) - - # With throttle active, do not log continued hits-- it is a - # waste of storage and can be abused to make your logs harder to - # read and/or fill up your disk. - return None, errors - - # Check IP and/or name bans - banned = cls.is_banned(username=username, ip=ip) - if banned: - # this is a banned IP or name! - errors.append( - _( - "|rYou have been banned and cannot continue from here." - "\nIf you feel this ban is in error, please email an admin.|x" - ) - ) - logger.log_sec(f"Authentication Denied (Banned): {username} (IP: {ip}).") - LOGIN_THROTTLE.update(ip, "Too many sightings of banned artifact.") - return None, errors - - # Authenticate and get Account object - account = authenticate(username=username, password=password) - if not account: - # User-facing message - errors.append(_("Username and/or password is incorrect.")) - - # Log auth failures while throttle is inactive - logger.log_sec(f"Authentication Failure: {username} (IP: {ip}).") - - # Update throttle - if ip: - LOGIN_THROTTLE.update(ip, "Too many authentication failures.") - - # Try to call post-failure hook - session = kwargs.get("session", None) - if session: - account = AccountDB.objects.get_account_from_name(username) - if account: - account.at_failed_login(session) - - return None, errors - - # Account successfully authenticated - logger.log_sec(f"Authentication Success: {account} (IP: {ip}).") - return account, errors
- -
[docs] @classmethod - def normalize_username(cls, username): - """ - Django: Applies NFKC Unicode normalization to usernames so that visually - identical characters with different Unicode code points are considered - identical. - - (This deals with the Turkish "i" problem and similar - annoyances. Only relevant if you go out of your way to allow Unicode - usernames though-- Evennia accepts ASCII by default.) - - In this case we're simply piggybacking on this feature to apply - additional normalization per Evennia's standards. - """ - username = super(DefaultAccount, cls).normalize_username(username) - - # strip excessive spaces in accountname - username = re.sub(r"\s+", " ", username).strip() - - return username
- -
[docs] @classmethod - def validate_username(cls, username): - """ - Checks the given username against the username validator associated with - Account objects, and also checks the database to make sure it is unique. - - Args: - username (str): Username to validate - - Returns: - valid (bool): Whether or not the password passed validation - errors (list): Error messages of any failures - - """ - valid = [] - errors = [] - - # Make sure we're at least using the default validator - validators = cls.get_username_validators() - if not validators: - validators = [cls.username_validator] - - # Try username against all enabled validators - for validator in validators: - try: - valid.append(not validator(username)) - except ValidationError as e: - valid.append(False) - errors.extend(e.messages) - - # Disqualify if any check failed - if False in valid: - valid = False - else: - valid = True - - return valid, errors
- -
[docs] @classmethod - def validate_password(cls, password, account=None): - """ - Checks the given password against the list of Django validators enabled - in the server.conf file. - - Args: - password (str): Password to validate - - Keyword Args: - account (DefaultAccount, optional): Account object to validate the - password for. Optional, but Django includes some validators to - do things like making sure users aren't setting passwords to the - same value as their username. If left blank, these user-specific - checks are skipped. - - Returns: - valid (bool): Whether or not the password passed validation - error (ValidationError, None): Any validation error(s) raised. Multiple - errors can be nested within a single object. - - """ - valid = False - error = None - - # Validation returns None on success; invert it and return a more sensible bool - try: - valid = not password_validation.validate_password(password, user=account) - except ValidationError as e: - error = e - - return valid, error
- -
[docs] def set_password(self, password, **kwargs): - """ - Applies the given password to the account. Logs and triggers the `at_password_change` hook. - - Args: - password (str): Password to set. - - Notes: - This is called by Django also when logging in; it should not be mixed up with validation, since that - would mean old passwords in the database (pre validation checks) could get invalidated. - - """ - super(DefaultAccount, self).set_password(password) - logger.log_sec(f"Password successfully changed for {self}.") - self.at_password_change()
- -
[docs] def create_character(self, *args, **kwargs): - """ - Create a character linked to this account. - - Args: - key (str, optional): If not given, use the same name as the account. - typeclass (str, optional): Typeclass to use for this character. If - not given, use settings.BASE_CHARACTER_TYPECLASS. - permissions (list, optional): If not given, use the account's permissions. - ip (str, optiona): The client IP creating this character. Will fall back to the - one stored for the account if not given. - kwargs (any): Other kwargs will be used in the create_call. - Returns: - Object: A new character of the `character_typeclass` type. None on an error. - list or None: A list of errors, or None. - - """ - # parse inputs - character_key = kwargs.pop("key", self.key) - character_ip = kwargs.pop("ip", self.db.creator_ip) - character_permissions = kwargs.pop("permissions", self.permissions) - - # Load the appropriate Character class - character_typeclass = kwargs.pop("typeclass", None) - character_typeclass = ( - character_typeclass if character_typeclass else settings.BASE_CHARACTER_TYPECLASS - ) - Character = class_from_module(character_typeclass) - - if "location" not in kwargs: - kwargs["location"] = ObjectDB.objects.get_id(settings.START_LOCATION) - - # Create the character - character, errs = Character.create( - character_key, - self, - ip=character_ip, - typeclass=character_typeclass, - permissions=character_permissions, - **kwargs, - ) - if character: - # Update playable character list - if character not in self.characters: - self.db._playable_characters.append(character) - - # We need to set this to have @ic auto-connect to this character - self.db._last_puppet = character - return character, errs
- -
[docs] @classmethod - def create(cls, *args, **kwargs): - """ - Creates an Account (or Account/Character pair for MULTISESSION_MODE<2) - with default (or overridden) permissions and having joined them to the - appropriate default channels. - - Keyword Args: - username (str): Username of Account owner - password (str): Password of Account owner - email (str, optional): Email address of Account owner - ip (str, optional): IP address of requesting connection - guest (bool, optional): Whether or not this is to be a Guest account - - permissions (str, optional): Default permissions for the Account - typeclass (str, optional): Typeclass to use for new Account - character_typeclass (str, optional): Typeclass to use for new char - when applicable. - - Returns: - account (Account): Account if successfully created; None if not - errors (list): List of error messages in string form - - """ - - account = None - errors = [] - - username = kwargs.get("username") - password = kwargs.get("password") - email = kwargs.get("email", "").strip() - guest = kwargs.get("guest", False) - - permissions = kwargs.get("permissions", settings.PERMISSION_ACCOUNT_DEFAULT) - typeclass = kwargs.get("typeclass", cls) - - ip = kwargs.get("ip", "") - if ip and CREATION_THROTTLE.check(ip): - errors.append( - _("You are creating too many accounts. Please log into an existing account.") - ) - return None, errors - - # Normalize username - username = cls.normalize_username(username) - - # Validate username - if not guest: - valid, errs = cls.validate_username(username) - if not valid: - # this echoes the restrictions made by django's auth - # module (except not allowing spaces, for convenience of - # logging in). - errors.extend(errs) - return None, errors - - # Validate password - # Have to create a dummy Account object to check username similarity - valid, errs = cls.validate_password(password, account=cls(username=username)) - if not valid: - errors.extend(errs) - return None, errors - - # Check IP and/or name bans - banned = cls.is_banned(username=username, ip=ip) - if banned: - # this is a banned IP or name! - string = _( - "|rYou have been banned and cannot continue from here." - "\nIf you feel this ban is in error, please email an admin.|x" - ) - errors.append(string) - return None, errors - - # everything's ok. Create the new account. - try: - try: - account = create.create_account( - username, email, password, permissions=permissions, typeclass=typeclass - ) - logger.log_sec(f"Account Created: {account} (IP: {ip}).") - - except Exception as e: - errors.append( - _( - "There was an error creating the Account. If this problem persists, contact an admin." - ) - ) - logger.log_trace() - return None, errors - - # This needs to be set so the engine knows this account is - # logging in for the first time. (so it knows to call the right - # hooks during login later) - account.db.FIRST_LOGIN = True - - # Record IP address of creation, if available - if ip: - account.db.creator_ip = ip - - # join the new account to the public channel - pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) - if not pchannel or not pchannel.connect(account): - string = f"New account '{account.key}' could not connect to public channel!" - errors.append(string) - logger.log_err(string) - - if account and settings.MULTISESSION_MODE < 2: - # Auto-create a character to go with this account - - character, errs = account.create_character( - typeclass=kwargs.get("character_typeclass") - ) - if errs: - errors.extend(errs) - - except Exception: - # We are in the middle between logged in and -not, so we have - # to handle tracebacks ourselves at this point. If we don't, - # we won't see any errors at all. - errors.append(_("An error occurred. Please e-mail an admin if the problem persists.")) - logger.log_trace() - - # Update the throttle to indicate a new account was created from this IP - if ip and not guest: - CREATION_THROTTLE.update(ip, "Too many accounts being created.") - SIGNAL_ACCOUNT_POST_CREATE.send(sender=account, ip=ip) - return account, errors
- -
[docs] def delete(self, *args, **kwargs): - """ - Deletes the account permanently. - - Notes: - `*args` and `**kwargs` are passed on to the base delete - mechanism (these are usually not used). - - """ - for session in self.sessions.all(): - # unpuppeting all objects and disconnecting the user, if any - # sessions remain (should usually be handled from the - # deleting command) - try: - self.unpuppet_object(session) - except RuntimeError: - # no puppet to disconnect from - pass - session.sessionhandler.disconnect(session, reason=_("Account being deleted.")) - self.scripts.stop() - self.attributes.clear() - self.nicks.clear() - self.aliases.clear() - super().delete(*args, **kwargs)
- - # methods inherited from database model - -
[docs] def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): - """ - Evennia -> User - This is the main route for sending data back to the user from the - server. - - Args: - text (str or tuple, optional): The message to send. This - is treated internally like any send-command, so its - value can be a tuple if sending multiple arguments to - the `text` oob command. - from_obj (Object or Account or list, optional): Object sending. If given, its - at_msg_send() hook will be called. If iterable, call on all entities. - session (Session or list, optional): Session object or a list of - Sessions to receive this send. If given, overrules the - default send behavior for the current - MULTISESSION_MODE. - options (list): Protocol-specific options. Passed on to the protocol. - Keyword Args: - any (dict): All other keywords are passed on to the protocol. - - """ - if from_obj: - # call hook - for obj in make_iter(from_obj): - try: - obj.at_msg_send(text=text, to_obj=self, **kwargs) - except Exception: - # this may not be assigned. - logger.log_trace() - try: - if not self.at_msg_receive(text=text, **kwargs): - # abort message to this account - return - except Exception: - # this may not be assigned. - pass - - kwargs["options"] = options - - if text is not None: - if not (isinstance(text, str) or isinstance(text, tuple)): - # sanitize text before sending across the wire - try: - text = to_str(text) - except Exception: - text = repr(text) - kwargs["text"] = text - - # session relay - sessions = make_iter(session) if session else self.sessions.all() - for session in sessions: - session.data_out(**kwargs)
- -
[docs] def execute_cmd(self, raw_string, session=None, **kwargs): - """ - Do something as this account. This method is never called normally, - but only when the account object itself is supposed to execute the - command. It takes account nicks into account, but not nicks of - eventual puppets. - - Args: - raw_string (str): Raw command input coming from the command line. - session (Session, optional): The session to be responsible - for the command-send - - Keyword Args: - kwargs (any): Other keyword arguments will be added to the - found command object instance as variables before it - executes. This is unused by default Evennia but may be - used to set flags and change operating paramaters for - commands at run-time. - - """ - # break circular import issues - global _CMDHANDLER - if not _CMDHANDLER: - from evennia.commands.cmdhandler import cmdhandler as _CMDHANDLER - raw_string = self.nicks.nickreplace( - raw_string, categories=("inputline", "channel"), include_account=False - ) - if not session and _MULTISESSION_MODE in (0, 1): - # for these modes we use the first/only session - sessions = self.sessions.get() - session = sessions[0] if sessions else None - - return _CMDHANDLER( - self, raw_string, callertype="account", session=session, **kwargs - )
- -
[docs] def search( - self, - searchdata, - return_puppet=False, - search_object=False, - typeclass=None, - nofound_string=None, - multimatch_string=None, - use_nicks=True, - quiet=False, - **kwargs, - ): - """ - This is similar to `DefaultObject.search` but defaults to searching - for Accounts only. - - Args: - searchdata (str or int): Search criterion, the Account's - key or dbref to search for. - return_puppet (bool, optional): Instructs the method to - return matches as the object the Account controls rather - than the Account itself (or None) if nothing is puppeted). - search_object (bool, optional): Search for Objects instead of - Accounts. This is used by e.g. the @examine command when - wanting to examine Objects while OOC. - typeclass (Account typeclass, optional): Limit the search - only to this particular typeclass. This can be used to - limit to specific account typeclasses or to limit the search - to a particular Object typeclass if `search_object` is True. - nofound_string (str, optional): A one-time error message - to echo if `searchdata` leads to no matches. If not given, - will fall back to the default handler. - multimatch_string (str, optional): A one-time error - message to echo if `searchdata` leads to multiple matches. - If not given, will fall back to the default handler. - use_nicks (bool, optional): Use account-level nick replacement. - quiet (bool, optional): If set, will not show any error to the user, - and will also lead to returning a list of matches. - - Return: - match (Account, Object or None): A single Account or Object match. - list: If `quiet=True` this is a list of 0, 1 or more Account or Object matches. - - Notes: - Extra keywords are ignored, but are allowed in call in - order to make API more consistent with - objects.objects.DefaultObject.search. - - """ - # handle me, self and *me, *self - if isinstance(searchdata, str): - # handle wrapping of common terms - if searchdata.lower() in ("me", "*me", "self", "*self"): - return self - searchdata = self.nicks.nickreplace( - searchdata, categories=("account",), include_account=False - ) - if search_object: - matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass) - else: - matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass) - - if quiet: - matches = list(matches) - if return_puppet: - matches = [match.puppet for match in matches] - else: - matches = _AT_SEARCH_RESULT( - matches, - self, - query=searchdata, - nofound_string=nofound_string, - multimatch_string=multimatch_string, - ) - if matches and return_puppet: - try: - matches = matches.puppet - except AttributeError: - return None - return matches
- -
[docs] def access( - self, accessing_obj, access_type="read", default=False, no_superuser_bypass=False, **kwargs - ): - """ - Determines if another object has permission to access this - object in whatever way. - - Args: - accessing_obj (Object): Object trying to access this one. - access_type (str, optional): Type of access sought. - default (bool, optional): What to return if no lock of - access_type was found - no_superuser_bypass (bool, optional): Turn off superuser - lock bypassing. Be careful with this one. - - Keyword Args: - kwargs (any): Passed to the at_access hook along with the result. - - Returns: - result (bool): Result of access check. - - """ - result = super().access( - accessing_obj, - access_type=access_type, - default=default, - no_superuser_bypass=no_superuser_bypass, - ) - self.at_access(result, accessing_obj, access_type, **kwargs) - return result
- - @property - def idle_time(self): - """ - Returns the idle time of the least idle session in seconds. If - no sessions are connected it returns nothing. - """ - idle = [session.cmd_last_visible for session in self.sessions.all()] - if idle: - return time.time() - float(max(idle)) - return None - - @property - def connection_time(self): - """ - Returns the maximum connection time of all connected sessions - in seconds. Returns nothing if there are no sessions. - """ - conn = [session.conn_time for session in self.sessions.all()] - if conn: - return time.time() - float(min(conn)) - return None - - # account hooks - -
[docs] def basetype_setup(self): - """ - This sets up the basic properties for an account. Overload this - with at_account_creation rather than changing this method. - - """ - # A basic security setup - lockstring = ( - "examine:perm(Admin);edit:perm(Admin);" - "delete:perm(Admin);boot:perm(Admin);msg:all();" - "noidletimeout:perm(Builder) or perm(noidletimeout)" - ) - self.locks.add(lockstring) - - # The ooc account cmdset - self.cmdset.add_default(_CMDSET_ACCOUNT, permanent=True)
- -
[docs] def at_account_creation(self): - """ - This is called once, the very first time the account is created - (i.e. first time they register with the game). It's a good - place to store attributes all accounts should have, like - configuration values etc. - - """ - # set an (empty) attribute holding the characters this account has - lockstring = "attrread:perm(Admins);attredit:perm(Admins);" "attrcreate:perm(Admins);" - self.attributes.add("_playable_characters", [], lockstring=lockstring) - self.attributes.add("_saved_protocol_flags", {}, lockstring=lockstring)
- -
[docs] def at_init(self): - """ - This is always called whenever this object is initiated -- - that is, whenever it its typeclass is cached from memory. This - happens on-demand first time the object is used or activated - in some way after being created but also after each server - restart or reload. In the case of account objects, this usually - happens the moment the account logs in or reconnects after a - reload. - - """ - pass
- - # Note that the hooks below also exist in the character object's - # typeclass. You can often ignore these and rely on the character - # ones instead, unless you are implementing a multi-character game - # and have some things that should be done regardless of which - # character is currently connected to this account. - -
[docs] def at_first_save(self): - """ - This is a generic hook called by Evennia when this object is - saved to the database the very first time. You generally - don't override this method but the hooks called by it. - - """ - self.basetype_setup() - self.at_account_creation() - - permissions = [settings.PERMISSION_ACCOUNT_DEFAULT] - if hasattr(self, "_createdict"): - # this will only be set if the utils.create_account - # function was used to create the object. - cdict = self._createdict - updates = [] - if not cdict.get("key"): - if not self.db_key: - self.db_key = f"#{self.dbid}" - updates.append("db_key") - elif self.key != cdict.get("key"): - updates.append("db_key") - self.db_key = cdict["key"] - if updates: - self.save(update_fields=updates) - - if cdict.get("locks"): - self.locks.add(cdict["locks"]) - if cdict.get("permissions"): - permissions = cdict["permissions"] - if cdict.get("tags"): - # this should be a list of tags, tuples (key, category) or (key, category, data) - self.tags.batch_add(*cdict["tags"]) - if cdict.get("attributes"): - # this should be tuples (key, val, ...) - self.attributes.batch_add(*cdict["attributes"]) - if cdict.get("nattributes"): - # this should be a dict of nattrname:value - for key, value in cdict["nattributes"]: - self.nattributes.add(key, value) - del self._createdict - - self.permissions.batch_add(*permissions)
- -
[docs] def at_access(self, result, accessing_obj, access_type, **kwargs): - """ - This is triggered after an access-call on this Account has - completed. - - Args: - result (bool): The result of the access check. - accessing_obj (any): The object requesting the access - check. - access_type (str): The type of access checked. - - Keyword Args: - kwargs (any): These are passed on from the access check - and can be used to relay custom instructions from the - check mechanism. - - Notes: - This method cannot affect the result of the lock check and - its return value is not used in any way. It can be used - e.g. to customize error messages in a central location or - create other effects based on the access result. - - """ - pass
- -
[docs] def at_cmdset_get(self, **kwargs): - """ - Called just *before* cmdsets on this account are requested by - the command handler. The cmdsets are available as - `self.cmdset`. If changes need to be done on the fly to the - cmdset before passing them on to the cmdhandler, this is the - place to do it. This is called also if the account currently - have no cmdsets. kwargs are usually not used unless the - cmdset is generated dynamically. - - """ - pass
- -
[docs] def at_first_login(self, **kwargs): - """ - Called the very first time this account logs into the game. - Note that this is called *before* at_pre_login, so no session - is established and usually no character is yet assigned at - this point. This hook is intended for account-specific setup - like configurations. - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_password_change(self, **kwargs): - """ - Called after a successful password set/modify. - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_pre_login(self, **kwargs): - """ - Called every time the user logs in, just before the actual - login-state is set. - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- - def _send_to_connect_channel(self, message): - """ - Helper method for loading and sending to the comm channel - dedicated to connection messages. - - Args: - message (str): A message to send to the connect channel. - - """ - global _MUDINFO_CHANNEL - if not _MUDINFO_CHANNEL: - try: - _MUDINFO_CHANNEL = ChannelDB.objects.filter(db_key=settings.CHANNEL_MUDINFO["key"])[ - 0 - ] - except Exception: - logger.log_trace() - if settings.USE_TZ: - now = timezone.localtime() - else: - now = timezone.now() - now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month, now.day, now.hour, now.minute) - if _MUDINFO_CHANNEL: - _MUDINFO_CHANNEL.tempmsg(f"[{_MUDINFO_CHANNEL.key}, {now}]: {message}") - else: - logger.log_info(f"[{now}]: {message}") - -
[docs] def at_post_login(self, session=None, **kwargs): - """ - Called at the end of the login process, just before letting - the account loose. - - Args: - session (Session, optional): Session logging in, if any. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - This is called *before* an eventual Character's - `at_post_login` hook. By default it is used to set up - auto-puppeting based on `MULTISESSION_MODE`. - - """ - # if we have saved protocol flags on ourselves, load them here. - protocol_flags = self.attributes.get("_saved_protocol_flags", {}) - if session and protocol_flags: - session.update_flags(**protocol_flags) - - # inform the client that we logged in through an OOB message - if session: - session.msg(logged_in={}) - - self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key)) - if _MULTISESSION_MODE == 0: - # in this mode we should have only one character available. We - # try to auto-connect to our last conneted object, if any - try: - self.puppet_object(session, self.db._last_puppet) - except RuntimeError: - self.msg(_("The Character does not exist.")) - return - elif _MULTISESSION_MODE == 1: - # in this mode all sessions connect to the same puppet. - try: - self.puppet_object(session, self.db._last_puppet) - except RuntimeError: - self.msg(_("The Character does not exist.")) - return - elif _MULTISESSION_MODE in (2, 3): - # In this mode we by default end up at a character selection - # screen. We execute look on the account. - # we make sure to clean up the _playable_characters list in case - # any was deleted in the interim. - self.db._playable_characters = [char for char in self.db._playable_characters if char] - self.msg( - self.at_look(target=self.db._playable_characters, session=session), session=session - )
- -
[docs] def at_failed_login(self, session, **kwargs): - """ - Called by the login process if a user account is targeted correctly - but provided with an invalid password. By default it does nothing, - but exists to be overriden. - - Args: - session (session): Session logging in. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - """ - pass
- -
[docs] def at_disconnect(self, reason=None, **kwargs): - """ - Called just before user is disconnected. - - Args: - reason (str, optional): The reason given for the disconnect, - (echoed to the connection channel by default). - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - - """ - reason = f" ({reason if reason else ''})" - self._send_to_connect_channel( - _("|R{key} disconnected{reason}|n").format(key=self.key, reason=reason) - )
- -
[docs] def at_post_disconnect(self, **kwargs): - """ - This is called *after* disconnection is complete. No messages - can be relayed to the account from here. After this call, the - account should not be accessed any more, making this a good - spot for deleting it (in the case of a guest account account, - for example). - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_msg_receive(self, text=None, from_obj=None, **kwargs): - """ - This hook is called whenever someone sends a message to this - object using the `msg` method. - - Note that from_obj may be None if the sender did not include - itself as an argument to the obj.msg() call - so you have to - check for this. . - - Consider this a pre-processing method before msg is passed on - to the user session. If this method returns False, the msg - will not be passed on. - - Args: - text (str, optional): The message received. - from_obj (any, optional): The object sending the message. - - Keyword Args: - This includes any keywords sent to the `msg` method. - - Returns: - receive (bool): If this message should be received. - - Notes: - If this method returns False, the `msg` operation - will abort without sending the message. - - """ - return True
- -
[docs] def at_msg_send(self, text=None, to_obj=None, **kwargs): - """ - This is a hook that is called when *this* object sends a - message to another object with `obj.msg(text, to_obj=obj)`. - - Args: - text (str, optional): Text to send. - to_obj (any, optional): The object to send to. - - Keyword Args: - Keywords passed from msg() - - Notes: - Since this method is executed by `from_obj`, if no `from_obj` - was passed to `DefaultCharacter.msg` this hook will never - get called. - - """ - pass
- -
[docs] def at_server_reload(self): - """ - This hook is called whenever the server is shutting down for - restart/reboot. If you want to, for example, save - non-persistent properties across a restart, this is the place - to do it. - """ - pass
- -
[docs] def at_server_shutdown(self): - """ - This hook is called whenever the server is shutting down fully - (i.e. not for a restart). - """ - pass
- -
[docs] def at_look(self, target=None, session=None, **kwargs): - """ - Called when this object executes a look. It allows to customize - just what this means. - - Args: - target (Object or list, optional): An object or a list - objects to inspect. - session (Session, optional): The session doing this look. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - look_string (str): A prepared look string, ready to send - off to any recipient (usually to ourselves) - - """ - - if target and not is_iter(target): - # single target - just show it - if hasattr(target, "return_appearance"): - return target.return_appearance(self) - else: - return _("{target} has no in-game appearance.").format(target=target) - else: - # list of targets - make list to disconnect from db - characters = list(tar for tar in target if tar) if target else [] - sessions = self.sessions.all() - if not sessions: - # no sessions, nothing to report - return "" - is_su = self.is_superuser - - # text shown when looking in the ooc area - result = [f"Account |g{self.key}|n (you are Out-of-Character)"] - - nsess = len(sessions) - result.append( - nsess == 1 - and "\n\n|wConnected session:|n" - or f"\n\n|wConnected sessions ({nsess}):|n" - ) - for isess, sess in enumerate(sessions): - csessid = sess.sessid - addr = "%s (%s)" % ( - sess.protocol_key, - isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address), - ) - result.append( - "\n %s %s" - % ( - session - and session.sessid == csessid - and "|w* %s|n" % (isess + 1) - or " %s" % (isess + 1), - addr, - ) - ) - result.append("\n\n |whelp|n - more commands") - result.append("\n |wpublic <Text>|n - talk on public channel") - - charmax = _MAX_NR_CHARACTERS - - if is_su or len(characters) < charmax: - if not characters: - result.append( - _( - "\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one." - ) - ) - else: - result.append("\n |w@charcreate <name> [=description]|n - create new character") - result.append( - "\n |w@chardelete <name>|n - delete a character (cannot be undone!)" - ) - - if characters: - string_s_ending = len(characters) > 1 and "s" or "" - result.append("\n |w@ic <character>|n - enter the game (|w@ooc|n to get back here)") - if is_su: - result.append( - f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):" - ) - else: - result.append( - "\n\nAvailable character%s%s:" - % ( - string_s_ending, - charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "", - ) - ) - - for char in characters: - csessions = char.sessions.all() - if csessions: - for sess in csessions: - # character is already puppeted - sid = sess in sessions and sessions.index(sess) + 1 - if sess and sid: - result.append( - f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] (played by you in session {sid})" - ) - else: - result.append( - f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] (played by someone else)" - ) - else: - # character is "free to puppet" - result.append(f"\n - {char.key} [{', '.join(char.permissions.all())}]") - look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68) - return look_string
- - -
[docs]class DefaultGuest(DefaultAccount): - """ - This class is used for guest logins. Unlike Accounts, Guests and - their characters are deleted after disconnection. - """ - -
[docs] @classmethod - def create(cls, **kwargs): - """ - Forwards request to cls.authenticate(); returns a DefaultGuest object - if one is available for use. - """ - return cls.authenticate(**kwargs)
- -
[docs] @classmethod - def authenticate(cls, **kwargs): - """ - Gets or creates a Guest account object. - - Keyword Args: - ip (str, optional): IP address of requestor; used for ban checking, - throttling and logging - - Returns: - account (Object): Guest account object, if available - errors (list): List of error messages accrued during this request. - - """ - errors = [] - account = None - username = None - ip = kwargs.get("ip", "").strip() - - # check if guests are enabled. - if not settings.GUEST_ENABLED: - errors.append(_("Guest accounts are not enabled on this server.")) - return None, errors - - try: - # Find an available guest name. - for name in settings.GUEST_LIST: - if not AccountDB.objects.filter(username__iexact=name).exists(): - username = name - break - if not username: - errors.append(_("All guest accounts are in use. Please try again later.")) - if ip: - LOGIN_THROTTLE.update(ip, "Too many requests for Guest access.") - return None, errors - else: - # build a new account with the found guest username - password = "%016x" % getrandbits(64) - home = settings.GUEST_HOME - permissions = settings.PERMISSION_GUEST_DEFAULT - typeclass = settings.BASE_GUEST_TYPECLASS - - # Call parent class creator - account, errs = super(DefaultGuest, cls).create( - guest=True, - username=username, - password=password, - permissions=permissions, - typeclass=typeclass, - home=home, - ip=ip, - ) - errors.extend(errs) - - if not account.characters: - # this can happen for multisession_mode > 1. For guests we - # always auto-create a character, regardless of multi-session-mode. - character, errs = account.create_character() - - if errs: - errors.extend(errs) - - return account, errors - - except Exception as e: - # We are in the middle between logged in and -not, so we have - # to handle tracebacks ourselves at this point. If we don't, - # we won't see any errors at all. - errors.append(_("An error occurred. Please e-mail an admin if the problem persists.")) - logger.log_trace() - return None, errors - - return account, errors
- -
[docs] def at_post_login(self, session=None, **kwargs): - """ - In theory, guests only have one character regardless of which - MULTISESSION_MODE we're in. They don't get a choice. - - Args: - session (Session, optional): Session connecting. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key)) - self.puppet_object(session, self.db._last_puppet)
- -
[docs] def at_server_shutdown(self): - """ - We repeat the functionality of `at_disconnect()` here just to - be on the safe side. - """ - super().at_server_shutdown() - characters = self.db._playable_characters - for character in characters: - if character: - character.delete()
- -
[docs] def at_post_disconnect(self, **kwargs): - """ - Once having disconnected, destroy the guest's characters and - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - super().at_post_disconnect() - characters = self.db._playable_characters - for character in characters: - if character: - character.delete() - self.delete()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/accounts/admin.html b/docs/0.9.5/_modules/evennia/accounts/admin.html deleted file mode 100644 index 4c4821234f..0000000000 --- a/docs/0.9.5/_modules/evennia/accounts/admin.html +++ /dev/null @@ -1,471 +0,0 @@ - - - - - - - - evennia.accounts.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.accounts.admin

-#
-# This sets up how models are displayed
-# in the web admin interface.
-#
-from django import forms
-from django.conf import settings
-from django.contrib import admin, messages
-from django.contrib.admin.options import IS_POPUP_VAR
-from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
-from django.contrib.auth.forms import UserChangeForm, UserCreationForm
-from django.contrib.admin.utils import unquote
-from django.template.response import TemplateResponse
-from django.http import Http404, HttpResponseRedirect
-from django.core.exceptions import PermissionDenied
-from django.views.decorators.debug import sensitive_post_parameters
-from django.utils.decorators import method_decorator
-from django.utils.html import escape
-from django.urls import path, reverse
-from django.contrib.auth import update_session_auth_hash
-
-from evennia.accounts.models import AccountDB
-from evennia.typeclasses.admin import AttributeInline, TagInline
-from evennia.utils import create
-
-sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
-
-
-# handle the custom User editor
-
[docs]class AccountDBChangeForm(UserChangeForm): - """ - Modify the accountdb class. - - """ - -
[docs] class Meta(object): - model = AccountDB - fields = "__all__"
- - username = forms.RegexField( - label="Username", - max_length=30, - regex=r"^[\w. @+-]+$", - widget=forms.TextInput(attrs={"size": "30"}), - error_messages={ - "invalid": "This value may contain only letters, spaces, numbers " - "and @/./+/-/_ characters." - }, - help_text="30 characters or fewer. Letters, spaces, digits and " "@/./+/-/_ only.", - ) - -
[docs] def clean_username(self): - """ - Clean the username and check its existence. - - """ - username = self.cleaned_data["username"] - if username.upper() == self.instance.username.upper(): - return username - elif AccountDB.objects.filter(username__iexact=username): - raise forms.ValidationError("An account with that name " "already exists.") - return self.cleaned_data["username"]
- - -
[docs]class AccountDBCreationForm(UserCreationForm): - """ - Create a new AccountDB instance. - """ - -
[docs] class Meta(object): - model = AccountDB - fields = "__all__"
- - username = forms.RegexField( - label="Username", - max_length=30, - regex=r"^[\w. @+-]+$", - widget=forms.TextInput(attrs={"size": "30"}), - error_messages={ - "invalid": "This value may contain only letters, spaces, numbers " - "and @/./+/-/_ characters." - }, - help_text="30 characters or fewer. Letters, spaces, digits and " "@/./+/-/_ only.", - ) - -
[docs] def clean_username(self): - """ - Cleanup username. - """ - username = self.cleaned_data["username"] - if AccountDB.objects.filter(username__iexact=username): - raise forms.ValidationError("An account with that name already " "exists.") - return username
- - -
[docs]class AccountForm(forms.ModelForm): - """ - Defines how to display Accounts - - """ - -
[docs] class Meta(object): - model = AccountDB - fields = "__all__" - app_label = "accounts"
- - db_key = forms.RegexField( - label="Username", - initial="AccountDummy", - max_length=30, - regex=r"^[\w. @+-]+$", - required=False, - widget=forms.TextInput(attrs={"size": "30"}), - error_messages={ - "invalid": "This value may contain only letters, spaces, numbers" - " and @/./+/-/_ characters." - }, - help_text="This should be the same as the connected Account's key " - "name. 30 characters or fewer. Letters, spaces, digits and " - "@/./+/-/_ only.", - ) - - db_typeclass_path = forms.CharField( - label="Typeclass", - initial=settings.BASE_ACCOUNT_TYPECLASS, - widget=forms.TextInput(attrs={"size": "78"}), - help_text="Required. Defines what 'type' of entity this is. This " - "variable holds a Python path to a module with a valid " - "Evennia Typeclass. Defaults to " - "settings.BASE_ACCOUNT_TYPECLASS.", - ) - - db_permissions = forms.CharField( - label="Permissions", - initial=settings.PERMISSION_ACCOUNT_DEFAULT, - required=False, - widget=forms.TextInput(attrs={"size": "78"}), - help_text="In-game permissions. A comma-separated list of text " - "strings checked by certain locks. They are often used for " - "hierarchies, such as letting an Account have permission " - "'Admin', 'Builder' etc. An Account permission can be " - "overloaded by the permissions of a controlled Character. " - "Normal accounts use 'Accounts' by default.", - ) - - db_lock_storage = forms.CharField( - label="Locks", - widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}), - required=False, - help_text="In-game lock definition string. If not given, defaults " - "will be used. This string should be on the form " - "<i>type:lockfunction(args);type2:lockfunction2(args);...", - ) - db_cmdset_storage = forms.CharField( - label="cmdset", - initial=settings.CMDSET_ACCOUNT, - widget=forms.TextInput(attrs={"size": "78"}), - required=False, - help_text="python path to account cmdset class (set in " - "settings.CMDSET_ACCOUNT by default)", - )
- - -
[docs]class AccountInline(admin.StackedInline): - """ - Inline creation of Account - - """ - - model = AccountDB - template = "admin/accounts/stacked.html" - form = AccountForm - fieldsets = ( - ( - "In-game Permissions and Locks", - { - "fields": ("db_lock_storage",), - # {'fields': ('db_permissions', 'db_lock_storage'), - "description": "<i>These are permissions/locks for in-game use. " - "They are unrelated to website access rights.</i>", - }, - ), - ( - "In-game Account data", - { - "fields": ("db_typeclass_path", "db_cmdset_storage"), - "description": "<i>These fields define in-game-specific properties " - "for the Account object in-game.</i>", - }, - ), - ) - - extra = 1 - max_num = 1
- - -
[docs]class AccountTagInline(TagInline): - """ - Inline Account Tags. - - """ - - model = AccountDB.db_tags.through - related_field = "accountdb"
- - -
[docs]class AccountAttributeInline(AttributeInline): - """ - Inline Account Attributes. - - """ - - model = AccountDB.db_attributes.through - related_field = "accountdb"
- - -
[docs]class AccountDBAdmin(BaseUserAdmin): - """ - This is the main creation screen for Users/accounts - - """ - - list_display = ("username", "email", "is_staff", "is_superuser") - form = AccountDBChangeForm - add_form = AccountDBCreationForm - inlines = [AccountTagInline, AccountAttributeInline] - fieldsets = ( - (None, {"fields": ("username", "password", "email")}), - ( - "Website profile", - { - "fields": ("first_name", "last_name"), - "description": "<i>These are not used " "in the default system.</i>", - }, - ), - ( - "Website dates", - { - "fields": ("last_login", "date_joined"), - "description": "<i>Relevant only to the website.</i>", - }, - ), - ( - "Website Permissions", - { - "fields": ("is_active", "is_staff", "is_superuser", "user_permissions", "groups"), - "description": "<i>These are permissions/permission groups for " - "accessing the admin site. They are unrelated to " - "in-game access rights.</i>", - }, - ), - ( - "Game Options", - { - "fields": ("db_typeclass_path", "db_cmdset_storage", "db_lock_storage"), - "description": "<i>These are attributes that are more relevant " "to gameplay.</i>", - }, - ), - ) - # ('Game Options', {'fields': ( - # 'db_typeclass_path', 'db_cmdset_storage', - # 'db_permissions', 'db_lock_storage'), - # 'description': '<i>These are attributes that are ' - # 'more relevant to gameplay.</i>'})) - - add_fieldsets = ( - ( - None, - { - "fields": ("username", "password1", "password2", "email"), - "description": "<i>These account details are shared by the admin " - "system and the game.</i>", - }, - ), - ) - -
[docs] @sensitive_post_parameters_m - def user_change_password(self, request, id, form_url=""): - user = self.get_object(request, unquote(id)) - if not self.has_change_permission(request, user): - raise PermissionDenied - if user is None: - raise Http404("%(name)s object with primary key %(key)r does not exist.") % { - "name": self.model._meta.verbose_name, - "key": escape(id), - } - if request.method == "POST": - form = self.change_password_form(user, request.POST) - if form.is_valid(): - form.save() - change_message = self.construct_change_message(request, form, None) - self.log_change(request, user, change_message) - msg = "Password changed successfully." - messages.success(request, msg) - update_session_auth_hash(request, form.user) - return HttpResponseRedirect( - reverse( - "%s:%s_%s_change" - % ( - self.admin_site.name, - user._meta.app_label, - # the model_name is something we need to hardcode - # since our accountdb is a proxy: - "accountdb", - ), - args=(user.pk,), - ) - ) - else: - form = self.change_password_form(user) - - fieldsets = [(None, {"fields": list(form.base_fields)})] - adminForm = admin.helpers.AdminForm(form, fieldsets, {}) - - context = { - "title": "Change password: %s" % escape(user.get_username()), - "adminForm": adminForm, - "form_url": form_url, - "form": form, - "is_popup": (IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET), - "add": True, - "change": False, - "has_delete_permission": False, - "has_change_permission": True, - "has_absolute_url": False, - "opts": self.model._meta, - "original": user, - "save_as": False, - "show_save": True, - **self.admin_site.each_context(request), - } - - request.current_app = self.admin_site.name - - return TemplateResponse( - request, - self.change_user_password_template or "admin/auth/user/change_password.html", - context, - )
- -
[docs] def save_model(self, request, obj, form, change): - """ - Custom save actions. - - Args: - request (Request): Incoming request. - obj (Object): Object to save. - form (Form): Related form instance. - change (bool): False if this is a new save and not an update. - - """ - obj.save() - if not change: - # calling hooks for new account - obj.set_class_from_typeclass(typeclass_path=settings.BASE_ACCOUNT_TYPECLASS) - obj.basetype_setup() - obj.at_account_creation()
- -
[docs] def response_add(self, request, obj, post_url_continue=None): - from django.http import HttpResponseRedirect - from django.urls import reverse - - return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id]))
- - -admin.site.register(AccountDB, AccountDBAdmin) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/accounts/bots.html b/docs/0.9.5/_modules/evennia/accounts/bots.html deleted file mode 100644 index 5041d94f4e..0000000000 --- a/docs/0.9.5/_modules/evennia/accounts/bots.html +++ /dev/null @@ -1,685 +0,0 @@ - - - - - - - - evennia.accounts.bots — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.accounts.bots

-"""
-Bots are a special child typeclasses of
-Account that are  controlled by the server.
-
-"""
-
-import time
-from django.conf import settings
-from evennia.accounts.accounts import DefaultAccount
-from evennia.scripts.scripts import DefaultScript
-from evennia.utils import search
-from evennia.utils import utils
-from django.utils.translation import gettext as _
-
-_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
-
-_IRC_ENABLED = settings.IRC_ENABLED
-_RSS_ENABLED = settings.RSS_ENABLED
-_GRAPEVINE_ENABLED = settings.GRAPEVINE_ENABLED
-
-
-_SESSIONS = None
-
-
-# Bot helper utilities
-
-
-
[docs]class BotStarter(DefaultScript): - """ - This non-repeating script has the - sole purpose of kicking its bot - into gear when it is initialized. - - """ - -
[docs] def at_script_creation(self): - """ - Called once, when script is created. - - """ - self.key = "botstarter" - self.desc = "bot start/keepalive" - self.persistent = True - self.db.started = False
- -
[docs] def at_start(self): - """ - Kick bot into gear. - - """ - if not self.db.started: - self.account.start() - self.db.started = True
- -
[docs] def at_repeat(self): - """ - Called self.interval seconds to keep connection. We cannot use - the IDLE command from inside the game since the system will - not catch it (commands executed from the server side usually - has no sessions). So we update the idle counter manually here - instead. This keeps the bot getting hit by IDLE_TIMEOUT. - - """ - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - for session in _SESSIONS.sessions_from_account(self.account): - session.update_session_counters(idle=True)
- -
[docs] def at_server_reload(self): - """ - If server reloads we don't need to reconnect the protocol - again, this is handled by the portal reconnect mechanism. - - """ - self.db.started = True
- -
[docs] def at_server_shutdown(self): - """ - Make sure we are shutdown. - - """ - self.db.started = False
- - -# -# Bot base class - - -
[docs]class Bot(DefaultAccount): - """ - A Bot will start itself when the server starts (it will generally - not do so on a reload - that will be handled by the normal Portal - session resync) - - """ - -
[docs] def basetype_setup(self): - """ - This sets up the basic properties for the bot. - - """ - # the text encoding to use. - self.db.encoding = "utf-8" - # A basic security setup (also avoid idle disconnects) - lockstring = ( - "examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);" - "boot:perm(Admin);msg:false();noidletimeout:true()" - ) - self.locks.add(lockstring) - # set the basics of being a bot - script_key = str(self.key) - self.scripts.add(BotStarter, key=script_key) - self.is_bot = True
- -
[docs] def start(self, **kwargs): - """ - This starts the bot, whatever that may mean. - - """ - pass
- -
[docs] def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): - """ - Evennia -> outgoing protocol - - """ - super().msg(text=text, from_obj=from_obj, session=session, options=options, **kwargs)
- -
[docs] def execute_cmd(self, raw_string, session=None): - """ - Incoming protocol -> Evennia - - """ - super().msg(raw_string, session=session)
- -
[docs] def at_server_shutdown(self): - """ - We need to handle this case manually since the shutdown may be - a reset. - - """ - for session in self.sessions.all(): - session.sessionhandler.disconnect(session)
- - -# Bot implementations - -# IRC - - -
[docs]class IRCBot(Bot): - """ - Bot for handling IRC connections. - - """ - - # override this on a child class to use custom factory - factory_path = "evennia.server.portal.irc.IRCBotFactory" - -
[docs] def start( - self, - ev_channel=None, - irc_botname=None, - irc_channel=None, - irc_network=None, - irc_port=None, - irc_ssl=None, - ): - """ - Start by telling the portal to start a new session. - - Args: - ev_channel (str): Key of the Evennia channel to connect to. - irc_botname (str): Name of bot to connect to irc channel. If - not set, use `self.key`. - irc_channel (str): Name of channel on the form `#channelname`. - irc_network (str): URL of the IRC network, like `irc.freenode.net`. - irc_port (str): Port number of the irc network, like `6667`. - irc_ssl (bool): Indicates whether to use SSL connection. - - """ - if not _IRC_ENABLED: - # the bot was created, then IRC was turned off. We delete - # ourselves (this will also kill the start script) - self.delete() - return - - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - - # if keywords are given, store (the BotStarter script - # will not give any keywords, so this should normally only - # happen at initialization) - if irc_botname: - self.db.irc_botname = irc_botname - elif not self.db.irc_botname: - self.db.irc_botname = self.key - if ev_channel: - # connect to Evennia channel - channel = search.channel_search(ev_channel) - if not channel: - raise RuntimeError(f"Evennia Channel '{ev_channel}' not found.") - channel = channel[0] - channel.connect(self) - self.db.ev_channel = channel - if irc_channel: - self.db.irc_channel = irc_channel - if irc_network: - self.db.irc_network = irc_network - if irc_port: - self.db.irc_port = irc_port - if irc_ssl: - self.db.irc_ssl = irc_ssl - - # instruct the server and portal to create a new session with - # the stored configuration - configdict = { - "uid": self.dbid, - "botname": self.db.irc_botname, - "channel": self.db.irc_channel, - "network": self.db.irc_network, - "port": self.db.irc_port, - "ssl": self.db.irc_ssl, - } - _SESSIONS.start_bot_session(self.factory_path, configdict)
- -
[docs] def at_msg_send(self, **kwargs): - "Shortcut here or we can end up in infinite loop" - pass
- -
[docs] def get_nicklist(self, caller): - """ - Retrive the nick list from the connected channel. - - Args: - caller (Object or Account): The requester of the list. This will - be stored and echoed to when the irc network replies with the - requested info. - - Notes: Since the return is asynchronous, the caller is stored internally - in a list; all callers in this list will get the nick info once it - returns (it is a custom OOB inputfunc option). The callback will not - survive a reload (which should be fine, it's very quick). - """ - if not hasattr(self, "_nicklist_callers"): - self._nicklist_callers = [] - self._nicklist_callers.append(caller) - super().msg(request_nicklist="") - return
- -
[docs] def ping(self, caller): - """ - Fire a ping to the IRC server. - - Args: - caller (Object or Account): The requester of the ping. - - """ - if not hasattr(self, "_ping_callers"): - self._ping_callers = [] - self._ping_callers.append(caller) - super().msg(ping="")
- -
[docs] def reconnect(self): - """ - Force a protocol-side reconnect of the client without - having to destroy/recreate the bot "account". - - """ - super().msg(reconnect="")
- -
[docs] def msg(self, text=None, **kwargs): - """ - Takes text from connected channel (only). - - Args: - text (str, optional): Incoming text from channel. - - Keyword Args: - options (dict): Options dict with the following allowed keys: - - from_channel (str): dbid of a channel this text originated from. - - from_obj (list): list of objects sending this text. - - """ - from_obj = kwargs.get("from_obj", None) - options = kwargs.get("options", None) or {} - - if not self.ndb.ev_channel and self.db.ev_channel: - # cache channel lookup - self.ndb.ev_channel = self.db.ev_channel - - if ( - "from_channel" in options - and text - and self.ndb.ev_channel.dbid == options["from_channel"] - ): - if not from_obj or from_obj != [self]: - super().msg(channel=text)
- -
[docs] def execute_cmd(self, session=None, txt=None, **kwargs): - """ - Take incoming data and send it to connected channel. This is - triggered by the bot_data_in Inputfunc. - - Args: - session (Session, optional): Session responsible for this - command. Note that this is the bot. - txt (str, optional): Command string. - Keyword Args: - user (str): The name of the user who sent the message. - channel (str): The name of channel the message was sent to. - type (str): Nature of message. Either 'msg', 'action', 'nicklist' - or 'ping'. - nicklist (list, optional): Set if `type='nicklist'`. This is a list - of nicks returned by calling the `self.get_nicklist`. It must look - for a list `self._nicklist_callers` which will contain all callers - waiting for the nicklist. - timings (float, optional): Set if `type='ping'`. This is the return - (in seconds) of a ping request triggered with `self.ping`. The - return must look for a list `self._ping_callers` which will contain - all callers waiting for the ping return. - - """ - if kwargs["type"] == "nicklist": - # the return of a nicklist request - if hasattr(self, "_nicklist_callers") and self._nicklist_callers: - chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})" - nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower())) - for obj in self._nicklist_callers: - obj.msg( - _("Nicks at {chstr}:\n {nicklist}").format(chstr=chstr, nicklist=nicklist) - ) - self._nicklist_callers = [] - return - - elif kwargs["type"] == "ping": - # the return of a ping - if hasattr(self, "_ping_callers") and self._ping_callers: - chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})" - for obj in self._ping_callers: - obj.msg( - _("IRC ping return from {chstr} took {time}s.").format( - chstr=chstr, time=kwargs["timing"] - ) - ) - self._ping_callers = [] - return - - elif kwargs["type"] == "privmsg": - # A private message to the bot - a command. - user = kwargs["user"] - - if txt.lower().startswith("who"): - # return server WHO list (abbreviated for IRC) - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - whos = [] - t0 = time.time() - for sess in _SESSIONS.get_sessions(): - delta_cmd = t0 - sess.cmd_last_visible - delta_conn = t0 - session.conn_time - account = sess.get_account() - whos.append( - "%s (%s/%s)" - % ( - utils.crop("|w%s|n" % account.name, width=25), - utils.time_format(delta_conn, 0), - utils.time_format(delta_cmd, 1), - ) - ) - text = f"Who list (online/idle): {', '.join(sorted(whos, key=lambda w: w.lower()))}" - elif txt.lower().startswith("about"): - # some bot info - text = f"This is an Evennia IRC bot connecting from '{settings.SERVERNAME}'." - else: - text = "I understand 'who' and 'about'." - super().msg(privmsg=((text,), {"user": user})) - else: - # something to send to the main channel - if kwargs["type"] == "action": - # An action (irc pose) - text = f"{kwargs['user']}@{kwargs['channel']} {txt}" - else: - # msg - A normal channel message - text = f"{kwargs['user']}@{kwargs['channel']}: {txt}" - - if not self.ndb.ev_channel and self.db.ev_channel: - # cache channel lookup - self.ndb.ev_channel = self.db.ev_channel - - if self.ndb.ev_channel: - self.ndb.ev_channel.msg(text, senders=self)
- - -# -# RSS - - -
[docs]class RSSBot(Bot): - """ - An RSS relayer. The RSS protocol itself runs a ticker to update - its feed at regular intervals. - - """ - -
[docs] def start(self, ev_channel=None, rss_url=None, rss_rate=None): - """ - Start by telling the portal to start a new RSS session - - Args: - ev_channel (str): Key of the Evennia channel to connect to. - rss_url (str): Full URL to the RSS feed to subscribe to. - rss_rate (int): How often for the feedreader to update. - - Raises: - RuntimeError: If `ev_channel` does not exist. - - """ - if not _RSS_ENABLED: - # The bot was created, then RSS was turned off. Delete ourselves. - self.delete() - return - - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - - if ev_channel: - # connect to Evennia channel - channel = search.channel_search(ev_channel) - if not channel: - raise RuntimeError(f"Evennia Channel '{ev_channel}' not found.") - channel = channel[0] - self.db.ev_channel = channel - if rss_url: - self.db.rss_url = rss_url - if rss_rate: - self.db.rss_rate = rss_rate - # instruct the server and portal to create a new session with - # the stored configuration - configdict = {"uid": self.dbid, "url": self.db.rss_url, "rate": self.db.rss_rate} - _SESSIONS.start_bot_session("evennia.server.portal.rss.RSSBotFactory", configdict)
- -
[docs] def execute_cmd(self, txt=None, session=None, **kwargs): - """ - Take incoming data and send it to connected channel. This is - triggered by the bot_data_in Inputfunc. - - Args: - session (Session, optional): Session responsible for this - command. - txt (str, optional): Command string. - kwargs (dict, optional): Additional Information passed from bot. - Not used by the RSSbot by default. - - """ - if not self.ndb.ev_channel and self.db.ev_channel: - # cache channel lookup - self.ndb.ev_channel = self.db.ev_channel - if self.ndb.ev_channel: - self.ndb.ev_channel.msg(txt, senders=self.id)
- - -# Grapevine bot - - -
[docs]class GrapevineBot(Bot): - """ - g Grapevine (https://grapevine.haus) relayer. The channel to connect to is the first - name in the settings.GRAPEVINE_CHANNELS list. - - """ - - factory_path = "evennia.server.portal.grapevine.RestartingWebsocketServerFactory" - -
[docs] def start(self, ev_channel=None, grapevine_channel=None): - """ - Start by telling the portal to connect to the grapevine network. - - """ - if not _GRAPEVINE_ENABLED: - self.delete() - return - - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - - # connect to Evennia channel - if ev_channel: - # connect to Evennia channel - channel = search.channel_search(ev_channel) - if not channel: - raise RuntimeError(f"Evennia Channel '{ev_channel}' not found.") - channel = channel[0] - channel.connect(self) - self.db.ev_channel = channel - - if grapevine_channel: - self.db.grapevine_channel = grapevine_channel - - # these will be made available as properties on the protocol factory - configdict = {"uid": self.dbid, "grapevine_channel": self.db.grapevine_channel} - - _SESSIONS.start_bot_session(self.factory_path, configdict)
- -
[docs] def at_msg_send(self, **kwargs): - "Shortcut here or we can end up in infinite loop" - pass
- -
[docs] def msg(self, text=None, **kwargs): - """ - Takes text from connected channel (only). - - Args: - text (str, optional): Incoming text from channel. - - Keyword Args: - options (dict): Options dict with the following allowed keys: - - from_channel (str): dbid of a channel this text originated from. - - from_obj (list): list of objects sending this text. - - """ - from_obj = kwargs.get("from_obj", None) - options = kwargs.get("options", None) or {} - - if not self.ndb.ev_channel and self.db.ev_channel: - # cache channel lookup - self.ndb.ev_channel = self.db.ev_channel - - if ( - "from_channel" in options - and text - and self.ndb.ev_channel.dbid == options["from_channel"] - ): - if not from_obj or from_obj != [self]: - # send outputfunc channel(msg, chan, sender) - - # TODO we should refactor channel formatting to operate on the - # account/object level instead. For now, remove the channel/name - # prefix since we pass that explicitly anyway - prefix, text = text.split(":", 1) - - super().msg( - channel=( - text.strip(), - self.db.grapevine_channel, - ", ".join(obj.key for obj in from_obj), - {}, - ) - )
- -
[docs] def execute_cmd( - self, - txt=None, - session=None, - event=None, - grapevine_channel=None, - sender=None, - game=None, - **kwargs, - ): - """ - Take incoming data from protocol and send it to connected channel. This is - triggered by the bot_data_in Inputfunc. - """ - if event == "channels/broadcast": - # A private message to the bot - a command. - - text = f"{sender}@{game}: {txt}" - - if not self.ndb.ev_channel and self.db.ev_channel: - # simple cache of channel lookup - self.ndb.ev_channel = self.db.ev_channel - if self.ndb.ev_channel: - self.ndb.ev_channel.msg(text, senders=self)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/accounts/manager.html b/docs/0.9.5/_modules/evennia/accounts/manager.html deleted file mode 100644 index 76bb223b3f..0000000000 --- a/docs/0.9.5/_modules/evennia/accounts/manager.html +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - evennia.accounts.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.accounts.manager

-"""
-The managers for the custom Account object and permissions.
-"""
-
-import datetime
-from django.utils import timezone
-from django.contrib.auth.models import UserManager
-from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
-
-__all__ = ("AccountManager", "AccountDBManager")
-
-
-#
-# Account Manager
-#
-
-
-
[docs]class AccountDBManager(TypedObjectManager, UserManager): - """ - This AccountManager implements methods for searching - and manipulating Accounts directly from the database. - - Evennia-specific search methods (will return Characters if - possible or a Typeclass/list of Typeclassed objects, whereas - Django-general methods will return Querysets or database objects): - - dbref (converter) - dbref_search - get_dbref_range - object_totals - typeclass_search - num_total_accounts - get_connected_accounts - get_recently_created_accounts - get_recently_connected_accounts - get_account_from_email - get_account_from_uid - get_account_from_name - account_search (equivalent to evennia.search_account) - - """ - -
[docs] def num_total_accounts(self): - """ - Get total number of accounts. - - Returns: - count (int): The total number of registered accounts. - - """ - return self.count()
- -
[docs] def get_connected_accounts(self): - """ - Get all currently connected accounts. - - Returns: - count (list): Account objects with currently - connected sessions. - - """ - return self.filter(db_is_connected=True)
- -
[docs] def get_recently_created_accounts(self, days=7): - """ - Get accounts recently created. - - Args: - days (int, optional): How many days in the past "recently" means. - - Returns: - accounts (list): The Accounts created the last `days` interval. - - """ - end_date = timezone.now() - tdelta = datetime.timedelta(days) - start_date = end_date - tdelta - return self.filter(date_joined__range=(start_date, end_date))
- -
[docs] def get_recently_connected_accounts(self, days=7): - """ - Get accounts recently connected to the game. - - Args: - days (int, optional): Number of days backwards to check - - Returns: - accounts (list): The Accounts connected to the game in the - last `days` interval. - - """ - end_date = timezone.now() - tdelta = datetime.timedelta(days) - start_date = end_date - tdelta - return self.filter(last_login__range=(start_date, end_date)).order_by("-last_login")
- -
[docs] def get_account_from_email(self, uemail): - """ - Search account by - Returns an account object based on email address. - - Args: - uemail (str): An email address to search for. - - Returns: - account (Account): A found account, if found. - - """ - return self.filter(email__iexact=uemail)
- -
[docs] def get_account_from_uid(self, uid): - """ - Get an account by id. - - Args: - uid (int): Account database id. - - Returns: - account (Account): The result. - - """ - try: - return self.get(id=uid) - except self.model.DoesNotExist: - return None
- -
[docs] def get_account_from_name(self, uname): - """ - Get account object based on name. - - Args: - uname (str): The Account name to search for. - - Returns: - account (Account): The found account. - - """ - try: - return self.get(username__iexact=uname) - except self.model.DoesNotExist: - return None
- -
[docs] def search_account(self, ostring, exact=True, typeclass=None): - """ - Searches for a particular account by name or - database id. - - Args: - ostring (str or int): A key string or database id. - exact (bool, optional): Only valid for string matches. If - `True`, requires exact (non-case-sensitive) match, - otherwise also match also keys containing the `ostring` - (non-case-sensitive fuzzy match). - typeclass (str or Typeclass, optional): Limit the search only to - accounts of this typeclass. - - """ - dbref = self.dbref(ostring) - if dbref or dbref == 0: - # bref search is always exact - matches = self.filter(id=dbref) - if matches: - return matches - query = {"username__iexact" if exact else "username__icontains": ostring} - if typeclass: - # we accept both strings and actual typeclasses - if callable(typeclass): - typeclass = f"{typeclass.__module__}.{typeclass.__name__}" - else: - typeclass = str(typeclass) - query["db_typeclass_path"] = typeclass - if exact: - matches = self.filter(**query) - else: - matches = self.filter(**query) - if not matches: - # try alias match - matches = self.filter( - db_tags__db_tagtype__iexact="alias", - **{"db_tags__db_key__iexact" if exact else "db_tags__db_key__icontains": ostring}, - ) - return matches
- - # back-compatibility alias - account_search = search_account
- - -
[docs]class AccountManager(AccountDBManager, TypeclassManager): - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/accounts/models.html b/docs/0.9.5/_modules/evennia/accounts/models.html deleted file mode 100644 index 95a66ffa4d..0000000000 --- a/docs/0.9.5/_modules/evennia/accounts/models.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - evennia.accounts.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.accounts.models

-"""
-Account
-
-The account class is an extension of the default Django user class,
-and is customized for the needs of Evennia.
-
-We use the Account to store a more mud-friendly style of permission
-system as well as to allow the admin more flexibility by storing
-attributes on the Account.  Within the game we should normally use the
-Account manager's methods to create users so that permissions are set
-correctly.
-
-To make the Account model more flexible for your own game, it can also
-persistently store attributes of its own. This is ideal for extra
-account info and OOC account configuration variables etc.
-
-"""
-from django.conf import settings
-from django.db import models
-from django.contrib.auth.models import AbstractUser
-from django.utils.encoding import smart_str
-
-from evennia.accounts.manager import AccountDBManager
-from evennia.typeclasses.models import TypedObject
-from evennia.utils.utils import make_iter
-from evennia.server.signals import SIGNAL_ACCOUNT_POST_RENAME
-
-__all__ = ("AccountDB",)
-
-# _ME = _("me")
-# _SELF = _("self")
-
-_MULTISESSION_MODE = settings.MULTISESSION_MODE
-
-_GA = object.__getattribute__
-_SA = object.__setattr__
-_DA = object.__delattr__
-
-_TYPECLASS = None
-
-
-# ------------------------------------------------------------
-#
-# AccountDB
-#
-# ------------------------------------------------------------
-
-
-
[docs]class AccountDB(TypedObject, AbstractUser): - """ - This is a special model using Django's 'profile' functionality - and extends the default Django User model. It is defined as such - by use of the variable AUTH_PROFILE_MODULE in the settings. - One accesses the fields/methods. We try use this model as much - as possible rather than User, since we can customize this to - our liking. - - The TypedObject supplies the following (inherited) properties: - - - key - main name - - typeclass_path - the path to the decorating typeclass - - typeclass - auto-linked typeclass - - date_created - time stamp of object creation - - permissions - perm strings - - dbref - #id of object - - db - persistent attribute storage - - ndb - non-persistent attribute storage - - The AccountDB adds the following properties: - - - is_connected - If any Session is currently connected to this Account - - name - alias for user.username - - sessions - sessions connected to this account - - is_superuser - bool if this account is a superuser - - is_bot - bool if this account is a bot and not a real account - - """ - - # - # AccountDB Database model setup - # - # inherited fields (from TypedObject): - # db_key, db_typeclass_path, db_date_created, db_permissions - - # store a connected flag here too, not just in sessionhandler. - # This makes it easier to track from various out-of-process locations - db_is_connected = models.BooleanField( - default=False, - verbose_name="is_connected", - help_text="If player is connected to game or not", - ) - # database storage of persistant cmdsets. - db_cmdset_storage = models.CharField( - "cmdset", - max_length=255, - null=True, - help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.", - ) - # marks if this is a "virtual" bot account object - db_is_bot = models.BooleanField( - default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots" - ) - - # Database manager - objects = AccountDBManager() - - # defaults - __defaultclasspath__ = "evennia.accounts.accounts.DefaultAccount" - __applabel__ = "accounts" - __settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS - - # class Meta: - # verbose_name = "Account" - - # cmdset_storage property - # This seems very sensitive to caching, so leaving it be for now /Griatch - # @property - def __cmdset_storage_get(self): - """ - Getter. Allows for value = self.name. Returns a list of cmdset_storage. - """ - storage = self.db_cmdset_storage - # we need to check so storage is not None - return [path.strip() for path in storage.split(",")] if storage else [] - - # @cmdset_storage.setter - def __cmdset_storage_set(self, value): - """ - Setter. Allows for self.name = value. Stores as a comma-separated - string. - """ - _SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value))) - _GA(self, "save")() - - # @cmdset_storage.deleter - def __cmdset_storage_del(self): - "Deleter. Allows for del self.name" - _SA(self, "db_cmdset_storage", None) - _GA(self, "save")() - - cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del) - - # - # property/field access - # - - def __str__(self): - return smart_str(f"{self.name}(account {self.dbid})") - - def __repr__(self): - return f"{self.name}(account#{self.dbid})" - - # @property - def __username_get(self): - return self.username - - def __username_set(self, value): - old_name = self.username - self.username = value - self.save(update_fields=["username"]) - SIGNAL_ACCOUNT_POST_RENAME.send(self, old_name=old_name, new_name=value) - - def __username_del(self): - del self.username - - # aliases - name = property(__username_get, __username_set, __username_del) - key = property(__username_get, __username_set, __username_del) - - # @property - def __uid_get(self): - "Getter. Retrieves the user id" - return self.id - - def __uid_set(self, value): - raise Exception("User id cannot be set!") - - def __uid_del(self): - raise Exception("User id cannot be deleted!") - - uid = property(__uid_get, __uid_set, __uid_del)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/cmdhandler.html b/docs/0.9.5/_modules/evennia/commands/cmdhandler.html deleted file mode 100644 index 599bf4634c..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/cmdhandler.html +++ /dev/null @@ -1,933 +0,0 @@ - - - - - - - - evennia.commands.cmdhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.cmdhandler

-"""
-Command handler
-
-This module contains the infrastructure for accepting commands on the
-command line. The processing of a command works as follows:
-
-1. The calling object (caller) is analyzed based on its callertype.
-2. Cmdsets are gathered from different sources:
-   - channels:  all available channel names are auto-created into a cmdset, to allow
-     for giving the channel name and have the following immediately
-     sent to the channel. The sending is performed by the CMD_CHANNEL
-     system command.
-   - object cmdsets: all objects at caller's location are scanned for non-empty
-     cmdsets. This includes cmdsets on exits.
-   - caller: the caller is searched for its own currently active cmdset.
-   - account: lastly the cmdsets defined on caller.account are added.
-3. The collected cmdsets are merged together to a combined, current cmdset.
-4. If the input string is empty -> check for CMD_NOINPUT command in
-   current cmdset or fallback to error message. Exit.
-5. The Command Parser is triggered, using the current cmdset to analyze the
-   input string for possible command matches.
-6. If multiple matches are found -> check for CMD_MULTIMATCH in current
-   cmdset, or fallback to error message. Exit.
-7. If no match was found -> check for CMD_NOMATCH in current cmdset or
-   fallback to error message. Exit.
-8. A single match was found. If this is a channel-command (i.e. the
-   ommand name is that of a channel), --> check for CMD_CHANNEL in
-   current cmdset or use channelhandler default. Exit.
-9. At this point we have found a normal command. We assign useful variables to it that
-   will be available to the command coder at run-time.
-12. We have a unique cmdobject, primed for use. Call all hooks:
-   `at_pre_cmd()`, `cmdobj.parse()`, `cmdobj.func()` and finally `at_post_cmd()`.
-13. Return deferred that will fire with the return from `cmdobj.func()` (unused by default).
-"""
-
-from collections import defaultdict
-from weakref import WeakValueDictionary
-from traceback import format_exc
-from itertools import chain
-from copy import copy
-import types
-from twisted.internet import reactor
-from twisted.internet.task import deferLater
-from twisted.internet.defer import inlineCallbacks, returnValue
-from django.conf import settings
-from evennia.commands.command import InterruptCommand
-from evennia.comms.channelhandler import CHANNELHANDLER
-from evennia.utils import logger, utils
-from evennia.utils.utils import string_suggestions
-
-from django.utils.translation import gettext as _
-
-_IN_GAME_ERRORS = settings.IN_GAME_ERRORS
-
-__all__ = ("cmdhandler", "InterruptCommand")
-_GA = object.__getattribute__
-_CMDSET_MERGE_CACHE = WeakValueDictionary()
-
-# tracks recursive calls by each caller
-# to avoid infinite loops (commands calling themselves)
-_COMMAND_NESTING = defaultdict(lambda: 0)
-_COMMAND_RECURSION_LIMIT = 10
-
-# This decides which command parser is to be used.
-# You have to restart the server for changes to take effect.
-_COMMAND_PARSER = utils.variable_from_module(*settings.COMMAND_PARSER.rsplit(".", 1))
-
-# System command names - import these variables rather than trying to
-# remember the actual string constants. If not defined, Evennia
-# hard-coded defaults are used instead.
-
-# command to call if user just presses <return> with no input
-CMD_NOINPUT = "__noinput_command"
-# command to call if no command match was found
-CMD_NOMATCH = "__nomatch_command"
-# command to call if multiple command matches were found
-CMD_MULTIMATCH = "__multimatch_command"
-# command to call if found command is the name of a channel
-CMD_CHANNEL = "__send_to_channel_command"
-# command to call as the very first one when the user connects.
-# (is expected to display the login screen)
-CMD_LOGINSTART = "__unloggedin_look_command"
-
-# Function for handling multiple command matches.
-_SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
-
-# Output strings. The first is the IN_GAME_ERRORS return, the second
-# is the normal "production message to echo to the account.
-
-_ERROR_UNTRAPPED = (
-    """
-An untrapped error occurred.
-""",
-    """
-An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
-""",
-)
-
-_ERROR_CMDSETS = (
-    """
-A cmdset merger-error occurred. This is often due to a syntax
-error in one of the cmdsets to merge.
-""",
-    """
-A cmdset merger-error occurred. Please file a bug report detailing the
-steps to reproduce.
-""",
-)
-
-_ERROR_NOCMDSETS = (
-    """
-No command sets found! This is a critical bug that can have
-multiple causes.
-""",
-    """
-No command sets found! This is a sign of a critical bug.  If
-disconnecting/reconnecting doesn't" solve the problem, try to contact
-the server admin through" some other means for assistance.
-""",
-)
-
-_ERROR_CMDHANDLER = (
-    """
-A command handler bug occurred. If this is not due to a local change,
-please file a bug report with the Evennia project, including the
-traceback and steps to reproduce.
-""",
-    """
-A command handler bug occurred. Please notify staff - they should
-likely file a bug report with the Evennia project.
-""",
-)
-
-_ERROR_RECURSION_LIMIT = (
-    "Command recursion limit ({recursion_limit}) " "reached for '{raw_cmdname}' ({cmdclass})."
-)
-
-
-# delayed imports
-_GET_INPUT = None
-
-
-# helper functions
-
-
-def _msg_err(receiver, stringtuple):
-    """
-    Helper function for returning an error to the caller.
-
-    Args:
-        receiver (Object): object to get the error message.
-        stringtuple (tuple): tuple with two strings - one for the
-            _IN_GAME_ERRORS mode (with the traceback) and one with the
-            production string (with a timestamp) to be shown to the user.
-
-    """
-    string = "{traceback}\n{errmsg}\n(Traceback was logged {timestamp})."
-    timestamp = logger.timeformat()
-    tracestring = format_exc()
-    logger.log_trace()
-    if _IN_GAME_ERRORS:
-        receiver.msg(
-            string.format(
-                traceback=tracestring, errmsg=stringtuple[0].strip(), timestamp=timestamp
-            ).strip()
-        )
-    else:
-        receiver.msg(
-            string.format(
-                traceback=tracestring.splitlines()[-1],
-                errmsg=stringtuple[1].strip(),
-                timestamp=timestamp,
-            ).strip()
-        )
-
-
-def _process_input(caller, prompt, result, cmd, generator):
-    """
-    Specifically handle the get_input value to send to _progressive_cmd_run as
-    part of yielding from a Command's `func`.
-
-    Args:
-        caller (Character, Account or Session): the caller.
-        prompt (str): The sent prompt.
-        result (str): The unprocessed answer.
-        cmd (Command): The command itself.
-        generator (GeneratorType): The generator.
-
-    Returns:
-        result (bool): Always `False` (stop processing).
-
-    """
-    # We call it using a Twisted deferLater to make sure the input is properly closed.
-    deferLater(reactor, 0, _progressive_cmd_run, cmd, generator, response=result)
-    return False
-
-
-def _progressive_cmd_run(cmd, generator, response=None):
-    """
-    Progressively call the command that was given in argument. Used
-    when `yield` is present in the Command's `func()` method.
-
-    Args:
-        cmd (Command): the command itself.
-        generator (GeneratorType): the generator describing the processing.
-        reponse (str, optional): the response to send to the generator.
-
-    Raises:
-        ValueError: If the func call yields something not identifiable as a
-            time-delay or a string prompt.
-
-    Note:
-        This function is responsible for executing the command, if
-        the func() method contains 'yield' instructions.  The yielded
-        value will be accessible at each step and will affect the
-        process.  If the value is a number, just delay the execution
-        of the command.  If it's a string, wait for the user input.
-
-    """
-    global _GET_INPUT
-    if not _GET_INPUT:
-        from evennia.utils.evmenu import get_input as _GET_INPUT
-
-    try:
-        if response is None:
-            value = next(generator)
-        else:
-            value = generator.send(response)
-    except StopIteration:
-        # duplicated from cmdhandler._run_command, to have these
-        # run in the right order while staying inside the deferred
-        cmd.at_post_cmd()
-        if cmd.save_for_next:
-            # store a reference to this command, possibly
-            # accessible by the next command.
-            cmd.caller.ndb.last_cmd = copy(cmd)
-        else:
-            cmd.caller.ndb.last_cmd = None
-    else:
-        if isinstance(value, (int, float)):
-            utils.delay(value, _progressive_cmd_run, cmd, generator)
-        elif isinstance(value, str):
-            _GET_INPUT(cmd.caller, value, _process_input, cmd=cmd, generator=generator)
-        else:
-            raise ValueError("unknown type for a yielded value in command: {}".format(type(value)))
-
-
-# custom Exceptions
-
-
-class NoCmdSets(Exception):
-    "No cmdsets found. Critical error."
-    pass
-
-
-class ExecSystemCommand(Exception):
-    "Run a system command"
-
-    def __init__(self, syscmd, sysarg):
-        self.args = (syscmd, sysarg)  # needed by exception error handling
-        self.syscmd = syscmd
-        self.sysarg = sysarg
-
-
-class ErrorReported(Exception):
-    "Re-raised when a subsructure already reported the error"
-
-    def __init__(self, raw_string):
-        self.args = (raw_string,)
-        self.raw_string = raw_string
-
-
-# Helper function
-
-
-@inlineCallbacks
-def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string):
-    """
-    Gather all relevant cmdsets and merge them.
-
-    Args:
-        caller (Session, Account or Object): The entity executing the command. Which
-            type of object this is depends on the current game state; for example
-            when the user is not logged in, this will be a Session, when being OOC
-            it will be an Account and when puppeting an object this will (often) be
-            a Character Object. In the end it depends on where the cmdset is stored.
-        session (Session or None): The Session associated with caller, if any.
-        account (Account or None): The calling Account associated with caller, if any.
-        obj (Object or None): The Object associated with caller, if any.
-        callertype (str): This identifies caller as either "account", "object" or "session"
-            to avoid having to do this check internally.
-        raw_string (str): The input string. This is only used for error reporting.
-
-    Returns:
-        cmdset (Deferred): This deferred fires with the merged cmdset
-        result once merger finishes.
-
-    Notes:
-        The cdmsets are merged in order or generality, so that the
-        Object's cmdset is merged last (and will thus take precedence
-        over same-named and same-prio commands on Account and Session).
-
-    """
-    try:
-
-        @inlineCallbacks
-        def _get_channel_cmdset(account_or_obj):
-            """
-            Helper-method; Get channel-cmdsets
-            """
-            # Create cmdset for all account's available channels
-            try:
-                channel_cmdset = yield CHANNELHANDLER.get_cmdset(account_or_obj)
-                returnValue([channel_cmdset])
-            except Exception:
-                _msg_err(caller, _ERROR_CMDSETS)
-                raise ErrorReported(raw_string)
-
-        @inlineCallbacks
-        def _get_local_obj_cmdsets(obj):
-            """
-            Helper-method; Get Object-level cmdsets
-            """
-            # Gather cmdsets from location, objects in location or carried
-            try:
-                local_obj_cmdsets = [None]
-                try:
-                    location = obj.location
-                except Exception:
-                    location = None
-                if location:
-                    # Gather all cmdsets stored on objects in the room and
-                    # also in the caller's inventory and the location itself
-                    local_objlist = yield (
-                        location.contents_get(exclude=obj) + obj.contents_get() + [location]
-                    )
-                    local_objlist = [o for o in local_objlist if not o._is_deleted]
-                    for lobj in local_objlist:
-                        try:
-                            # call hook in case we need to do dynamic changing to cmdset
-                            _GA(lobj, "at_cmdset_get")(caller=caller)
-                        except Exception:
-                            logger.log_trace()
-                    # the call-type lock is checked here, it makes sure an account
-                    # is not seeing e.g. the commands on a fellow account (which is why
-                    # the no_superuser_bypass must be True)
-                    local_obj_cmdsets = yield list(
-                        chain.from_iterable(
-                            lobj.cmdset.cmdset_stack
-                            for lobj in local_objlist
-                            if (
-                                lobj.cmdset.current
-                                and lobj.access(
-                                    caller, access_type="call", no_superuser_bypass=True
-                                )
-                            )
-                        )
-                    )
-                    for cset in local_obj_cmdsets:
-                        # This is necessary for object sets, or we won't be able to
-                        # separate the command sets from each other in a busy room. We
-                        # only keep the setting if duplicates were set to False/True
-                        # explicitly.
-                        cset.old_duplicates = cset.duplicates
-                        cset.duplicates = True if cset.duplicates is None else cset.duplicates
-                returnValue(local_obj_cmdsets)
-            except Exception:
-                _msg_err(caller, _ERROR_CMDSETS)
-                raise ErrorReported(raw_string)
-
-        @inlineCallbacks
-        def _get_cmdsets(obj):
-            """
-            Helper method; Get cmdset while making sure to trigger all
-            hooks safely. Returns the stack and the valid options.
-            """
-            try:
-                yield obj.at_cmdset_get()
-            except Exception:
-                _msg_err(caller, _ERROR_CMDSETS)
-                raise ErrorReported(raw_string)
-            try:
-                returnValue((obj.cmdset.current, list(obj.cmdset.cmdset_stack)))
-            except AttributeError:
-                returnValue(((None, None, None), []))
-
-        local_obj_cmdsets = []
-        if callertype == "session":
-            # we are calling the command from the session level
-            report_to = session
-            current, cmdsets = yield _get_cmdsets(session)
-            if account:  # this automatically implies logged-in
-                pcurrent, account_cmdsets = yield _get_cmdsets(account)
-                cmdsets += account_cmdsets
-                current = current + pcurrent
-                if obj:
-                    ocurrent, obj_cmdsets = yield _get_cmdsets(obj)
-                    current = current + ocurrent
-                    cmdsets += obj_cmdsets
-                    if not current.no_objs:
-                        local_obj_cmdsets = yield _get_local_obj_cmdsets(obj)
-                        if current.no_exits:
-                            # filter out all exits
-                            local_obj_cmdsets = [
-                                cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
-                            ]
-                        cmdsets += local_obj_cmdsets
-                    if not current.no_channels:
-                        # also objs may have channels
-                        channel_cmdsets = yield _get_channel_cmdset(obj)
-                        cmdsets += channel_cmdsets
-                if not current.no_channels:
-                    channel_cmdsets = yield _get_channel_cmdset(account)
-                    cmdsets += channel_cmdsets
-
-        elif callertype == "account":
-            # we are calling the command from the account level
-            report_to = account
-            current, cmdsets = yield _get_cmdsets(account)
-            if obj:
-                ocurrent, obj_cmdsets = yield _get_cmdsets(obj)
-                current = current + ocurrent
-                cmdsets += obj_cmdsets
-                if not current.no_objs:
-                    local_obj_cmdsets = yield _get_local_obj_cmdsets(obj)
-                    if current.no_exits:
-                        # filter out all exits
-                        local_obj_cmdsets = [
-                            cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
-                        ]
-                    cmdsets += local_obj_cmdsets
-                if not current.no_channels:
-                    # also objs may have channels
-                    cmdsets += yield _get_channel_cmdset(obj)
-            if not current.no_channels:
-                cmdsets += yield _get_channel_cmdset(account)
-
-        elif callertype == "object":
-            # we are calling the command from the object level
-            report_to = obj
-            current, cmdsets = yield _get_cmdsets(obj)
-            if not current.no_objs:
-                local_obj_cmdsets = yield _get_local_obj_cmdsets(obj)
-                if current.no_exits:
-                    # filter out all exits
-                    local_obj_cmdsets = [
-                        cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
-                    ]
-                cmdsets += yield local_obj_cmdsets
-            if not current.no_channels:
-                # also objs may have channels
-                cmdsets += yield _get_channel_cmdset(obj)
-        else:
-            raise Exception("get_and_merge_cmdsets: callertype %s is not valid." % callertype)
-
-        # weed out all non-found sets
-        cmdsets = yield [cmdset for cmdset in cmdsets if cmdset and cmdset.key != "_EMPTY_CMDSET"]
-        # report cmdset errors to user (these should already have been logged)
-        yield [
-            report_to.msg(cmdset.errmessage) for cmdset in cmdsets if cmdset.key == "_CMDSET_ERROR"
-        ]
-
-        if cmdsets:
-            # faster to do tuple on list than to build tuple directly
-            mergehash = tuple([id(cmdset) for cmdset in cmdsets])
-            if mergehash in _CMDSET_MERGE_CACHE:
-                # cached merge exist; use that
-                cmdset = _CMDSET_MERGE_CACHE[mergehash]
-            else:
-                # we group and merge all same-prio cmdsets separately (this avoids
-                # order-dependent clashes in certain cases, such as
-                # when duplicates=True)
-                tempmergers = {}
-                for cmdset in cmdsets:
-                    prio = cmdset.priority
-                    if prio in tempmergers:
-                        # merge same-prio cmdset together separately
-                        tempmergers[prio] = yield tempmergers[prio] + cmdset
-                    else:
-                        tempmergers[prio] = cmdset
-
-                # sort cmdsets after reverse priority (highest prio are merged in last)
-                sorted_cmdsets = yield sorted(list(tempmergers.values()), key=lambda x: x.priority)
-
-                # Merge all command sets into one, beginning with the lowest-prio one
-                cmdset = sorted_cmdsets[0]
-                for merging_cmdset in sorted_cmdsets[1:]:
-                    cmdset = yield cmdset + merging_cmdset
-                # store the original, ungrouped set for diagnosis
-                cmdset.merged_from = cmdsets
-                # cache
-                _CMDSET_MERGE_CACHE[mergehash] = cmdset
-        else:
-            cmdset = None
-        for cset in (cset for cset in local_obj_cmdsets if cset):
-            cset.duplicates = cset.old_duplicates
-        # important - this syncs the CmdSetHandler's .current field with the
-        # true current cmdset!
-        if cmdset:
-            caller.cmdset.current = cmdset
-
-        returnValue(cmdset)
-    except ErrorReported:
-        raise
-    except Exception:
-        _msg_err(caller, _ERROR_CMDSETS)
-        raise
-        # raise ErrorReported
-
-
-# Main command-handler function
-
-
-
[docs]@inlineCallbacks -def cmdhandler( - called_by, - raw_string, - _testing=False, - callertype="session", - session=None, - cmdobj=None, - cmdobj_key=None, - **kwargs, -): - """ - This is the main mechanism that handles any string sent to the engine. - - Args: - called_by (Session, Account or Object): Object from which this - command was called. which this was called from. What this is - depends on the game state. - raw_string (str): The command string as given on the command line. - _testing (bool, optional): Used for debug purposes and decides if we - should actually execute the command or not. If True, the - command instance will be returned. - callertype (str, optional): One of "session", "account" or - "object". These are treated in decending order, so when the - Session is the caller, it will merge its own cmdset into - cmdsets from both Account and eventual puppeted Object (and - cmdsets in its room etc). An Account will only include its own - cmdset and the Objects and so on. Merge order is the same - order, so that Object cmdsets are merged in last, giving them - precendence for same-name and same-prio commands. - session (Session, optional): Relevant if callertype is "account" - the session will help - retrieve the correct cmdsets from puppeted objects. - cmdobj (Command, optional): If given a command instance, this will be executed using - `called_by` as the caller, `raw_string` representing its arguments and (optionally) - `cmdobj_key` as its input command name. No cmdset lookup will be performed but - all other options apply as normal. This allows for running a specific Command - within the command system mechanism. - cmdobj_key (string, optional): Used together with `cmdobj` keyword to specify - which cmdname should be assigned when calling the specified Command instance. This - is made available as `self.cmdstring` when the Command runs. - If not given, the command will be assumed to be called as `cmdobj.key`. - - Keyword Args: - kwargs (any): other keyword arguments will be assigned as named variables on the - retrieved command object *before* it is executed. This is unused - in default Evennia but may be used by code to set custom flags or - special operating conditions for a command as it executes. - - Returns: - deferred (Deferred): This deferred is fired with the return - value of the command's `func` method. This is not used in - default Evennia. - - """ - - @inlineCallbacks - def _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account): - """ - Helper function: This initializes and runs the Command - instance once the parser has identified it as either a normal - command or one of the system commands. - - Args: - cmd (Command): Command object - cmdname (str): Name of command - args (str): extra text entered after the identified command - raw_cmdname (str): Name of Command, unaffected by eventual - prefix-stripping (if no prefix-stripping, this is the same - as cmdname). - cmdset (CmdSet): Command sert the command belongs to (if any).. - session (Session): Session of caller (if any). - account (Account): Account of caller (if any). - - Returns: - deferred (Deferred): this will fire with the return of the - command's `func` method. - - Raises: - RuntimeError: If command recursion limit was reached. - - """ - global _COMMAND_NESTING - try: - # Assign useful variables to the instance - cmd.caller = caller - cmd.cmdname = cmdname - cmd.raw_cmdname = raw_cmdname - cmd.cmdstring = cmdname # deprecated - cmd.args = args - cmd.cmdset = cmdset - cmd.session = session - cmd.account = account - cmd.raw_string = unformatted_raw_string - # cmd.obj # set via on-object cmdset handler for each command, - # since this may be different for every command when - # merging multiple cmdsets - - if hasattr(cmd, "obj") and hasattr(cmd.obj, "scripts"): - # cmd.obj is automatically made available by the cmdhandler. - # we make sure to validate its scripts. - yield cmd.obj.scripts.validate() - - if _testing: - # only return the command instance - returnValue(cmd) - - # assign custom kwargs to found cmd object - for key, val in kwargs.items(): - setattr(cmd, key, val) - - _COMMAND_NESTING[called_by] += 1 - if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT: - err = _ERROR_RECURSION_LIMIT.format( - recursion_limit=_COMMAND_RECURSION_LIMIT, - raw_cmdname=raw_cmdname, - cmdclass=cmd.__class__, - ) - raise RuntimeError(err) - - # pre-command hook - abort = yield cmd.at_pre_cmd() - if abort: - # abort sequence - returnValue(abort) - - # Parse and execute - yield cmd.parse() - - # main command code - # (return value is normally None) - ret = cmd.func() - if isinstance(ret, types.GeneratorType): - # cmd.func() is a generator, execute progressively - _progressive_cmd_run(cmd, ret) - ret = yield ret - # note that the _progressive_cmd_run will itself run - # the at_post_cmd etc as it finishes; this is a bit of - # code duplication but there seems to be no way to - # catch the StopIteration here (it's not in the same - # frame since this is in a deferred chain) - else: - ret = yield ret - # post-command hook - yield cmd.at_post_cmd() - - if cmd.save_for_next: - # store a reference to this command, possibly - # accessible by the next command. - caller.ndb.last_cmd = yield copy(cmd) - else: - caller.ndb.last_cmd = None - - # return result to the deferred - returnValue(ret) - - except InterruptCommand: - # Do nothing, clean exit - pass - except Exception: - _msg_err(caller, _ERROR_UNTRAPPED) - raise ErrorReported(raw_string) - finally: - _COMMAND_NESTING[called_by] -= 1 - - session, account, obj = session, None, None - if callertype == "session": - session = called_by - account = session.account - obj = session.puppet - elif callertype == "account": - account = called_by - if session: - obj = yield session.puppet - elif callertype == "object": - obj = called_by - else: - raise RuntimeError("cmdhandler: callertype %s is not valid." % callertype) - # the caller will be the one to receive messages and excert its permissions. - # we assign the caller with preference 'bottom up' - caller = obj or account or session - # The error_to is the default recipient for errors. Tries to make sure an account - # does not get spammed for errors while preserving character mirroring. - error_to = obj or session or account - - try: # catch bugs in cmdhandler itself - try: # catch special-type commands - if cmdobj: - # the command object is already given - cmd = cmdobj() if callable(cmdobj) else cmdobj - cmdname = cmdobj_key if cmdobj_key else cmd.key - args = raw_string - unformatted_raw_string = "%s%s" % (cmdname, args) - cmdset = None - raw_cmdname = cmdname - # session = session - # account = account - - else: - # no explicit cmdobject given, figure it out - cmdset = yield get_and_merge_cmdsets( - caller, session, account, obj, callertype, raw_string - ) - if not cmdset: - # this is bad and shouldn't happen. - raise NoCmdSets - # store the completely unmodified raw string - including - # whitespace and eventual prefixes-to-be-stripped. - unformatted_raw_string = raw_string - raw_string = raw_string.strip() - if not raw_string: - # Empty input. Test for system command instead. - syscmd = yield cmdset.get(CMD_NOINPUT) - sysarg = "" - raise ExecSystemCommand(syscmd, sysarg) - # Parse the input string and match to available cmdset. - # This also checks for permissions, so all commands in match - # are commands the caller is allowed to call. - matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) - - # Deal with matches - - if len(matches) > 1: - # We have a multiple-match - syscmd = yield cmdset.get(CMD_MULTIMATCH) - sysarg = _("There were multiple matches.") - if syscmd: - # use custom CMD_MULTIMATCH - syscmd.matches = matches - else: - # fall back to default error handling - sysarg = yield _SEARCH_AT_RESULT( - [match[2] for match in matches], caller, query=matches[0][0] - ) - raise ExecSystemCommand(syscmd, sysarg) - - cmdname, args, cmd, raw_cmdname = "", "", None, "" - if len(matches) == 1: - # We have a unique command match. But it may still be invalid. - match = matches[0] - cmdname, args, cmd, raw_cmdname = (match[0], match[1], match[2], match[5]) - - if not matches: - # No commands match our entered command - syscmd = yield cmdset.get(CMD_NOMATCH) - if syscmd: - # use custom CMD_NOMATCH command - sysarg = raw_string - else: - # fallback to default error text - sysarg = _("Command '{command}' is not available.").format( - command=raw_string - ) - suggestions = string_suggestions( - raw_string, - cmdset.get_all_cmd_keys_and_aliases(caller), - cutoff=0.7, - maxnum=3, - ) - if suggestions: - sysarg += _(" Maybe you meant {command}?").format( - command=utils.list_to_string(suggestions, _("or"), addquote=True) - ) - else: - sysarg += _(' Type "help" for help.') - raise ExecSystemCommand(syscmd, sysarg) - - # Check if this is a Channel-cmd match. - if hasattr(cmd, "is_channel") and cmd.is_channel: - # even if a user-defined syscmd is not defined, the - # found cmd is already a system command in its own right. - syscmd = yield cmdset.get(CMD_CHANNEL) - if syscmd: - # replace system command with custom version - cmd = syscmd - cmd.session = session - sysarg = "%s:%s" % (cmdname, args) - raise ExecSystemCommand(cmd, sysarg) - - # A normal command. - ret = yield _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account) - returnValue(ret) - - except ErrorReported as exc: - # this error was already reported, so we - # catch it here and don't pass it on. - logger.log_err("User input was: '%s'." % exc.raw_string) - - except ExecSystemCommand as exc: - # Not a normal command: run a system command, if available, - # or fall back to a return string. - syscmd = exc.syscmd - sysarg = exc.sysarg - - if syscmd: - ret = yield _run_command( - syscmd, syscmd.key, sysarg, unformatted_raw_string, cmdset, session, account - ) - returnValue(ret) - elif sysarg: - # return system arg - error_to.msg(exc.sysarg) - - except NoCmdSets: - # Critical error. - logger.log_err("No cmdsets found: %s" % caller) - error_to.msg(_ERROR_NOCMDSETS) - - except Exception: - # We should not end up here. If we do, it's a programming bug. - _msg_err(error_to, _ERROR_UNTRAPPED) - - except Exception: - # This catches exceptions in cmdhandler exceptions themselves - _msg_err(error_to, _ERROR_CMDHANDLER)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/cmdparser.html b/docs/0.9.5/_modules/evennia/commands/cmdparser.html deleted file mode 100644 index be4fc255cc..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/cmdparser.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - - - - - evennia.commands.cmdparser — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.cmdparser

-"""
-The default command parser. Use your own by assigning
-`settings.COMMAND_PARSER` to a Python path to a module containing the
-replacing cmdparser function. The replacement parser must accept the
-same inputs as the default one.
-
-"""
-
-
-import re
-from django.conf import settings
-from evennia.utils.logger import log_trace
-
-_MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U)
-_CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
-
-
-
[docs]def create_match(cmdname, string, cmdobj, raw_cmdname): - """ - Builds a command match by splitting the incoming string and - evaluating the quality of the match. - - Args: - cmdname (str): Name of command to check for. - string (str): The string to match against. - cmdobj (str): The full Command instance. - raw_cmdname (str, optional): If CMD_IGNORE_PREFIX is set and the cmdname starts with - one of the prefixes to ignore, this contains the raw, unstripped cmdname, - otherwise it is None. - - Returns: - match (tuple): This is on the form (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname), - where `cmdname` is the command's name and `args` is the rest of the incoming - string, without said command name. `cmdobj` is - the Command instance, the cmdlen is the same as len(cmdname) and mratio - is a measure of how big a part of the full input string the cmdname - takes up - an exact match would be 1.0. Finally, the `raw_cmdname` is - the cmdname unmodified by eventual prefix-stripping. - - """ - cmdlen, strlen = len(str(cmdname)), len(str(string)) - mratio = 1 - (strlen - cmdlen) / (1.0 * strlen) - args = string[cmdlen:] - return (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname)
- - -
[docs]def build_matches(raw_string, cmdset, include_prefixes=False): - """ - Build match tuples by matching raw_string against available commands. - - Args: - raw_string (str): Input string that can look in any way; the only assumption is - that the sought command's name/alias must be *first* in the string. - cmdset (CmdSet): The current cmdset to pick Commands from. - include_prefixes (bool): If set, include prefixes like @, ! etc (specified in settings) - in the match, otherwise strip them before matching. - - Returns: - matches (list) A list of match tuples created by `cmdparser.create_match`. - - """ - matches = [] - try: - if include_prefixes: - # use the cmdname as-is - l_raw_string = raw_string.lower() - for cmd in cmdset: - matches.extend( - [ - create_match(cmdname, raw_string, cmd, cmdname) - for cmdname in [cmd.key] + cmd.aliases - if cmdname - and l_raw_string.startswith(cmdname.lower()) - and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname) :])) - ] - ) - else: - # strip prefixes set in settings - raw_string = ( - raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string - ) - l_raw_string = raw_string.lower() - for cmd in cmdset: - for raw_cmdname in [cmd.key] + cmd.aliases: - cmdname = ( - raw_cmdname.lstrip(_CMD_IGNORE_PREFIXES) - if len(raw_cmdname) > 1 - else raw_cmdname - ) - if ( - cmdname - and l_raw_string.startswith(cmdname.lower()) - and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname) :])) - ): - matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname)) - except Exception: - log_trace("cmdhandler error. raw_input:%s" % raw_string) - return matches
- - -
[docs]def try_num_prefixes(raw_string): - """ - Test if user tried to separate multi-matches with a number separator - (default 1-name, 2-name etc). This is usually called last, if no other - match was found. - - Args: - raw_string (str): The user input to parse. - - Returns: - mindex, new_raw_string (tuple): If a multimatch-separator was detected, - this is stripped out as an integer to separate between the matches. The - new_raw_string is the result of stripping out that identifier. If no - such form was found, returns (None, None). - - Example: - In the default configuration, entering 2-ball (e.g. in a room will more - than one 'ball' object), will lead to a multimatch and this function - will parse `"2-ball"` and return `(2, "ball")`. - - """ - # no matches found - num_ref_match = _MULTIMATCH_REGEX.match(raw_string) - if num_ref_match: - # the user might be trying to identify the command - # with a #num-command style syntax. We expect the regex to - # contain the groups "number" and "name". - mindex, new_raw_string = (num_ref_match.group("number"), num_ref_match.group("name")) - return mindex, new_raw_string - else: - return None, None
- - -
[docs]def cmdparser(raw_string, cmdset, caller, match_index=None): - """ - This function is called by the cmdhandler once it has - gathered and merged all valid cmdsets valid for this particular parsing. - - Args: - raw_string (str): The unparsed text entered by the caller. - cmdset (CmdSet): The merged, currently valid cmdset - caller (Session, Account or Object): The caller triggering this parsing. - match_index (int, optional): Index to pick a given match in a - list of same-named command matches. If this is given, it suggests - this is not the first time this function was called: normally - the first run resulted in a multimatch, and the index is given - to select between the results for the second run. - - Returns: - matches (list): This is a list of match-tuples as returned by `create_match`. - If no matches were found, this is an empty list. - - Notes: - The cmdparser understand the following command combinations (where - [] marks optional parts. - - ``` - [cmdname[ cmdname2 cmdname3 ...] [the rest] - ``` - - A command may consist of any number of space-separated words of any - length, and contain any character. It may also be empty. - - The parser makes use of the cmdset to find command candidates. The - parser return a list of matches. Each match is a tuple with its - first three elements being the parsed cmdname (lower case), - the remaining arguments, and the matched cmdobject from the cmdset. - - """ - if not raw_string: - return [] - - # find mathces, first using the full name - matches = build_matches(raw_string, cmdset, include_prefixes=True) - if not matches: - # try to match a number 1-cmdname, 2-cmdname etc - mindex, new_raw_string = try_num_prefixes(raw_string) - if mindex is not None: - return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) - if _CMD_IGNORE_PREFIXES: - # still no match. Try to strip prefixes - raw_string = ( - raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string - ) - matches = build_matches(raw_string, cmdset, include_prefixes=False) - - # only select command matches we are actually allowed to call. - matches = [match for match in matches if match[2].access(caller, "cmd")] - - # try to bring the number of matches down to 1 - if len(matches) > 1: - # See if it helps to analyze the match with preserved case but only if - # it leaves at least one match. - trimmed = [match for match in matches if raw_string.startswith(match[0])] - if trimmed: - matches = trimmed - - if len(matches) > 1: - # we still have multiple matches. Sort them by count quality. - matches = sorted(matches, key=lambda m: m[3]) - # only pick the matches with highest count quality - quality = [mat[3] for mat in matches] - matches = matches[-quality.count(quality[-1]) :] - - if len(matches) > 1: - # still multiple matches. Fall back to ratio-based quality. - matches = sorted(matches, key=lambda m: m[4]) - # only pick the highest rated ratio match - quality = [mat[4] for mat in matches] - matches = matches[-quality.count(quality[-1]) :] - - if len(matches) > 1 and match_index is not None: - # We couldn't separate match by quality, but we have an - # index argument to tell us which match to use. - if 0 < match_index <= len(matches): - matches = [matches[match_index - 1]] - else: - # we tried to give an index outside of the range - this means - # a no-match - matches = [] - - # no matter what we have at this point, we have to return it. - return matches
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/cmdset.html b/docs/0.9.5/_modules/evennia/commands/cmdset.html deleted file mode 100644 index 13f5ab06c9..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/cmdset.html +++ /dev/null @@ -1,784 +0,0 @@ - - - - - - - - evennia.commands.cmdset — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.cmdset

-"""
-
-A Command Set (CmdSet) holds a set of commands. The Cmdsets can be
-merged and combined to create new sets of commands in a
-non-destructive way. This makes them very powerful for implementing
-custom game states where different commands (or different variations
-of commands) are available to the accounts depending on circumstance.
-
-The available merge operations are partly borrowed from mathematical
-Set theory.
-
-
-* Union The two command sets are merged so that as many commands as
-    possible of each cmdset ends up in the merged cmdset. Same-name
-    commands are merged by priority.  This is the most common default.
-    Ex: A1,A3 + B1,B2,B4,B5 = A1,B2,A3,B4,B5
-* Intersect - Only commands found in *both* cmdsets (i.e. which have
-    same names) end up in the merged cmdset, with the higher-priority
-    cmdset replacing the lower one. Ex: A1,A3 + B1,B2,B4,B5 = A1
-* Replace -   The commands of this cmdset completely replaces the
-    lower-priority cmdset's commands, regardless of if same-name commands
-    exist. Ex: A1,A3 + B1,B2,B4,B5 = A1,A3
-* Remove -    This removes the relevant commands from the
-    lower-priority cmdset completely.  They are not replaced with
-    anything, so this in effects uses the high-priority cmdset as a filter
-    to affect the low-priority cmdset.  Ex: A1,A3 + B1,B2,B4,B5 = B2,B4,B5
-
-"""
-from weakref import WeakKeyDictionary
-from django.utils.translation import gettext as _
-from evennia.utils.utils import inherits_from, is_iter
-
-__all__ = ("CmdSet",)
-
-
-class _CmdSetMeta(type):
-    """
-    This metaclass makes some minor on-the-fly convenience fixes to
-    the cmdset class.
-
-    """
-
-    def __init__(cls, *args, **kwargs):
-        """
-        Fixes some things in the cmdclass
-
-        """
-        # by default we key the cmdset the same as the
-        # name of its class.
-        if not hasattr(cls, "key") or not cls.key:
-            cls.key = cls.__name__
-        cls.path = "%s.%s" % (cls.__module__, cls.__name__)
-
-        if not isinstance(cls.key_mergetypes, dict):
-            cls.key_mergetypes = {}
-
-        super().__init__(*args, **kwargs)
-
-
-
[docs]class CmdSet(object, metaclass=_CmdSetMeta): - """ - This class describes a unique cmdset that understands priorities. - CmdSets can be merged and made to perform various set operations - on each other. CmdSets have priorities that affect which of their - ingoing commands gets used. - - In the examples, cmdset A always have higher priority than cmdset B. - - key - the name of the cmdset. This can be used on its own for game - operations - - mergetype (partly from Set theory): - - Union - The two command sets are merged so that as many - commands as possible of each cmdset ends up in the - merged cmdset. Same-name commands are merged by - priority. This is the most common default. - Ex: A1,A3 + B1,B2,B4,B5 = A1,B2,A3,B4,B5 - Intersect - Only commands found in *both* cmdsets - (i.e. which have same names) end up in the merged - cmdset, with the higher-priority cmdset replacing the - lower one. Ex: A1,A3 + B1,B2,B4,B5 = A1 - Replace - The commands of this cmdset completely replaces - the lower-priority cmdset's commands, regardless - of if same-name commands exist. - Ex: A1,A3 + B1,B2,B4,B5 = A1,A3 - Remove - This removes the relevant commands from the - lower-priority cmdset completely. They are not - replaced with anything, so this in effects uses the - high-priority cmdset as a filter to affect the - low-priority cmdset. - Ex: A1,A3 + B1,B2,B4,B5 = B2,B4,B5 - - Note: Commands longer than 2 characters and starting - with double underscrores, like '__noinput_command' - are considered 'system commands' and are - excempt from all merge operations - they are - ALWAYS included across mergers and only affected - if same-named system commands replace them. - - priority- All cmdsets are always merged in pairs of two so that - the higher set's mergetype is applied to the - lower-priority cmdset. Default commands have priority 0, - high-priority ones like Exits and Channels have 10 and 9. - Priorities can be negative as well to give default - commands preference. - - duplicates - determines what happens when two sets of equal - priority merge (only). Defaults to None and has the first of them in the - merger (i.e. A above) automatically taking - precedence. But if `duplicates` is true, the - result will be a merger with more than one of each - name match. This will usually lead to the account - receiving a multiple-match error higher up the road, - but can be good for things like cmdsets on non-account - objects in a room, to allow the system to warn that - more than one 'ball' in the room has the same 'kick' - command defined on it, so it may offer a chance to - select which ball to kick ... Allowing duplicates - only makes sense for Union and Intersect, the setting - is ignored for the other mergetypes. - Note that the `duplicates` flag is *not* propagated in - a cmdset merger. So `A + B = C` will result in - a cmdset with duplicate commands, but C.duplicates will - be `None`. For duplication to apply to a whole cmdset - stack merge, _all_ cmdsets in the stack must have - `.duplicates=True` set. - Finally, if a final cmdset has `.duplicates=None` (the normal - unless created alone with another value), the cmdhandler - will assume True for object-based cmdsets and False for - all other. This is usually the most intuitive outcome. - - key_mergetype (dict) - allows the cmdset to define a unique - mergetype for particular cmdsets. Format is - {CmdSetkeystring:mergetype}. Priorities still apply. - Example: {'Myevilcmdset','Replace'} which would make - sure for this set to always use 'Replace' on - Myevilcmdset no matter what overall mergetype this set - has. - - no_objs - don't include any commands from nearby objects - when searching for suitable commands - no_exits - ignore the names of exits when matching against - commands - no_channels - ignore the name of channels when matching against - commands (WARNING- this is dangerous since the - account can then not even ask staff for help if - something goes wrong) - - - """ - - key = "Unnamed CmdSet" - mergetype = "Union" - priority = 0 - - # These flags, if set to None should be interpreted as 'I don't care' and, - # will allow "pass-through" even of lower-prio cmdsets' explicitly True/False - # options. If this is set to True/False however, priority matters. - no_exits = None - no_objs = None - no_channels = None - # The .duplicates setting does not propagate and since duplicates can only happen - # on same-prio cmdsets, there is no concept of passthrough on `None`. - # The merger of two cmdsets always return in a cmdset with `duplicates=None` - # (even if the result may have duplicated commands). - # If a final cmdset has `duplicates=None` (normal, unless the cmdset is - # created on its own with the flag set), the cmdhandler will auto-assume it to be - # True for Object-based cmdsets and stay None/False for all other entities. - # - # Example: - # A and C has .duplicates=True, B has .duplicates=None (or False) - # B + A = BA, where BA will have duplicate cmds, but BA.duplicates = None - # BA + C = BAC, where BAC will have more duplication, but BAC.duplicates = None - # - # Basically, for the `.duplicate` setting to survive throughout a - # merge-stack, every cmdset in the stack must have `duplicates` set explicitly. - duplicates = None - - permanent = False - key_mergetypes = {} - errmessage = "" - # pre-store properties to duplicate straight off - to_duplicate = ( - "key", - "cmdsetobj", - "no_exits", - "no_objs", - "no_channels", - "permanent", - "mergetype", - "priority", - "duplicates", - "errmessage", - ) - -
[docs] def __init__(self, cmdsetobj=None, key=None): - """ - Creates a new CmdSet instance. - - Args: - cmdsetobj (Session, Account, Object, optional): This is the database object - to which this particular instance of cmdset is related. It - is often a character but may also be a regular object, Account - or Session. - key (str, optional): The idenfier for this cmdset. This - helps if wanting to selectively remov cmdsets. - - """ - - if key: - self.key = key - self.commands = [] - self.system_commands = [] - self.actual_mergetype = self.mergetype - self.cmdsetobj = cmdsetobj - # this is set only on merged sets, in cmdhandler.py, in order to - # track, list and debug mergers correctly. - self.merged_from = [] - - # initialize system - self.at_cmdset_creation() - self._contains_cache = WeakKeyDictionary() # {}
- - # Priority-sensitive merge operations for cmdsets - - def _union(self, cmdset_a, cmdset_b): - """ - Merge two sets using union merger - - Args: - cmdset_a (Cmdset): Cmdset given higher priority in the case of a tie. - cmdset_b (Cmdset): Cmdset given lower priority in the case of a tie. - - Returns: - cmdset_c (Cmdset): The result of A U B operation. - - Notes: - Union, C = A U B, means that C gets all elements from both A and B. - - """ - cmdset_c = cmdset_a._duplicate() - # we make copies, not refs by use of [:] - cmdset_c.commands = cmdset_a.commands[:] - if cmdset_a.duplicates and cmdset_a.priority == cmdset_b.priority: - cmdset_c.commands.extend(cmdset_b.commands) - else: - cmdset_c.commands.extend([cmd for cmd in cmdset_b if cmd not in cmdset_a]) - return cmdset_c - - def _intersect(self, cmdset_a, cmdset_b): - """ - Merge two sets using intersection merger - - Args: - cmdset_a (Cmdset): Cmdset given higher priority in the case of a tie. - cmdset_b (Cmdset): Cmdset given lower priority in the case of a tie. - - Returns: - cmdset_c (Cmdset): The result of A (intersect) B operation. - - Notes: - Intersection, C = A (intersect) B, means that C only gets the - parts of A and B that are the same (that is, the commands - of each set having the same name. Only the one of these - having the higher prio ends up in C). - - """ - cmdset_c = cmdset_a._duplicate() - if cmdset_a.duplicates and cmdset_a.priority == cmdset_b.priority: - for cmd in [cmd for cmd in cmdset_a if cmd in cmdset_b]: - cmdset_c.add(cmd) - cmdset_c.add(cmdset_b.get(cmd)) - else: - cmdset_c.commands = [cmd for cmd in cmdset_a if cmd in cmdset_b] - return cmdset_c - - def _replace(self, cmdset_a, cmdset_b): - """ - Replace the contents of one set with another - - Args: - cmdset_a (Cmdset): Cmdset replacing - cmdset_b (Cmdset): Cmdset to replace - - Returns: - cmdset_c (Cmdset): This is indentical to cmdset_a. - - Notes: - C = A, where B is ignored. - - """ - cmdset_c = cmdset_a._duplicate() - cmdset_c.commands = cmdset_a.commands[:] - return cmdset_c - - def _remove(self, cmdset_a, cmdset_b): - """ - Filter a set by another. - - Args: - cmdset_a (Cmdset): Cmdset acting as a removal filter. - cmdset_b (Cmdset): Cmdset to filter - - Returns: - cmdset_c (Cmdset): B, with all matching commands from A removed. - - Notes: - C = B - A, where A is used to remove the commands of B. - - """ - - cmdset_c = cmdset_a._duplicate() - cmdset_c.commands = [cmd for cmd in cmdset_b if cmd not in cmdset_a] - return cmdset_c - - def _instantiate(self, cmd): - """ - checks so that object is an instantiated command and not, say - a cmdclass. If it is, instantiate it. Other types, like - strings, are passed through. - - Args: - cmd (any): Entity to analyze. - - Returns: - result (any): An instantiated Command or the input unmodified. - - """ - if callable(cmd): - return cmd() - else: - return cmd - - def _duplicate(self): - """ - Returns a new cmdset with the same settings as this one (no - actual commands are copied over) - - Returns: - cmdset (Cmdset): A copy of the current cmdset. - """ - cmdset = CmdSet() - for key, val in ((key, getattr(self, key)) for key in self.to_duplicate): - if val != getattr(cmdset, key): - # only copy if different from default; avoid turning - # class-vars into instance vars - setattr(cmdset, key, val) - cmdset.key_mergetypes = self.key_mergetypes.copy() - return cmdset - - def __str__(self): - """ - Show all commands in cmdset when printing it. - - Returns: - commands (str): Representation of commands in Cmdset. - - """ - perm = "perm" if self.permanent else "non-perm" - options = ", ".join( - [ - "{}:{}".format(opt, "T" if getattr(self, opt) else "F") - for opt in ("no_exits", "no_objs", "no_channels", "duplicates") - if getattr(self, opt) is not None - ] - ) - options = (", " + options) if options else "" - return ( - f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: " - + ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)]) - ) - - def __iter__(self): - """ - Allows for things like 'for cmd in cmdset': - - Returns: - iterable (iter): Commands in Cmdset. - - """ - return iter(self.commands) - - def __contains__(self, othercmd): - """ - Returns True if this cmdset contains the given command (as - defined by command name and aliases). This allows for things - like 'if cmd in cmdset' - - """ - ret = self._contains_cache.get(othercmd) - if ret is None: - ret = othercmd in self.commands - self._contains_cache[othercmd] = ret - return ret - - def __add__(self, cmdset_a): - """ - Merge this cmdset (B) with another cmdset (A) using the + operator, - - C = B + A - - Here, we (by convention) say that 'A is merged onto B to form - C'. The actual merge operation used in the 'addition' depends - on which priorities A and B have. The one of the two with the - highest priority will apply and give its properties to C. In - the case of a tie, A takes priority and replaces the - same-named commands in B unless A has the 'duplicate' variable - set (which means both sets' commands are kept). - """ - - # It's okay to merge with None - if not cmdset_a: - return self - - sys_commands_a = cmdset_a.get_system_cmds() - sys_commands_b = self.get_system_cmds() - - if self.priority <= cmdset_a.priority: - # A higher or equal priority to B - - # preserve system __commands - sys_commands = sys_commands_a + [ - cmd for cmd in sys_commands_b if cmd not in sys_commands_a - ] - - mergetype = cmdset_a.key_mergetypes.get(self.key, cmdset_a.mergetype) - if mergetype == "Intersect": - cmdset_c = self._intersect(cmdset_a, self) - elif mergetype == "Replace": - cmdset_c = self._replace(cmdset_a, self) - elif mergetype == "Remove": - cmdset_c = self._remove(cmdset_a, self) - else: # Union - cmdset_c = self._union(cmdset_a, self) - - # pass through options whenever they are set, unless the merging or higher-prio - # set changes the setting (i.e. has a non-None value). We don't pass through - # the duplicates setting; that is per-merge; the resulting .duplicates value - # is always None (so merging cmdsets must all have explicit values if wanting - # to cause duplicates). - cmdset_c.no_channels = ( - self.no_channels if cmdset_a.no_channels is None else cmdset_a.no_channels - ) - cmdset_c.no_exits = self.no_exits if cmdset_a.no_exits is None else cmdset_a.no_exits - cmdset_c.no_objs = self.no_objs if cmdset_a.no_objs is None else cmdset_a.no_objs - cmdset_c.duplicates = None - - else: - # B higher priority than A - - # preserver system __commands - sys_commands = sys_commands_b + [ - cmd for cmd in sys_commands_a if cmd not in sys_commands_b - ] - - mergetype = self.key_mergetypes.get(cmdset_a.key, self.mergetype) - if mergetype == "Intersect": - cmdset_c = self._intersect(self, cmdset_a) - elif mergetype == "Replace": - cmdset_c = self._replace(self, cmdset_a) - elif mergetype == "Remove": - cmdset_c = self._remove(self, cmdset_a) - else: # Union - cmdset_c = self._union(self, cmdset_a) - - # pass through options whenever they are set, unless the higher-prio - # set changes the setting (i.e. has a non-None value). We don't pass through - # the duplicates setting; that is per-merge; the resulting .duplicates value# - # is always None (so merging cmdsets must all have explicit values if wanting - # to cause duplicates). - cmdset_c.no_channels = ( - cmdset_a.no_channels if self.no_channels is None else self.no_channels - ) - cmdset_c.no_exits = cmdset_a.no_exits if self.no_exits is None else self.no_exits - cmdset_c.no_objs = cmdset_a.no_objs if self.no_objs is None else self.no_objs - cmdset_c.duplicates = None - - # we store actual_mergetype since key_mergetypes - # might be different from the main mergetype. - # This is used for diagnosis. - cmdset_c.actual_mergetype = mergetype - - # print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority, cmdset_a.key, cmdset_a.priority) - - # return the system commands to the cmdset - cmdset_c.add(sys_commands, allow_duplicates=True) - return cmdset_c - -
[docs] def add(self, cmd, allow_duplicates=False): - """ - Add a new command or commands to this CmdSet, a list of - commands or a cmdset to this cmdset. Note that this is *not* - a merge operation (that is handled by the + operator). - - Args: - cmd (Command, list, Cmdset): This allows for adding one or - more commands to this Cmdset in one go. If another Cmdset - is given, all its commands will be added. - allow_duplicates (bool, optional): If set, will not try to remove - duplicate cmds in the set. This is needed during the merge process - to avoid wiping commands coming from cmdsets with duplicate=True. - - Notes: - If cmd already exists in set, it will replace the old one - (no priority checking etc happens here). This is very useful - when overloading default commands). - - If cmd is another cmdset class or -instance, the commands of - that command set is added to this one, as if they were part of - the original cmdset definition. No merging or priority checks - are made, rather later added commands will simply replace - existing ones to make a unique set. - - """ - - if inherits_from(cmd, "evennia.commands.cmdset.CmdSet"): - # cmd is a command set so merge all commands in that set - # to this one. We raise a visible error if we created - # an infinite loop (adding cmdset to itself somehow) - cmdset = cmd - try: - cmdset = self._instantiate(cmdset) - except RuntimeError: - err = ("Adding cmdset {cmdset} to {cls} lead to an " - "infinite loop. When adding a cmdset to another, " - "make sure they are not themself cyclically added to " - "the new cmdset somewhere in the chain.") - raise RuntimeError(_(err.format(cmdset=cmdset, cls=self.__class__))) - cmds = cmdset.commands - elif is_iter(cmd): - cmds = [self._instantiate(c) for c in cmd] - else: - cmds = [self._instantiate(cmd)] - commands = self.commands - system_commands = self.system_commands - for cmd in cmds: - # add all commands - if not hasattr(cmd, "obj") or cmd.obj is None: - cmd.obj = self.cmdsetobj - try: - ic = commands.index(cmd) - commands[ic] = cmd # replace - except ValueError: - commands.append(cmd) - self.commands = commands - if not allow_duplicates: - # extra run to make sure to avoid doublets - self.commands = list(set(self.commands)) - # add system_command to separate list as well, - # for quick look-up - if cmd.key.startswith("__"): - try: - ic = system_commands.index(cmd) - system_commands[ic] = cmd # replace - except ValueError: - system_commands.append(cmd)
- -
[docs] def remove(self, cmd): - """ - Remove a command instance from the cmdset. - - Args: - cmd (Command or str): Either the Command object to remove - or the key of such a command. - - """ - cmd = self._instantiate(cmd) - if cmd.key.startswith("__"): - try: - ic = self.system_commands.index(cmd) - del self.system_commands[ic] - except ValueError: - # ignore error - pass - else: - self.commands = [oldcmd for oldcmd in self.commands if oldcmd != cmd]
- -
[docs] def get(self, cmd): - """ - Get a command from the cmdset. This is mostly useful to - check if the command is part of this cmdset or not. - - Args: - cmd (Command or str): Either the Command object or its key. - - Returns: - cmd (Command): The first matching Command in the set. - - """ - cmd = self._instantiate(cmd) - for thiscmd in self.commands: - if thiscmd == cmd: - return thiscmd - return None
- -
[docs] def count(self): - """ - Number of commands in set. - - Returns: - N (int): Number of commands in this Cmdset. - - """ - return len(self.commands)
- -
[docs] def get_system_cmds(self): - """ - Get system commands in cmdset - - Returns: - sys_cmds (list): The system commands in the set. - - Notes: - As far as the Cmdset is concerned, system commands are any - commands with a key starting with double underscore __. - These are excempt from merge operations. - - """ - return self.system_commands
- -
[docs] def make_unique(self, caller): - """ - Remove duplicate command-keys (unsafe) - - Args: - caller (object): Commands on this object will - get preference in the duplicate removal. - - Notes: - This is an unsafe command meant to clean out a cmdset of - doublet commands after it has been created. It is useful - for commands inheriting cmdsets from the cmdhandler where - obj-based cmdsets always are added double. Doublets will - be weeded out with preference to commands defined on - caller, otherwise just by first-come-first-served. - - """ - unique = {} - for cmd in self.commands: - if cmd.key in unique: - ocmd = unique[cmd.key] - if (hasattr(cmd, "obj") and cmd.obj == caller) and not ( - hasattr(ocmd, "obj") and ocmd.obj == caller - ): - unique[cmd.key] = cmd - else: - unique[cmd.key] = cmd - self.commands = list(unique.values())
- -
[docs] def get_all_cmd_keys_and_aliases(self, caller=None): - """ - Collects keys/aliases from commands - - Args: - caller (Object, optional): If set, this is used to check access permissions - on each command. Only commands that pass are returned. - - Returns: - names (list): A list of all command keys and aliases in this cmdset. If `caller` - was given, this list will only contain commands to which `caller` passed - the `call` locktype check. - - """ - names = [] - if caller: - [names.extend(cmd._keyaliases) for cmd in self.commands if cmd.access(caller)] - else: - [names.extend(cmd._keyaliases) for cmd in self.commands] - return names
- -
[docs] def at_cmdset_creation(self): - """ - Hook method - this should be overloaded in the inheriting - class, and should take care of populating the cmdset by use of - self.add(). - """ - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/cmdsethandler.html b/docs/0.9.5/_modules/evennia/commands/cmdsethandler.html deleted file mode 100644 index 2058feb366..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/cmdsethandler.html +++ /dev/null @@ -1,760 +0,0 @@ - - - - - - - - evennia.commands.cmdsethandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.cmdsethandler

-"""
-CmdSethandler
-
-The Cmdsethandler tracks an object's 'Current CmdSet', which is the
-current merged sum of all CmdSets added to it.
-
-A CmdSet constitues a set of commands. The CmdSet works as a special
-intelligent container that, when added to other CmdSet make sure that
-same-name commands are treated correctly (usually so there are no
-doublets).  This temporary but up-to-date merger of CmdSet is jointly
-called the Current Cmset. It is this Current CmdSet that the
-commandhandler looks through whenever an account enters a command (it
-also adds CmdSets from objects in the room in real-time). All account
-objects have a 'default cmdset' containing all the normal in-game mud
-commands (look etc).
-
-So what is all this cmdset complexity good for?
-
-In its simplest form, a CmdSet has no commands, only a key name. In
-this case the cmdset's use is up to each individual game - it can be
-used by an AI module for example (mobs in cmdset 'roam' move from room
-to room, in cmdset 'attack' they enter combat with accounts).
-
-Defining commands in cmdsets offer some further powerful game-design
-consequences however. Here are some examples:
-
-As mentioned above, all accounts always have at least the Default
-CmdSet.  This contains the set of all normal-use commands in-game,
-stuff like look and @desc etc. Now assume our players end up in a dark
-room. You don't want the player to be able to do much in that dark
-room unless they light a candle. You could handle this by changing all
-your normal commands to check if the player is in a dark room. This
-rapidly goes unwieldly and error prone. Instead you just define a
-cmdset with only those commands you want to be available in the 'dark'
-cmdset - maybe a modified look command and a 'light candle' command -
-and have this completely replace the default cmdset.
-
-Another example: Say you want your players to be able to go
-fishing. You could implement this as a 'fish' command that fails
-whenever the account has no fishing rod. Easy enough.  But what if you
-want to make fishing more complex - maybe you want four-five different
-commands for throwing your line, reeling in, etc? Most players won't
-(we assume) have fishing gear, and having all those detailed commands
-is cluttering up the command list. And what if you want to use the
-'throw' command also for throwing rocks etc instead of 'using it up'
-for a minor thing like fishing?
-
-So instead you put all those detailed fishing commands into their own
-CommandSet called 'Fishing'. Whenever the player gives the command
-'fish' (presumably the code checks there is also water nearby), only
-THEN this CommandSet is added to the Cmdhandler of the account. The
-'throw' command (which normally throws rocks) is replaced by the
-custom 'fishing variant' of throw. What has happened is that the
-Fishing CommandSet was merged on top of the Default ones, and due to
-how we defined it, its command overrules the default ones.
-
-When we are tired of fishing, we give the 'go home' command (or
-whatever) and the Cmdhandler simply removes the fishing CommandSet
-so that we are back at defaults (and can throw rocks again).
-
-Since any number of CommandSets can be piled on top of each other, you
-can then implement separate sets for different situations. For
-example, you can have a 'On a boat' set, onto which you then tack on
-the 'Fishing' set. Fishing from a boat? No problem!
-"""
-import sys
-from traceback import format_exc
-from importlib import import_module
-from inspect import trace
-from django.conf import settings
-from evennia.utils import logger, utils
-from evennia.commands.cmdset import CmdSet
-from evennia.server.models import ServerConfig
-
-from django.utils.translation import gettext as _
-
-__all__ = ("import_cmdset", "CmdSetHandler")
-
-_CACHED_CMDSETS = {}
-_CMDSET_PATHS = utils.make_iter(settings.CMDSET_PATHS)
-_IN_GAME_ERRORS = settings.IN_GAME_ERRORS
-_CMDSET_FALLBACKS = settings.CMDSET_FALLBACKS
-
-
-# Output strings
-
-_ERROR_CMDSET_IMPORT = _(
-    """{traceback}
-Error loading cmdset '{path}'
-(Traceback was logged {timestamp})"""
-)
-
-_ERROR_CMDSET_KEYERROR = _(
-    """Error loading cmdset: No cmdset class '{classname}' in '{path}'.
-(Traceback was logged {timestamp})"""
-)
-
-_ERROR_CMDSET_SYNTAXERROR = _(
-    """{traceback}
-SyntaxError encountered when loading cmdset '{path}'.
-(Traceback was logged {timestamp})"""
-)
-
-_ERROR_CMDSET_EXCEPTION = _(
-    """{traceback}
-Compile/Run error when loading cmdset '{path}'.",
-(Traceback was logged {timestamp})"""
-)
-
-_ERROR_CMDSET_FALLBACK = _(
-    """
-Error encountered for cmdset at path '{path}'.
-Replacing with fallback '{fallback_path}'.
-"""
-)
-
-_ERROR_CMDSET_NO_FALLBACK = _("""Fallback path '{fallback_path}' failed to generate a cmdset.""")
-
-
-class _ErrorCmdSet(CmdSet):
-    """
-    This is a special cmdset used to report errors.
-    """
-
-    key = "_CMDSET_ERROR"
-    errmessage = "Error when loading cmdset."
-
-
-class _EmptyCmdSet(CmdSet):
-    """
-    This cmdset represents an empty cmdset
-    """
-
-    key = "_EMPTY_CMDSET"
-    priority = -101
-    mergetype = "Union"
-
-
-
[docs]def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False): - """ - This helper function is used by the cmdsethandler to load a cmdset - instance from a python module, given a python_path. It's usually accessed - through the cmdsethandler's add() and add_default() methods. - path - This is the full path to the cmdset object on python dot-form - - Args: - path (str): The path to the command set to load. - cmdsetobj (CmdSet): The database object/typeclass on which this cmdset is to be - assigned (this can be also channels and exits, as well as accounts - but there will always be such an object) - emit_to_obj (Object, optional): If given, error is emitted to - this object (in addition to logging) - no_logging (bool, optional): Don't log/send error messages. - This can be useful if import_cmdset is just used to check if - this is a valid python path or not. - Returns: - cmdset (CmdSet): The imported command set. If an error was - encountered, `commands.cmdsethandler._ErrorCmdSet` is returned - for the benefit of the handler. - - """ - python_paths = [path] + [ - "%s.%s" % (prefix, path) for prefix in _CMDSET_PATHS if not path.startswith(prefix) - ] - errstring = "" - for python_path in python_paths: - - if "." in path: - modpath, classname = python_path.rsplit(".", 1) - else: - raise ImportError("The path '%s' is not on the form modulepath.ClassName" % path) - - try: - # first try to get from cache - cmdsetclass = _CACHED_CMDSETS.get(python_path, None) - - if not cmdsetclass: - try: - module = import_module(modpath, package="evennia") - except ImportError as exc: - if len(trace()) > 2: - # error in module, make sure to not hide it. - dum, dum, tb = sys.exc_info() - raise exc.with_traceback(tb) - else: - # try next suggested path - errstring += _("\n(Unsuccessfully tried '{path}').").format( - path=python_path - ) - continue - try: - cmdsetclass = getattr(module, classname) - except AttributeError as exc: - if len(trace()) > 2: - # Attribute error within module, don't hide it - dum, dum, tb = sys.exc_info() - raise exc.with_traceback(tb) - else: - errstring += _("\n(Unsuccessfully tried '{path}').").format( - path=python_path - ) - continue - _CACHED_CMDSETS[python_path] = cmdsetclass - - # instantiate the cmdset (and catch its errors) - if callable(cmdsetclass): - cmdsetclass = cmdsetclass(cmdsetobj) - return cmdsetclass - except ImportError as err: - logger.log_trace() - errstring += _ERROR_CMDSET_IMPORT - if _IN_GAME_ERRORS: - errstring = errstring.format( - path=python_path, traceback=format_exc(), timestamp=logger.timeformat() - ) - else: - errstring = errstring.format( - path=python_path, traceback=err, timestamp=logger.timeformat() - ) - break - except KeyError: - logger.log_trace() - errstring += _ERROR_CMDSET_KEYERROR - errstring = errstring.format( - classname=classname, path=python_path, timestamp=logger.timeformat() - ) - break - except SyntaxError as err: - logger.log_trace() - errstring += _ERROR_CMDSET_SYNTAXERROR - if _IN_GAME_ERRORS: - errstring = errstring.format( - path=python_path, traceback=format_exc(), timestamp=logger.timeformat() - ) - else: - errstring = errstring.format( - path=python_path, traceback=err, timestamp=logger.timeformat() - ) - break - except Exception as err: - logger.log_trace() - errstring += _ERROR_CMDSET_EXCEPTION - if _IN_GAME_ERRORS: - errstring = errstring.format( - path=python_path, traceback=format_exc(), timestamp=logger.timeformat() - ) - else: - errstring = errstring.format( - path=python_path, traceback=err, timestamp=logger.timeformat() - ) - break - - if errstring: - # returning an empty error cmdset - errstring = errstring.strip() - if not no_logging: - logger.log_err(errstring) - if emit_to_obj and not ServerConfig.objects.conf("server_starting_mode"): - emit_to_obj.msg(errstring) - err_cmdset = _ErrorCmdSet() - err_cmdset.errmessage = errstring - return err_cmdset - return None # undefined error
- - -# classes - - -
[docs]class CmdSetHandler(object): - """ - The CmdSetHandler is always stored on an object, this object is supplied - as an argument. - - The 'current' cmdset is the merged set currently active for this object. - This is the set the game engine will retrieve when determining which - commands are available to the object. The cmdset_stack holds a history of - all CmdSets to allow the handler to remove/add cmdsets at will. Doing so - will re-calculate the 'current' cmdset. - """ - -
[docs] def __init__(self, obj, init_true=True): - """ - This method is called whenever an object is recreated. - - Args: - obj (Object): An reference to the game object this handler - belongs to. - init_true (bool, optional): Set when the handler is initializing - and loads the current cmdset. - - """ - self.obj = obj - - # the id of the "merged" current cmdset for easy access. - self.key = None - # this holds the "merged" current command set. Note that while the .update - # method updates this field in order to have it synced when operating on - # cmdsets in-code, when the game runs, this field is kept up-to-date by - # the cmdsethandler's get_and_merge_cmdsets! - self.current = None - # this holds a history of CommandSets - self.cmdset_stack = [_EmptyCmdSet(cmdsetobj=self.obj)] - # this tracks which mergetypes are actually in play in the stack - self.mergetype_stack = ["Union"] - - # the subset of the cmdset_paths that are to be stored in the database - self.permanent_paths = [""] - - if init_true: - self.update(init_mode=True) # is then called from the object __init__.
- - def __str__(self): - """ - Display current commands - """ - - strings = ["<CmdSetHandler> stack:"] - mergelist = [] - if len(self.cmdset_stack) > 1: - # We have more than one cmdset in stack; list them all - for snum, cmdset in enumerate(self.cmdset_stack): - mergelist.append(str(snum + 1)) - strings.append(f" {snum + 1}: {cmdset}") - - # Display the currently active cmdset, limited by self.obj's permissions - mergetype = self.mergetype_stack[-1] - if mergetype != self.current.mergetype: - merged_on = self.cmdset_stack[-2].key - mergetype = _("custom {mergetype} on cmdset '{cmdset}'") - mergetype = mergetype.format(mergetype=mergetype, cmdset=merged_on) - - if mergelist: - # current is a result of mergers - mergelist = "+".join(mergelist) - strings.append(f" <Merged {mergelist}>: {self.current}") - else: - # current is a single cmdset - strings.append(" " + str(self.current)) - return "\n".join(strings).rstrip() - - def _import_cmdset(self, cmdset_path, emit_to_obj=None): - """ - Method wrapper for import_cmdset; Loads a cmdset from a - module. - - Args: - cmdset_path (str): The python path to an cmdset object. - emit_to_obj (Object): The object to send error messages to - - Returns: - cmdset (Cmdset): The imported cmdset. - - """ - if not emit_to_obj: - emit_to_obj = self.obj - return import_cmdset(cmdset_path, self.obj, emit_to_obj) - -
[docs] def update(self, init_mode=False): - """ - Re-adds all sets in the handler to have an updated current - - Args: - init_mode (bool, optional): Used automatically right after - this handler was created; it imports all permanent cmdsets - from the database. - - Notes: - This method is necessary in order to always have a `.current` - cmdset when working with the cmdsethandler in code. But the - CmdSetHandler doesn't (cannot) consider external cmdsets and game - state. This means that the .current calculated from this method - will likely not match the true current cmdset as determined at - run-time by `cmdhandler.get_and_merge_cmdsets()`. So in a running - game the responsibility of keeping `.current` upt-to-date belongs - to the central `cmdhandler.get_and_merge_cmdsets()`! - - """ - if init_mode: - # reimport all permanent cmdsets - storage = self.obj.cmdset_storage - if storage: - self.cmdset_stack = [] - for pos, path in enumerate(storage): - if pos == 0 and not path: - self.cmdset_stack = [_EmptyCmdSet(cmdsetobj=self.obj)] - elif path: - cmdset = self._import_cmdset(path) - if cmdset: - if cmdset.key == "_CMDSET_ERROR": - # If a cmdset fails to load, check if we have a fallback path to use - fallback_path = _CMDSET_FALLBACKS.get(path, None) - if fallback_path: - err = _ERROR_CMDSET_FALLBACK.format( - path=path, fallback_path=fallback_path - ) - logger.log_err(err) - if _IN_GAME_ERRORS: - self.obj.msg(err) - cmdset = self._import_cmdset(fallback_path) - # If no cmdset is returned from the fallback, we can't go further - if not cmdset: - err = _ERROR_CMDSET_NO_FALLBACK.format( - fallback_path=fallback_path - ) - logger.log_err(err) - if _IN_GAME_ERRORS: - self.obj.msg(err) - continue - cmdset.permanent = cmdset.key != "_CMDSET_ERROR" - self.cmdset_stack.append(cmdset) - - # merge the stack into a new merged cmdset - new_current = None - self.mergetype_stack = [] - for cmdset in self.cmdset_stack: - try: - # for cmdset's '+' operator, order matters. - new_current = cmdset + new_current - except TypeError: - continue - self.mergetype_stack.append(new_current.actual_mergetype) - self.current = new_current
- -
[docs] def add(self, cmdset, emit_to_obj=None, permanent=False, default_cmdset=False): - """ - Add a cmdset to the handler, on top of the old ones, unless it - is set as the default one (it will then end up at the bottom of the stack) - - Args: - cmdset (CmdSet or str): Can be a cmdset object or the python path - to such an object. - emit_to_obj (Object, optional): An object to receive error messages. - permanent (bool, optional): This cmdset will remain across a server reboot. - default_cmdset (Cmdset, optional): Insert this to replace the - default cmdset position (there is only one such position, - always at the bottom of the stack). - - Notes: - An interesting feature of this method is if you were to send - it an *already instantiated cmdset* (i.e. not a class), the - current cmdsethandler's obj attribute will then *not* be - transferred over to this already instantiated set (this is - because it might be used elsewhere and can cause strange - effects). This means you could in principle have the - handler launch command sets tied to a *different* object - than the handler. Not sure when this would be useful, but - it's a 'quirk' that has to be documented. - - """ - if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)): - string = _("Only CmdSets can be added to the cmdsethandler!") - raise Exception(string) - - if callable(cmdset): - cmdset = cmdset(self.obj) - elif isinstance(cmdset, str): - # this is (maybe) a python path. Try to import from cache. - cmdset = self._import_cmdset(cmdset) - if cmdset and cmdset.key != "_CMDSET_ERROR": - cmdset.permanent = permanent - if permanent and cmdset.key != "_CMDSET_ERROR": - # store the path permanently - storage = self.obj.cmdset_storage or [""] - if default_cmdset: - storage[0] = cmdset.path - else: - storage.append(cmdset.path) - self.obj.cmdset_storage = storage - if default_cmdset: - self.cmdset_stack[0] = cmdset - else: - self.cmdset_stack.append(cmdset) - self.update()
- -
[docs] def add_default(self, cmdset, emit_to_obj=None, permanent=True): - """ - Shortcut for adding a default cmdset. - - Args: - cmdset (Cmdset): The Cmdset to add. - emit_to_obj (Object, optional): Gets error messages - permanent (bool, optional): The new Cmdset should survive a server reboot. - - """ - self.add(cmdset, emit_to_obj=emit_to_obj, permanent=permanent, default_cmdset=True)
- -
[docs] def remove(self, cmdset=None, default_cmdset=False): - """ - Remove a cmdset from the handler. - - Args: - cmdset (CommandSet or str, optional): This can can be supplied either as a cmdset-key, - an instance of the CmdSet or a python path to the cmdset. - If no key is given, the last cmdset in the stack is - removed. Whenever the cmdset_stack changes, the cmdset is - updated. If default_cmdset is set, this argument is ignored. - default_cmdset (bool, optional): If set, this will remove the - default cmdset (at the bottom of the stack). - - """ - if default_cmdset: - # remove the default cmdset only - if self.cmdset_stack: - cmdset = self.cmdset_stack[0] - if cmdset.permanent: - storage = self.obj.cmdset_storage or [""] - storage[0] = "" - self.obj.cmdset_storage = storage - self.cmdset_stack[0] = _EmptyCmdSet(cmdsetobj=self.obj) - else: - self.cmdset_stack = [_EmptyCmdSet(cmdsetobj=self.obj)] - self.update() - return - - if len(self.cmdset_stack) < 2: - # don't allow deleting default cmdsets here. - return - - if not cmdset: - # remove the last one in the stack - cmdset = self.cmdset_stack.pop() - if cmdset.permanent: - storage = self.obj.cmdset_storage - storage.pop() - self.obj.cmdset_storage = storage - else: - # try it as a callable - if callable(cmdset) and hasattr(cmdset, "path"): - delcmdsets = [cset for cset in self.cmdset_stack[1:] if cset.path == cmdset.path] - else: - # try it as a path or key - delcmdsets = [ - cset - for cset in self.cmdset_stack[1:] - if cset.path == cmdset or cset.key == cmdset - ] - storage = [] - - if any(cset.permanent for cset in delcmdsets): - # only hit database if there's need to - storage = self.obj.cmdset_storage - updated = False - for cset in delcmdsets: - if cset.permanent: - try: - storage.remove(cset.path) - updated = True - except ValueError: - # nothing to remove - pass - if updated: - self.obj.cmdset_storage = storage - for cset in delcmdsets: - # clean the in-memory stack - try: - self.cmdset_stack.remove(cset) - except ValueError: - # nothing to remove - pass - # re-sync the cmdsethandler. - self.update()
- - # legacy alias - delete = remove - -
[docs] def remove_default(self): - """ - This explicitly deletes only the default cmdset. - - """ - self.remove(default_cmdset=True)
- - # legacy alias - delete_default = remove_default - -
[docs] def get(self): - """ - Get all cmdsets. - - Returns: - cmdsets (list): All the command sets currently in the handler. - - """ - return self.cmdset_stack
- - # backwards-compatible alias - all = get - -
[docs] def clear(self): - """ - Removes all Command Sets from the handler except the default one - (use `self.remove_default` to remove that). - - """ - self.cmdset_stack = [self.cmdset_stack[0]] - storage = self.obj.cmdset_storage - if storage: - storage = storage[0] - self.obj.cmdset_storage = storage - self.update()
- -
[docs] def has(self, cmdset, must_be_default=False): - """ - checks so the cmdsethandler contains a given cmdset - - Args: - cmdset (str or Cmdset): Cmdset key, pythonpath or - Cmdset to check the existence for. - must_be_default (bool, optional): Only return True if - the checked cmdset is the default one. - - Returns: - has_cmdset (bool): Whether or not the cmdset is in the handler. - - """ - if callable(cmdset) and hasattr(cmdset, "path"): - # try it as a callable - if must_be_default: - return self.cmdset_stack and (self.cmdset_stack[0].path == cmdset.path) - else: - return any([cset for cset in self.cmdset_stack if cset.path == cmdset.path]) - else: - # try it as a path or key - if must_be_default: - return self.cmdset_stack and ( - self.cmdset_stack[0].key == cmdset or self.cmdset_stack[0].path == cmdset - ) - else: - return any( - [ - cset - for cset in self.cmdset_stack - if cset.path == cmdset or cset.key == cmdset - ] - )
- - # backwards-compatability alias - has_cmdset = has - -
[docs] def reset(self): - """ - Force reload of all cmdsets in handler. This should be called - after _CACHED_CMDSETS have been cleared (normally this is - handled automatically by @reload). - - """ - new_cmdset_stack = [] - for cmdset in self.cmdset_stack: - if cmdset.key == "_EMPTY_CMDSET": - new_cmdset_stack.append(cmdset) - else: - new_cmdset_stack.append(self._import_cmdset(cmdset.path)) - self.cmdset_stack = new_cmdset_stack - self.update()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/command.html b/docs/0.9.5/_modules/evennia/commands/command.html deleted file mode 100644 index 13b1c17d43..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/command.html +++ /dev/null @@ -1,786 +0,0 @@ - - - - - - - - evennia.commands.command — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.command

-"""
-The base Command class.
-
-All commands in Evennia inherit from the 'Command' class in this module.
-
-"""
-import re
-import math
-import inspect
-
-from django.conf import settings
-
-from evennia.locks.lockhandler import LockHandler
-from evennia.utils.utils import is_iter, fill, lazy_property, make_iter
-from evennia.utils.evtable import EvTable
-from evennia.utils.ansi import ANSIString
-
-
-def _init_command(cls, **kwargs):
-    """
-    Helper command.
-    Makes sure all data are stored as lowercase and
-    do checking on all properties that should be in list form.
-    Sets up locks to be more forgiving. This is used both by the metaclass
-    and (optionally) at instantiation time.
-
-    If kwargs are given, these are set as instance-specific properties
-    on the command - but note that the Command instance is *re-used* on a given
-    host object, so a kwarg value set on the instance will *remain* on the instance
-    for subsequent uses of that Command on that particular object.
-
-    """
-    for i in range(len(kwargs)):
-        # used for dynamic creation of commands
-        key, value = kwargs.popitem()
-        setattr(cls, key, value)
-
-    cls.key = cls.key.lower()
-    if cls.aliases and not is_iter(cls.aliases):
-        try:
-            cls.aliases = [str(alias).strip().lower() for alias in cls.aliases.split(",")]
-        except Exception:
-            cls.aliases = []
-    cls.aliases = list(set(alias for alias in cls.aliases if alias and alias != cls.key))
-
-    # optimization - a set is much faster to match against than a list
-    cls._matchset = set([cls.key] + cls.aliases)
-    # optimization for looping over keys+aliases
-    cls._keyaliases = tuple(cls._matchset)
-
-    # by default we don't save the command between runs
-    if not hasattr(cls, "save_for_next"):
-        cls.save_for_next = False
-
-    # pre-process locks as defined in class definition
-    temp = []
-    if hasattr(cls, "permissions"):
-        cls.locks = cls.permissions
-    if not hasattr(cls, "locks"):
-        # default if one forgets to define completely
-        cls.locks = "cmd:all()"
-    if "cmd:" not in cls.locks:
-        cls.locks = "cmd:all();" + cls.locks
-    for lockstring in cls.locks.split(";"):
-        if lockstring and ":" not in lockstring:
-            lockstring = "cmd:%s" % lockstring
-        temp.append(lockstring)
-    cls.lock_storage = ";".join(temp)
-
-    if hasattr(cls, "arg_regex") and isinstance(cls.arg_regex, str):
-        cls.arg_regex = re.compile(r"%s" % cls.arg_regex, re.I + re.UNICODE)
-    if not hasattr(cls, "auto_help"):
-        cls.auto_help = True
-    if not hasattr(cls, "is_exit"):
-        cls.is_exit = False
-    if not hasattr(cls, "help_category"):
-        cls.help_category = "general"
-    # make sure to pick up the parent's docstring if the child class is
-    # missing one (important for auto-help)
-    if cls.__doc__ is None:
-        for parent_class in inspect.getmro(cls):
-            if parent_class.__doc__ is not None:
-                cls.__doc__ = parent_class.__doc__
-                break
-    cls.help_category = cls.help_category.lower()
-
-
-
[docs]class CommandMeta(type): - """ - The metaclass cleans up all properties on the class - """ - -
[docs] def __init__(cls, *args, **kwargs): - _init_command(cls, **kwargs) - super().__init__(*args, **kwargs)
- - -# The Command class is the basic unit of an Evennia command; when -# defining new commands, the admin subclass this class and -# define their own parser method to handle the input. The -# advantage of this is inheritage; commands that have similar -# structure can parse the input string the same way, minimizing -# parsing errors. - - -
[docs]class Command(object, metaclass=CommandMeta): - """ - Base command - - Usage: - command [args] - - This is the base command class. Inherit from this - to create new commands. - - The cmdhandler makes the following variables available to the - command methods (so you can always assume them to be there): - self.caller - the game object calling the command - self.cmdstring - the command name used to trigger this command (allows - you to know which alias was used, for example) - cmd.args - everything supplied to the command following the cmdstring - (this is usually what is parsed in self.parse()) - cmd.cmdset - the cmdset from which this command was matched (useful only - seldomly, notably for help-type commands, to create dynamic - help entries and lists) - cmd.obj - the object on which this command is defined. If a default command, - this is usually the same as caller. - cmd.rawstring - the full raw string input, including any args and no parsing. - - The following class properties can/should be defined on your child class: - - key - identifier for command (e.g. "look") - aliases - (optional) list of aliases (e.g. ["l", "loo"]) - locks - lock string (default is "cmd:all()") - help_category - how to organize this help entry in help system - (default is "General") - auto_help - defaults to True. Allows for turning off auto-help generation - arg_regex - (optional) raw string regex defining how the argument part of - the command should look in order to match for this command - (e.g. must it be a space between cmdname and arg?) - auto_help_display_key - (optional) if given, this replaces the string shown - in the auto-help listing. This is particularly useful for system-commands - whose actual key is not really meaningful. - - (Note that if auto_help is on, this initial string is also used by the - system to create the help entry for the command, so it's a good idea to - format it similar to this one). This behavior can be changed by - overriding the method 'get_help' of a command: by default, this - method returns cmd.__doc__ (that is, this very docstring, or - the docstring of your command). You can, however, extend or - replace this without disabling auto_help. - """ - - # the main way to call this command (e.g. 'look') - key = "command" - # alternative ways to call the command (e.g. 'l', 'glance', 'examine') - aliases = [] - # a list of lock definitions on the form - # cmd:[NOT] func(args) [ AND|OR][ NOT] func2(args) - locks = settings.COMMAND_DEFAULT_LOCKS - # used by the help system to group commands in lists. - help_category = settings.COMMAND_DEFAULT_HELP_CATEGORY - # This allows to turn off auto-help entry creation for individual commands. - auto_help = True - # optimization for quickly separating exit-commands from normal commands - is_exit = False - # define the command not only by key but by the regex form of its arguments - arg_regex = settings.COMMAND_DEFAULT_ARG_REGEX - # whether self.msg sends to all sessions of a related account/object (default - # is to only send to the session sending the command). - msg_all_sessions = settings.COMMAND_DEFAULT_MSG_ALL_SESSIONS - - # auto-set (by Evennia on command instantiation) are: - # obj - which object this command is defined on - # session - which session is responsible for triggering this command. Only set - # if triggered by an account. - -
[docs] def __init__(self, **kwargs): - """ - The lockhandler works the same as for objects. - optional kwargs will be set as properties on the Command at runtime, - overloading evential same-named class properties. - - """ - if kwargs: - _init_command(self, **kwargs)
- -
[docs] @lazy_property - def lockhandler(self): - return LockHandler(self)
- - def __str__(self): - """ - Print the command key - """ - return self.key - - def __eq__(self, cmd): - """ - Compare two command instances to each other by matching their - key and aliases. - - Args: - cmd (Command or str): Allows for equating both Command - objects and their keys. - - Returns: - equal (bool): If the commands are equal or not. - - """ - try: - # first assume input is a command (the most common case) - return self._matchset.intersection(cmd._matchset) - except AttributeError: - # probably got a string - return cmd in self._matchset - - def __hash__(self): - """ - Python 3 requires that any class which implements __eq__ must also - implement __hash__ and that the corresponding hashes for equivalent - instances are themselves equivalent. - - Technically, the following implementation is only valid for comparison - against other Commands, as our __eq__ supports comparison against - str, too. - - """ - return hash("\n".join(self._matchset)) - - def __ne__(self, cmd): - """ - The logical negation of __eq__. Since this is one of the most - called methods in Evennia (along with __eq__) we do some - code-duplication here rather than issuing a method-lookup to - __eq__. - """ - try: - return self._matchset.isdisjoint(cmd._matchset) - except AttributeError: - return cmd not in self._matchset - - def __contains__(self, query): - """ - This implements searches like 'if query in cmd'. It's a fuzzy - matching used by the help system, returning True if query can - be found as a substring of the commands key or its aliases. - - Args: - query (str): query to match against. Should be lower case. - - Returns: - result (bool): Fuzzy matching result. - - """ - return any(query in keyalias for keyalias in self._keyaliases) - - def _optimize(self): - """ - Optimize the key and aliases for lookups. - """ - # optimization - a set is much faster to match against than a list - self._matchset = set([self.key] + self.aliases) - # optimization for looping over keys+aliases - self._keyaliases = tuple(self._matchset) - -
[docs] def set_key(self, new_key): - """ - Update key. - - Args: - new_key (str): The new key. - - Notes: - This is necessary to use to make sure the optimization - caches are properly updated as well. - - """ - self.key = new_key.lower() - self._optimize()
- -
[docs] def set_aliases(self, new_aliases): - """ - Replace aliases with new ones. - - Args: - new_aliases (str or list): Either a ;-separated string - or a list of aliases. These aliases will replace the - existing ones, if any. - - Notes: - This is necessary to use to make sure the optimization - caches are properly updated as well. - - """ - if isinstance(new_aliases, str): - new_aliases = new_aliases.split(";") - aliases = (str(alias).strip().lower() for alias in make_iter(new_aliases)) - self.aliases = list(set(alias for alias in aliases if alias != self.key)) - self._optimize()
- -
[docs] def match(self, cmdname): - """ - This is called by the system when searching the available commands, - in order to determine if this is the one we wanted. cmdname was - previously extracted from the raw string by the system. - - Args: - cmdname (str): Always lowercase when reaching this point. - - Returns: - result (bool): Match result. - - """ - return cmdname in self._matchset
- -
[docs] def access(self, srcobj, access_type="cmd", default=False): - """ - This hook is called by the cmdhandler to determine if srcobj - is allowed to execute this command. It should return a boolean - value and is not normally something that need to be changed since - it's using the Evennia permission system directly. - - Args: - srcobj (Object): Object trying to gain permission - access_type (str, optional): The lock type to check. - default (bool, optional): The fallback result if no lock - of matching `access_type` is found on this Command. - - """ - return self.lockhandler.check(srcobj, access_type, default=default)
- -
[docs] def msg(self, text=None, to_obj=None, from_obj=None, session=None, **kwargs): - """ - This is a shortcut instead of calling msg() directly on an - object - it will detect if caller is an Object or an Account and - also appends self.session automatically if self.msg_all_sessions is False. - - Args: - text (str, optional): Text string of message to send. - to_obj (Object, optional): Target object of message. Defaults to self.caller. - from_obj (Object, optional): Source of message. Defaults to to_obj. - session (Session, optional): Supply data only to a unique - session (ignores the value of `self.msg_all_sessions`). - - Keyword Args: - options (dict): Options to the protocol. - any (any): All other keywords are interpreted as th - name of send-instructions. - - """ - from_obj = from_obj or self.caller - to_obj = to_obj or from_obj - if not session and not self.msg_all_sessions: - if to_obj == self.caller: - session = self.session - else: - session = to_obj.sessions.get() - to_obj.msg(text=text, from_obj=from_obj, session=session, **kwargs)
- -
[docs] def execute_cmd(self, raw_string, session=None, obj=None, **kwargs): - """ - A shortcut of execute_cmd on the caller. It appends the - session automatically. - - Args: - raw_string (str): Execute this string as a command input. - session (Session, optional): If not given, the current command's Session will be used. - obj (Object or Account, optional): Object or Account on which to call the execute_cmd. - If not given, self.caller will be used. - - Keyword Args: - Other keyword arguments will be added to the found command - object instace as variables before it executes. This is - unused by default Evennia but may be used to set flags and - change operating paramaters for commands at run-time. - - """ - obj = self.caller if obj is None else obj - session = self.session if session is None else session - obj.execute_cmd(raw_string, session=session, **kwargs)
- - # Common Command hooks - -
[docs] def at_pre_cmd(self): - """ - This hook is called before self.parse() on all commands. If - this hook returns anything but False/None, the command - sequence is aborted. - - """ - pass
- -
[docs] def at_post_cmd(self): - """ - This hook is called after the command has finished executing - (after self.func()). - - """ - pass
- -
[docs] def parse(self): - """ - Once the cmdhandler has identified this as the command we - want, this function is run. If many of your commands have a - similar syntax (for example 'cmd arg1 = arg2') you should - simply define this once and just let other commands of the - same form inherit from this. See the docstring of this module - for which object properties are available to use (notably - self.args). - - """ - pass
- -
[docs] def get_command_info(self): - """ - This is the default output of func() if no func() overload is done. - Provided here as a separate method so that it can be called for debugging - purposes when making commands. - - """ - variables = "\n".join( - " |w{}|n ({}): {}".format(key, type(val), val) for key, val in self.__dict__.items() - ) - string = f""" -Command {self} has no defined `func()` - showing on-command variables: -{variables} - """ - # a simple test command to show the available properties - string += "-" * 50 - string += "\n|w%s|n - Command variables from evennia:\n" % self.key - string += "-" * 50 - string += "\nname of cmd (self.key): |w%s|n\n" % self.key - string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases - string += "cmd locks (self.locks): |w%s|n\n" % self.locks - string += "help category (self.help_category): |w%s|n\n" % self.help_category.capitalize() - string += "object calling (self.caller): |w%s|n\n" % self.caller - string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj - string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring - # show cmdset.key instead of cmdset to shorten output - string += fill( - "current cmdset (self.cmdset): |w%s|n\n" - % (self.cmdset.key if self.cmdset.key else self.cmdset.__class__) - ) - - self.caller.msg(string)
- -
[docs] def func(self): - """ - This is the actual executing part of the command. It is - called directly after self.parse(). See the docstring of this - module for which object properties are available (beyond those - set in self.parse()) - - """ - self.get_command_info()
- -
[docs] def get_extra_info(self, caller, **kwargs): - """ - Display some extra information that may help distinguish this - command from others, for instance, in a disambiguity prompt. - - If this command is a potential match in an ambiguous - situation, one distinguishing feature may be its attachment to - a nearby object, so we include this if available. - - Args: - caller (TypedObject): The caller who typed an ambiguous - term handed to the search function. - - Returns: - A string with identifying information to disambiguate the - object, conventionally with a preceding space. - - """ - if hasattr(self, "obj") and self.obj and self.obj != caller: - return " (%s)" % self.obj.get_display_name(caller).strip() - return ""
- -
[docs] def get_help(self, caller, cmdset): - """ - Return the help message for this command and this caller. - - By default, return self.__doc__ (the docstring just under - the class definition). You can override this behavior, - though, and even customize it depending on the caller, or other - commands the caller can use. - - Args: - caller (Object or Account): the caller asking for help on the command. - cmdset (CmdSet): the command set (if you need additional commands). - - Returns: - docstring (str): the help text to provide the caller for this command. - - """ - return self.__doc__
- -
[docs] def client_width(self): - """ - Get the client screenwidth for the session using this command. - - Returns: - client width (int): The width (in characters) of the client window. - - """ - if self.session: - return self.session.protocol_flags.get( - "SCREENWIDTH", {0: settings.CLIENT_DEFAULT_WIDTH} - )[0] - return settings.CLIENT_DEFAULT_WIDTH
- -
[docs] def client_height(self): - """ - Get the client screenheight for the session using this command. - - Returns: - client height (int): The height (in characters) of the client window. - - """ - if self.session: - return self.session.protocol_flags.get( - "SCREENHEIGHT", {0: settings.CLIENT_DEFAULT_HEIGHT} - )[0] - return settings.CLIENT_DEFAULT_HEIGHT
- -
[docs] def styled_table(self, *args, **kwargs): - """ - Create an EvTable styled by on user preferences. - - Args: - *args (str): Column headers. If not colored explicitly, these will get colors - from user options. - Keyword Args: - any (str, int or dict): EvTable options, including, optionally a `table` dict - detailing the contents of the table. - Returns: - table (EvTable): An initialized evtable entity, either complete (if using `table` kwarg) - or incomplete and ready for use with `.add_row` or `.add_collumn`. - - """ - border_color = self.account.options.get("border_color") - column_color = self.account.options.get("column_names_color") - - colornames = ["|%s%s|n" % (column_color, col) for col in args] - - h_line_char = kwargs.pop("header_line_char", "~") - header_line_char = ANSIString(f"|{border_color}{h_line_char}|n") - c_char = kwargs.pop("corner_char", "+") - corner_char = ANSIString(f"|{border_color}{c_char}|n") - - b_left_char = kwargs.pop("border_left_char", "||") - border_left_char = ANSIString(f"|{border_color}{b_left_char}|n") - - b_right_char = kwargs.pop("border_right_char", "||") - border_right_char = ANSIString(f"|{border_color}{b_right_char}|n") - - b_bottom_char = kwargs.pop("border_bottom_char", "-") - border_bottom_char = ANSIString(f"|{border_color}{b_bottom_char}|n") - - b_top_char = kwargs.pop("border_top_char", "-") - border_top_char = ANSIString(f"|{border_color}{b_top_char}|n") - - table = EvTable( - *colornames, - header_line_char=header_line_char, - corner_char=corner_char, - border_left_char=border_left_char, - border_right_char=border_right_char, - border_top_char=border_top_char, - **kwargs, - ) - return table
- - def _render_decoration( - self, - header_text=None, - fill_character=None, - edge_character=None, - mode="header", - color_header=True, - width=None, - ): - """ - Helper for formatting a string into a pretty display, for a header, separator or footer. - - Keyword Args: - header_text (str): Text to include in header. - fill_character (str): This single character will be used to fill the width of the - display. - edge_character (str): This character caps the edges of the display. - mode(str): One of 'header', 'separator' or 'footer'. - color_header (bool): If the header should be colorized based on user options. - width (int): If not given, the client's width will be used if available. - - Returns: - string (str): The decorated and formatted text. - - """ - - colors = dict() - colors["border"] = self.account.options.get("border_color") - colors["headertext"] = self.account.options.get("%s_text_color" % mode) - colors["headerstar"] = self.account.options.get("%s_star_color" % mode) - - width = width or self.client_width() - if edge_character: - width -= 2 - - if header_text: - if color_header: - header_text = ANSIString(header_text).clean() - header_text = ANSIString("|n|%s%s|n" % (colors["headertext"], header_text)) - if mode == "header": - begin_center = ANSIString( - "|n|%s<|%s* |n" % (colors["border"], colors["headerstar"]) - ) - end_center = ANSIString("|n |%s*|%s>|n" % (colors["headerstar"], colors["border"])) - center_string = ANSIString(begin_center + header_text + end_center) - else: - center_string = ANSIString("|n |%s%s |n" % (colors["headertext"], header_text)) - else: - center_string = "" - - fill_character = self.account.options.get("%s_fill" % mode) - - remain_fill = width - len(center_string) - if remain_fill % 2 == 0: - right_width = remain_fill / 2 - left_width = remain_fill / 2 - else: - right_width = math.floor(remain_fill / 2) - left_width = math.ceil(remain_fill / 2) - - right_fill = ANSIString("|n|%s%s|n" % (colors["border"], fill_character * int(right_width))) - left_fill = ANSIString("|n|%s%s|n" % (colors["border"], fill_character * int(left_width))) - - if edge_character: - edge_fill = ANSIString("|n|%s%s|n" % (colors["border"], edge_character)) - main_string = ANSIString(center_string) - final_send = ( - ANSIString(edge_fill) + left_fill + main_string + right_fill + ANSIString(edge_fill) - ) - else: - final_send = left_fill + ANSIString(center_string) + right_fill - return final_send - -
[docs] def styled_header(self, *args, **kwargs): - """ - Create a pretty header. - """ - - if "mode" not in kwargs: - kwargs["mode"] = "header" - return self._render_decoration(*args, **kwargs)
- -
[docs] def styled_separator(self, *args, **kwargs): - """ - Create a separator. - - """ - if "mode" not in kwargs: - kwargs["mode"] = "separator" - return self._render_decoration(*args, **kwargs)
- -
- - -
[docs]class InterruptCommand(Exception): - - """Cleanly interrupt a command.""" - - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/account.html b/docs/0.9.5/_modules/evennia/commands/default/account.html deleted file mode 100644 index 3c6a7e3002..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/account.html +++ /dev/null @@ -1,1160 +0,0 @@ - - - - - - - - evennia.commands.default.account — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.account

-"""
-Account (OOC) commands. These are stored on the Account object
-and self.caller is thus always an Account, not an Object/Character.
-
-These commands go in the AccountCmdset and are accessible also
-when puppeting a Character (although with lower priority)
-
-These commands use the account_caller property which tells the command
-parent (MuxCommand, usually) to setup caller correctly. They use
-self.account to make sure to always use the account object rather than
-self.caller (which change depending on the level you are calling from)
-The property self.character can be used to access the character when
-these commands are triggered with a connected character (such as the
-case of the `ooc` command), it is None if we are OOC.
-
-Note that under MULTISESSION_MODE > 2, Account commands should use
-self.msg() and similar methods to reroute returns to the correct
-method. Otherwise all text will be returned to all connected sessions.
-
-"""
-import time
-from codecs import lookup as codecs_lookup
-from django.conf import settings
-from evennia.server.sessionhandler import SESSIONS
-from evennia.utils import utils, create, logger, search
-
-COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
-_MULTISESSION_MODE = settings.MULTISESSION_MODE
-
-# limit symbol import for API
-__all__ = (
-    "CmdOOCLook",
-    "CmdIC",
-    "CmdOOC",
-    "CmdPassword",
-    "CmdQuit",
-    "CmdCharCreate",
-    "CmdOption",
-    "CmdSessions",
-    "CmdWho",
-    "CmdColorTest",
-    "CmdQuell",
-    "CmdCharDelete",
-    "CmdStyle",
-)
-
-
-class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS):
-    """
-    Custom parent (only) parsing for OOC looking, sets a "playable"
-    property on the command based on the parsing.
-
-    """
-
-    def parse(self):
-        """Custom parsing"""
-
-        super().parse()
-
-        if _MULTISESSION_MODE < 2:
-            # only one character allowed - not used in this mode
-            self.playable = None
-            return
-
-        playable = self.account.db._playable_characters
-        if playable is not None:
-            # clean up list if character object was deleted in between
-            if None in playable:
-                playable = [character for character in playable if character]
-                self.account.db._playable_characters = playable
-        # store playable property
-        if self.args:
-            self.playable = dict((utils.to_str(char.key.lower()), char) for char in playable).get(
-                self.args.lower(), None
-            )
-        else:
-            self.playable = playable
-
-
-# Obs - these are all intended to be stored on the Account, and as such,
-# use self.account instead of self.caller, just to be sure. Also self.msg()
-# is used to make sure returns go to the right session
-
-# note that this is inheriting from MuxAccountLookCommand,
-# and has the .playable property.
-
[docs]class CmdOOCLook(MuxAccountLookCommand): - """ - look while out-of-character - - Usage: - look - - Look in the ooc state. - """ - - # This is an OOC version of the look command. Since a - # Account doesn't have an in-game existence, there is no - # concept of location or "self". If we are controlling - # a character, pass control over to normal look. - - key = "look" - aliases = ["l", "ls"] - locks = "cmd:all()" - help_category = "General" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """implement the ooc look command""" - - if _MULTISESSION_MODE < 2: - # only one character allowed - self.msg("You are out-of-character (OOC).\nUse |wic|n to get back into the game.") - return - - # call on-account look helper method - self.msg(self.account.at_look(target=self.playable, session=self.session))
- - -
[docs]class CmdCharCreate(COMMAND_DEFAULT_CLASS): - """ - create a new character - - Usage: - charcreate <charname> [= desc] - - Create a new character, optionally giving it a description. You - may use upper-case letters in the name - you will nevertheless - always be able to access your character using lower-case letters - if you want. - """ - - key = "charcreate" - locks = "cmd:pperm(Player)" - help_category = "General" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """create the new character""" - account = self.account - if not self.args: - self.msg("Usage: charcreate <charname> [= description]") - return - key = self.lhs - desc = self.rhs - - charmax = _MAX_NR_CHARACTERS - - if not account.is_superuser and ( - account.db._playable_characters and len(account.db._playable_characters) >= charmax - ): - plural = "" if charmax == 1 else "s" - self.msg(f"You may only create a maximum of {charmax} character{plural}.") - return - from evennia.objects.models import ObjectDB - - typeclass = settings.BASE_CHARACTER_TYPECLASS - - if ObjectDB.objects.filter(db_typeclass_path=typeclass, db_key__iexact=key): - # check if this Character already exists. Note that we are only - # searching the base character typeclass here, not any child - # classes. - self.msg("|rA character named '|w%s|r' already exists.|n" % key) - return - - # create the character - start_location = ObjectDB.objects.get_id(settings.START_LOCATION) - default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - permissions = settings.PERMISSION_ACCOUNT_DEFAULT - new_character = create.create_object( - typeclass, key=key, location=start_location, home=default_home, permissions=permissions - ) - # only allow creator (and developers) to puppet this char - new_character.locks.add( - "puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer);delete:id(%i) or perm(Admin)" - % (new_character.id, account.id, account.id) - ) - account.db._playable_characters.append(new_character) - if desc: - new_character.db.desc = desc - elif not new_character.db.desc: - new_character.db.desc = "This is a character." - self.msg( - "Created new character %s. Use |wic %s|n to enter the game as this character." - % (new_character.key, new_character.key) - ) - logger.log_sec( - "Character Created: %s (Caller: %s, IP: %s)." - % (new_character, account, self.session.address) - )
- - -
[docs]class CmdCharDelete(COMMAND_DEFAULT_CLASS): - """ - delete a character - this cannot be undone! - - Usage: - chardelete <charname> - - Permanently deletes one of your characters. - """ - - key = "chardelete" - locks = "cmd:pperm(Player)" - help_category = "General" - -
[docs] def func(self): - """delete the character""" - account = self.account - - if not self.args: - self.msg("Usage: chardelete <charactername>") - return - - # use the playable_characters list to search - match = [ - char - for char in utils.make_iter(account.db._playable_characters) - if char.key.lower() == self.args.lower() - ] - if not match: - self.msg("You have no such character to delete.") - return - elif len(match) > 1: - self.msg( - "Aborting - there are two characters with the same name. Ask an admin to delete the right one." - ) - return - else: # one match - from evennia.utils.evmenu import get_input - - def _callback(caller, callback_prompt, result): - if result.lower() == "yes": - # only take action - delobj = caller.ndb._char_to_delete - key = delobj.key - caller.db._playable_characters = [ - pc for pc in caller.db._playable_characters if pc != delobj - ] - delobj.delete() - self.msg("Character '%s' was permanently deleted." % key) - logger.log_sec( - "Character Deleted: %s (Caller: %s, IP: %s)." - % (key, account, self.session.address) - ) - else: - self.msg("Deletion was aborted.") - del caller.ndb._char_to_delete - - match = match[0] - account.ndb._char_to_delete = match - - # Return if caller has no permission to delete this - if not match.access(account, "delete"): - self.msg("You do not have permission to delete this character.") - return - - prompt = ( - "|rThis will permanently destroy '%s'. This cannot be undone.|n Continue yes/[no]?" - ) - get_input(account, prompt % match.key, _callback)
- - -
[docs]class CmdIC(COMMAND_DEFAULT_CLASS): - """ - control an object you have permission to puppet - - Usage: - ic <character> - - Go in-character (IC) as a given Character. - - This will attempt to "become" a different object assuming you have - the right to do so. Note that it's the ACCOUNT character that puppets - characters/objects and which needs to have the correct permission! - - You cannot become an object that is already controlled by another - account. In principle <character> can be any in-game object as long - as you the account have access right to puppet it. - """ - - key = "ic" - # lock must be all() for different puppeted objects to access it. - locks = "cmd:all()" - aliases = "puppet" - help_category = "General" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """ - Main puppet method - """ - account = self.account - session = self.session - - new_character = None - character_candidates = [] - - if not self.args: - character_candidates = [account.db._last_puppet] if account.db._last_puppet else [] - if not character_candidates: - self.msg("Usage: ic <character>") - return - else: - # argument given - - if account.db._playable_characters: - # look at the playable_characters list first - character_candidates.extend( - account.search( - self.args, - candidates=account.db._playable_characters, - search_object=True, - quiet=True, - ) - ) - - if account.locks.check_lockstring(account, "perm(Builder)"): - # builders and higher should be able to puppet more than their - # playable characters. - if session.puppet: - # start by local search - this helps to avoid the user - # getting locked into their playable characters should one - # happen to be named the same as another. We replace the suggestion - # from playable_characters here - this allows builders to puppet objects - # with the same name as their playable chars should it be necessary - # (by going to the same location). - character_candidates = [ - char - for char in session.puppet.search(self.args, quiet=True) - if char.access(account, "puppet") - ] - if not character_candidates: - # fall back to global search only if Builder+ has no - # playable_characers in list and is not standing in a room - # with a matching char. - character_candidates.extend( - [ - char - for char in search.object_search(self.args) - if char.access(account, "puppet") - ] - ) - - # handle possible candidates - if not character_candidates: - self.msg("That is not a valid character choice.") - return - if len(character_candidates) > 1: - self.msg( - "Multiple targets with the same name:\n %s" - % ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in character_candidates) - ) - return - else: - new_character = character_candidates[0] - - # do the puppet puppet - try: - account.puppet_object(session, new_character) - account.db._last_puppet = new_character - logger.log_sec( - "Puppet Success: (Caller: %s, Target: %s, IP: %s)." - % (account, new_character, self.session.address) - ) - except RuntimeError as exc: - self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc)) - logger.log_sec( - "Puppet Failed: %s (Caller: %s, Target: %s, IP: %s)." - % (exc, account, new_character, self.session.address) - )
- - -# note that this is inheriting from MuxAccountLookCommand, -# and as such has the .playable property. -
[docs]class CmdOOC(MuxAccountLookCommand): - """ - stop puppeting and go ooc - - Usage: - ooc - - Go out-of-character (OOC). - - This will leave your current character and put you in a incorporeal OOC state. - """ - - key = "ooc" - locks = "cmd:pperm(Player)" - aliases = "unpuppet" - help_category = "General" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """Implement function""" - - account = self.account - session = self.session - - old_char = account.get_puppet(session) - if not old_char: - string = "You are already OOC." - self.msg(string) - return - - account.db._last_puppet = old_char - - # disconnect - try: - account.unpuppet_object(session) - self.msg("\n|GYou go OOC.|n\n") - - if _MULTISESSION_MODE < 2: - # only one character allowed - self.msg("You are out-of-character (OOC).\nUse |wic|n to get back into the game.") - return - - self.msg(account.at_look(target=self.playable, session=session)) - - except RuntimeError as exc: - self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc))
- - -
[docs]class CmdSessions(COMMAND_DEFAULT_CLASS): - """ - check your connected session(s) - - Usage: - sessions - - Lists the sessions currently connected to your account. - - """ - - key = "sessions" - locks = "cmd:all()" - help_category = "General" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """Implement function""" - account = self.account - sessions = account.sessions.all() - table = self.styled_table( - "|wsessid", "|wprotocol", "|whost", "|wpuppet/character", "|wlocation" - ) - for sess in sorted(sessions, key=lambda x: x.sessid): - char = account.get_puppet(sess) - table.add_row( - str(sess.sessid), - str(sess.protocol_key), - isinstance(sess.address, tuple) and sess.address[0] or sess.address, - char and str(char) or "None", - char and str(char.location) or "N/A", - ) - self.msg("|wYour current session(s):|n\n%s" % table)
- - -
[docs]class CmdWho(COMMAND_DEFAULT_CLASS): - """ - list who is currently online - - Usage: - who - doing - - Shows who is currently online. Doing is an alias that limits info - also for those with all permissions. - """ - - key = "who" - aliases = "doing" - locks = "cmd:all()" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """ - Get all connected accounts by polling session. - """ - - account = self.account - session_list = SESSIONS.get_sessions() - - session_list = sorted(session_list, key=lambda o: o.account.key) - - if self.cmdstring == "doing": - show_session_data = False - else: - show_session_data = account.check_permstring("Developer") or account.check_permstring( - "Admins" - ) - - naccounts = SESSIONS.account_count() - if show_session_data: - # privileged info - table = self.styled_table( - "|wAccount Name", - "|wOn for", - "|wIdle", - "|wPuppeting", - "|wRoom", - "|wCmds", - "|wProtocol", - "|wHost", - ) - for session in session_list: - if not session.logged_in: - continue - delta_cmd = time.time() - session.cmd_last_visible - delta_conn = time.time() - session.conn_time - session_account = session.get_account() - puppet = session.get_puppet() - location = puppet.location.key if puppet and puppet.location else "None" - table.add_row( - utils.crop(session_account.get_display_name(account), width=25), - utils.time_format(delta_conn, 0), - utils.time_format(delta_cmd, 1), - utils.crop(puppet.get_display_name(account) if puppet else "None", width=25), - utils.crop(location, width=25), - session.cmd_total, - session.protocol_key, - isinstance(session.address, tuple) and session.address[0] or session.address, - ) - else: - # unprivileged - table = self.styled_table("|wAccount name", "|wOn for", "|wIdle") - for session in session_list: - if not session.logged_in: - continue - delta_cmd = time.time() - session.cmd_last_visible - delta_conn = time.time() - session.conn_time - session_account = session.get_account() - table.add_row( - utils.crop(session_account.get_display_name(account), width=25), - utils.time_format(delta_conn, 0), - utils.time_format(delta_cmd, 1), - ) - is_one = naccounts == 1 - self.msg( - "|wAccounts:|n\n%s\n%s unique account%s logged in." - % (table, "One" if is_one else naccounts, "" if is_one else "s") - )
- - -
[docs]class CmdOption(COMMAND_DEFAULT_CLASS): - """ - Set an account option - - Usage: - option[/save] [name = value] - - Switches: - save - Save the current option settings for future logins. - clear - Clear the saved options. - - This command allows for viewing and setting client interface - settings. Note that saved options may not be able to be used if - later connecting with a client with different capabilities. - - - """ - - key = "option" - aliases = "options" - switch_options = ("save", "clear") - locks = "cmd:all()" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """ - Implements the command - """ - if self.session is None: - return - - flags = self.session.protocol_flags - - # Display current options - if not self.args: - # list the option settings - - if "save" in self.switches: - # save all options - self.caller.db._saved_protocol_flags = flags - self.msg("|gSaved all options. Use option/clear to remove.|n") - if "clear" in self.switches: - # clear all saves - self.caller.db._saved_protocol_flags = {} - self.msg("|gCleared all saved options.") - - options = dict(flags) # make a copy of the flag dict - saved_options = dict(self.caller.attributes.get("_saved_protocol_flags", default={})) - - if "SCREENWIDTH" in options: - if len(options["SCREENWIDTH"]) == 1: - options["SCREENWIDTH"] = options["SCREENWIDTH"][0] - else: - options["SCREENWIDTH"] = " \n".join( - "%s : %s" % (screenid, size) - for screenid, size in options["SCREENWIDTH"].items() - ) - if "SCREENHEIGHT" in options: - if len(options["SCREENHEIGHT"]) == 1: - options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0] - else: - options["SCREENHEIGHT"] = " \n".join( - "%s : %s" % (screenid, size) - for screenid, size in options["SCREENHEIGHT"].items() - ) - options.pop("TTYPE", None) - - header = ("Name", "Value", "Saved") if saved_options else ("Name", "Value") - table = self.styled_table(*header) - for key in sorted(options): - row = [key, options[key]] - if saved_options: - saved = " |YYes|n" if key in saved_options else "" - changed = ( - "|y*|n" if key in saved_options and flags[key] != saved_options[key] else "" - ) - row.append("%s%s" % (saved, changed)) - table.add_row(*row) - self.msg("|wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table)) - - return - - if not self.rhs: - self.msg("Usage: option [name = [value]]") - return - - # Try to assign new values - - def validate_encoding(new_encoding): - # helper: change encoding - try: - codecs_lookup(new_encoding) - except LookupError: - raise RuntimeError("The encoding '|w%s|n' is invalid. " % new_encoding) - return val - - def validate_size(new_size): - return {0: int(new_size)} - - def validate_bool(new_bool): - return True if new_bool.lower() in ("true", "on", "1") else False - - def update(new_name, new_val, validator): - # helper: update property and report errors - try: - old_val = flags.get(new_name, False) - new_val = validator(new_val) - if old_val == new_val: - self.msg("Option |w%s|n was kept as '|w%s|n'." % (new_name, old_val)) - else: - flags[new_name] = new_val - self.msg( - "Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." - % (new_name, old_val, new_val) - ) - return {new_name: new_val} - except Exception as err: - self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err)) - return False - - validators = { - "ANSI": validate_bool, - "CLIENTNAME": utils.to_str, - "ENCODING": validate_encoding, - "MCCP": validate_bool, - "NOGOAHEAD": validate_bool, - "MXP": validate_bool, - "NOCOLOR": validate_bool, - "NOPKEEPALIVE": validate_bool, - "OOB": validate_bool, - "RAW": validate_bool, - "SCREENHEIGHT": validate_size, - "SCREENWIDTH": validate_size, - "SCREENREADER": validate_bool, - "TERM": utils.to_str, - "UTF-8": validate_bool, - "XTERM256": validate_bool, - "INPUTDEBUG": validate_bool, - "FORCEDENDLINE": validate_bool, - } - - name = self.lhs.upper() - val = self.rhs.strip() - optiondict = False - if val and name in validators: - optiondict = update(name, val, validators[name]) - else: - self.msg("|rNo option named '|w%s|r'." % name) - if optiondict: - # a valid setting - if "save" in self.switches: - # save this option only - saved_options = self.account.attributes.get("_saved_protocol_flags", default={}) - saved_options.update(optiondict) - self.account.attributes.add("_saved_protocol_flags", saved_options) - for key in optiondict: - self.msg("|gSaved option %s.|n" % key) - if "clear" in self.switches: - # clear this save - for key in optiondict: - self.account.attributes.get("_saved_protocol_flags", {}).pop(key, None) - self.msg("|gCleared saved %s." % key) - self.session.update_flags(**optiondict)
- - -
[docs]class CmdPassword(COMMAND_DEFAULT_CLASS): - """ - change your password - - Usage: - password <old password> = <new password> - - Changes your password. Make sure to pick a safe one. - """ - - key = "password" - locks = "cmd:pperm(Player)" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """hook function.""" - - account = self.account - if not self.rhs: - self.msg("Usage: password <oldpass> = <newpass>") - return - oldpass = self.lhslist[0] # Both of these are - newpass = self.rhslist[0] # already stripped by parse() - - # Validate password - validated, error = account.validate_password(newpass) - - if not account.check_password(oldpass): - self.msg("The specified old password isn't correct.") - elif not validated: - errors = [e for suberror in error.messages for e in error.messages] - string = "\n".join(errors) - self.msg(string) - else: - account.set_password(newpass) - account.save() - self.msg("Password changed.") - logger.log_sec( - "Password Changed: %s (Caller: %s, IP: %s)." - % (account, account, self.session.address) - )
- - -
[docs]class CmdQuit(COMMAND_DEFAULT_CLASS): - """ - quit the game - - Usage: - quit - - Switch: - all - disconnect all connected sessions - - Gracefully disconnect your current session from the - game. Use the /all switch to disconnect from all sessions. - """ - - key = "quit" - switch_options = ("all",) - locks = "cmd:all()" - - # this is used by the parent - account_caller = True - -
[docs] def func(self): - """hook function""" - account = self.account - - if "all" in self.switches: - account.msg( - "|RQuitting|n all sessions. Hope to see you soon again.", session=self.session - ) - reason = "quit/all" - for session in account.sessions.all(): - account.disconnect_session_from_account(session, reason) - else: - nsess = len(account.sessions.all()) - reason = "quit" - if nsess == 2: - account.msg("|RQuitting|n. One session is still connected.", session=self.session) - elif nsess > 2: - account.msg( - "|RQuitting|n. %i sessions are still connected." % (nsess - 1), - session=self.session, - ) - else: - # we are quitting the last available session - account.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session) - account.disconnect_session_from_account(self.session, reason)
- - -
[docs]class CmdColorTest(COMMAND_DEFAULT_CLASS): - """ - testing which colors your client support - - Usage: - color ansi||xterm256 - - Prints a color map along with in-mud color codes to use to produce - them. It also tests what is supported in your client. Choices are - 16-color ansi (supported in most muds) or the 256-color xterm256 - standard. No checking is done to determine your client supports - color - if not you will see rubbish appear. - """ - - key = "color" - locks = "cmd:all()" - help_category = "General" - - # this is used by the parent - account_caller = True - - # the slices of the ANSI_PARSER lists to use for retrieving the - # relevant color tags to display. Replace if using another schema. - # This command can only show one set of markup. - slice_bright_fg = slice(7, 15) # from ANSI_PARSER.ansi_map - slice_dark_fg = slice(15, 23) # from ANSI_PARSER.ansi_map - slice_dark_bg = slice(-8, None) # from ANSI_PARSER.ansi_map - slice_bright_bg = slice(None, None) # from ANSI_PARSER.ansi_xterm256_bright_bg_map - -
[docs] def table_format(self, table): - """ - Helper method to format the ansi/xterm256 tables. - Takes a table of columns [[val,val,...],[val,val,...],...] - """ - if not table: - return [[]] - - extra_space = 1 - max_widths = [max([len(str(val)) for val in col]) for col in table] - ftable = [] - for irow in range(len(table[0])): - ftable.append( - [ - str(col[irow]).ljust(max_widths[icol]) + " " * extra_space - for icol, col in enumerate(table) - ] - ) - return ftable
- -
[docs] def func(self): - """Show color tables""" - - if self.args.startswith("a"): - # show ansi 16-color table - from evennia.utils import ansi - - ap = ansi.ANSI_PARSER - # ansi colors - # show all ansi color-related codes - bright_fg = [ - "%s%s|n" % (code, code.replace("|", "||")) - for code, _ in ap.ansi_map[self.slice_bright_fg] - ] - dark_fg = [ - "%s%s|n" % (code, code.replace("|", "||")) - for code, _ in ap.ansi_map[self.slice_dark_fg] - ] - dark_bg = [ - "%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) - for code, _ in ap.ansi_map[self.slice_dark_bg] - ] - bright_bg = [ - "%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) - for code, _ in ap.ansi_xterm256_bright_bg_map[self.slice_bright_bg] - ] - dark_fg.extend(["" for _ in range(len(bright_fg) - len(dark_fg))]) - table = utils.format_table([bright_fg, dark_fg, bright_bg, dark_bg]) - string = "ANSI colors:" - for row in table: - string += "\n " + " ".join(row) - self.msg(string) - self.msg( - "||X : black. ||/ : return, ||- : tab, ||_ : space, ||* : invert, ||u : underline\n" - "To combine background and foreground, add background marker last, e.g. ||r||[B.\n" - "Note: bright backgrounds like ||[r requires your client handling Xterm256 colors." - ) - - elif self.args.startswith("x"): - # show xterm256 table - table = [[], [], [], [], [], [], [], [], [], [], [], []] - for ir in range(6): - for ig in range(6): - for ib in range(6): - # foreground table - table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib))) - # background table - table[6 + ir].append( - "|%i%i%i|[%i%i%i%s|n" - % (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib)) - ) - table = self.table_format(table) - string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):" - string += "\n" + "\n".join("".join(row) for row in table) - table = [[], [], [], [], [], [], [], [], [], [], [], []] - for ibatch in range(4): - for igray in range(6): - letter = chr(97 + (ibatch * 6 + igray)) - inverse = chr(122 - (ibatch * 6 + igray)) - table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % letter)) - table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter)) - for igray in range(6): - # the last row (y, z) has empty columns - if igray < 2: - letter = chr(121 + igray) - inverse = chr(98 - igray) - fg = "|=%s%s |n" % (letter, "||=%s" % letter) - bg = "|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter) - else: - fg, bg = " ", " " - table[0 + igray].append(fg) - table[6 + igray].append(bg) - table = self.table_format(table) - string += "\n" + "\n".join("".join(row) for row in table) - self.msg(string) - else: - # malformed input - self.msg("Usage: color ansi||xterm256")
- - -
[docs]class CmdQuell(COMMAND_DEFAULT_CLASS): - """ - use character's permissions instead of account's - - Usage: - quell - unquell - - Normally the permission level of the Account is used when puppeting a - Character/Object to determine access. This command will switch the lock - system to make use of the puppeted Object's permissions instead. This is - useful mainly for testing. - Hierarchical permission quelling only work downwards, thus an Account cannot - use a higher-permission Character to escalate their permission level. - Use the unquell command to revert back to normal operation. - """ - - key = "quell" - aliases = ["unquell"] - locks = "cmd:pperm(Player)" - help_category = "General" - - # this is used by the parent - account_caller = True - - def _recache_locks(self, account): - """Helper method to reset the lockhandler on an already puppeted object""" - if self.session: - char = self.session.puppet - if char: - # we are already puppeting an object. We need to reset - # the lock caches (otherwise the superuser status change - # won't be visible until repuppet) - char.locks.reset() - account.locks.reset() - -
[docs] def func(self): - """Perform the command""" - account = self.account - permstr = ( - account.is_superuser - and " (superuser)" - or "(%s)" % (", ".join(account.permissions.all())) - ) - if self.cmdstring in ("unquell", "unquell"): - if not account.attributes.get("_quell"): - self.msg("Already using normal Account permissions %s." % permstr) - else: - account.attributes.remove("_quell") - self.msg("Account permissions %s restored." % permstr) - else: - if account.attributes.get("_quell"): - self.msg("Already quelling Account %s permissions." % permstr) - return - account.attributes.add("_quell", True) - puppet = self.session.puppet if self.session else None - if puppet: - cpermstr = "(%s)" % ", ".join(puppet.permissions.all()) - cpermstr = "Quelling to current puppet's permissions %s." % cpermstr - cpermstr += ( - "\n(Note: If this is higher than Account permissions %s," - " the lowest of the two will be used.)" % permstr - ) - cpermstr += "\nUse unquell to return to normal permission usage." - self.msg(cpermstr) - else: - self.msg("Quelling Account permissions%s. Use unquell to get them back." % permstr) - self._recache_locks(account)
- - -
[docs]class CmdStyle(COMMAND_DEFAULT_CLASS): - """ - In-game style options - - Usage: - style - style <option> = <value> - - Configure stylings for in-game display elements like table borders, help - entriest etc. Use without arguments to see all available options. - - """ - - key = "style" - switch_options = ["clear"] - -
[docs] def func(self): - if not self.args: - self.list_styles() - return - self.set()
- -
[docs] def list_styles(self): - table = self.styled_table("Option", "Description", "Type", "Value", width=78) - for op_key in self.account.options.options_dict.keys(): - op_found = self.account.options.get(op_key, return_obj=True) - table.add_row( - op_key, op_found.description, op_found.__class__.__name__, op_found.display() - ) - self.msg(str(table))
- -
[docs] def set(self): - try: - result = self.account.options.set(self.lhs, self.rhs) - except ValueError as e: - self.msg(str(e)) - return - self.msg("Style %s set to %s" % (self.lhs, result))
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/admin.html b/docs/0.9.5/_modules/evennia/commands/default/admin.html deleted file mode 100644 index 4ae0938150..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/admin.html +++ /dev/null @@ -1,701 +0,0 @@ - - - - - - - - evennia.commands.default.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.admin

-"""
-
-Admin commands
-
-"""
-
-import time
-import re
-from django.conf import settings
-from evennia.server.sessionhandler import SESSIONS
-from evennia.server.models import ServerConfig
-from evennia.utils import evtable, logger, search, class_from_module
-
-COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
-
-# limit members for API inclusion
-__all__ = (
-    "CmdBoot",
-    "CmdBan",
-    "CmdUnban",
-    "CmdEmit",
-    "CmdNewPassword",
-    "CmdPerm",
-    "CmdWall",
-    "CmdForce",
-)
-
-
-
[docs]class CmdBoot(COMMAND_DEFAULT_CLASS): - """ - kick an account from the server. - - Usage - boot[/switches] <account obj> [: reason] - - Switches: - quiet - Silently boot without informing account - sid - boot by session id instead of name or dbref - - Boot an account object from the server. If a reason is - supplied it will be echoed to the user unless /quiet is set. - """ - - key = "boot" - switch_options = ("quiet", "sid") - locks = "cmd:perm(boot) or perm(Admin)" - help_category = "Admin" - -
[docs] def func(self): - """Implementing the function""" - caller = self.caller - args = self.args - - if not args: - caller.msg("Usage: boot[/switches] <account> [:reason]") - return - - if ":" in args: - args, reason = [a.strip() for a in args.split(":", 1)] - else: - args, reason = args, "" - - boot_list = [] - - if "sid" in self.switches: - # Boot a particular session id. - sessions = SESSIONS.get_sessions(True) - for sess in sessions: - # Find the session with the matching session id. - if sess.sessid == int(args): - boot_list.append(sess) - break - else: - # Boot by account object - pobj = search.account_search(args) - if not pobj: - caller.msg("Account %s was not found." % args) - return - pobj = pobj[0] - if not pobj.access(caller, "boot"): - string = "You don't have the permission to boot %s." % (pobj.key,) - caller.msg(string) - return - # we have a bootable object with a connected user - matches = SESSIONS.sessions_from_account(pobj) - for match in matches: - boot_list.append(match) - - if not boot_list: - caller.msg("No matching sessions found. The Account does not seem to be online.") - return - - # Carry out the booting of the sessions in the boot list. - - feedback = None - if "quiet" not in self.switches: - feedback = "You have been disconnected by %s.\n" % caller.name - if reason: - feedback += "\nReason given: %s" % reason - - for session in boot_list: - session.msg(feedback) - session.account.disconnect_session_from_account(session) - - if pobj and boot_list: - logger.log_sec( - "Booted: %s (Reason: %s, Caller: %s, IP: %s)." - % (pobj, reason, caller, self.session.address) - )
- - -# regex matching IP addresses with wildcards, eg. 233.122.4.* -IPREGEX = re.compile(r"[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}") - - -def list_bans(cmd, banlist): - """ - Helper function to display a list of active bans. Input argument - is the banlist read into the two commands ban and unban below. - - Args: - cmd (Command): Instance of the Ban command. - banlist (list): List of bans to list. - """ - if not banlist: - return "No active bans were found." - - table = cmd.styled_table("|wid", "|wname/ip", "|wdate", "|wreason") - for inum, ban in enumerate(banlist): - table.add_row(str(inum + 1), ban[0] and ban[0] or ban[1], ban[3], ban[4]) - return "|wActive bans:|n\n%s" % table - - -
[docs]class CmdBan(COMMAND_DEFAULT_CLASS): - """ - ban an account from the server - - Usage: - ban [<name or ip> [: reason]] - - Without any arguments, shows numbered list of active bans. - - This command bans a user from accessing the game. Supply an optional - reason to be able to later remember why the ban was put in place. - - It is often preferable to ban an account from the server than to - delete an account with accounts/delete. If banned by name, that account - account can no longer be logged into. - - IP (Internet Protocol) address banning allows blocking all access - from a specific address or subnet. Use an asterisk (*) as a - wildcard. - - Examples: - ban thomas - ban account 'thomas' - ban/ip 134.233.2.111 - ban specific ip address - ban/ip 134.233.2.* - ban all in a subnet - ban/ip 134.233.*.* - even wider ban - - A single IP filter can be easy to circumvent by changing computers - or requesting a new IP address. Setting a wide IP block filter with - wildcards might be tempting, but remember that it may also - accidentally block innocent users connecting from the same country - or region. - - """ - - key = "ban" - aliases = ["bans"] - locks = "cmd:perm(ban) or perm(Developer)" - help_category = "Admin" - -
[docs] def func(self): - """ - Bans are stored in a serverconf db object as a list of - dictionaries: - [ (name, ip, ipregex, date, reason), - (name, ip, ipregex, date, reason),... ] - where name and ip are set by the user and are shown in - lists. ipregex is a converted form of ip where the * is - replaced by an appropriate regex pattern for fast - matching. date is the time stamp the ban was instigated and - 'reason' is any optional info given to the command. Unset - values in each tuple is set to the empty string. - """ - banlist = ServerConfig.objects.conf("server_bans") - if not banlist: - banlist = [] - - if not self.args or ( - self.switches and not any(switch in ("ip", "name") for switch in self.switches) - ): - self.caller.msg(list_bans(self, banlist)) - return - - now = time.ctime() - reason = "" - if ":" in self.args: - ban, reason = self.args.rsplit(":", 1) - else: - ban = self.args - ban = ban.lower() - ipban = IPREGEX.findall(ban) - if not ipban: - # store as name - typ = "Name" - bantup = (ban, "", "", now, reason) - else: - # an ip address. - typ = "IP" - ban = ipban[0] - # replace * with regex form and compile it - ipregex = ban.replace(".", "\.") - ipregex = ipregex.replace("*", "[0-9]{1,3}") - ipregex = re.compile(r"%s" % ipregex) - bantup = ("", ban, ipregex, now, reason) - # save updated banlist - banlist.append(bantup) - ServerConfig.objects.conf("server_bans", banlist) - self.caller.msg("%s-Ban |w%s|n was added." % (typ, ban)) - logger.log_sec( - "Banned %s: %s (Caller: %s, IP: %s)." - % (typ, ban.strip(), self.caller, self.session.address) - )
- - -
[docs]class CmdUnban(COMMAND_DEFAULT_CLASS): - """ - remove a ban from an account - - Usage: - unban <banid> - - This will clear an account name/ip ban previously set with the ban - command. Use this command without an argument to view a numbered - list of bans. Use the numbers in this list to select which one to - unban. - - """ - - key = "unban" - locks = "cmd:perm(unban) or perm(Developer)" - help_category = "Admin" - -
[docs] def func(self): - """Implement unbanning""" - - banlist = ServerConfig.objects.conf("server_bans") - - if not self.args: - self.caller.msg(list_bans(self, banlist)) - return - - try: - num = int(self.args) - except Exception: - self.caller.msg("You must supply a valid ban id to clear.") - return - - if not banlist: - self.caller.msg("There are no bans to clear.") - elif not (0 < num < len(banlist) + 1): - self.caller.msg("Ban id |w%s|x was not found." % self.args) - else: - # all is ok, clear ban - ban = banlist[num - 1] - del banlist[num - 1] - ServerConfig.objects.conf("server_bans", banlist) - value = " ".join([s for s in ban[:2]]) - self.caller.msg("Cleared ban %s: %s" % (num, value)) - logger.log_sec( - "Unbanned: %s (Caller: %s, IP: %s)." - % (value.strip(), self.caller, self.session.address) - )
- - -
[docs]class CmdEmit(COMMAND_DEFAULT_CLASS): - """ - admin command for emitting message to multiple objects - - Usage: - emit[/switches] [<obj>, <obj>, ... =] <message> - remit [<obj>, <obj>, ... =] <message> - pemit [<obj>, <obj>, ... =] <message> - - Switches: - room - limit emits to rooms only (default) - accounts - limit emits to accounts only - contents - send to the contents of matched objects too - - Emits a message to the selected objects or to - your immediate surroundings. If the object is a room, - send to its contents. remit and pemit are just - limited forms of emit, for sending to rooms and - to accounts respectively. - """ - - key = "emit" - aliases = ["pemit", "remit"] - switch_options = ("room", "accounts", "contents") - locks = "cmd:perm(emit) or perm(Builder)" - help_category = "Admin" - -
[docs] def func(self): - """Implement the command""" - - caller = self.caller - args = self.args - - if not args: - string = "Usage: " - string += "\nemit[/switches] [<obj>, <obj>, ... =] <message>" - string += "\nremit [<obj>, <obj>, ... =] <message>" - string += "\npemit [<obj>, <obj>, ... =] <message>" - caller.msg(string) - return - - rooms_only = "rooms" in self.switches - accounts_only = "accounts" in self.switches - send_to_contents = "contents" in self.switches - - # we check which command was used to force the switches - if self.cmdstring == "remit": - rooms_only = True - send_to_contents = True - elif self.cmdstring == "pemit": - accounts_only = True - - if not self.rhs: - message = self.args - objnames = [caller.location.key] - else: - message = self.rhs - objnames = self.lhslist - - # send to all objects - for objname in objnames: - obj = caller.search(objname, global_search=True) - if not obj: - return - if rooms_only and obj.location is not None: - caller.msg("%s is not a room. Ignored." % objname) - continue - if accounts_only and not obj.has_account: - caller.msg("%s has no active account. Ignored." % objname) - continue - if obj.access(caller, "tell"): - obj.msg(message) - if send_to_contents and hasattr(obj, "msg_contents"): - obj.msg_contents(message) - caller.msg("Emitted to %s and contents:\n%s" % (objname, message)) - else: - caller.msg("Emitted to %s:\n%s" % (objname, message)) - else: - caller.msg("You are not allowed to emit to %s." % objname)
- - -
[docs]class CmdNewPassword(COMMAND_DEFAULT_CLASS): - """ - change the password of an account - - Usage: - userpassword <user obj> = <new password> - - Set an account's password. - """ - - key = "userpassword" - locks = "cmd:perm(newpassword) or perm(Admin)" - help_category = "Admin" - -
[docs] def func(self): - """Implement the function.""" - - caller = self.caller - - if not self.rhs: - self.msg("Usage: userpassword <user obj> = <new password>") - return - - # the account search also matches 'me' etc. - account = caller.search_account(self.lhs) - if not account: - return - - newpass = self.rhs - - # Validate password - validated, error = account.validate_password(newpass) - if not validated: - errors = [e for suberror in error.messages for e in error.messages] - string = "\n".join(errors) - caller.msg(string) - return - - account.set_password(newpass) - account.save() - self.msg("%s - new password set to '%s'." % (account.name, newpass)) - if account.character != caller: - account.msg("%s has changed your password to '%s'." % (caller.name, newpass)) - logger.log_sec( - "Password Changed: %s (Caller: %s, IP: %s)." % (account, caller, self.session.address) - )
- - -
[docs]class CmdPerm(COMMAND_DEFAULT_CLASS): - """ - set the permissions of an account/object - - Usage: - perm[/switch] <object> [= <permission>[,<permission>,...]] - perm[/switch] *<account> [= <permission>[,<permission>,...]] - - Switches: - del - delete the given permission from <object> or <account>. - account - set permission on an account (same as adding * to name) - - This command sets/clears individual permission strings on an object - or account. If no permission is given, list all permissions on <object>. - """ - - key = "perm" - aliases = "setperm" - switch_options = ("del", "account") - locks = "cmd:perm(perm) or perm(Developer)" - help_category = "Admin" - -
[docs] def func(self): - """Implement function""" - - caller = self.caller - switches = self.switches - lhs, rhs = self.lhs, self.rhs - - if not self.args: - string = "Usage: perm[/switch] object [ = permission, permission, ...]" - caller.msg(string) - return - - accountmode = "account" in self.switches or lhs.startswith("*") - lhs = lhs.lstrip("*") - - if accountmode: - obj = caller.search_account(lhs) - else: - obj = caller.search(lhs, global_search=True) - if not obj: - return - - if not rhs: - if not obj.access(caller, "examine"): - caller.msg("You are not allowed to examine this object.") - return - - string = "Permissions on |w%s|n: " % obj.key - if not obj.permissions.all(): - string += "<None>" - else: - string += ", ".join(obj.permissions.all()) - if ( - hasattr(obj, "account") - and hasattr(obj.account, "is_superuser") - and obj.account.is_superuser - ): - string += "\n(... but this object is currently controlled by a SUPERUSER! " - string += "All access checks are passed automatically.)" - caller.msg(string) - return - - # we supplied an argument on the form obj = perm - locktype = "edit" if accountmode else "control" - if not obj.access(caller, locktype): - caller.msg( - "You are not allowed to edit this %s's permissions." - % ("account" if accountmode else "object") - ) - return - - caller_result = [] - target_result = [] - if "del" in switches: - # delete the given permission(s) from object. - for perm in self.rhslist: - obj.permissions.remove(perm) - if obj.permissions.get(perm): - caller_result.append( - "\nPermissions %s could not be removed from %s." % (perm, obj.name) - ) - else: - caller_result.append( - "\nPermission %s removed from %s (if they existed)." % (perm, obj.name) - ) - target_result.append( - "\n%s revokes the permission(s) %s from you." % (caller.name, perm) - ) - logger.log_sec( - "Permissions Deleted: %s, %s (Caller: %s, IP: %s)." - % (perm, obj, caller, self.session.address) - ) - else: - # add a new permission - permissions = obj.permissions.all() - - for perm in self.rhslist: - - # don't allow to set a permission higher in the hierarchy than - # the one the caller has (to prevent self-escalation) - if perm.lower() in PERMISSION_HIERARCHY and not obj.locks.check_lockstring( - caller, "dummy:perm(%s)" % perm - ): - caller.msg( - "You cannot assign a permission higher than the one you have yourself." - ) - return - - if perm in permissions: - caller_result.append( - "\nPermission '%s' is already defined on %s." % (perm, obj.name) - ) - else: - obj.permissions.add(perm) - plystring = "the Account" if accountmode else "the Object/Character" - caller_result.append( - "\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring) - ) - target_result.append( - "\n%s gives you (%s, %s) the permission '%s'." - % (caller.name, obj.name, plystring, perm) - ) - logger.log_sec( - "Permissions Added: %s, %s (Caller: %s, IP: %s)." - % (obj, perm, caller, self.session.address) - ) - - caller.msg("".join(caller_result).strip()) - if target_result: - obj.msg("".join(target_result).strip())
- - -
[docs]class CmdWall(COMMAND_DEFAULT_CLASS): - """ - make an announcement to all - - Usage: - wall <message> - - Announces a message to all connected sessions - including all currently unlogged in. - """ - - key = "wall" - locks = "cmd:perm(wall) or perm(Admin)" - help_category = "Admin" - -
[docs] def func(self): - """Implements command""" - if not self.args: - self.caller.msg("Usage: wall <message>") - return - message = '%s shouts "%s"' % (self.caller.name, self.args) - self.msg("Announcing to all connected sessions ...") - SESSIONS.announce_all(message)
- - -
[docs]class CmdForce(COMMAND_DEFAULT_CLASS): - """ - forces an object to execute a command - - Usage: - force <object>=<command string> - - Example: - force bob=get stick - """ - - key = "force" - locks = "cmd:perm(spawn) or perm(Builder)" - help_category = "Building" - perm_used = "edit" - -
[docs] def func(self): - """Implements the force command""" - if not self.lhs or not self.rhs: - self.caller.msg("You must provide a target and a command string to execute.") - return - targ = self.caller.search(self.lhs) - if not targ: - return - if not targ.access(self.caller, self.perm_used): - self.caller.msg("You don't have permission to force them to execute commands.") - return - targ.execute_cmd(self.rhs) - self.caller.msg("You have forced %s to: %s" % (targ, self.rhs))
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/batchprocess.html b/docs/0.9.5/_modules/evennia/commands/default/batchprocess.html deleted file mode 100644 index 718911a2cf..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/batchprocess.html +++ /dev/null @@ -1,927 +0,0 @@ - - - - - - - - evennia.commands.default.batchprocess — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.batchprocess

-"""
-Batch processors
-
-These commands implements the 'batch-command' and 'batch-code'
-processors, using the functionality in evennia.utils.batchprocessors.
-They allow for offline world-building.
-
-Batch-command is the simpler system. This reads a file (*.ev)
-containing a list of in-game commands and executes them in sequence as
-if they had been entered in the game (including permission checks
-etc).
-
-Batch-code is a full-fledged python code interpreter that reads blocks
-of python code (*.py) and executes them in sequence. This allows for
-much more power than Batch-command, but requires knowing Python and
-the Evennia API.  It is also a severe security risk and should
-therefore always be limited to superusers only.
-
-"""
-import re
-
-from django.conf import settings
-from evennia.utils.batchprocessors import BATCHCMD, BATCHCODE
-from evennia.commands.cmdset import CmdSet
-from evennia.utils import logger, utils
-
-
-_RE_COMMENT = re.compile(r"^#.*?$", re.MULTILINE + re.DOTALL)
-_RE_CODE_START = re.compile(r"^# batchcode code:", re.MULTILINE)
-_COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-# limit symbols for API inclusion
-__all__ = ("CmdBatchCommands", "CmdBatchCode")
-
-_HEADER_WIDTH = 70
-_UTF8_ERROR = """
- |rDecode error in '%s'.|n
-
- This file contains non-ascii character(s). This is common if you
- wrote some input in a language that has more letters and special
- symbols than English; such as accents or umlauts.  This is usually
- fine and fully supported! But for Evennia to know how to decode such
- characters in a universal way, the batchfile must be saved with the
- international 'UTF-8' encoding. This file is not.
-
- Please re-save the batchfile with the UTF-8 encoding (refer to the
- documentation of your text editor on how to do this, or switch to a
- better featured one) and try again.
-
- Error reported was: '%s'
-"""
-
-
-# -------------------------------------------------------------
-# Helper functions
-# -------------------------------------------------------------
-
-
-def format_header(caller, entry):
-    """
-    Formats a header
-    """
-    width = _HEADER_WIDTH - 10
-    # strip all comments for the header
-    if caller.ndb.batch_batchmode != "batch_commands":
-        # only do cleanup for  batchcode
-        entry = _RE_CODE_START.split(entry, 1)[1]
-        entry = _RE_COMMENT.sub("", entry).strip()
-    header = utils.crop(entry, width=width)
-    ptr = caller.ndb.batch_stackptr + 1
-    stacklen = len(caller.ndb.batch_stack)
-    header = "|w%02i/%02i|G: %s|n" % (ptr, stacklen, header)
-    # add extra space to the side for padding.
-    header = "%s%s" % (header, " " * (width - len(header)))
-    header = header.replace("\n", "\\n")
-
-    return header
-
-
-def format_code(entry):
-    """
-    Formats the viewing of code and errors
-    """
-    code = ""
-    for line in entry.split("\n"):
-        code += "\n|G>>>|n %s" % line
-    return code.strip()
-
-
-def batch_cmd_exec(caller):
-    """
-    Helper function for executing a single batch-command entry
-    """
-    ptr = caller.ndb.batch_stackptr
-    stack = caller.ndb.batch_stack
-    command = stack[ptr]
-    caller.msg(format_header(caller, command))
-    try:
-        caller.execute_cmd(command)
-    except Exception:
-        logger.log_trace()
-        return False
-    return True
-
-
-def batch_code_exec(caller):
-    """
-    Helper function for executing a single batch-code entry
-    """
-    ptr = caller.ndb.batch_stackptr
-    stack = caller.ndb.batch_stack
-    debug = caller.ndb.batch_debug
-    code = stack[ptr]
-
-    caller.msg(format_header(caller, code))
-    err = BATCHCODE.code_exec(code, extra_environ={"caller": caller}, debug=debug)
-    if err:
-        caller.msg(format_code(err))
-        return False
-    return True
-
-
-def step_pointer(caller, step=1):
-    """
-    Step in stack, returning the item located.
-
-    stackptr - current position in stack
-    stack - the stack of units
-    step - how many steps to move from stackptr
-    """
-    ptr = caller.ndb.batch_stackptr
-    stack = caller.ndb.batch_stack
-    nstack = len(stack)
-    if ptr + step <= 0:
-        caller.msg("|RBeginning of batch file.")
-    if ptr + step >= nstack:
-        caller.msg("|REnd of batch file.")
-    caller.ndb.batch_stackptr = max(0, min(nstack - 1, ptr + step))
-
-
-def show_curr(caller, showall=False):
-    """
-    Show the current position in stack
-    """
-    stackptr = caller.ndb.batch_stackptr
-    stack = caller.ndb.batch_stack
-
-    if stackptr >= len(stack):
-        caller.ndb.batch_stackptr = len(stack) - 1
-        show_curr(caller, showall)
-        return
-
-    entry = stack[stackptr]
-
-    string = format_header(caller, entry)
-    codeall = entry.strip()
-    string += "|G(hh for help)"
-    if showall:
-        for line in codeall.split("\n"):
-            string += "\n|G||n %s" % line
-    caller.msg(string)
-
-
-def purge_processor(caller):
-    """
-    This purges all effects running
-    on the caller.
-    """
-    try:
-        del caller.ndb.batch_stack
-        del caller.ndb.batch_stackptr
-        del caller.ndb.batch_pythonpath
-        del caller.ndb.batch_batchmode
-    except Exception:
-        # something might have already been erased; it's not critical
-        pass
-    # clear everything back to the state before the batch call
-    if caller.ndb.batch_cmdset_backup:
-        caller.cmdset.cmdset_stack = caller.ndb.batch_cmdset_backup
-        caller.cmdset.update()
-        del caller.ndb.batch_cmdset_backup
-    else:
-        # something went wrong. Purge cmdset except default
-        caller.cmdset.clear()
-
-    caller.scripts.validate()  # this will purge interactive mode
-
-
-# -------------------------------------------------------------
-# main access commands
-# -------------------------------------------------------------
-
-
-
[docs]class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): - """ - build from batch-command file - - Usage: - batchcommands[/interactive] <python.path.to.file> - - Switch: - interactive - this mode will offer more control when - executing the batch file, like stepping, - skipping, reloading etc. - - Runs batches of commands from a batch-cmd text file (*.ev). - - """ - - key = "batchcommands" - aliases = ["batchcommand", "batchcmd"] - switch_options = ("interactive",) - locks = "cmd:perm(batchcommands) or perm(Developer)" - help_category = "Building" - -
[docs] def func(self): - """Starts the processor.""" - - caller = self.caller - - args = self.args - if not args: - caller.msg("Usage: batchcommands[/interactive] <path.to.file>") - return - python_path = self.args - - # parse indata file - - try: - commands = BATCHCMD.parse_file(python_path) - except UnicodeDecodeError as err: - caller.msg(_UTF8_ERROR % (python_path, err)) - return - except IOError as err: - if err: - err = "{}\n".format(str(err)) - else: - err = "" - string = ( - "%s'%s' could not load. You have to supply python paths " - "from one of the defined batch-file directories\n (%s)." - ) - caller.msg(string % (err, python_path, ", ".join(settings.BASE_BATCHPROCESS_PATHS))) - return - if not commands: - caller.msg("File %s seems empty of valid commands." % python_path) - return - - switches = self.switches - - # Store work data in cache - caller.ndb.batch_stack = commands - caller.ndb.batch_stackptr = 0 - caller.ndb.batch_pythonpath = python_path - caller.ndb.batch_batchmode = "batch_commands" - # we use list() here to create a new copy of the cmdset stack - caller.ndb.batch_cmdset_backup = list(caller.cmdset.cmdset_stack) - caller.cmdset.add(BatchSafeCmdSet) - - if "inter" in switches or "interactive" in switches: - # Allow more control over how batch file is executed - - # Set interactive state directly - caller.cmdset.add(BatchInteractiveCmdSet) - - caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path) - show_curr(caller) - else: - caller.msg( - "Running Batch-command processor - Automatic mode " - "for %s (this might take some time) ..." % python_path - ) - - # run in-process (might block) - for _ in range(len(commands)): - # loop through the batch file - if not batch_cmd_exec(caller): - return - step_pointer(caller, 1) - # clean out the safety cmdset and clean out all other - # temporary attrs. - string = " Batchfile '%s' applied." % python_path - caller.msg("|G%s" % string) - purge_processor(caller)
- - -
[docs]class CmdBatchCode(_COMMAND_DEFAULT_CLASS): - """ - build from batch-code file - - Usage: - batchcode[/interactive] <python path to file> - - Switch: - interactive - this mode will offer more control when - executing the batch file, like stepping, - skipping, reloading etc. - debug - auto-delete all objects that has been marked as - deletable in the script file (see example files for - syntax). This is useful so as to to not leave multiple - object copies behind when testing out the script. - - Runs batches of commands from a batch-code text file (*.py). - - """ - - key = "batchcode" - aliases = ["batchcodes"] - switch_options = ("interactive", "debug") - locks = "cmd:superuser()" - help_category = "Building" - -
[docs] def func(self): - """Starts the processor.""" - - caller = self.caller - - args = self.args - if not args: - caller.msg("Usage: batchcode[/interactive/debug] <path.to.file>") - return - python_path = self.args - debug = "debug" in self.switches - - # parse indata file - try: - codes = BATCHCODE.parse_file(python_path) - except UnicodeDecodeError as err: - caller.msg(_UTF8_ERROR % (python_path, err)) - return - except IOError as err: - if err: - err = "{}\n".format(str(err)) - else: - err = "" - string = ( - "%s'%s' could not load. You have to supply python paths " - "from one of the defined batch-file directories\n (%s)." - ) - caller.msg(string % (err, python_path, ", ".join(settings.BASE_BATCHPROCESS_PATHS))) - return - if not codes: - caller.msg("File %s seems empty of functional code." % python_path) - return - - switches = self.switches - - # Store work data in cache - caller.ndb.batch_stack = codes - caller.ndb.batch_stackptr = 0 - caller.ndb.batch_pythonpath = python_path - caller.ndb.batch_batchmode = "batch_code" - caller.ndb.batch_debug = debug - # we use list() here to create a new copy of cmdset_stack - caller.ndb.batch_cmdset_backup = list(caller.cmdset.cmdset_stack) - caller.cmdset.add(BatchSafeCmdSet) - - if "inter" in switches or "interactive" in switches: - # Allow more control over how batch file is executed - - # Set interactive state directly - caller.cmdset.add(BatchInteractiveCmdSet) - - caller.msg("\nBatch-code processor - Interactive mode for %s ..." % python_path) - show_curr(caller) - else: - caller.msg("Running Batch-code processor - Automatic mode for %s ..." % python_path) - - for _ in range(len(codes)): - # loop through the batch file - if not batch_code_exec(caller): - return - step_pointer(caller, 1) - # clean out the safety cmdset and clean out all other - # temporary attrs. - string = " Batchfile '%s' applied." % python_path - caller.msg("|G%s" % string) - purge_processor(caller)
- - -# ------------------------------------------------------------- -# State-commands for the interactive batch processor modes -# (these are the same for both processors) -# ------------------------------------------------------------- - - -class CmdStateAbort(_COMMAND_DEFAULT_CLASS): - """ - abort - - This is a safety feature. It force-ejects us out of the processor and to - the default cmdset, regardless of what current cmdset the processor might - have put us in (e.g. when testing buggy scripts etc). - """ - - key = "abort" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - """Exit back to default.""" - purge_processor(self.caller) - self.caller.msg("Exited processor and reset out active cmdset back to the default one.") - - -class CmdStateLL(_COMMAND_DEFAULT_CLASS): - """ - ll - - Look at the full source for the current - command definition. - """ - - key = "ll" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - show_curr(self.caller, showall=True) - - -class CmdStatePP(_COMMAND_DEFAULT_CLASS): - """ - pp - - Process the currently shown command definition. - """ - - key = "pp" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - """ - This checks which type of processor we are running. - """ - caller = self.caller - if caller.ndb.batch_batchmode == "batch_code": - batch_code_exec(caller) - else: - batch_cmd_exec(caller) - - -class CmdStateRR(_COMMAND_DEFAULT_CLASS): - """ - rr - - Reload the batch file, keeping the current - position in it. - """ - - key = "rr" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - if caller.ndb.batch_batchmode == "batch_code": - new_data = BATCHCODE.parse_file(caller.ndb.batch_pythonpath) - else: - new_data = BATCHCMD.parse_file(caller.ndb.batch_pythonpath) - caller.ndb.batch_stack = new_data - caller.msg(format_code("File reloaded. Staying on same command.")) - show_curr(caller) - - -class CmdStateRRR(_COMMAND_DEFAULT_CLASS): - """ - rrr - - Reload the batch file, starting over - from the beginning. - """ - - key = "rrr" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - if caller.ndb.batch_batchmode == "batch_code": - BATCHCODE.parse_file(caller.ndb.batch_pythonpath) - else: - BATCHCMD.parse_file(caller.ndb.batch_pythonpath) - caller.ndb.batch_stackptr = 0 - caller.msg(format_code("File reloaded. Restarting from top.")) - show_curr(caller) - - -class CmdStateNN(_COMMAND_DEFAULT_CLASS): - """ - nn - - Go to next command. No commands are executed. - """ - - key = "nn" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - arg = self.args - if arg and arg.isdigit(): - step = int(self.args) - else: - step = 1 - step_pointer(caller, step) - show_curr(caller) - - -class CmdStateNL(_COMMAND_DEFAULT_CLASS): - """ - nl - - Go to next command, viewing its full source. - No commands are executed. - """ - - key = "nl" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - arg = self.args - if arg and arg.isdigit(): - step = int(self.args) - else: - step = 1 - step_pointer(caller, step) - show_curr(caller, showall=True) - - -class CmdStateBB(_COMMAND_DEFAULT_CLASS): - """ - bb - - Backwards to previous command. No commands - are executed. - """ - - key = "bb" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - arg = self.args - if arg and arg.isdigit(): - step = -int(self.args) - else: - step = -1 - step_pointer(caller, step) - show_curr(caller) - - -class CmdStateBL(_COMMAND_DEFAULT_CLASS): - """ - bl - - Backwards to previous command, viewing its full - source. No commands are executed. - """ - - key = "bl" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - arg = self.args - if arg and arg.isdigit(): - step = -int(self.args) - else: - step = -1 - step_pointer(caller, step) - show_curr(caller, showall=True) - - -class CmdStateSS(_COMMAND_DEFAULT_CLASS): - """ - ss [steps] - - Process current command, then step to the next - one. If steps is given, - process this many commands. - """ - - key = "ss" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - arg = self.args - if arg and arg.isdigit(): - step = int(self.args) - else: - step = 1 - - for _ in range(step): - if caller.ndb.batch_batchmode == "batch_code": - batch_code_exec(caller) - else: - batch_cmd_exec(caller) - step_pointer(caller, 1) - show_curr(caller) - - -class CmdStateSL(_COMMAND_DEFAULT_CLASS): - """ - sl [steps] - - Process current command, then step to the next - one, viewing its full source. If steps is given, - process this many commands. - """ - - key = "sl" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - arg = self.args - if arg and arg.isdigit(): - step = int(self.args) - else: - step = 1 - - for _ in range(step): - if caller.ndb.batch_batchmode == "batch_code": - batch_code_exec(caller) - else: - batch_cmd_exec(caller) - step_pointer(caller, 1) - show_curr(caller) - - -class CmdStateCC(_COMMAND_DEFAULT_CLASS): - """ - cc - - Continue to process all remaining - commands. - """ - - key = "cc" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - nstack = len(caller.ndb.batch_stack) - ptr = caller.ndb.batch_stackptr - step = nstack - ptr - - for _ in range(step): - if caller.ndb.batch_batchmode == "batch_code": - batch_code_exec(caller) - else: - batch_cmd_exec(caller) - step_pointer(caller, 1) - show_curr(caller) - - purge_processor(self) - caller.msg(format_code("Finished processing batch file.")) - - -class CmdStateJJ(_COMMAND_DEFAULT_CLASS): - """ - jj <command number> - - Jump to specific command number - """ - - key = "jj" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - arg = self.args - if arg and arg.isdigit(): - number = int(self.args) - 1 - else: - caller.msg(format_code("You must give a number index.")) - return - ptr = caller.ndb.batch_stackptr - step = number - ptr - step_pointer(caller, step) - show_curr(caller) - - -class CmdStateJL(_COMMAND_DEFAULT_CLASS): - """ - jl <command number> - - Jump to specific command number and view its full source. - """ - - key = "jl" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - caller = self.caller - arg = self.args - if arg and arg.isdigit(): - number = int(self.args) - 1 - else: - caller.msg(format_code("You must give a number index.")) - return - ptr = caller.ndb.batch_stackptr - step = number - ptr - step_pointer(caller, step) - show_curr(caller, showall=True) - - -class CmdStateQQ(_COMMAND_DEFAULT_CLASS): - """ - qq - - Quit the batchprocessor. - """ - - key = "qq" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - purge_processor(self.caller) - self.caller.msg("Aborted interactive batch mode.") - - -class CmdStateHH(_COMMAND_DEFAULT_CLASS): - """Help command""" - - key = "hh" - help_category = "BatchProcess" - locks = "cmd:perm(batchcommands)" - - def func(self): - string = """ - Interactive batch processing commands: - - nn [steps] - next command (no processing) - nl [steps] - next & look - bb [steps] - back to previous command (no processing) - bl [steps] - back & look - jj <N> - jump to command nr N (no processing) - jl <N> - jump & look - pp - process currently shown command (no step) - ss [steps] - process & step - sl [steps] - process & step & look - ll - look at full definition of current command - rr - reload batch file (stay on current) - rrr - reload batch file (start from first) - hh - this help list - - cc - continue processing to end, then quit. - qq - quit (abort all remaining commands) - - abort - this is a safety command that always is available - regardless of what cmdsets gets added to us during - batch-command processing. It immediately shuts down - the processor and returns us to the default cmdset. - """ - self.caller.msg(string) - - -# ------------------------------------------------------------- -# -# Defining the cmdsets for the interactive batchprocessor -# mode (same for both processors) -# -# ------------------------------------------------------------- - - -class BatchSafeCmdSet(CmdSet): - """ - The base cmdset for the batch processor. - This sets a 'safe' abort command that will - always be available to get out of everything. - """ - - key = "Batch_default" - priority = 150 # override other cmdsets. - - def at_cmdset_creation(self): - """Init the cmdset""" - self.add(CmdStateAbort()) - - -class BatchInteractiveCmdSet(CmdSet): - """ - The cmdset for the interactive batch processor mode. - """ - - key = "Batch_interactive" - priority = 104 - - def at_cmdset_creation(self): - """init the cmdset""" - self.add(CmdStateAbort()) - self.add(CmdStateLL()) - self.add(CmdStatePP()) - self.add(CmdStateRR()) - self.add(CmdStateRRR()) - self.add(CmdStateNN()) - self.add(CmdStateNL()) - self.add(CmdStateBB()) - self.add(CmdStateBL()) - self.add(CmdStateSS()) - self.add(CmdStateSL()) - self.add(CmdStateCC()) - self.add(CmdStateJJ()) - self.add(CmdStateJL()) - self.add(CmdStateQQ()) - self.add(CmdStateHH()) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/building.html b/docs/0.9.5/_modules/evennia/commands/default/building.html deleted file mode 100644 index e033e9825a..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/building.html +++ /dev/null @@ -1,3939 +0,0 @@ - - - - - - - - evennia.commands.default.building — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.building

-"""
-Building and world design commands
-"""
-import re
-from django.conf import settings
-from django.db.models import Q, Min, Max
-from evennia.objects.models import ObjectDB
-from evennia.locks.lockhandler import LockException
-from evennia.commands.cmdhandler import get_and_merge_cmdsets
-from evennia.utils import create, utils, search, logger
-from evennia.utils.utils import (
-    inherits_from,
-    class_from_module,
-    get_all_typeclasses,
-    variable_from_module,
-    dbref,
-    interactive,
-    list_to_string,
-    display_len,
-)
-from evennia.utils.eveditor import EvEditor
-from evennia.utils.evmore import EvMore
-from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
-from evennia.utils.ansi import raw as ansi_raw
-from evennia.utils.inlinefuncs import raw as inlinefunc_raw
-
-COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-# limit symbol import for API
-__all__ = (
-    "ObjManipCommand",
-    "CmdSetObjAlias",
-    "CmdCopy",
-    "CmdCpAttr",
-    "CmdMvAttr",
-    "CmdCreate",
-    "CmdDesc",
-    "CmdDestroy",
-    "CmdDig",
-    "CmdTunnel",
-    "CmdLink",
-    "CmdUnLink",
-    "CmdSetHome",
-    "CmdListCmdSets",
-    "CmdName",
-    "CmdOpen",
-    "CmdSetAttribute",
-    "CmdTypeclass",
-    "CmdWipe",
-    "CmdLock",
-    "CmdExamine",
-    "CmdFind",
-    "CmdTeleport",
-    "CmdScript",
-    "CmdTag",
-    "CmdSpawn",
-)
-
-# used by set
-from ast import literal_eval as _LITERAL_EVAL
-
-LIST_APPEND_CHAR = "+"
-
-# used by find
-CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
-ROOM_TYPECLASS = settings.BASE_ROOM_TYPECLASS
-EXIT_TYPECLASS = settings.BASE_EXIT_TYPECLASS
-_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-
-_PROTOTYPE_PARENTS = None
-
-
-
[docs]class ObjManipCommand(COMMAND_DEFAULT_CLASS): - """ - This is a parent class for some of the defining objmanip commands - since they tend to have some more variables to define new objects. - - Each object definition can have several components. First is - always a name, followed by an optional alias list and finally an - some optional data, such as a typeclass or a location. A comma ',' - separates different objects. Like this: - - name1;alias;alias;alias:option, name2;alias;alias ... - - Spaces between all components are stripped. - - A second situation is attribute manipulation. Such commands - are simpler and offer combinations - - objname/attr/attr/attr, objname/attr, ... - - """ - - # OBS - this is just a parent - it's not intended to actually be - # included in a commandset on its own! - -
[docs] def parse(self): - """ - We need to expand the default parsing to get all - the cases, see the module doc. - """ - # get all the normal parsing done (switches etc) - super().parse() - - obj_defs = ([], []) # stores left- and right-hand side of '=' - obj_attrs = ([], []) # " - - for iside, arglist in enumerate((self.lhslist, self.rhslist)): - # lhslist/rhslist is already split by ',' at this point - for objdef in arglist: - aliases, option, attrs = [], None, [] - if ":" in objdef: - objdef, option = [part.strip() for part in objdef.rsplit(":", 1)] - if ";" in objdef: - objdef, aliases = [part.strip() for part in objdef.split(";", 1)] - aliases = [alias.strip() for alias in aliases.split(";") if alias.strip()] - if "/" in objdef: - objdef, attrs = [part.strip() for part in objdef.split("/", 1)] - attrs = [part.strip().lower() for part in attrs.split("/") if part.strip()] - # store data - obj_defs[iside].append({"name": objdef, "option": option, "aliases": aliases}) - obj_attrs[iside].append({"name": objdef, "attrs": attrs}) - - # store for future access - self.lhs_objs = obj_defs[0] - self.rhs_objs = obj_defs[1] - self.lhs_objattr = obj_attrs[0] - self.rhs_objattr = obj_attrs[1]
- - -
[docs]class CmdSetObjAlias(COMMAND_DEFAULT_CLASS): - """ - adding permanent aliases for object - - Usage: - alias <obj> [= [alias[,alias,alias,...]]] - alias <obj> = - alias/category <obj> = [alias[,alias,...]:<category> - - Switches: - category - requires ending input with :category, to store the - given aliases with the given category. - - Assigns aliases to an object so it can be referenced by more - than one name. Assign empty to remove all aliases from object. If - assigning a category, all aliases given will be using this category. - - Observe that this is not the same thing as personal aliases - created with the 'nick' command! Aliases set with alias are - changing the object in question, making those aliases usable - by everyone. - """ - - key = "alias" - aliases = "setobjalias" - switch_options = ("category",) - locks = "cmd:perm(setobjalias) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """Set the aliases.""" - - caller = self.caller - - if not self.lhs: - string = "Usage: alias <obj> [= [alias[,alias ...]]]" - self.caller.msg(string) - return - objname = self.lhs - - # Find the object to receive aliases - obj = caller.search(objname) - if not obj: - return - if self.rhs is None: - # no =, so we just list aliases on object. - aliases = obj.aliases.all(return_key_and_category=True) - if aliases: - caller.msg( - "Aliases for %s: %s" - % ( - obj.get_display_name(caller), - ", ".join( - "'%s'%s" - % (alias, "" if category is None else "[category:'%s']" % category) - for (alias, category) in aliases - ), - ) - ) - else: - caller.msg("No aliases exist for '%s'." % obj.get_display_name(caller)) - return - - if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.msg("You don't have permission to do that.") - return - - if not self.rhs: - # we have given an empty =, so delete aliases - old_aliases = obj.aliases.all() - if old_aliases: - caller.msg( - "Cleared aliases from %s: %s" - % (obj.get_display_name(caller), ", ".join(old_aliases)) - ) - obj.aliases.clear() - else: - caller.msg("No aliases to clear.") - return - - category = None - if "category" in self.switches: - if ":" in self.rhs: - rhs, category = self.rhs.rsplit(":", 1) - category = category.strip() - else: - caller.msg( - "If specifying the /category switch, the category must be given " - "as :category at the end." - ) - else: - rhs = self.rhs - - # merge the old and new aliases (if any) - old_aliases = obj.aliases.get(category=category, return_list=True) - new_aliases = [alias.strip().lower() for alias in rhs.split(",") if alias.strip()] - - # make the aliases only appear once - old_aliases.extend(new_aliases) - aliases = list(set(old_aliases)) - - # save back to object. - obj.aliases.add(aliases, category=category) - - # we need to trigger this here, since this will force - # (default) Exits to rebuild their Exit commands with the new - # aliases - obj.at_cmdset_get(force_init=True) - - # report all aliases on the object - caller.msg( - "Alias(es) for '%s' set to '%s'%s." - % ( - obj.get_display_name(caller), - str(obj.aliases), - " (category: '%s')" % category if category else "", - ) - )
- - -
[docs]class CmdCopy(ObjManipCommand): - """ - copy an object and its properties - - Usage: - copy <original obj> [= <new_name>][;alias;alias..] - [:<new_location>] [,<new_name2> ...] - - Create one or more copies of an object. If you don't supply any targets, - one exact copy of the original object will be created with the name *_copy. - """ - - key = "copy" - locks = "cmd:perm(copy) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """Uses ObjManipCommand.parse()""" - - caller = self.caller - args = self.args - if not args: - caller.msg( - "Usage: copy <obj> [=<new_name>[;alias;alias..]]" - "[:<new_location>] [, <new_name2>...]" - ) - return - - if not self.rhs: - # this has no target =, so an identical new object is created. - from_obj_name = self.args - from_obj = caller.search(from_obj_name) - if not from_obj: - return - to_obj_name = "%s_copy" % from_obj_name - to_obj_aliases = ["%s_copy" % alias for alias in from_obj.aliases.all()] - copiedobj = ObjectDB.objects.copy_object( - from_obj, new_key=to_obj_name, new_aliases=to_obj_aliases - ) - if copiedobj: - string = "Identical copy of %s, named '%s' was created." % ( - from_obj_name, - to_obj_name, - ) - else: - string = "There was an error copying %s." - else: - # we have specified =. This might mean many object targets - from_obj_name = self.lhs_objs[0]["name"] - from_obj = caller.search(from_obj_name) - if not from_obj: - return - for objdef in self.rhs_objs: - # loop through all possible copy-to targets - to_obj_name = objdef["name"] - to_obj_aliases = objdef["aliases"] - to_obj_location = objdef["option"] - if to_obj_location: - to_obj_location = caller.search(to_obj_location, global_search=True) - if not to_obj_location: - return - - copiedobj = ObjectDB.objects.copy_object( - from_obj, - new_key=to_obj_name, - new_location=to_obj_location, - new_aliases=to_obj_aliases, - ) - if copiedobj: - string = "Copied %s to '%s' (aliases: %s)." % ( - from_obj_name, - to_obj_name, - to_obj_aliases, - ) - else: - string = "There was an error copying %s to '%s'." % (from_obj_name, to_obj_name) - # we are done, echo to user - caller.msg(string)
- - -
[docs]class CmdCpAttr(ObjManipCommand): - """ - copy attributes between objects - - Usage: - cpattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...] - cpattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,...] - cpattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...] - cpattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,...] - - Switches: - move - delete the attribute from the source object after copying. - - Example: - cpattr coolness = Anna/chillout, Anna/nicety, Tom/nicety - -> - copies the coolness attribute (defined on yourself), to attributes - on Anna and Tom. - - Copy the attribute one object to one or more attributes on another object. - If you don't supply a source object, yourself is used. - """ - - key = "cpattr" - switch_options = ("move",) - locks = "cmd:perm(cpattr) or perm(Builder)" - help_category = "Building" - -
[docs] def check_from_attr(self, obj, attr, clear=False): - """ - Hook for overriding on subclassed commands. Checks to make sure a - caller can copy the attr from the object in question. If not, return a - false value and the command will abort. An error message should be - provided by this function. - - If clear is True, user is attempting to move the attribute. - """ - return True
- -
[docs] def check_to_attr(self, obj, attr): - """ - Hook for overriding on subclassed commands. Checks to make sure a - caller can write to the specified attribute on the specified object. - If not, return a false value and the attribute will be skipped. An - error message should be provided by this function. - """ - return True
- -
[docs] def check_has_attr(self, obj, attr): - """ - Hook for overriding on subclassed commands. Do any preprocessing - required and verify an object has an attribute. - """ - if not obj.attributes.has(attr): - self.caller.msg("%s doesn't have an attribute %s." % (obj.name, attr)) - return False - return True
- -
[docs] def get_attr(self, obj, attr): - """ - Hook for overriding on subclassed commands. Do any preprocessing - required and get the attribute from the object. - """ - return obj.attributes.get(attr)
- -
[docs] def func(self): - """ - Do the copying. - """ - caller = self.caller - - if not self.rhs: - string = """Usage: - cpattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...] - cpattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,...] - cpattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...] - cpattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,...]""" - caller.msg(string) - return - - lhs_objattr = self.lhs_objattr - to_objs = self.rhs_objattr - from_obj_name = lhs_objattr[0]["name"] - from_obj_attrs = lhs_objattr[0]["attrs"] - - if not from_obj_attrs: - # this means the from_obj_name is actually an attribute - # name on self. - from_obj_attrs = [from_obj_name] - from_obj = self.caller - else: - from_obj = caller.search(from_obj_name) - if not from_obj or not to_objs: - caller.msg("You have to supply both source object and target(s).") - return - # copy to all to_obj:ects - if "move" in self.switches: - clear = True - else: - clear = False - if not self.check_from_attr(from_obj, from_obj_attrs[0], clear=clear): - return - - for attr in from_obj_attrs: - if not self.check_has_attr(from_obj, attr): - return - - if (len(from_obj_attrs) != len(set(from_obj_attrs))) and clear: - self.caller.msg("|RCannot have duplicate source names when moving!") - return - - result = [] - - for to_obj in to_objs: - to_obj_name = to_obj["name"] - to_obj_attrs = to_obj["attrs"] - to_obj = caller.search(to_obj_name) - if not to_obj: - result.append("\nCould not find object '%s'" % to_obj_name) - continue - for inum, from_attr in enumerate(from_obj_attrs): - try: - to_attr = to_obj_attrs[inum] - except IndexError: - # if there are too few attributes given - # on the to_obj, we copy the original name instead. - to_attr = from_attr - if not self.check_to_attr(to_obj, to_attr): - continue - value = self.get_attr(from_obj, from_attr) - to_obj.attributes.add(to_attr, value) - if clear and not (from_obj == to_obj and from_attr == to_attr): - from_obj.attributes.remove(from_attr) - result.append( - "\nMoved %s.%s -> %s.%s. (value: %s)" - % (from_obj.name, from_attr, to_obj_name, to_attr, repr(value)) - ) - else: - result.append( - "\nCopied %s.%s -> %s.%s. (value: %s)" - % (from_obj.name, from_attr, to_obj_name, to_attr, repr(value)) - ) - caller.msg("".join(result))
- - -
[docs]class CmdMvAttr(ObjManipCommand): - """ - move attributes between objects - - Usage: - mvattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...] - mvattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,...] - mvattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...] - mvattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,...] - - Switches: - copy - Don't delete the original after moving. - - Move an attribute from one object to one or more attributes on another - object. If you don't supply a source object, yourself is used. - """ - - key = "mvattr" - switch_options = ("copy",) - locks = "cmd:perm(mvattr) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """ - Do the moving - """ - if not self.rhs: - string = """Usage: - mvattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...] - mvattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,...] - mvattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...] - mvattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,...]""" - self.caller.msg(string) - return - - # simply use cpattr for all the functionality - if "copy" in self.switches: - self.execute_cmd("cpattr %s" % self.args) - else: - self.execute_cmd("cpattr/move %s" % self.args)
- - -
[docs]class CmdCreate(ObjManipCommand): - """ - create new objects - - Usage: - create[/drop] <objname>[;alias;alias...][:typeclass], <objname>... - - switch: - drop - automatically drop the new object into your current - location (this is not echoed). This also sets the new - object's home to the current location rather than to you. - - Creates one or more new objects. If typeclass is given, the object - is created as a child of this typeclass. The typeclass script is - assumed to be located under types/ and any further - directory structure is given in Python notation. So if you have a - correct typeclass 'RedButton' defined in - types/examples/red_button.py, you could create a new - object of this type like this: - - create/drop button;red : examples.red_button.RedButton - - """ - - key = "create" - switch_options = ("drop",) - locks = "cmd:perm(create) or perm(Builder)" - help_category = "Building" - - # lockstring of newly created objects, for easy overloading. - # Will be formatted with the {id} of the creating object. - new_obj_lockstring = "control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)" - -
[docs] def func(self): - """ - Creates the object. - """ - - caller = self.caller - - if not self.args: - string = "Usage: create[/drop] <newname>[;alias;alias...] [:typeclass.path]" - caller.msg(string) - return - - # create the objects - for objdef in self.lhs_objs: - string = "" - name = objdef["name"] - aliases = objdef["aliases"] - typeclass = objdef["option"] - - # create object (if not a valid typeclass, the default - # object typeclass will automatically be used) - lockstring = self.new_obj_lockstring.format(id=caller.id) - obj = create.create_object( - typeclass, - name, - caller, - home=caller, - aliases=aliases, - locks=lockstring, - report_to=caller, - ) - if not obj: - continue - if aliases: - string = "You create a new %s: %s (aliases: %s)." - string = string % (obj.typename, obj.name, ", ".join(aliases)) - else: - string = "You create a new %s: %s." - string = string % (obj.typename, obj.name) - # set a default desc - if not obj.db.desc: - obj.db.desc = "You see nothing special." - if "drop" in self.switches: - if caller.location: - obj.home = caller.location - obj.move_to(caller.location, quiet=True) - if string: - caller.msg(string)
- - -def _desc_load(caller): - return caller.db.evmenu_target.db.desc or "" - - -def _desc_save(caller, buf): - """ - Save line buffer to the desc prop. This should - return True if successful and also report its status to the user. - """ - caller.db.evmenu_target.db.desc = buf - caller.msg("Saved.") - return True - - -def _desc_quit(caller): - caller.attributes.remove("evmenu_target") - caller.msg("Exited editor.") - - -
[docs]class CmdDesc(COMMAND_DEFAULT_CLASS): - """ - describe an object or the current room. - - Usage: - desc [<obj> =] <description> - - Switches: - edit - Open up a line editor for more advanced editing. - - Sets the "desc" attribute on an object. If an object is not given, - describe the current room. - """ - - key = "desc" - aliases = "describe" - switch_options = ("edit",) - locks = "cmd:perm(desc) or perm(Builder)" - help_category = "Building" - -
[docs] def edit_handler(self): - if self.rhs: - self.msg("|rYou may specify a value, or use the edit switch, " "but not both.|n") - return - if self.args: - obj = self.caller.search(self.args) - else: - obj = self.caller.location or self.msg("|rYou can't describe oblivion.|n") - if not obj: - return - - if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")): - self.caller.msg("You don't have permission to edit the description of %s." % obj.key) - - self.caller.db.evmenu_target = obj - # launch the editor - EvEditor( - self.caller, - loadfunc=_desc_load, - savefunc=_desc_save, - quitfunc=_desc_quit, - key="desc", - persistent=True, - ) - return
- -
[docs] def func(self): - """Define command""" - - caller = self.caller - if not self.args and "edit" not in self.switches: - caller.msg("Usage: desc [<obj> =] <description>") - return - - if "edit" in self.switches: - self.edit_handler() - return - - if "=" in self.args: - # We have an = - obj = caller.search(self.lhs) - if not obj: - return - desc = self.rhs or "" - else: - obj = caller.location or self.msg("|rYou can't describe oblivion.|n") - if not obj: - return - desc = self.args - if obj.access(self.caller, "control") or obj.access(self.caller, "edit"): - obj.db.desc = desc - caller.msg("The description was set on %s." % obj.get_display_name(caller)) - else: - caller.msg("You don't have permission to edit the description of %s." % obj.key)
- - -
[docs]class CmdDestroy(COMMAND_DEFAULT_CLASS): - """ - permanently delete objects - - Usage: - destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...] - - Switches: - override - The destroy command will usually avoid accidentally - destroying account objects. This switch overrides this safety. - force - destroy without confirmation. - Examples: - destroy house, roof, door, 44-78 - destroy 5-10, flower, 45 - destroy/force north - - Destroys one or many objects. If dbrefs are used, a range to delete can be - given, e.g. 4-10. Also the end points will be deleted. This command - displays a confirmation before destroying, to make sure of your choice. - You can specify the /force switch to bypass this confirmation. - """ - - key = "destroy" - aliases = ["delete", "del"] - switch_options = ("override", "force") - locks = "cmd:perm(destroy) or perm(Builder)" - help_category = "Building" - - confirm = True # set to False to always bypass confirmation - default_confirm = "yes" # what to assume if just pressing enter (yes/no) - -
[docs] def func(self): - """Implements the command.""" - - caller = self.caller - delete = True - - if not self.args or not self.lhslist: - caller.msg("Usage: destroy[/switches] [obj, obj2, obj3, [dbref-dbref],...]") - delete = False - - def delobj(obj): - # helper function for deleting a single object - string = "" - if not obj.pk: - string = "\nObject %s was already deleted." % obj.db_key - else: - objname = obj.name - if not (obj.access(caller, "control") or obj.access(caller, "delete")): - return "\nYou don't have permission to delete %s." % objname - if obj.account and "override" not in self.switches: - return ( - "\nObject %s is controlled by an active account. Use /override to delete anyway." - % objname - ) - if obj.dbid == int(settings.DEFAULT_HOME.lstrip("#")): - return ( - "\nYou are trying to delete |c%s|n, which is set as DEFAULT_HOME. " - "Re-point settings.DEFAULT_HOME to another " - "object before continuing." % objname - ) - - had_exits = hasattr(obj, "exits") and obj.exits - had_objs = hasattr(obj, "contents") and any( - obj - for obj in obj.contents - if not (hasattr(obj, "exits") and obj not in obj.exits) - ) - # do the deletion - okay = obj.delete() - if not okay: - string += ( - "\nERROR: %s not deleted, probably because delete() returned False." - % objname - ) - else: - string += "\n%s was destroyed." % objname - if had_exits: - string += " Exits to and from %s were destroyed as well." % objname - if had_objs: - string += " Objects inside %s were moved to their homes." % objname - return string - - objs = [] - for objname in self.lhslist: - if not delete: - continue - - if "-" in objname: - # might be a range of dbrefs - dmin, dmax = [utils.dbref(part, reqhash=False) for part in objname.split("-", 1)] - if dmin and dmax: - for dbref in range(int(dmin), int(dmax + 1)): - obj = caller.search("#" + str(dbref)) - if obj: - objs.append(obj) - continue - else: - obj = caller.search(objname) - else: - obj = caller.search(objname) - - if obj is None: - self.caller.msg( - " (Objects to destroy must either be local or specified with a unique #dbref.)" - ) - elif obj not in objs: - objs.append(obj) - - if objs and ("force" not in self.switches and type(self).confirm): - confirm = "Are you sure you want to destroy " - if len(objs) == 1: - confirm += objs[0].get_display_name(caller) - elif len(objs) < 5: - confirm += ", ".join([obj.get_display_name(caller) for obj in objs]) - else: - confirm += ", ".join(["#{}".format(obj.id) for obj in objs]) - confirm += " [yes]/no?" if self.default_confirm == "yes" else " yes/[no]" - answer = "" - answer = yield (confirm) - answer = self.default_confirm if answer == "" else answer - - if answer and answer not in ("yes", "y", "no", "n"): - caller.msg( - "Canceled: Either accept the default by pressing return or specify yes/no." - ) - delete = False - elif answer.strip().lower() in ("n", "no"): - caller.msg("Canceled: No object was destroyed.") - delete = False - - if delete: - results = [] - for obj in objs: - results.append(delobj(obj)) - - if results: - caller.msg("".join(results).strip())
- - -
[docs]class CmdDig(ObjManipCommand): - """ - build new rooms and connect them to the current location - - Usage: - dig[/switches] <roomname>[;alias;alias...][:typeclass] - [= <exit_to_there>[;alias][:typeclass]] - [, <exit_to_here>[;alias][:typeclass]] - - Switches: - tel or teleport - move yourself to the new room - - Examples: - dig kitchen = north;n, south;s - dig house:myrooms.MyHouseTypeclass - dig sheer cliff;cliff;sheer = climb up, climb down - - This command is a convenient way to build rooms quickly; it creates the - new room and you can optionally set up exits back and forth between your - current room and the new one. You can add as many aliases as you - like to the name of the room and the exits in question; an example - would be 'north;no;n'. - """ - - key = "dig" - switch_options = ("teleport",) - locks = "cmd:perm(dig) or perm(Builder)" - help_category = "Building" - - # lockstring of newly created rooms, for easy overloading. - # Will be formatted with the {id} of the creating object. - new_room_lockstring = ( - "control:id({id}) or perm(Admin); " - "delete:id({id}) or perm(Admin); " - "edit:id({id}) or perm(Admin)" - ) - -
[docs] def func(self): - """Do the digging. Inherits variables from ObjManipCommand.parse()""" - - caller = self.caller - - if not self.lhs: - string = "Usage: dig[/teleport] <roomname>[;alias;alias...]" "[:parent] [= <exit_there>" - string += "[;alias;alias..][:parent]] " - string += "[, <exit_back_here>[;alias;alias..][:parent]]" - caller.msg(string) - return - - room = self.lhs_objs[0] - - if not room["name"]: - caller.msg("You must supply a new room name.") - return - location = caller.location - - # Create the new room - typeclass = room["option"] - if not typeclass: - typeclass = settings.BASE_ROOM_TYPECLASS - - # create room - new_room = create.create_object( - typeclass, room["name"], aliases=room["aliases"], report_to=caller - ) - lockstring = self.new_room_lockstring.format(id=caller.id) - new_room.locks.add(lockstring) - alias_string = "" - if new_room.aliases.all(): - alias_string = " (%s)" % ", ".join(new_room.aliases.all()) - room_string = "Created room %s(%s)%s of type %s." % ( - new_room, - new_room.dbref, - alias_string, - typeclass, - ) - - # create exit to room - - exit_to_string = "" - exit_back_string = "" - - if self.rhs_objs: - to_exit = self.rhs_objs[0] - if not to_exit["name"]: - exit_to_string = "\nNo exit created to new room." - elif not location: - exit_to_string = "\nYou cannot create an exit from a None-location." - else: - # Build the exit to the new room from the current one - typeclass = to_exit["option"] - if not typeclass: - typeclass = settings.BASE_EXIT_TYPECLASS - - new_to_exit = create.create_object( - typeclass, - to_exit["name"], - location, - aliases=to_exit["aliases"], - locks=lockstring, - destination=new_room, - report_to=caller, - ) - alias_string = "" - if new_to_exit.aliases.all(): - alias_string = " (%s)" % ", ".join(new_to_exit.aliases.all()) - exit_to_string = "\nCreated Exit from %s to %s: %s(%s)%s." - exit_to_string = exit_to_string % ( - location.name, - new_room.name, - new_to_exit, - new_to_exit.dbref, - alias_string, - ) - - # Create exit back from new room - - if len(self.rhs_objs) > 1: - # Building the exit back to the current room - back_exit = self.rhs_objs[1] - if not back_exit["name"]: - exit_back_string = "\nNo back exit created." - elif not location: - exit_back_string = "\nYou cannot create an exit back to a None-location." - else: - typeclass = back_exit["option"] - if not typeclass: - typeclass = settings.BASE_EXIT_TYPECLASS - new_back_exit = create.create_object( - typeclass, - back_exit["name"], - new_room, - aliases=back_exit["aliases"], - locks=lockstring, - destination=location, - report_to=caller, - ) - alias_string = "" - if new_back_exit.aliases.all(): - alias_string = " (%s)" % ", ".join(new_back_exit.aliases.all()) - exit_back_string = "\nCreated Exit back from %s to %s: %s(%s)%s." - exit_back_string = exit_back_string % ( - new_room.name, - location.name, - new_back_exit, - new_back_exit.dbref, - alias_string, - ) - caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string)) - if new_room and "teleport" in self.switches: - caller.move_to(new_room)
- - -
[docs]class CmdTunnel(COMMAND_DEFAULT_CLASS): - """ - create new rooms in cardinal directions only - - Usage: - tunnel[/switch] <direction>[:typeclass] [= <roomname>[;alias;alias;...][:typeclass]] - - Switches: - oneway - do not create an exit back to the current location - tel - teleport to the newly created room - - Example: - tunnel n - tunnel n = house;mike's place;green building - - This is a simple way to build using pre-defined directions: - |wn,ne,e,se,s,sw,w,nw|n (north, northeast etc) - |wu,d|n (up and down) - |wi,o|n (in and out) - The full names (north, in, southwest, etc) will always be put as - main name for the exit, using the abbreviation as an alias (so an - exit will always be able to be used with both "north" as well as - "n" for example). Opposite directions will automatically be - created back from the new room unless the /oneway switch is given. - For more flexibility and power in creating rooms, use dig. - """ - - key = "tunnel" - aliases = ["tun"] - switch_options = ("oneway", "tel") - locks = "cmd: perm(tunnel) or perm(Builder)" - help_category = "Building" - - # store the direction, full name and its opposite - directions = { - "n": ("north", "s"), - "ne": ("northeast", "sw"), - "e": ("east", "w"), - "se": ("southeast", "nw"), - "s": ("south", "n"), - "sw": ("southwest", "ne"), - "w": ("west", "e"), - "nw": ("northwest", "se"), - "u": ("up", "d"), - "d": ("down", "u"), - "i": ("in", "o"), - "o": ("out", "i"), - } - -
[docs] def func(self): - """Implements the tunnel command""" - - if not self.args or not self.lhs: - string = ( - "Usage: tunnel[/switch] <direction>[:typeclass] [= <roomname>" - "[;alias;alias;...][:typeclass]]" - ) - self.caller.msg(string) - return - - # If we get a typeclass, we need to get just the exitname - exitshort = self.lhs.split(":")[0] - - if exitshort not in self.directions: - string = "tunnel can only understand the following directions: %s." % ",".join( - sorted(self.directions.keys()) - ) - string += "\n(use dig for more freedom)" - self.caller.msg(string) - return - - # retrieve all input and parse it - exitname, backshort = self.directions[exitshort] - backname = self.directions[backshort][0] - - # if we recieved a typeclass for the exit, add it to the alias(short name) - if ":" in self.lhs: - # limit to only the first : character - exit_typeclass = ":" + self.lhs.split(":", 1)[-1] - # exitshort and backshort are the last part of the exit strings, - # so we add our typeclass argument after - exitshort += exit_typeclass - backshort += exit_typeclass - - roomname = "Some place" - if self.rhs: - roomname = self.rhs # this may include aliases; that's fine. - - telswitch = "" - if "tel" in self.switches: - telswitch = "/teleport" - backstring = "" - if "oneway" not in self.switches: - backstring = ", %s;%s" % (backname, backshort) - - # build the string we will use to call dig - digstring = "dig%s %s = %s;%s%s" % (telswitch, roomname, exitname, exitshort, backstring) - self.execute_cmd(digstring)
- - - - - - - - -
[docs]class CmdSetHome(CmdLink): - """ - set an object's home location - - Usage: - sethome <obj> [= <home_location>] - sethom <obj> - - The "home" location is a "safety" location for objects; they - will be moved there if their current location ceases to exist. All - objects should always have a home location for this reason. - It is also a convenient target of the "home" command. - - If no location is given, just view the object's home location. - """ - - key = "sethome" - locks = "cmd:perm(sethome) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """implement the command""" - if not self.args: - string = "Usage: sethome <obj> [= <home_location>]" - self.caller.msg(string) - return - - obj = self.caller.search(self.lhs, global_search=True) - if not obj: - return - if not self.rhs: - # just view - home = obj.home - if not home: - string = "This object has no home location set!" - else: - string = "%s's current home is %s(%s)." % (obj, home, home.dbref) - else: - # set a home location - new_home = self.caller.search(self.rhs, global_search=True) - if not new_home: - return - old_home = obj.home - obj.home = new_home - if old_home: - string = "Home location of %s was changed from %s(%s) to %s(%s)." % ( - obj, - old_home, - old_home.dbref, - new_home, - new_home.dbref, - ) - else: - string = "Home location of %s was set to %s(%s)." % (obj, new_home, new_home.dbref) - self.caller.msg(string)
- - -
[docs]class CmdListCmdSets(COMMAND_DEFAULT_CLASS): - """ - list command sets defined on an object - - Usage: - cmdsets <obj> - - This displays all cmdsets assigned - to a user. Defaults to yourself. - """ - - key = "cmdsets" - aliases = "listcmsets" - locks = "cmd:perm(listcmdsets) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """list the cmdsets""" - - caller = self.caller - if self.arglist: - obj = caller.search(self.arglist[0]) - if not obj: - return - else: - obj = caller - string = "%s" % obj.cmdset - caller.msg(string)
- - -
[docs]class CmdName(ObjManipCommand): - """ - change the name and/or aliases of an object - - Usage: - name <obj> = <newname>;alias1;alias2 - - Rename an object to something new. Use *obj to - rename an account. - - """ - - key = "name" - aliases = ["rename"] - locks = "cmd:perm(rename) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """change the name""" - - caller = self.caller - if not self.args: - caller.msg("Usage: name <obj> = <newname>[;alias;alias;...]") - return - - obj = None - if self.lhs_objs: - objname = self.lhs_objs[0]["name"] - if objname.startswith("*"): - # account mode - obj = caller.account.search(objname.lstrip("*")) - if obj: - if self.rhs_objs[0]["aliases"]: - caller.msg("Accounts can't have aliases.") - return - newname = self.rhs - if not newname: - caller.msg("No name defined!") - return - if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.msg("You don't have right to edit this account %s." % obj) - return - obj.username = newname - obj.save() - caller.msg("Account's name changed to '%s'." % newname) - return - # object search, also with * - obj = caller.search(objname) - if not obj: - return - if self.rhs_objs: - newname = self.rhs_objs[0]["name"] - aliases = self.rhs_objs[0]["aliases"] - else: - newname = self.rhs - aliases = None - if not newname and not aliases: - caller.msg("No names or aliases defined!") - return - if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.msg("You don't have the right to edit %s." % obj) - return - # change the name and set aliases: - if newname: - obj.name = newname - astring = "" - if aliases: - [obj.aliases.add(alias) for alias in aliases] - astring = " (%s)" % (", ".join(aliases)) - # fix for exits - we need their exit-command to change name too - if obj.destination: - obj.flush_from_cache(force=True) - caller.msg("Object's name changed to '%s'%s." % (newname, astring))
- - -
[docs]class CmdOpen(ObjManipCommand): - """ - open a new exit from the current room - - Usage: - open <new exit>[;alias;alias..][:typeclass] [,<return exit>[;alias;..][:typeclass]]] = <destination> - - Handles the creation of exits. If a destination is given, the exit - will point there. The <return exit> argument sets up an exit at the - destination leading back to the current room. Destination name - can be given both as a #dbref and a name, if that name is globally - unique. - - """ - - key = "open" - locks = "cmd:perm(open) or perm(Builder)" - help_category = "Building" - - new_obj_lockstring = "control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)" - # a custom member method to chug out exits and do checks -
[docs] def create_exit(self, exit_name, location, destination, exit_aliases=None, typeclass=None): - """ - Helper function to avoid code duplication. - At this point we know destination is a valid location - - """ - caller = self.caller - string = "" - # check if this exit object already exists at the location. - # we need to ignore errors (so no automatic feedback)since we - # have to know the result of the search to decide what to do. - exit_obj = caller.search(exit_name, location=location, quiet=True, exact=True) - if len(exit_obj) > 1: - # give error message and return - caller.search(exit_name, location=location, exact=True) - return None - if exit_obj: - exit_obj = exit_obj[0] - if not exit_obj.destination: - # we are trying to link a non-exit - string = "'%s' already exists and is not an exit!\nIf you want to convert it " - string += ( - "to an exit, you must assign an object to the 'destination' property first." - ) - caller.msg(string % exit_name) - return None - # we are re-linking an old exit. - old_destination = exit_obj.destination - if old_destination: - string = "Exit %s already exists." % exit_name - if old_destination.id != destination.id: - # reroute the old exit. - exit_obj.destination = destination - if exit_aliases: - [exit_obj.aliases.add(alias) for alias in exit_aliases] - string += " Rerouted its old destination '%s' to '%s' and changed aliases." % ( - old_destination.name, - destination.name, - ) - else: - string += " It already points to the correct place." - - else: - # exit does not exist before. Create a new one. - lockstring = self.new_obj_lockstring.format(id=caller.id) - if not typeclass: - typeclass = settings.BASE_EXIT_TYPECLASS - exit_obj = create.create_object( - typeclass, - key=exit_name, - location=location, - aliases=exit_aliases, - locks=lockstring, - report_to=caller, - ) - if exit_obj: - # storing a destination is what makes it an exit! - exit_obj.destination = destination - string = ( - "" - if not exit_aliases - else " (aliases: %s)" % (", ".join([str(e) for e in exit_aliases])) - ) - string = "Created new Exit '%s' from %s to %s%s." % ( - exit_name, - location.name, - destination.name, - string, - ) - else: - string = "Error: Exit '%s' not created." % exit_name - # emit results - caller.msg(string) - return exit_obj
- -
[docs] def func(self): - """ - This is where the processing starts. - Uses the ObjManipCommand.parser() for pre-processing - as well as the self.create_exit() method. - """ - caller = self.caller - - if not self.args or not self.rhs: - string = "Usage: open <new exit>[;alias...][:typeclass][,<return exit>[;alias..][:typeclass]]] " - string += "= <destination>" - caller.msg(string) - return - - # We must have a location to open an exit - location = caller.location - if not location: - caller.msg("You cannot create an exit from a None-location.") - return - - # obtain needed info from cmdline - - exit_name = self.lhs_objs[0]["name"] - exit_aliases = self.lhs_objs[0]["aliases"] - exit_typeclass = self.lhs_objs[0]["option"] - dest_name = self.rhs - - # first, check so the destination exists. - destination = caller.search(dest_name, global_search=True) - if not destination: - return - - # Create exit - ok = self.create_exit(exit_name, location, destination, exit_aliases, exit_typeclass) - if not ok: - # an error; the exit was not created, so we quit. - return - # Create back exit, if any - if len(self.lhs_objs) > 1: - back_exit_name = self.lhs_objs[1]["name"] - back_exit_aliases = self.lhs_objs[1]["aliases"] - back_exit_typeclass = self.lhs_objs[1]["option"] - self.create_exit( - back_exit_name, destination, location, back_exit_aliases, back_exit_typeclass - )
- - -def _convert_from_string(cmd, strobj): - """ - Converts a single object in *string form* to its equivalent python - type. - - Python earlier than 2.6: - Handles floats, ints, and limited nested lists and dicts - (can't handle lists in a dict, for example, this is mainly due to - the complexity of parsing this rather than any technical difficulty - - if there is a need for set-ing such complex structures on the - command line we might consider adding it). - Python 2.6 and later: - Supports all Python structures through literal_eval as long as they - are valid Python syntax. If they are not (such as [test, test2], ie - without the quotes around the strings), the entire structure will - be converted to a string and a warning will be given. - - We need to convert like this since all data being sent over the - telnet connection by the Account is text - but we will want to - store it as the "real" python type so we can do convenient - comparisons later (e.g. obj.db.value = 2, if value is stored as a - string this will always fail). - """ - - # Use literal_eval to parse python structure exactly. - try: - return _LITERAL_EVAL(strobj) - except (SyntaxError, ValueError): - # treat as string - strobj = utils.to_str(strobj) - string = ( - '|RNote: name "|r%s|R" was converted to a string. ' - "Make sure this is acceptable." % strobj - ) - cmd.caller.msg(string) - return strobj - except Exception as err: - string = "|RUnknown error in evaluating Attribute: {}".format(err) - return string - - -
[docs]class CmdSetAttribute(ObjManipCommand): - """ - set attribute on an object or account - - Usage: - set <obj>/<attr> = <value> - set <obj>/<attr> = - set <obj>/<attr> - set *<account>/<attr> = <value> - - Switch: - edit: Open the line editor (string values only) - script: If we're trying to set an attribute on a script - channel: If we're trying to set an attribute on a channel - account: If we're trying to set an attribute on an account - room: Setting an attribute on a room (global search) - exit: Setting an attribute on an exit (global search) - char: Setting an attribute on a character (global search) - character: Alias for char, as above. - - Sets attributes on objects. The second example form above clears a - previously set attribute while the third form inspects the current value of - the attribute (if any). The last one (with the star) is a shortcut for - operating on a player Account rather than an Object. - - The most common data to save with this command are strings and - numbers. You can however also set Python primitives such as lists, - dictionaries and tuples on objects (this might be important for - the functionality of certain custom objects). This is indicated - by you starting your value with one of |c'|n, |c"|n, |c(|n, |c[|n - or |c{ |n. - - Once you have stored a Python primitive as noted above, you can include - |c[<key>]|n in <attr> to reference nested values in e.g. a list or dict. - - Remember that if you use Python primitives like this, you must - write proper Python syntax too - notably you must include quotes - around your strings or you will get an error. - - """ - - key = "set" - locks = "cmd:perm(set) or perm(Builder)" - help_category = "Building" - nested_re = re.compile(r"\[.*?\]") - not_found = object() - -
[docs] def check_obj(self, obj): - """ - This may be overridden by subclasses in case restrictions need to be - placed on whether certain objects can have attributes set by certain - accounts. - - This function is expected to display its own error message. - - Returning False will abort the command. - """ - return True
- -
[docs] def check_attr(self, obj, attr_name): - """ - This may be overridden by subclasses in case restrictions need to be - placed on what attributes can be set by who beyond the normal lock. - - This functions is expected to display its own error message. It is - run once for every attribute that is checked, blocking only those - attributes which are not permitted and letting the others through. - """ - return attr_name
- -
[docs] def split_nested_attr(self, attr): - """ - Yields tuples of (possible attr name, nested keys on that attr). - For performance, this is biased to the deepest match, but allows compatability - with older attrs that might have been named with `[]`'s. - - > list(split_nested_attr("nested['asdf'][0]")) - [ - ('nested', ['asdf', 0]), - ("nested['asdf']", [0]), - ("nested['asdf'][0]", []), - ] - """ - quotes = "\"'" - - def clean_key(val): - val = val.strip("[]") - if val[0] in quotes: - return val.strip(quotes) - if val[0] == LIST_APPEND_CHAR: - # List insert/append syntax - return val - try: - return int(val) - except ValueError: - return val - - parts = self.nested_re.findall(attr) - - base_attr = "" - if parts: - base_attr = attr[: attr.find(parts[0])] - for index, part in enumerate(parts): - yield (base_attr, [clean_key(p) for p in parts[index:]]) - base_attr += part - yield (attr, [])
- -
[docs] def do_nested_lookup(self, value, *keys): - result = value - for key in keys: - try: - result = result.__getitem__(key) - except (IndexError, KeyError, TypeError): - return self.not_found - return result
- -
[docs] def view_attr(self, obj, attr): - """ - Look up the value of an attribute and return a string displaying it. - """ - nested = False - for key, nested_keys in self.split_nested_attr(attr): - nested = True - if obj.attributes.has(key): - val = obj.attributes.get(key) - val = self.do_nested_lookup(val, *nested_keys) - if val is not self.not_found: - return "\nAttribute %s/%s = %s" % (obj.name, attr, val) - error = "\n%s has no attribute '%s'." % (obj.name, attr) - if nested: - error += " (Nested lookups attempted)" - return error
- -
[docs] def rm_attr(self, obj, attr): - """ - Remove an attribute from the object, or a nested data structure, and report back. - """ - nested = False - for key, nested_keys in self.split_nested_attr(attr): - nested = True - if obj.attributes.has(key): - if nested_keys: - del_key = nested_keys[-1] - val = obj.attributes.get(key) - deep = self.do_nested_lookup(val, *nested_keys[:-1]) - if deep is not self.not_found: - try: - del deep[del_key] - except (IndexError, KeyError, TypeError): - continue - return "\nDeleted attribute '%s' (= nested) from %s." % (attr, obj.name) - else: - exists = obj.attributes.has(key) - obj.attributes.remove(attr) - return "\nDeleted attribute '%s' (= %s) from %s." % (attr, exists, obj.name) - error = "\n%s has no attribute '%s'." % (obj.name, attr) - if nested: - error += " (Nested lookups attempted)" - return error
- -
[docs] def set_attr(self, obj, attr, value): - done = False - for key, nested_keys in self.split_nested_attr(attr): - if obj.attributes.has(key) and nested_keys: - acc_key = nested_keys[-1] - lookup_value = obj.attributes.get(key) - deep = self.do_nested_lookup(lookup_value, *nested_keys[:-1]) - if deep is not self.not_found: - # To support appending and inserting to lists - # a key that starts with LIST_APPEND_CHAR will insert a new item at that - # location, and move the other elements down. - # Using LIST_APPEND_CHAR alone will append to the list - if isinstance(acc_key, str) and acc_key[0] == LIST_APPEND_CHAR: - try: - if len(acc_key) > 1: - where = int(acc_key[1:]) - deep.insert(where, value) - else: - deep.append(value) - except (ValueError, AttributeError): - pass - else: - value = lookup_value - attr = key - done = True - break - - # List magic failed, just use like a key/index - try: - deep[acc_key] = value - except TypeError as err: - # Tuples can't be modified - return "\n%s - %s" % (err, deep) - - value = lookup_value - attr = key - done = True - break - - verb = "Modified" if obj.attributes.has(attr) else "Created" - try: - if not done: - obj.attributes.add(attr, value) - return "\n%s attribute %s/%s = %s" % (verb, obj.name, attr, repr(value)) - except SyntaxError: - # this means literal_eval tried to parse a faulty string - return ( - "\n|RCritical Python syntax error in your value. Only " - "primitive Python structures are allowed.\nYou also " - "need to use correct Python syntax. Remember especially " - "to put quotes around all strings inside lists and " - "dicts.|n" - )
- -
[docs] def edit_handler(self, obj, attr): - """Activate the line editor""" - - def load(caller): - """Called for the editor to load the buffer""" - old_value = obj.attributes.get(attr) - if old_value is not None and not isinstance(old_value, str): - typ = type(old_value).__name__ - self.caller.msg( - "|RWARNING! Saving this buffer will overwrite the " - "current attribute (of type %s) with a string!|n" % typ - ) - return str(old_value) - return old_value - - def save(caller, buf): - """Called when editor saves its buffer.""" - obj.attributes.add(attr, buf) - caller.msg("Saved Attribute %s." % attr) - - # start the editor - EvEditor(self.caller, load, save, key="%s/%s" % (obj, attr))
- -
[docs] def search_for_obj(self, objname): - """ - Searches for an object matching objname. The object may be of different typeclasses. - Args: - objname: Name of the object we're looking for - - Returns: - A typeclassed object, or None if nothing is found. - """ - from evennia.utils.utils import variable_from_module - - _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1)) - caller = self.caller - if objname.startswith("*") or "account" in self.switches: - found_obj = caller.search_account(objname.lstrip("*")) - elif "script" in self.switches: - found_obj = _AT_SEARCH_RESULT(search.search_script(objname), caller) - elif "channel" in self.switches: - found_obj = _AT_SEARCH_RESULT(search.search_channel(objname), caller) - else: - global_search = True - if "char" in self.switches or "character" in self.switches: - typeclass = settings.BASE_CHARACTER_TYPECLASS - elif "room" in self.switches: - typeclass = settings.BASE_ROOM_TYPECLASS - elif "exit" in self.switches: - typeclass = settings.BASE_EXIT_TYPECLASS - else: - global_search = False - typeclass = None - found_obj = caller.search(objname, global_search=global_search, typeclass=typeclass) - return found_obj
- -
[docs] def func(self): - """Implement the set attribute - a limited form of py.""" - - caller = self.caller - if not self.args: - caller.msg("Usage: set obj/attr = value. Use empty value to clear.") - return - - # get values prepared by the parser - value = self.rhs - objname = self.lhs_objattr[0]["name"] - attrs = self.lhs_objattr[0]["attrs"] - - obj = self.search_for_obj(objname) - if not obj: - return - - if not self.check_obj(obj): - return - - result = [] - if "edit" in self.switches: - # edit in the line editor - if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")): - caller.msg("You don't have permission to edit %s." % obj.key) - return - - if len(attrs) > 1: - caller.msg("The Line editor can only be applied " "to one attribute at a time.") - return - if not attrs: - caller.msg("Use `set/edit <objname>/<attr>` to define the Attribute to edit.\nTo " - "edit the current room description, use `set/edit here/desc` (or " - "use the `desc` command).") - return - self.edit_handler(obj, attrs[0]) - return - if not value: - if self.rhs is None: - # no = means we inspect the attribute(s) - if not attrs: - attrs = [attr.key for attr in obj.attributes.all()] - for attr in attrs: - if not self.check_attr(obj, attr): - continue - result.append(self.view_attr(obj, attr)) - # we view it without parsing markup. - self.caller.msg("".join(result).strip(), options={"raw": True}) - return - else: - # deleting the attribute(s) - if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")): - caller.msg("You don't have permission to edit %s." % obj.key) - return - for attr in attrs: - if not self.check_attr(obj, attr): - continue - result.append(self.rm_attr(obj, attr)) - else: - # setting attribute(s). Make sure to convert to real Python type before saving. - if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")): - caller.msg("You don't have permission to edit %s." % obj.key) - return - for attr in attrs: - if not self.check_attr(obj, attr): - continue - value = _convert_from_string(self, value) - result.append(self.set_attr(obj, attr, value)) - # send feedback - caller.msg("".join(result).strip("\n"))
- - -
[docs]class CmdTypeclass(COMMAND_DEFAULT_CLASS): - """ - set or change an object's typeclass - - Usage: - typeclass[/switch] <object> [= typeclass.path] - typeclass/prototype <object> = prototype_key - - typeclass/list/show [typeclass.path] - swap - this is a shorthand for using /force/reset flags. - update - this is a shorthand for using the /force/reload flag. - - Switch: - show, examine - display the current typeclass of object (default) or, if - given a typeclass path, show the docstring of that typeclass. - update - *only* re-run at_object_creation on this object - meaning locks or other properties set later may remain. - reset - clean out *all* the attributes and properties on the - object - basically making this a new clean object. This will - also reset cmdsets. - force - change to the typeclass also if the object - already has a typeclass of the same name. - list - show available typeclasses. Only typeclasses in modules actually - imported or used from somewhere in the code will show up here - (those typeclasses are still available if you know the path) - prototype - clean and overwrite the object with the specified - prototype key - effectively making a whole new object. - - Example: - type button = examples.red_button.RedButton - type/prototype button=a red button - - If the typeclass_path is not given, the current object's typeclass is - assumed. - - View or set an object's typeclass. If setting, the creation hooks of the - new typeclass will be run on the object. If you have clashing properties on - the old class, use /reset. By default you are protected from changing to a - typeclass of the same name as the one you already have - use /force to - override this protection. - - The given typeclass must be identified by its location using python - dot-notation pointing to the correct module and class. If no typeclass is - given (or a wrong typeclass is given). Errors in the path or new typeclass - will lead to the old typeclass being kept. The location of the typeclass - module is searched from the default typeclass directory, as defined in the - server settings. - - """ - - key = "typeclass" - aliases = ["type", "parent", "swap", "update"] - switch_options = ("show", "examine", "update", "reset", "force", "list", "prototype") - locks = "cmd:perm(typeclass) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """Implements command""" - - caller = self.caller - - if "list" in self.switches: - tclasses = get_all_typeclasses() - contribs = [key for key in sorted(tclasses) if key.startswith("evennia.contrib")] or [ - "<None loaded>" - ] - core = [ - key for key in sorted(tclasses) if key.startswith("evennia") and key not in contribs - ] or ["<None loaded>"] - game = [key for key in sorted(tclasses) if not key.startswith("evennia")] or [ - "<None loaded>" - ] - string = ( - "|wCore typeclasses|n\n" - " {core}\n" - "|wLoaded Contrib typeclasses|n\n" - " {contrib}\n" - "|wGame-dir typeclasses|n\n" - " {game}" - ).format( - core="\n ".join(core), contrib="\n ".join(contribs), game="\n ".join(game) - ) - EvMore(caller, string, exit_on_lastpage=True) - return - - if not self.args: - caller.msg("Usage: %s <object> [= typeclass]" % self.cmdstring) - return - - if "show" in self.switches or "examine" in self.switches: - oquery = self.lhs - obj = caller.search(oquery, quiet=True) - if not obj: - # no object found to examine, see if it's a typeclass-path instead - tclasses = get_all_typeclasses() - matches = [ - (key, tclass) for key, tclass in tclasses.items() if key.endswith(oquery) - ] - nmatches = len(matches) - if nmatches > 1: - caller.msg( - "Multiple typeclasses found matching {}:\n {}".format( - oquery, "\n ".join(tup[0] for tup in matches) - ) - ) - elif not matches: - caller.msg("No object or typeclass path found to match '{}'".format(oquery)) - else: - # one match found - caller.msg( - "Docstring for typeclass '{}':\n{}".format(oquery, matches[0][1].__doc__) - ) - else: - # do the search again to get the error handling in case of multi-match - obj = caller.search(oquery) - if not obj: - return - caller.msg( - "{}'s current typeclass is '{}.{}'".format( - obj.name, obj.__class__.__module__, obj.__class__.__name__ - ) - ) - return - - # get object to swap on - obj = caller.search(self.lhs) - if not obj: - return - - if not hasattr(obj, "__dbclass__"): - string = "%s is not a typed object." % obj.name - caller.msg(string) - return - - new_typeclass = self.rhs or obj.path - - prototype = None - if "prototype" in self.switches: - key = self.rhs - prototype = protlib.search_prototype(key=key) - if len(prototype) > 1: - caller.msg( - "More than one match for {}:\n{}".format( - key, "\n".join(proto.get("prototype_key", "") for proto in prototype) - ) - ) - return - elif prototype: - # one match - prototype = prototype[0] - else: - # no match - caller.msg("No prototype '{}' was found.".format(key)) - return - new_typeclass = prototype["typeclass"] - self.switches.append("force") - - if "show" in self.switches or "examine" in self.switches: - string = "%s's current typeclass is %s." % (obj.name, obj.__class__) - caller.msg(string) - return - - if self.cmdstring == "swap": - self.switches.append("force") - self.switches.append("reset") - elif self.cmdstring == "update": - self.switches.append("force") - self.switches.append("update") - - if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.msg("You are not allowed to do that.") - return - - if not hasattr(obj, "swap_typeclass"): - caller.msg("This object cannot have a type at all!") - return - - is_same = obj.is_typeclass(new_typeclass, exact=True) - if is_same and "force" not in self.switches: - string = "%s already has the typeclass '%s'. Use /force to override." % ( - obj.name, - new_typeclass, - ) - else: - update = "update" in self.switches - reset = "reset" in self.switches - hooks = "at_object_creation" if update and not reset else "all" - old_typeclass_path = obj.typeclass_path - - # special prompt for the user in cases where we want - # to confirm changes. - if "prototype" in self.switches: - diff, _ = spawner.prototype_diff_from_object(prototype, obj) - txt = spawner.format_diff(diff) - prompt = ( - "Applying prototype '%s' over '%s' will cause the follow changes:\n%s\n" - % (prototype["key"], obj.name, txt) - ) - if not reset: - prompt += "\n|yWARNING:|n Use the /reset switch to apply the prototype over a blank state." - prompt += "\nAre you sure you want to apply these changes [yes]/no?" - answer = yield (prompt) - if answer and answer in ("no", "n"): - caller.msg("Canceled: No changes were applied.") - return - - # we let this raise exception if needed - obj.swap_typeclass( - new_typeclass, clean_attributes=reset, clean_cmdsets=reset, run_start_hooks=hooks - ) - - if "prototype" in self.switches: - modified = spawner.batch_update_objects_with_prototype(prototype, objects=[obj]) - prototype_success = modified > 0 - if not prototype_success: - caller.msg("Prototype %s failed to apply." % prototype["key"]) - - if is_same: - string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path) - else: - string = "%s changed typeclass from %s to %s.\n" % ( - obj.name, - old_typeclass_path, - obj.typeclass_path, - ) - if update: - string += "Only the at_object_creation hook was run (update mode)." - else: - string += "All object creation hooks were run." - if reset: - string += " All old attributes where deleted before the swap." - else: - string += " Attributes set before swap were not removed." - if "prototype" in self.switches and prototype_success: - string += ( - " Prototype '%s' was successfully applied over the object type." - % prototype["key"] - ) - - caller.msg(string)
- - -
[docs]class CmdWipe(ObjManipCommand): - """ - clear all attributes from an object - - Usage: - wipe <object>[/<attr>[/<attr>...]] - - Example: - wipe box - wipe box/colour - - Wipes all of an object's attributes, or optionally only those - matching the given attribute-wildcard search string. - """ - - key = "wipe" - locks = "cmd:perm(wipe) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """ - inp is the dict produced in ObjManipCommand.parse() - """ - - caller = self.caller - - if not self.args: - caller.msg("Usage: wipe <object>[/<attr>/<attr>...]") - return - - # get the attributes set by our custom parser - objname = self.lhs_objattr[0]["name"] - attrs = self.lhs_objattr[0]["attrs"] - - obj = caller.search(objname) - if not obj: - return - if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.msg("You are not allowed to do that.") - return - if not attrs: - # wipe everything - obj.attributes.clear() - string = "Wiped all attributes on %s." % obj.name - else: - for attrname in attrs: - obj.attributes.remove(attrname) - string = "Wiped attributes %s on %s." - string = string % (",".join(attrs), obj.name) - caller.msg(string)
- - -
[docs]class CmdLock(ObjManipCommand): - """ - assign a lock definition to an object - - Usage: - lock <object or *account>[ = <lockstring>] - or - lock[/switch] <object or *account>/<access_type> - - Switch: - del - delete given access type - view - view lock associated with given access type (default) - - If no lockstring is given, shows all locks on - object. - - Lockstring is of the form - access_type:[NOT] func1(args)[ AND|OR][ NOT] func2(args) ...] - Where func1, func2 ... valid lockfuncs with or without arguments. - Separator expressions need not be capitalized. - - For example: - 'get: id(25) or perm(Admin)' - The 'get' lock access_type is checked e.g. by the 'get' command. - An object locked with this example lock will only be possible to pick up - by Admins or by an object with id=25. - - You can add several access_types after one another by separating - them by ';', i.e: - 'get:id(25); delete:perm(Builder)' - """ - - key = "lock" - aliases = ["locks"] - locks = "cmd: perm(locks) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """Sets up the command""" - - caller = self.caller - if not self.args: - string = ( - "Usage: lock <object>[ = <lockstring>] or lock[/switch] " "<object>/<access_type>" - ) - caller.msg(string) - return - - if "/" in self.lhs: - # call of the form lock obj/access_type - objname, access_type = [p.strip() for p in self.lhs.split("/", 1)] - obj = None - if objname.startswith("*"): - obj = caller.search_account(objname.lstrip("*")) - if not obj: - obj = caller.search(objname) - if not obj: - return - has_control_access = obj.access(caller, "control") - if access_type == "control" and not has_control_access: - # only allow to change 'control' access if you have 'control' access already - caller.msg("You need 'control' access to change this type of lock.") - return - - if not (has_control_access or obj.access(caller, "edit")): - caller.msg("You are not allowed to do that.") - return - - lockdef = obj.locks.get(access_type) - - if lockdef: - if "del" in self.switches: - obj.locks.delete(access_type) - string = "deleted lock %s" % lockdef - else: - string = lockdef - else: - string = "%s has no lock of access type '%s'." % (obj, access_type) - caller.msg(string) - return - - if self.rhs: - # we have a = separator, so we are assigning a new lock - if self.switches: - swi = ", ".join(self.switches) - caller.msg( - "Switch(es) |w%s|n can not be used with a " - "lock assignment. Use e.g. " - "|wlock/del objname/locktype|n instead." % swi - ) - return - - objname, lockdef = self.lhs, self.rhs - obj = None - if objname.startswith("*"): - obj = caller.search_account(objname.lstrip("*")) - if not obj: - obj = caller.search(objname) - if not obj: - return - if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.msg("You are not allowed to do that.") - return - ok = False - lockdef = re.sub(r"\'|\"", "", lockdef) - try: - ok = obj.locks.add(lockdef) - except LockException as e: - caller.msg(str(e)) - if "cmd" in lockdef.lower() and inherits_from( - obj, "evennia.objects.objects.DefaultExit" - ): - # special fix to update Exits since "cmd"-type locks won't - # update on them unless their cmdsets are rebuilt. - obj.at_init() - if ok: - caller.msg("Added lock '%s' to %s." % (lockdef, obj)) - return - - # if we get here, we are just viewing all locks on obj - obj = None - if self.lhs.startswith("*"): - obj = caller.search_account(self.lhs.lstrip("*")) - if not obj: - obj = caller.search(self.lhs) - if not obj: - return - if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.msg("You are not allowed to do that.") - return - caller.msg("\n".join(obj.locks.all()))
- - -
[docs]class CmdExamine(ObjManipCommand): - """ - get detailed information about an object - - Usage: - examine [<object>[/attrname]] - examine [*<account>[/attrname]] - - Switch: - account - examine an Account (same as adding *) - object - examine an Object (useful when OOC) - - The examine command shows detailed game info about an - object and optionally a specific attribute on it. - If object is not specified, the current location is examined. - - Append a * before the search string to examine an account. - - """ - - key = "examine" - aliases = ["ex", "exam"] - locks = "cmd:perm(examine) or perm(Builder)" - help_category = "Building" - arg_regex = r"(/\w+?(\s|$))|\s|$" - - account_mode = False - detail_color = "|c" - header_color = "|w" - quell_color = "|r" - separator = "-" - -
[docs] def list_attribute(self, crop, attr, category, value): - """ - Formats a single attribute line. - - Args: - crop (bool): If output should be cropped if too long. - attr (str): Attribute key. - category (str): Attribute category. - value (any): Attribute value. - Returns: - """ - if attr is None: - return "No such attribute was found." - value = utils.to_str(value) - if crop: - value = utils.crop(value) - value = inlinefunc_raw(ansi_raw(value)) - if category: - return f"{attr}[{category}] = {value}" - else: - return f"{attr} = {value}"
- -
[docs] def format_attributes(self, obj, attrname=None, crop=True): - """ - Helper function that returns info about attributes and/or - non-persistent data stored on object - - """ - if attrname: - if obj.attributes.has(attrname): - db_attr = [(attrname, obj.attributes.get(attrname), None)] - else: - db_attr = None - try: - ndb_attr = [(attrname, object.__getattribute__(obj.ndb, attrname))] - except Exception: - ndb_attr = None - if not (db_attr or ndb_attr): - return {"Attribue(s)": f"\n No Attribute '{attrname}' found on {obj.name}"} - else: - db_attr = [(attr.key, attr.value, attr.category) for attr in obj.db_attributes.all()] - try: - ndb_attr = obj.nattributes.all(return_tuples=True) - except Exception: - ndb_attr = (None, None, None) - - output = {} - if db_attr and db_attr[0]: - output["Persistent attribute(s)"] = "\n " + "\n ".join( - sorted( - self.list_attribute(crop, attr, category, value) - for attr, value, category in db_attr - ) - ) - if ndb_attr and ndb_attr[0]: - output["Non-Persistent attribute(s)"] = " \n" + " \n".join( - sorted(self.list_attribute(crop, attr, None, value) for attr, value in ndb_attr) - ) - return output
- -
[docs] def format_output(self, obj, current_cmdset): - """ - Helper function that creates a nice report about an object. - - Args: - obj (any): Object to analyze. - current_cmdset (CmdSet): Current cmdset for object. - - Returns: - str: The formatted string. - - """ - hclr = self.header_color - dclr = self.detail_color - qclr = self.quell_color - - output = {} - # main key - output["Name/key"] = f"{dclr}{obj.name}|n ({obj.dbref})" - # aliases - if hasattr(obj, "aliases") and obj.aliases.all(): - output["Aliases"] = ", ".join(utils.make_iter(str(obj.aliases))) - # typeclass - output["Typeclass"] = f"{obj.typename} ({obj.typeclass_path})" - # sessions - if hasattr(obj, "sessions") and obj.sessions.all(): - output["Session id(s)"] = ", ".join(f"#{sess.sessid}" for sess in obj.sessions.all()) - # email, if any - if hasattr(obj, "email") and obj.email: - output["Email"] = f"{dclr}{obj.email}|n" - # account, for puppeted objects - if hasattr(obj, "has_account") and obj.has_account: - output["Account"] = f"{dclr}{obj.account.name}|n ({obj.account.dbref})" - # account typeclass - output[" Account Typeclass"] = f"{obj.account.typename} ({obj.account.typeclass_path})" - # account permissions - perms = obj.account.permissions.all() - if obj.account.is_superuser: - perms = ["<Superuser>"] - elif not perms: - perms = ["<None>"] - perms = ", ".join(perms) - if obj.account.attributes.has("_quell"): - perms += f" {qclr}(quelled)|n" - output[" Account Permissions"] = perms - # location - if hasattr(obj, "location"): - loc = str(obj.location) - if obj.location: - loc += f" (#{obj.location.id})" - output["Location"] = loc - # home - if hasattr(obj, "home"): - home = str(obj.home) - if obj.home: - home += f" (#{obj.home.id})" - output["Home"] = home - # destination, for exits - if hasattr(obj, "destination") and obj.destination: - dest = str(obj.destination) - if obj.destination: - dest += f" (#{obj.destination.id})" - output["Destination"] = dest - # main permissions - perms = obj.permissions.all() - perms_string = "" - if perms: - perms_string = ", ".join(perms) - if obj.is_superuser: - perms_string += " <Superuser>" - if perms_string: - output["Permissions"] = perms_string - # locks - locks = str(obj.locks) - if locks: - locks_string = "\n" + utils.fill( - "; ".join([lock for lock in locks.split(";")]), indent=2 - ) - else: - locks_string = " Default" - output["Locks"] = locks_string - # cmdsets - if current_cmdset and not ( - len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"): - # all() returns a 'stack', so make a copy to sort. - - def _format_options(cmdset): - """helper for cmdset-option display""" - - def _truefalse(string, value): - if value is None: - return "" - if value: - return f"{string}: T" - return f"{string}: F" - - options = ", ".join( - _truefalse(opt, getattr(cmdset, opt)) - for opt in ("no_exits", "no_objs", "no_channels", "duplicates") - if getattr(cmdset, opt) is not None - ) - options = ", " + options if options else "" - return options - - # cmdset stored on us - stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority, reverse=True) - stored = [] - for cmdset in stored_cmdsets: - if cmdset.key == "_EMPTY_CMDSET": - continue - options = _format_options(cmdset) - stored.append( - f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority}{options})" - ) - output["Stored Cmdset(s)"] = "\n " + "\n ".join(stored) - - # this gets all components of the currently merged set - all_cmdsets = [(cmdset.key, cmdset) for cmdset in current_cmdset.merged_from] - # we always at least try to add account- and session sets since these are ignored - # if we merge on the object level. - if hasattr(obj, "account") and obj.account: - all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.account.cmdset.all()]) - if obj.sessions.count(): - # if there are more sessions than one on objects it's because of multisession mode 3. - # we only show the first session's cmdset here (it is -in principle- possible that - # different sessions have different cmdsets but for admins who want such madness - # it is better that they overload with their own CmdExamine to handle it). - all_cmdsets.extend( - [ - (cmdset.key, cmdset) - for cmdset in obj.account.sessions.all()[0].cmdset.all() - ] - ) - else: - try: - # we have to protect this since many objects don't have sessions. - all_cmdsets.extend( - [ - (cmdset.key, cmdset) - for cmdset in obj.get_session(obj.sessions.get()).cmdset.all() - ] - ) - except (TypeError, AttributeError): - # an error means we are merging an object without a session - pass - all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()] - all_cmdsets.sort(key=lambda x: x.priority, reverse=True) - - # the resulting merged cmdset - options = _format_options(current_cmdset) - merged = [ - f"<Current merged cmdset> ({current_cmdset.mergetype} prio {current_cmdset.priority}{options})" - ] - - # the merge stack - for cmdset in all_cmdsets: - options = _format_options(cmdset) - merged.append( - f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype} prio {cmdset.priority}{options})" - ) - output["Merged Cmdset(s)"] = "\n " + "\n ".join(merged) - - # list the commands available to this object - current_commands = sorted([cmd.key for cmd in current_cmdset if cmd.access(obj, "cmd")]) - cmdsetstr = "\n" + utils.fill(", ".join(current_commands), indent=2) - output[f"Commands available to {obj.key} (result of Merged CmdSets)"] = str(cmdsetstr) - - # scripts - if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all(): - output["Scripts"] = "\n " + f"{obj.scripts}" - # add the attributes - output.update(self.format_attributes(obj)) - # Tags - tags = obj.tags.all(return_key_and_category=True) - tags_string = "\n" + utils.fill( - ", ".join(sorted(f"{tag}[{category}]" for tag, category in tags)), indent=2, - ) - if tags: - output["Tags[category]"] = tags_string - # Contents of object - exits = [] - pobjs = [] - things = [] - if hasattr(obj, "contents"): - for content in obj.contents: - if content.destination: - exits.append(content) - elif content.account: - pobjs.append(content) - else: - things.append(content) - if exits: - output["Exits (has .destination)"] = ", ".join( - f"{exit.name}({exit.dbref})" for exit in exits - ) - if pobjs: - output["Characters"] = ", ".join( - f"{dclr}{pobj.name}|n({pobj.dbref})" for pobj in pobjs - ) - if things: - output["Contents"] = ", ".join( - f"{cont.name}({cont.dbref})" - for cont in obj.contents - if cont not in exits and cont not in pobjs - ) - # format output - max_width = -1 - for block in output.values(): - max_width = max(max_width, max(display_len(line) for line in block.split("\n"))) - max_width = max(0, min(self.client_width(), max_width)) - - sep = self.separator * max_width - mainstr = "\n".join(f"{hclr}{header}|n: {block}" for (header, block) in output.items()) - return f"{sep}\n{mainstr}\n{sep}"
- -
[docs] def func(self): - """Process command""" - caller = self.caller - - def get_cmdset_callback(cmdset): - """ - We make use of the cmdhandeler.get_and_merge_cmdsets below. This - is an asynchronous function, returning a Twisted deferred. - So in order to properly use this we need use this callback; - it is called with the result of get_and_merge_cmdsets, whenever - that function finishes. Taking the resulting cmdset, we continue - to format and output the result. - """ - self.msg(self.format_output(obj, cmdset).strip()) - - if not self.args: - # If no arguments are provided, examine the invoker's location. - if hasattr(caller, "location"): - obj = caller.location - if not obj.access(caller, "examine"): - # If we don't have special info access, just look at the object instead. - self.msg(caller.at_look(obj)) - return - obj_session = obj.sessions.get()[0] if obj.sessions.count() else None - - # using callback for printing result whenever function returns. - get_and_merge_cmdsets( - obj, obj_session, self.account, obj, "object", self.raw_string - ).addCallback(get_cmdset_callback) - else: - self.msg("You need to supply a target to examine.") - return - - # we have given a specific target object - for objdef in self.lhs_objattr: - - obj = None - obj_name = objdef["name"] - obj_attrs = objdef["attrs"] - - self.account_mode = ( - utils.inherits_from(caller, "evennia.accounts.accounts.DefaultAccount") - or "account" in self.switches - or obj_name.startswith("*") - ) - if self.account_mode: - try: - obj = caller.search_account(obj_name.lstrip("*")) - except AttributeError: - # this means we are calling examine from an account object - obj = caller.search( - obj_name.lstrip("*"), search_object="object" in self.switches - ) - else: - obj = caller.search(obj_name) - if not obj: - continue - - if not obj.access(caller, "examine"): - # If we don't have special info access, just look - # at the object instead. - self.msg(caller.at_look(obj)) - continue - - if obj_attrs: - for attrname in obj_attrs: - # we are only interested in specific attributes - ret = "\n".join( - f"{self.header_color}{header}|n:{value}" - for header, value in self.format_attributes( - obj, attrname, crop=False - ).items() - ) - self.caller.msg(ret) - else: - session = None - if obj.sessions.count(): - mergemode = "session" - session = obj.sessions.get()[0] - elif self.account_mode: - mergemode = "account" - else: - mergemode = "object" - - account = None - objct = None - if self.account_mode: - account = obj - else: - account = obj.account - objct = obj - - # this is usually handled when a command runs, but when we examine - # we may have leftover inherited cmdsets directly after a move etc. - obj.cmdset.update() - # using callback to print results whenever function returns. - get_and_merge_cmdsets( - obj, session, account, objct, mergemode, self.raw_string - ).addCallback(get_cmdset_callback)
- - -
[docs]class CmdFind(COMMAND_DEFAULT_CLASS): - """ - search the database for objects - - Usage: - find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]] - locate - this is a shorthand for using the /loc switch. - - Switches: - room - only look for rooms (location=None) - exit - only look for exits (destination!=None) - char - only look for characters (BASE_CHARACTER_TYPECLASS) - exact - only exact matches are returned. - loc - display object location if exists and match has one result - startswith - search for names starting with the string, rather than containing - - Searches the database for an object of a particular name or exact #dbref. - Use *accountname to search for an account. The switches allows for - limiting object matches to certain game entities. Dbrefmin and dbrefmax - limits matches to within the given dbrefs range, or above/below if only - one is given. - """ - - key = "find" - aliases = "search, locate" - switch_options = ("room", "exit", "char", "exact", "loc", "startswith") - locks = "cmd:perm(find) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """Search functionality""" - caller = self.caller - switches = self.switches - - if not self.args or (not self.lhs and not self.rhs): - caller.msg("Usage: find <string> [= low [-high]]") - return - - if "locate" in self.cmdstring: # Use option /loc as a default for locate command alias - switches.append("loc") - - searchstring = self.lhs - - try: - # Try grabbing the actual min/max id values by database aggregation - qs = ObjectDB.objects.values("id").aggregate(low=Min("id"), high=Max("id")) - low, high = sorted(qs.values()) - if not (low and high): - raise ValueError( - f"{self.__class__.__name__}: Min and max ID not returned by aggregation; falling back to queryset slicing." - ) - except Exception as e: - logger.log_trace(e) - # If that doesn't work for some reason (empty DB?), guess the lower - # bound and do a less-efficient query to find the upper. - low, high = 1, ObjectDB.objects.all().order_by("-id").first().id - - if self.rhs: - try: - # Check that rhs is either a valid dbref or dbref range - bounds = tuple( - sorted(dbref(x, False) for x in re.split("[-\s]+", self.rhs.strip())) - ) - - # dbref() will return either a valid int or None - assert bounds - # None should not exist in the bounds list - assert None not in bounds - - low = bounds[0] - if len(bounds) > 1: - high = bounds[-1] - - except AssertionError: - caller.msg("Invalid dbref range provided (not a number).") - return - except IndexError as e: - logger.log_err( - f"{self.__class__.__name__}: Error parsing upper and lower bounds of query." - ) - logger.log_trace(e) - - low = min(low, high) - high = max(low, high) - - is_dbref = utils.dbref(searchstring) - is_account = searchstring.startswith("*") - - restrictions = "" - if self.switches: - restrictions = ", %s" % (", ".join(self.switches)) - - if is_dbref or is_account: - if is_dbref: - # a dbref search - result = caller.search(searchstring, global_search=True, quiet=True) - string = "|wExact dbref match|n(#%i-#%i%s):" % (low, high, restrictions) - else: - # an account search - searchstring = searchstring.lstrip("*") - result = caller.search_account(searchstring, quiet=True) - string = "|wMatch|n(#%i-#%i%s):" % (low, high, restrictions) - - if "room" in switches: - result = result if inherits_from(result, ROOM_TYPECLASS) else None - if "exit" in switches: - result = result if inherits_from(result, EXIT_TYPECLASS) else None - if "char" in switches: - result = result if inherits_from(result, CHAR_TYPECLASS) else None - - if not result: - string += "\n |RNo match found.|n" - elif not low <= int(result[0].id) <= high: - string += "\n |RNo match found for '%s' in #dbref interval.|n" % searchstring - else: - result = result[0] - string += "\n|g %s - %s|n" % (result.get_display_name(caller), result.path) - if "loc" in self.switches and not is_account and result.location: - string += " (|wlocation|n: |g{}|n)".format( - result.location.get_display_name(caller) - ) - else: - # Not an account/dbref search but a wider search; build a queryset. - # Searches for key and aliases - if "exact" in switches: - keyquery = Q(db_key__iexact=searchstring, id__gte=low, id__lte=high) - aliasquery = Q( - db_tags__db_key__iexact=searchstring, - db_tags__db_tagtype__iexact="alias", - id__gte=low, - id__lte=high, - ) - elif "startswith" in switches: - keyquery = Q(db_key__istartswith=searchstring, id__gte=low, id__lte=high) - aliasquery = Q( - db_tags__db_key__istartswith=searchstring, - db_tags__db_tagtype__iexact="alias", - id__gte=low, - id__lte=high, - ) - else: - keyquery = Q(db_key__icontains=searchstring, id__gte=low, id__lte=high) - aliasquery = Q( - db_tags__db_key__icontains=searchstring, - db_tags__db_tagtype__iexact="alias", - id__gte=low, - id__lte=high, - ) - - # Keep the initial queryset handy for later reuse - result_qs = ObjectDB.objects.filter(keyquery | aliasquery).distinct() - nresults = result_qs.count() - - # Use iterator to minimize memory ballooning on large result sets - results = result_qs.iterator() - - # Check and see if type filtering was requested; skip it if not - if any(x in switches for x in ("room", "exit", "char")): - obj_ids = set() - for obj in results: - if ( - ("room" in switches and inherits_from(obj, ROOM_TYPECLASS)) - or ("exit" in switches and inherits_from(obj, EXIT_TYPECLASS)) - or ("char" in switches and inherits_from(obj, CHAR_TYPECLASS)) - ): - obj_ids.add(obj.id) - - # Filter previous queryset instead of requesting another - filtered_qs = result_qs.filter(id__in=obj_ids).distinct() - nresults = filtered_qs.count() - - # Use iterator again to minimize memory ballooning - results = filtered_qs.iterator() - - # still results after type filtering? - if nresults: - if nresults > 1: - header = f"{nresults} Matches" - else: - header = "One Match" - - string = f"|w{header}|n(#{low}-#{high}{restrictions}):" - res = None - for res in results: - string += f"\n |g{res.get_display_name(caller)} - {res.path}|n" - if ( - "loc" in self.switches - and nresults == 1 - and res - and getattr(res, "location", None) - ): - string += f" (|wlocation|n: |g{res.location.get_display_name(caller)}|n)" - else: - string = f"|wNo Matches|n(#{low}-#{high}{restrictions}):" - string += f"\n |RNo matches found for '{searchstring}'|n" - - # send result - caller.msg(string.strip())
- - -
[docs]class CmdTeleport(COMMAND_DEFAULT_CLASS): - """ - teleport object to another location - - Usage: - tel/switch [<object> to||=] <target location> - - Examples: - tel Limbo - tel/quiet box = Limbo - tel/tonone box - - Switches: - quiet - don't echo leave/arrive messages to the source/target - locations for the move. - intoexit - if target is an exit, teleport INTO - the exit object instead of to its destination - tonone - if set, teleport the object to a None-location. If this - switch is set, <target location> is ignored. - Note that the only way to retrieve - an object from a None location is by direct #dbref - reference. A puppeted object cannot be moved to None. - loc - teleport object to the target's location instead of its contents - - Teleports an object somewhere. If no object is given, you yourself are - teleported to the target location. - """ - - key = "tel" - aliases = "teleport" - switch_options = ("quiet", "intoexit", "tonone", "loc") - rhs_split = ("=", " to ") # Prefer = delimiter, but allow " to " usage. - locks = "cmd:perm(teleport) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """Performs the teleport""" - - caller = self.caller - args = self.args - lhs, rhs = self.lhs, self.rhs - switches = self.switches - - # setting switches - tel_quietly = "quiet" in switches - to_none = "tonone" in switches - to_loc = "loc" in switches - - if to_none: - # teleporting to None - if not args: - obj_to_teleport = caller - else: - obj_to_teleport = caller.search(lhs, global_search=True) - if not obj_to_teleport: - caller.msg("Did not find object to teleport.") - return - if obj_to_teleport.has_account: - caller.msg( - "Cannot teleport a puppeted object " - "(%s, puppeted by %s) to a None-location." - % (obj_to_teleport.key, obj_to_teleport.account) - ) - return - caller.msg("Teleported %s -> None-location." % obj_to_teleport) - if obj_to_teleport.location and not tel_quietly: - obj_to_teleport.location.msg_contents( - "%s teleported %s into nothingness." % (caller, obj_to_teleport), exclude=caller - ) - obj_to_teleport.location = None - return - - # not teleporting to None location - if not args and not to_none: - caller.msg("Usage: teleport[/switches] [<obj> =] <target_loc>||home") - return - - if rhs: - obj_to_teleport = caller.search(lhs, global_search=True) - destination = caller.search(rhs, global_search=True) - else: - obj_to_teleport = caller - destination = caller.search(lhs, global_search=True) - if not obj_to_teleport: - caller.msg("Did not find object to teleport.") - return - - if not destination: - caller.msg("Destination not found.") - return - if to_loc: - destination = destination.location - if not destination: - caller.msg("Destination has no location.") - return - if obj_to_teleport == destination: - caller.msg("You can't teleport an object inside of itself!") - return - if obj_to_teleport == destination.location: - caller.msg("You can't teleport an object inside something it holds!") - return - if obj_to_teleport.location and obj_to_teleport.location == destination: - caller.msg("%s is already at %s." % (obj_to_teleport, destination)) - return - use_destination = True - if "intoexit" in self.switches: - use_destination = False - - # try the teleport - if obj_to_teleport.move_to( - destination, quiet=tel_quietly, emit_to_obj=caller, use_destination=use_destination - ): - if obj_to_teleport == caller: - caller.msg("Teleported to %s." % destination) - else: - caller.msg("Teleported %s -> %s." % (obj_to_teleport, destination))
- - -
[docs]class CmdScript(COMMAND_DEFAULT_CLASS): - """ - attach a script to an object - - Usage: - script[/switch] <obj> [= script_path or <scriptkey>] - - Switches: - start - start all non-running scripts on object, or a given script only - stop - stop all scripts on objects, or a given script only - - If no script path/key is given, lists all scripts active on the given - object. - Script path can be given from the base location for scripts as given in - settings. If adding a new script, it will be started automatically - (no /start switch is needed). Using the /start or /stop switches on an - object without specifying a script key/path will start/stop ALL scripts on - the object. - """ - - key = "script" - aliases = "addscript" - switch_options = ("start", "stop") - locks = "cmd:perm(script) or perm(Builder)" - help_category = "Building" - -
[docs] def func(self): - """Do stuff""" - - caller = self.caller - - if not self.args: - string = "Usage: script[/switch] <obj> [= script_path or <script key>]" - caller.msg(string) - return - - if not self.lhs: - caller.msg("To create a global script you need |wscripts/add <typeclass>|n.") - return - - obj = caller.search(self.lhs) - if not obj: - return - - result = [] - if not self.rhs: - # no rhs means we want to operate on all scripts - scripts = obj.scripts.all() - if not scripts: - result.append("No scripts defined on %s." % obj.get_display_name(caller)) - elif not self.switches: - # view all scripts - from evennia.commands.default.system import ScriptEvMore - - ScriptEvMore(self.caller, scripts.order_by("id"), session=self.session) - return - elif "start" in self.switches: - num = sum([obj.scripts.start(script.key) for script in scripts]) - result.append("%s scripts started on %s." % (num, obj.get_display_name(caller))) - elif "stop" in self.switches: - for script in scripts: - result.append( - "Stopping script %s on %s." - % (script.get_display_name(caller), obj.get_display_name(caller)) - ) - script.stop() - obj.scripts.validate() - else: # rhs exists - if not self.switches: - # adding a new script, and starting it - ok = obj.scripts.add(self.rhs, autostart=True) - if not ok: - result.append( - "\nScript %s could not be added and/or started on %s " - "(or it started and immediately shut down)." - % (self.rhs, obj.get_display_name(caller)) - ) - else: - result.append( - "Script |w%s|n successfully added and started on %s." - % (self.rhs, obj.get_display_name(caller)) - ) - - else: - paths = [self.rhs] + [ - "%s.%s" % (prefix, self.rhs) for prefix in settings.TYPECLASS_PATHS - ] - if "stop" in self.switches: - # we are stopping an already existing script - for path in paths: - ok = obj.scripts.stop(path) - if not ok: - result.append("\nScript %s could not be stopped. Does it exist?" % path) - else: - result = ["Script stopped and removed from object."] - break - if "start" in self.switches: - # we are starting an already existing script - for path in paths: - ok = obj.scripts.start(path) - if not ok: - result.append("\nScript %s could not be (re)started." % path) - else: - result = ["Script started successfully."] - break - - EvMore(caller, "".join(result).strip())
- - -
[docs]class CmdTag(COMMAND_DEFAULT_CLASS): - """ - handles the tags of an object - - Usage: - tag[/del] <obj> [= <tag>[:<category>]] - tag/search <tag>[:<category] - - Switches: - search - return all objects with a given Tag - del - remove the given tag. If no tag is specified, - clear all tags on object. - - Manipulates and lists tags on objects. Tags allow for quick - grouping of and searching for objects. If only <obj> is given, - list all tags on the object. If /search is used, list objects - with the given tag. - The category can be used for grouping tags themselves, but it - should be used with restrain - tags on their own are usually - enough to for most grouping schemes. - """ - - key = "tag" - aliases = ["tags"] - options = ("search", "del") - locks = "cmd:perm(tag) or perm(Builder)" - help_category = "Building" - arg_regex = r"(/\w+?(\s|$))|\s|$" - -
[docs] def func(self): - """Implement the tag functionality""" - - if not self.args: - self.caller.msg("Usage: tag[/switches] <obj> [= <tag>[:<category>]]") - return - if "search" in self.switches: - # search by tag - tag = self.args - category = None - if ":" in tag: - tag, category = [part.strip() for part in tag.split(":", 1)] - objs = search.search_tag(tag, category=category) - nobjs = len(objs) - if nobjs > 0: - catstr = ( - " (category: '|w%s|n')" % category - if category - else ("" if nobjs == 1 else " (may have different tag categories)") - ) - matchstr = ", ".join(o.get_display_name(self.caller) for o in objs) - - string = "Found |w%i|n object%s with tag '|w%s|n'%s:\n %s" % ( - nobjs, - "s" if nobjs > 1 else "", - tag, - catstr, - matchstr, - ) - else: - string = "No objects found with tag '%s%s'." % ( - tag, - " (category: %s)" % category if category else "", - ) - self.caller.msg(string) - return - if "del" in self.switches: - # remove one or all tags - obj = self.caller.search(self.lhs, global_search=True) - if not obj: - return - if self.rhs: - # remove individual tag - tag = self.rhs - category = None - if ":" in tag: - tag, category = [part.strip() for part in tag.split(":", 1)] - if obj.tags.get(tag, category=category): - obj.tags.remove(tag, category=category) - string = "Removed tag '%s'%s from %s." % ( - tag, - " (category: %s)" % category if category else "", - obj, - ) - else: - string = "No tag '%s'%s to delete on %s." % ( - tag, - " (category: %s)" % category if category else "", - obj, - ) - else: - # no tag specified, clear all tags - old_tags = [ - "%s%s" % (tag, " (category: %s)" % category if category else "") - for tag, category in obj.tags.all(return_key_and_category=True) - ] - if old_tags: - obj.tags.clear() - string = "Cleared all tags from %s: %s" % (obj, ", ".join(sorted(old_tags))) - else: - string = "No Tags to clear on %s." % obj - self.caller.msg(string) - return - # no search/deletion - if self.rhs: - # = is found; command args are of the form obj = tag - obj = self.caller.search(self.lhs, global_search=True) - if not obj: - return - tag = self.rhs - category = None - if ":" in tag: - tag, category = [part.strip() for part in tag.split(":", 1)] - # create the tag - obj.tags.add(tag, category=category) - string = "Added tag '%s'%s to %s." % ( - tag, - " (category: %s)" % category if category else "", - obj, - ) - self.caller.msg(string) - else: - # no = found - list tags on object - obj = self.caller.search(self.args, global_search=True) - if not obj: - return - tagtuples = obj.tags.all(return_key_and_category=True) - ntags = len(tagtuples) - tags = [tup[0] for tup in tagtuples] - categories = [" (category: %s)" % tup[1] if tup[1] else "" for tup in tagtuples] - if ntags: - string = "Tag%s on %s: %s" % ( - "s" if ntags > 1 else "", - obj, - ", ".join(sorted("'%s'%s" % (tags[i], categories[i]) for i in range(ntags))), - ) - else: - string = "No tags attached to %s." % obj - self.caller.msg(string)
- - -# helper functions for spawn - - -
[docs]class CmdSpawn(COMMAND_DEFAULT_CLASS): - """ - spawn objects from prototype - - Usage: - spawn[/noloc] <prototype_key> - spawn[/noloc] <prototype_dict> - - spawn/search [prototype_keykey][;tag[,tag]] - spawn/list [tag, tag, ...] - spawn/list modules - list only module-based prototypes - spawn/show [<prototype_key>] - spawn/update <prototype_key> - - spawn/save <prototype_dict> - spawn/edit [<prototype_key>] - olc - equivalent to spawn/edit - - Switches: - noloc - allow location to be None if not specified explicitly. Otherwise, - location will default to caller's current location. - search - search prototype by name or tags. - list - list available prototypes, optionally limit by tags. - show, examine - inspect prototype by key. If not given, acts like list. - raw - show the raw dict of the prototype as a one-line string for manual editing. - save - save a prototype to the database. It will be listable by /list. - delete - remove a prototype from database, if allowed to. - update - find existing objects with the same prototype_key and update - them with latest version of given prototype. If given with /save, - will auto-update all objects with the old version of the prototype - without asking first. - edit, menu, olc - create/manipulate prototype in a menu interface. - - Example: - spawn GOBLIN - spawn {"key":"goblin", "typeclass":"monster.Monster", "location":"#2"} - spawn/save {"key": "grunt", prototype: "goblin"};;mobs;edit:all() - \f - Dictionary keys: - |wprototype_parent |n - name of parent prototype to use. Required if typeclass is - not set. Can be a path or a list for multiple inheritance (inherits - left to right). If set one of the parents must have a typeclass. - |wtypeclass |n - string. Required if prototype_parent is not set. - |wkey |n - string, the main object identifier - |wlocation |n - this should be a valid object or #dbref - |whome |n - valid object or #dbref - |wdestination|n - only valid for exits (object or dbref) - |wpermissions|n - string or list of permission strings - |wlocks |n - a lock-string - |waliases |n - string or list of strings. - |wndb_|n<name> - value of a nattribute (ndb_ is stripped) - - |wprototype_key|n - name of this prototype. Unique. Used to store/retrieve from db - and update existing prototyped objects if desired. - |wprototype_desc|n - desc of this prototype. Used in listings - |wprototype_locks|n - locks of this prototype. Limits who may use prototype - |wprototype_tags|n - tags of this prototype. Used to find prototype - - any other keywords are interpreted as Attributes and their values. - - The available prototypes are defined globally in modules set in - settings.PROTOTYPE_MODULES. If spawn is used without arguments it - displays a list of available prototypes. - - """ - - key = "spawn" - aliases = ["olc"] - switch_options = ( - "noloc", - "search", - "list", - "show", - "raw", - "examine", - "save", - "delete", - "menu", - "olc", - "update", - "edit", - ) - locks = "cmd:perm(spawn) or perm(Builder)" - help_category = "Building" - - def _search_prototype(self, prototype_key, quiet=False): - """ - Search for prototype and handle no/multi-match and access. - - Returns a single found prototype or None - in the - case, the caller has already been informed of the - search error we need not do any further action. - - """ - prototypes = protlib.search_prototype(prototype_key) - nprots = len(prototypes) - - # handle the search result - err = None - if not prototypes: - err = f"No prototype named '{prototype_key}' was found." - elif nprots > 1: - err = "Found {} prototypes matching '{}':\n {}".format( - nprots, - prototype_key, - ", ".join(proto.get("prototype_key", "") for proto in prototypes), - ) - else: - # we have a single prototype, check access - prototype = prototypes[0] - if not self.caller.locks.check_lockstring( - self.caller, prototype.get("prototype_locks", ""), access_type="spawn", default=True - ): - err = "You don't have access to use this prototype." - - if err: - # return None on any error - if not quiet: - self.caller.msg(err) - return - return prototype - - def _parse_prototype(self, inp, expect=dict): - """ - Parse a prototype dict or key from the input and convert it safely - into a dict if appropriate. - - Args: - inp (str): The input from user. - expect (type, optional): - Returns: - prototype (dict, str or None): The parsed prototype. If None, the error - was already reported. - - """ - eval_err = None - try: - prototype = _LITERAL_EVAL(inp) - except (SyntaxError, ValueError) as err: - # treat as string - eval_err = err - prototype = utils.to_str(inp) - finally: - # it's possible that the input was a prototype-key, in which case - # it's okay for the LITERAL_EVAL to fail. Only if the result does not - # match the expected type do we have a problem. - if not isinstance(prototype, expect): - if eval_err: - string = ( - f"{inp}\n{eval_err}\n|RCritical Python syntax error in argument. Only primitive " - "Python structures are allowed. \nMake sure to use correct " - "Python syntax. Remember especially to put quotes around all " - "strings inside lists and dicts.|n For more advanced uses, embed " - "inlinefuncs in the strings." - ) - else: - string = "Expected {}, got {}.".format(expect, type(prototype)) - self.caller.msg(string) - return - - if expect == dict: - # an actual prototype. We need to make sure it's safe, - # so don't allow exec. - # TODO: Exec support is deprecated. Remove completely for 1.0. - if "exec" in prototype and not self.caller.check_permstring("Developer"): - self.caller.msg( - "Spawn aborted: You are not allowed to " "use the 'exec' prototype key." - ) - return - try: - # we homogenize the protoype first, to be more lenient with free-form - protlib.validate_prototype(protlib.homogenize_prototype(prototype)) - except RuntimeError as err: - self.caller.msg(str(err)) - return - return prototype - - def _get_prototype_detail(self, query=None, prototypes=None): - """ - Display the detailed specs of one or more prototypes. - - Args: - query (str, optional): If this is given and `prototypes` is not, search for - the prototype(s) by this query. This may be a partial query which - may lead to multiple matches, all being displayed. - prototypes (list, optional): If given, ignore `query` and only show these - prototype-details. - Returns: - display (str, None): A formatted string of one or more prototype details. - If None, the caller was already informed of the error. - - - """ - if not prototypes: - # we need to query. Note that if query is None, all prototypes will - # be returned. - prototypes = protlib.search_prototype(key=query) - if prototypes: - return "\n".join(protlib.prototype_to_str(prot) for prot in prototypes) - elif query: - self.caller.msg(f"No prototype named '{query}' was found.") - else: - self.caller.msg("No prototypes found.") - - def _list_prototypes(self, key=None, tags=None): - """Display prototypes as a list, optionally limited by key/tags. """ - protlib.list_prototypes(self.caller, key=key, tags=tags, session=self.session) - - @interactive - def _update_existing_objects(self, caller, prototype_key, quiet=False): - """ - Update existing objects (if any) with this prototype-key to the latest - prototype version. - - Args: - caller (Object): This is necessary for @interactive to work. - prototype_key (str): The prototype to update. - quiet (bool, optional): If set, don't report to user if no - old objects were found to update. - Returns: - n_updated (int): Number of updated objects. - - """ - prototype = self._search_prototype(prototype_key) - if not prototype: - return - - existing_objects = protlib.search_objects_with_prototype(prototype_key) - if not existing_objects: - if not quiet: - caller.msg("No existing objects found with an older version of this prototype.") - return - - if existing_objects: - n_existing = len(existing_objects) - slow = " (note that this may be slow)" if n_existing > 10 else "" - string = ( - f"There are {n_existing} existing object(s) with an older version " - f"of prototype '{prototype_key}'. Should it be re-applied to them{slow}? [Y]/N" - ) - answer = yield (string) - if answer.lower() in ["n", "no"]: - caller.msg( - "|rNo update was done of existing objects. " - "Use spawn/update <key> to apply later as needed.|n" - ) - return - try: - n_updated = spawner.batch_update_objects_with_prototype( - prototype, objects=existing_objects - ) - except Exception: - logger.log_trace() - caller.msg(f"{n_updated} objects were updated.") - return - - def _parse_key_desc_tags(self, argstring, desc=True): - """ - Parse ;-separated input list. - """ - key, desc, tags = "", "", [] - if ";" in argstring: - parts = [part.strip().lower() for part in argstring.split(";")] - if len(parts) > 1 and desc: - key = parts[0] - desc = parts[1] - tags = parts[2:] - else: - key = parts[0] - tags = parts[1:] - else: - key = argstring.strip().lower() - return key, desc, tags - -
[docs] def func(self): - """Implements the spawner""" - - caller = self.caller - noloc = "noloc" in self.switches - - # run the menu/olc - if ( - self.cmdstring == "olc" - or "menu" in self.switches - or "olc" in self.switches - or "edit" in self.switches - ): - # OLC menu mode - prototype = None - if self.lhs: - prototype_key = self.lhs - prototype = self._search_prototype(prototype_key) - if not prototype: - return - olc_menus.start_olc(caller, session=self.session, prototype=prototype) - return - - if "search" in self.switches: - # query for a key match. The arg is a search query or nothing. - - if not self.args: - # an empty search returns the full list - self._list_prototypes() - return - - # search for key;tag combinations - key, _, tags = self._parse_key_desc_tags(self.args, desc=False) - self._list_prototypes(key, tags) - return - - if "raw" in self.switches: - # query for key match and return the prototype as a safe one-liner string. - if not self.args: - caller.msg("You need to specify a prototype-key to get the raw data for.") - prototype = self._search_prototype(self.args) - if not prototype: - return - caller.msg(str(prototype)) - return - - if "show" in self.switches or "examine" in self.switches: - # show a specific prot detail. The argument is a search query or empty. - if not self.args: - # we don't show the list of all details, that's too spammy. - caller.msg("You need to specify a prototype-key to show.") - return - - detail_string = self._get_prototype_detail(self.args) - if not detail_string: - return - caller.msg(detail_string) - return - - if "list" in self.switches: - # for list, all optional arguments are tags. - tags = self.lhslist - err = self._list_prototypes(tags=tags) - if err: - caller.msg( - "No prototypes found with prototype-tag(s): {}".format( - list_to_string(tags, "or") - ) - ) - return - - if "save" in self.switches: - # store a prototype to the database store - if not self.args: - caller.msg( - "Usage: spawn/save [<key>[;desc[;tag,tag[,...][;lockstring]]]] = <prototype_dict>" - ) - return - if self.rhs: - # input on the form key = prototype - prototype_key, prototype_desc, prototype_tags = self._parse_key_desc_tags(self.lhs) - prototype_key = None if not prototype_key else prototype_key - prototype_desc = None if not prototype_desc else prototype_desc - prototype_tags = None if not prototype_tags else prototype_tags - prototype_input = self.rhs.strip() - else: - prototype_key = prototype_desc = None - prototype_tags = None - prototype_input = self.lhs.strip() - - # handle parsing - prototype = self._parse_prototype(prototype_input) - if not prototype: - return - - prot_prototype_key = prototype.get("prototype_key") - - if not (prototype_key or prot_prototype_key): - caller.msg( - "A prototype_key must be given, either as `prototype_key = <prototype>` " - "or as a key 'prototype_key' inside the prototype structure." - ) - return - - if prototype_key is None: - prototype_key = prot_prototype_key - - if prot_prototype_key != prototype_key: - caller.msg("(Replacing `prototype_key` in prototype with given key.)") - prototype["prototype_key"] = prototype_key - - if prototype_desc is not None and prot_prototype_key != prototype_desc: - caller.msg("(Replacing `prototype_desc` in prototype with given desc.)") - prototype["prototype_desc"] = prototype_desc - if prototype_tags is not None and prototype.get("prototype_tags") != prototype_tags: - caller.msg("(Replacing `prototype_tags` in prototype with given tag(s))") - prototype["prototype_tags"] = prototype_tags - - string = "" - # check for existing prototype (exact match) - old_prototype = self._search_prototype(prototype_key, quiet=True) - - diff = spawner.prototype_diff(old_prototype, prototype, homogenize=True) - diffstr = spawner.format_diff(diff) - new_prototype_detail = self._get_prototype_detail(prototypes=[prototype]) - - if old_prototype: - if not diffstr: - string = f"|yAlready existing Prototype:|n\n{new_prototype_detail}\n" - question = ( - "\nThere seems to be no changes. Do you still want to (re)save? [Y]/N" - ) - else: - string = ( - f'|yExisting prototype "{prototype_key}" found. Change:|n\n{diffstr}\n' - f"|yNew changed prototype:|n\n{new_prototype_detail}" - ) - question = ( - "\n|yDo you want to apply the change to the existing prototype?|n [Y]/N" - ) - else: - string = f"|yCreating new prototype:|n\n{new_prototype_detail}" - question = "\nDo you want to continue saving? [Y]/N" - - answer = yield (string + question) - if answer.lower() in ["n", "no"]: - caller.msg("|rSave cancelled.|n") - return - - # all seems ok. Try to save. - try: - prot = protlib.save_prototype(prototype) - if not prot: - caller.msg("|rError saving:|R {}.|n".format(prototype_key)) - return - except protlib.PermissionError as err: - caller.msg("|rError saving:|R {}|n".format(err)) - return - caller.msg("|gSaved prototype:|n {}".format(prototype_key)) - - # check if we want to update existing objects - - self._update_existing_objects(self.caller, prototype_key, quiet=True) - return - - if not self.args: - # all switches beyond this point gets a common non-arg return - ncount = len(protlib.search_prototype()) - caller.msg( - "Usage: spawn <prototype-key> or {{key: value, ...}}" - f"\n ({ncount} existing prototypes. Use /list to inspect)" - ) - return - - if "delete" in self.switches: - # remove db-based prototype - prototype_detail = self._get_prototype_detail(self.args) - if not prototype_detail: - return - - string = f"|rDeleting prototype:|n\n{prototype_detail}" - question = "\nDo you want to continue deleting? [Y]/N" - answer = yield (string + question) - if answer.lower() in ["n", "no"]: - caller.msg("|rDeletion cancelled.|n") - return - - try: - success = protlib.delete_prototype(self.args) - except protlib.PermissionError as err: - retmsg = f"|rError deleting:|R {err}|n" - else: - retmsg = ( - "Deletion successful" - if success - else "Deletion failed (does the prototype exist?)" - ) - caller.msg(retmsg) - return - - if "update" in self.switches: - # update existing prototypes - prototype_key = self.args.strip().lower() - self._update_existing_objects(self.caller, prototype_key) - return - - # If we get to this point, we use not switches but are trying a - # direct creation of an object from a given prototype or -key - - prototype = self._parse_prototype( - self.args, expect=dict if self.args.strip().startswith("{") else str - ) - if not prototype: - # this will only let through dicts or strings - return - - key = "<unnamed>" - if isinstance(prototype, str): - # A prototype key we are looking to apply - prototype_key = prototype - prototype = self._search_prototype(prototype_key) - - if not prototype: - return - - # proceed to spawning - try: - for obj in spawner.spawn(prototype): - self.caller.msg("Spawned %s." % obj.get_display_name(self.caller)) - if not prototype.get("location") and not noloc: - # we don't hardcode the location in the prototype (unless the user - # did so manually) - that would lead to it having to be 'removed' every - # time we try to update objects with this prototype in the future. - obj.location = caller.location - except RuntimeError as err: - caller.msg(err)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/cmdset_account.html b/docs/0.9.5/_modules/evennia/commands/default/cmdset_account.html deleted file mode 100644 index 8b22716b76..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/cmdset_account.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - evennia.commands.default.cmdset_account — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.cmdset_account

-"""
-
-This is the cmdset for Account (OOC) commands.  These are
-stored on the Account object and should thus be able to handle getting
-an Account object as caller rather than a Character.
-
-Note - in order for session-rerouting (in MULTISESSION_MODE=2) to
-function, all commands in this cmdset should use the self.msg()
-command method rather than caller.msg().
-"""
-
-from evennia.commands.cmdset import CmdSet
-from evennia.commands.default import help, comms, admin, system
-from evennia.commands.default import building, account, general
-
-
-
[docs]class AccountCmdSet(CmdSet): - """ - Implements the account command set. - """ - - key = "DefaultAccount" - priority = -10 - -
[docs] def at_cmdset_creation(self): - "Populates the cmdset" - - # Account-specific commands - self.add(account.CmdOOCLook()) - self.add(account.CmdIC()) - self.add(account.CmdOOC()) - self.add(account.CmdCharCreate()) - self.add(account.CmdCharDelete()) - # self.add(account.CmdSessions()) - self.add(account.CmdWho()) - self.add(account.CmdOption()) - self.add(account.CmdQuit()) - self.add(account.CmdPassword()) - self.add(account.CmdColorTest()) - self.add(account.CmdQuell()) - self.add(account.CmdStyle()) - - # nicks - self.add(general.CmdNick()) - - # testing - self.add(building.CmdExamine()) - - # Help command - self.add(help.CmdHelp()) - - # system commands - self.add(system.CmdReload()) - self.add(system.CmdReset()) - self.add(system.CmdShutdown()) - self.add(system.CmdPy()) - - # Admin commands - self.add(admin.CmdNewPassword()) - - # Comm commands - self.add(comms.CmdAddCom()) - self.add(comms.CmdDelCom()) - self.add(comms.CmdAllCom()) - self.add(comms.CmdChannels()) - self.add(comms.CmdCdestroy()) - self.add(comms.CmdChannelCreate()) - self.add(comms.CmdClock()) - self.add(comms.CmdCBoot()) - self.add(comms.CmdCemit()) - self.add(comms.CmdCWho()) - self.add(comms.CmdCdesc()) - self.add(comms.CmdPage()) - self.add(comms.CmdIRC2Chan()) - self.add(comms.CmdIRCStatus()) - self.add(comms.CmdRSS2Chan()) - self.add(comms.CmdGrapevine2Chan())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/cmdset_character.html b/docs/0.9.5/_modules/evennia/commands/default/cmdset_character.html deleted file mode 100644 index fc5aa4610d..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/cmdset_character.html +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - - evennia.commands.default.cmdset_character — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.cmdset_character

-"""
-This module ties together all the commands default Character objects have
-available (i.e. IC commands). Note that some commands, such as
-communication-commands are instead put on the account level, in the
-Account cmdset. Account commands remain available also to Characters.
-"""
-from evennia.commands.cmdset import CmdSet
-from evennia.commands.default import general, help, admin, system
-from evennia.commands.default import building
-from evennia.commands.default import batchprocess
-
-
-
[docs]class CharacterCmdSet(CmdSet): - """ - Implements the default command set. - """ - - key = "DefaultCharacter" - priority = 0 - -
[docs] def at_cmdset_creation(self): - "Populates the cmdset" - - # The general commands - self.add(general.CmdLook()) - self.add(general.CmdHome()) - self.add(general.CmdInventory()) - self.add(general.CmdPose()) - self.add(general.CmdNick()) - self.add(general.CmdSetDesc()) - self.add(general.CmdGet()) - self.add(general.CmdDrop()) - self.add(general.CmdGive()) - self.add(general.CmdSay()) - self.add(general.CmdWhisper()) - self.add(general.CmdAccess()) - - # The help system - self.add(help.CmdHelp()) - self.add(help.CmdSetHelp()) - - # System commands - self.add(system.CmdPy()) - self.add(system.CmdScripts()) - self.add(system.CmdObjects()) - self.add(system.CmdAccounts()) - self.add(system.CmdService()) - self.add(system.CmdAbout()) - self.add(system.CmdTime()) - self.add(system.CmdServerLoad()) - # self.add(system.CmdPs()) - self.add(system.CmdTickers()) - - # Admin commands - self.add(admin.CmdBoot()) - self.add(admin.CmdBan()) - self.add(admin.CmdUnban()) - self.add(admin.CmdEmit()) - self.add(admin.CmdPerm()) - self.add(admin.CmdWall()) - self.add(admin.CmdForce()) - - # Building and world manipulation - self.add(building.CmdTeleport()) - self.add(building.CmdSetObjAlias()) - self.add(building.CmdListCmdSets()) - self.add(building.CmdWipe()) - self.add(building.CmdSetAttribute()) - self.add(building.CmdName()) - self.add(building.CmdDesc()) - self.add(building.CmdCpAttr()) - self.add(building.CmdMvAttr()) - self.add(building.CmdCopy()) - self.add(building.CmdFind()) - self.add(building.CmdOpen()) - self.add(building.CmdLink()) - self.add(building.CmdUnLink()) - self.add(building.CmdCreate()) - self.add(building.CmdDig()) - self.add(building.CmdTunnel()) - self.add(building.CmdDestroy()) - self.add(building.CmdExamine()) - self.add(building.CmdTypeclass()) - self.add(building.CmdLock()) - self.add(building.CmdScript()) - self.add(building.CmdSetHome()) - self.add(building.CmdTag()) - self.add(building.CmdSpawn()) - - # Batchprocessor commands - self.add(batchprocess.CmdBatchCommands()) - self.add(batchprocess.CmdBatchCode())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/cmdset_session.html b/docs/0.9.5/_modules/evennia/commands/default/cmdset_session.html deleted file mode 100644 index 4f7968c36b..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/cmdset_session.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - evennia.commands.default.cmdset_session — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.cmdset_session

-"""
-This module stores session-level commands.
-"""
-from evennia.commands.cmdset import CmdSet
-from evennia.commands.default import account
-
-
-
[docs]class SessionCmdSet(CmdSet): - """ - Sets up the unlogged cmdset. - """ - - key = "DefaultSession" - priority = -20 - -
[docs] def at_cmdset_creation(self): - "Populate the cmdset" - self.add(account.CmdSessions())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/cmdset_unloggedin.html b/docs/0.9.5/_modules/evennia/commands/default/cmdset_unloggedin.html deleted file mode 100644 index 86664caa55..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/cmdset_unloggedin.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - evennia.commands.default.cmdset_unloggedin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.cmdset_unloggedin

-"""
-This module describes the unlogged state of the default game.
-The setting STATE_UNLOGGED should be set to the python path
-of the state instance in this module.
-"""
-from evennia.commands.cmdset import CmdSet
-from evennia.commands.default import unloggedin
-
-
-
[docs]class UnloggedinCmdSet(CmdSet): - """ - Sets up the unlogged cmdset. - """ - - key = "DefaultUnloggedin" - priority = 0 - -
[docs] def at_cmdset_creation(self): - "Populate the cmdset" - self.add(unloggedin.CmdUnconnectedConnect()) - self.add(unloggedin.CmdUnconnectedCreate()) - self.add(unloggedin.CmdUnconnectedQuit()) - self.add(unloggedin.CmdUnconnectedLook()) - self.add(unloggedin.CmdUnconnectedHelp()) - self.add(unloggedin.CmdUnconnectedEncoding()) - self.add(unloggedin.CmdUnconnectedScreenreader()) - self.add(unloggedin.CmdUnconnectedInfo())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/comms.html b/docs/0.9.5/_modules/evennia/commands/default/comms.html deleted file mode 100644 index 56f6949652..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/comms.html +++ /dev/null @@ -1,1463 +0,0 @@ - - - - - - - - evennia.commands.default.comms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.comms

-"""
-Comsystem command module.
-
-Comm commands are OOC commands and intended to be made available to
-the Account at all times (they go into the AccountCmdSet). So we
-make sure to homogenize self.caller to always be the account object
-for easy handling.
-
-"""
-import hashlib
-import time
-from django.conf import settings
-from evennia.comms.models import ChannelDB, Msg
-from evennia.accounts.models import AccountDB
-from evennia.accounts import bots
-from evennia.comms.channelhandler import CHANNELHANDLER
-from evennia.locks.lockhandler import LockException
-from evennia.utils import create, logger, utils, evtable
-from evennia.utils.utils import make_iter, class_from_module
-
-COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
-CHANNEL_DEFAULT_TYPECLASS = class_from_module(
-    settings.BASE_CHANNEL_TYPECLASS, fallback=settings.FALLBACK_CHANNEL_TYPECLASS)
-
-
-# limit symbol import for API
-__all__ = (
-    "CmdAddCom",
-    "CmdDelCom",
-    "CmdAllCom",
-    "CmdChannels",
-    "CmdCdestroy",
-    "CmdCBoot",
-    "CmdCemit",
-    "CmdCWho",
-    "CmdChannelCreate",
-    "CmdClock",
-    "CmdCdesc",
-    "CmdPage",
-    "CmdIRC2Chan",
-    "CmdIRCStatus",
-    "CmdRSS2Chan",
-    "CmdGrapevine2Chan",
-)
-_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-
-
-def find_channel(caller, channelname, silent=False, noaliases=False):
-    """
-    Helper function for searching for a single channel with
-    some error handling.
-    """
-    channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname)
-    if not channels:
-        if not noaliases:
-            channels = [
-                chan
-                for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels()
-                if channelname in chan.aliases.all()
-            ]
-        if channels:
-            return channels[0]
-        if not silent:
-            caller.msg("Channel '%s' not found." % channelname)
-        return None
-    elif len(channels) > 1:
-        matches = ", ".join(["%s(%s)" % (chan.key, chan.id) for chan in channels])
-        if not silent:
-            caller.msg("Multiple channels match (be more specific): \n%s" % matches)
-        return None
-    return channels[0]
-
-
-
[docs]class CmdAddCom(COMMAND_DEFAULT_CLASS): - """ - add a channel alias and/or subscribe to a channel - - Usage: - addcom [alias=] <channel> - - Joins a given channel. If alias is given, this will allow you to - refer to the channel by this alias rather than the full channel - name. Subsequent calls of this command can be used to add multiple - aliases to an already joined channel. - """ - - key = "addcom" - aliases = ["aliaschan", "chanalias"] - help_category = "Comms" - locks = "cmd:not pperm(channel_banned)" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Implement the command""" - - caller = self.caller - args = self.args - account = caller - - if not args: - self.msg("Usage: addcom [alias =] channelname.") - return - - if self.rhs: - # rhs holds the channelname - channelname = self.rhs - alias = self.lhs - else: - channelname = self.args - alias = None - - channel = find_channel(caller, channelname) - if not channel: - # we use the custom search method to handle errors. - return - - # check permissions - if not channel.access(account, "listen"): - self.msg("%s: You are not allowed to listen to this channel." % channel.key) - return - - string = "" - if not channel.has_connection(account): - # we want to connect as well. - if not channel.connect(account): - # if this would have returned True, the account is connected - self.msg("%s: You are not allowed to join this channel." % channel.key) - return - else: - string += "You now listen to the channel %s. " % channel.key - else: - if channel.unmute(account): - string += "You unmute channel %s." % channel.key - else: - string += "You are already connected to channel %s." % channel.key - - if alias: - # create a nick and add it to the caller. - caller.nicks.add(alias, channel.key, category="channel") - string += " You can now refer to the channel %s with the alias '%s'." - self.msg(string % (channel.key, alias)) - else: - string += " No alias added." - self.msg(string)
- - -
[docs]class CmdDelCom(COMMAND_DEFAULT_CLASS): - """ - remove a channel alias and/or unsubscribe from channel - - Usage: - delcom <alias or channel> - delcom/all <channel> - - If the full channel name is given, unsubscribe from the - channel. If an alias is given, remove the alias but don't - unsubscribe. If the 'all' switch is used, remove all aliases - for that channel. - """ - - key = "delcom" - aliases = ["delaliaschan", "delchanalias"] - help_category = "Comms" - locks = "cmd:not perm(channel_banned)" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Implementing the command. """ - - caller = self.caller - account = caller - - if not self.args: - self.msg("Usage: delcom <alias or channel>") - return - ostring = self.args.lower() - - channel = find_channel(caller, ostring, silent=True, noaliases=True) - if channel: - # we have given a channel name - unsubscribe - if not channel.has_connection(account): - self.msg("You are not listening to that channel.") - return - chkey = channel.key.lower() - delnicks = "all" in self.switches - # find all nicks linked to this channel and delete them - if delnicks: - for nick in [ - nick - for nick in make_iter(caller.nicks.get(category="channel", return_obj=True)) - if nick and nick.pk and nick.value[3].lower() == chkey - ]: - nick.delete() - disconnect = channel.disconnect(account) - if disconnect: - wipednicks = " Eventual aliases were removed." if delnicks else "" - self.msg("You stop listening to channel '%s'.%s" % (channel.key, wipednicks)) - return - else: - # we are removing a channel nick - channame = caller.nicks.get(key=ostring, category="channel") - channel = find_channel(caller, channame, silent=True) - if not channel: - self.msg("No channel with alias '%s' was found." % ostring) - else: - if caller.nicks.get(ostring, category="channel"): - caller.nicks.remove(ostring, category="channel") - self.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key)) - else: - self.msg("You had no such alias defined for this channel.")
- - -
[docs]class CmdAllCom(COMMAND_DEFAULT_CLASS): - """ - perform admin operations on all channels - - Usage: - allcom [on | off | who | destroy] - - Allows the user to universally turn off or on all channels they are on, as - well as perform a 'who' for all channels they are on. Destroy deletes all - channels that you control. - - Without argument, works like comlist. - """ - - key = "allcom" - locks = "cmd: not pperm(channel_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Runs the function""" - - caller = self.caller - args = self.args - if not args: - self.execute_cmd("channels") - self.msg("(Usage: allcom on | off | who | destroy)") - return - - if args == "on": - # get names of all channels available to listen to - # and activate them all - channels = [ - chan - for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() - if chan.access(caller, "listen") - ] - for channel in channels: - self.execute_cmd("addcom %s" % channel.key) - elif args == "off": - # get names all subscribed channels and disconnect from them all - channels = CHANNEL_DEFAULT_TYPECLASS.objects.get_subscriptions(caller) - for channel in channels: - self.execute_cmd("delcom %s" % channel.key) - elif args == "destroy": - # destroy all channels you control - channels = [ - chan - for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() - if chan.access(caller, "control") - ] - for channel in channels: - self.execute_cmd("cdestroy %s" % channel.key) - elif args == "who": - # run a who, listing the subscribers on visible channels. - string = "\n|CChannel subscriptions|n" - channels = [ - chan - for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() - if chan.access(caller, "listen") - ] - if not channels: - string += "No channels." - for channel in channels: - string += "\n|w%s:|n\n %s" % (channel.key, channel.wholist) - self.msg(string.strip()) - else: - # wrong input - self.msg("Usage: allcom on | off | who | clear")
- - -
[docs]class CmdChannels(COMMAND_DEFAULT_CLASS): - """ - list all channels available to you - - Usage: - channels - clist - comlist - - Lists all channels available to you, whether you listen to them or not. - Use 'comlist' to only view your current channel subscriptions. - Use addcom/delcom to join and leave channels - """ - - key = "channels" - aliases = ["clist", "comlist", "chanlist", "channellist", "all channels"] - help_category = "Comms" - locks = "cmd: not pperm(channel_banned)" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Implement function""" - - caller = self.caller - - # all channels we have available to listen to - channels = [ - chan - for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() - if chan.access(caller, "listen") - ] - if not channels: - self.msg("No channels available.") - return - # all channel we are already subscribed to - subs = CHANNEL_DEFAULT_TYPECLASS.objects.get_subscriptions(caller) - - if self.cmdstring == "comlist": - # just display the subscribed channels with no extra info - comtable = self.styled_table( - "|wchannel|n", - "|wmy aliases|n", - "|wdescription|n", - align="l", - maxwidth=_DEFAULT_WIDTH, - ) - for chan in subs: - clower = chan.key.lower() - nicks = caller.nicks.get(category="channel", return_obj=True) - comtable.add_row( - *[ - "%s%s" - % ( - chan.key, - chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or "", - ), - "%s" - % ",".join( - nick.db_key - for nick in make_iter(nicks) - if nick and nick.value[3].lower() == clower - ), - chan.db.desc, - ] - ) - self.msg( - "\n|wChannel subscriptions|n (use |wchannels|n to list all," - " |waddcom|n/|wdelcom|n to sub/unsub):|n\n%s" % comtable - ) - else: - # full listing (of channels caller is able to listen to) - comtable = self.styled_table( - "|wsub|n", - "|wchannel|n", - "|wmy aliases|n", - "|wlocks|n", - "|wdescription|n", - maxwidth=_DEFAULT_WIDTH, - ) - for chan in channels: - clower = chan.key.lower() - nicks = caller.nicks.get(category="channel", return_obj=True) - nicks = nicks or [] - if chan not in subs: - substatus = "|rNo|n" - elif caller in chan.mutelist: - substatus = "|rMuted|n" - else: - substatus = "|gYes|n" - comtable.add_row( - *[ - substatus, - "%s%s" - % ( - chan.key, - chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or "", - ), - "%s" - % ",".join( - nick.db_key - for nick in make_iter(nicks) - if nick.value[3].lower() == clower - ), - str(chan.locks), - chan.db.desc, - ] - ) - comtable.reformat_column(0, width=9) - comtable.reformat_column(3, width=14) - self.msg( - "\n|wAvailable channels|n (use |wcomlist|n,|waddcom|n and |wdelcom|n" - " to manage subscriptions):\n%s" % comtable - )
- - -
[docs]class CmdCdestroy(COMMAND_DEFAULT_CLASS): - """ - destroy a channel you created - - Usage: - cdestroy <channel> - - Destroys a channel that you control. - """ - - key = "cdestroy" - help_category = "Comms" - locks = "cmd: not pperm(channel_banned)" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Destroy objects cleanly.""" - caller = self.caller - - if not self.args: - self.msg("Usage: cdestroy <channelname>") - return - channel = find_channel(caller, self.args) - if not channel: - self.msg("Could not find channel %s." % self.args) - return - if not channel.access(caller, "control"): - self.msg("You are not allowed to do that.") - return - channel_key = channel.key - message = "%s is being destroyed. Make sure to change your aliases." % channel_key - msgobj = create.create_message(caller, message, channel) - channel.msg(msgobj) - channel.delete() - CHANNELHANDLER.update() - self.msg("Channel '%s' was destroyed." % channel_key) - logger.log_sec( - "Channel Deleted: %s (Caller: %s, IP: %s)." - % (channel_key, caller, self.session.address) - )
- - -
[docs]class CmdCBoot(COMMAND_DEFAULT_CLASS): - """ - kick an account from a channel you control - - Usage: - cboot[/quiet] <channel> = <account> [:reason] - - Switch: - quiet - don't notify the channel - - Kicks an account or object from a channel you control. - - """ - - key = "cboot" - switch_options = ("quiet",) - locks = "cmd: not pperm(channel_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """implement the function""" - - if not self.args or not self.rhs: - string = "Usage: cboot[/quiet] <channel> = <account> [:reason]" - self.msg(string) - return - - channel = find_channel(self.caller, self.lhs) - if not channel: - return - reason = "" - if ":" in self.rhs: - accountname, reason = self.rhs.rsplit(":", 1) - searchstring = accountname.lstrip("*") - else: - searchstring = self.rhs.lstrip("*") - account = self.caller.search(searchstring, account=True) - if not account: - return - if reason: - reason = " (reason: %s)" % reason - if not channel.access(self.caller, "control"): - string = "You don't control this channel." - self.msg(string) - return - if not channel.subscriptions.has(account): - string = "Account %s is not connected to channel %s." % (account.key, channel.key) - self.msg(string) - return - if "quiet" not in self.switches: - string = "%s boots %s from channel.%s" % (self.caller, account.key, reason) - channel.msg(string) - # find all account's nicks linked to this channel and delete them - for nick in [ - nick - for nick in account.character.nicks.get(category="channel") or [] - if nick.value[3].lower() == channel.key - ]: - nick.delete() - # disconnect account - channel.disconnect(account) - CHANNELHANDLER.update() - logger.log_sec( - "Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s)." - % (account, channel, reason, self.caller, self.session.address) - )
- - -
[docs]class CmdCemit(COMMAND_DEFAULT_CLASS): - """ - send an admin message to a channel you control - - Usage: - cemit[/switches] <channel> = <message> - - Switches: - sendername - attach the sender's name before the message - quiet - don't echo the message back to sender - - Allows the user to broadcast a message over a channel as long as - they control it. It does not show the user's name unless they - provide the /sendername switch. - - """ - - key = "cemit" - aliases = ["cmsg"] - switch_options = ("sendername", "quiet") - locks = "cmd: not pperm(channel_banned) and pperm(Player)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Implement function""" - - if not self.args or not self.rhs: - string = "Usage: cemit[/switches] <channel> = <message>" - self.msg(string) - return - channel = find_channel(self.caller, self.lhs) - if not channel: - return - if not channel.access(self.caller, "control"): - string = "You don't control this channel." - self.msg(string) - return - message = self.rhs - if "sendername" in self.switches: - message = "%s: %s" % (self.caller.key, message) - channel.msg(message) - if "quiet" not in self.switches: - string = "Sent to channel %s: %s" % (channel.key, message) - self.msg(string)
- - -
[docs]class CmdCWho(COMMAND_DEFAULT_CLASS): - """ - show who is listening to a channel - - Usage: - cwho <channel> - - List who is connected to a given channel you have access to. - """ - - key = "cwho" - locks = "cmd: not pperm(channel_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """implement function""" - - if not self.args: - string = "Usage: cwho <channel>" - self.msg(string) - return - - channel = find_channel(self.caller, self.lhs) - if not channel: - return - if not channel.access(self.caller, "listen"): - string = "You can't access this channel." - self.msg(string) - return - string = "\n|CChannel subscriptions|n" - string += "\n|w%s:|n\n %s" % (channel.key, channel.wholist) - self.msg(string.strip())
- - -
[docs]class CmdChannelCreate(COMMAND_DEFAULT_CLASS): - """ - create a new channel - - Usage: - ccreate <new channel>[;alias;alias...] = description - - Creates a new channel owned by you. - """ - - key = "ccreate" - aliases = "channelcreate" - locks = "cmd:not pperm(channel_banned) and pperm(Player)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Implement the command""" - - caller = self.caller - - if not self.args: - self.msg("Usage ccreate <channelname>[;alias;alias..] = description") - return - - description = "" - - if self.rhs: - description = self.rhs - lhs = self.lhs - channame = lhs - aliases = None - if ";" in lhs: - channame, aliases = lhs.split(";", 1) - aliases = [alias.strip().lower() for alias in aliases.split(";")] - channel = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channame) - if channel: - self.msg("A channel with that name already exists.") - return - # Create and set the channel up - lockstring = "send:all();listen:all();control:id(%s)" % caller.id - new_chan = create.create_channel(channame.strip(), aliases, description, locks=lockstring) - new_chan.connect(caller) - CHANNELHANDLER.update() - self.msg("Created channel %s and connected to it." % new_chan.key)
- - -
[docs]class CmdClock(COMMAND_DEFAULT_CLASS): - """ - change channel locks of a channel you control - - Usage: - clock <channel> [= <lockstring>] - - Changes the lock access restrictions of a channel. If no - lockstring was given, view the current lock definitions. - """ - - key = "clock" - locks = "cmd:not pperm(channel_banned)" - aliases = ["clock"] - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """run the function""" - - if not self.args: - string = "Usage: clock channel [= lockstring]" - self.msg(string) - return - - channel = find_channel(self.caller, self.lhs) - if not channel: - return - if not self.rhs: - # no =, so just view the current locks - string = "Current locks on %s:" % channel.key - string = "%s\n %s" % (string, channel.locks) - self.msg(string) - return - # we want to add/change a lock. - if not channel.access(self.caller, "control"): - string = "You don't control this channel." - self.msg(string) - return - # Try to add the lock - try: - channel.locks.add(self.rhs) - except LockException as err: - self.msg(err) - return - string = "Lock(s) applied. " - string += "Current locks on %s:" % channel.key - string = "%s\n %s" % (string, channel.locks) - self.msg(string)
- - -
[docs]class CmdCdesc(COMMAND_DEFAULT_CLASS): - """ - describe a channel you control - - Usage: - cdesc <channel> = <description> - - Changes the description of the channel as shown in - channel lists. - """ - - key = "cdesc" - locks = "cmd:not pperm(channel_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Implement command""" - - caller = self.caller - - if not self.rhs: - self.msg("Usage: cdesc <channel> = <description>") - return - channel = find_channel(caller, self.lhs) - if not channel: - self.msg("Channel '%s' not found." % self.lhs) - return - # check permissions - if not channel.access(caller, "control"): - self.msg("You cannot admin this channel.") - return - # set the description - channel.db.desc = self.rhs - channel.save() - self.msg("Description of channel '%s' set to '%s'." % (channel.key, self.rhs))
- - -
[docs]class CmdPage(COMMAND_DEFAULT_CLASS): - """ - send a private message to another account - - Usage: - page[/switches] [<account>,<account>,... = <message>] - tell '' - page <number> - - Switch: - last - shows who you last messaged - list - show your last <number> of tells/pages (default) - - Send a message to target user (if online). If no - argument is given, you will get a list of your latest messages. - """ - - key = "page" - aliases = ["tell"] - switch_options = ("last", "list") - locks = "cmd:not pperm(page_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - -
[docs] def func(self): - """Implement function using the Msg methods""" - - # Since account_caller is set above, this will be an Account. - caller = self.caller - - # get the messages we've sent (not to channels) - pages_we_sent = Msg.objects.get_messages_by_sender(caller, exclude_channel_messages=True) - # get last messages we've got - pages_we_got = Msg.objects.get_messages_by_receiver(caller) - - if "last" in self.switches: - if pages_we_sent: - recv = ",".join(obj.key for obj in pages_we_sent[-1].receivers) - self.msg("You last paged |c%s|n:%s" % (recv, pages_we_sent[-1].message)) - return - else: - self.msg("You haven't paged anyone yet.") - return - - if not self.args or not self.rhs: - pages = pages_we_sent + pages_we_got - pages = sorted(pages, key=lambda page: page.date_created) - - number = 5 - if self.args: - try: - number = int(self.args) - except ValueError: - self.msg("Usage: tell [<account> = msg]") - return - - if len(pages) > number: - lastpages = pages[-number:] - else: - lastpages = pages - to_template = "|w{date}{clr} {sender}|nto{clr}{receiver}|n:> {message}" - from_template = "|w{date}{clr} {receiver}|nfrom{clr}{sender}|n:< {message}" - listing = [] - prev_selfsend = False - for page in lastpages: - multi_send = len(page.senders) > 1 - multi_recv = len(page.receivers) > 1 - sending = self.caller in page.senders - # self-messages all look like sends, so we assume they always - # come in close pairs and treat the second of the pair as the recv. - selfsend = sending and self.caller in page.receivers - if selfsend: - if prev_selfsend: - # this is actually a receive of a self-message - sending = False - prev_selfsend = False - else: - prev_selfsend = True - - clr = "|c" if sending else "|g" - - sender = f"|n,{clr}".join(obj.key for obj in page.senders) - receiver = f"|n,{clr}".join([obj.name for obj in page.receivers]) - if sending: - template = to_template - sender = f"{sender} " if multi_send else "" - receiver = f" {receiver}" if multi_recv else f" {receiver}" - else: - template = from_template - receiver = f"{receiver} " if multi_recv else "" - sender = f" {sender} " if multi_send else f" {sender}" - - listing.append( - template.format( - date=utils.datetime_format(page.date_created), - clr=clr, - sender=sender, - receiver=receiver, - message=page.message, - ) - ) - lastpages = "\n ".join(listing) - - if lastpages: - string = "Your latest pages:\n %s" % lastpages - else: - string = "You haven't paged anyone yet." - self.msg(string) - return - - # We are sending. Build a list of targets - - if not self.lhs: - # If there are no targets, then set the targets - # to the last person we paged. - if pages_we_sent: - receivers = pages_we_sent[-1].receivers - else: - self.msg("Who do you want to page?") - return - else: - receivers = self.lhslist - - recobjs = [] - for receiver in set(receivers): - if isinstance(receiver, str): - pobj = caller.search(receiver) - elif hasattr(receiver, "character"): - pobj = receiver - else: - self.msg("Who do you want to page?") - return - if pobj: - recobjs.append(pobj) - if not recobjs: - self.msg("Noone found to page.") - return - - header = "|wAccount|n |c%s|n |wpages:|n" % caller.key - message = self.rhs - - # if message begins with a :, we assume it is a 'page-pose' - if message.startswith(":"): - message = "%s %s" % (caller.key, message.strip(":").strip()) - - # create the persistent message object - create.create_message(caller, message, receivers=recobjs) - - # tell the accounts they got a message. - received = [] - rstrings = [] - for pobj in recobjs: - if not pobj.access(caller, "msg"): - rstrings.append("You are not allowed to page %s." % pobj) - continue - pobj.msg("%s %s" % (header, message)) - if hasattr(pobj, "sessions") and not pobj.sessions.count(): - received.append("|C%s|n" % pobj.name) - rstrings.append( - "%s is offline. They will see your message if they list their pages later." - % received[-1] - ) - else: - received.append("|c%s|n" % pobj.name) - if rstrings: - self.msg("\n".join(rstrings)) - self.msg("You paged %s with: '%s'." % (", ".join(received), message))
- - -def _list_bots(cmd): - """ - Helper function to produce a list of all IRC bots. - - Args: - cmd (Command): Instance of the Bot command. - Returns: - bots (str): A table of bots or an error message. - - """ - ircbots = [ - bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="ircbot-") - ] - if ircbots: - table = cmd.styled_table( - "|w#dbref|n", - "|wbotname|n", - "|wev-channel|n", - "|wirc-channel|n", - "|wSSL|n", - maxwidth=_DEFAULT_WIDTH, - ) - for ircbot in ircbots: - ircinfo = "%s (%s:%s)" % ( - ircbot.db.irc_channel, - ircbot.db.irc_network, - ircbot.db.irc_port, - ) - table.add_row( - "#%i" % ircbot.id, - ircbot.db.irc_botname, - ircbot.db.ev_channel, - ircinfo, - ircbot.db.irc_ssl, - ) - return table - else: - return "No irc bots found." - - -
[docs]class CmdIRC2Chan(COMMAND_DEFAULT_CLASS): - """ - Link an evennia channel to an external IRC channel - - Usage: - irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>[:typeclass] - irc2chan/delete botname|#dbid - - Switches: - /delete - this will delete the bot and remove the irc connection - to the channel. Requires the botname or #dbid as input. - /remove - alias to /delete - /disconnect - alias to /delete - /list - show all irc<->evennia mappings - /ssl - use an SSL-encrypted connection - - Example: - irc2chan myircchan = irc.dalnet.net 6667 #mychannel evennia-bot - irc2chan public = irc.freenode.net 6667 #evgaming #evbot:accounts.mybot.MyBot - - This creates an IRC bot that connects to a given IRC network and - channel. If a custom typeclass path is given, this will be used - instead of the default bot class. - The bot will relay everything said in the evennia channel to the - IRC channel and vice versa. The bot will automatically connect at - server start, so this command need only be given once. The - /disconnect switch will permanently delete the bot. To only - temporarily deactivate it, use the |wservices|n command instead. - Provide an optional bot class path to use a custom bot. - """ - - key = "irc2chan" - switch_options = ("delete", "remove", "disconnect", "list", "ssl") - locks = "cmd:serversetting(IRC_ENABLED) and pperm(Developer)" - help_category = "Comms" - -
[docs] def func(self): - """Setup the irc-channel mapping""" - - if not settings.IRC_ENABLED: - string = """IRC is not enabled. You need to activate it in game/settings.py.""" - self.msg(string) - return - - if "list" in self.switches: - # show all connections - self.msg(_list_bots(self)) - return - - if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches: - botname = "ircbot-%s" % self.lhs - matches = AccountDB.objects.filter(db_is_bot=True, username=botname) - dbref = utils.dbref(self.lhs) - if not matches and dbref: - # try dbref match - matches = AccountDB.objects.filter(db_is_bot=True, id=dbref) - if matches: - matches[0].delete() - self.msg("IRC connection destroyed.") - else: - self.msg("IRC connection/bot could not be removed, does it exist?") - return - - if not self.args or not self.rhs: - string = ( - "Usage: irc2chan[/switches] <evennia_channel> =" - " <ircnetwork> <port> <#irchannel> <botname>[:typeclass]" - ) - self.msg(string) - return - - channel = self.lhs - self.rhs = self.rhs.replace("#", " ") # to avoid Python comment issues - try: - irc_network, irc_port, irc_channel, irc_botname = [ - part.strip() for part in self.rhs.split(None, 4) - ] - irc_channel = "#%s" % irc_channel - except Exception: - string = "IRC bot definition '%s' is not valid." % self.rhs - self.msg(string) - return - - botclass = None - if ":" in irc_botname: - irc_botname, botclass = [part.strip() for part in irc_botname.split(":", 2)] - botname = "ircbot-%s" % irc_botname - # If path given, use custom bot otherwise use default. - botclass = botclass if botclass else bots.IRCBot - irc_ssl = "ssl" in self.switches - - # create a new bot - bot = AccountDB.objects.filter(username__iexact=botname) - if bot: - # re-use an existing bot - bot = bot[0] - if not bot.is_bot: - self.msg("Account '%s' already exists and is not a bot." % botname) - return - else: - try: - bot = create.create_account(botname, None, None, typeclass=botclass) - except Exception as err: - self.msg("|rError, could not create the bot:|n '%s'." % err) - return - bot.start( - ev_channel=channel, - irc_botname=irc_botname, - irc_channel=irc_channel, - irc_network=irc_network, - irc_port=irc_port, - irc_ssl=irc_ssl, - ) - self.msg("Connection created. Starting IRC bot.")
- - -
[docs]class CmdIRCStatus(COMMAND_DEFAULT_CLASS): - """ - Check and reboot IRC bot. - - Usage: - ircstatus [#dbref ping||nicklist||reconnect] - - If not given arguments, will return a list of all bots (like - irc2chan/list). The 'ping' argument will ping the IRC network to - see if the connection is still responsive. The 'nicklist' argument - (aliases are 'who' and 'users') will return a list of users on the - remote IRC channel. Finally, 'reconnect' will force the client to - disconnect and reconnect again. This may be a last resort if the - client has silently lost connection (this may happen if the remote - network experience network issues). During the reconnection - messages sent to either channel will be lost. - - """ - - key = "ircstatus" - locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builder))" - help_category = "Comms" - -
[docs] def func(self): - """Handles the functioning of the command.""" - - if not self.args: - self.msg(_list_bots(self)) - return - # should always be on the form botname option - args = self.args.split() - if len(args) != 2: - self.msg("Usage: ircstatus [#dbref ping||nicklist||reconnect]") - return - botname, option = args - if option not in ("ping", "users", "reconnect", "nicklist", "who"): - self.msg("Not a valid option.") - return - matches = None - if utils.dbref(botname): - matches = AccountDB.objects.filter(db_is_bot=True, id=utils.dbref(botname)) - if not matches: - self.msg( - "No matching IRC-bot found. Use ircstatus without arguments to list active bots." - ) - return - ircbot = matches[0] - channel = ircbot.db.irc_channel - network = ircbot.db.irc_network - port = ircbot.db.irc_port - chtext = "IRC bot '%s' on channel %s (%s:%s)" % ( - ircbot.db.irc_botname, - channel, - network, - port, - ) - if option == "ping": - # check connection by sending outself a ping through the server. - self.caller.msg("Pinging through %s." % chtext) - ircbot.ping(self.caller) - elif option in ("users", "nicklist", "who"): - # retrieve user list. The bot must handles the echo since it's - # an asynchronous call. - self.caller.msg("Requesting nicklist from %s (%s:%s)." % (channel, network, port)) - ircbot.get_nicklist(self.caller) - elif self.caller.locks.check_lockstring( - self.caller, "dummy:perm(ircstatus) or perm(Developer)" - ): - # reboot the client - self.caller.msg("Forcing a disconnect + reconnect of %s." % chtext) - ircbot.reconnect() - else: - self.caller.msg("You don't have permission to force-reload the IRC bot.")
- - -# RSS connection -
[docs]class CmdRSS2Chan(COMMAND_DEFAULT_CLASS): - """ - link an evennia channel to an external RSS feed - - Usage: - rss2chan[/switches] <evennia_channel> = <rss_url> - - Switches: - /disconnect - this will stop the feed and remove the connection to the - channel. - /remove - " - /list - show all rss->evennia mappings - - Example: - rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic - - This creates an RSS reader that connects to a given RSS feed url. Updates - will be echoed as a title and news link to the given channel. The rate of - updating is set with the RSS_UPDATE_INTERVAL variable in settings (default - is every 10 minutes). - - When disconnecting you need to supply both the channel and url again so as - to identify the connection uniquely. - """ - - key = "rss2chan" - switch_options = ("disconnect", "remove", "list") - locks = "cmd:serversetting(RSS_ENABLED) and pperm(Developer)" - help_category = "Comms" - -
[docs] def func(self): - """Setup the rss-channel mapping""" - - # checking we have all we need - if not settings.RSS_ENABLED: - string = """RSS is not enabled. You need to activate it in game/settings.py.""" - self.msg(string) - return - try: - import feedparser - - assert feedparser # to avoid checker error of not being used - except ImportError: - string = ( - "RSS requires python-feedparser (https://pypi.python.org/pypi/feedparser)." - " Install before continuing." - ) - self.msg(string) - return - - if "list" in self.switches: - # show all connections - rssbots = [ - bot - for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="rssbot-") - ] - if rssbots: - table = self.styled_table( - "|wdbid|n", - "|wupdate rate|n", - "|wev-channel", - "|wRSS feed URL|n", - border="cells", - maxwidth=_DEFAULT_WIDTH, - ) - for rssbot in rssbots: - table.add_row( - rssbot.id, rssbot.db.rss_rate, rssbot.db.ev_channel, rssbot.db.rss_url - ) - self.msg(table) - else: - self.msg("No rss bots found.") - return - - if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches: - botname = "rssbot-%s" % self.lhs - matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname) - if not matches: - # try dbref match - matches = AccountDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#")) - if matches: - matches[0].delete() - self.msg("RSS connection destroyed.") - else: - self.msg("RSS connection/bot could not be removed, does it exist?") - return - - if not self.args or not self.rhs: - string = "Usage: rss2chan[/switches] <evennia_channel> = <rss url>" - self.msg(string) - return - channel = self.lhs - url = self.rhs - - botname = "rssbot-%s" % url - bot = AccountDB.objects.filter(username__iexact=botname) - if bot: - # re-use existing bot - bot = bot[0] - if not bot.is_bot: - self.msg("Account '%s' already exists and is not a bot." % botname) - return - else: - # create a new bot - bot = create.create_account(botname, None, None, typeclass=bots.RSSBot) - bot.start(ev_channel=channel, rss_url=url, rss_rate=10) - self.msg("RSS reporter created. Fetching RSS.")
- - -
[docs]class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS): - """ - Link an Evennia channel to an exteral Grapevine channel - - Usage: - grapevine2chan[/switches] <evennia_channel> = <grapevine_channel> - grapevine2chan/disconnect <connection #id> - - Switches: - /list - (or no switch): show existing grapevine <-> Evennia - mappings and available grapevine chans - /remove - alias to disconnect - /delete - alias to disconnect - - Example: - grapevine2chan mygrapevine = gossip - - This creates a link between an in-game Evennia channel and an external - Grapevine channel. The game must be registered with the Grapevine network - (register at https://grapevine.haus) and the GRAPEVINE_* auth information - must be added to game settings. - """ - - key = "grapevine2chan" - switch_options = ("disconnect", "remove", "delete", "list") - locks = "cmd:serversetting(GRAPEVINE_ENABLED) and pperm(Developer)" - help_category = "Comms" - -
[docs] def func(self): - """Setup the Grapevine channel mapping""" - - if not settings.GRAPEVINE_ENABLED: - self.msg("Set GRAPEVINE_ENABLED=True in settings to enable.") - return - - if "list" in self.switches: - # show all connections - gwbots = [ - bot - for bot in AccountDB.objects.filter( - db_is_bot=True, username__startswith="grapevinebot-" - ) - ] - if gwbots: - table = self.styled_table( - "|wdbid|n", - "|wev-channel", - "|wgw-channel|n", - border="cells", - maxwidth=_DEFAULT_WIDTH, - ) - for gwbot in gwbots: - table.add_row(gwbot.id, gwbot.db.ev_channel, gwbot.db.grapevine_channel) - self.msg(table) - else: - self.msg("No grapevine bots found.") - return - - if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches: - botname = "grapevinebot-%s" % self.lhs - matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname) - - if not matches: - # try dbref match - matches = AccountDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#")) - if matches: - matches[0].delete() - self.msg("Grapevine connection destroyed.") - else: - self.msg("Grapevine connection/bot could not be removed, does it exist?") - return - - if not self.args or not self.rhs: - string = "Usage: grapevine2chan[/switches] <evennia_channel> = <grapevine_channel>" - self.msg(string) - return - - channel = self.lhs - grapevine_channel = self.rhs - - botname = "grapewinebot-%s-%s" % (channel, grapevine_channel) - bot = AccountDB.objects.filter(username__iexact=botname) - if bot: - # re-use existing bot - bot = bot[0] - if not bot.is_bot: - self.msg("Account '%s' already exists and is not a bot." % botname) - return - else: - self.msg("Reusing bot '%s' (%s)" % (botname, bot.dbref)) - else: - # create a new bot - bot = create.create_account(botname, None, None, typeclass=bots.GrapevineBot) - - bot.start(ev_channel=channel, grapevine_channel=grapevine_channel) - self.msg(f"Grapevine connection created {channel} <-> {grapevine_channel}.")
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/general.html b/docs/0.9.5/_modules/evennia/commands/default/general.html deleted file mode 100644 index 0e59dd6dd6..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/general.html +++ /dev/null @@ -1,837 +0,0 @@ - - - - - - - - evennia.commands.default.general — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.general

-"""
-General Character commands usually available to all characters
-"""
-import re
-from django.conf import settings
-from evennia.utils import utils, evtable
-from evennia.typeclasses.attributes import NickTemplateInvalid
-
-COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-# limit symbol import for API
-__all__ = (
-    "CmdHome",
-    "CmdLook",
-    "CmdNick",
-    "CmdInventory",
-    "CmdSetDesc",
-    "CmdGet",
-    "CmdDrop",
-    "CmdGive",
-    "CmdSay",
-    "CmdWhisper",
-    "CmdPose",
-    "CmdAccess",
-)
-
-
-
[docs]class CmdHome(COMMAND_DEFAULT_CLASS): - """ - move to your character's home location - - Usage: - home - - Teleports you to your home location. - """ - - key = "home" - locks = "cmd:perm(home) or perm(Builder)" - arg_regex = r"$" - -
[docs] def func(self): - """Implement the command""" - caller = self.caller - home = caller.home - if not home: - caller.msg("You have no home!") - elif home == caller.location: - caller.msg("You are already home!") - else: - caller.msg("There's no place like home ...") - caller.move_to(home)
- - -
[docs]class CmdLook(COMMAND_DEFAULT_CLASS): - """ - look at location or object - - Usage: - look - look <obj> - look *<account> - - Observes your location or objects in your vicinity. - """ - - key = "look" - aliases = ["l", "ls"] - locks = "cmd:all()" - arg_regex = r"\s|$" - -
[docs] def func(self): - """ - Handle the looking. - """ - caller = self.caller - if not self.args: - target = caller.location - if not target: - caller.msg("You have no location to look at!") - return - else: - target = caller.search(self.args) - if not target: - return - self.msg((caller.at_look(target), {"type": "look"}), options=None)
- - -
[docs]class CmdNick(COMMAND_DEFAULT_CLASS): - """ - define a personal alias/nick by defining a string to - match and replace it with another on the fly - - Usage: - nick[/switches] <string> [= [replacement_string]] - nick[/switches] <template> = <replacement_template> - nick/delete <string> or number - nicks - - Switches: - inputline - replace on the inputline (default) - object - replace on object-lookup - account - replace on account-lookup - list - show all defined aliases (also "nicks" works) - delete - remove nick by index in /list - clearall - clear all nicks - - Examples: - nick hi = say Hello, I'm Sarah! - nick/object tom = the tall man - nick build $1 $2 = create/drop $1;$2 - nick tell $1 $2=page $1=$2 - nick tm?$1=page tallman=$1 - nick tm\=$1=page tallman=$1 - - A 'nick' is a personal string replacement. Use $1, $2, ... to catch arguments. - Put the last $-marker without an ending space to catch all remaining text. You - can also use unix-glob matching for the left-hand side <string>: - - * - matches everything - ? - matches 0 or 1 single characters - [abcd] - matches these chars in any order - [!abcd] - matches everything not among these chars - \= - escape literal '=' you want in your <string> - - Note that no objects are actually renamed or changed by this command - your nicks - are only available to you. If you want to permanently add keywords to an object - for everyone to use, you need build privileges and the alias command. - - """ - - key = "nick" - switch_options = ("inputline", "object", "account", "list", "delete", "clearall") - aliases = ["nickname", "nicks"] - locks = "cmd:all()" - -
[docs] def parse(self): - """ - Support escaping of = with \= - """ - super(CmdNick, self).parse() - args = (self.lhs or "") + (" = %s" % self.rhs if self.rhs else "") - parts = re.split(r"(?<!\\)=", args, 1) - self.rhs = None - if len(parts) < 2: - self.lhs = parts[0].strip() - else: - self.lhs, self.rhs = [part.strip() for part in parts] - self.lhs = self.lhs.replace("\=", "=")
- -
[docs] def func(self): - """Create the nickname""" - - def _cy(string): - "add color to the special markers" - return re.sub(r"(\$[0-9]+|\*|\?|\[.+?\])", r"|Y\1|n", string) - - caller = self.caller - switches = self.switches - nicktypes = [switch for switch in switches if switch in ("object", "account", "inputline")] - specified_nicktype = bool(nicktypes) - nicktypes = nicktypes if specified_nicktype else ["inputline"] - - nicklist = ( - utils.make_iter(caller.nicks.get(category="inputline", return_obj=True) or []) - + utils.make_iter(caller.nicks.get(category="object", return_obj=True) or []) - + utils.make_iter(caller.nicks.get(category="account", return_obj=True) or []) - ) - - if "list" in switches or self.cmdstring in ("nicks",): - - if not nicklist: - string = "|wNo nicks defined.|n" - else: - table = self.styled_table("#", "Type", "Nick match", "Replacement") - for inum, nickobj in enumerate(nicklist): - _, _, nickvalue, replacement = nickobj.value - table.add_row( - str(inum + 1), nickobj.db_category, _cy(nickvalue), _cy(replacement) - ) - string = "|wDefined Nicks:|n\n%s" % table - caller.msg(string) - return - - if "clearall" in switches: - caller.nicks.clear() - caller.account.nicks.clear() - caller.msg("Cleared all nicks.") - return - - if "delete" in switches or "del" in switches: - if not self.args or not self.lhs: - caller.msg("usage nick/delete <nick> or <#num> ('nicks' for list)") - return - # see if a number was given - arg = self.args.lstrip("#") - oldnicks = [] - if arg.isdigit(): - # we are given a index in nicklist - delindex = int(arg) - if 0 < delindex <= len(nicklist): - oldnicks.append(nicklist[delindex - 1]) - else: - caller.msg("Not a valid nick index. See 'nicks' for a list.") - return - else: - if not specified_nicktype: - nicktypes = ("object", "account", "inputline") - for nicktype in nicktypes: - oldnicks.append(caller.nicks.get(arg, category=nicktype, return_obj=True)) - - oldnicks = [oldnick for oldnick in oldnicks if oldnick] - if oldnicks: - for oldnick in oldnicks: - nicktype = oldnick.category - nicktypestr = "%s-nick" % nicktype.capitalize() - _, _, old_nickstring, old_replstring = oldnick.value - caller.nicks.remove(old_nickstring, category=nicktype) - caller.msg( - "%s removed: '|w%s|n' -> |w%s|n." - % (nicktypestr, old_nickstring, old_replstring) - ) - else: - caller.msg("No matching nicks to remove.") - return - - if not self.rhs and self.lhs: - # check what a nick is set to - strings = [] - if not specified_nicktype: - nicktypes = ("object", "account", "inputline") - for nicktype in nicktypes: - nicks = [ - nick - for nick in utils.make_iter( - caller.nicks.get(category=nicktype, return_obj=True) - ) - if nick - ] - for nick in nicks: - _, _, nick, repl = nick.value - if nick.startswith(self.lhs): - strings.append( - "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl) - ) - if strings: - caller.msg("\n".join(strings)) - else: - caller.msg("No nicks found matching '{}'".format(self.lhs)) - return - - if not self.rhs and self.lhs: - # check what a nick is set to - strings = [] - if not specified_nicktype: - nicktypes = ("object", "account", "inputline") - for nicktype in nicktypes: - if nicktype == "account": - obj = account - else: - obj = caller - nicks = utils.make_iter(obj.nicks.get(category=nicktype, return_obj=True)) - for nick in nicks: - _, _, nick, repl = nick.value - if nick.startswith(self.lhs): - strings.append( - "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl) - ) - if strings: - caller.msg("\n".join(strings)) - else: - caller.msg("No nicks found matching '{}'".format(self.lhs)) - return - - if not self.rhs and self.lhs: - # check what a nick is set to - strings = [] - if not specified_nicktype: - nicktypes = ("object", "account", "inputline") - for nicktype in nicktypes: - if nicktype == "account": - obj = account - else: - obj = caller - nicks = utils.make_iter(obj.nicks.get(category=nicktype, return_obj=True)) - for nick in nicks: - _, _, nick, repl = nick.value - if nick.startswith(self.lhs): - strings.append( - "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl) - ) - if strings: - caller.msg("\n".join(strings)) - else: - caller.msg("No nicks found matching '{}'".format(self.lhs)) - return - - if not self.args or not self.lhs: - caller.msg("Usage: nick[/switches] nickname = [realname]") - return - - # setting new nicks - - nickstring = self.lhs - replstring = self.rhs - - if replstring == nickstring: - caller.msg("No point in setting nick same as the string to replace...") - return - - # check so we have a suitable nick type - errstring = "" - string = "" - for nicktype in nicktypes: - nicktypestr = "%s-nick" % nicktype.capitalize() - old_nickstring = None - old_replstring = None - - oldnick = caller.nicks.get(key=nickstring, category=nicktype, return_obj=True) - if oldnick: - _, _, old_nickstring, old_replstring = oldnick.value - if replstring: - # creating new nick - errstring = "" - if oldnick: - if replstring == old_replstring: - string += "\nIdentical %s already set." % nicktypestr.lower() - else: - string += "\n%s '|w%s|n' updated to map to '|w%s|n'." % ( - nicktypestr, - old_nickstring, - replstring, - ) - else: - string += "\n%s '|w%s|n' mapped to '|w%s|n'." % ( - nicktypestr, - nickstring, - replstring, - ) - try: - caller.nicks.add(nickstring, replstring, category=nicktype) - except NickTemplateInvalid: - caller.msg( - "You must use the same $-markers both in the nick and in the replacement." - ) - return - elif old_nickstring and old_replstring: - # just looking at the nick - string += "\n%s '|w%s|n' maps to '|w%s|n'." % ( - nicktypestr, - old_nickstring, - old_replstring, - ) - errstring = "" - string = errstring if errstring else string - caller.msg(_cy(string))
- - -
[docs]class CmdInventory(COMMAND_DEFAULT_CLASS): - """ - view inventory - - Usage: - inventory - inv - - Shows your inventory. - """ - - key = "inventory" - aliases = ["inv", "i"] - locks = "cmd:all()" - arg_regex = r"$" - -
[docs] def func(self): - """check inventory""" - items = self.caller.contents - if not items: - string = "You are not carrying anything." - else: - from evennia.utils.ansi import raw as raw_ansi - - table = self.styled_table(border="header") - for item in items: - table.add_row( - f"|C{item.name}|n", - "{}|n".format(utils.crop(raw_ansi(item.db.desc), width=50) or ""), - ) - string = f"|wYou are carrying:\n{table}" - self.caller.msg(string)
- - -
[docs]class CmdGet(COMMAND_DEFAULT_CLASS): - """ - pick up something - - Usage: - get <obj> - - Picks up an object from your location and puts it in - your inventory. - """ - - key = "get" - aliases = "grab" - locks = "cmd:all()" - arg_regex = r"\s|$" - -
[docs] def func(self): - """implements the command.""" - - caller = self.caller - - if not self.args: - caller.msg("Get what?") - return - obj = caller.search(self.args, location=caller.location) - if not obj: - return - if caller == obj: - caller.msg("You can't get yourself.") - return - if not obj.access(caller, "get"): - if obj.db.get_err_msg: - caller.msg(obj.db.get_err_msg) - else: - caller.msg("You can't get that.") - return - - # calling at_before_get hook method - if not obj.at_before_get(caller): - return - - success = obj.move_to(caller, quiet=True) - if not success: - caller.msg("This can't be picked up.") - else: - caller.msg("You pick up %s." % obj.name) - caller.location.msg_contents( - "%s picks up %s." % (caller.name, obj.name), exclude=caller - ) - # calling at_get hook method - obj.at_get(caller)
- - -
[docs]class CmdDrop(COMMAND_DEFAULT_CLASS): - """ - drop something - - Usage: - drop <obj> - - Lets you drop an object from your inventory into the - location you are currently in. - """ - - key = "drop" - locks = "cmd:all()" - arg_regex = r"\s|$" - -
[docs] def func(self): - """Implement command""" - - caller = self.caller - if not self.args: - caller.msg("Drop what?") - return - - # Because the DROP command by definition looks for items - # in inventory, call the search function using location = caller - obj = caller.search( - self.args, - location=caller, - nofound_string="You aren't carrying %s." % self.args, - multimatch_string="You carry more than one %s:" % self.args, - ) - if not obj: - return - - # Call the object script's at_before_drop() method. - if not obj.at_before_drop(caller): - return - - success = obj.move_to(caller.location, quiet=True) - if not success: - caller.msg("This couldn't be dropped.") - else: - caller.msg("You drop %s." % (obj.name,)) - caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller) - # Call the object script's at_drop() method. - obj.at_drop(caller)
- - -
[docs]class CmdGive(COMMAND_DEFAULT_CLASS): - """ - give away something to someone - - Usage: - give <inventory obj> <to||=> <target> - - Gives an items from your inventory to another character, - placing it in their inventory. - """ - - key = "give" - rhs_split = ("=", " to ") # Prefer = delimiter, but allow " to " usage. - locks = "cmd:all()" - arg_regex = r"\s|$" - -
[docs] def func(self): - """Implement give""" - - caller = self.caller - if not self.args or not self.rhs: - caller.msg("Usage: give <inventory object> = <target>") - return - to_give = caller.search( - self.lhs, - location=caller, - nofound_string="You aren't carrying %s." % self.lhs, - multimatch_string="You carry more than one %s:" % self.lhs, - ) - target = caller.search(self.rhs) - if not (to_give and target): - return - if target == caller: - caller.msg("You keep %s to yourself." % to_give.key) - return - if not to_give.location == caller: - caller.msg("You are not holding %s." % to_give.key) - return - - # calling at_before_give hook method - if not to_give.at_before_give(caller, target): - return - - # give object - success = to_give.move_to(target, quiet=True) - if not success: - caller.msg("This could not be given.") - else: - caller.msg("You give %s to %s." % (to_give.key, target.key)) - target.msg("%s gives you %s." % (caller.key, to_give.key)) - # Call the object script's at_give() method. - to_give.at_give(caller, target)
- - -
[docs]class CmdSetDesc(COMMAND_DEFAULT_CLASS): - """ - describe yourself - - Usage: - setdesc <description> - - Add a description to yourself. This - will be visible to people when they - look at you. - """ - - key = "setdesc" - locks = "cmd:all()" - arg_regex = r"\s|$" - -
[docs] def func(self): - """add the description""" - - if not self.args: - self.caller.msg("You must add a description.") - return - - self.caller.db.desc = self.args.strip() - self.caller.msg("You set your description.")
- - -
[docs]class CmdSay(COMMAND_DEFAULT_CLASS): - """ - speak as your character - - Usage: - say <message> - - Talk to those in your current location. - """ - - key = "say" - aliases = ['"', "'"] - locks = "cmd:all()" - -
[docs] def func(self): - """Run the say command""" - - caller = self.caller - - if not self.args: - caller.msg("Say what?") - return - - speech = self.args - - # Calling the at_before_say hook on the character - speech = caller.at_before_say(speech) - - # If speech is empty, stop here - if not speech: - return - - # Call the at_after_say hook on the character - caller.at_say(speech, msg_self=True)
- - -
[docs]class CmdWhisper(COMMAND_DEFAULT_CLASS): - """ - Speak privately as your character to another - - Usage: - whisper <character> = <message> - whisper <char1>, <char2> = <message> - - Talk privately to one or more characters in your current location, without - others in the room being informed. - """ - - key = "whisper" - locks = "cmd:all()" - -
[docs] def func(self): - """Run the whisper command""" - - caller = self.caller - - if not self.lhs or not self.rhs: - caller.msg("Usage: whisper <character> = <message>") - return - - receivers = [recv.strip() for recv in self.lhs.split(",")] - - receivers = [caller.search(receiver) for receiver in set(receivers)] - receivers = [recv for recv in receivers if recv] - - speech = self.rhs - # If the speech is empty, abort the command - if not speech or not receivers: - return - - # Call a hook to change the speech before whispering - speech = caller.at_before_say(speech, whisper=True, receivers=receivers) - - # no need for self-message if we are whispering to ourselves (for some reason) - msg_self = None if caller in receivers else True - caller.at_say(speech, msg_self=msg_self, receivers=receivers, whisper=True)
- - -
[docs]class CmdPose(COMMAND_DEFAULT_CLASS): - """ - strike a pose - - Usage: - pose <pose text> - pose's <pose text> - - Example: - pose is standing by the wall, smiling. - -> others will see: - Tom is standing by the wall, smiling. - - Describe an action being taken. The pose text will - automatically begin with your name. - """ - - key = "pose" - aliases = [":", "emote"] - locks = "cmd:all()" - -
[docs] def parse(self): - """ - Custom parse the cases where the emote - starts with some special letter, such - as 's, at which we don't want to separate - the caller's name and the emote with a - space. - """ - args = self.args - if args and not args[0] in ["'", ",", ":"]: - args = " %s" % args.strip() - self.args = args
- -
[docs] def func(self): - """Hook function""" - if not self.args: - msg = "What do you want to do?" - self.caller.msg(msg) - else: - msg = "%s%s" % (self.caller.name, self.args) - self.caller.location.msg_contents(text=(msg, {"type": "pose"}), from_obj=self.caller)
- - -
[docs]class CmdAccess(COMMAND_DEFAULT_CLASS): - """ - show your current game access - - Usage: - access - - This command shows you the permission hierarchy and - which permission groups you are a member of. - """ - - key = "access" - aliases = ["groups", "hierarchy"] - locks = "cmd:all()" - arg_regex = r"$" - -
[docs] def func(self): - """Load the permission groups""" - - caller = self.caller - hierarchy_full = settings.PERMISSION_HIERARCHY - string = "\n|wPermission Hierarchy|n (climbing):\n %s" % ", ".join(hierarchy_full) - - if self.caller.account.is_superuser: - cperms = "<Superuser>" - pperms = "<Superuser>" - else: - cperms = ", ".join(caller.permissions.all()) - pperms = ", ".join(caller.account.permissions.all()) - - string += "\n|wYour access|n:" - string += "\nCharacter |c%s|n: %s" % (caller.key, cperms) - if hasattr(caller, "account"): - string += "\nAccount |c%s|n: %s" % (caller.account.key, pperms) - caller.msg(string)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/help.html b/docs/0.9.5/_modules/evennia/commands/default/help.html deleted file mode 100644 index 02e9d5331b..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/help.html +++ /dev/null @@ -1,606 +0,0 @@ - - - - - - - - evennia.commands.default.help — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.help

-"""
-The help command. The basic idea is that help texts for commands
-are best written by those that write the commands - the admins. So
-command-help is all auto-loaded and searched from the current command
-set. The normal, database-tied help system is used for collaborative
-creation of other help topics such as RP help or game-world aides.
-"""
-
-from django.conf import settings
-from collections import defaultdict
-from evennia.utils.utils import fill, dedent
-from evennia.commands.command import Command
-from evennia.help.models import HelpEntry
-from evennia.utils import create, evmore
-from evennia.utils.eveditor import EvEditor
-from evennia.utils.utils import string_suggestions, class_from_module
-
-COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
-HELP_MORE = settings.HELP_MORE
-CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
-
-# limit symbol import for API
-__all__ = ("CmdHelp", "CmdSetHelp")
-_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-_SEP = "|C" + "-" * _DEFAULT_WIDTH + "|n"
-
-
-
[docs]class CmdHelp(COMMAND_DEFAULT_CLASS): - """ - View help or a list of topics - - Usage: - help <topic or command> - help list - help all - - This will search for help on commands and other - topics related to the game. - """ - - key = "help" - aliases = ["?"] - locks = "cmd:all()" - arg_regex = r"\s|$" - - # this is a special cmdhandler flag that makes the cmdhandler also pack - # the current cmdset with the call to self.func(). - return_cmdset = True - - # Help messages are wrapped in an EvMore call (unless using the webclient - # with separate help popups) If you want to avoid this, simply add - # 'HELP_MORE = False' in your settings/conf/settings.py - help_more = HELP_MORE - - # suggestion cutoff, between 0 and 1 (1 => perfect match) - suggestion_cutoff = 0.6 - - # number of suggestions (set to 0 to remove suggestions from help) - suggestion_maxnum = 5 - -
[docs] def msg_help(self, text): - """ - messages text to the caller, adding an extra oob argument to indicate - that this is a help command result and could be rendered in a separate - help window - """ - if type(self).help_more: - usemore = True - - if self.session and self.session.protocol_key in ("websocket", "ajax/comet"): - try: - options = self.account.db._saved_webclient_options - if options and options["helppopup"]: - usemore = False - except KeyError: - pass - - if usemore: - evmore.msg(self.caller, text, session=self.session) - return - - self.msg(text=(text, {"type": "help"}))
- -
[docs] @staticmethod - def format_help_entry(title, help_text, aliases=None, suggested=None): - """ - This visually formats the help entry. - This method can be overriden to customize the way a help - entry is displayed. - - Args: - title (str): the title of the help entry. - help_text (str): the text of the help entry. - aliases (list of str or None): the list of aliases. - suggested (list of str or None): suggested reading. - - Returns the formatted string, ready to be sent. - - """ - string = _SEP + "\n" - if title: - string += "|CHelp for |w%s|n" % title - if aliases: - string += " |C(aliases: %s|C)|n" % ("|C,|n ".join("|w%s|n" % ali for ali in aliases)) - if help_text: - string += "\n%s" % dedent(help_text.rstrip()) - if suggested: - string += "\n\n|CSuggested:|n " - string += "%s" % fill("|C,|n ".join("|w%s|n" % sug for sug in suggested)) - string.strip() - string += "\n" + _SEP - return string
- -
[docs] @staticmethod - def format_help_list(hdict_cmds, hdict_db): - """ - Output a category-ordered list. The input are the - pre-loaded help files for commands and database-helpfiles - respectively. You can override this method to return a - custom display of the list of commands and topics. - """ - string = "" - if hdict_cmds and any(hdict_cmds.values()): - string += "\n" + _SEP + "\n |CCommand help entries|n\n" + _SEP - for category in sorted(hdict_cmds.keys()): - string += "\n |w%s|n:\n" % (str(category).title()) - string += "|G" + fill("|C, |G".join(sorted(hdict_cmds[category]))) + "|n" - if hdict_db and any(hdict_db.values()): - string += "\n\n" + _SEP + "\n\r |COther help entries|n\n" + _SEP - for category in sorted(hdict_db.keys()): - string += "\n\r |w%s|n:\n" % (str(category).title()) - string += ( - "|G" - + fill(", ".join(sorted([str(topic) for topic in hdict_db[category]]))) - + "|n" - ) - return string
- -
[docs] def check_show_help(self, cmd, caller): - """ - Helper method. If this return True, the given cmd - auto-help will be viewable in the help listing. - Override this to easily select what is shown to - the account. Note that only commands available - in the caller's merged cmdset are available. - - Args: - cmd (Command): Command class from the merged cmdset - caller (Character, Account or Session): The current caller - executing the help command. - - """ - # return only those with auto_help set and passing the cmd: lock - return cmd.auto_help and cmd.access(caller)
- -
[docs] def should_list_cmd(self, cmd, caller): - """ - Should the specified command appear in the help table? - - This method only checks whether a specified command should - appear in the table of topics/commands. The command can be - used by the caller (see the 'check_show_help' method) and - the command will still be available, for instance, if a - character type 'help name of the command'. However, if - you return False, the specified command will not appear in - the table. This is sometimes useful to "hide" commands in - the table, but still access them through the help system. - - Args: - cmd: the command to be tested. - caller: the caller of the help system. - - Return: - True: the command should appear in the table. - False: the command shouldn't appear in the table. - - """ - return cmd.access(caller, "view", default=True)
- -
[docs] def parse(self): - """ - input is a string containing the command or topic to match. - """ - self.original_args = self.args.strip() - self.args = self.args.strip().lower()
- -
[docs] def func(self): - """ - Run the dynamic help entry creator. - """ - query, cmdset = self.args, self.cmdset - caller = self.caller - - suggestion_cutoff = self.suggestion_cutoff - suggestion_maxnum = self.suggestion_maxnum - - if not query: - query = "all" - - # removing doublets in cmdset, caused by cmdhandler - # having to allow doublet commands to manage exits etc. - cmdset.make_unique(caller) - - # retrieve all available commands and database topics - all_cmds = [cmd for cmd in cmdset if self.check_show_help(cmd, caller)] - all_topics = [ - topic for topic in HelpEntry.objects.all() if topic.access(caller, "view", default=True) - ] - all_categories = list( - set( - [cmd.help_category.lower() for cmd in all_cmds] - + [topic.help_category.lower() for topic in all_topics] - ) - ) - - if query in ("list", "all"): - # we want to list all available help entries, grouped by category - hdict_cmd = defaultdict(list) - hdict_topic = defaultdict(list) - # create the dictionaries {category:[topic, topic ...]} required by format_help_list - # Filter commands that should be reached by the help - # system, but not be displayed in the table, or be displayed differently. - for cmd in all_cmds: - if self.should_list_cmd(cmd, caller): - key = ( - cmd.auto_help_display_key - if hasattr(cmd, "auto_help_display_key") - else cmd.key - ) - hdict_cmd[cmd.help_category].append(key) - [hdict_topic[topic.help_category].append(topic.key) for topic in all_topics] - # report back - self.msg_help(self.format_help_list(hdict_cmd, hdict_topic)) - return - - # Try to access a particular command - - # build vocabulary of suggestions and rate them by string similarity. - suggestions = None - if suggestion_maxnum > 0: - vocabulary = ( - [cmd.key for cmd in all_cmds if cmd] - + [topic.key for topic in all_topics] - + all_categories - ) - [vocabulary.extend(cmd.aliases) for cmd in all_cmds] - suggestions = [ - sugg - for sugg in string_suggestions( - query, set(vocabulary), cutoff=suggestion_cutoff, maxnum=suggestion_maxnum - ) - if sugg != query - ] - if not suggestions: - suggestions = [ - sugg for sugg in vocabulary if sugg != query and sugg.startswith(query) - ] - - # try an exact command auto-help match - match = [cmd for cmd in all_cmds if cmd == query] - - if not match: - # try an inexact match with prefixes stripped from query and cmds - _query = query[1:] if query[0] in CMD_IGNORE_PREFIXES else query - - match = [ - cmd - for cmd in all_cmds - for m in cmd._matchset - if m == _query or m[0] in CMD_IGNORE_PREFIXES and m[1:] == _query - ] - - if len(match) == 1: - cmd = match[0] - key = cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key - formatted = self.format_help_entry( - key, cmd.get_help(caller, cmdset), aliases=cmd.aliases, suggested=suggestions, - ) - self.msg_help(formatted) - return - - # try an exact database help entry match - match = list(HelpEntry.objects.find_topicmatch(query, exact=True)) - if len(match) == 1: - formatted = self.format_help_entry( - match[0].key, - match[0].entrytext, - aliases=match[0].aliases.all(), - suggested=suggestions, - ) - self.msg_help(formatted) - return - - # try to see if a category name was entered - if query in all_categories: - self.msg_help( - self.format_help_list( - { - query: [ - cmd.auto_help_display_key - if hasattr(cmd, "auto_help_display_key") - else cmd.key - for cmd in all_cmds - if cmd.help_category == query - ] - }, - {query: [topic.key for topic in all_topics if topic.help_category == query]}, - ) - ) - return - - # no exact matches found. Just give suggestions. - self.msg( - self.format_help_entry( - "", f"No help entry found for '{query}'", None, suggested=suggestions - ), - options={"type": "help"}, - )
- - -def _loadhelp(caller): - entry = caller.db._editing_help - if entry: - return entry.entrytext - else: - return "" - - -def _savehelp(caller, buffer): - entry = caller.db._editing_help - caller.msg("Saved help entry.") - if entry: - entry.entrytext = buffer - - -def _quithelp(caller): - caller.msg("Closing the editor.") - del caller.db._editing_help - - -
[docs]class CmdSetHelp(COMMAND_DEFAULT_CLASS): - """ - Edit the help database. - - Usage: - help[/switches] <topic>[[;alias;alias][,category[,locks]] [= <text>] - - Switches: - edit - open a line editor to edit the topic's help text. - replace - overwrite existing help topic. - append - add text to the end of existing topic with a newline between. - extend - as append, but don't add a newline. - delete - remove help topic. - - Examples: - sethelp throw = This throws something at ... - sethelp/append pickpocketing,Thievery = This steals ... - sethelp/replace pickpocketing, ,attr(is_thief) = This steals ... - sethelp/edit thievery - - This command manipulates the help database. A help entry can be created, - appended/merged to and deleted. If you don't assign a category, the - "General" category will be used. If no lockstring is specified, default - is to let everyone read the help file. - - """ - - key = "sethelp" - switch_options = ("edit", "replace", "append", "extend", "delete") - locks = "cmd:perm(Helper)" - help_category = "Building" - -
[docs] def func(self): - """Implement the function""" - - switches = self.switches - lhslist = self.lhslist - - if not self.args: - self.msg( - "Usage: sethelp[/switches] <topic>[;alias;alias][,category[,locks,..] = <text>" - ) - return - - nlist = len(lhslist) - topicstr = lhslist[0] if nlist > 0 else "" - if not topicstr: - self.msg("You have to define a topic!") - return - topicstrlist = topicstr.split(";") - topicstr, aliases = (topicstrlist[0], topicstrlist[1:] if len(topicstr) > 1 else []) - aliastxt = ("(aliases: %s)" % ", ".join(aliases)) if aliases else "" - old_entry = None - - # check if we have an old entry with the same name - try: - for querystr in topicstrlist: - old_entry = HelpEntry.objects.find_topicmatch(querystr) # also search by alias - if old_entry: - old_entry = list(old_entry)[0] - break - category = lhslist[1] if nlist > 1 else old_entry.help_category - lockstring = ",".join(lhslist[2:]) if nlist > 2 else old_entry.locks.get() - except Exception: - old_entry = None - category = lhslist[1] if nlist > 1 else "General" - lockstring = ",".join(lhslist[2:]) if nlist > 2 else "view:all()" - category = category.lower() - - if "edit" in switches: - # open the line editor to edit the helptext. No = is needed. - if old_entry: - topicstr = old_entry.key - if self.rhs: - # we assume append here. - old_entry.entrytext += "\n%s" % self.rhs - helpentry = old_entry - else: - helpentry = create.create_help_entry( - topicstr, self.rhs, category=category, locks=lockstring, aliases=aliases - ) - self.caller.db._editing_help = helpentry - - EvEditor( - self.caller, - loadfunc=_loadhelp, - savefunc=_savehelp, - quitfunc=_quithelp, - key="topic {}".format(topicstr), - persistent=True, - ) - return - - if "append" in switches or "merge" in switches or "extend" in switches: - # merge/append operations - if not old_entry: - self.msg("Could not find topic '%s'. You must give an exact name." % topicstr) - return - if not self.rhs: - self.msg("You must supply text to append/merge.") - return - if "merge" in switches: - old_entry.entrytext += " " + self.rhs - else: - old_entry.entrytext += "\n%s" % self.rhs - old_entry.aliases.add(aliases) - self.msg("Entry updated:\n%s%s" % (old_entry.entrytext, aliastxt)) - return - if "delete" in switches or "del" in switches: - # delete the help entry - if not old_entry: - self.msg("Could not find topic '%s'%s." % (topicstr, aliastxt)) - return - old_entry.delete() - self.msg("Deleted help entry '%s'%s." % (topicstr, aliastxt)) - return - - # at this point it means we want to add a new help entry. - if not self.rhs: - self.msg("You must supply a help text to add.") - return - if old_entry: - if "replace" in switches: - # overwrite old entry - old_entry.key = topicstr - old_entry.entrytext = self.rhs - old_entry.help_category = category - old_entry.locks.clear() - old_entry.locks.add(lockstring) - old_entry.aliases.add(aliases) - old_entry.save() - self.msg("Overwrote the old topic '%s'%s." % (topicstr, aliastxt)) - else: - self.msg( - "Topic '%s'%s already exists. Use /replace to overwrite " - "or /append or /merge to add text to it." % (topicstr, aliastxt) - ) - else: - # no old entry. Create a new one. - new_entry = create.create_help_entry( - topicstr, self.rhs, category=category, locks=lockstring, aliases=aliases - ) - if new_entry: - self.msg("Topic '%s'%s was successfully created." % (topicstr, aliastxt)) - if "edit" in switches: - # open the line editor to edit the helptext - self.caller.db._editing_help = new_entry - EvEditor( - self.caller, - loadfunc=_loadhelp, - savefunc=_savehelp, - quitfunc=_quithelp, - key="topic {}".format(new_entry.key), - persistent=True, - ) - return - else: - self.msg( - "Error when creating topic '%s'%s! Contact an admin." % (topicstr, aliastxt) - )
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/muxcommand.html b/docs/0.9.5/_modules/evennia/commands/default/muxcommand.html deleted file mode 100644 index 7b55791f51..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/muxcommand.html +++ /dev/null @@ -1,376 +0,0 @@ - - - - - - - - evennia.commands.default.muxcommand — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.muxcommand

-"""
-The command template for the default MUX-style command set. There
-is also an Account/OOC version that makes sure caller is an Account object.
-"""
-
-from evennia.utils import utils
-from evennia.commands.command import Command
-
-# limit symbol import for API
-__all__ = ("MuxCommand", "MuxAccountCommand")
-
-
-
[docs]class MuxCommand(Command): - """ - This sets up the basis for a MUX command. The idea - is that most other Mux-related commands should just - inherit from this and don't have to implement much - parsing of their own unless they do something particularly - advanced. - - Note that the class's __doc__ string (this text) is - used by Evennia to create the automatic help entry for - the command, so make sure to document consistently here. - """ - -
[docs] def has_perm(self, srcobj): - """ - This is called by the cmdhandler to determine - if srcobj is allowed to execute this command. - We just show it here for completeness - we - are satisfied using the default check in Command. - """ - return super().has_perm(srcobj)
- -
[docs] def at_pre_cmd(self): - """ - This hook is called before self.parse() on all commands - """ - pass
- -
[docs] def at_post_cmd(self): - """ - This hook is called after the command has finished executing - (after self.func()). - """ - pass
- -
[docs] def parse(self): - """ - This method is called by the cmdhandler once the command name - has been identified. It creates a new set of member variables - that can be later accessed from self.func() (see below) - - The following variables are available for our use when entering this - method (from the command definition, and assigned on the fly by the - cmdhandler): - self.key - the name of this command ('look') - self.aliases - the aliases of this cmd ('l') - self.permissions - permission string for this command - self.help_category - overall category of command - - self.caller - the object calling this command - self.cmdstring - the actual command name used to call this - (this allows you to know which alias was used, - for example) - self.args - the raw input; everything following self.cmdstring. - self.cmdset - the cmdset from which this command was picked. Not - often used (useful for commands like 'help' or to - list all available commands etc) - self.obj - the object on which this command was defined. It is often - the same as self.caller. - - A MUX command has the following possible syntax: - - name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]] - - The 'name[ with several words]' part is already dealt with by the - cmdhandler at this point, and stored in self.cmdname (we don't use - it here). The rest of the command is stored in self.args, which can - start with the switch indicator /. - - Optional variables to aid in parsing, if set: - self.switch_options - (tuple of valid /switches expected by this - command (without the /)) - self.rhs_split - Alternate string delimiter or tuple of strings - to separate left/right hand sides. tuple form - gives priority split to first string delimiter. - - This parser breaks self.args into its constituents and stores them in the - following variables: - self.switches = [list of /switches (without the /)] - self.raw = This is the raw argument input, including switches - self.args = This is re-defined to be everything *except* the switches - self.lhs = Everything to the left of = (lhs:'left-hand side'). If - no = is found, this is identical to self.args. - self.rhs: Everything to the right of = (rhs:'right-hand side'). - If no '=' is found, this is None. - self.lhslist - [self.lhs split into a list by comma] - self.rhslist - [list of self.rhs split into a list by comma] - self.arglist = [list of space-separated args (stripped, including '=' if it exists)] - - All args and list members are stripped of excess whitespace around the - strings, but case is preserved. - """ - raw = self.args - args = raw.strip() - # Without explicitly setting these attributes, they assume default values: - if not hasattr(self, "switch_options"): - self.switch_options = None - if not hasattr(self, "rhs_split"): - self.rhs_split = "=" - if not hasattr(self, "account_caller"): - self.account_caller = False - - # split out switches - switches, delimiters = [], self.rhs_split - if self.switch_options: - self.switch_options = [opt.lower() for opt in self.switch_options] - if args and len(args) > 1 and raw[0] == "/": - # we have a switch, or a set of switches. These end with a space. - switches = args[1:].split(None, 1) - if len(switches) > 1: - switches, args = switches - switches = switches.split("/") - else: - args = "" - switches = switches[0].split("/") - # If user-provides switches, parse them with parser switch options. - if switches and self.switch_options: - valid_switches, unused_switches, extra_switches = [], [], [] - for element in switches: - option_check = [opt for opt in self.switch_options if opt == element] - if not option_check: - option_check = [ - opt for opt in self.switch_options if opt.startswith(element) - ] - match_count = len(option_check) - if match_count > 1: - extra_switches.extend( - option_check - ) # Either the option provided is ambiguous, - elif match_count == 1: - valid_switches.extend(option_check) # or it is a valid option abbreviation, - elif match_count == 0: - unused_switches.append(element) # or an extraneous option to be ignored. - if extra_switches: # User provided switches - self.msg( - "|g%s|n: |wAmbiguous switch supplied: Did you mean /|C%s|w?" - % (self.cmdstring, " |nor /|C".join(extra_switches)) - ) - if unused_switches: - plural = "" if len(unused_switches) == 1 else "es" - self.msg( - '|g%s|n: |wExtra switch%s "/|C%s|w" ignored.' - % (self.cmdstring, plural, "|n, /|C".join(unused_switches)) - ) - switches = valid_switches # Only include valid_switches in command function call - arglist = [arg.strip() for arg in args.split()] - - # check for arg1, arg2, ... = argA, argB, ... constructs - lhs, rhs = args.strip(), None - if lhs: - if delimiters and hasattr(delimiters, "__iter__"): # If delimiter is iterable, - best_split = delimiters[0] # (default to first delimiter) - for this_split in delimiters: # try each delimiter - if this_split in lhs: # to find first successful split - best_split = this_split # to be the best split. - break - else: - best_split = delimiters - # Parse to separate left into left/right sides using best_split delimiter string - if best_split in lhs: - lhs, rhs = lhs.split(best_split, 1) - # Trim user-injected whitespace - rhs = rhs.strip() if rhs is not None else None - lhs = lhs.strip() - # Further split left/right sides by comma delimiter - lhslist = [arg.strip() for arg in lhs.split(",")] if lhs is not None else "" - rhslist = [arg.strip() for arg in rhs.split(",")] if rhs is not None else "" - # save to object properties: - self.raw = raw - self.switches = switches - self.args = args.strip() - self.arglist = arglist - self.lhs = lhs - self.lhslist = lhslist - self.rhs = rhs - self.rhslist = rhslist - - # if the class has the account_caller property set on itself, we make - # sure that self.caller is always the account if possible. We also create - # a special property "character" for the puppeted object, if any. This - # is convenient for commands defined on the Account only. - if self.account_caller: - if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"): - # caller is an Object/Character - self.character = self.caller - self.caller = self.caller.account - elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"): - # caller was already an Account - self.character = self.caller.get_puppet(self.session) - else: - self.character = None
- -
[docs] def get_command_info(self): - """ - Update of parent class's get_command_info() for MuxCommand. - """ - variables = "\n".join( - " |w{}|n ({}): {}".format(key, type(val), val) for key, val in self.__dict__.items() - ) - string = f""" -Command {self} has no defined `func()` - showing on-command variables: No child func() defined for {self} - available variables: -{variables} - """ - self.caller.msg(string) - # a simple test command to show the available properties - string = "-" * 50 - string += "\n|w%s|n - Command variables from evennia:\n" % self.key - string += "-" * 50 - string += "\nname of cmd (self.key): |w%s|n\n" % self.key - string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases - string += "cmd locks (self.locks): |w%s|n\n" % self.locks - string += "help category (self.help_category): |w%s|n\n" % self.help_category - string += "object calling (self.caller): |w%s|n\n" % self.caller - string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj - string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring - # show cmdset.key instead of cmdset to shorten output - string += utils.fill("current cmdset (self.cmdset): |w%s|n\n" % self.cmdset) - string += "\n" + "-" * 50 - string += "\nVariables from MuxCommand baseclass\n" - string += "-" * 50 - string += "\nraw argument (self.raw): |w%s|n \n" % self.raw - string += "cmd args (self.args): |w%s|n\n" % self.args - string += "cmd switches (self.switches): |w%s|n\n" % self.switches - string += "cmd options (self.switch_options): |w%s|n\n" % self.switch_options - string += "cmd parse left/right using (self.rhs_split): |w%s|n\n" % self.rhs_split - string += "space-separated arg list (self.arglist): |w%s|n\n" % self.arglist - string += "lhs, left-hand side of '=' (self.lhs): |w%s|n\n" % self.lhs - string += "lhs, comma separated (self.lhslist): |w%s|n\n" % self.lhslist - string += "rhs, right-hand side of '=' (self.rhs): |w%s|n\n" % self.rhs - string += "rhs, comma separated (self.rhslist): |w%s|n\n" % self.rhslist - string += "-" * 50 - self.caller.msg(string)
- -
[docs] def func(self): - """ - This is the hook function that actually does all the work. It is called - by the cmdhandler right after self.parser() finishes, and so has access - to all the variables defined therein. - """ - self.get_command_info()
- - -
[docs]class MuxAccountCommand(MuxCommand): - """ - This is an on-Account version of the MuxCommand. Since these commands sit - on Accounts rather than on Characters/Objects, we need to check - this in the parser. - - Account commands are available also when puppeting a Character, it's - just that they are applied with a lower priority and are always - available, also when disconnected from a character (i.e. "ooc"). - - This class makes sure that caller is always an Account object, while - creating a new property "character" that is set only if a - character is actually attached to this Account and Session. - """ - - account_caller = True # Using MuxAccountCommand explicitly defaults the caller to an account
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/syscommands.html b/docs/0.9.5/_modules/evennia/commands/default/syscommands.html deleted file mode 100644 index 50932bf6cb..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/syscommands.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - - - evennia.commands.default.syscommands — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.syscommands

-"""
-System commands
-
-These are the default commands called by the system commandhandler
-when various exceptions occur. If one of these commands are not
-implemented and part of the current cmdset, the engine falls back
-to a default solution instead.
-
-Some system commands are shown in this module
-as a REFERENCE only (they are not all added to Evennia's
-default cmdset since they don't currently do anything differently from the
-default backup systems hard-wired in the engine).
-
-Overloading these commands in a cmdset can be used to create
-interesting effects. An example is using the NoMatch system command
-to implement a line-editor where you don't have to start each
-line with a command (if there is no match to a known command,
-the line is just added to the editor buffer).
-"""
-
-from evennia.comms.models import ChannelDB
-from evennia.utils import create
-from evennia.utils.utils import at_search_result
-
-# The command keys the engine is calling
-# (the actual names all start with __)
-from evennia.commands.cmdhandler import CMD_NOINPUT
-from evennia.commands.cmdhandler import CMD_NOMATCH
-from evennia.commands.cmdhandler import CMD_MULTIMATCH
-from evennia.commands.cmdhandler import CMD_CHANNEL
-from evennia.utils import utils
-
-from django.conf import settings
-
-COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-# Command called when there is no input at line
-# (i.e. an lone return key)
-
-
-
[docs]class SystemNoInput(COMMAND_DEFAULT_CLASS): - """ - This is called when there is no input given - """ - - key = CMD_NOINPUT - locks = "cmd:all()" - -
[docs] def func(self): - "Do nothing." - pass
- - -# -# Command called when there was no match to the -# command name -# -
[docs]class SystemNoMatch(COMMAND_DEFAULT_CLASS): - """ - No command was found matching the given input. - """ - - key = CMD_NOMATCH - locks = "cmd:all()" - -
[docs] def func(self): - """ - This is given the failed raw string as input. - """ - self.msg("Huh?")
- - -# -# Command called when there were multiple matches to the command. -# -
[docs]class SystemMultimatch(COMMAND_DEFAULT_CLASS): - """ - Multiple command matches. - - The cmdhandler adds a special attribute 'matches' to this - system command. - - matches = [(cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname) , (cmdname, ...), ...] - - Here, `cmdname` is the command's name and `args` the rest of the incoming string, - without said command name. `cmdobj` is the Command instance, the cmdlen is - the same as len(cmdname) and mratio is a measure of how big a part of the - full input string the cmdname takes up - an exact match would be 1.0. Finally, - the `raw_cmdname` is the cmdname unmodified by eventual prefix-stripping. - - """ - - key = CMD_MULTIMATCH - locks = "cmd:all()" - -
[docs] def func(self): - """ - Handle multiple-matches by using the at_search_result default handler. - - """ - # this was set by the cmdparser and is a tuple - # (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname). See - # evennia.commands.cmdparse.create_match for more details. - matches = self.matches - # at_search_result will itself msg the multimatch options to the caller. - at_search_result([match[2] for match in matches], self.caller, query=matches[0][0])
- - -# Command called when the command given at the command line -# was identified as a channel name, like there existing a -# channel named 'ooc' and the user wrote -# > ooc Hello! - - -
[docs]class SystemSendToChannel(COMMAND_DEFAULT_CLASS): - """ - This is a special command that the cmdhandler calls - when it detects that the command given matches - an existing Channel object key (or alias). - """ - - key = CMD_CHANNEL - locks = "cmd:all()" - -
[docs] def parse(self): - channelname, msg = self.args.split(":", 1) - self.args = channelname.strip(), msg.strip()
- -
[docs] def func(self): - """ - Create a new message and send it to channel, using - the already formatted input. - """ - caller = self.caller - channelkey, msg = self.args - if not msg: - caller.msg("Say what?") - return - channel = ChannelDB.objects.get_channel(channelkey) - if not channel: - caller.msg("Channel '%s' not found." % channelkey) - return - if not channel.has_connection(caller): - string = "You are not connected to channel '%s'." - caller.msg(string % channelkey) - return - if not channel.access(caller, "send"): - string = "You are not permitted to send to channel '%s'." - caller.msg(string % channelkey) - return - msg = "[%s] %s: %s" % (channel.key, caller.name, msg) - msgobj = create.create_message(caller, msg, channels=[channel]) - channel.msg(msgobj)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/system.html b/docs/0.9.5/_modules/evennia/commands/default/system.html deleted file mode 100644 index e93ede8c97..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/system.html +++ /dev/null @@ -1,1288 +0,0 @@ - - - - - - - - evennia.commands.default.system — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.system

-"""
-
-System commands
-
-"""
-
-
-import code
-import traceback
-import os
-import io
-import datetime
-import sys
-import django
-import twisted
-import time
-
-from django.conf import settings
-from django.core.paginator import Paginator
-from evennia.server.sessionhandler import SESSIONS
-from evennia.scripts.models import ScriptDB
-from evennia.objects.models import ObjectDB
-from evennia.accounts.models import AccountDB
-from evennia.utils import logger, utils, gametime, create, search
-from evennia.utils.eveditor import EvEditor
-from evennia.utils.evtable import EvTable
-from evennia.utils.evmore import EvMore
-from evennia.utils.utils import crop, class_from_module
-
-COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-# delayed imports
-_RESOURCE = None
-_IDMAPPER = None
-
-# limit symbol import for API
-__all__ = (
-    "CmdReload",
-    "CmdReset",
-    "CmdShutdown",
-    "CmdPy",
-    "CmdScripts",
-    "CmdObjects",
-    "CmdService",
-    "CmdAbout",
-    "CmdTime",
-    "CmdServerLoad",
-    "CmdAccounts",
-    "CmdTickers"
-)
-
-
-
[docs]class CmdReload(COMMAND_DEFAULT_CLASS): - """ - reload the server - - Usage: - reload [reason] - - This restarts the server. The Portal is not - affected. Non-persistent scripts will survive a reload (use - reset to purge) and at_reload() hooks will be called. - """ - - key = "reload" - aliases = ["restart"] - locks = "cmd:perm(reload) or perm(Developer)" - help_category = "System" - -
[docs] def func(self): - """ - Reload the system. - """ - reason = "" - if self.args: - reason = "(Reason: %s) " % self.args.rstrip(".") - SESSIONS.announce_all(" Server restart initiated %s..." % reason) - SESSIONS.portal_restart_server()
- - -
[docs]class CmdReset(COMMAND_DEFAULT_CLASS): - """ - reset and reboot the server - - Usage: - reset - - Notes: - For normal updating you are recommended to use reload rather - than this command. Use shutdown for a complete stop of - everything. - - This emulates a cold reboot of the Server component of Evennia. - The difference to shutdown is that the Server will auto-reboot - and that it does not affect the Portal, so no users will be - disconnected. Contrary to reload however, all shutdown hooks will - be called and any non-database saved scripts, ndb-attributes, - cmdsets etc will be wiped. - - """ - - key = "reset" - aliases = ["reboot"] - locks = "cmd:perm(reload) or perm(Developer)" - help_category = "System" - -
[docs] def func(self): - """ - Reload the system. - """ - SESSIONS.announce_all(" Server resetting/restarting ...") - SESSIONS.portal_reset_server()
- - -
[docs]class CmdShutdown(COMMAND_DEFAULT_CLASS): - - """ - stop the server completely - - Usage: - shutdown [announcement] - - Gracefully shut down both Server and Portal. - """ - - key = "shutdown" - locks = "cmd:perm(shutdown) or perm(Developer)" - help_category = "System" - -
[docs] def func(self): - """Define function""" - # Only allow shutdown if caller has session - if not self.caller.sessions.get(): - return - self.msg("Shutting down server ...") - announcement = "\nServer is being SHUT DOWN!\n" - if self.args: - announcement += "%s\n" % self.args - logger.log_info("Server shutdown by %s." % self.caller.name) - SESSIONS.announce_all(announcement) - SESSIONS.portal_shutdown()
- - -def _py_load(caller): - return "" - - -def _py_code(caller, buf): - """ - Execute the buffer. - """ - measure_time = caller.db._py_measure_time - client_raw = caller.db._py_clientraw - string = "Executing code%s ..." % (" (measure timing)" if measure_time else "") - caller.msg(string) - _run_code_snippet( - caller, buf, mode="exec", measure_time=measure_time, client_raw=client_raw, show_input=False - ) - return True - - -def _py_quit(caller): - del caller.db._py_measure_time - caller.msg("Exited the code editor.") - - -def _run_code_snippet( - caller, pycode, mode="eval", measure_time=False, client_raw=False, show_input=True -): - """ - Run code and try to display information to the caller. - - Args: - caller (Object): The caller. - pycode (str): The Python code to run. - measure_time (bool, optional): Should we measure the time of execution? - client_raw (bool, optional): Should we turn off all client-specific escaping? - show_input (bookl, optional): Should we display the input? - - """ - # Try to retrieve the session - session = caller - if hasattr(caller, "sessions"): - sessions = caller.sessions.all() - - available_vars = evennia_local_vars(caller) - - if show_input: - for session in sessions: - try: - caller.msg(">>> %s" % pycode, session=session, options={"raw": True}) - except TypeError: - caller.msg(">>> %s" % pycode, options={"raw": True}) - - try: - # reroute standard output to game client console - old_stdout = sys.stdout - old_stderr = sys.stderr - - class FakeStd: - def __init__(self, caller): - self.caller = caller - - def write(self, string): - self.caller.msg(string.rsplit("\n", 1)[0]) - - fake_std = FakeStd(caller) - sys.stdout = fake_std - sys.stderr = fake_std - - try: - pycode_compiled = compile(pycode, "", mode) - except Exception: - mode = "exec" - pycode_compiled = compile(pycode, "", mode) - - duration = "" - if measure_time: - t0 = time.time() - ret = eval(pycode_compiled, {}, available_vars) - t1 = time.time() - duration = " (runtime ~ %.4f ms)" % ((t1 - t0) * 1000) - caller.msg(duration) - else: - ret = eval(pycode_compiled, {}, available_vars) - - except Exception: - errlist = traceback.format_exc().split("\n") - if len(errlist) > 4: - errlist = errlist[4:] - ret = "\n".join("%s" % line for line in errlist if line) - finally: - # return to old stdout - sys.stdout = old_stdout - sys.stderr = old_stderr - - if ret is None: - return - elif isinstance(ret, tuple): - # we must convert here to allow msg to pass it (a tuple is confused - # with a outputfunc structure) - ret = str(ret) - - for session in sessions: - try: - caller.msg(ret, session=session, options={"raw": True, "client_raw": client_raw}) - except TypeError: - caller.msg(ret, options={"raw": True, "client_raw": client_raw}) - - -def evennia_local_vars(caller): - """Return Evennia local variables usable in the py command as a dictionary.""" - import evennia - - return { - "self": caller, - "me": caller, - "here": getattr(caller, "location", None), - "evennia": evennia, - "ev": evennia, - "inherits_from": utils.inherits_from, - } - - -class EvenniaPythonConsole(code.InteractiveConsole): - - """Evennia wrapper around a Python interactive console.""" - - def __init__(self, caller): - super().__init__(evennia_local_vars(caller)) - self.caller = caller - - def write(self, string): - """Don't send to stderr, send to self.caller.""" - self.caller.msg(string) - - def push(self, line): - """Push some code, whether complete or not.""" - old_stdout = sys.stdout - old_stderr = sys.stderr - - class FakeStd: - def __init__(self, caller): - self.caller = caller - - def write(self, string): - self.caller.msg(string.split("\n", 1)[0]) - - fake_std = FakeStd(self.caller) - sys.stdout = fake_std - sys.stderr = fake_std - result = None - try: - result = super().push(line) - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - return result - - -
[docs]class CmdPy(COMMAND_DEFAULT_CLASS): - """ - execute a snippet of python code - - Usage: - py [cmd] - py/edit - py/time <cmd> - py/clientraw <cmd> - py/noecho - - Switches: - time - output an approximate execution time for <cmd> - edit - open a code editor for multi-line code experimentation - clientraw - turn off all client-specific escaping. Note that this may - lead to different output depending on prototocol (such as angular brackets - being parsed as HTML in the webclient but not in telnet clients) - noecho - in Python console mode, turn off the input echo (e.g. if your client - does this for you already) - - Without argument, open a Python console in-game. This is a full console, - accepting multi-line Python code for testing and debugging. Type `exit()` to - return to the game. If Evennia is reloaded, the console will be closed. - - Enter a line of instruction after the 'py' command to execute it - immediately. Separate multiple commands by ';' or open the code editor - using the /edit switch (all lines added in editor will be executed - immediately when closing or using the execute command in the editor). - - A few variables are made available for convenience in order to offer access - to the system (you can import more at execution time). - - Available variables in py environment: - self, me : caller - here : caller.location - evennia : the evennia API - inherits_from(obj, parent) : check object inheritance - - You can explore The evennia API from inside the game by calling - the `__doc__` property on entities: - py evennia.__doc__ - py evennia.managers.__doc__ - - |rNote: In the wrong hands this command is a severe security risk. It - should only be accessible by trusted server admins/superusers.|n - - """ - - key = "py" - aliases = ["!"] - switch_options = ("time", "edit", "clientraw", "noecho") - locks = "cmd:perm(py) or perm(Developer)" - help_category = "System" - -
[docs] def func(self): - """hook function""" - - caller = self.caller - pycode = self.args - - noecho = "noecho" in self.switches - - if "edit" in self.switches: - caller.db._py_measure_time = "time" in self.switches - caller.db._py_clientraw = "clientraw" in self.switches - EvEditor( - self.caller, - loadfunc=_py_load, - savefunc=_py_code, - quitfunc=_py_quit, - key="Python exec: :w or :!", - persistent=True, - codefunc=_py_code, - ) - return - - if not pycode: - # Run in interactive mode - console = EvenniaPythonConsole(self.caller) - banner = ( - "|gEvennia Interactive Python mode{echomode}\n" - "Python {version} on {platform}".format( - echomode=" (no echoing of prompts)" if noecho else "", - version=sys.version, - platform=sys.platform, - ) - ) - self.msg(banner) - line = "" - main_prompt = "|x[py mode - quit() to exit]|n" - prompt = main_prompt - while line.lower() not in ("exit", "exit()"): - try: - line = yield (prompt) - if noecho: - prompt = "..." if console.push(line) else main_prompt - else: - prompt = line if console.push(line) else f"{line}\n{main_prompt}" - except SystemExit: - break - self.msg("|gClosing the Python console.|n") - return - - _run_code_snippet( - caller, - self.args, - measure_time="time" in self.switches, - client_raw="clientraw" in self.switches, - )
- - -class ScriptEvMore(EvMore): - """ - Listing 1000+ Scripts can be very slow and memory-consuming. So - we use this custom EvMore child to build en EvTable only for - each page of the list. - - """ - - def init_pages(self, scripts): - """Prepare the script list pagination""" - script_pages = Paginator(scripts, max(1, int(self.height / 2))) - super().init_pages(script_pages) - - def page_formatter(self, scripts): - """Takes a page of scripts and formats the output - into an EvTable.""" - - if not scripts: - return "<No scripts>" - - table = EvTable( - "|wdbref|n", - "|wobj|n", - "|wkey|n", - "|wintval|n", - "|wnext|n", - "|wrept|n", - "|wdb", - "|wtypeclass|n", - "|wdesc|n", - align="r", - border="tablecols", - width=self.width, - ) - - for script in scripts: - - nextrep = script.time_until_next_repeat() - if nextrep is None: - nextrep = "PAUSED" if script.db._paused_time else "--" - else: - nextrep = "%ss" % nextrep - - maxrepeat = script.repeats - remaining = script.remaining_repeats() or 0 - if maxrepeat: - rept = "%i/%i" % (maxrepeat - remaining, maxrepeat) - else: - rept = "-/-" - - table.add_row( - script.id, - f"{script.obj.key}({script.obj.dbref})" - if (hasattr(script, "obj") and script.obj) - else "<Global>", - script.key, - script.interval if script.interval > 0 else "--", - nextrep, - rept, - "*" if script.persistent else "-", - script.typeclass_path.rsplit(".", 1)[-1], - crop(script.desc, width=20), - ) - - return str(table) - - -
[docs]class CmdScripts(COMMAND_DEFAULT_CLASS): - """ - list and manage all running scripts - - Usage: - scripts[/switches] [#dbref, key, script.path or <obj>] - - Switches: - start - start a script (must supply a script path) - stop - stops an existing script - kill - kills a script - without running its cleanup hooks - validate - run a validation on the script(s) - - If no switches are given, this command just views all active - scripts. The argument can be either an object, at which point it - will be searched for all scripts defined on it, or a script name - or #dbref. For using the /stop switch, a unique script #dbref is - required since whole classes of scripts often have the same name. - - Use script for managing commands on objects. - """ - - key = "scripts" - aliases = ["globalscript", "listscripts"] - switch_options = ("start", "stop", "kill", "validate") - locks = "cmd:perm(listscripts) or perm(Admin)" - help_category = "System" - - excluded_typeclass_paths = ["evennia.prototypes.prototypes.DbPrototype"] - -
[docs] def func(self): - """implement method""" - - caller = self.caller - args = self.args - - if args: - if "start" in self.switches: - # global script-start mode - new_script = create.create_script(args) - if new_script: - caller.msg("Global script %s was started successfully." % args) - else: - caller.msg("Global script %s could not start correctly. See logs." % args) - return - - # test first if this is a script match - scripts = ScriptDB.objects.get_all_scripts(key=args) - if not scripts: - # try to find an object instead. - objects = ObjectDB.objects.object_search(args) - if objects: - scripts = [] - for obj in objects: - # get all scripts on the object(s) - scripts.extend(ScriptDB.objects.get_all_scripts_on_obj(obj)) - else: - # we want all scripts. - scripts = ScriptDB.objects.get_all_scripts() - if not scripts: - caller.msg("No scripts are running.") - return - # filter any found scripts by tag category. - scripts = scripts.exclude(db_typeclass_path__in=self.excluded_typeclass_paths) - - if not scripts: - string = "No scripts found with a key '%s', or on an object named '%s'." % (args, args) - caller.msg(string) - return - - if self.switches and self.switches[0] in ("stop", "del", "delete", "kill"): - # we want to delete something - if len(scripts) == 1: - # we have a unique match! - if "kill" in self.switches: - string = "Killing script '%s'" % scripts[0].key - scripts[0].stop(kill=True) - else: - string = "Stopping script '%s'." % scripts[0].key - scripts[0].stop() - # import pdb # DEBUG - # pdb.set_trace() # DEBUG - ScriptDB.objects.validate() # just to be sure all is synced - caller.msg(string) - else: - # multiple matches. - ScriptEvMore(caller, scripts, session=self.session) - caller.msg("Multiple script matches. Please refine your search") - elif self.switches and self.switches[0] in ("validate", "valid", "val"): - # run validation on all found scripts - nr_started, nr_stopped = ScriptDB.objects.validate(scripts=scripts) - string = "Validated %s scripts. " % ScriptDB.objects.all().count() - string += "Started %s and stopped %s scripts." % (nr_started, nr_stopped) - caller.msg(string) - else: - # No stopping or validation. We just want to view things. - ScriptEvMore(caller, scripts.order_by("id"), session=self.session)
- - -
[docs]class CmdObjects(COMMAND_DEFAULT_CLASS): - """ - statistics on objects in the database - - Usage: - objects [<nr>] - - Gives statictics on objects in database as well as - a list of <nr> latest objects in database. If not - given, <nr> defaults to 10. - """ - - key = "objects" - aliases = ["listobjects", "listobjs", "stats", "db"] - locks = "cmd:perm(listobjects) or perm(Builder)" - help_category = "System" - -
[docs] def func(self): - """Implement the command""" - - caller = self.caller - nlim = int(self.args) if self.args and self.args.isdigit() else 10 - nobjs = ObjectDB.objects.count() - Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS) - nchars = Character.objects.all_family().count() - Room = class_from_module(settings.BASE_ROOM_TYPECLASS) - nrooms = Room.objects.all_family().count() - Exit = class_from_module(settings.BASE_EXIT_TYPECLASS) - nexits = Exit.objects.all_family().count() - nother = nobjs - nchars - nrooms - nexits - nobjs = nobjs or 1 # fix zero-div error with empty database - - # total object sum table - totaltable = self.styled_table( - "|wtype|n", "|wcomment|n", "|wcount|n", "|w%|n", border="table", align="l" - ) - totaltable.align = "l" - totaltable.add_row( - "Characters", - "(BASE_CHARACTER_TYPECLASS + children)", - nchars, - "%.2f" % ((float(nchars) / nobjs) * 100), - ) - totaltable.add_row( - "Rooms", - "(BASE_ROOM_TYPECLASS + children)", - nrooms, - "%.2f" % ((float(nrooms) / nobjs) * 100), - ) - totaltable.add_row( - "Exits", - "(BASE_EXIT_TYPECLASS + children)", - nexits, - "%.2f" % ((float(nexits) / nobjs) * 100), - ) - totaltable.add_row("Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100)) - - # typeclass table - typetable = self.styled_table( - "|wtypeclass|n", "|wcount|n", "|w%|n", border="table", align="l" - ) - typetable.align = "l" - dbtotals = ObjectDB.objects.get_typeclass_totals() - for stat in dbtotals: - typetable.add_row( - stat.get("typeclass", "<error>"), - stat.get("count", -1), - "%.2f" % stat.get("percent", -1), - ) - - # last N table - objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim) :] - latesttable = self.styled_table( - "|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table" - ) - latesttable.align = "l" - for obj in objs: - latesttable.add_row( - utils.datetime_format(obj.date_created), obj.dbref, obj.key, obj.path - ) - - string = "\n|wObject subtype totals (out of %i Objects):|n\n%s" % (nobjs, totaltable) - string += "\n|wObject typeclass distribution:|n\n%s" % typetable - string += "\n|wLast %s Objects created:|n\n%s" % (min(nobjs, nlim), latesttable) - caller.msg(string)
- - -
[docs]class CmdAccounts(COMMAND_DEFAULT_CLASS): - """ - Manage registered accounts - - Usage: - accounts [nr] - accounts/delete <name or #id> [: reason] - - Switches: - delete - delete an account from the server - - By default, lists statistics about the Accounts registered with the game. - It will list the <nr> amount of latest registered accounts - If not given, <nr> defaults to 10. - """ - - key = "accounts" - aliases = ["account", "listaccounts"] - switch_options = ("delete",) - locks = "cmd:perm(listaccounts) or perm(Admin)" - help_category = "System" - -
[docs] def func(self): - """List the accounts""" - - caller = self.caller - args = self.args - - if "delete" in self.switches: - account = getattr(caller, "account") - if not account or not account.check_permstring("Developer"): - caller.msg("You are not allowed to delete accounts.") - return - if not args: - caller.msg("Usage: accounts/delete <name or #id> [: reason]") - return - reason = "" - if ":" in args: - args, reason = [arg.strip() for arg in args.split(":", 1)] - # We use account_search since we want to be sure to find also accounts - # that lack characters. - accounts = search.account_search(args) - if not accounts: - self.msg("Could not find an account by that name.") - return - if len(accounts) > 1: - string = "There were multiple matches:\n" - string += "\n".join(" %s %s" % (account.id, account.key) for account in accounts) - self.msg(string) - return - account = accounts.first() - if not account.access(caller, "delete"): - self.msg("You don't have the permissions to delete that account.") - return - username = account.username - # ask for confirmation - confirm = ( - "It is often better to block access to an account rather than to delete it. " - "|yAre you sure you want to permanently delete " - "account '|n{}|y'|n yes/[no]?".format(username) - ) - answer = yield (confirm) - if answer.lower() not in ("y", "yes"): - caller.msg("Canceled deletion.") - return - - # Boot the account then delete it. - self.msg("Informing and disconnecting account ...") - string = "\nYour account '%s' is being *permanently* deleted.\n" % username - if reason: - string += " Reason given:\n '%s'" % reason - account.msg(string) - logger.log_sec( - "Account Deleted: %s (Reason: %s, Caller: %s, IP: %s)." - % (account, reason, caller, self.session.address) - ) - account.delete() - self.msg("Account %s was successfully deleted." % username) - return - - # No switches, default to displaying a list of accounts. - if self.args and self.args.isdigit(): - nlim = int(self.args) - else: - nlim = 10 - - naccounts = AccountDB.objects.count() - - # typeclass table - dbtotals = AccountDB.objects.object_totals() - typetable = self.styled_table( - "|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l" - ) - for path, count in dbtotals.items(): - typetable.add_row(path, count, "%.2f" % ((float(count) / naccounts) * 100)) - # last N table - plyrs = AccountDB.objects.all().order_by("db_date_created")[max(0, naccounts - nlim) :] - latesttable = self.styled_table( - "|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l" - ) - for ply in plyrs: - latesttable.add_row( - utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path - ) - - string = "\n|wAccount typeclass distribution:|n\n%s" % typetable - string += "\n|wLast %s Accounts created:|n\n%s" % (min(naccounts, nlim), latesttable) - caller.msg(string)
- - -
[docs]class CmdService(COMMAND_DEFAULT_CLASS): - """ - manage system services - - Usage: - service[/switch] <service> - - Switches: - list - shows all available services (default) - start - activates or reactivate a service - stop - stops/inactivate a service (can often be restarted) - delete - tries to permanently remove a service - - Service management system. Allows for the listing, - starting, and stopping of services. If no switches - are given, services will be listed. Note that to operate on the - service you have to supply the full (green or red) name as given - in the list. - """ - - key = "service" - aliases = ["services"] - switch_options = ("list", "start", "stop", "delete") - locks = "cmd:perm(service) or perm(Developer)" - help_category = "System" - -
[docs] def func(self): - """Implement command""" - - caller = self.caller - switches = self.switches - - if switches and switches[0] not in ("list", "start", "stop", "delete"): - caller.msg("Usage: service/<list|start|stop|delete> [servicename]") - return - - # get all services - service_collection = SESSIONS.server.services - - if not switches or switches[0] == "list": - # Just display the list of installed services and their - # status, then exit. - table = self.styled_table( - "|wService|n (use services/start|stop|delete)", "|wstatus", align="l" - ) - for service in service_collection.services: - table.add_row(service.name, service.running and "|gRunning" or "|rNot Running") - caller.msg(str(table)) - return - - # Get the service to start / stop - - try: - service = service_collection.getServiceNamed(self.args) - except Exception: - string = "Invalid service name. This command is case-sensitive. " - string += "See service/list for valid service name (enter the full name exactly)." - caller.msg(string) - return - - if switches[0] in ("stop", "delete"): - # Stopping/killing a service gracefully closes it and disconnects - # any connections (if applicable). - - delmode = switches[0] == "delete" - if not service.running: - caller.msg("That service is not currently running.") - return - if service.name[:7] == "Evennia": - if delmode: - caller.msg("You cannot remove a core Evennia service (named 'Evennia*').") - return - string = ("|RYou seem to be shutting down a core Evennia " - "service (named 'Evennia*').\nNote that stopping " - "some TCP port services will *not* disconnect users " - "*already* connected on those ports, but *may* " - "instead cause spurious errors for them.\nTo safely " - "and permanently remove ports, change settings file " - "and restart the server.|n\n") - caller.msg(string) - - if delmode: - service.stopService() - service_collection.removeService(service) - caller.msg("|gStopped and removed service '%s'.|n" % self.args) - else: - caller.msg(f"Stopping service '{self.args}'...") - try: - service.stopService() - except Exception as err: - caller.msg(f"|rErrors were reported when stopping this service{err}.\n" - "If there are remaining problems, try reloading " - "or rebooting the server.") - caller.msg("|g... Stopped service '%s'.|n" % self.args) - return - - if switches[0] == "start": - # Attempt to start a service. - if service.running: - caller.msg("That service is already running.") - return - caller.msg(f"Starting service '{self.args}' ...") - try: - service.startService() - except Exception as err: - caller.msg(f"|rErrors were reported when starting this service{err}.\n" - "If there are remaining problems, try reloading the server, changing the " - "settings if it's a non-standard service.|n") - caller.msg("|gService started.|n")
- - -
[docs]class CmdAbout(COMMAND_DEFAULT_CLASS): - """ - show Evennia info - - Usage: - about - - Display info about the game engine. - """ - - key = "about" - aliases = "version" - locks = "cmd:all()" - help_category = "System" - -
[docs] def func(self): - """Display information about server or target""" - - string = """ - |cEvennia|n MU* development system - - |wEvennia version|n: {version} - |wOS|n: {os} - |wPython|n: {python} - |wTwisted|n: {twisted} - |wDjango|n: {django} - - |wLicence|n https://opensource.org/licenses/BSD-3-Clause - |wWeb|n http://www.evennia.com - |wIrc|n #evennia on irc.freenode.net:6667 - |wForum|n http://www.evennia.com/discussions - |wMaintainer|n (2010-) Griatch (griatch AT gmail DOT com) - |wMaintainer|n (2006-10) Greg Taylor - - """.format( - version=utils.get_evennia_version(), - os=os.name, - python=sys.version.split()[0], - twisted=twisted.version.short(), - django=django.get_version(), - ) - self.caller.msg(string)
- - -
[docs]class CmdTime(COMMAND_DEFAULT_CLASS): - """ - show server time statistics - - Usage: - time - - List Server time statistics such as uptime - and the current time stamp. - """ - - key = "time" - aliases = "uptime" - locks = "cmd:perm(time) or perm(Player)" - help_category = "System" - -
[docs] def func(self): - """Show server time data in a table.""" - table1 = self.styled_table("|wServer time", "", align="l", width=78) - table1.add_row("Current uptime", utils.time_format(gametime.uptime(), 3)) - table1.add_row("Portal uptime", utils.time_format(gametime.portal_uptime(), 3)) - table1.add_row("Total runtime", utils.time_format(gametime.runtime(), 2)) - table1.add_row("First start", datetime.datetime.fromtimestamp(gametime.server_epoch())) - table1.add_row("Current time", datetime.datetime.now()) - table1.reformat_column(0, width=30) - table2 = self.styled_table( - "|wIn-Game time", - "|wReal time x %g" % gametime.TIMEFACTOR, - align="l", - width=77, - border_top=0, - ) - epochtxt = "Epoch (%s)" % ("from settings" if settings.TIME_GAME_EPOCH else "server start") - table2.add_row(epochtxt, datetime.datetime.fromtimestamp(gametime.game_epoch())) - table2.add_row("Total time passed:", utils.time_format(gametime.gametime(), 2)) - table2.add_row( - "Current time ", datetime.datetime.fromtimestamp(gametime.gametime(absolute=True)) - ) - table2.reformat_column(0, width=30) - self.caller.msg(str(table1) + "\n" + str(table2))
- - -
[docs]class CmdServerLoad(COMMAND_DEFAULT_CLASS): - """ - show server load and memory statistics - - Usage: - server[/mem] - - Switches: - mem - return only a string of the current memory usage - flushmem - flush the idmapper cache - - This command shows server load statistics and dynamic memory - usage. It also allows to flush the cache of accessed database - objects. - - Some Important statistics in the table: - - |wServer load|n is an average of processor usage. It's usually - between 0 (no usage) and 1 (100% usage), but may also be - temporarily higher if your computer has multiple CPU cores. - - The |wResident/Virtual memory|n displays the total memory used by - the server process. - - Evennia |wcaches|n all retrieved database entities when they are - loaded by use of the idmapper functionality. This allows Evennia - to maintain the same instances of an entity and allowing - non-persistent storage schemes. The total amount of cached objects - are displayed plus a breakdown of database object types. - - The |wflushmem|n switch allows to flush the object cache. Please - note that due to how Python's memory management works, releasing - caches may not show you a lower Residual/Virtual memory footprint, - the released memory will instead be re-used by the program. - - """ - - key = "server" - aliases = ["serverload", "serverprocess"] - switch_options = ("mem", "flushmem") - locks = "cmd:perm(list) or perm(Developer)" - help_category = "System" - -
[docs] def func(self): - """Show list.""" - - global _IDMAPPER - if not _IDMAPPER: - from evennia.utils.idmapper import models as _IDMAPPER - - if "flushmem" in self.switches: - # flush the cache - prev, _ = _IDMAPPER.cache_size() - nflushed = _IDMAPPER.flush_cache() - now, _ = _IDMAPPER.cache_size() - string = ( - "The Idmapper cache freed |w{idmapper}|n database objects.\n" - "The Python garbage collector freed |w{gc}|n Python instances total." - ) - self.caller.msg(string.format(idmapper=(prev - now), gc=nflushed)) - return - - # display active processes - - os_windows = os.name == "nt" - pid = os.getpid() - - if os_windows: - # Windows requires the psutil module to even get paltry - # statistics like this (it's pretty much worthless, - # unfortunately, since it's not specific to the process) /rant - try: - import psutil - - has_psutil = True - except ImportError: - has_psutil = False - - if has_psutil: - loadavg = psutil.cpu_percent() - _mem = psutil.virtual_memory() - rmem = _mem.used / (1000.0 * 1000) - pmem = _mem.percent - - if "mem" in self.switches: - string = "Total computer memory usage: |w%g|n MB (%g%%)" - self.caller.msg(string % (rmem, pmem)) - return - # Display table - loadtable = self.styled_table("property", "statistic", align="l") - loadtable.add_row("Total CPU load", "%g %%" % loadavg) - loadtable.add_row("Total computer memory usage", "%g MB (%g%%)" % (rmem, pmem)) - loadtable.add_row("Process ID", "%g" % pid), - else: - loadtable = ( - "Not available on Windows without 'psutil' library " - "(install with |wpip install psutil|n)." - ) - - else: - # Linux / BSD (OSX) - proper pid-based statistics - - global _RESOURCE - if not _RESOURCE: - import resource as _RESOURCE - - loadavg = os.getloadavg()[0] - rmem = ( - float(os.popen("ps -p %d -o %s | tail -1" % (pid, "rss")).read()) / 1000.0 - ) # resident memory - vmem = ( - float(os.popen("ps -p %d -o %s | tail -1" % (pid, "vsz")).read()) / 1000.0 - ) # virtual memory - pmem = float( - os.popen("ps -p %d -o %s | tail -1" % (pid, "%mem")).read() - ) # % of resident memory to total - rusage = _RESOURCE.getrusage(_RESOURCE.RUSAGE_SELF) - - if "mem" in self.switches: - string = "Memory usage: RMEM: |w%g|n MB (%g%%), VMEM (res+swap+cache): |w%g|n MB." - self.caller.msg(string % (rmem, pmem, vmem)) - return - - loadtable = self.styled_table("property", "statistic", align="l") - loadtable.add_row("Server load (1 min)", "%g" % loadavg) - loadtable.add_row("Process ID", "%g" % pid), - loadtable.add_row("Memory usage", "%g MB (%g%%)" % (rmem, pmem)) - loadtable.add_row("Virtual address space", "") - loadtable.add_row("|x(resident+swap+caching)|n", "%g MB" % vmem) - loadtable.add_row( - "CPU time used (total)", - "%s (%gs)" % (utils.time_format(rusage.ru_utime), rusage.ru_utime), - ) - loadtable.add_row( - "CPU time used (user)", - "%s (%gs)" % (utils.time_format(rusage.ru_stime), rusage.ru_stime), - ) - loadtable.add_row( - "Page faults", - "%g hard, %g soft, %g swapouts" - % (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap), - ) - loadtable.add_row( - "Disk I/O", "%g reads, %g writes" % (rusage.ru_inblock, rusage.ru_oublock) - ) - loadtable.add_row("Network I/O", "%g in, %g out" % (rusage.ru_msgrcv, rusage.ru_msgsnd)) - loadtable.add_row( - "Context switching", - "%g vol, %g forced, %g signals" - % (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals), - ) - - # os-generic - - string = "|wServer CPU and Memory load:|n\n%s" % loadtable - - # object cache count (note that sys.getsiseof is not called so this works for pypy too. - total_num, cachedict = _IDMAPPER.cache_size() - sorted_cache = sorted( - [(key, num) for key, num in cachedict.items() if num > 0], - key=lambda tup: tup[1], - reverse=True, - ) - memtable = self.styled_table("entity name", "number", "idmapper %", align="l") - for tup in sorted_cache: - memtable.add_row(tup[0], "%i" % tup[1], "%.2f" % (float(tup[1]) / total_num * 100)) - - string += "\n|w Entity idmapper cache:|n %i items\n%s" % (total_num, memtable) - - # return to caller - self.caller.msg(string)
- - -
[docs]class CmdTickers(COMMAND_DEFAULT_CLASS): - """ - View running tickers - - Usage: - tickers - - Note: Tickers are created, stopped and manipulated in Python code - using the TickerHandler. This is merely a convenience function for - inspecting the current status. - - """ - - key = "tickers" - help_category = "System" - locks = "cmd:perm(tickers) or perm(Builder)" - -
[docs] def func(self): - from evennia import TICKER_HANDLER - - all_subs = TICKER_HANDLER.all_display() - if not all_subs: - self.caller.msg("No tickers are currently active.") - return - table = self.styled_table("interval (s)", "object", "path/methodname", "idstring", "db") - for sub in all_subs: - table.add_row( - sub[3], - "%s%s" - % ( - sub[0] or "[None]", - sub[0] and " (#%s)" % (sub[0].id if hasattr(sub[0], "id") else "") or "", - ), - sub[1] if sub[1] else sub[2], - sub[4] or "[Unset]", - "*" if sub[5] else "-", - ) - self.caller.msg("|wActive tickers|n:\n" + str(table))
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/tests.html b/docs/0.9.5/_modules/evennia/commands/default/tests.html deleted file mode 100644 index bad34122ec..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/tests.html +++ /dev/null @@ -1,1694 +0,0 @@ - - - - - - - - evennia.commands.default.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.tests

-# -*- coding: utf-8 -*-
-"""
- ** OBS - this is not a normal command module! **
- ** You cannot import anything in this module as a command! **
-
-This is part of the Evennia unittest framework, for testing the
-stability and integrity of the codebase during updates. This module
-test the default command set. It is instantiated by the
-evennia/objects/tests.py module, which in turn is run by as part of the
-main test suite started with
- > python game/manage.py test.
-
-"""
-import re
-import types
-import datetime
-from anything import Anything
-
-from django.conf import settings
-from unittest.mock import patch, Mock, MagicMock
-
-from evennia import DefaultRoom, DefaultExit, ObjectDB
-from evennia.commands.default.cmdset_character import CharacterCmdSet
-from evennia.utils.test_resources import EvenniaTest
-from evennia.commands.default import (
-    help,
-    general,
-    system,
-    admin,
-    account,
-    building,
-    batchprocess,
-    comms,
-    unloggedin,
-    syscommands,
-)
-from evennia.commands.cmdparser import build_matches
-from evennia.commands.default.muxcommand import MuxCommand
-from evennia.commands.command import Command, InterruptCommand
-from evennia.commands import cmdparser
-from evennia.commands.cmdset import CmdSet
-from evennia.utils import ansi, utils, gametime
-from evennia.server.sessionhandler import SESSIONS
-from evennia import search_object
-from evennia import DefaultObject, DefaultCharacter
-from evennia.prototypes import prototypes as protlib
-
-
-# set up signal here since we are not starting the server
-
-_RE = re.compile(r"^\+|-+\+|\+-+|--+|\|(?:\s|$)", re.MULTILINE)
-
-
-# ------------------------------------------------------------
-# Command testing
-# ------------------------------------------------------------
-
-
-
[docs]@patch("evennia.server.portal.portal.LoopingCall", new=MagicMock()) -class CommandTest(EvenniaTest): - """ - Tests a command - """ - -
[docs] def call( - self, - cmdobj, - args, - msg=None, - cmdset=None, - noansi=True, - caller=None, - receiver=None, - cmdstring=None, - obj=None, - inputs=None, - raw_string=None, - ): - """ - Test a command by assigning all the needed - properties to cmdobj and running - cmdobj.at_pre_cmd() - cmdobj.parse() - cmdobj.func() - cmdobj.at_post_cmd() - The msgreturn value is compared to eventual - output sent to caller.msg in the game - - Returns: - msg (str): The received message that was sent to the caller. - - """ - caller = caller if caller else self.char1 - receiver = receiver if receiver else caller - cmdobj.caller = caller - cmdobj.cmdname = cmdstring if cmdstring else cmdobj.key - cmdobj.raw_cmdname = cmdobj.cmdname - cmdobj.cmdstring = cmdobj.cmdname # deprecated - cmdobj.args = args - cmdobj.cmdset = cmdset - cmdobj.session = SESSIONS.session_from_sessid(1) - cmdobj.account = self.account - cmdobj.raw_string = raw_string if raw_string is not None else cmdobj.key + " " + args - cmdobj.obj = obj or (caller if caller else self.char1) - # test - old_msg = receiver.msg - inputs = inputs or [] - - try: - receiver.msg = Mock() - if cmdobj.at_pre_cmd(): - return - cmdobj.parse() - ret = cmdobj.func() - - # handle func's with yield in them (generators) - if isinstance(ret, types.GeneratorType): - while True: - try: - inp = inputs.pop() if inputs else None - if inp: - try: - ret.send(inp) - except TypeError: - next(ret) - ret = ret.send(inp) - else: - next(ret) - except StopIteration: - break - - cmdobj.at_post_cmd() - except StopIteration: - pass - except InterruptCommand: - pass - - # clean out evtable sugar. We only operate on text-type - stored_msg = [ - args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs)) - for name, args, kwargs in receiver.msg.mock_calls - ] - # Get the first element of a tuple if msg received a tuple instead of a string - stored_msg = [str(smsg[0]) if isinstance(smsg, tuple) else str(smsg) for smsg in stored_msg] - if msg is not None: - msg = str(msg) # to be safe, e.g. `py` command may return ints - # set our separator for returned messages based on parsing ansi or not - msg_sep = "|" if noansi else "||" - # Have to strip ansi for each returned message for the regex to handle it correctly - returned_msg = msg_sep.join( - _RE.sub("", ansi.parse_ansi(mess, strip_ansi=noansi)) for mess in stored_msg - ).strip() - msg = msg.strip() - if msg == "" and returned_msg or not returned_msg.startswith(msg): - prt = "" - for ic, char in enumerate(msg): - import re - - prt += char - - sep1 = "\n" + "=" * 30 + "Wanted message" + "=" * 34 + "\n" - sep2 = "\n" + "=" * 30 + "Returned message" + "=" * 32 + "\n" - sep3 = "\n" + "=" * 78 - retval = sep1 + msg + sep2 + returned_msg + sep3 - raise AssertionError(retval) - else: - returned_msg = "\n".join(str(msg) for msg in stored_msg) - returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip() - receiver.msg = old_msg - - return returned_msg
- - -# ------------------------------------------------------------ -# Individual module Tests -# ------------------------------------------------------------ - - -
[docs]class TestGeneral(CommandTest): -
[docs] def test_look(self): - rid = self.room1.id - self.call(general.CmdLook(), "here", "Room(#{})\nroom_desc".format(rid))
- -
[docs] def test_home(self): - self.call(general.CmdHome(), "", "You are already home")
- -
[docs] def test_inventory(self): - self.call(general.CmdInventory(), "", "You are not carrying anything.")
- -
[docs] def test_pose(self): - self.call(general.CmdPose(), "looks around", "Char looks around")
- -
[docs] def test_nick(self): - self.call( - general.CmdNick(), - "testalias = testaliasedstring1", - "Inputline-nick 'testalias' mapped to 'testaliasedstring1'.", - ) - self.call( - general.CmdNick(), - "/account testalias = testaliasedstring2", - "Account-nick 'testalias' mapped to 'testaliasedstring2'.", - ) - self.call( - general.CmdNick(), - "/object testalias = testaliasedstring3", - "Object-nick 'testalias' mapped to 'testaliasedstring3'.", - ) - self.assertEqual("testaliasedstring1", self.char1.nicks.get("testalias")) - self.assertEqual( - "testaliasedstring2", self.char1.nicks.get("testalias", category="account") - ) - self.assertEqual(None, self.char1.account.nicks.get("testalias", category="account")) - self.assertEqual("testaliasedstring3", self.char1.nicks.get("testalias", category="object"))
- -
[docs] def test_get_and_drop(self): - self.call(general.CmdGet(), "Obj", "You pick up Obj.") - self.call(general.CmdDrop(), "Obj", "You drop Obj.")
- -
[docs] def test_give(self): - self.call(general.CmdGive(), "Obj to Char2", "You aren't carrying Obj.") - self.call(general.CmdGive(), "Obj = Char2", "You aren't carrying Obj.") - self.call(general.CmdGet(), "Obj", "You pick up Obj.") - self.call(general.CmdGive(), "Obj to Char2", "You give") - self.call(general.CmdGive(), "Obj = Char", "You give", caller=self.char2)
- -
[docs] def test_mux_command(self): - class CmdTest(MuxCommand): - key = "test" - switch_options = ("test", "testswitch", "testswitch2") - - def func(self): - self.msg("Switches matched: {}".format(self.switches)) - - self.call( - CmdTest(), - "/test/testswitch/testswitch2", - "Switches matched: ['test', 'testswitch', 'testswitch2']", - ) - self.call(CmdTest(), "/test", "Switches matched: ['test']") - self.call(CmdTest(), "/test/testswitch", "Switches matched: ['test', 'testswitch']") - self.call( - CmdTest(), "/testswitch/testswitch2", "Switches matched: ['testswitch', 'testswitch2']" - ) - self.call(CmdTest(), "/testswitch", "Switches matched: ['testswitch']") - self.call(CmdTest(), "/testswitch2", "Switches matched: ['testswitch2']") - self.call( - CmdTest(), - "/t", - "test: Ambiguous switch supplied: " - "Did you mean /test or /testswitch or /testswitch2?|Switches matched: []", - ) - self.call( - CmdTest(), - "/tests", - "test: Ambiguous switch supplied: " - "Did you mean /testswitch or /testswitch2?|Switches matched: []", - )
- -
[docs] def test_say(self): - self.call(general.CmdSay(), "Testing", 'You say, "Testing"')
- -
[docs] def test_whisper(self): - self.call( - general.CmdWhisper(), - "Obj = Testing", - 'You whisper to Obj, "Testing"', - caller=self.char2, - )
- -
[docs] def test_access(self): - self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):")
- - -
[docs]class TestHelp(CommandTest): -
[docs] def test_help(self): - self.call(help.CmdHelp(), "", "Command help entries", cmdset=CharacterCmdSet())
- -
[docs] def test_set_help(self): - self.call( - help.CmdSetHelp(), - "testhelp, General = This is a test", - "Topic 'testhelp' was successfully created.", - ) - self.call(help.CmdHelp(), "testhelp", "Help for testhelp", cmdset=CharacterCmdSet())
- - -
[docs]class TestSystem(CommandTest): -
[docs] def test_py(self): - # we are not testing CmdReload, CmdReset and CmdShutdown, CmdService or CmdTime - # since the server is not running during these tests. - self.call(system.CmdPy(), "1+2", ">>> 1+2|3") - self.call(system.CmdPy(), "/clientraw 1+2", ">>> 1+2|3")
- -
[docs] def test_scripts(self): - self.call(system.CmdScripts(), "", "dbref ")
- -
[docs] def test_objects(self): - self.call(system.CmdObjects(), "", "Object subtype totals")
- -
[docs] def test_about(self): - self.call(system.CmdAbout(), "", None)
- -
[docs] def test_server_load(self): - self.call(system.CmdServerLoad(), "", "Server CPU and Memory load:")
- - -
[docs]class TestAdmin(CommandTest): -
[docs] def test_emit(self): - self.call(admin.CmdEmit(), "Char2 = Test", "Emitted to Char2:\nTest")
- -
[docs] def test_perm(self): - self.call( - admin.CmdPerm(), - "Obj = Builder", - "Permission 'Builder' given to Obj (the Object/Character).", - ) - self.call( - admin.CmdPerm(), - "Char2 = Builder", - "Permission 'Builder' given to Char2 (the Object/Character).", - )
- -
[docs] def test_wall(self): - self.call(admin.CmdWall(), "Test", "Announcing to all connected sessions ...")
- -
[docs] def test_ban(self): - self.call(admin.CmdBan(), "Char", "Name-Ban char was added.")
- -
[docs] def test_force(self): - cid = self.char2.id - self.call( - admin.CmdForce(), - "Char2=say test", - 'Char2(#{}) says, "test"|You have forced Char2 to: say test'.format(cid), - )
- - -
[docs]class TestAccount(CommandTest): -
[docs] def test_ooc_look(self): - if settings.MULTISESSION_MODE < 2: - self.call( - account.CmdOOCLook(), "", "You are out-of-character (OOC).", caller=self.account - ) - if settings.MULTISESSION_MODE == 2: - self.call( - account.CmdOOCLook(), - "", - "Account TestAccount (you are OutofCharacter)", - caller=self.account, - )
- -
[docs] def test_ooc(self): - self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account)
- -
[docs] def test_ic(self): - self.account.db._playable_characters = [self.char1] - self.account.unpuppet_object(self.session) - self.call( - account.CmdIC(), "Char", "You become Char.", caller=self.account, receiver=self.char1 - )
- -
[docs] def test_ic__other_object(self): - self.account.db._playable_characters = [self.obj1] - self.account.unpuppet_object(self.session) - self.call( - account.CmdIC(), "Obj", "You become Obj.", caller=self.account, receiver=self.obj1 - )
- -
[docs] def test_ic__nonaccess(self): - self.account.unpuppet_object(self.session) - self.call( - account.CmdIC(), - "Nonexistent", - "That is not a valid character choice.", - caller=self.account, - receiver=self.account, - )
- -
[docs] def test_password(self): - self.call( - account.CmdPassword(), - "testpassword = testpassword", - "Password changed.", - caller=self.account, - )
- -
[docs] def test_option(self): - self.call(account.CmdOption(), "", "Client settings", caller=self.account)
- -
[docs] def test_who(self): - self.call(account.CmdWho(), "", "Accounts:", caller=self.account)
- -
[docs] def test_quit(self): - self.call( - account.CmdQuit(), "", "Quitting. Hope to see you again, soon.", caller=self.account - )
- -
[docs] def test_sessions(self): - self.call(account.CmdSessions(), "", "Your current session(s):", caller=self.account)
- -
[docs] def test_color_test(self): - self.call(account.CmdColorTest(), "ansi", "ANSI colors:", caller=self.account)
- -
[docs] def test_char_create(self): - self.call( - account.CmdCharCreate(), - "Test1=Test char", - "Created new character Test1. Use ic Test1 to enter the game", - caller=self.account, - )
- -
[docs] def test_char_delete(self): - # Chardelete requires user input; this test is mainly to confirm - # whether permissions are being checked - - # Add char to account playable characters - self.account.db._playable_characters.append(self.char1) - - # Try deleting as Developer - self.call( - account.CmdCharDelete(), - "Char", - "This will permanently destroy 'Char'. This cannot be undone. Continue yes/[no]?", - caller=self.account, - ) - - # Downgrade permissions on account - self.account.permissions.add("Player") - self.account.permissions.remove("Developer") - - # Set lock on character object to prevent deletion - self.char1.locks.add("delete:none()") - - # Try deleting as Player - self.call( - account.CmdCharDelete(), - "Char", - "You do not have permission to delete this character.", - caller=self.account, - ) - - # Set lock on character object to allow self-delete - self.char1.locks.add("delete:pid(%i)" % self.account.id) - - # Try deleting as Player again - self.call( - account.CmdCharDelete(), - "Char", - "This will permanently destroy 'Char'. This cannot be undone. Continue yes/[no]?", - caller=self.account, - )
- -
[docs] def test_quell(self): - self.call( - account.CmdQuell(), - "", - "Quelling to current puppet's permissions (developer).", - caller=self.account, - )
- - -
[docs]class TestBuilding(CommandTest): -
[docs] def test_create(self): - name = settings.BASE_OBJECT_TYPECLASS.rsplit(".", 1)[1] - self.call( - building.CmdCreate(), - "/d TestObj1", # /d switch is abbreviated form of /drop - "You create a new %s: TestObj1." % name, - ) - self.call(building.CmdCreate(), "", "Usage: ") - self.call( - building.CmdCreate(), - "TestObj1;foo;bar", - "You create a new %s: TestObj1 (aliases: foo, bar)." % name, - )
- -
[docs] def test_examine(self): - self.call(building.CmdExamine(), "", "Name/key: Room") - self.call(building.CmdExamine(), "Obj", "Name/key: Obj") - self.call(building.CmdExamine(), "Obj", "Name/key: Obj") - self.call(building.CmdExamine(), "*TestAccount", "Name/key: TestAccount") - - self.char1.db.test = "testval" - self.call(building.CmdExamine(), "self/test", "Persistent attribute(s):\n test = testval") - self.call(building.CmdExamine(), "NotFound", "Could not find 'NotFound'.") - self.call(building.CmdExamine(), "out", "Name/key: out") - - # escape inlinefuncs - self.char1.db.test2 = "this is a $random() value." - self.call( - building.CmdExamine(), - "self/test2", - "Persistent attribute(s):\n test2 = this is a \$random() value.", - ) - - self.room1.scripts.add(self.script.__class__) - self.call(building.CmdExamine(), "") - self.account.scripts.add(self.script.__class__) - self.call(building.CmdExamine(), "*TestAccount")
- -
[docs] def test_set_obj_alias(self): - oid = self.obj1.id - self.call(building.CmdSetObjAlias(), "Obj =", "Cleared aliases from Obj") - self.call( - building.CmdSetObjAlias(), - "Obj = TestObj1b", - "Alias(es) for 'Obj(#{})' set to 'testobj1b'.".format(oid), - ) - self.call(building.CmdSetObjAlias(), "", "Usage: ") - self.call(building.CmdSetObjAlias(), "NotFound =", "Could not find 'NotFound'.") - - self.call(building.CmdSetObjAlias(), "Obj", "Aliases for Obj(#{}): 'testobj1b'".format(oid)) - self.call(building.CmdSetObjAlias(), "Obj2 =", "Cleared aliases from Obj2") - self.call(building.CmdSetObjAlias(), "Obj2 =", "No aliases to clear.")
- -
[docs] def test_copy(self): - self.call( - building.CmdCopy(), - "Obj = TestObj2;TestObj2b, TestObj3;TestObj3b", - "Copied Obj to 'TestObj3' (aliases: ['TestObj3b']", - ) - self.call(building.CmdCopy(), "", "Usage: ") - self.call(building.CmdCopy(), "Obj", "Identical copy of Obj, named 'Obj_copy' was created.") - self.call(building.CmdCopy(), "NotFound = Foo", "Could not find 'NotFound'.")
- -
[docs] def test_attribute_commands(self): - self.call(building.CmdSetAttribute(), "", "Usage: ") - self.call( - building.CmdSetAttribute(), - 'Obj/test1="value1"', - "Created attribute Obj/test1 = 'value1'", - ) - self.call( - building.CmdSetAttribute(), - 'Obj2/test2="value2"', - "Created attribute Obj2/test2 = 'value2'", - ) - self.call(building.CmdSetAttribute(), "Obj2/test2", "Attribute Obj2/test2 = value2") - self.call(building.CmdSetAttribute(), "Obj2/NotFound", "Obj2 has no attribute 'notfound'.") - - with patch("evennia.commands.default.building.EvEditor") as mock_ed: - self.call(building.CmdSetAttribute(), "/edit Obj2/test3") - mock_ed.assert_called_with(self.char1, Anything, Anything, key="Obj2/test3") - - self.call( - building.CmdSetAttribute(), - 'Obj2/test3="value3"', - "Created attribute Obj2/test3 = 'value3'", - ) - self.call( - building.CmdSetAttribute(), - "Obj2/test3 = ", - "Deleted attribute 'test3' (= True) from Obj2.", - ) - - self.call( - building.CmdCpAttr(), - "/copy Obj2/test2 = Obj2/test3", - 'cpattr: Extra switch "/copy" ignored.|\nCopied Obj2.test2 -> Obj2.test3. ' - "(value: 'value2')", - ) - self.call(building.CmdMvAttr(), "", "Usage: ") - self.call(building.CmdMvAttr(), "Obj2/test2 = Obj/test3", "Moved Obj2.test2 -> Obj.test3") - self.call(building.CmdCpAttr(), "", "Usage: ") - self.call(building.CmdCpAttr(), "Obj/test1 = Obj2/test3", "Copied Obj.test1 -> Obj2.test3") - - self.call(building.CmdWipe(), "", "Usage: ") - self.call(building.CmdWipe(), "Obj2/test2/test3", "Wiped attributes test2,test3 on Obj2.") - self.call(building.CmdWipe(), "Obj2", "Wiped all attributes on Obj2.")
- -
[docs] def test_nested_attribute_commands(self): - # list - adding white space proves real parsing - self.call( - building.CmdSetAttribute(), "Obj/test1=[1,2]", "Created attribute Obj/test1 = [1, 2]" - ) - self.call(building.CmdSetAttribute(), "Obj/test1", "Attribute Obj/test1 = [1, 2]") - self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 1") - self.call(building.CmdSetAttribute(), "Obj/test1[1]", "Attribute Obj/test1[1] = 2") - self.call( - building.CmdSetAttribute(), - "Obj/test1[0] = 99", - "Modified attribute Obj/test1 = [99, 2]", - ) - self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 99") - # list delete - self.call( - building.CmdSetAttribute(), - "Obj/test1[0] =", - "Deleted attribute 'test1[0]' (= nested) from Obj.", - ) - self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 2") - self.call( - building.CmdSetAttribute(), - "Obj/test1[1]", - "Obj has no attribute 'test1[1]'. (Nested lookups attempted)", - ) - # Delete non-existent - self.call( - building.CmdSetAttribute(), - "Obj/test1[5] =", - "Obj has no attribute 'test1[5]'. (Nested lookups attempted)", - ) - # Append - self.call( - building.CmdSetAttribute(), - "Obj/test1[+] = 42", - "Modified attribute Obj/test1 = [2, 42]", - ) - self.call( - building.CmdSetAttribute(), - "Obj/test1[+0] = -1", - "Modified attribute Obj/test1 = [-1, 2, 42]", - ) - - # dict - removing white space proves real parsing - self.call( - building.CmdSetAttribute(), - "Obj/test2={ 'one': 1, 'two': 2 }", - "Created attribute Obj/test2 = {'one': 1, 'two': 2}", - ) - self.call( - building.CmdSetAttribute(), "Obj/test2", "Attribute Obj/test2 = {'one': 1, 'two': 2}" - ) - self.call(building.CmdSetAttribute(), "Obj/test2['one']", "Attribute Obj/test2['one'] = 1") - self.call(building.CmdSetAttribute(), "Obj/test2['one]", "Attribute Obj/test2['one] = 1") - self.call( - building.CmdSetAttribute(), - "Obj/test2['one']=99", - "Modified attribute Obj/test2 = {'one': 99, 'two': 2}", - ) - self.call(building.CmdSetAttribute(), "Obj/test2['one']", "Attribute Obj/test2['one'] = 99") - self.call(building.CmdSetAttribute(), "Obj/test2['two']", "Attribute Obj/test2['two'] = 2") - self.call( - building.CmdSetAttribute(), - "Obj/test2[+'three']", - "Obj has no attribute 'test2[+'three']'. (Nested lookups attempted)", - ) - self.call( - building.CmdSetAttribute(), - "Obj/test2[+'three'] = 3", - "Modified attribute Obj/test2 = {'one': 99, 'two': 2, \"+'three'\": 3}", - ) - self.call( - building.CmdSetAttribute(), - "Obj/test2[+'three'] =", - "Deleted attribute 'test2[+'three']' (= nested) from Obj.", - ) - self.call( - building.CmdSetAttribute(), - "Obj/test2['three']=3", - "Modified attribute Obj/test2 = {'one': 99, 'two': 2, 'three': 3}", - ) - # Dict delete - self.call( - building.CmdSetAttribute(), - "Obj/test2['two'] =", - "Deleted attribute 'test2['two']' (= nested) from Obj.", - ) - self.call( - building.CmdSetAttribute(), - "Obj/test2['two']", - "Obj has no attribute 'test2['two']'. (Nested lookups attempted)", - ) - self.call( - building.CmdSetAttribute(), "Obj/test2", "Attribute Obj/test2 = {'one': 99, 'three': 3}" - ) - self.call( - building.CmdSetAttribute(), - "Obj/test2[0]", - "Obj has no attribute 'test2[0]'. (Nested lookups attempted)", - ) - self.call( - building.CmdSetAttribute(), - "Obj/test2['five'] =", - "Obj has no attribute 'test2['five']'. (Nested lookups attempted)", - ) - self.call( - building.CmdSetAttribute(), - "Obj/test2[+]=42", - "Modified attribute Obj/test2 = {'one': 99, 'three': 3, '+': 42}", - ) - self.call( - building.CmdSetAttribute(), - "Obj/test2[+1]=33", - "Modified attribute Obj/test2 = {'one': 99, 'three': 3, '+': 42, '+1': 33}", - ) - - # tuple - self.call( - building.CmdSetAttribute(), "Obj/tup = (1,2)", "Created attribute Obj/tup = (1, 2)" - ) - self.call( - building.CmdSetAttribute(), - "Obj/tup[1] = 99", - "'tuple' object does not support item assignment - (1, 2)", - ) - self.call( - building.CmdSetAttribute(), - "Obj/tup[+] = 99", - "'tuple' object does not support item assignment - (1, 2)", - ) - self.call( - building.CmdSetAttribute(), - "Obj/tup[+1] = 99", - "'tuple' object does not support item assignment - (1, 2)", - ) - self.call( - building.CmdSetAttribute(), - # Special case for tuple, could have a better message - "Obj/tup[1] = ", - "Obj has no attribute 'tup[1]'. (Nested lookups attempted)", - ) - - # Deaper nesting - self.call( - building.CmdSetAttribute(), - "Obj/test3=[{'one': 1}]", - "Created attribute Obj/test3 = [{'one': 1}]", - ) - self.call( - building.CmdSetAttribute(), "Obj/test3[0]['one']", "Attribute Obj/test3[0]['one'] = 1" - ) - self.call(building.CmdSetAttribute(), "Obj/test3[0]", "Attribute Obj/test3[0] = {'one': 1}") - self.call( - building.CmdSetAttribute(), - "Obj/test3[0]['one'] =", - "Deleted attribute 'test3[0]['one']' (= nested) from Obj.", - ) - self.call(building.CmdSetAttribute(), "Obj/test3[0]", "Attribute Obj/test3[0] = {}") - self.call(building.CmdSetAttribute(), "Obj/test3", "Attribute Obj/test3 = [{}]") - - # Naughty keys - self.call( - building.CmdSetAttribute(), - "Obj/test4[0]='foo'", - "Created attribute Obj/test4[0] = 'foo'", - ) - self.call(building.CmdSetAttribute(), "Obj/test4[0]", "Attribute Obj/test4[0] = foo") - self.call( - building.CmdSetAttribute(), - "Obj/test4=[{'one': 1}]", - "Created attribute Obj/test4 = [{'one': 1}]", - ) - self.call( - building.CmdSetAttribute(), "Obj/test4[0]['one']", "Attribute Obj/test4[0]['one'] = 1" - ) - # Prefer nested items - self.call(building.CmdSetAttribute(), "Obj/test4[0]", "Attribute Obj/test4[0] = {'one': 1}") - self.call( - building.CmdSetAttribute(), "Obj/test4[0]['one']", "Attribute Obj/test4[0]['one'] = 1" - ) - # Restored access - self.call(building.CmdWipe(), "Obj/test4", "Wiped attributes test4 on Obj.") - self.call(building.CmdSetAttribute(), "Obj/test4[0]", "Attribute Obj/test4[0] = foo") - self.call( - building.CmdSetAttribute(), - "Obj/test4[0]['one']", - "Obj has no attribute 'test4[0]['one']'.", - )
- -
[docs] def test_split_nested_attr(self): - split_nested_attr = building.CmdSetAttribute().split_nested_attr - test_cases = { - "test1": [("test1", [])], - 'test2["dict"]': [("test2", ["dict"]), ('test2["dict"]', [])], - # Quotes not actually required - "test3[dict]": [("test3", ["dict"]), ("test3[dict]", [])], - 'test4["dict]': [("test4", ["dict"]), ('test4["dict]', [])], - # duplicate keys don't cause issues - "test5[0][0]": [("test5", [0, 0]), ("test5[0]", [0]), ("test5[0][0]", [])], - # String ints preserved - 'test6["0"][0]': [("test6", ["0", 0]), ('test6["0"]', [0]), ('test6["0"][0]', [])], - # Unmatched [] - "test7[dict": [("test7[dict", [])], - } - - for attr, result in test_cases.items(): - self.assertEqual(list(split_nested_attr(attr)), result)
- -
[docs] def test_do_nested_lookup(self): - do_nested_lookup = building.CmdSetAttribute().do_nested_lookup - not_found = building.CmdSetAttribute.not_found - - def do_test_single(value, key, result): - self.assertEqual(do_nested_lookup(value, key), result) - - def do_test_multi(value, keys, result): - self.assertEqual(do_nested_lookup(value, *keys), result) - - do_test_single([], "test1", not_found) - do_test_single([1], "test2", not_found) - do_test_single([], 0, not_found) - do_test_single([], "0", not_found) - do_test_single([1], 2, not_found) - do_test_single([1], 0, 1) - do_test_single([1], "0", not_found) # str key is str not int - do_test_single({}, "test3", not_found) - do_test_single({}, 0, not_found) - do_test_single({"foo": "bar"}, "foo", "bar") - - do_test_multi({"one": [1, 2, 3]}, ("one", 0), 1) - do_test_multi([{}, {"two": 2}, 3], (1, "two"), 2)
- -
[docs] def test_name(self): - self.call(building.CmdName(), "", "Usage: ") - self.call(building.CmdName(), "Obj2=Obj3", "Object's name changed to 'Obj3'.") - self.call( - building.CmdName(), - "*TestAccount=TestAccountRenamed", - "Account's name changed to 'TestAccountRenamed'.", - ) - self.call(building.CmdName(), "*NotFound=TestAccountRenamed", "Could not find '*NotFound'") - self.call( - building.CmdName(), "Obj3=Obj4;foo;bar", "Object's name changed to 'Obj4' (foo, bar)." - ) - self.call(building.CmdName(), "Obj4=", "No names or aliases defined!")
- -
[docs] def test_desc(self): - oid = self.obj2.id - self.call( - building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2(#{}).".format(oid) - ) - self.call(building.CmdDesc(), "", "Usage: ") - - with patch("evennia.commands.default.building.EvEditor") as mock_ed: - self.call(building.CmdDesc(), "/edit") - mock_ed.assert_called_with( - self.char1, - key="desc", - loadfunc=building._desc_load, - quitfunc=building._desc_quit, - savefunc=building._desc_save, - persistent=True, - )
- -
[docs] def test_empty_desc(self): - """ - empty desc sets desc as '' - """ - oid = self.obj2.id - o2d = self.obj2.db.desc - r1d = self.room1.db.desc - self.call(building.CmdDesc(), "Obj2=", "The description was set on Obj2(#{}).".format(oid)) - assert self.obj2.db.desc == "" and self.obj2.db.desc != o2d - assert self.room1.db.desc == r1d
- -
[docs] def test_desc_default_to_room(self): - """no rhs changes room's desc""" - rid = self.room1.id - o2d = self.obj2.db.desc - r1d = self.room1.db.desc - self.call(building.CmdDesc(), "Obj2", "The description was set on Room(#{}).".format(rid)) - assert self.obj2.db.desc == o2d - assert self.room1.db.desc == "Obj2" and self.room1.db.desc != r1d
- -
[docs] def test_destroy(self): - confirm = building.CmdDestroy.confirm - building.CmdDestroy.confirm = False - self.call(building.CmdDestroy(), "", "Usage: ") - self.call(building.CmdDestroy(), "Obj", "Obj was destroyed.") - self.call(building.CmdDestroy(), "Obj", "Obj2 was destroyed.") - self.call( - building.CmdDestroy(), - "Obj", - "Could not find 'Obj'.| (Objects to destroy " - "must either be local or specified with a unique #dbref.)", - ) - self.call( - building.CmdDestroy(), settings.DEFAULT_HOME, "You are trying to delete" - ) # DEFAULT_HOME should not be deleted - self.char2.location = self.room2 - charid = self.char2.id - room1id = self.room1.id - room2id = self.room2.id - self.call( - building.CmdDestroy(), - self.room2.dbref, - "Char2(#{}) arrives to Room(#{}) from Room2(#{}).|Room2 was destroyed.".format( - charid, room1id, room2id - ), - ) - building.CmdDestroy.confirm = confirm
- -
[docs] def test_destroy_sequence(self): - confirm = building.CmdDestroy.confirm - building.CmdDestroy.confirm = False - self.call( - building.CmdDestroy(), - "{}-{}".format(self.obj1.dbref, self.obj2.dbref), - "Obj was destroyed.\nObj2 was destroyed.", - )
- -
[docs] def test_dig(self): - self.call(building.CmdDig(), "TestRoom1=testroom;tr,back;b", "Created room TestRoom1") - self.call(building.CmdDig(), "", "Usage: ")
- -
[docs] def test_tunnel(self): - self.call(building.CmdTunnel(), "n = TestRoom2;test2", "Created room TestRoom2") - self.call(building.CmdTunnel(), "", "Usage: ") - self.call(building.CmdTunnel(), "foo = TestRoom2;test2", "tunnel can only understand the") - self.call(building.CmdTunnel(), "/tel e = TestRoom3;test3", "Created room TestRoom3") - DefaultRoom.objects.get_family(db_key="TestRoom3") - exits = DefaultExit.objects.filter_family(db_key__in=("east", "west")) - self.assertEqual(len(exits), 2)
- -
[docs] def test_tunnel_exit_typeclass(self): - self.call( - building.CmdTunnel(), - "n:evennia.objects.objects.DefaultExit = TestRoom3", - "Created room TestRoom3", - )
- -
[docs] def test_exit_commands(self): - self.call( - building.CmdOpen(), "TestExit1=Room2", "Created new Exit 'TestExit1' from Room to Room2" - ) - self.call(building.CmdLink(), "TestExit1=Room", "Link created TestExit1 -> Room (one way).") - self.call(building.CmdUnLink(), "", "Usage: ") - self.call(building.CmdLink(), "NotFound", "Could not find 'NotFound'.") - self.call(building.CmdLink(), "TestExit", "TestExit1 is an exit to Room.") - self.call(building.CmdLink(), "Obj", "Obj is not an exit. Its home location is Room.") - self.call( - building.CmdUnLink(), "TestExit1", "Former exit TestExit1 no longer links anywhere." - ) - - self.char1.location = self.room2 - self.call( - building.CmdOpen(), "TestExit2=Room", "Created new Exit 'TestExit2' from Room2 to Room." - ) - self.call( - building.CmdOpen(), - "TestExit2=Room", - "Exit TestExit2 already exists. It already points to the correct place.", - ) - - # ensure it matches locally first - self.call( - building.CmdLink(), "TestExit=Room2", "Link created TestExit2 -> Room2 (one way)." - ) - self.call( - building.CmdLink(), - "/twoway TestExit={}".format(self.exit.dbref), - "Link created TestExit2 (in Room2) <-> out (in Room) (two-way).", - ) - self.call( - building.CmdLink(), - "/twoway TestExit={}".format(self.room1.dbref), - "To create a two-way link, TestExit2 and Room must both have a location ", - ) - self.call( - building.CmdLink(), - "/twoway {}={}".format(self.exit.dbref, self.exit.dbref), - "Cannot link an object to itself.", - ) - self.call(building.CmdLink(), "", "Usage: ") - # ensure can still match globally when not a local name - self.call(building.CmdLink(), "TestExit1=Room2", "Note: TestExit1") - self.call( - building.CmdLink(), "TestExit1=", "Former exit TestExit1 no longer links anywhere." - )
- -
[docs] def test_set_home(self): - self.call( - building.CmdSetHome(), "Obj = Room2", "Home location of Obj was changed from Room" - ) - self.call(building.CmdSetHome(), "", "Usage: ") - self.call(building.CmdSetHome(), "self", "Char's current home is Room") - self.call(building.CmdSetHome(), "Obj", "Obj's current home is Room2") - self.obj1.home = None - self.call(building.CmdSetHome(), "Obj = Room2", "Home location of Obj was set to Room")
- -
[docs] def test_list_cmdsets(self): - self.call( - building.CmdListCmdSets(), - "", - "<CmdSetHandler> stack:\n <CmdSet DefaultCharacter, Union, perm, prio 0>:", - ) - self.call(building.CmdListCmdSets(), "NotFound", "Could not find 'NotFound'")
- -
[docs] def test_typeclass(self): - self.call(building.CmdTypeclass(), "", "Usage: ") - self.call( - building.CmdTypeclass(), - "Obj = evennia.objects.objects.DefaultExit", - "Obj changed typeclass from evennia.objects.objects.DefaultObject " - "to evennia.objects.objects.DefaultExit.", - ) - self.call( - building.CmdTypeclass(), - "Obj2 = evennia.objects.objects.DefaultExit", - "Obj2 changed typeclass from evennia.objects.objects.DefaultObject " - "to evennia.objects.objects.DefaultExit.", - cmdstring="swap", - ) - self.call(building.CmdTypeclass(), "/list Obj", "Core typeclasses") - self.call( - building.CmdTypeclass(), - "/show Obj", - "Obj's current typeclass is 'evennia.objects.objects.DefaultExit'", - ) - self.call( - building.CmdTypeclass(), - "Obj = evennia.objects.objects.DefaultExit", - "Obj already has the typeclass 'evennia.objects.objects.DefaultExit'. Use /force to override.", - ) - self.call( - building.CmdTypeclass(), - "/force Obj = evennia.objects.objects.DefaultExit", - "Obj updated its existing typeclass ", - ) - self.call(building.CmdTypeclass(), "Obj = evennia.objects.objects.DefaultObject") - self.call( - building.CmdTypeclass(), - "/show Obj", - "Obj's current typeclass is 'evennia.objects.objects.DefaultObject'", - ) - self.call( - building.CmdTypeclass(), - "Obj", - "Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n" - "Only the at_object_creation hook was run (update mode). Attributes set before swap were not removed.", - cmdstring="update", - ) - self.call( - building.CmdTypeclass(), - "/reset/force Obj=evennia.objects.objects.DefaultObject", - "Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n" - "All object creation hooks were run. All old attributes where deleted before the swap.", - ) - - from evennia.prototypes.prototypes import homogenize_prototype - - test_prototype = [ - homogenize_prototype( - { - "prototype_key": "testkey", - "prototype_tags": [], - "typeclass": "typeclasses.objects.Object", - "key": "replaced_obj", - "attrs": [("foo", "bar", None, ""), ("desc", "protdesc", None, "")], - } - ) - ] - with patch( - "evennia.commands.default.building.protlib.search_prototype", - new=MagicMock(return_value=test_prototype), - ) as mprot: - self.call( - building.CmdTypeclass(), - "/prototype Obj=testkey", - "replaced_obj changed typeclass from " - "evennia.objects.objects.DefaultObject to " - "typeclasses.objects.Object.\nAll object creation hooks were " - "run. Attributes set before swap were not removed. Prototype " - "'replaced_obj' was successfully applied over the object type.", - ) - assert self.obj1.db.desc == "protdesc"
- -
[docs] def test_lock(self): - self.call(building.CmdLock(), "", "Usage: ") - self.call(building.CmdLock(), "Obj = test:all()", "Added lock 'test:all()' to Obj.") - self.call( - building.CmdLock(), - "*TestAccount = test:all()", - "Added lock 'test:all()' to TestAccount", - ) - self.call(building.CmdLock(), "Obj/notfound", "Obj has no lock of access type 'notfound'.") - self.call(building.CmdLock(), "Obj/test", "test:all()") - self.call( - building.CmdLock(), - "/view Obj = edit:false()", - "Switch(es) view can not be used with a lock assignment. " - "Use e.g. lock/del objname/locktype instead.", - ) - self.call(building.CmdLock(), "Obj = control:false()") - self.call(building.CmdLock(), "Obj = edit:false()") - self.call(building.CmdLock(), "Obj/test", "You are not allowed to do that.") - self.obj1.locks.add("control:true()") - self.call(building.CmdLock(), "Obj", "call:true()") # etc - self.call(building.CmdLock(), "*TestAccount", "boot:perm(Admin)") # etc
- -
[docs] def test_find(self): - rid2 = self.room2.id - rmax = rid2 + 100 - self.call(building.CmdFind(), "", "Usage: ") - self.call(building.CmdFind(), "oom2", "One Match") - self.call(building.CmdFind(), "oom2 = 1-{}".format(rmax), "One Match") - self.call(building.CmdFind(), "oom2 = 1 {}".format(rmax), "One Match") # space works too - self.call(building.CmdFind(), "Char2", "One Match", cmdstring="locate") - self.call( - building.CmdFind(), - "/ex Char2", # /ex is an ambiguous switch - "locate: Ambiguous switch supplied: Did you mean /exit or /exact?|", - cmdstring="locate", - ) - self.call(building.CmdFind(), "Char2", "One Match", cmdstring="locate") - self.call( - building.CmdFind(), "/l Char2", "One Match", cmdstring="find" - ) # /l switch is abbreviated form of /loc - self.call(building.CmdFind(), "Char2", "One Match", cmdstring="find") - self.call(building.CmdFind(), "/startswith Room2", "One Match") - - self.call(building.CmdFind(), self.char1.dbref, "Exact dbref match") - self.call(building.CmdFind(), "*TestAccount", "Match") - - self.call(building.CmdFind(), "/char Obj", "No Matches") - self.call(building.CmdFind(), "/room Obj", "No Matches") - self.call(building.CmdFind(), "/exit Obj", "No Matches") - self.call(building.CmdFind(), "/exact Obj", "One Match") - - # Test multitype filtering - with patch( - "evennia.commands.default.building.CHAR_TYPECLASS", - "evennia.objects.objects.DefaultCharacter", - ): - self.call(building.CmdFind(), "/char/room Obj", "No Matches") - self.call(building.CmdFind(), "/char/room/exit Char", "2 Matches") - self.call(building.CmdFind(), "/char/room/exit/startswith Cha", "2 Matches") - - # Test null search - self.call(building.CmdFind(), "=", "Usage: ") - - # Test bogus dbref range with no search term - self.call(building.CmdFind(), "= obj", "Invalid dbref range provided (not a number).") - self.call(building.CmdFind(), "= #1a", "Invalid dbref range provided (not a number).") - - # Test valid dbref ranges with no search term - id1 = self.obj1.id - id2 = self.obj2.id - maxid = ObjectDB.objects.latest("id").id - maxdiff = maxid - id1 + 1 - mdiff = id2 - id1 + 1 - - self.call(building.CmdFind(), f"=#{id1}", f"{maxdiff} Matches(#{id1}-#{maxid}") - self.call(building.CmdFind(), f"={id1}-{id2}", f"{mdiff} Matches(#{id1}-#{id2}):") - self.call(building.CmdFind(), f"={id1} - {id2}", f"{mdiff} Matches(#{id1}-#{id2}):") - self.call(building.CmdFind(), f"={id1}- #{id2}", f"{mdiff} Matches(#{id1}-#{id2}):") - self.call(building.CmdFind(), f"={id1}-#{id2}", f"{mdiff} Matches(#{id1}-#{id2}):") - self.call(building.CmdFind(), f"=#{id1}-{id2}", f"{mdiff} Matches(#{id1}-#{id2}):")
- -
[docs] def test_script(self): - self.call(building.CmdScript(), "Obj = ", "No scripts defined on Obj") - self.call( - building.CmdScript(), "Obj = scripts.Script", "Script scripts.Script successfully added" - ) - self.call(building.CmdScript(), "", "Usage: ") - self.call( - building.CmdScript(), - "= Obj", - "To create a global script you need scripts/add <typeclass>.", - ) - self.call(building.CmdScript(), "Obj ", "dbref ") - - self.call( - building.CmdScript(), "/start Obj", "0 scripts started on Obj" - ) # because it's already started - self.call(building.CmdScript(), "/stop Obj", "Stopping script") - - self.call( - building.CmdScript(), "Obj = scripts.Script", "Script scripts.Script successfully added" - ) - self.call( - building.CmdScript(), - "/start Obj = scripts.Script", - "Script scripts.Script could not be (re)started.", - ) - self.call( - building.CmdScript(), - "/stop Obj = scripts.Script", - "Script stopped and removed from object.", - )
- -
[docs] def test_teleport(self): - oid = self.obj1.id - rid = self.room1.id - rid2 = self.room2.id - self.call(building.CmdTeleport(), "", "Usage: ") - self.call(building.CmdTeleport(), "Obj = Room", "Obj is already at Room.") - self.call( - building.CmdTeleport(), - "Obj = NotFound", - "Could not find 'NotFound'.|Destination not found.", - ) - self.call( - building.CmdTeleport(), - "Obj = Room2", - "Obj(#{}) is leaving Room(#{}), heading for Room2(#{}).|Teleported Obj -> Room2.".format( - oid, rid, rid2 - ), - ) - self.call(building.CmdTeleport(), "NotFound = Room", "Could not find 'NotFound'.") - self.call( - building.CmdTeleport(), "Obj = Obj", "You can't teleport an object inside of itself!" - ) - - self.call(building.CmdTeleport(), "/tonone Obj2", "Teleported Obj2 -> None-location.") - self.call(building.CmdTeleport(), "/quiet Room2", "Room2(#{})".format(rid2)) - self.call( - building.CmdTeleport(), - "/t", # /t switch is abbreviated form of /tonone - "Cannot teleport a puppeted object (Char, puppeted by TestAccount", - ) - self.call( - building.CmdTeleport(), - "/l Room2", # /l switch is abbreviated form of /loc - "Destination has no location.", - ) - self.call( - building.CmdTeleport(), - "/q me to Room2", # /q switch is abbreviated form of /quiet - "Char is already at Room2.", - )
- -
[docs] def test_tag(self): - self.call(building.CmdTag(), "", "Usage: ") - - self.call(building.CmdTag(), "Obj = testtag") - self.call(building.CmdTag(), "Obj = testtag2") - self.call(building.CmdTag(), "Obj = testtag2:category1") - self.call(building.CmdTag(), "Obj = testtag3") - - self.call( - building.CmdTag(), - "Obj", - "Tags on Obj: 'testtag', 'testtag2', " "'testtag2' (category: category1), 'testtag3'", - ) - - self.call(building.CmdTag(), "/search NotFound", "No objects found with tag 'NotFound'.") - self.call(building.CmdTag(), "/search testtag", "Found 1 object with tag 'testtag':") - self.call(building.CmdTag(), "/search testtag2", "Found 1 object with tag 'testtag2':") - self.call( - building.CmdTag(), - "/search testtag2:category1", - "Found 1 object with tag 'testtag2' (category: 'category1'):", - ) - - self.call(building.CmdTag(), "/del Obj = testtag3", "Removed tag 'testtag3' from Obj.") - self.call( - building.CmdTag(), - "/del Obj", - "Cleared all tags from Obj: testtag, testtag2, testtag2 (category: category1)", - )
- -
[docs] def test_spawn(self): - def get_object(commandTest, obj_key): - # A helper function to get a spawned object and - # check that it exists in the process. - query = search_object(obj_key) - commandTest.assertIsNotNone(query) - commandTest.assertTrue(bool(query)) - obj = query[0] - commandTest.assertIsNotNone(obj) - return obj - - # Tests "spawn" without any arguments. - self.call(building.CmdSpawn(), " ", "Usage: spawn") - - # Tests "spawn <prototype_dictionary>" without specifying location. - - self.call( - building.CmdSpawn(), - "/save {'prototype_key': 'testprot', 'key':'Test Char', " - "'typeclass':'evennia.objects.objects.DefaultCharacter'}", - "Saved prototype: testprot", - inputs=["y"], - ) - - self.call( - building.CmdSpawn(), - "/save testprot2 = {'key':'Test Char', " - "'typeclass':'evennia.objects.objects.DefaultCharacter'}", - "(Replacing `prototype_key` in prototype with given key.)|Saved prototype: testprot2", - inputs=["y"], - ) - - self.call(building.CmdSpawn(), "/search ", "Key ") - self.call(building.CmdSpawn(), "/search test;test2", "No prototypes found.") - - self.call( - building.CmdSpawn(), - "/save {'key':'Test Char', " "'typeclass':'evennia.objects.objects.DefaultCharacter'}", - "A prototype_key must be given, either as `prototype_key = <prototype>` or as " - "a key 'prototype_key' inside the prototype structure.", - ) - - self.call(building.CmdSpawn(), "/list", "Key ") - self.call(building.CmdSpawn(), "testprot", "Spawned Test Char") - - # Tests that the spawned object's location is the same as the character's location, since - # we did not specify it. - testchar = get_object(self, "Test Char") - self.assertEqual(testchar.location, self.char1.location) - testchar.delete() - - # Test "spawn <prototype_dictionary>" with a location other than the character's. - spawnLoc = self.room2 - if spawnLoc == self.char1.location: - # Just to make sure we use a different location, in case someone changes - # char1's default location in the future... - spawnLoc = self.room1 - - self.call( - building.CmdSpawn(), - "{'prototype_key':'GOBLIN', 'typeclass':'evennia.objects.objects.DefaultCharacter', " - "'key':'goblin', 'location':'%s'}" % spawnLoc.dbref, - "Spawned goblin", - ) - goblin = get_object(self, "goblin") - # Tests that the spawned object's type is a DefaultCharacter. - self.assertIsInstance(goblin, DefaultCharacter) - self.assertEqual(goblin.location, spawnLoc) - - goblin.delete() - - # create prototype - protlib.create_prototype( - { - "key": "Ball", - "typeclass": "evennia.objects.objects.DefaultCharacter", - "prototype_key": "testball", - } - ) - - # Tests "spawn <prototype_name>" - self.call(building.CmdSpawn(), "testball", "Spawned Ball") - - ball = get_object(self, "Ball") - self.assertEqual(ball.location, self.char1.location) - self.assertIsInstance(ball, DefaultObject) - ball.delete() - - # Tests "spawn/n ..." without specifying a location. - # Location should be "None". - self.call( - building.CmdSpawn(), "/n 'BALL'", "Spawned Ball" - ) # /n switch is abbreviated form of /noloc - ball = get_object(self, "Ball") - self.assertIsNone(ball.location) - ball.delete() - - self.call( - building.CmdSpawn(), - "/noloc {'prototype_parent':'TESTBALL', 'prototype_key': 'testball', 'location':'%s'}" - % spawnLoc.dbref, - "Error: Prototype testball tries to parent itself.", - ) - - # Tests "spawn/noloc ...", but DO specify a location. - # Location should be the specified location. - self.call( - building.CmdSpawn(), - "/noloc {'prototype_parent':'TESTBALL', 'key': 'Ball', 'prototype_key': 'foo', 'location':'%s'}" - % spawnLoc.dbref, - "Spawned Ball", - ) - ball = get_object(self, "Ball") - self.assertEqual(ball.location, spawnLoc) - ball.delete() - - # test calling spawn with an invalid prototype. - self.call(building.CmdSpawn(), "'NO_EXIST'", "No prototype named 'NO_EXIST' was found.") - - # Test listing commands - self.call(building.CmdSpawn(), "/list", "Key ") - - # spawn/edit (missing prototype) - # brings up olc menu - msg = self.call(building.CmdSpawn(), "/edit") - assert "Prototype wizard" in msg - - # spawn/edit with valid prototype - # brings up olc menu loaded with prototype - msg = self.call(building.CmdSpawn(), "/edit testball") - assert "Prototype wizard" in msg - assert hasattr(self.char1.ndb._menutree, "olc_prototype") - assert ( - dict == type(self.char1.ndb._menutree.olc_prototype) - and "prototype_key" in self.char1.ndb._menutree.olc_prototype - and "key" in self.char1.ndb._menutree.olc_prototype - and "testball" == self.char1.ndb._menutree.olc_prototype["prototype_key"] - and "Ball" == self.char1.ndb._menutree.olc_prototype["key"] - ) - assert "Ball" in msg and "testball" in msg - - # spawn/edit with valid prototype (synomym) - msg = self.call(building.CmdSpawn(), "/edit BALL") - assert "Prototype wizard" in msg - assert "Ball" in msg and "testball" in msg - - # spawn/edit with invalid prototype - msg = self.call( - building.CmdSpawn(), "/edit NO_EXISTS", "No prototype named 'NO_EXISTS' was found." - ) - - # spawn/examine (missing prototype) - # lists all prototypes that exist - self.call(building.CmdSpawn(), "/examine", "You need to specify a prototype-key to show.") - - # spawn/examine with valid prototype - # prints the prototype - msg = self.call(building.CmdSpawn(), "/examine BALL") - assert "Ball" in msg and "testball" in msg - - # spawn/examine with invalid prototype - # shows error - self.call( - building.CmdSpawn(), "/examine NO_EXISTS", "No prototype named 'NO_EXISTS' was found." - )
- - -
[docs]class TestComms(CommandTest): -
[docs] def setUp(self): - super(CommandTest, self).setUp() - self.call( - comms.CmdChannelCreate(), - "testchan;test=Test Channel", - "Created channel testchan and connected to it.", - receiver=self.account, - )
- -
[docs] def test_toggle_com(self): - self.call( - comms.CmdAddCom(), - "tc = testchan", - "You are already connected to channel testchan. You can now", - receiver=self.account, - ) - self.call( - comms.CmdDelCom(), - "tc", - "Your alias 'tc' for channel testchan was cleared.", - receiver=self.account, - )
- -
[docs] def test_channels(self): - self.call( - comms.CmdChannels(), - "", - "Available channels (use comlist,addcom and delcom to manage", - receiver=self.account, - )
- -
[docs] def test_all_com(self): - self.call( - comms.CmdAllCom(), - "", - "Available channels (use comlist,addcom and delcom to manage", - receiver=self.account, - )
- -
[docs] def test_clock(self): - self.call( - comms.CmdClock(), - "testchan=send:all()", - "Lock(s) applied. Current locks on testchan:", - receiver=self.account, - )
- -
[docs] def test_cdesc(self): - self.call( - comms.CmdCdesc(), - "testchan = Test Channel", - "Description of channel 'testchan' set to 'Test Channel'.", - receiver=self.account, - )
- -
[docs] def test_cemit(self): - self.call( - comms.CmdCemit(), - "testchan = Test Message", - "[testchan] Test Message|Sent to channel testchan: Test Message", - receiver=self.account, - )
- -
[docs] def test_cwho(self): - self.call( - comms.CmdCWho(), - "testchan", - "Channel subscriptions\ntestchan:\n TestAccount", - receiver=self.account, - )
- -
[docs] def test_page(self): - self.call( - comms.CmdPage(), - "TestAccount2 = Test", - "TestAccount2 is offline. They will see your message if they list their pages later." - "|You paged TestAccount2 with: 'Test'.", - receiver=self.account, - )
- -
[docs] def test_cboot(self): - # No one else connected to boot - self.call( - comms.CmdCBoot(), - "", - "Usage: cboot[/quiet] <channel> = <account> [:reason]", - receiver=self.account, - )
- -
[docs] def test_cdestroy(self): - self.call( - comms.CmdCdestroy(), - "testchan", - "[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases." - "|Channel 'testchan' was destroyed.", - receiver=self.account, - )
- - -
[docs]class TestBatchProcess(CommandTest): -
[docs] def test_batch_commands(self): - # cannot test batchcode here, it must run inside the server process - self.call( - batchprocess.CmdBatchCommands(), - "example_batch_cmds", - "Running Batch-command processor - Automatic mode for example_batch_cmds", - ) - # we make sure to delete the button again here to stop the running reactor - confirm = building.CmdDestroy.confirm - building.CmdDestroy.confirm = False - self.call(building.CmdDestroy(), "button", "button was destroyed.") - building.CmdDestroy.confirm = confirm
- - -
[docs]class CmdInterrupt(Command): - - key = "interrupt" - -
[docs] def parse(self): - raise InterruptCommand
- -
[docs] def func(self): - self.msg("in func")
- - -
[docs]class TestInterruptCommand(CommandTest): -
[docs] def test_interrupt_command(self): - ret = self.call(CmdInterrupt(), "") - self.assertEqual(ret, "")
- - -
[docs]class TestUnconnectedCommand(CommandTest): -
[docs] def test_info_command(self): - # instead of using SERVER_START_TIME (0), we use 86400 because Windows won't let us use anything lower - gametime.SERVER_START_TIME = 86400 - expected = ( - "## BEGIN INFO 1.1\nName: %s\nUptime: %s\nConnected: %d\nVersion: Evennia %s\n## END INFO" - % ( - settings.SERVERNAME, - datetime.datetime.fromtimestamp(gametime.SERVER_START_TIME).ctime(), - SESSIONS.account_count(), - utils.get_evennia_version(), - ) - ) - self.call(unloggedin.CmdUnconnectedInfo(), "", expected) - del gametime.SERVER_START_TIME
- - -# Test syscommands - - -
[docs]class TestSystemCommands(CommandTest): -
[docs] def test_simple_defaults(self): - self.call(syscommands.SystemNoInput(), "") - self.call(syscommands.SystemNoMatch(), "Huh?")
- -
[docs] def test_multimatch(self): - # set up fake matches and store on command instance - cmdset = CmdSet() - cmdset.add(general.CmdLook()) - cmdset.add(general.CmdLook()) - matches = cmdparser.build_matches("look", cmdset) - - multimatch = syscommands.SystemMultimatch() - multimatch.matches = matches - - self.call(multimatch, "look", "")
- -
[docs] @patch("evennia.commands.default.syscommands.ChannelDB") - def test_channelcommand(self, mock_channeldb): - channel = MagicMock() - channel.msg = MagicMock() - mock_channeldb.objects.get_channel = MagicMock(return_value=channel) - - self.call(syscommands.SystemSendToChannel(), "public:Hello") - channel.msg.assert_called()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/commands/default/unloggedin.html b/docs/0.9.5/_modules/evennia/commands/default/unloggedin.html deleted file mode 100644 index 46fd98a783..0000000000 --- a/docs/0.9.5/_modules/evennia/commands/default/unloggedin.html +++ /dev/null @@ -1,605 +0,0 @@ - - - - - - - - evennia.commands.default.unloggedin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.commands.default.unloggedin

-"""
-Commands that are available from the connect screen.
-"""
-import re
-import datetime
-from codecs import lookup as codecs_lookup
-from django.conf import settings
-from evennia.comms.models import ChannelDB
-from evennia.server.sessionhandler import SESSIONS
-
-from evennia.utils import class_from_module, create, logger, utils, gametime
-from evennia.commands.cmdhandler import CMD_LOGINSTART
-
-COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-# limit symbol import for API
-__all__ = (
-    "CmdUnconnectedConnect",
-    "CmdUnconnectedCreate",
-    "CmdUnconnectedQuit",
-    "CmdUnconnectedLook",
-    "CmdUnconnectedHelp",
-    "CmdUnconnectedEncoding",
-    "CmdUnconnectedInfo",
-    "CmdUnconnectedScreenreader"
-)
-
-MULTISESSION_MODE = settings.MULTISESSION_MODE
-CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
-
-
-def create_guest_account(session):
-    """
-    Creates a guest account/character for this session, if one is available.
-
-    Args:
-        session (Session): the session which will use the guest account/character.
-
-    Returns:
-        GUEST_ENABLED (boolean), account (Account):
-            the boolean is whether guest accounts are enabled at all.
-            the Account which was created from an available guest name.
-    """
-    enabled = settings.GUEST_ENABLED
-    address = session.address
-
-    # Get account class
-    Guest = class_from_module(settings.BASE_GUEST_TYPECLASS)
-
-    # Get an available guest account
-    # authenticate() handles its own throttling
-    account, errors = Guest.authenticate(ip=address)
-    if account:
-        return enabled, account
-    else:
-        session.msg("|R%s|n" % "\n".join(errors))
-        return enabled, None
-
-
-def create_normal_account(session, name, password):
-    """
-    Creates an account with the given name and password.
-
-    Args:
-        session (Session): the session which is requesting to create an account.
-        name (str): the name that the account wants to use for login.
-        password (str): the password desired by this account, for login.
-
-    Returns:
-        account (Account): the account which was created from the name and password.
-    """
-    # Get account class
-    Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
-
-    address = session.address
-
-    # Match account name and check password
-    # authenticate() handles all its own throttling
-    account, errors = Account.authenticate(
-        username=name, password=password, ip=address, session=session
-    )
-    if not account:
-        # No accountname or password match
-        session.msg("|R%s|n" % "\n".join(errors))
-        return None
-
-    return account
-
-
-
[docs]class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS): - """ - connect to the game - - Usage (at login screen): - connect accountname password - connect "account name" "pass word" - - Use the create command to first create an account before logging in. - - If you have spaces in your name, enclose it in double quotes. - """ - - key = "connect" - aliases = ["conn", "con", "co"] - locks = "cmd:all()" # not really needed - arg_regex = r"\s.*?|$" - -
[docs] def func(self): - """ - Uses the Django admin api. Note that unlogged-in commands - have a unique position in that their func() receives - a session object instead of a source_object like all - other types of logged-in commands (this is because - there is no object yet before the account has logged in) - """ - session = self.caller - address = session.address - - args = self.args - # extract double quote parts - parts = [part.strip() for part in re.split(r"\"", args) if part.strip()] - if len(parts) == 1: - # this was (hopefully) due to no double quotes being found, or a guest login - parts = parts[0].split(None, 1) - - # Guest login - if len(parts) == 1 and parts[0].lower() == "guest": - # Get Guest typeclass - Guest = class_from_module(settings.BASE_GUEST_TYPECLASS) - - account, errors = Guest.authenticate(ip=address) - if account: - session.sessionhandler.login(session, account) - return - else: - session.msg("|R%s|n" % "\n".join(errors)) - return - - if len(parts) != 2: - session.msg("\n\r Usage (without <>): connect <name> <password>") - return - - # Get account class - Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) - - name, password = parts - account, errors = Account.authenticate( - username=name, password=password, ip=address, session=session - ) - if account: - session.sessionhandler.login(session, account) - else: - session.msg("|R%s|n" % "\n".join(errors))
- - -
[docs]class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): - """ - create a new account account - - Usage (at login screen): - create <accountname> <password> - create "account name" "pass word" - - This creates a new account account. - - If you have spaces in your name, enclose it in double quotes. - """ - - key = "create" - aliases = ["cre", "cr"] - locks = "cmd:all()" - arg_regex = r"\s.*?|$" - -
[docs] def func(self): - """Do checks and create account""" - - session = self.caller - args = self.args.strip() - - address = session.address - - # Get account class - Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) - - # extract double quoted parts - parts = [part.strip() for part in re.split(r"\"", args) if part.strip()] - if len(parts) == 1: - # this was (hopefully) due to no quotes being found - parts = parts[0].split(None, 1) - if len(parts) != 2: - string = ( - "\n Usage (without <>): create <name> <password>" - "\nIf <name> or <password> contains spaces, enclose it in double quotes." - ) - session.msg(string) - return - - username, password = parts - - # everything's ok. Create the new account account. - account, errors = Account.create( - username=username, password=password, ip=address, session=session - ) - if account: - # tell the caller everything went well. - string = "A new account '%s' was created. Welcome!" - if " " in username: - string += ( - "\n\nYou can now log in with the command 'connect \"%s\" <your password>'." - ) - else: - string += "\n\nYou can now log with the command 'connect %s <your password>'." - session.msg(string % (username, username)) - else: - session.msg("|R%s|n" % "\n".join(errors))
- - -
[docs]class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS): - """ - quit when in unlogged-in state - - Usage: - quit - - We maintain a different version of the quit command - here for unconnected accounts for the sake of simplicity. The logged in - version is a bit more complicated. - """ - - key = "quit" - aliases = ["q", "qu"] - locks = "cmd:all()" - -
[docs] def func(self): - """Simply close the connection.""" - session = self.caller - session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
- - -
[docs]class CmdUnconnectedLook(COMMAND_DEFAULT_CLASS): - """ - look when in unlogged-in state - - Usage: - look - - This is an unconnected version of the look command for simplicity. - - This is called by the server and kicks everything in gear. - All it does is display the connect screen. - """ - - key = CMD_LOGINSTART - aliases = ["look", "l"] - locks = "cmd:all()" - -
[docs] def func(self): - """Show the connect screen.""" - - callables = utils.callables_from_module(CONNECTION_SCREEN_MODULE) - if "connection_screen" in callables: - connection_screen = callables["connection_screen"]() - else: - connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE) - if not connection_screen: - connection_screen = "No connection screen found. Please contact an admin." - self.caller.msg(connection_screen)
- - -
[docs]class CmdUnconnectedHelp(COMMAND_DEFAULT_CLASS): - """ - get help when in unconnected-in state - - Usage: - help - - This is an unconnected version of the help command, - for simplicity. It shows a pane of info. - """ - - key = "help" - aliases = ["h", "?"] - locks = "cmd:all()" - -
[docs] def func(self): - """Shows help""" - - string = """ -You are not yet logged into the game. Commands available at this point: - - |wcreate|n - create a new account - |wconnect|n - connect with an existing account - |wlook|n - re-show the connection screen - |whelp|n - show this help - |wencoding|n - change the text encoding to match your client - |wscreenreader|n - make the server more suitable for use with screen readers - |wquit|n - abort the connection - -First create an account e.g. with |wcreate Anna c67jHL8p|n -Next you can connect to the game: |wconnect Anna c67jHL8p|n - -You can use the |wlook|n command if you want to see the connect screen again. - -""" - - if settings.STAFF_CONTACT_EMAIL: - string += "For support, please contact: %s" % settings.STAFF_CONTACT_EMAIL - self.caller.msg(string)
- - -
[docs]class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS): - """ - set which text encoding to use in unconnected-in state - - Usage: - encoding/switches [<encoding>] - - Switches: - clear - clear your custom encoding - - - This sets the text encoding for communicating with Evennia. This is mostly - an issue only if you want to use non-ASCII characters (i.e. letters/symbols - not found in English). If you see that your characters look strange (or you - get encoding errors), you should use this command to set the server - encoding to be the same used in your client program. - - Common encodings are utf-8 (default), latin-1, ISO-8859-1 etc. - - If you don't submit an encoding, the current encoding will be displayed - instead. - """ - - key = "encoding" - aliases = "encode" - locks = "cmd:all()" - -
[docs] def func(self): - """ - Sets the encoding. - """ - - if self.session is None: - return - - sync = False - if "clear" in self.switches: - # remove customization - old_encoding = self.session.protocol_flags.get("ENCODING", None) - if old_encoding: - string = "Your custom text encoding ('%s') was cleared." % old_encoding - else: - string = "No custom encoding was set." - self.session.protocol_flags["ENCODING"] = "utf-8" - sync = True - elif not self.args: - # just list the encodings supported - pencoding = self.session.protocol_flags.get("ENCODING", None) - string = "" - if pencoding: - string += ( - "Default encoding: |g%s|n (change with |wencoding <encoding>|n)" % pencoding - ) - encodings = settings.ENCODINGS - if encodings: - string += ( - "\nServer's alternative encodings (tested in this order):\n |g%s|n" - % ", ".join(encodings) - ) - if not string: - string = "No encodings found." - else: - # change encoding - old_encoding = self.session.protocol_flags.get("ENCODING", None) - encoding = self.args - try: - codecs_lookup(encoding) - except LookupError: - string = ( - "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n" - % (encoding, old_encoding) - ) - else: - self.session.protocol_flags["ENCODING"] = encoding - string = "Your custom text encoding was changed from '|w%s|n' to '|w%s|n'." % ( - old_encoding, - encoding, - ) - sync = True - if sync: - self.session.sessionhandler.session_portal_sync(self.session) - self.caller.msg(string.strip())
- - -
[docs]class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS): - """ - Activate screenreader mode. - - Usage: - screenreader - - Used to flip screenreader mode on and off before logging in (when - logged in, use option screenreader on). - """ - - key = "screenreader" - -
[docs] def func(self): - """Flips screenreader setting.""" - new_setting = not self.session.protocol_flags.get("SCREENREADER", False) - self.session.protocol_flags["SCREENREADER"] = new_setting - string = "Screenreader mode turned |w%s|n." % ("on" if new_setting else "off") - self.caller.msg(string) - self.session.sessionhandler.session_portal_sync(self.session)
- - -
[docs]class CmdUnconnectedInfo(COMMAND_DEFAULT_CLASS): - """ - Provides MUDINFO output, so that Evennia games can be added to Mudconnector - and Mudstats. Sadly, the MUDINFO specification seems to have dropped off the - face of the net, but it is still used by some crawlers. This implementation - was created by looking at the MUDINFO implementation in MUX2, TinyMUSH, Rhost, - and PennMUSH. - """ - - key = "info" - locks = "cmd:all()" - -
[docs] def func(self): - self.caller.msg( - "## BEGIN INFO 1.1\nName: %s\nUptime: %s\nConnected: %d\nVersion: Evennia %s\n## END INFO" - % ( - settings.SERVERNAME, - datetime.datetime.fromtimestamp(gametime.SERVER_START_TIME).ctime(), - SESSIONS.account_count(), - utils.get_evennia_version(), - ) - )
- - -def _create_account(session, accountname, password, permissions, typeclass=None, email=None): - """ - Helper function, creates an account of the specified typeclass. - """ - try: - new_account = create.create_account( - accountname, email, password, permissions=permissions, typeclass=typeclass - ) - - except Exception as e: - session.msg( - "There was an error creating the Account:\n%s\n If this problem persists, contact an admin." - % e - ) - logger.log_trace() - return False - - # This needs to be set so the engine knows this account is - # logging in for the first time. (so it knows to call the right - # hooks during login later) - new_account.db.FIRST_LOGIN = True - - # join the new account to the public channel - pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) - if not pchannel or not pchannel.connect(new_account): - string = "New account '%s' could not connect to public channel!" % new_account.key - logger.log_err(string) - return new_account - - -def _create_character(session, new_account, typeclass, home, permissions): - """ - Helper function, creates a character based on an account's name. - This is meant for Guest and MULTISESSION_MODE < 2 situations. - """ - try: - new_character = create.create_object( - typeclass, key=new_account.key, home=home, permissions=permissions - ) - # set playable character list - new_account.db._playable_characters.append(new_character) - - # allow only the character itself and the account to puppet this character (and Developers). - new_character.locks.add( - "puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" - % (new_character.id, new_account.id) - ) - - # If no description is set, set a default description - if not new_character.db.desc: - new_character.db.desc = "This is a character." - # We need to set this to have ic auto-connect to this character - new_account.db._last_puppet = new_character - except Exception as e: - session.msg( - "There was an error creating the Character:\n%s\n If this problem persists, contact an admin." - % e - ) - logger.log_trace() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/comms/admin.html b/docs/0.9.5/_modules/evennia/comms/admin.html deleted file mode 100644 index f24384d412..0000000000 --- a/docs/0.9.5/_modules/evennia/comms/admin.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - - - evennia.comms.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.comms.admin

-"""
-This defines how Comm models are displayed in the web admin interface.
-
-"""
-
-from django.contrib import admin
-from evennia.comms.models import ChannelDB
-from evennia.typeclasses.admin import AttributeInline, TagInline
-from django.conf import settings
-
-
-
[docs]class ChannelAttributeInline(AttributeInline): - """ - Inline display of Channel Attribute - experimental - - """ - - model = ChannelDB.db_attributes.through - related_field = "channeldb"
- - -
[docs]class ChannelTagInline(TagInline): - """ - Inline display of Channel Tags - experimental - - """ - - model = ChannelDB.db_tags.through - related_field = "channeldb"
- - -
[docs]class MsgAdmin(admin.ModelAdmin): - """ - Defines display for Msg objects - - """ - - list_display = ( - "id", - "db_date_created", - "db_sender", - "db_receivers", - "db_channels", - "db_message", - "db_lock_storage", - ) - list_display_links = ("id",) - ordering = ["db_date_created", "db_sender", "db_receivers", "db_channels"] - # readonly_fields = ['db_message', 'db_sender', 'db_receivers', 'db_channels'] - search_fields = ["id", "^db_date_created", "^db_message"] - save_as = True - save_on_top = True - list_select_related = True
- - -# admin.site.register(Msg, MsgAdmin) - - -
[docs]class ChannelAdmin(admin.ModelAdmin): - """ - Defines display for Channel objects - - """ - - inlines = [ChannelTagInline, ChannelAttributeInline] - list_display = ("id", "db_key", "db_lock_storage", "subscriptions") - list_display_links = ("id", "db_key") - ordering = ["db_key"] - search_fields = ["id", "db_key", "db_tags__db_key"] - save_as = True - save_on_top = True - list_select_related = True - raw_id_fields = ("db_object_subscriptions", "db_account_subscriptions") - fieldsets = ( - ( - None, - { - "fields": ( - ("db_key",), - "db_lock_storage", - "db_account_subscriptions", - "db_object_subscriptions", - ) - }, - ), - ) - -
[docs] def subscriptions(self, obj): - """ - Helper method to get subs from a channel. - - Args: - obj (Channel): The channel to get subs from. - - """ - return ", ".join([str(sub) for sub in obj.subscriptions.all()])
- -
[docs] def save_model(self, request, obj, form, change): - """ - Model-save hook. - - Args: - request (Request): Incoming request. - obj (Object): Database object. - form (Form): Form instance. - change (bool): If this is a change or a new object. - - """ - obj.save() - if not change: - # adding a new object - # have to call init with typeclass passed to it - obj.set_class_from_typeclass(typeclass_path=settings.BASE_CHANNEL_TYPECLASS) - obj.at_init()
- -
[docs] def response_add(self, request, obj, post_url_continue=None): - from django.http import HttpResponseRedirect - from django.urls import reverse - - return HttpResponseRedirect(reverse("admin:comms_channeldb_change", args=[obj.id]))
- - -admin.site.register(ChannelDB, ChannelAdmin) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/comms/channelhandler.html b/docs/0.9.5/_modules/evennia/comms/channelhandler.html deleted file mode 100644 index 538b24487f..0000000000 --- a/docs/0.9.5/_modules/evennia/comms/channelhandler.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - - - evennia.comms.channelhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.comms.channelhandler

-"""
-The channel handler, accessed from this module as CHANNEL_HANDLER is a
-singleton that handles the stored set of channels and how they are
-represented against the cmdhandler.
-
-If there is a channel named 'newbie', we want to be able to just write
-
-    newbie Hello!
-
-For this to work, 'newbie', the name of the channel, must be
-identified by the cmdhandler as a command name. The channelhandler
-stores all channels as custom 'commands' that the cmdhandler can
-import and look through.
-
-> Warning - channel names take precedence over command names, so make
-sure to not pick clashing channel names.
-
-Unless deleting a channel you normally don't need to bother about the
-channelhandler at all - the create_channel method handles the update.
-
-To delete a channel cleanly, delete the channel object, then call
-update() on the channelhandler. Or use Channel.objects.delete() which
-does this for you.
-
-"""
-from django.conf import settings
-from evennia.commands import cmdset
-from evennia.utils.logger import tail_log_file
-from evennia.utils.utils import class_from_module
-from django.utils.translation import gettext as _
-
-# we must late-import these since any overloads are likely to
-# themselves be using these classes leading to a circular import.
-
-_CHANNEL_HANDLER_CLASS = None
-_CHANNEL_COMMAND_CLASS = None
-_CHANNELDB = None
-_COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-
[docs]class ChannelCommand(_COMMAND_DEFAULT_CLASS): - """ - {channelkey} channel - - {channeldesc} - - Usage: - {lower_channelkey} <message> - {lower_channelkey}/history [start] - {lower_channelkey} off - mutes the channel - {lower_channelkey} on - unmutes the channel - - Switch: - history: View 20 previous messages, either from the end or - from <start> number of messages from the end. - - Example: - {lower_channelkey} Hello World! - {lower_channelkey}/history - {lower_channelkey}/history 30 - - """ - - # ^note that channeldesc and lower_channelkey will be filled - # automatically by ChannelHandler - - # this flag is what identifies this cmd as a channel cmd - # and branches off to the system send-to-channel command - # (which is customizable by admin) - is_channel = True - key = "general" - help_category = "Channel Names" - obj = None - arg_regex = r"\s.*?|/history.*?" - -
[docs] def parse(self): - """ - Simple parser - """ - # cmdhandler sends channame:msg here. - channelname, msg = self.args.split(":", 1) - self.history_start = None - if msg.startswith("/history"): - arg = msg[8:] - try: - self.history_start = int(arg) if arg else 0 - except ValueError: - # if no valid number was given, ignore it - pass - self.args = (channelname.strip(), msg.strip())
- -
[docs] def func(self): - """ - Create a new message and send it to channel, using - the already formatted input. - """ - global _CHANNELDB - if not _CHANNELDB: - from evennia.comms.models import ChannelDB as _CHANNELDB - - channelkey, msg = self.args - caller = self.caller - if not msg: - self.msg(_("Say what?")) - return - channel = _CHANNELDB.objects.get_channel(channelkey) - - if not channel: - self.msg(_("Channel '%s' not found.") % channelkey) - return - if not channel.has_connection(caller): - string = _("You are not connected to channel '%s'.") - self.msg(string % channelkey) - return - if not channel.access(caller, "send"): - string = _("You are not permitted to send to channel '%s'.") - self.msg(string % channelkey) - return - if msg == "on": - caller = caller if not hasattr(caller, "account") else caller.account - unmuted = channel.unmute(caller) - if unmuted: - self.msg(_("You start listening to %s.") % channel) - return - self.msg(_("You were already listening to %s.") % channel) - return - if msg == "off": - caller = caller if not hasattr(caller, "account") else caller.account - muted = channel.mute(caller) - if muted: - self.msg(_("You stop listening to %s.") % channel) - return - self.msg(_("You were already not listening to %s.") % channel) - return - if self.history_start is not None: - # Try to view history - log_file = channel.attributes.get("log_file", default="channel_%s.log" % channel.key) - - def send_msg(lines): - return self.msg( - "".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines) - ) - - tail_log_file(log_file, self.history_start, 20, callback=send_msg) - else: - caller = caller if not hasattr(caller, "account") else caller.account - if caller in channel.mutelist: - self.msg(_("You currently have %s muted.") % channel) - return - channel.msg(msg, senders=self.caller, online=True)
- -
[docs] def get_extra_info(self, caller, **kwargs): - """ - Let users know that this command is for communicating on a channel. - - Args: - caller (TypedObject): A Character or Account who has entered an ambiguous command. - - Returns: - A string with identifying information to disambiguate the object, conventionally with a preceding space. - """ - return _(" (channel)")
- - -
[docs]class ChannelHandler(object): - """ - The ChannelHandler manages all active in-game channels and - dynamically creates channel commands for users so that they can - just give the channel's key or alias to write to it. Whenever a - new channel is created in the database, the update() method on - this handler must be called to sync it with the database (this is - done automatically if creating the channel with - evennia.create_channel()) - - """ - -
[docs] def __init__(self): - """ - Initializes the channel handler's internal state. - - """ - self._cached_channel_cmds = {} - self._cached_cmdsets = {} - self._cached_channels = {}
- - def __str__(self): - """ - Returns the string representation of the handler - - """ - return ", ".join(str(cmd) for cmd in self._cached_channel_cmds) - -
[docs] def clear(self): - """ - Reset the cache storage. - - """ - self._cached_channel_cmds = {} - self._cached_cmdsets = {} - self._cached_channels = {}
- -
[docs] def add(self, channel): - """ - Add an individual channel to the handler. This is called - whenever a new channel is created. - - Args: - channel (Channel): The channel to add. - - Notes: - To remove a channel, simply delete the channel object and - run self.update on the handler. This should usually be - handled automatically by one of the deletion methos of - the Channel itself. - - """ - global _CHANNEL_COMMAND_CLASS - if not _CHANNEL_COMMAND_CLASS: - _CHANNEL_COMMAND_CLASS = class_from_module(settings.CHANNEL_COMMAND_CLASS) - - # map the channel to a searchable command - cmd = _CHANNEL_COMMAND_CLASS( - key=channel.key.strip().lower(), - aliases=channel.aliases.all(), - locks="cmd:all();%s" % channel.locks, - help_category="Channel names", - obj=channel, - is_channel=True, - ) - # format the help entry - key = channel.key - cmd.__doc__ = cmd.__doc__.format( - channelkey=key, - lower_channelkey=key.strip().lower(), - channeldesc=channel.attributes.get("desc", default="").strip(), - ) - self._cached_channel_cmds[channel] = cmd - self._cached_channels[key] = channel - self._cached_cmdsets = {}
- - add_channel = add # legacy alias - -
[docs] def remove(self, channel): - """ - Remove channel from channelhandler. This will also delete it. - - Args: - channel (Channel): Channel to remove/delete. - - """ - if channel.pk: - channel.delete() - self.update()
- -
[docs] def update(self): - """ - Updates the handler completely, including removing old removed - Channel objects. This must be called after deleting a Channel. - - """ - global _CHANNELDB - if not _CHANNELDB: - from evennia.comms.models import ChannelDB as _CHANNELDB - self._cached_channel_cmds = {} - self._cached_cmdsets = {} - self._cached_channels = {} - for channel in _CHANNELDB.objects.get_all_channels(): - self.add(channel)
- -
[docs] def get(self, channelname=None): - """ - Get a channel from the handler, or all channels - - Args: - channelame (str, optional): Channel key, case insensitive. - Returns - channels (list): The matching channels in a list, or all - channels in the handler. - - """ - if channelname: - channel = self._cached_channels.get(channelname.lower(), None) - return [channel] if channel else [] - return list(self._cached_channels.values())
- -
[docs] def get_cmdset(self, source_object): - """ - Retrieve cmdset for channels this source_object has - access to send to. - - Args: - source_object (Object): An object subscribing to one - or more channels. - - Returns: - cmdsets (list): The Channel-Cmdsets `source_object` has - access to. - - """ - if source_object in self._cached_cmdsets: - return self._cached_cmdsets[source_object] - else: - # create a new cmdset holding all viable channels - chan_cmdset = None - chan_cmds = [ - channelcmd - for channel, channelcmd in self._cached_channel_cmds.items() - if channel.subscriptions.has(source_object) - and channelcmd.access(source_object, "send") - ] - if chan_cmds: - chan_cmdset = cmdset.CmdSet() - chan_cmdset.key = "ChannelCmdSet" - chan_cmdset.priority = 101 - chan_cmdset.duplicates = True - for cmd in chan_cmds: - chan_cmdset.add(cmd) - self._cached_cmdsets[source_object] = chan_cmdset - return chan_cmdset
- - -# set up the singleton -CHANNEL_HANDLER = class_from_module(settings.CHANNEL_HANDLER_CLASS)() -CHANNELHANDLER = CHANNEL_HANDLER # legacy -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/comms/comms.html b/docs/0.9.5/_modules/evennia/comms/comms.html deleted file mode 100644 index e2b652ad89..0000000000 --- a/docs/0.9.5/_modules/evennia/comms/comms.html +++ /dev/null @@ -1,928 +0,0 @@ - - - - - - - - evennia.comms.comms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.comms.comms

-"""
-Base typeclass for in-game Channels.
-
-"""
-from django.contrib.contenttypes.models import ContentType
-from django.urls import reverse
-from django.utils.text import slugify
-
-from evennia.typeclasses.models import TypeclassBase
-from evennia.comms.models import TempMsg, ChannelDB
-from evennia.comms.managers import ChannelManager
-from evennia.utils import create, logger
-from evennia.utils.utils import make_iter
-
-_CHANNEL_HANDLER = None
-
-
-
[docs]class DefaultChannel(ChannelDB, metaclass=TypeclassBase): - """ - This is the base class for all Channel Comms. Inherit from this to - create different types of communication channels. - - """ - - objects = ChannelManager() - -
[docs] def at_first_save(self): - """ - Called by the typeclass system the very first time the channel - is saved to the database. Generally, don't overload this but - the hooks called by this method. - - """ - self.basetype_setup() - self.at_channel_creation() - self.attributes.add("log_file", "channel_%s.log" % self.key) - if hasattr(self, "_createdict"): - # this is only set if the channel was created - # with the utils.create.create_channel function. - cdict = self._createdict - if not cdict.get("key"): - if not self.db_key: - self.db_key = "#i" % self.dbid - elif cdict["key"] and self.key != cdict["key"]: - self.key = cdict["key"] - if cdict.get("aliases"): - self.aliases.add(cdict["aliases"]) - if cdict.get("locks"): - self.locks.add(cdict["locks"]) - if cdict.get("keep_log"): - self.attributes.add("keep_log", cdict["keep_log"]) - if cdict.get("desc"): - self.attributes.add("desc", cdict["desc"]) - if cdict.get("tags"): - self.tags.batch_add(*cdict["tags"])
- -
[docs] def basetype_setup(self): - # delayed import of the channelhandler - global _CHANNEL_HANDLER - if not _CHANNEL_HANDLER: - from evennia.comms.channelhandler import CHANNEL_HANDLER as _CHANNEL_HANDLER - # register ourselves with the channelhandler. - _CHANNEL_HANDLER.add(self) - - self.locks.add("send:all();listen:all();control:perm(Admin)")
- -
[docs] def at_channel_creation(self): - """ - Called once, when the channel is first created. - - """ - pass
- - # helper methods, for easy overloading - -
[docs] def has_connection(self, subscriber): - """ - Checks so this account is actually listening - to this channel. - - Args: - subscriber (Account or Object): Entity to check. - - Returns: - has_sub (bool): Whether the subscriber is subscribing to - this channel or not. - - Notes: - This will first try Account subscribers and only try Object - if the Account fails. - - """ - has_sub = self.subscriptions.has(subscriber) - if not has_sub and hasattr(subscriber, "account"): - # it's common to send an Object when we - # by default only allow Accounts to subscribe. - has_sub = self.subscriptions.has(subscriber.account) - return has_sub
- - @property - def mutelist(self): - return self.db.mute_list or [] - - @property - def wholist(self): - subs = self.subscriptions.all() - muted = list(self.mutelist) - listening = [ob for ob in subs if ob.is_connected and ob not in muted] - if subs: - # display listening subscribers in bold - string = ", ".join( - [ - account.key if account not in listening else "|w%s|n" % account.key - for account in subs - ] - ) - else: - string = "<None>" - return string - -
[docs] def mute(self, subscriber, **kwargs): - """ - Adds an entity to the list of muted subscribers. - A muted subscriber will no longer see channel messages, - but may use channel commands. - - Args: - subscriber (Object or Account): Subscriber to mute. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - mutelist = self.mutelist - if subscriber not in mutelist: - mutelist.append(subscriber) - self.db.mute_list = mutelist - return True - return False
- -
[docs] def unmute(self, subscriber, **kwargs): - """ - Removes an entity to the list of muted subscribers. A muted subscriber will no longer see channel messages, - but may use channel commands. - - Args: - subscriber (Object or Account): The subscriber to unmute. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - mutelist = self.mutelist - if subscriber in mutelist: - mutelist.remove(subscriber) - self.db.mute_list = mutelist - return True - return False
- -
[docs] def connect(self, subscriber, **kwargs): - """ - Connect the user to this channel. This checks access. - - Args: - subscriber (Account or Object): the entity to subscribe - to this channel. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - success (bool): Whether or not the addition was - successful. - - """ - # check access - if not self.access(subscriber, "listen"): - return False - # pre-join hook - connect = self.pre_join_channel(subscriber) - if not connect: - return False - # subscribe - self.subscriptions.add(subscriber) - # unmute - self.unmute(subscriber) - # post-join hook - self.post_join_channel(subscriber) - return True
- -
[docs] def disconnect(self, subscriber, **kwargs): - """ - Disconnect entity from this channel. - - Args: - subscriber (Account of Object): the - entity to disconnect. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - success (bool): Whether or not the removal was - successful. - - """ - # pre-disconnect hook - disconnect = self.pre_leave_channel(subscriber) - if not disconnect: - return False - # disconnect - self.subscriptions.remove(subscriber) - # unmute - self.unmute(subscriber) - # post-disconnect hook - self.post_leave_channel(subscriber) - return True
- -
[docs] def access( - self, - accessing_obj, - access_type="listen", - default=False, - no_superuser_bypass=False, - **kwargs, - ): - """ - Determines if another object has permission to access. - - Args: - accessing_obj (Object): Object trying to access this one. - access_type (str, optional): Type of access sought. - default (bool, optional): What to return if no lock of access_type was found - no_superuser_bypass (bool, optional): Turns off superuser - lock bypass. Be careful with this one. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - return (bool): Result of lock check. - - """ - return self.locks.check( - accessing_obj, - access_type=access_type, - default=default, - no_superuser_bypass=no_superuser_bypass, - )
- -
[docs] @classmethod - def create(cls, key, account=None, *args, **kwargs): - """ - Creates a basic Channel with default parameters, unless otherwise - specified or extended. - - Provides a friendlier interface to the utils.create_channel() function. - - Args: - key (str): This must be unique. - account (Account): Account to attribute this object to. - - Keyword Args: - aliases (list of str): List of alternative (likely shorter) keynames. - description (str): A description of the channel, for use in listings. - locks (str): Lockstring. - keep_log (bool): Log channel throughput. - typeclass (str or class): The typeclass of the Channel (not - often used). - ip (str): IP address of creator (for object auditing). - - Returns: - channel (Channel): A newly created Channel. - errors (list): A list of errors in string form, if any. - - """ - errors = [] - obj = None - ip = kwargs.pop("ip", "") - - try: - kwargs["desc"] = kwargs.pop("description", "") - kwargs["typeclass"] = kwargs.get("typeclass", cls) - obj = create.create_channel(key, *args, **kwargs) - - # Record creator id and creation IP - if ip: - obj.db.creator_ip = ip - if account: - obj.db.creator_id = account.id - - except Exception as exc: - errors.append("An error occurred while creating this '%s' object." % key) - logger.log_err(exc) - - return obj, errors
- -
[docs] def delete(self): - """ - Deletes channel while also cleaning up channelhandler. - - """ - self.attributes.clear() - self.aliases.clear() - super().delete() - from evennia.comms.channelhandler import CHANNELHANDLER - - CHANNELHANDLER.update()
- -
[docs] def message_transform( - self, msgobj, emit=False, prefix=True, sender_strings=None, external=False, **kwargs - ): - """ - Generates the formatted string sent to listeners on a channel. - - Args: - msgobj (Msg): Message object to send. - emit (bool, optional): In emit mode the message is not associated - with a specific sender name. - prefix (bool, optional): Prefix `msg` with a text given by `self.channel_prefix`. - sender_strings (list, optional): Used by bots etc, one string per external sender. - external (bool, optional): If this is an external sender or not. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - if sender_strings or external: - body = self.format_external(msgobj, sender_strings, emit=emit) - else: - body = self.format_message(msgobj, emit=emit) - if prefix: - body = "%s%s" % (self.channel_prefix(msgobj, emit=emit), body) - msgobj.message = body - return msgobj
- -
[docs] def distribute_message(self, msgobj, online=False, **kwargs): - """ - Method for grabbing all listeners that a message should be - sent to on this channel, and sending them a message. - - Args: - msgobj (Msg or TempMsg): Message to distribute. - online (bool): Only send to receivers who are actually online - (not currently used): - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - This is also where logging happens, if enabled. - - """ - # get all accounts or objects connected to this channel and send to them - if online: - subs = self.subscriptions.online() - else: - subs = self.subscriptions.all() - for entity in subs: - # if the entity is muted, we don't send them a message - if entity in self.mutelist: - continue - try: - # note our addition of the from_channel keyword here. This could be checked - # by a custom account.msg() to treat channel-receives differently. - entity.msg( - msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id} - ) - except AttributeError as e: - logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity)) - - if msgobj.keep_log: - # log to file - logger.log_file( - msgobj.message, self.attributes.get("log_file") or "channel_%s.log" % self.key - )
- -
[docs] def msg( - self, - msgobj, - header=None, - senders=None, - sender_strings=None, - keep_log=None, - online=False, - emit=False, - external=False, - ): - """ - Send the given message to all accounts connected to channel. Note that - no permission-checking is done here; it is assumed to have been - done before calling this method. The optional keywords are not used if - persistent is False. - - Args: - msgobj (Msg, TempMsg or str): If a Msg/TempMsg, the remaining - keywords will be ignored (since the Msg/TempMsg object already - has all the data). If a string, this will either be sent as-is - (if persistent=False) or it will be used together with `header` - and `senders` keywords to create a Msg instance on the fly. - header (str, optional): A header for building the message. - senders (Object, Account or list, optional): Optional if persistent=False, used - to build senders for the message. - sender_strings (list, optional): Name strings of senders. Used for external - connections where the sender is not an account or object. - When this is defined, external will be assumed. The list will be - filtered so each sender-string only occurs once. - keep_log (bool or None, optional): This allows to temporarily change the logging status of - this channel message. If `None`, the Channel's `keep_log` Attribute will - be used. If `True` or `False`, that logging status will be used for this - message only (note that for unlogged channels, a `True` value here will - create a new log file only for this message). - online (bool, optional) - If this is set true, only messages people who are - online. Otherwise, messages all accounts connected. This can - make things faster, but may not trigger listeners on accounts - that are offline. - emit (bool, optional) - Signals to the message formatter that this message is - not to be directly associated with a name. - external (bool, optional): Treat this message as being - agnostic of its sender. - - Returns: - success (bool): Returns `True` if message sending was - successful, `False` otherwise. - - """ - senders = make_iter(senders) if senders else [] - if isinstance(msgobj, str): - # given msgobj is a string - convert to msgobject (always TempMsg) - msgobj = TempMsg(senders=senders, header=header, message=msgobj, channels=[self]) - # we store the logging setting for use in distribute_message() - msgobj.keep_log = keep_log if keep_log is not None else self.db.keep_log - - # start the sending - msgobj = self.pre_send_message(msgobj) - if not msgobj: - return False - if sender_strings: - sender_strings = list(set(make_iter(sender_strings))) - msgobj = self.message_transform( - msgobj, emit=emit, sender_strings=sender_strings, external=external - ) - self.distribute_message(msgobj, online=online) - self.post_send_message(msgobj) - return True
- -
[docs] def tempmsg(self, message, header=None, senders=None): - """ - A wrapper for sending non-persistent messages. - - Args: - message (str): Message to send. - header (str, optional): Header of message to send. - senders (Object or list, optional): Senders of message to send. - - """ - self.msg(message, senders=senders, header=header, keep_log=False)
- - # hooks - -
[docs] def channel_prefix(self, msg=None, emit=False, **kwargs): - """ - Hook method. How the channel should prefix itself for users. - - Args: - msg (str, optional): Prefix text - emit (bool, optional): Switches to emit mode, which usually - means to not prefix the channel's info. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - prefix (str): The created channel prefix. - - """ - return "" if emit else "[%s] " % self.key
- -
[docs] def format_senders(self, senders=None, **kwargs): - """ - Hook method. Function used to format a list of sender names. - - Args: - senders (list): Sender object names. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - formatted_list (str): The list of names formatted appropriately. - - Notes: - This function exists separately so that external sources - can use it to format source names in the same manner as - normal object/account names. - - """ - if not senders: - return "" - return ", ".join(senders)
- -
[docs] def pose_transform(self, msgobj, sender_string, **kwargs): - """ - Hook method. Detects if the sender is posing, and modifies the - message accordingly. - - Args: - msgobj (Msg or TempMsg): The message to analyze for a pose. - sender_string (str): The name of the sender/poser. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - string (str): A message that combines the `sender_string` - component with `msg` in different ways depending on if a - pose was performed or not (this must be analyzed by the - hook). - - """ - pose = False - message = msgobj.message - message_start = message.lstrip() - if message_start.startswith((":", ";")): - pose = True - message = message[1:] - if not message.startswith((":", "'", ",")): - if not message.startswith(" "): - message = " " + message - if pose: - return "%s%s" % (sender_string, message) - else: - return "%s: %s" % (sender_string, message)
- -
[docs] def format_external(self, msgobj, senders, emit=False, **kwargs): - """ - Hook method. Used for formatting external messages. This is - needed as a separate operation because the senders of external - messages may not be in-game objects/accounts, and so cannot - have things like custom user preferences. - - Args: - msgobj (Msg or TempMsg): The message to send. - senders (list): Strings, one per sender. - emit (bool, optional): A sender-agnostic message or not. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - transformed (str): A formatted string. - - """ - if emit or not senders: - return msgobj.message - senders = ", ".join(senders) - return self.pose_transform(msgobj, senders)
- -
[docs] def format_message(self, msgobj, emit=False, **kwargs): - """ - Hook method. Formats a message body for display. - - Args: - msgobj (Msg or TempMsg): The message object to send. - emit (bool, optional): The message is agnostic of senders. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - transformed (str): The formatted message. - - """ - # We don't want to count things like external sources as senders for - # the purpose of constructing the message string. - senders = [sender for sender in msgobj.senders if hasattr(sender, "key")] - if not senders: - emit = True - if emit: - return msgobj.message - else: - senders = [sender.key for sender in msgobj.senders] - senders = ", ".join(senders) - return self.pose_transform(msgobj, senders)
- -
[docs] def pre_join_channel(self, joiner, **kwargs): - """ - Hook method. Runs right before a channel is joined. If this - returns a false value, channel joining is aborted. - - Args: - joiner (object): The joining object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - should_join (bool): If `False`, channel joining is aborted. - - """ - return True
- -
[docs] def post_join_channel(self, joiner, **kwargs): - """ - Hook method. Runs right after an object or account joins a channel. - - Args: - joiner (object): The joining object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def pre_leave_channel(self, leaver, **kwargs): - """ - Hook method. Runs right before a user leaves a channel. If this returns a false - value, leaving the channel will be aborted. - - Args: - leaver (object): The leaving object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - should_leave (bool): If `False`, channel parting is aborted. - - """ - return True
- -
[docs] def post_leave_channel(self, leaver, **kwargs): - """ - Hook method. Runs right after an object or account leaves a channel. - - Args: - leaver (object): The leaving object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def pre_send_message(self, msg, **kwargs): - """ - Hook method. Runs before a message is sent to the channel and - should return the message object, after any transformations. - If the message is to be discarded, return a false value. - - Args: - msg (Msg or TempMsg): Message to send. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - result (Msg, TempMsg or bool): If False, abort send. - - """ - return msg
- -
[docs] def post_send_message(self, msg, **kwargs): - """ - Hook method. Run after a message is sent to the channel. - - Args: - msg (Msg or TempMsg): Message sent. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_init(self): - """ - Hook method. This is always called whenever this channel is - initiated -- that is, whenever it its typeclass is cached from - memory. This happens on-demand first time the channel is used - or activated in some way after being created but also after - each server restart or reload. - - """ - pass
- - # - # Web/Django methods - # - -
[docs] def web_get_admin_url(self): - """ - Returns the URI path for the Django Admin page for this object. - - ex. Account#1 = '/admin/accounts/accountdb/1/change/' - - Returns: - path (str): URI path to Django Admin page for object. - - """ - content_type = ContentType.objects.get_for_model(self.__class__) - return reverse( - "admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,) - )
- -
[docs] @classmethod - def web_get_create_url(cls): - """ - Returns the URI path for a View that allows users to create new - instances of this object. - - ex. Chargen = '/characters/create/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'channel-create' would be referenced by this method. - - ex. - url(r'channels/create/', ChannelCreateView.as_view(), name='channel-create') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can create new objects is the - developer's responsibility. - - Returns: - path (str): URI path to object creation page, if defined. - - """ - try: - return reverse("%s-create" % slugify(cls._meta.verbose_name)) - except: - return "#"
- -
[docs] def web_get_detail_url(self): - """ - Returns the URI path for a View that allows users to view details for - this object. - - ex. Oscar (Character) = '/characters/oscar/1/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'channel-detail' would be referenced by this method. - - ex. - url(r'channels/(?P<slug>[\w\d\-]+)/$', - ChannelDetailView.as_view(), name='channel-detail') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can view this object is the developer's - responsibility. - - Returns: - path (str): URI path to object detail page, if defined. - - """ - try: - return reverse( - "%s-detail" % slugify(self._meta.verbose_name), - kwargs={"slug": slugify(self.db_key)}, - ) - except: - return "#"
- -
[docs] def web_get_update_url(self): - """ - Returns the URI path for a View that allows users to update this - object. - - ex. Oscar (Character) = '/characters/oscar/1/change/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'channel-update' would be referenced by this method. - - ex. - url(r'channels/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$', - ChannelUpdateView.as_view(), name='channel-update') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can modify objects is the developer's - responsibility. - - Returns: - path (str): URI path to object update page, if defined. - - """ - try: - return reverse( - "%s-update" % slugify(self._meta.verbose_name), - kwargs={"slug": slugify(self.db_key)}, - ) - except: - return "#"
- -
[docs] def web_get_delete_url(self): - """ - Returns the URI path for a View that allows users to delete this object. - - ex. Oscar (Character) = '/characters/oscar/1/delete/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'channel-delete' would be referenced by this method. - - ex. - url(r'channels/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$', - ChannelDeleteView.as_view(), name='channel-delete') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can delete this object is the developer's - responsibility. - - Returns: - path (str): URI path to object deletion page, if defined. - - """ - try: - return reverse( - "%s-delete" % slugify(self._meta.verbose_name), - kwargs={"slug": slugify(self.db_key)}, - ) - except: - return "#"
- - # Used by Django Sites/Admin - get_absolute_url = web_get_detail_url
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/comms/managers.html b/docs/0.9.5/_modules/evennia/comms/managers.html deleted file mode 100644 index b8fa81bd5d..0000000000 --- a/docs/0.9.5/_modules/evennia/comms/managers.html +++ /dev/null @@ -1,521 +0,0 @@ - - - - - - - - evennia.comms.managers — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.comms.managers

-"""
-These managers define helper methods for accessing the database from
-Comm system components.
-
-"""
-
-
-from django.db.models import Q
-from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
-from evennia.utils import logger
-from evennia.utils.utils import dbref
-
-_GA = object.__getattribute__
-_AccountDB = None
-_ObjectDB = None
-_ChannelDB = None
-_SESSIONS = None
-
-# error class
-
-
-
[docs]class CommError(Exception): - """ - Raised by comm system, to allow feedback to player when caught. - """ - - pass
- - -# -# helper functions -# - - -
[docs]def identify_object(inp): - """ - Helper function. Identifies if an object is an account or an object; - return its database model - - Args: - inp (any): Entity to be idtified. - - Returns: - identified (tuple): This is a tuple with (`inp`, identifier) - where `identifier` is one of "account", "object", "channel", - "string", "dbref" or None. - - """ - if hasattr(inp, "__dbclass__"): - clsname = inp.__dbclass__.__name__ - if clsname == "AccountDB": - return inp, "account" - elif clsname == "ObjectDB": - return inp, "object" - elif clsname == "ChannelDB": - return inp, "channel" - if isinstance(inp, str): - return inp, "string" - elif dbref(inp): - return dbref(inp), "dbref" - else: - return inp, None
- - -
[docs]def to_object(inp, objtype="account"): - """ - Locates the object related to the given accountname or channel key. - If input was already the correct object, return it. - - Args: - inp (any): The input object/string - objtype (str): Either 'account' or 'channel'. - - Returns: - obj (object): The correct object related to `inp`. - - """ - obj, typ = identify_object(inp) - if typ == objtype: - return obj - if objtype == "account": - if typ == "object": - return obj.account - if typ == "string": - return _AccountDB.objects.get(user_username__iexact=obj) - if typ == "dbref": - return _AccountDB.objects.get(id=obj) - logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) - raise CommError() - elif objtype == "object": - if typ == "account": - return obj.obj - if typ == "string": - return _ObjectDB.objects.get(db_key__iexact=obj) - if typ == "dbref": - return _ObjectDB.objects.get(id=obj) - logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) - raise CommError() - elif objtype == "channel": - if typ == "string": - return _ChannelDB.objects.get(db_key__iexact=obj) - if typ == "dbref": - return _ChannelDB.objects.get(id=obj) - logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) - raise CommError() - # an unknown - return None
- - -# -# Msg manager -# - - -
[docs]class MsgManager(TypedObjectManager): - """ - This MsgManager implements methods for searching and manipulating - Messages directly from the database. - - These methods will all return database objects (or QuerySets) - directly. - - A Message represents one unit of communication, be it over a - Channel or via some form of in-game mail system. Like an e-mail, - it always has a sender and can have any number of receivers (some - of which may be Channels). - - """ - -
[docs] def identify_object(self, inp): - """ - Wrapper to identify_object if accessing via the manager directly. - - Args: - inp (any): Entity to be idtified. - - Returns: - identified (tuple): This is a tuple with (`inp`, identifier) - where `identifier` is one of "account", "object", "channel", - "string", "dbref" or None. - - """ - return identify_object(inp)
- -
[docs] def get_message_by_id(self, idnum): - """ - Retrieve message by its id. - - Args: - idnum (int or str): The dbref to retrieve. - - Returns: - message (Msg): The message. - - """ - try: - return self.get(id=self.dbref(idnum, reqhash=False)) - except Exception: - return None
- -
[docs] def get_messages_by_sender(self, sender, exclude_channel_messages=False): - """ - Get all messages sent by one entity - this could be either a - account or an object - - Args: - sender (Account or Object): The sender of the message. - exclude_channel_messages (bool, optional): Only return messages - not aimed at a channel (that is, private tells for example) - - Returns: - messages (list): List of matching messages - - Raises: - CommError: For incorrect sender types. - - """ - obj, typ = identify_object(sender) - if exclude_channel_messages: - # explicitly exclude channel recipients - if typ == "account": - return list( - self.filter(db_sender_accounts=obj, db_receivers_channels__isnull=True).exclude( - db_hide_from_accounts=obj - ) - ) - elif typ == "object": - return list( - self.filter(db_sender_objects=obj, db_receivers_channels__isnull=True).exclude( - db_hide_from_objects=obj - ) - ) - else: - raise CommError - else: - # get everything, channel or not - if typ == "account": - return list(self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj)) - elif typ == "object": - return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj)) - else: - raise CommError
- -
[docs] def get_messages_by_receiver(self, recipient): - """ - Get all messages sent to one given recipient. - - Args: - recipient (Object, Account or Channel): The recipient of the messages to search for. - - Returns: - messages (list): Matching messages. - - Raises: - CommError: If the `recipient` is not of a valid type. - - """ - obj, typ = identify_object(recipient) - if typ == "account": - return list(self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj)) - elif typ == "object": - return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj)) - elif typ == "channel": - return list(self.filter(db_receivers_channels=obj).exclude(db_hide_from_channels=obj)) - else: - raise CommError
- -
[docs] def get_messages_by_channel(self, channel): - """ - Get all persistent messages sent to one channel. - - Args: - channel (Channel): The channel to find messages for. - - Returns: - messages (list): Persistent Msg objects saved for this channel. - - """ - return self.filter(db_receivers_channels=channel).exclude(db_hide_from_channels=channel)
- -
[docs] def search_message(self, sender=None, receiver=None, freetext=None, dbref=None): - """ - Search the message database for particular messages. At least - one of the arguments must be given to do a search. - - Args: - sender (Object or Account, optional): Get messages sent by a particular account or object - receiver (Object, Account or Channel, optional): Get messages - received by a certain account,object or channel - freetext (str): Search for a text string in a message. NOTE: - This can potentially be slow, so make sure to supply one of - the other arguments to limit the search. - dbref (int): The exact database id of the message. This will override - all other search criteria since it's unique and - always gives only one match. - - Returns: - messages (list or Msg): A list of message matches or a single match if `dbref` was given. - - """ - # unique msg id - if dbref: - msg = self.objects.filter(id=dbref) - if msg: - return msg[0] - - # We use Q objects to gradually build up the query - this way we only - # need to do one database lookup at the end rather than gradually - # refining with multiple filter:s. Django Note: Q objects can be - # combined with & and | (=AND,OR). ~ negates the queryset - - # filter by sender - sender, styp = identify_object(sender) - if styp == "account": - sender_restrict = Q(db_sender_accounts=sender) & ~Q(db_hide_from_accounts=sender) - elif styp == "object": - sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender) - else: - sender_restrict = Q() - # filter by receiver - receiver, rtyp = identify_object(receiver) - if rtyp == "account": - receiver_restrict = Q(db_receivers_accounts=receiver) & ~Q( - db_hide_from_accounts=receiver - ) - elif rtyp == "object": - receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver) - elif rtyp == "channel": - receiver_restrict = Q(db_receivers_channels=receiver) & ~Q( - db_hide_from_channels=receiver - ) - else: - receiver_restrict = Q() - # filter by full text - if freetext: - fulltext_restrict = Q(db_header__icontains=freetext) | Q(db_message__icontains=freetext) - else: - fulltext_restrict = Q() - # execute the query - return list(self.filter(sender_restrict & receiver_restrict & fulltext_restrict))
- - # back-compatibility alias - message_search = search_message
- - -# -# Channel manager -# - - -
[docs]class ChannelDBManager(TypedObjectManager): - """ - This ChannelManager implements methods for searching and - manipulating Channels directly from the database. - - These methods will all return database objects (or QuerySets) - directly. - - A Channel is an in-game venue for communication. It's essentially - representation of a re-sender: Users sends Messages to the - Channel, and the Channel re-sends those messages to all users - subscribed to the Channel. - - """ - -
[docs] def get_all_channels(self): - """ - Get all channels. - - Returns: - channels (list): All channels in game. - - """ - return self.all()
- -
[docs] def get_channel(self, channelkey): - """ - Return the channel object if given its key. - Also searches its aliases. - - Args: - channelkey (str): Channel key to search for. - - Returns: - channel (Channel or None): A channel match. - - """ - dbref = self.dbref(channelkey) - if dbref: - try: - return self.get(id=dbref) - except self.model.DoesNotExist: - pass - results = self.filter( - Q(db_key__iexact=channelkey) - | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__iexact=channelkey) - ).distinct() - return results[0] if results else None
- -
[docs] def get_subscriptions(self, subscriber): - """ - Return all channels a given entity is subscribed to. - - Args: - subscriber (Object or Account): The one subscribing. - - Returns: - subscriptions (list): Channel subscribed to. - - """ - clsname = subscriber.__dbclass__.__name__ - if clsname == "AccountDB": - return subscriber.account_subscription_set.all() - if clsname == "ObjectDB": - return subscriber.object_subscription_set.all() - return []
- -
[docs] def search_channel(self, ostring, exact=True): - """ - Search the channel database for a particular channel. - - Args: - ostring (str): The key or database id of the channel. - exact (bool, optional): Require an exact (but not - case sensitive) match. - - """ - dbref = self.dbref(ostring) - if dbref: - try: - return self.get(id=dbref) - except self.model.DoesNotExist: - pass - if exact: - channels = self.filter( - Q(db_key__iexact=ostring) - | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__iexact=ostring) - ).distinct() - else: - channels = self.filter( - Q(db_key__icontains=ostring) - | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__icontains=ostring) - ).distinct() - return channels
- - # back-compatibility alias - channel_search = search_channel
- - -
[docs]class ChannelManager(ChannelDBManager, TypeclassManager): - """ - Wrapper to group the typeclass manager to a consistent name. - """ - - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/comms/models.html b/docs/0.9.5/_modules/evennia/comms/models.html deleted file mode 100644 index 2087e68a03..0000000000 --- a/docs/0.9.5/_modules/evennia/comms/models.html +++ /dev/null @@ -1,847 +0,0 @@ - - - - - - - - evennia.comms.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.comms.models

-"""
-Models for the in-game communication system.
-
-The comm system could take the form of channels, but can also be
-adopted for storing tells or in-game mail.
-
-The comsystem's main component is the Message (Msg), which carries the
-actual information between two parties.  Msgs are stored in the
-database and usually not deleted.  A Msg always have one sender (a
-user), but can have any number targets, both users and channels.
-
-For non-persistent (and slightly faster) use one can also use the
-TempMsg, which mimics the Msg API but without actually saving to the
-database.
-
-Channels are central objects that act as targets for Msgs. Accounts can
-connect to channels by use of a ChannelConnect object (this object is
-necessary to easily be able to delete connections on the fly).
-"""
-from django.conf import settings
-from django.utils import timezone
-from django.db import models
-from evennia.typeclasses.models import TypedObject
-from evennia.typeclasses.tags import Tag, TagHandler
-from evennia.utils.idmapper.models import SharedMemoryModel
-from evennia.comms import managers
-from evennia.locks.lockhandler import LockHandler
-from evennia.utils.utils import crop, make_iter, lazy_property
-
-__all__ = ("Msg", "TempMsg", "ChannelDB")
-
-
-_GA = object.__getattribute__
-_SA = object.__setattr__
-_DA = object.__delattr__
-
-_CHANNELHANDLER = None
-
-
-# ------------------------------------------------------------
-#
-# Msg
-#
-# ------------------------------------------------------------
-
-
-
[docs]class Msg(SharedMemoryModel): - """ - A single message. This model describes all ooc messages - sent in-game, both to channels and between accounts. - - The Msg class defines the following database fields (all - accessed via specific handler methods): - - - db_sender_accounts: Account senders - - db_sender_objects: Object senders - - db_sender_scripts: Script senders - - db_sender_external: External senders (defined as string names) - - db_receivers_accounts: Receiving accounts - - db_receivers_objects: Receiving objects - - db_receivers_scripts: Receiveing scripts - - db_receivers_channels: Receiving channels - - db_header: Header text - - db_message: The actual message text - - db_date_created: time message was created / sent - - db_hide_from_sender: bool if message should be hidden from sender - - db_hide_from_receivers: list of receiver objects to hide message from - - db_hide_from_channels: list of channels objects to hide message from - - db_lock_storage: Internal storage of lock strings. - - """ - - # - # Msg database model setup - # - # - # These databse fields are all set using their corresponding properties, - # named same as the field, but withtout the db_* prefix. - - # Sender is either an account, an object or an external sender, like - # an IRC channel; normally there is only one, but if co-modification of - # a message is allowed, there may be more than one "author" - db_sender_accounts = models.ManyToManyField( - "accounts.AccountDB", - related_name="sender_account_set", - blank=True, - verbose_name="sender(account)", - db_index=True, - ) - - db_sender_objects = models.ManyToManyField( - "objects.ObjectDB", - related_name="sender_object_set", - blank=True, - verbose_name="sender(object)", - db_index=True, - ) - db_sender_scripts = models.ManyToManyField( - "scripts.ScriptDB", - related_name="sender_script_set", - blank=True, - verbose_name="sender(script)", - db_index=True, - ) - db_sender_external = models.CharField( - "external sender", - max_length=255, - null=True, - blank=True, - db_index=True, - help_text="identifier for external sender, for example a sender over an " - "IRC connection (i.e. someone who doesn't have an exixtence in-game).", - ) - # The destination objects of this message. Stored as a - # comma-separated string of object dbrefs. Can be defined along - # with channels below. - db_receivers_accounts = models.ManyToManyField( - "accounts.AccountDB", - related_name="receiver_account_set", - blank=True, - help_text="account receivers", - ) - - db_receivers_objects = models.ManyToManyField( - "objects.ObjectDB", - related_name="receiver_object_set", - blank=True, - help_text="object receivers", - ) - db_receivers_scripts = models.ManyToManyField( - "scripts.ScriptDB", - related_name="receiver_script_set", - blank=True, - help_text="script_receivers", - ) - db_receivers_channels = models.ManyToManyField( - "ChannelDB", related_name="channel_set", blank=True, help_text="channel recievers" - ) - - # header could be used for meta-info about the message if your system needs - # it, or as a separate store for the mail subject line maybe. - db_header = models.TextField("header", null=True, blank=True) - # the message body itself - db_message = models.TextField("message") - # send date - db_date_created = models.DateTimeField( - "date sent", editable=False, auto_now_add=True, db_index=True - ) - # lock storage - db_lock_storage = models.TextField( - "locks", blank=True, help_text="access locks on this message." - ) - - # these can be used to filter/hide a given message from supplied objects/accounts/channels - db_hide_from_accounts = models.ManyToManyField( - "accounts.AccountDB", related_name="hide_from_accounts_set", blank=True - ) - - db_hide_from_objects = models.ManyToManyField( - "objects.ObjectDB", related_name="hide_from_objects_set", blank=True - ) - db_hide_from_channels = models.ManyToManyField( - "ChannelDB", related_name="hide_from_channels_set", blank=True - ) - - db_tags = models.ManyToManyField( - Tag, - blank=True, - help_text="tags on this message. Tags are simple string markers to identify, group and alias messages.", - ) - - # Database manager - objects = managers.MsgManager() - _is_deleted = False - -
[docs] def __init__(self, *args, **kwargs): - SharedMemoryModel.__init__(self, *args, **kwargs) - self.extra_senders = []
- - class Meta(object): - "Define Django meta options" - verbose_name = "Msg" - -
[docs] @lazy_property - def locks(self): - return LockHandler(self)
- -
[docs] @lazy_property - def tags(self): - return TagHandler(self)
- - # Wrapper properties to easily set database fields. These are - # @property decorators that allows to access these fields using - # normal python operations (without having to remember to save() - # etc). So e.g. a property 'attr' has a get/set/del decorator - # defined that allows the user to do self.attr = value, - # value = self.attr and del self.attr respectively (where self - # is the object in question). - - # sender property (wraps db_sender_*) - # @property - def __senders_get(self): - "Getter. Allows for value = self.sender" - return ( - list(self.db_sender_accounts.all()) - + list(self.db_sender_objects.all()) - + list(self.db_sender_scripts.all()) - + self.extra_senders - ) - - # @sender.setter - def __senders_set(self, senders): - "Setter. Allows for self.sender = value" - for sender in make_iter(senders): - if not sender: - continue - if isinstance(sender, str): - self.db_sender_external = sender - self.extra_senders.append(sender) - self.save(update_fields=["db_sender_external"]) - continue - if not hasattr(sender, "__dbclass__"): - raise ValueError("This is a not a typeclassed object!") - clsname = sender.__dbclass__.__name__ - if clsname == "ObjectDB": - self.db_sender_objects.add(sender) - elif clsname == "AccountDB": - self.db_sender_accounts.add(sender) - elif clsname == "ScriptDB": - self.db_sender_scripts.add(sender) - - # @sender.deleter - def __senders_del(self): - "Deleter. Clears all senders" - self.db_sender_accounts.clear() - self.db_sender_objects.clear() - self.db_sender_scripts.clear() - self.db_sender_external = "" - self.extra_senders = [] - self.save() - - senders = property(__senders_get, __senders_set, __senders_del) - -
[docs] def remove_sender(self, senders): - """ - Remove a single sender or a list of senders. - - Args: - senders (Account, Object, str or list): Senders to remove. - - """ - for sender in make_iter(senders): - if not sender: - continue - if isinstance(sender, str): - self.db_sender_external = "" - self.save(update_fields=["db_sender_external"]) - if not hasattr(sender, "__dbclass__"): - raise ValueError("This is a not a typeclassed object!") - clsname = sender.__dbclass__.__name__ - if clsname == "ObjectDB": - self.db_sender_objects.remove(sender) - elif clsname == "AccountDB": - self.db_sender_accounts.remove(sender) - elif clsname == "ScriptDB": - self.db_sender_accounts.remove(sender)
- - # receivers property - # @property - def __receivers_get(self): - """ - Getter. Allows for value = self.receivers. - Returns four lists of receivers: accounts, objects, scripts and channels. - """ - return ( - list(self.db_receivers_accounts.all()) - + list(self.db_receivers_objects.all()) - + list(self.db_receivers_scripts.all()) - + list(self.db_receivers_channels.all()) - ) - - # @receivers.setter - def __receivers_set(self, receivers): - """ - Setter. Allows for self.receivers = value. - This appends a new receiver to the message. - """ - for receiver in make_iter(receivers): - if not receiver: - continue - if not hasattr(receiver, "__dbclass__"): - raise ValueError("This is a not a typeclassed object!") - clsname = receiver.__dbclass__.__name__ - if clsname == "ObjectDB": - self.db_receivers_objects.add(receiver) - elif clsname == "AccountDB": - self.db_receivers_accounts.add(receiver) - elif clsname == "ScriptDB": - self.db_receivers_scripts.add(receiver) - elif clsname == "ChannelDB": - self.db_receivers_channels.add(receiver) - - # @receivers.deleter - def __receivers_del(self): - "Deleter. Clears all receivers" - self.db_receivers_accounts.clear() - self.db_receivers_objects.clear() - self.db_receivers_scripts.clear() - self.db_receivers_channels.clear() - self.save() - - receivers = property(__receivers_get, __receivers_set, __receivers_del) - -
[docs] def remove_receiver(self, receivers): - """ - Remove a single receiver or a list of receivers. - - Args: - receivers (Account, Object, Script, Channel or list): Receiver to remove. - - """ - for receiver in make_iter(receivers): - if not receiver: - continue - if not hasattr(receiver, "__dbclass__"): - raise ValueError("This is a not a typeclassed object!") - clsname = receiver.__dbclass__.__name__ - if clsname == "ObjectDB": - self.db_receivers_objects.remove(receiver) - elif clsname == "AccountDB": - self.db_receivers_accounts.remove(receiver) - elif clsname == "ScriptDB": - self.db_receivers_scripts.remove(receiver) - elif clsname == "ChannelDB": - self.db_receivers_channels.remove(receiver)
- - # channels property - # @property - def __channels_get(self): - "Getter. Allows for value = self.channels. Returns a list of channels." - return self.db_receivers_channels.all() - - # @channels.setter - def __channels_set(self, value): - """ - Setter. Allows for self.channels = value. - Requires a channel to be added. - """ - for val in (v for v in make_iter(value) if v): - self.db_receivers_channels.add(val) - - # @channels.deleter - def __channels_del(self): - "Deleter. Allows for del self.channels" - self.db_receivers_channels.clear() - self.save() - - channels = property(__channels_get, __channels_set, __channels_del) - - def __hide_from_get(self): - """ - Getter. Allows for value = self.hide_from. - Returns 3 lists of accounts, objects and channels - """ - return ( - self.db_hide_from_accounts.all(), - self.db_hide_from_objects.all(), - self.db_hide_from_channels.all(), - ) - - # @hide_from_sender.setter - def __hide_from_set(self, hiders): - "Setter. Allows for self.hide_from = value. Will append to hiders" - for hider in make_iter(hiders): - if not hider: - continue - if not hasattr(hider, "__dbclass__"): - raise ValueError("This is a not a typeclassed object!") - clsname = hider.__dbclass__.__name__ - if clsname == "AccountDB": - self.db_hide_from_accounts.add(hider.__dbclass__) - elif clsname == "ObjectDB": - self.db_hide_from_objects.add(hider.__dbclass__) - elif clsname == "ChannelDB": - self.db_hide_from_channels.add(hider.__dbclass__) - - # @hide_from_sender.deleter - def __hide_from_del(self): - "Deleter. Allows for del self.hide_from_senders" - self.db_hide_from_accounts.clear() - self.db_hide_from_objects.clear() - self.db_hide_from_channels.clear() - self.save() - - hide_from = property(__hide_from_get, __hide_from_set, __hide_from_del) - - # - # Msg class methods - # - - def __str__(self): - "This handles what is shown when e.g. printing the message" - senders = ",".join(getattr(obj, "key", str(obj)) for obj in self.senders) - - receivers = ",".join( - ["[%s]" % getattr(obj, "key", str(obj)) for obj in self.channels] - + [getattr(obj, "key", str(obj)) for obj in self.receivers] - ) - return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40)) - -
[docs] def access(self, accessing_obj, access_type="read", default=False): - """ - Checks lock access. - - Args: - accessing_obj (Object or Account): The object trying to gain access. - access_type (str, optional): The type of lock access to check. - default (bool): Fallback to use if `access_type` lock is not defined. - - Returns: - result (bool): If access was granted or not. - - """ - return self.locks.check(accessing_obj, access_type=access_type, default=default)
- - -# ------------------------------------------------------------ -# -# TempMsg -# -# ------------------------------------------------------------ - - -
[docs]class TempMsg(object): - """ - This is a non-persistent object for sending temporary messages - that will not be stored. It mimics the "real" Msg object, but - doesn't require sender to be given. - - """ - -
[docs] def __init__( - self, - senders=None, - receivers=None, - channels=None, - message="", - header="", - type="", - lockstring="", - hide_from=None, - ): - """ - Creates the temp message. - - Args: - senders (any or list, optional): Senders of the message. - receivers (Account, Object, Channel or list, optional): Receivers of this message. - channels (Channel or list, optional): Channels to send to. - message (str, optional): Message to send. - header (str, optional): Header of message. - type (str, optional): Message class, if any. - lockstring (str, optional): Lock for the message. - hide_from (Account, Object, Channel or list, optional): Entities to hide this message from. - - """ - self.senders = senders and make_iter(senders) or [] - self.receivers = receivers and make_iter(receivers) or [] - self.channels = channels and make_iter(channels) or [] - self.type = type - self.header = header - self.message = message - self.lock_storage = lockstring - self.hide_from = hide_from and make_iter(hide_from) or [] - self.date_created = timezone.now()
- -
[docs] @lazy_property - def locks(self): - return LockHandler(self)
- - def __str__(self): - """ - This handles what is shown when e.g. printing the message. - """ - senders = ",".join(obj.key for obj in self.senders) - receivers = ",".join( - ["[%s]" % obj.key for obj in self.channels] + [obj.key for obj in self.receivers] - ) - return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40)) - -
[docs] def remove_sender(self, sender): - """ - Remove a sender or a list of senders. - - Args: - sender (Object, Account, str or list): Senders to remove. - - """ - for o in make_iter(sender): - try: - self.senders.remove(o) - except ValueError: - pass # nothing to remove
- -
[docs] def remove_receiver(self, receiver): - """ - Remove a receiver or a list of receivers - - Args: - receiver (Object, Account, Channel, str or list): Receivers to remove. - """ - - for o in make_iter(receiver): - try: - self.senders.remove(o) - except ValueError: - pass # nothing to remove
- -
[docs] def access(self, accessing_obj, access_type="read", default=False): - """ - Checks lock access. - - Args: - accessing_obj (Object or Account): The object trying to gain access. - access_type (str, optional): The type of lock access to check. - default (bool): Fallback to use if `access_type` lock is not defined. - - Returns: - result (bool): If access was granted or not. - - """ - return self.locks.check(accessing_obj, access_type=access_type, default=default)
- - -# ------------------------------------------------------------ -# -# Channel -# -# ------------------------------------------------------------ - - -class SubscriptionHandler(object): - """ - This handler manages subscriptions to the - channel and hides away which type of entity is - subscribing (Account or Object) - """ - - def __init__(self, obj): - """ - Initialize the handler - - Attr: - obj (ChannelDB): The channel the handler sits on. - - """ - self.obj = obj - self._cache = None - - def _recache(self): - self._cache = { - account: True - for account in self.obj.db_account_subscriptions.all() - if hasattr(account, "pk") and account.pk - } - self._cache.update( - { - obj: True - for obj in self.obj.db_object_subscriptions.all() - if hasattr(obj, "pk") and obj.pk - } - ) - - def has(self, entity): - """ - Check if the given entity subscribe to this channel - - Args: - entity (str, Account or Object): The entity to return. If - a string, it assumed to be the key or the #dbref - of the entity. - - Returns: - subscriber (Account, Object or None): The given - subscriber. - - """ - if self._cache is None: - self._recache() - return entity in self._cache - - def add(self, entity): - """ - Subscribe an entity to this channel. - - Args: - entity (Account, Object or list): The entity or - list of entities to subscribe to this channel. - - Note: - No access-checking is done here, this must have - been done before calling this method. Also - no hooks will be called. - - """ - global _CHANNELHANDLER - if not _CHANNELHANDLER: - from evennia.comms.channelhandler import CHANNEL_HANDLER as _CHANNELHANDLER - for subscriber in make_iter(entity): - if subscriber: - clsname = subscriber.__dbclass__.__name__ - # chooses the right type - if clsname == "ObjectDB": - self.obj.db_object_subscriptions.add(subscriber) - elif clsname == "AccountDB": - self.obj.db_account_subscriptions.add(subscriber) - _CHANNELHANDLER._cached_cmdsets.pop(subscriber, None) - self._recache() - - def remove(self, entity): - """ - Remove a subscriber from the channel. - - Args: - entity (Account, Object or list): The entity or - entities to un-subscribe from the channel. - - """ - global _CHANNELHANDLER - if not _CHANNELHANDLER: - from evennia.comms.channelhandler import CHANNEL_HANDLER as _CHANNELHANDLER - for subscriber in make_iter(entity): - if subscriber: - clsname = subscriber.__dbclass__.__name__ - # chooses the right type - if clsname == "AccountDB": - self.obj.db_account_subscriptions.remove(entity) - elif clsname == "ObjectDB": - self.obj.db_object_subscriptions.remove(entity) - _CHANNELHANDLER._cached_cmdsets.pop(subscriber, None) - self._recache() - - def all(self): - """ - Get all subscriptions to this channel. - - Returns: - subscribers (list): The subscribers. This - may be a mix of Accounts and Objects! - - """ - if self._cache is None: - self._recache() - return self._cache - - get = all # alias - - def online(self): - """ - Get all online accounts from our cache - Returns: - subscribers (list): Subscribers who are online or - are puppeted by an online account. - """ - subs = [] - recache_needed = False - for obj in self.all(): - from django.core.exceptions import ObjectDoesNotExist - - try: - if hasattr(obj, "account") and obj.account: - obj = obj.account - if not obj.is_connected: - continue - except ObjectDoesNotExist: - # a subscribed object has already been deleted. Mark that we need a recache and ignore it - recache_needed = True - continue - subs.append(obj) - if recache_needed: - self._recache() - return subs - - def clear(self): - """ - Remove all subscribers from channel. - - """ - self.obj.db_account_subscriptions.clear() - self.obj.db_object_subscriptions.clear() - self._cache = None - - -
[docs]class ChannelDB(TypedObject): - """ - This is the basis of a comm channel, only implementing - the very basics of distributing messages. - - The Channel class defines the following database fields - beyond the ones inherited from TypedObject: - - - db_account_subscriptions: The Account subscriptions. - - db_object_subscriptions: The Object subscriptions. - - """ - - db_account_subscriptions = models.ManyToManyField( - "accounts.AccountDB", - related_name="account_subscription_set", - blank=True, - verbose_name="account subscriptions", - db_index=True, - ) - - db_object_subscriptions = models.ManyToManyField( - "objects.ObjectDB", - related_name="object_subscription_set", - blank=True, - verbose_name="object subscriptions", - db_index=True, - ) - - # Database manager - objects = managers.ChannelDBManager() - - __settingclasspath__ = settings.BASE_CHANNEL_TYPECLASS - __defaultclasspath__ = "evennia.comms.comms.DefaultChannel" - __applabel__ = "comms" - - class Meta(object): - "Define Django meta options" - verbose_name = "Channel" - verbose_name_plural = "Channels" - - def __str__(self): - "Echoes the text representation of the channel." - return "Channel '%s' (%s)" % (self.key, self.db.desc) - -
[docs] @lazy_property - def subscriptions(self): - return SubscriptionHandler(self)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/barter.html b/docs/0.9.5/_modules/evennia/contrib/barter.html deleted file mode 100644 index c12f1c5d3b..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/barter.html +++ /dev/null @@ -1,1003 +0,0 @@ - - - - - - - - evennia.contrib.barter — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.barter

-"""
-Barter system
-
-Evennia contribution - Griatch 2012
-
-
-This implements a full barter system - a way for players to safely
-trade items between each other using code rather than simple free-form
-talking.  The advantage of this is increased buy/sell safety but it
-also streamlines the process and makes it faster when doing many
-transactions (since goods are automatically exchanged once both
-agree).
-
-This system is primarily intended for a barter economy, but can easily
-be used in a monetary economy as well -- just let the "goods" on one
-side be coin objects (this is more flexible than a simple "buy"
-command since you can mix coins and goods in your trade).
-
-In this module, a "barter" is generally referred to as a "trade".
-
-
-- Trade example
-
-A trade (barter) action works like this: A and B are the parties.
-
-1) opening a trade
-
-A: trade B: Hi, I have a nice extra sword. You wanna trade?
-B sees: A says: "Hi, I have a nice extra sword. You wanna trade?"
-   A wants to trade with you. Enter 'trade A <emote>' to accept.
-B: trade A: Hm, I could use a good sword ...
-A sees: B says: "Hm, I could use a good sword ...
-   B accepts the trade. Use 'trade help' for aid.
-B sees: You are now trading with A. Use 'trade help' for aid.
-
-2) negotiating
-
-A: offer sword: This is a nice sword. I would need some rations in trade.
-B sees: A says: "This is a nice sword. I would need some rations in trade."
-   [A offers Sword of might.]
-B evaluate sword
-B sees: <Sword's description and possibly stats>
-B: offer ration: This is a prime ration.
-A sees: B says: "This is a prime ration."
-  [B offers iron ration]
-A: say Hey, this is a nice sword, I need something more for it.
-B sees: A says: "Hey this is a nice sword, I need something more for it."
-B: offer sword,apple: Alright. I will also include a magic apple. That's my last offer.
-A sees: B says: "Alright, I will also include a magic apple. That's my last offer."
-  [B offers iron ration and magic apple]
-A accept: You are killing me here, but alright.
-B sees: A says: "You are killing me here, but alright."
-  [A accepts your offer. You must now also accept.]
-B accept: Good, nice making business with you.
-  You accept the deal. Deal is made and goods changed hands.
-A sees: B says: "Good, nice making business with you."
-  B accepts the deal. Deal is made and goods changed hands.
-
-At this point the trading system is exited and the negotiated items
-are automatically exchanged between the parties. In this example B was
-the only one changing their offer, but also A could have changed their
-offer until the two parties found something they could agree on. The
-emotes are optional but useful for RP-heavy worlds.
-
-- Technical info
-
-The trade is implemented by use of a TradeHandler. This object is a
-common place for storing the current status of negotiations. It is
-created on the object initiating the trade, and also stored on the
-other party once that party agrees to trade. The trade request times
-out after a certain time - this is handled by a Script. Once trade
-starts, the CmdsetTrade cmdset is initiated on both parties along with
-the commands relevant for the trading.
-
-- Ideas for NPC bartering:
-
-This module is primarily intended for trade between two players. But
-it can also in principle be used for a player negotiating with an
-AI-controlled NPC. If the NPC uses normal commands they can use it
-directly -- but more efficient is to have the NPC object send its
-replies directly through the tradehandler to the player. One may want
-to add some functionality to the decline command, so players can
-decline specific objects in the NPC offer (decline <object>) and allow
-the AI to maybe offer something else and make it into a proper
-barter.  Along with an AI that "needs" things or has some sort of
-personality in the trading, this can make bartering with NPCs at least
-moderately more interesting than just plain 'buy'.
-
-- Installation:
-
-Just import the CmdTrade command into (for example) the default
-cmdset. This will make the trade (or barter) command available
-in-game.
-
-"""
-
-from evennia import Command, DefaultScript, CmdSet
-
-TRADE_TIMEOUT = 60  # timeout for B to accept trade
-
-
-
[docs]class TradeTimeout(DefaultScript): - """ - This times out the trade request, in case player B did not reply in time. - """ - -
[docs] def at_script_creation(self): - """ - Called when script is first created - """ - self.key = "trade_request_timeout" - self.desc = "times out trade requests" - self.interval = TRADE_TIMEOUT - self.start_delay = True - self.repeats = 1 - self.persistent = False
- -
[docs] def at_repeat(self): - """ - called once - """ - if self.ndb.tradeevent: - self.obj.ndb.tradeevent.finish(force=True) - self.obj.msg("Trade request timed out.")
- -
[docs] def is_valid(self): - """ - Only valid if the trade has not yet started - """ - return self.obj.ndb.tradeevent and not self.obj.ndb.tradeevent.trade_started
- - -
[docs]class TradeHandler(object): - """ - Objects of this class handles the ongoing trade, notably storing the current - offers from each side and wether both have accepted or not. - """ - -
[docs] def __init__(self, part_a, part_b): - """ - Initializes the trade. This is called when part A tries to - initiate a trade with part B. The trade will not start until - part B repeats this command (B will then call the self.join() - command) - - Args: - part_a (object): The party trying to start barter. - part_b (object): The party asked to barter. - - Notes: - We also store the back-reference from the respective party - to this object. - - """ - # parties - self.part_a = part_a - self.part_b = part_b - - self.part_a.cmdset.add(CmdsetTrade()) - self.trade_started = False - self.part_a.ndb.tradehandler = self - # trade variables - self.part_a_offers = [] - self.part_b_offers = [] - self.part_a_accepted = False - self.part_b_accepted = False
- -
[docs] def msg_other(self, sender, string): - """ - Relay a message to the *other* party without needing to know - which party that is. This allows the calling command to not - have to worry about which party they are in the handler. - - Args: - sender (object): One of A or B. The method will figure - out the *other* party to send to. - string (str): Text to send. - """ - if self.part_a == sender: - self.part_b.msg(string) - elif self.part_b == sender: - self.part_a.msg(string) - else: - # no match, relay to oneself - sender.msg(string) if sender else self.part_a.msg(string)
- -
[docs] def get_other(self, party): - """ - Returns the other party of the trade - - Args: - party (object): One of the parties of the negotiation - - Returns: - party_other (object): The other party, not the first party. - - """ - if self.part_a == party: - return self.part_b - if self.part_b == party: - return self.part_a - return None
- -
[docs] def join(self, part_b): - """ - This is used once B decides to join the trade - - Args: - part_b (object): The party accepting the barter. - - """ - if self.part_b == part_b: - self.part_b.ndb.tradehandler = self - self.part_b.cmdset.add(CmdsetTrade()) - self.trade_started = True - return True - return False
- -
[docs] def unjoin(self, part_b): - """ - This is used if B decides not to join the trade. - - Args: - part_b (object): The party leaving the barter. - - """ - if self.part_b == part_b: - self.finish(force=True) - return True - return False
- -
[docs] def offer(self, party, *args): - """ - Change the current standing offer. We leave it up to the - command to do the actual checks that the offer consists - of real, valid, objects. - - Args: - party (object): Who is making the offer - args (objects or str): Offerings. - - """ - if self.trade_started: - # reset accept statements whenever an offer changes - self.part_a_accepted = False - self.part_b_accepted = False - if party == self.part_a: - self.part_a_offers = list(args) - elif party == self.part_b: - self.part_b_offers = list(args) - else: - raise ValueError
- -
[docs] def list(self): - """ - List current offers. - - Returns: - offers (tuple): A tuple with two lists, (A_offers, B_offers). - - """ - return self.part_a_offers, self.part_b_offers
- -
[docs] def search(self, offername): - """ - Search current offers. - - Args: - offername (str or int): Object to search for, or its index in - the list of offered items. - - Returns: - offer (object): An object on offer, based on the search criterion. - - """ - all_offers = self.part_a_offers + self.part_b_offers - if isinstance(offername, int): - # an index to return - if 0 <= offername < len(all_offers): - return all_offers[offername] - - all_keys = [offer.key for offer in all_offers] - try: - imatch = all_keys.index(offername) - return all_offers[imatch] - except ValueError: - for offer in all_offers: - if offer.aliases.get(offername): - return offer - return None
- -
[docs] def accept(self, party): - """ - Accept the current offer. - - Args: - party (object): The party accepting the deal. - - Returns: - result (object): `True` if this closes the deal, `False` - otherwise - - Notes: - This will only close the deal if both parties have - accepted independently. This is done by calling the - `finish()` method. - - """ - if self.trade_started: - if party == self.part_a: - self.part_a_accepted = True - elif party == self.part_b: - self.part_b_accepted = True - else: - raise ValueError - return self.finish() # try to close the deal - return False
- -
[docs] def decline(self, party): - """ - Decline the offer (or change one's mind). - - Args: - party (object): Party declining the deal. - - Returns: - did_decline (bool): `True` if there was really an - `accepted` status to change, `False` otherwise. - - Notes: - If previously having used the `accept` command, this - function will only work as long as the other party has not - yet accepted. - - """ - if self.trade_started: - if party == self.part_a: - if self.part_a_accepted: - self.part_a_accepted = False - return True - return False - elif party == self.part_b: - if self.part_b_accepted: - self.part_b_accepted = False - return True - return False - else: - raise ValueError - return False
- -
[docs] def finish(self, force=False): - """ - Conclude trade - move all offers and clean up - - Args: - force (bool, optional): Force cleanup regardless of if the - trade was accepted or not (if not, no goods will change - hands but trading will stop anyway) - Returns: - result (bool): If the finish was successful. - - """ - fin = False - if self.trade_started and self.part_a_accepted and self.part_b_accepted: - # both accepted - move objects before cleanup - for obj in self.part_a_offers: - obj.location = self.part_b - for obj in self.part_b_offers: - obj.location = self.part_a - fin = True - if fin or force: - # cleanup - self.part_a.cmdset.delete("cmdset_trade") - self.part_b.cmdset.delete("cmdset_trade") - self.part_a_offers = None - self.part_b_offers = None - self.part_a.scripts.stop("trade_request_timeout") - # this will kill it also from B - del self.part_a.ndb.tradehandler - if self.part_b.ndb.tradehandler: - del self.part_b.ndb.tradehandler - return True - return False
- - -# trading commands (will go into CmdsetTrade, initialized by the -# CmdTrade command further down). - - -
[docs]class CmdTradeBase(Command): - """ - Base command for Trade commands to inherit from. Implements the - custom parsing. - """ - -
[docs] def parse(self): - """ - Parse the relevant parts and make it easily - available to the command - """ - self.args = self.args.strip() - self.tradehandler = self.caller.ndb.tradehandler - self.part_a = self.tradehandler.part_a - self.part_b = self.tradehandler.part_b - - self.other = self.tradehandler.get_other(self.caller) - self.msg_other = self.tradehandler.msg_other - - self.trade_started = self.tradehandler.trade_started - self.emote = "" - self.str_caller = "Your trade action: %s" - self.str_other = "%s:s trade action: " % self.caller.key + "%s" - if ":" in self.args: - self.args, self.emote = [part.strip() for part in self.args.rsplit(":", 1)] - self.str_caller = 'You say, "' + self.emote + '"\n [%s]' - if self.caller.has_account: - self.str_other = '|c%s|n says, "' % self.caller.key + self.emote + '"\n [%s]' - else: - self.str_other = '%s says, "' % self.caller.key + self.emote + '"\n [%s]'
- - -# trade help - - -
[docs]class CmdTradeHelp(CmdTradeBase): - """ - help command for the trade system. - - Usage: - trade help - - Displays help for the trade commands. - """ - - key = "trade help" - locks = "cmd:all()" - help_category = "Trade" - -
[docs] def func(self): - """Show the help""" - string = """ - Trading commands - - |woffer <objects> [:emote]|n - offer one or more objects for trade. The emote can be used for - RP/arguments. A new offer will require both parties to re-accept - it again. - |waccept [:emote]|n - accept the currently standing offer from both sides. Also 'agree' - works. Once both have accepted, the deal is finished and goods - will change hands. - |wdecline [:emote]|n - change your mind and remove a previous accept (until other - has also accepted) - |wstatus|n - show the current offers on each side of the deal. Also 'offers' - and 'deal' works. - |wevaluate <nr> or <offer>|n - examine any offer in the deal. List them with the 'status' command. - |wend trade|n - end the negotiations prematurely. No trade will take place. - - You can also use |wemote|n, |wsay|n etc to discuss - without making a decision or offer. - """ - self.caller.msg(string)
- - -# offer - - -
[docs]class CmdOffer(CmdTradeBase): - """ - offer one or more items in trade. - - Usage: - offer <object> [, object2, ...][:emote] - - Offer objects in trade. This will replace the currently - standing offer. - """ - - key = "offer" - locks = "cmd:all()" - help_category = "Trading" - -
[docs] def func(self): - """implement the offer""" - - caller = self.caller - if not self.args: - caller.msg("Usage: offer <object> [, object2, ...] [:emote]") - return - if not self.trade_started: - caller.msg("Wait until the other party has accepted to trade with you.") - return - - # gather all offers - offers = [part.strip() for part in self.args.split(",")] - offerobjs = [] - for offername in offers: - obj = caller.search(offername) - if not obj: - return - offerobjs.append(obj) - self.tradehandler.offer(self.caller, *offerobjs) - - # output - if len(offerobjs) > 1: - objnames = ( - ", ".join("|w%s|n" % obj.key for obj in offerobjs[:-1]) - + " and |w%s|n" % offerobjs[-1].key - ) - else: - objnames = "|w%s|n" % offerobjs[0].key - - caller.msg(self.str_caller % ("You offer %s" % objnames)) - self.msg_other(caller, self.str_other % ("They offer %s" % objnames))
- - -# accept - - -
[docs]class CmdAccept(CmdTradeBase): - """ - accept the standing offer - - Usage: - accept [:emote] - agreee [:emote] - - This will accept the current offer. The other party must also accept - for the deal to go through. You can use the 'decline' command to change - your mind as long as the other party has not yet accepted. You can inspect - the current offer using the 'offers' command. - """ - - key = "accept" - aliases = ["agree"] - locks = "cmd:all()" - help_category = "Trading" - -
[docs] def func(self): - """accept the offer""" - caller = self.caller - if not self.trade_started: - caller.msg("Wait until the other party has accepted to trade with you.") - return - if self.tradehandler.accept(self.caller): - # deal finished. Trade ended and cleaned. - caller.msg( - self.str_caller - % "You |gaccept|n the deal. |gDeal is made and goods changed hands.|n" - ) - self.msg_other( - caller, - self.str_other % "%s |gaccepts|n the deal." - " |gDeal is made and goods changed hands.|n" % caller.key, - ) - else: - # a one-sided accept. - caller.msg( - self.str_caller - % "You |Gaccept|n the offer. %s must now also accept." - % self.other.key - ) - self.msg_other( - caller, - self.str_other % "%s |Gaccepts|n the offer. You must now also accept." % caller.key, - )
- - -# decline - - -
[docs]class CmdDecline(CmdTradeBase): - """ - decline the standing offer - - Usage: - decline [:emote] - - This will decline a previously 'accept'ed offer (so this allows you to - change your mind). You can only use this as long as the other party - has not yet accepted the deal. Also, changing the offer will automatically - decline the old offer. - """ - - key = "decline" - locks = "cmd:all()" - help_category = "Trading" - -
[docs] def func(self): - """decline the offer""" - caller = self.caller - if not self.trade_started: - caller.msg("Wait until the other party has accepted to trade with you.") - return - offer_a, offer_b = self.tradehandler.list() - if not offer_a or not offer_b: - caller.msg("No offers have been made yet, so there is nothing to decline.") - return - if self.tradehandler.decline(self.caller): - # changed a previous accept - caller.msg(self.str_caller % "You change your mind, |Rdeclining|n the current offer.") - self.msg_other( - caller, - self.str_other - % "%s changes their mind, |Rdeclining|n the current offer." - % caller.key, - ) - else: - # no acceptance to change - caller.msg(self.str_caller % "You |Rdecline|n the current offer.") - self.msg_other(caller, self.str_other % "%s declines the current offer." % caller.key)
- - -# evaluate - -# Note: This version only shows the description. If your particular game -# lists other important properties of objects (such as weapon damage, weight, -# magical properties, ammo requirements or whatnot), then you need to add this -# here. - - -
[docs]class CmdEvaluate(CmdTradeBase): - """ - evaluate objects on offer - - Usage: - evaluate <offered object> - - This allows you to examine any object currently on offer, to - determine if it's worth your while. - """ - - key = "evaluate" - aliases = ["eval"] - locks = "cmd:all()" - help_category = "Trading" - -
[docs] def func(self): - """evaluate an object""" - caller = self.caller - if not self.args: - caller.msg("Usage: evaluate <offered object>") - return - # we also accept indices - try: - ind = int(self.args) - self.args = ind - 1 - except Exception: - # not a valid index - ignore - pass - - offer = self.tradehandler.search(self.args) - if not offer: - caller.msg("No offer matching '%s' was found." % self.args) - return - # show the description - caller.msg(offer.db.desc)
- - -# status - - -
[docs]class CmdStatus(CmdTradeBase): - """ - show a list of the current deal - - Usage: - status - deal - offers - - Shows the currently suggested offers on each sides of the deal. To - accept the current deal, use the 'accept' command. Use 'offer' to - change your deal. You might also want to use 'say', 'emote' etc to - try to influence the other part in the deal. - """ - - key = "status" - aliases = ["offers", "deal"] - locks = "cmd:all()" - help_category = "Trading" - -
[docs] def func(self): - """Show the current deal""" - caller = self.caller - part_a_offers, part_b_offers = self.tradehandler.list() - count = 1 - part_a_offerlist = [] - for offer in part_a_offers: - part_a_offerlist.append("\n |w%i|n %s" % (count, offer.key)) - count += 1 - if not part_a_offerlist: - part_a_offerlist = "\n <nothing>" - part_b_offerlist = [] - for offer in part_b_offers: - part_b_offerlist.append("\n |w%i|n %s" % (count, offer.key)) - count += 1 - if not part_b_offerlist: - part_b_offerlist = "\n <nothing>" - - string = "|gOffered by %s:|n%s\n|yOffered by %s:|n%s" % ( - self.part_a.key, - "".join(part_a_offerlist), - self.part_b.key, - "".join(part_b_offerlist), - ) - accept_a = self.tradehandler.part_a_accepted and "|gYes|n" or "|rNo|n" - accept_b = self.tradehandler.part_b_accepted and "|gYes|n" or "|rNo|n" - string += "\n\n%s agreed: %s, %s agreed: %s" % ( - self.part_a.key, - accept_a, - self.part_b.key, - accept_b, - ) - string += "\n Use 'offer', 'eval' and 'accept'/'decline' to trade. See also 'trade help'." - caller.msg(string)
- - -# finish - - -
[docs]class CmdFinish(CmdTradeBase): - """ - end the trade prematurely - - Usage: - end trade [:say] - finish trade [:say] - - This ends the trade prematurely. No trade will take place. - - """ - - key = "end trade" - aliases = "finish trade" - locks = "cmd:all()" - help_category = "Trading" - -
[docs] def func(self): - """end trade""" - caller = self.caller - self.tradehandler.finish(force=True) - caller.msg(self.str_caller % "You |raborted|n trade. No deal was made.") - self.msg_other( - caller, self.str_other % "%s |raborted|n trade. No deal was made." % caller.key - )
- - -# custom Trading cmdset - - -
[docs]class CmdsetTrade(CmdSet): - """ - This cmdset is added when trade is initated. It is handled by the - trade event handler. - """ - - key = "cmdset_trade" - -
[docs] def at_cmdset_creation(self): - """Called when cmdset is created""" - self.add(CmdTradeHelp()) - self.add(CmdOffer()) - self.add(CmdAccept()) - self.add(CmdDecline()) - self.add(CmdEvaluate()) - self.add(CmdStatus()) - self.add(CmdFinish())
- - -# access command - once both have given this, this will create the -# trading cmdset to start trade. - - -
[docs]class CmdTrade(Command): - """ - Initiate trade with another party - - Usage: - trade <other party> [:say] - trade <other party> accept [:say] - trade <other party> decline [:say] - - Initiate trade with another party. The other party needs to repeat - this command with trade accept/decline within a minute in order to - properly initiate the trade action. You can use the decline option - yourself if you want to retract an already suggested trade. The - optional say part works like the say command and allows you to add - info to your choice. - """ - - key = "trade" - aliases = ["barter"] - locks = "cmd:all()" - help_category = "General" - -
[docs] def func(self): - """Initiate trade""" - - if not self.args: - if self.caller.ndb.tradehandler and self.caller.ndb.tradeevent.trade_started: - self.caller.msg("You are already in a trade. Use 'end trade' to abort it.") - else: - self.caller.msg("Usage: trade <other party> [accept|decline] [:emote]") - return - self.args = self.args.strip() - - # handle the emote manually here - selfemote = "" - theiremote = "" - if ":" in self.args: - self.args, emote = [part.strip() for part in self.args.rsplit(":", 1)] - selfemote = 'You say, "%s"\n ' % emote - if self.caller.has_account: - theiremote = '|c%s|n says, "%s"\n ' % (self.caller.key, emote) - else: - theiremote = '%s says, "%s"\n ' % (self.caller.key, emote) - - # for the sake of this command, the caller is always part_a; this - # might not match the actual name in tradehandler (in the case of - # using this command to accept/decline a trade invitation). - part_a = self.caller - accept = "accept" in self.args - decline = "decline" in self.args - if accept: - part_b = self.args.rstrip("accept").strip() - elif decline: - part_b = self.args.rstrip("decline").strip() - else: - part_b = self.args - part_b = self.caller.search(part_b) - if not part_b: - return - if part_a == part_b: - part_a.msg("You play trader with yourself.") - return - - # messages - str_init_a = "You ask to trade with %s. They need to accept within %s secs." - str_init_b = "%s wants to trade with you. Use |wtrade %s accept/decline [:emote]|n to answer (within %s secs)." - str_noinit_a = "%s declines the trade" - str_noinit_b = "You decline trade with %s." - str_start_a = "%s starts to trade with you. See |wtrade help|n for aid." - str_start_b = "You start to trade with %s. See |wtrade help|n for aid." - - if not (accept or decline): - # initialization of trade - if self.caller.ndb.tradehandler: - # trying to start trade without stopping a previous one - if self.caller.ndb.tradehandler.trade_started: - string = "You are already in trade with %s. You need to end trade first." - else: - string = "You are already trying to initiate trade with %s. You need to decline that trade first." - self.caller.msg(string % part_b.key) - elif part_b.ndb.tradehandler and part_b.ndb.tradehandler.part_b == part_a: - # this is equivalent to part_a accepting a trade from part_b (so roles are reversed) - part_b.ndb.tradehandler.join(part_a) - part_b.msg(theiremote + str_start_a % part_a.key) - part_a.msg(selfemote + str_start_b % part_b.key) - else: - # initiate a new trade - TradeHandler(part_a, part_b) - part_a.msg(selfemote + str_init_a % (part_b.key, TRADE_TIMEOUT)) - part_b.msg(theiremote + str_init_b % (part_a.key, part_a.key, TRADE_TIMEOUT)) - part_a.scripts.add(TradeTimeout) - return - elif accept: - # accept a trade proposal from part_b (so roles are reversed) - if part_a.ndb.tradehandler: - # already in a trade - part_a.msg( - "You are already in trade with %s. You need to end that first." % part_b.key - ) - return - if part_b.ndb.tradehandler.join(part_a): - part_b.msg(theiremote + str_start_a % part_a.key) - part_a.msg(selfemote + str_start_b % part_b.key) - else: - part_a.msg("No trade proposal to accept.") - return - else: - # decline trade proposal from part_b (so roles are reversed) - if part_a.ndb.tradehandler and part_a.ndb.tradehandler.part_b == part_a: - # stopping an invite - part_a.ndb.tradehandler.finish(force=True) - part_b.msg(theiremote + "%s aborted trade attempt with you." % part_a) - part_a.msg(selfemote + "You aborted the trade attempt with %s." % part_b) - elif part_b.ndb.tradehandler and part_b.ndb.tradehandler.unjoin(part_a): - part_b.msg(theiremote + str_noinit_a % part_a.key) - part_a.msg(selfemote + str_noinit_b % part_b.key) - else: - part_a.msg("No trade proposal to decline.") - return
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/building_menu.html b/docs/0.9.5/_modules/evennia/contrib/building_menu.html deleted file mode 100644 index 699df22b41..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/building_menu.html +++ /dev/null @@ -1,1373 +0,0 @@ - - - - - - - - evennia.contrib.building_menu — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.building_menu

-"""
-Module containing the building menu system.
-
-Evennia contributor: vincent-lg 2018
-
-Building menus are in-game menus, not unlike `EvMenu` though using a
-different approach.  Building menus have been specifically designed to edit
-information as a builder.  Creating a building menu in a command allows
-builders quick-editing of a given object, like a room.  If you follow the
-steps below to add the contrib, you will have access to an `@edit` command
-that will edit any default object offering to change its key and description.
-
-1. Import the `GenericBuildingCmd` class from this contrib in your `mygame/commands/default_cmdset.py` file:
-
-    ```python
-    from evennia.contrib.building_menu import GenericBuildingCmd
-    ```
-
-2. Below, add the command in the `CharacterCmdSet`:
-
-    ```python
-    # ... These lines should exist in the file
-    class CharacterCmdSet(default_cmds.CharacterCmdSet):
-        key = "DefaultCharacter"
-
-        def at_cmdset_creation(self):
-            super(CharacterCmdSet, self).at_cmdset_creation()
-            # ... add the line below
-            self.add(GenericBuildingCmd())
-    ```
-
-The `@edit` command will allow you to edit any object.  You will need to
-specify the object name or ID as an argument.  For instance: `@edit here`
-will edit the current room.  However, building menus can perform much more
-than this very simple example, read on for more details.
-
-Building menus can be set to edit about anything.  Here is an example of
-output you could obtain when editing the room:
-
-```
- Editing the room: Limbo(#2)
-
- [T]itle: the limbo room
- [D]escription
-    This is the limbo room.  You can easily change this default description,
-    either by using the |y@desc/edit|n command, or simply by entering this
-    menu (enter |yd|n).
- [E]xits:
-     north to A parking(#4)
- [Q]uit this menu
-```
-
-From there, you can open the title choice by pressing t.  You can then
-change the room title by simply entering text, and go back to the
-main menu entering @ (all this is customizable).  Press q to quit this menu.
-
-The first thing to do is to create a new module and place a class
-inheriting from `BuildingMenu` in it.
-
-```python
-from evennia.contrib.building_menu import BuildingMenu
-
-class RoomBuildingMenu(BuildingMenu):
-    # ...
-```
-
-Next, override the `init` method.  You can add choices (like the title,
-description, and exits choices as seen above) by using the `add_choice`
-method.
-
-```
-class RoomBuildingMenu(BuildingMenu):
-    def init(self, room):
-        self.add_choice("title", "t", attr="key")
-```
-
-That will create the first choice, the title choice.  If one opens your menu
-and enter t, she will be in the title choice.  She can change the title
-(it will write in the room's `key` attribute) and then go back to the
-main menu using `@`.
-
-`add_choice` has a lot of arguments and offers a great deal of
-flexibility.  The most useful ones is probably the usage of callbacks,
-as you can set almost any argument in `add_choice` to be a callback, a
-function that you have defined above in your module.  This function will be
-called when the menu element is triggered.
-
-Notice that in order to edit a description, the best method to call isn't
-`add_choice`, but `add_choice_edit`.  This is a convenient shortcut
-which is available to quickly open an `EvEditor` when entering this choice
-and going back to the menu when the editor closes.
-
-```
-class RoomBuildingMenu(BuildingMenu):
-    def init(self, room):
-        self.add_choice("title", "t", attr="key")
-        self.add_choice_edit("description", key="d", attr="db.desc")
-```
-
-When you wish to create a building menu, you just need to import your
-class, create it specifying your intended caller and object to edit,
-then call `open`:
-
-```python
-from <wherever> import RoomBuildingMenu
-
-class CmdEdit(Command):
-
-    key = "redit"
-
-    def func(self):
-        menu = RoomBuildingMenu(self.caller, self.caller.location)
-        menu.open()
-```
-
-This is a very short introduction.  For more details, see the online tutorial
-(https://github.com/evennia/evennia/wiki/Building-menus) or read the
-heavily-documented code below.
-
-"""
-
-from inspect import getargspec
-from textwrap import dedent
-
-from django.conf import settings
-from evennia import Command, CmdSet
-from evennia.commands import cmdhandler
-from evennia.utils.ansi import strip_ansi
-from evennia.utils.eveditor import EvEditor
-from evennia.utils.logger import log_err, log_trace
-from evennia.utils.utils import class_from_module
-
-
-# Constants
-_MAX_TEXT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-_CMD_NOMATCH = cmdhandler.CMD_NOMATCH
-_CMD_NOINPUT = cmdhandler.CMD_NOINPUT
-
-
-# Private functions
-def _menu_loadfunc(caller):
-    obj, attr = caller.attributes.get("_building_menu_to_edit", [None, None])
-    if obj and attr:
-        for part in attr.split(".")[:-1]:
-            obj = getattr(obj, part)
-
-    return getattr(obj, attr.split(".")[-1]) if obj is not None else ""
-
-
-def _menu_savefunc(caller, buf):
-    obj, attr = caller.attributes.get("_building_menu_to_edit", [None, None])
-    if obj and attr:
-        for part in attr.split(".")[:-1]:
-            obj = getattr(obj, part)
-
-        setattr(obj, attr.split(".")[-1], buf)
-
-    caller.attributes.remove("_building_menu_to_edit")
-    return True
-
-
-def _menu_quitfunc(caller):
-    caller.cmdset.add(
-        BuildingMenuCmdSet,
-        permanent=caller.ndb._building_menu and caller.ndb._building_menu.persistent or False,
-    )
-    if caller.ndb._building_menu:
-        caller.ndb._building_menu.move(back=True)
-
-
-def _call_or_get(value, menu=None, choice=None, string=None, obj=None, caller=None):
-    """
-    Call the value, if appropriate, or just return it.
-
-    Args:
-        value (any): the value to obtain.  It might be a callable (see note).
-
-    Keyword Args:
-        menu (BuildingMenu, optional): the building menu to pass to value
-                if it is a callable.
-        choice (Choice, optional): the choice to pass to value if a callable.
-        string (str, optional): the raw string to pass to value if a callback.
-        obj (Object): the object to pass to value if a callable.
-        caller (Account or Object, optional): the caller to pass to value
-                if a callable.
-
-    Returns:
-        The value itself.  If the argument is a function, call it with
-        specific arguments (see note).
-
-    Note:
-        If `value` is a function, call it with varying arguments.  The
-        list of arguments will depend on the argument names in your callable.
-        - An argument named `menu` will contain the building menu or None.
-        - The `choice` argument will contain the choice or None.
-        - The `string` argument will contain the raw string or None.
-        - The `obj` argument will contain the object or None.
-        - The `caller` argument will contain the caller or None.
-        - Any other argument will contain the object (`obj`).
-        Thus, you could define callbacks like this:
-            def on_enter(menu, caller, obj):
-            def on_nomatch(string, choice, menu):
-            def on_leave(caller, room): # note that room will contain `obj`
-
-    """
-    if callable(value):
-        # Check the function arguments
-        kwargs = {}
-        spec = getargspec(value)
-        args = spec.args
-        if spec.keywords:
-            kwargs.update(dict(menu=menu, choice=choice, string=string, obj=obj, caller=caller))
-        else:
-            if "menu" in args:
-                kwargs["menu"] = menu
-            if "choice" in args:
-                kwargs["choice"] = choice
-            if "string" in args:
-                kwargs["string"] = string
-            if "obj" in args:
-                kwargs["obj"] = obj
-            if "caller" in args:
-                kwargs["caller"] = caller
-
-        # Fill missing arguments
-        for arg in args:
-            if arg not in kwargs:
-                kwargs[arg] = obj
-
-        # Call the function and return its return value
-        return value(**kwargs)
-
-    return value
-
-
-# Helper functions, to be used in menu choices
-
-
-
-
-
-
-
-
-
-
-
-# Building menu commands and CmdSet
-
-
-
[docs]class CmdNoInput(Command): - - """No input has been found.""" - - key = _CMD_NOINPUT - locks = "cmd:all()" - -
[docs] def __init__(self, **kwargs): - self.menu = kwargs.pop("building_menu", None) - super(Command, self).__init__(**kwargs)
- -
[docs] def func(self): - """Display the menu or choice text.""" - if self.menu: - self.menu.display() - else: - log_err("When CMDNOINPUT was called, the building menu couldn't be found") - self.caller.msg("|rThe building menu couldn't be found, remove the CmdSet.|n") - self.caller.cmdset.delete(BuildingMenuCmdSet)
- - -
[docs]class CmdNoMatch(Command): - - """No input has been found.""" - - key = _CMD_NOMATCH - locks = "cmd:all()" - -
[docs] def __init__(self, **kwargs): - self.menu = kwargs.pop("building_menu", None) - super(Command, self).__init__(**kwargs)
- -
[docs] def func(self): - """Call the proper menu or redirect to nomatch.""" - raw_string = self.args.rstrip() - if self.menu is None: - log_err("When CMDNOMATCH was called, the building menu couldn't be found") - self.caller.msg("|rThe building menu couldn't be found, remove the CmdSet.|n") - self.caller.cmdset.delete(BuildingMenuCmdSet) - return - - choice = self.menu.current_choice - if raw_string in self.menu.keys_go_back: - if self.menu.keys: - self.menu.move(back=True) - elif self.menu.parents: - self.menu.open_parent_menu() - else: - self.menu.display() - elif choice: - if choice.nomatch(raw_string): - self.caller.msg(choice.format_text()) - else: - for choice in self.menu.relevant_choices: - if choice.key.lower() == raw_string.lower() or any( - raw_string.lower() == alias for alias in choice.aliases - ): - self.menu.move(choice.key) - return - - self.msg("|rUnknown command: {}|n.".format(raw_string))
- - -
[docs]class BuildingMenuCmdSet(CmdSet): - - """Building menu CmdSet.""" - - key = "building_menu" - priority = 5 - -
[docs] def at_cmdset_creation(self): - """Populates the cmdset with commands.""" - caller = self.cmdsetobj - - # The caller could recall the menu - menu = caller.ndb._building_menu - if menu is None: - menu = caller.db._building_menu - if menu: - menu = BuildingMenu.restore(caller) - - cmds = [CmdNoInput, CmdNoMatch] - for cmd in cmds: - self.add(cmd(building_menu=menu))
- - -# Menu classes - - -
[docs]class Choice(object): - - """A choice object, created by `add_choice`.""" - -
[docs] def __init__( - self, - title, - key=None, - aliases=None, - attr=None, - text=None, - glance=None, - on_enter=None, - on_nomatch=None, - on_leave=None, - menu=None, - caller=None, - obj=None, - ): - """Constructor. - - Args: - title (str): the choice's title. - key (str, optional): the key of the letters to type to access - the choice. If not set, try to guess it based on the title. - aliases (list of str, optional): the allowed aliases for this choice. - attr (str, optional): the name of the attribute of 'obj' to set. - text (str or callable, optional): a text to be displayed for this - choice. It can be a callable. - glance (str or callable, optional): an at-a-glance summary of the - sub-menu shown in the main menu. It can be set to - display the current value of the attribute in the - main menu itself. - menu (BuildingMenu, optional): the parent building menu. - on_enter (callable, optional): a callable to call when the - caller enters into the choice. - on_nomatch (callable, optional): a callable to call when no - match is entered in the choice. - on_leave (callable, optional): a callable to call when the caller - leaves the choice. - caller (Account or Object, optional): the caller. - obj (Object, optional): the object to edit. - - """ - self.title = title - self.key = key - self.aliases = aliases - self.attr = attr - self.text = text - self.glance = glance - self.on_enter = on_enter - self.on_nomatch = on_nomatch - self.on_leave = on_leave - self.menu = menu - self.caller = caller - self.obj = obj
- - def __repr__(self): - return "<Choice (title={}, key={})>".format(self.title, self.key) - - @property - def keys(self): - """Return a tuple of keys separated by `sep_keys`.""" - return tuple(self.key.split(self.menu.sep_keys)) - -
[docs] def format_text(self): - """Format the choice text and return it, or an empty string.""" - text = "" - if self.text: - text = _call_or_get( - self.text, menu=self.menu, choice=self, string="", caller=self.caller, obj=self.obj - ) - text = dedent(text.strip("\n")) - text = text.format(obj=self.obj, caller=self.caller) - - return text
- -
[docs] def enter(self, string): - """Called when the user opens the choice. - - Args: - string (str): the entered string. - - """ - if self.on_enter: - _call_or_get( - self.on_enter, - menu=self.menu, - choice=self, - string=string, - caller=self.caller, - obj=self.obj, - )
- -
[docs] def nomatch(self, string): - """Called when the user entered something in the choice. - - Args: - string (str): the entered string. - - Returns: - to_display (bool): The return value of `nomatch` if set or - `True`. The rule is that if `no_match` returns `True`, - then the choice or menu is displayed. - - """ - if self.on_nomatch: - return _call_or_get( - self.on_nomatch, - menu=self.menu, - choice=self, - string=string, - caller=self.caller, - obj=self.obj, - ) - - return True
- -
[docs] def leave(self, string): - """Called when the user closes the choice. - - Args: - string (str): the entered string. - - """ - if self.on_leave: - _call_or_get( - self.on_leave, - menu=self.menu, - choice=self, - string=string, - caller=self.caller, - obj=self.obj, - )
- - -
[docs]class BuildingMenu(object): - - """ - Class allowing to create and set building menus to edit specific objects. - - A building menu is somewhat similar to `EvMenu`, but designed to edit - objects by builders, although it can be used for players in some contexts. - You could, for instance, create a building menu to edit a room with a - sub-menu for the room's key, another for the room's description, - another for the room's exits, and so on. - - To add choices (simple sub-menus), you should call `add_choice` (see the - full documentation of this method). With most arguments, you can - specify either a plain string or a callback. This callback will be - called when the operation is to be performed. - - Some methods are provided for frequent needs (see the `add_choice_*` - methods). Some helper functions are defined at the top of this - module in order to be used as arguments to `add_choice` - in frequent cases. - - """ - - keys_go_back = ["@"] # The keys allowing to go back in the menu tree - sep_keys = "." # The key separator for menus with more than 2 levels - joker_key = "*" # The special key meaning "anything" in a choice key - min_shortcut = 1 # The minimum length of shortcuts when `key` is not set - -
[docs] def __init__( - self, - caller=None, - obj=None, - title="Building menu: {obj}", - keys=None, - parents=None, - persistent=False, - ): - """Constructor, you shouldn't override. See `init` instead. - - Args: - caller (Account or Object): the caller. - obj (Object): the object to be edited, like a room. - title (str, optional): the menu title. - keys (list of str, optional): the starting menu keys (None - to start from the first level). - parents (tuple, optional): information for parent menus, - automatically supplied. - persistent (bool, optional): should this building menu - survive a reload/restart? - - Note: - If some of these options have to be changed, it is - preferable to do so in the `init` method and not to - override `__init__`. For instance: - class RoomBuildingMenu(BuildingMenu): - def init(self, room): - self.title = "Menu for room: {obj.key}(#{obj.id})" - # ... - - """ - self.caller = caller - self.obj = obj - self.title = title - self.keys = keys or [] - self.parents = parents or () - self.persistent = persistent - self.choices = [] - self.cmds = {} - self.can_quit = False - - if obj: - self.init(obj) - if not parents and not self.can_quit: - # Automatically add the menu to quit - self.add_choice_quit(key=None) - self._add_keys_choice()
- - @property - def current_choice(self): - """Return the current choice or None. - - Returns: - choice (Choice): the current choice or None. - - Note: - We use the menu keys to identify the current position of - the caller in the menu. The menu `keys` hold a list of - keys that should match a choice to be usable. - - """ - menu_keys = self.keys - if not menu_keys: - return None - - for choice in self.choices: - choice_keys = choice.keys - if len(menu_keys) == len(choice_keys): - # Check all the intermediate keys - common = True - for menu_key, choice_key in zip(menu_keys, choice_keys): - if choice_key == self.joker_key: - continue - - if not isinstance(menu_key, str) or menu_key != choice_key: - common = False - break - - if common: - return choice - - return None - - @property - def relevant_choices(self): - """Only return the relevant choices according to the current meny key. - - Returns: - relevant (list of Choice object): the relevant choices. - - Note: - We use the menu keys to identify the current position of - the caller in the menu. The menu `keys` hold a list of - keys that should match a choice to be usable. - - """ - menu_keys = self.keys - relevant = [] - for choice in self.choices: - choice_keys = choice.keys - if not menu_keys and len(choice_keys) == 1: - # First level choice with the menu key empty, that's relevant - relevant.append(choice) - elif len(menu_keys) == len(choice_keys) - 1: - # Check all the intermediate keys - common = True - for menu_key, choice_key in zip(menu_keys, choice_keys): - if choice_key == self.joker_key: - continue - - if not isinstance(menu_key, str) or menu_key != choice_key: - common = False - break - - if common: - relevant.append(choice) - - return relevant - - def _save(self): - """Save the menu in a attributes on the caller. - - If `persistent` is set to `True`, also save in a persistent attribute. - - """ - self.caller.ndb._building_menu = self - - if self.persistent: - self.caller.db._building_menu = { - "class": type(self).__module__ + "." + type(self).__name__, - "obj": self.obj, - "title": self.title, - "keys": self.keys, - "parents": self.parents, - "persistent": self.persistent, - } - - def _add_keys_choice(self): - """Add the choices' keys if some choices don't have valid keys.""" - # If choices have been added without keys, try to guess them - for choice in self.choices: - if not choice.key: - title = strip_ansi(choice.title.strip()).lower() - length = self.min_shortcut - while length <= len(title): - i = 0 - while i < len(title) - length + 1: - guess = title[i : i + length] - if guess not in self.cmds: - choice.key = guess - break - - i += 1 - - if choice.key: - break - - length += 1 - - if choice.key: - self.cmds[choice.key] = choice - else: - raise ValueError("Cannot guess the key for {}".format(choice)) - -
[docs] def init(self, obj): - """Create the sub-menu to edit the specified object. - - Args: - obj (Object): the object to edit. - - Note: - This method is probably to be overridden in your subclasses. - Use `add_choice` and its variants to create menu choices. - - """ - pass
- -
[docs] def add_choice( - self, - title, - key=None, - aliases=None, - attr=None, - text=None, - glance=None, - on_enter=None, - on_nomatch=None, - on_leave=None, - ): - """ - Add a choice, a valid sub-menu, in the current builder menu. - - Args: - title (str): the choice's title. - key (str, optional): the key of the letters to type to access - the sub-neu. If not set, try to guess it based on the - choice title. - aliases (list of str, optional): the aliases for this choice. - attr (str, optional): the name of the attribute of 'obj' to set. - This is really useful if you want to edit an - attribute of the object (that's a frequent need). If - you don't want to do so, just use the `on_*` arguments. - text (str or callable, optional): a text to be displayed when - the menu is opened It can be a callable. - glance (str or callable, optional): an at-a-glance summary of the - sub-menu shown in the main menu. It can be set to - display the current value of the attribute in the - main menu itself. - on_enter (callable, optional): a callable to call when the - caller enters into this choice. - on_nomatch (callable, optional): a callable to call when - the caller enters something in this choice. If you - don't set this argument but you have specified - `attr`, then `obj`.`attr` will be set with the value - entered by the user. - on_leave (callable, optional): a callable to call when the - caller leaves the choice. - - Returns: - choice (Choice): the newly-created choice. - - Raises: - ValueError if the choice cannot be added. - - Note: - Most arguments can be callables, like functions. This has the - advantage of allowing great flexibility. If you specify - a callable in most of the arguments, the callable should return - the value expected by the argument (a str more often than - not). For instance, you could set a function to be called - to get the menu text, which allows for some filtering: - def text_exits(menu): - return "Some text to display" - class RoomBuildingMenu(BuildingMenu): - def init(self): - self.add_choice("exits", key="x", text=text_exits) - - The allowed arguments in a callable are specific to the - argument names (they are not sensitive to orders, not all - arguments have to be present). For more information, see - `_call_or_get`. - - """ - key = key or "" - key = key.lower() - aliases = aliases or [] - aliases = [a.lower() for a in aliases] - if attr and on_nomatch is None: - on_nomatch = menu_setattr - - if key and key in self.cmds: - raise ValueError( - "A conflict exists between {} and {}, both use " - "key or alias {}".format(self.cmds[key], title, repr(key)) - ) - - if attr: - if glance is None: - glance = "{obj." + attr + "}" - if text is None: - text = """ - ------------------------------------------------------------------------------- - {attr} for {{obj}}(#{{obj.id}}) - - You can change this value simply by entering it. - Use |y{back}|n to go back to the main menu. - - Current value: |c{{{obj_attr}}}|n - """.format( - attr=attr, obj_attr="obj." + attr, back="|n or |y".join(self.keys_go_back) - ) - - choice = Choice( - title, - key=key, - aliases=aliases, - attr=attr, - text=text, - glance=glance, - on_enter=on_enter, - on_nomatch=on_nomatch, - on_leave=on_leave, - menu=self, - caller=self.caller, - obj=self.obj, - ) - self.choices.append(choice) - if key: - self.cmds[key] = choice - - for alias in aliases: - self.cmds[alias] = choice - - return choice
- -
[docs] def add_choice_edit( - self, - title="description", - key="d", - aliases=None, - attr="db.desc", - glance="\n {obj.db.desc}", - on_enter=None, - ): - """ - Add a simple choice to edit a given attribute in the EvEditor. - - Args: - title (str, optional): the choice's title. - key (str, optional): the choice's key. - aliases (list of str, optional): the choice's aliases. - glance (str or callable, optional): the at-a-glance description. - on_enter (callable, optional): a different callable to edit - the attribute. - - Returns: - choice (Choice): the newly-created choice. - - Note: - This is just a shortcut method, calling `add_choice`. - If `on_enter` is not set, use `menu_edit` which opens - an EvEditor to edit the specified attribute. - When the caller closes the editor (with :q), the menu - will be re-opened. - - """ - on_enter = on_enter or menu_edit - return self.add_choice( - title, key=key, aliases=aliases, attr=attr, glance=glance, on_enter=on_enter, text="" - )
- -
[docs] def add_choice_quit(self, title="quit the menu", key="q", aliases=None, on_enter=None): - """ - Add a simple choice just to quit the building menu. - - Args: - title (str, optional): the choice's title. - key (str, optional): the choice's key. - aliases (list of str, optional): the choice's aliases. - on_enter (callable, optional): a different callable - to quit the building menu. - - Note: - This is just a shortcut method, calling `add_choice`. - If `on_enter` is not set, use `menu_quit` which simply - closes the menu and displays a message. It also - removes the CmdSet from the caller. If you supply - another callable instead, make sure to do the same. - - """ - on_enter = on_enter or menu_quit - self.can_quit = True - return self.add_choice(title, key=key, aliases=aliases, on_enter=on_enter)
- -
[docs] def open(self): - """Open the building menu for the caller. - - Note: - This method should be called once when the building menu - has been instanciated. From there, the building menu will - be re-created automatically when the server - reloads/restarts, assuming `persistent` is set to `True`. - - """ - caller = self.caller - self._save() - - # Remove the same-key cmdset if exists - if caller.cmdset.has(BuildingMenuCmdSet): - caller.cmdset.remove(BuildingMenuCmdSet) - - self.caller.cmdset.add(BuildingMenuCmdSet, permanent=self.persistent) - self.display()
- -
[docs] def open_parent_menu(self): - """Open the parent menu, using `self.parents`. - - Note: - You probably don't need to call this method directly, - since the caller can go back to the parent menu using the - `keys_go_back` automatically. - - """ - parents = list(self.parents) - if parents: - parent_class, parent_obj, parent_keys = parents[-1] - del parents[-1] - - if self.caller.cmdset.has(BuildingMenuCmdSet): - self.caller.cmdset.remove(BuildingMenuCmdSet) - - try: - menu_class = class_from_module(parent_class) - except Exception: - log_trace( - "BuildingMenu: attempting to load class {} failed".format(repr(parent_class)) - ) - return - - # Create the parent menu - try: - building_menu = menu_class( - self.caller, parent_obj, keys=parent_keys, parents=tuple(parents) - ) - except Exception: - log_trace( - "An error occurred while creating building menu {}".format(repr(parent_class)) - ) - return - else: - return building_menu.open()
- -
[docs] def open_submenu(self, submenu_class, submenu_obj, parent_keys=None): - """ - Open a sub-menu, closing the current menu and opening the new one. - - Args: - submenu_class (str): the submenu class as a Python path. - submenu_obj (Object): the object to give to the submenu. - parent_keys (list of str, optional): the parent keys when - the submenu is closed. - - Note: - When the user enters `@` in the submenu, she will go back to - the current menu, with the `parent_keys` set as its keys. - Therefore, you should set it on the keys of the choice that - should be opened when the user leaves the submenu. - - Returns: - new_menu (BuildingMenu): the new building menu or None. - - """ - parent_keys = parent_keys or [] - parents = list(self.parents) - parents.append((type(self).__module__ + "." + type(self).__name__, self.obj, parent_keys)) - if self.caller.cmdset.has(BuildingMenuCmdSet): - self.caller.cmdset.remove(BuildingMenuCmdSet) - - # Shift to the new menu - try: - menu_class = class_from_module(submenu_class) - except Exception: - log_trace( - "BuildingMenu: attempting to load class {} failed".format(repr(submenu_class)) - ) - return - - # Create the submenu - try: - building_menu = menu_class(self.caller, submenu_obj, parents=parents) - except Exception: - log_trace( - "An error occurred while creating building menu {}".format(repr(submenu_class)) - ) - return - else: - return building_menu.open()
- -
[docs] def move(self, key=None, back=False, quiet=False, string=""): - """ - Move inside the menu. - - Args: - key (any): the portion of the key to add to the current - menu keys. If you wish to go back in the menu - tree, don't provide a `key`, just set `back` to `True`. - back (bool, optional): go back in the menu (`False` by default). - quiet (bool, optional): should the menu or choice be - displayed afterward? - string (str, optional): the string sent by the caller to move. - - Note: - This method will need to be called directly should you - use more than two levels in your menu. For instance, - in your room menu, if you want to have an "exits" - option, and then be able to enter "north" in this - choice to edit an exit. The specific exit choice - could be a different menu (with a different class), but - it could also be an additional level in your original menu. - If that's the case, you will need to use this method. - - """ - choice = self.current_choice - if choice: - choice.leave("") - - if not back: # Move forward - if not key: - raise ValueError("you are asking to move forward, you should specify a key.") - - self.keys.append(key) - else: # Move backward - if not self.keys: - raise ValueError( - "you already are at the top of the tree, you cannot move backward." - ) - - del self.keys[-1] - - self._save() - choice = self.current_choice - if choice: - choice.enter(string) - - if not quiet: - self.display()
- -
[docs] def close(self): - """Close the building menu, removing the CmdSet.""" - if self.caller.cmdset.has(BuildingMenuCmdSet): - self.caller.cmdset.delete(BuildingMenuCmdSet) - if self.caller.attributes.has("_building_menu"): - self.caller.attributes.remove("_building_menu") - if self.caller.nattributes.has("_building_menu"): - self.caller.nattributes.remove("_building_menu")
- - # Display methods. Override for customization -
[docs] def display_title(self): - """Return the menu title to be displayed.""" - return _call_or_get(self.title, menu=self, obj=self.obj, caller=self.caller).format( - obj=self.obj - )
- -
[docs] def display_choice(self, choice): - """Display the specified choice. - - Args: - choice (Choice): the menu choice. - - """ - title = _call_or_get( - choice.title, menu=self, choice=choice, obj=self.obj, caller=self.caller - ) - clear_title = title.lower() - pos = clear_title.find(choice.key.lower()) - ret = " " - if pos >= 0: - ret += title[:pos] + "[|y" + choice.key.title() + "|n]" + title[pos + len(choice.key) :] - else: - ret += "[|y" + choice.key.title() + "|n] " + title - - if choice.glance: - glance = _call_or_get( - choice.glance, menu=self, choice=choice, caller=self.caller, string="", obj=self.obj - ) - glance = glance.format(obj=self.obj, caller=self.caller) - - ret += ": " + glance - - return ret
- -
[docs] def display(self): - """Display the entire menu or a single choice, depending on the keys.""" - choice = self.current_choice - if self.keys and choice: - text = choice.format_text() - else: - text = self.display_title() + "\n" - for choice in self.relevant_choices: - text += "\n" + self.display_choice(choice) - - self.caller.msg(text)
- -
[docs] @staticmethod - def restore(caller): - """Restore the building menu for the caller. - - Args: - caller (Account or Object): the caller. - - Note: - This method should be automatically called if a menu is - saved in the caller, but the object itself cannot be found. - - """ - menu = caller.db._building_menu - if menu: - class_name = menu.get("class") - if not class_name: - log_err( - "BuildingMenu: on caller {}, a persistent attribute holds building menu " - "data, but no class could be found to restore the menu".format(caller) - ) - return - - try: - menu_class = class_from_module(class_name) - except Exception: - log_trace( - "BuildingMenu: attempting to load class {} failed".format(repr(class_name)) - ) - return - - # Create the menu - obj = menu.get("obj") - keys = menu.get("keys") - title = menu.get("title", "") - parents = menu.get("parents") - persistent = menu.get("persistent", False) - try: - building_menu = menu_class( - caller, obj, title=title, keys=keys, parents=parents, persistent=persistent - ) - except Exception: - log_trace( - "An error occurred while creating building menu {}".format(repr(class_name)) - ) - return - - return building_menu
- - -# Generic building menu and command -
[docs]class GenericBuildingMenu(BuildingMenu): - - """A generic building menu, allowing to edit any object. - - This is more a demonstration menu. By default, it allows to edit the - object key and description. Nevertheless, it will be useful to demonstrate - how building menus are meant to be used. - - """ - -
[docs] def init(self, obj): - """Build the meny, adding the 'key' and 'description' choices. - - Args: - obj (Object): any object to be edited, like a character or room. - - Note: - The 'quit' choice will be automatically added, though you can - call `add_choice_quit` to add this choice with different options. - - """ - self.add_choice( - "key", - key="k", - attr="key", - glance="{obj.key}", - text=""" - ------------------------------------------------------------------------------- - Editing the key of {{obj.key}}(#{{obj.id}}) - - You can change the simply by entering it. - Use |y{back}|n to go back to the main menu. - - Current key: |c{{obj.key}}|n - """.format( - back="|n or |y".join(self.keys_go_back) - ), - ) - self.add_choice_edit("description", key="d", attr="db.desc")
- - -
[docs]class GenericBuildingCmd(Command): - - """ - Generic building command. - - Syntax: - @edit [object] - - Open a building menu to edit the specified object. This menu allows to - change the object's key and description. - - Examples: - @edit here - @edit self - @edit #142 - - """ - - key = "@edit" - -
[docs] def func(self): - if not self.args.strip(): - self.msg("You should provide an argument to this function: the object to edit.") - return - - obj = self.caller.search(self.args.strip(), global_search=True) - if not obj: - return - - menu = GenericBuildingMenu(self.caller, obj) - menu.open()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/chargen.html b/docs/0.9.5/_modules/evennia/contrib/chargen.html deleted file mode 100644 index 3d61e093f6..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/chargen.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - - - evennia.contrib.chargen — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.chargen

-"""
-
-Contribution - Griatch 2011
-
-> Note - with the advent of MULTISESSION_MODE=2, this is not really as
-necessary anymore - the ooclook and @charcreate commands in that mode
-replaces this module with better functionality. This remains here for
-inspiration.
-
-This is a simple character creation commandset for the Account level.
-It shows some more info and gives the Account the option to create a
-character without any more customizations than their name (further
-options are unique for each game anyway).
-
-In MULTISESSION_MODEs 0 and 1, you will automatically log into an
-existing Character. When using `@ooc` you will then end up in this
-cmdset.
-
-Installation:
-
-Import this module to `mygame/commands/default_cmdsets.py` and
-add `chargen.OOCCMdSetCharGen` to the `AccountCmdSet` class
-(it says where to add it). Reload.
-
-"""
-
-from django.conf import settings
-from evennia import Command, create_object, utils
-from evennia import default_cmds, managers
-
-CHARACTER_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
-
-
-
[docs]class CmdOOCLook(default_cmds.CmdLook): - """ - ooc look - - Usage: - look - look <character> - - This is an OOC version of the look command. Since an Account doesn't - have an in-game existence, there is no concept of location or - "self". - - If any characters are available for you to control, you may look - at them with this command. - """ - - key = "look" - aliases = ["l", "ls"] - locks = "cmd:all()" - help_category = "General" - -
[docs] def func(self): - """ - Implements the ooc look command - - We use an attribute _character_dbrefs on the account in order - to figure out which characters are "theirs". A drawback of this - is that only the CmdCharacterCreate command adds this attribute, - and thus e.g. account #1 will not be listed (although it will work). - Existence in this list does not depend on puppeting rights though, - that is checked by the @ic command directly. - """ - - # making sure caller is really an account - self.character = None - if utils.inherits_from(self.caller, "evennia.objects.objects.Object"): - # An object of some type is calling. Convert to account. - self.character = self.caller - if hasattr(self.caller, "account"): - self.caller = self.caller.account - - if not self.character: - # ooc mode, we are accounts - - avail_chars = self.caller.db._character_dbrefs - if self.args: - # Maybe the caller wants to look at a character - if not avail_chars: - self.caller.msg("You have no characters to look at. Why not create one?") - return - objs = managers.objects.get_objs_with_key_and_typeclass( - self.args.strip(), CHARACTER_TYPECLASS - ) - objs = [obj for obj in objs if obj.id in avail_chars] - if not objs: - self.caller.msg("You cannot see this Character.") - return - self.caller.msg(objs[0].return_appearance(self.caller)) - return - - # not inspecting a character. Show the OOC info. - charnames = [] - if self.caller.db._character_dbrefs: - dbrefs = self.caller.db._character_dbrefs - charobjs = [managers.objects.get_id(dbref) for dbref in dbrefs] - charnames = [charobj.key for charobj in charobjs if charobj] - if charnames: - charlist = "The following Character(s) are available:\n\n" - charlist += "\n\r".join(["|w %s|n" % charname for charname in charnames]) - charlist += "\n\n Use |w@ic <character name>|n to switch to that Character." - else: - charlist = "You have no Characters." - string = """ You, %s, are an |wOOC ghost|n without form. The world is hidden - from you and besides chatting on channels your options are limited. - You need to have a Character in order to interact with the world. - - %s - - Use |wcreate <name>|n to create a new character and |whelp|n for a - list of available commands.""" % ( - self.caller.key, - charlist, - ) - self.caller.msg(string) - - else: - # not ooc mode - leave back to normal look - # we have to put this back for normal look to work. - self.caller = self.character - super().func()
- - -
[docs]class CmdOOCCharacterCreate(Command): - """ - creates a character - - Usage: - create <character name> - - This will create a new character, assuming - the given character name does not already exist. - """ - - key = "create" - locks = "cmd:all()" - -
[docs] def func(self): - """ - Tries to create the Character object. We also put an - attribute on ourselves to remember it. - """ - - # making sure caller is really an account - self.character = None - if utils.inherits_from(self.caller, "evennia.objects.objects.Object"): - # An object of some type is calling. Convert to account. - self.character = self.caller - if hasattr(self.caller, "account"): - self.caller = self.caller.account - - if not self.args: - self.caller.msg("Usage: create <character name>") - return - charname = self.args.strip() - old_char = managers.objects.get_objs_with_key_and_typeclass(charname, CHARACTER_TYPECLASS) - if old_char: - self.caller.msg("Character |c%s|n already exists." % charname) - return - # create the character - - new_character = create_object(CHARACTER_TYPECLASS, key=charname) - if not new_character: - self.caller.msg( - "|rThe Character couldn't be created. This is a bug. Please contact an admin." - ) - return - # make sure to lock the character to only be puppeted by this account - new_character.locks.add( - "puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" - % (new_character.id, self.caller.id) - ) - - # save dbref - avail_chars = self.caller.db._character_dbrefs - if avail_chars: - avail_chars.append(new_character.id) - else: - avail_chars = [new_character.id] - self.caller.db._character_dbrefs = avail_chars - self.caller.msg("|gThe character |c%s|g was successfully created!" % charname)
- - -
[docs]class OOCCmdSetCharGen(default_cmds.AccountCmdSet): - """ - Extends the default OOC cmdset. - """ - -
[docs] def at_cmdset_creation(self): - """Install everything from the default set, then overload""" - self.add(CmdOOCLook()) - self.add(CmdOOCCharacterCreate())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/clothing.html b/docs/0.9.5/_modules/evennia/contrib/clothing.html deleted file mode 100644 index 84bc7aacfa..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/clothing.html +++ /dev/null @@ -1,850 +0,0 @@ - - - - - - - - evennia.contrib.clothing — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.clothing

-"""
-Clothing - Provides a typeclass and commands for wearable clothing,
-which is appended to a character's description when worn.
-
-Evennia contribution - Tim Ashley Jenkins 2017
-
-Clothing items, when worn, are added to the character's description
-in a list. For example, if wearing the following clothing items:
-
-    a thin and delicate necklace
-    a pair of regular ol' shoes
-    one nice hat
-    a very pretty dress
-
-A character's description may look like this:
-
-    Superuser(#1)
-    This is User #1.
-
-    Superuser is wearing one nice hat, a thin and delicate necklace,
-    a very pretty dress and a pair of regular ol' shoes.
-
-Characters can also specify the style of wear for their clothing - I.E.
-to wear a scarf 'tied into a tight knot around the neck' or 'draped
-loosely across the shoulders' - to add an easy avenue of customization.
-For example, after entering:
-
-    wear scarf draped loosely across the shoulders
-
-The garment appears like so in the description:
-
-    Superuser(#1)
-    This is User #1.
-
-    Superuser is wearing a fanciful-looking scarf draped loosely
-    across the shoulders.
-
-Items of clothing can be used to cover other items, and many options
-are provided to define your own clothing types and their limits and
-behaviors. For example, to have undergarments automatically covered
-by outerwear, or to put a limit on the number of each type of item
-that can be worn. The system as-is is fairly freeform - you
-can cover any garment with almost any other, for example - but it
-can easily be made more restrictive, and can even be tied into a
-system for armor or other equipment.
-
-To install, import this module and have your default character
-inherit from ClothedCharacter in your game's characters.py file:
-
-    from evennia.contrib.clothing import ClothedCharacter
-
-    class Character(ClothedCharacter):
-
-And then add ClothedCharacterCmdSet in your character set in your
-game's commands/default_cmdsets.py:
-
-    from evennia.contrib.clothing import ClothedCharacterCmdSet
-
-    class CharacterCmdSet(default_cmds.CharacterCmdSet):
-         ...
-         at_cmdset_creation(self):
-
-             super().at_cmdset_creation()
-             ...
-             self.add(ClothedCharacterCmdSet)    # <-- add this
-
-From here, you can use the default builder commands to create clothes
-with which to test the system:
-
-    @create a pretty shirt : evennia.contrib.clothing.Clothing
-    @set shirt/clothing_type = 'top'
-    wear shirt
-
-"""
-
-from evennia import DefaultObject
-from evennia import DefaultCharacter
-from evennia import default_cmds
-from evennia.commands.default.muxcommand import MuxCommand
-from evennia.utils import list_to_string
-from evennia.utils import evtable
-
-# Options start here.
-# Maximum character length of 'wear style' strings, or None for unlimited.
-WEARSTYLE_MAXLENGTH = 50
-
-# The rest of these options have to do with clothing types. Clothing types are optional,
-# but can be used to give better control over how different items of clothing behave. You
-# can freely add, remove, or change clothing types to suit the needs of your game and use
-# the options below to affect their behavior.
-
-# The order in which clothing types appear on the description. Untyped clothing or clothing
-# with a type not given in this list goes last.
-CLOTHING_TYPE_ORDER = [
-    "hat",
-    "jewelry",
-    "top",
-    "undershirt",
-    "gloves",
-    "fullbody",
-    "bottom",
-    "underpants",
-    "socks",
-    "shoes",
-    "accessory",
-]
-# The maximum number of each type of clothes that can be worn. Unlimited if untyped or not specified.
-CLOTHING_TYPE_LIMIT = {"hat": 1, "gloves": 1, "socks": 1, "shoes": 1}
-# The maximum number of clothing items that can be worn, or None for unlimited.
-CLOTHING_OVERALL_LIMIT = 20
-# What types of clothes will automatically cover what other types of clothes when worn.
-# Note that clothing only gets auto-covered if it's already worn when you put something
-# on that auto-covers it - for example, it's perfectly possible to have your underpants
-# showing if you put them on after your pants!
-CLOTHING_TYPE_AUTOCOVER = {
-    "top": ["undershirt"],
-    "bottom": ["underpants"],
-    "fullbody": ["undershirt", "underpants"],
-    "shoes": ["socks"],
-}
-# Types of clothes that can't be used to cover other clothes.
-CLOTHING_TYPE_CANT_COVER_WITH = ["jewelry"]
-
-
-# HELPER FUNCTIONS START HERE
-
-
-
[docs]def order_clothes_list(clothes_list): - """ - Orders a given clothes list by the order specified in CLOTHING_TYPE_ORDER. - - Args: - clothes_list (list): List of clothing items to put in order - - Returns: - ordered_clothes_list (list): The same list as passed, but re-ordered - according to the hierarchy of clothing types - specified in CLOTHING_TYPE_ORDER. - """ - ordered_clothes_list = clothes_list - # For each type of clothing that exists... - for current_type in reversed(CLOTHING_TYPE_ORDER): - # Check each item in the given clothes list. - for clothes in clothes_list: - # If the item has a clothing type... - if clothes.db.clothing_type: - item_type = clothes.db.clothing_type - # And the clothing type matches the current type... - if item_type == current_type: - # Move it to the front of the list! - ordered_clothes_list.remove(clothes) - ordered_clothes_list.insert(0, clothes) - return ordered_clothes_list
- - -
[docs]def get_worn_clothes(character, exclude_covered=False): - """ - Get a list of clothes worn by a given character. - - Args: - character (obj): The character to get a list of worn clothes from. - - Keyword Args: - exclude_covered (bool): If True, excludes clothes covered by other - clothing from the returned list. - - Returns: - ordered_clothes_list (list): A list of clothing items worn by the - given character, ordered according to - the CLOTHING_TYPE_ORDER option specified - in this module. - """ - clothes_list = [] - for thing in character.contents: - # If uncovered or not excluding covered items - if not thing.db.covered_by or exclude_covered is False: - # If 'worn' is True, add to the list - if thing.db.worn: - clothes_list.append(thing) - # Might as well put them in order here too. - ordered_clothes_list = order_clothes_list(clothes_list) - return ordered_clothes_list
- - -
[docs]def clothing_type_count(clothes_list): - """ - Returns a dictionary of the number of each clothing type - in a given list of clothing objects. - - Args: - clothes_list (list): A list of clothing items from which - to count the number of clothing types - represented among them. - - Returns: - types_count (dict): A dictionary of clothing types represented - in the given list and the number of each - clothing type represented. - """ - types_count = {} - for garment in clothes_list: - if garment.db.clothing_type: - type = garment.db.clothing_type - if type not in list(types_count.keys()): - types_count[type] = 1 - else: - types_count[type] += 1 - return types_count
- - -
[docs]def single_type_count(clothes_list, type): - """ - Returns an integer value of the number of a given type of clothing in a list. - - Args: - clothes_list (list): List of clothing objects to count from - type (str): Clothing type to count - - Returns: - type_count (int): Number of garments of the specified type in the given - list of clothing objects - """ - type_count = 0 - for garment in clothes_list: - if garment.db.clothing_type: - if garment.db.clothing_type == type: - type_count += 1 - return type_count
- - -
[docs]class Clothing(DefaultObject): -
[docs] def wear(self, wearer, wearstyle, quiet=False): - """ - Sets clothes to 'worn' and optionally echoes to the room. - - Args: - wearer (obj): character object wearing this clothing object - wearstyle (True or str): string describing the style of wear or True for none - - Keyword Args: - quiet (bool): If false, does not message the room - - Notes: - Optionally sets db.worn with a 'wearstyle' that appends a short passage to - the end of the name of the clothing to describe how it's worn that shows - up in the wearer's desc - I.E. 'around his neck' or 'tied loosely around - her waist'. If db.worn is set to 'True' then just the name will be shown. - """ - # Set clothing as worn - self.db.worn = wearstyle - # Auto-cover appropirate clothing types, as specified above - to_cover = [] - if self.db.clothing_type and self.db.clothing_type in CLOTHING_TYPE_AUTOCOVER: - for garment in get_worn_clothes(wearer): - if ( - garment.db.clothing_type - and garment.db.clothing_type in CLOTHING_TYPE_AUTOCOVER[self.db.clothing_type] - ): - to_cover.append(garment) - garment.db.covered_by = self - # Return if quiet - if quiet: - return - # Echo a message to the room - message = "%s puts on %s" % (wearer, self.name) - if wearstyle is not True: - message = "%s wears %s %s" % (wearer, self.name, wearstyle) - if to_cover: - message = message + ", covering %s" % list_to_string(to_cover) - wearer.location.msg_contents(message + ".")
- -
[docs] def remove(self, wearer, quiet=False): - """ - Removes worn clothes and optionally echoes to the room. - - Args: - wearer (obj): character object wearing this clothing object - - Keyword Args: - quiet (bool): If false, does not message the room - """ - self.db.worn = False - remove_message = "%s removes %s." % (wearer, self.name) - uncovered_list = [] - - # Check to see if any other clothes are covered by this object. - for thing in wearer.contents: - # If anything is covered by - if thing.db.covered_by == self: - thing.db.covered_by = False - uncovered_list.append(thing.name) - if len(uncovered_list) > 0: - remove_message = "%s removes %s, revealing %s." % ( - wearer, - self.name, - list_to_string(uncovered_list), - ) - # Echo a message to the room - if not quiet: - wearer.location.msg_contents(remove_message)
- -
[docs] def at_get(self, getter): - """ - Makes absolutely sure clothes aren't already set as 'worn' - when they're picked up, in case they've somehow had their - location changed without getting removed. - """ - self.db.worn = False
- - -
[docs]class ClothedCharacter(DefaultCharacter): - """ - Character that displays worn clothing when looked at. You can also - just copy the return_appearance hook defined below to your own game's - character typeclass. - """ - -
[docs] def return_appearance(self, looker): - """ - This formats a description. It is the hook a 'look' command - should call. - - Args: - looker (Object): Object doing the looking. - - Notes: - The name of every clothing item carried and worn by the character - is appended to their description. If the clothing's db.worn value - is set to True, only the name is appended, but if the value is a - string, the string is appended to the end of the name, to allow - characters to specify how clothing is worn. - """ - if not looker: - return "" - # get description, build string - string = "|c%s|n\n" % self.get_display_name(looker) - desc = self.db.desc - worn_string_list = [] - clothes_list = get_worn_clothes(self, exclude_covered=True) - # Append worn, uncovered clothing to the description - for garment in clothes_list: - # If 'worn' is True, just append the name - if garment.db.worn is True: - worn_string_list.append(garment.name) - # Otherwise, append the name and the string value of 'worn' - elif garment.db.worn: - worn_string_list.append("%s %s" % (garment.name, garment.db.worn)) - if desc: - string += "%s" % desc - # Append worn clothes. - if worn_string_list: - string += "|/|/%s is wearing %s." % (self, list_to_string(worn_string_list)) - else: - string += "|/|/%s is not wearing anything." % self - return string
- - -# COMMANDS START HERE - - -
[docs]class CmdWear(MuxCommand): - """ - Puts on an item of clothing you are holding. - - Usage: - wear <obj> [wear style] - - Examples: - wear shirt - wear scarf wrapped loosely about the shoulders - - All the clothes you are wearing are appended to your description. - If you provide a 'wear style' after the command, the message you - provide will be displayed after the clothing's name. - """ - - key = "wear" - help_category = "clothing" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not self.args: - self.caller.msg("Usage: wear <obj> [wear style]") - return - clothing = self.caller.search(self.arglist[0], candidates=self.caller.contents) - wearstyle = True - if not clothing: - self.caller.msg("Thing to wear must be in your inventory.") - return - if not clothing.is_typeclass("evennia.contrib.clothing.Clothing", exact=False): - self.caller.msg("That's not clothes!") - return - - # Enforce overall clothing limit. - if CLOTHING_OVERALL_LIMIT and len(get_worn_clothes(self.caller)) >= CLOTHING_OVERALL_LIMIT: - self.caller.msg("You can't wear any more clothes.") - return - - # Apply individual clothing type limits. - if clothing.db.clothing_type and not clothing.db.worn: - type_count = single_type_count(get_worn_clothes(self.caller), clothing.db.clothing_type) - if clothing.db.clothing_type in list(CLOTHING_TYPE_LIMIT.keys()): - if type_count >= CLOTHING_TYPE_LIMIT[clothing.db.clothing_type]: - self.caller.msg( - "You can't wear any more clothes of the type '%s'." - % clothing.db.clothing_type - ) - return - - if clothing.db.worn and len(self.arglist) == 1: - self.caller.msg("You're already wearing %s!" % clothing.name) - return - if len(self.arglist) > 1: # If wearstyle arguments given - wearstyle_list = self.arglist # Split arguments into a list of words - del wearstyle_list[0] # Leave first argument (the clothing item) out of the wearstyle - wearstring = " ".join( - str(e) for e in wearstyle_list - ) # Join list of args back into one string - if ( - WEARSTYLE_MAXLENGTH and len(wearstring) > WEARSTYLE_MAXLENGTH - ): # If length of wearstyle exceeds limit - self.caller.msg( - "Please keep your wear style message to less than %i characters." - % WEARSTYLE_MAXLENGTH - ) - else: - wearstyle = wearstring - clothing.wear(self.caller, wearstyle)
- - -
[docs]class CmdRemove(MuxCommand): - """ - Takes off an item of clothing. - - Usage: - remove <obj> - - Removes an item of clothing you are wearing. You can't remove - clothes that are covered up by something else - you must take - off the covering item first. - """ - - key = "remove" - help_category = "clothing" - -
[docs] def func(self): - """ - This performs the actual command. - """ - clothing = self.caller.search(self.args, candidates=self.caller.contents) - if not clothing: - self.caller.msg("Thing to remove must be carried or worn.") - return - if not clothing.db.worn: - self.caller.msg("You're not wearing that!") - return - if clothing.db.covered_by: - self.caller.msg("You have to take off %s first." % clothing.db.covered_by.name) - return - clothing.remove(self.caller)
- - -
[docs]class CmdCover(MuxCommand): - """ - Covers a worn item of clothing with another you're holding or wearing. - - Usage: - cover <obj> [with] <obj> - - When you cover a clothing item, it is hidden and no longer appears in - your description until it's uncovered or the item covering it is removed. - You can't remove an item of clothing if it's covered. - """ - - key = "cover" - help_category = "clothing" - -
[docs] def func(self): - """ - This performs the actual command. - """ - - if len(self.arglist) < 2: - self.caller.msg("Usage: cover <worn clothing> [with] <clothing object>") - return - # Get rid of optional 'with' syntax - if self.arglist[1].lower() == "with" and len(self.arglist) > 2: - del self.arglist[1] - to_cover = self.caller.search(self.arglist[0], candidates=self.caller.contents) - cover_with = self.caller.search(self.arglist[1], candidates=self.caller.contents) - if not to_cover or not cover_with: - return - if not to_cover.is_typeclass("evennia.contrib.clothing.Clothing", exact=False): - self.caller.msg("%s isn't clothes!" % to_cover.name) - return - if not cover_with.is_typeclass("evennia.contrib.clothing.Clothing", exact=False): - self.caller.msg("%s isn't clothes!" % cover_with.name) - return - if cover_with.db.clothing_type: - if cover_with.db.clothing_type in CLOTHING_TYPE_CANT_COVER_WITH: - self.caller.msg("You can't cover anything with that!") - return - if not to_cover.db.worn: - self.caller.msg("You're not wearing %s!" % to_cover.name) - return - if to_cover == cover_with: - self.caller.msg("You can't cover an item with itself!") - return - if cover_with.db.covered_by: - self.caller.msg("%s is covered by something else!" % cover_with.name) - return - if to_cover.db.covered_by: - self.caller.msg( - "%s is already covered by %s." % (cover_with.name, to_cover.db.covered_by.name) - ) - return - if not cover_with.db.worn: - cover_with.wear( - self.caller, True - ) # Put on the item to cover with if it's not on already - self.caller.location.msg_contents( - "%s covers %s with %s." % (self.caller, to_cover.name, cover_with.name) - ) - to_cover.db.covered_by = cover_with
- - -
[docs]class CmdUncover(MuxCommand): - """ - Reveals a worn item of clothing that's currently covered up. - - Usage: - uncover <obj> - - When you uncover an item of clothing, you allow it to appear in your - description without having to take off the garment that's currently - covering it. You can't uncover an item of clothing if the item covering - it is also covered by something else. - """ - - key = "uncover" - help_category = "clothing" - -
[docs] def func(self): - """ - This performs the actual command. - """ - - if not self.args: - self.caller.msg("Usage: uncover <worn clothing object>") - return - - to_uncover = self.caller.search(self.args, candidates=self.caller.contents) - if not to_uncover: - return - if not to_uncover.db.worn: - self.caller.msg("You're not wearing %s!" % to_uncover.name) - return - if not to_uncover.db.covered_by: - self.caller.msg("%s isn't covered by anything!" % to_uncover.name) - return - covered_by = to_uncover.db.covered_by - if covered_by.db.covered_by: - self.caller.msg("%s is under too many layers to uncover." % (to_uncover.name)) - return - self.caller.location.msg_contents("%s uncovers %s." % (self.caller, to_uncover.name)) - to_uncover.db.covered_by = None
- - -
[docs]class CmdDrop(MuxCommand): - """ - drop something - - Usage: - drop <obj> - - Lets you drop an object from your inventory into the - location you are currently in. - """ - - key = "drop" - locks = "cmd:all()" - arg_regex = r"\s|$" - -
[docs] def func(self): - """Implement command""" - - caller = self.caller - if not self.args: - caller.msg("Drop what?") - return - - # Because the DROP command by definition looks for items - # in inventory, call the search function using location = caller - obj = caller.search( - self.args, - location=caller, - nofound_string="You aren't carrying %s." % self.args, - multimatch_string="You carry more than one %s:" % self.args, - ) - if not obj: - return - - # This part is new! - # You can't drop clothing items that are covered. - if obj.db.covered_by: - caller.msg("You can't drop that because it's covered by %s." % obj.db.covered_by) - return - # Remove clothes if they're dropped. - if obj.db.worn: - obj.remove(caller, quiet=True) - - obj.move_to(caller.location, quiet=True) - caller.msg("You drop %s." % (obj.name,)) - caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller) - # Call the object script's at_drop() method. - obj.at_drop(caller)
- - -
[docs]class CmdGive(MuxCommand): - """ - give away something to someone - - Usage: - give <inventory obj> = <target> - - Gives an items from your inventory to another character, - placing it in their inventory. - """ - - key = "give" - locks = "cmd:all()" - arg_regex = r"\s|$" - -
[docs] def func(self): - """Implement give""" - - caller = self.caller - if not self.args or not self.rhs: - caller.msg("Usage: give <inventory object> = <target>") - return - to_give = caller.search( - self.lhs, - location=caller, - nofound_string="You aren't carrying %s." % self.lhs, - multimatch_string="You carry more than one %s:" % self.lhs, - ) - target = caller.search(self.rhs) - if not (to_give and target): - return - if target == caller: - caller.msg("You keep %s to yourself." % to_give.key) - return - if not to_give.location == caller: - caller.msg("You are not holding %s." % to_give.key) - return - # This is new! Can't give away something that's worn. - if to_give.db.covered_by: - caller.msg( - "You can't give that away because it's covered by %s." % to_give.db.covered_by - ) - return - # Remove clothes if they're given. - if to_give.db.worn: - to_give.remove(caller) - to_give.move_to(caller.location, quiet=True) - # give object - caller.msg("You give %s to %s." % (to_give.key, target.key)) - to_give.move_to(target, quiet=True) - target.msg("%s gives you %s." % (caller.key, to_give.key)) - # Call the object script's at_give() method. - to_give.at_give(caller, target)
- - -
[docs]class CmdInventory(MuxCommand): - """ - view inventory - - Usage: - inventory - inv - - Shows your inventory. - """ - - # Alternate version of the inventory command which separates - # worn and carried items. - - key = "inventory" - aliases = ["inv", "i"] - locks = "cmd:all()" - arg_regex = r"$" - -
[docs] def func(self): - """check inventory""" - if not self.caller.contents: - self.caller.msg("You are not carrying or wearing anything.") - return - - items = self.caller.contents - - carry_table = evtable.EvTable(border="header") - wear_table = evtable.EvTable(border="header") - for item in items: - if not item.db.worn: - carry_table.add_row("|C%s|n" % item.name, item.db.desc or "") - if carry_table.nrows == 0: - carry_table.add_row("|CNothing.|n", "") - string = "|wYou are carrying:\n%s" % carry_table - for item in items: - if item.db.worn: - wear_table.add_row("|C%s|n" % item.name, item.db.desc or "") - if wear_table.nrows == 0: - wear_table.add_row("|CNothing.|n", "") - string += "|/|wYou are wearing:\n%s" % wear_table - self.caller.msg(string)
- - -
[docs]class ClothedCharacterCmdSet(default_cmds.CharacterCmdSet): - """ - Command set for clothing, including new versions of 'give' and 'drop' - that take worn and covered clothing into account, as well as a new - version of 'inventory' that differentiates between carried and worn - items. - """ - - key = "DefaultCharacter" - -
[docs] def at_cmdset_creation(self): - """ - Populates the cmdset - """ - super().at_cmdset_creation() - # - # any commands you add below will overload the default ones. - # - self.add(CmdWear()) - self.add(CmdRemove()) - self.add(CmdCover()) - self.add(CmdUncover()) - self.add(CmdGive()) - self.add(CmdDrop()) - self.add(CmdInventory())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/custom_gametime.html b/docs/0.9.5/_modules/evennia/contrib/custom_gametime.html deleted file mode 100644 index 7acfd2643f..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/custom_gametime.html +++ /dev/null @@ -1,415 +0,0 @@ - - - - - - - - evennia.contrib.custom_gametime — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.custom_gametime

-"""
-Custom gametime
-
-Contrib - Griatch 2017, vlgeoff 2017
-
-This implements the evennia.utils.gametime module but supporting
-a custom calendar for your game world. It allows for scheduling
-events to happen at given in-game times, taking this custom
-calendar into account.
-
-Usage:
-
-Use as the normal gametime module, that is by importing and using the
-helper functions in this module in your own code. The calendar can be
-customized by adding the `TIME_UNITS` dictionary to your settings
-file. This maps unit names to their length, expressed in the smallest
-unit. Here's the default as an example:
-
-    TIME_UNITS = {
-        "sec": 1,
-        "min": 60,
-        "hr": 60 * 60,
-        "hour": 60 * 60,
-        "day": 60 * 60 * 24,
-        "week": 60 * 60 * 24 * 7,
-        "month": 60 * 60 * 24 * 7 * 4,
-        "yr": 60 * 60 * 24 * 7 * 4 * 12,
-        "year": 60 * 60 * 24 * 7 * 4 * 12, }
-
-When using a custom calendar, these time unit names are used as kwargs to
-the converter functions in this module.
-
-"""
-
-# change these to fit your game world
-
-from django.conf import settings
-from evennia import DefaultScript
-from evennia.utils.create import create_script
-from evennia.utils import gametime
-
-# The game time speedup  / slowdown relative real time
-TIMEFACTOR = settings.TIME_FACTOR
-
-# These are the unit names understood by the scheduler.
-# Each unit must be consistent and expressed in seconds.
-UNITS = getattr(
-    settings,
-    "TIME_UNITS",
-    {
-        # default custom calendar
-        "sec": 1,
-        "min": 60,
-        "hr": 60 * 60,
-        "hour": 60 * 60,
-        "day": 60 * 60 * 24,
-        "week": 60 * 60 * 24 * 7,
-        "month": 60 * 60 * 24 * 7 * 4,
-        "yr": 60 * 60 * 24 * 7 * 4 * 12,
-        "year": 60 * 60 * 24 * 7 * 4 * 12,
-    },
-)
-
-
-
[docs]def time_to_tuple(seconds, *divisors): - """ - Helper function. Creates a tuple of even dividends given a range - of divisors. - - Args: - seconds (int): Number of seconds to format - *divisors (int): a sequence of numbers of integer dividends. The - number of seconds will be integer-divided by the first number in - this sequence, the remainder will be divided with the second and - so on. - Returns: - time (tuple): This tuple has length len(*args)+1, with the - last element being the last remaining seconds not evenly - divided by the supplied dividends. - - """ - results = [] - seconds = int(seconds) - for divisor in divisors: - results.append(seconds // divisor) - seconds %= divisor - results.append(seconds) - return tuple(results)
- - -
[docs]def gametime_to_realtime(format=False, **kwargs): - """ - This method helps to figure out the real-world time it will take until an - in-game time has passed. E.g. if an event should take place a month later - in-game, you will be able to find the number of real-world seconds this - corresponds to (hint: Interval events deal with real life seconds). - - Keyword Args: - format (bool): Formatting the output. - days, month etc (int): These are the names of time units that must - match the `settings.TIME_UNITS` dict keys. - - Returns: - time (float or tuple): The realtime difference or the same - time split up into time units. - - Example: - gametime_to_realtime(days=2) -> number of seconds in real life from - now after which 2 in-game days will have passed. - - """ - # Dynamically creates the list of units based on kwarg names and UNITs list - rtime = 0 - for name, value in kwargs.items(): - # Allow plural names (like mins instead of min) - if name not in UNITS and name.endswith("s"): - name = name[:-1] - - if name not in UNITS: - raise ValueError("the unit {} isn't defined as a valid " "game time unit".format(name)) - rtime += value * UNITS[name] - rtime /= TIMEFACTOR - if format: - return time_to_tuple(rtime, 31536000, 2628000, 604800, 86400, 3600, 60) - return rtime
- - -
[docs]def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0, months=0, yrs=0, format=False): - """ - This method calculates how much in-game time a real-world time - interval would correspond to. This is usually a lot less - interesting than the other way around. - - Keyword Args: - times (int): The various components of the time. - format (bool): Formatting the output. - - Returns: - time (float or tuple): The gametime difference or the same - time split up into time units. - - Example: - realtime_to_gametime(days=2) -> number of game-world seconds - - """ - gtime = TIMEFACTOR * ( - secs - + mins * 60 - + hrs * 3600 - + days * 86400 - + weeks * 604800 - + months * 2628000 - + yrs * 31536000 - ) - if format: - units = sorted(set(UNITS.values()), reverse=True) - # Remove seconds from the tuple - del units[-1] - - return time_to_tuple(gtime, *units) - return gtime
- - -
[docs]def custom_gametime(absolute=False): - """ - Return the custom game time as a tuple of units, as defined in settings. - - Args: - absolute (bool, optional): return the relative or absolute time. - - Returns: - The tuple describing the game time. The length of the tuple - is related to the number of unique units defined in the - settings. By default, the tuple would be (year, month, - week, day, hour, minute, second). - - """ - current = gametime.gametime(absolute=absolute) - units = sorted(set(UNITS.values()), reverse=True) - del units[-1] - return time_to_tuple(current, *units)
- - -
[docs]def real_seconds_until(**kwargs): - """ - Return the real seconds until game time. - - If the game time is 5:00, TIME_FACTOR is set to 2 and you ask - the number of seconds until it's 5:10, then this function should - return 300 (5 minutes). - - Args: - times (str: int): the time units. - - Example: - real_seconds_until(hour=5, min=10, sec=0) - - Returns: - The number of real seconds before the given game time is up. - - """ - current = gametime.gametime(absolute=True) - units = sorted(set(UNITS.values()), reverse=True) - # Remove seconds from the tuple - del units[-1] - divisors = list(time_to_tuple(current, *units)) - - # For each keyword, add in the unit's - units.append(1) - higher_unit = None - for unit, value in kwargs.items(): - # Get the unit's index - if unit not in UNITS: - raise ValueError("unknown unit".format(unit)) - - seconds = UNITS[unit] - index = units.index(seconds) - divisors[index] = value - if higher_unit is None or higher_unit > index: - higher_unit = index - - # Check the projected time - # Note that it can be already passed (the given time may be in the past) - projected = 0 - for i, value in enumerate(divisors): - seconds = units[i] - projected += value * seconds - - if projected <= current: - # The time is in the past, increase the higher unit - if higher_unit: - divisors[higher_unit - 1] += 1 - else: - divisors[0] += 1 - - # Get the projected time again - projected = 0 - for i, value in enumerate(divisors): - seconds = units[i] - projected += value * seconds - - return (projected - current) / TIMEFACTOR
- - -
[docs]def schedule(callback, repeat=False, **kwargs): - """ - Call the callback when the game time is up. - - Args: - callback (function): The callback function that will be called. This - must be a top-level function since the script will be persistent. - repeat (bool, optional): Should the callback be called regularly? - day, month, etc (str: int): The time units to call the callback; should - match the keys of TIME_UNITS. - - Returns: - script (Script): The created script. - - Examples: - schedule(func, min=5, sec=0) # Will call next hour at :05. - schedule(func, hour=2, min=30, sec=0) # Will call the next day at 02:30. - Notes: - This function will setup a script that will be called when the - time corresponds to the game time. If the game is stopped for - more than a few seconds, the callback may be called with a - slight delay. If `repeat` is set to True, the callback will be - called again next time the game time matches the given time. - The time is given in units as keyword arguments. - - """ - seconds = real_seconds_until(**kwargs) - script = create_script( - "evennia.contrib.custom_gametime.GametimeScript", - key="GametimeScript", - desc="A timegame-sensitive script", - interval=seconds, - start_delay=True, - repeats=-1 if repeat else 1, - ) - script.db.callback = callback - script.db.gametime = kwargs - return script
- - -# Scripts dealing in gametime (use `schedule` to create it) - - -
[docs]class GametimeScript(DefaultScript): - - """Gametime-sensitive script.""" - -
[docs] def at_script_creation(self): - """The script is created.""" - self.key = "unknown scr" - self.interval = 100 - self.start_delay = True - self.persistent = True
- -
[docs] def at_repeat(self): - """Call the callback and reset interval.""" - - from evennia.utils.utils import calledby - - callback = self.db.callback - if callback: - callback() - - seconds = real_seconds_until(**self.db.gametime) - self.restart(interval=seconds)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/dice.html b/docs/0.9.5/_modules/evennia/contrib/dice.html deleted file mode 100644 index 7cfb926963..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/dice.html +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - - - evennia.contrib.dice — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.dice

-"""
-Dice - rolls dice for roleplaying, in-game gambling or GM:ing
-
-Evennia contribution - Griatch 2012
-
-
-This module implements a full-fledged dice-roller and a 'dice' command
-to go with it. It uses standard RPG 'd'-syntax (e.g. 2d6 to roll two
-six-sided die) and also supports modifiers such as 3d6 + 5.
-
-One can also specify a standard Python operator in order to specify
-eventual target numbers and get results in a fair and guaranteed
-unbiased way.  For example a GM could (using the dice command) from
-the start define the roll as 2d6 < 8 to show that a roll below 8 is
-required to succeed. The command will normally echo this result to all
-parties (although it also has options for hidden and secret rolls).
-
-
-Installation:
-
-To use in your code, just import the roll_dice function from this module.
-
-To use  the dice/roll command, just import this module in your custom
-cmdset module and add the following line to the end of DefaultCmdSet's
-at_cmdset_creation():
-
-   self.add(dice.CmdDice())
-
-After a reload the dice (or roll) command will be available in-game.
-
-"""
-import re
-from random import randint
-from evennia import default_cmds, CmdSet
-
-
-
[docs]def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False): - """ - This is a standard dice roller. - - Args: - dicenum (int): Number of dice to roll (the result to be added). - dicetype (int): Number of sides of the dice to be rolled. - modifier (tuple): A tuple `(operator, value)`, where operator is - one of `"+"`, `"-"`, `"/"` or `"*"`. The result of the dice - roll(s) will be modified by this value. - conditional (tuple): A tuple `(conditional, value)`, where - conditional is one of `"=="`,`"<"`,`">"`,`">="`,`"<=`" or "`!=`". - This allows the roller to directly return a result depending - on if the conditional was passed or not. - return_tuple (bool): Return a tuple with all individual roll - results or not. - - Returns: - roll_result (int): The result of the roll + modifiers. This is the - default return. - condition_result (bool): A True/False value returned if `conditional` - is set but not `return_tuple`. This effectively hides the result - of the roll. - full_result (tuple): If, return_tuple` is `True`, instead - return a tuple `(result, outcome, diff, rolls)`. Here, - `result` is the normal result of the roll + modifiers. - `outcome` and `diff` are the boolean result of the roll and - absolute difference to the `conditional` input; they will - be will be `None` if `conditional` is not set. `rolls` is - itself a tuple holding all the individual rolls in the case of - multiple die-rolls. - - Raises: - TypeError if non-supported modifiers or conditionals are given. - - Notes: - All input numbers are converted to integers. - - Examples: - print roll_dice(2, 6) # 2d6 - <<< 7 - print roll_dice(1, 100, ('+', 5) # 1d100 + 5 - <<< 34 - print roll_dice(1, 20, conditional=('<', 10) # let'say we roll 3 - <<< True - print roll_dice(3, 10, return_tuple=True) - <<< (11, None, None, (2, 5, 4)) - print roll_dice(2, 20, ('-', 2), conditional=('>=', 10), return_tuple=True) - <<< (8, False, 2, (4, 6)) # roll was 4 + 6 - 2 = 8 - - """ - dicenum = int(dicenum) - dicetype = int(dicetype) - - # roll all dice, remembering each roll - rolls = tuple([randint(1, dicetype) for roll in range(dicenum)]) - result = sum(rolls) - - if modifier: - # make sure to check types well before eval - mod, modvalue = modifier - if mod not in ("+", "-", "*", "/"): - raise TypeError("Non-supported dice modifier: %s" % mod) - modvalue = int(modvalue) # for safety - result = eval("%s %s %s" % (result, mod, modvalue)) - outcome, diff = None, None - if conditional: - # make sure to check types well before eval - cond, condvalue = conditional - if cond not in (">", "<", ">=", "<=", "!=", "=="): - raise TypeError("Non-supported dice result conditional: %s" % conditional) - condvalue = int(condvalue) # for safety - outcome = eval("%s %s %s" % (result, cond, condvalue)) # True/False - diff = abs(result - condvalue) - if return_tuple: - return result, outcome, diff, rolls - else: - if conditional: - return outcome - else: - return result
- - -RE_PARTS = re.compile(r"(d|\+|-|/|\*|<|>|<=|>=|!=|==)") -RE_MOD = re.compile(r"(\+|-|/|\*)") -RE_COND = re.compile(r"(<|>|<=|>=|!=|==)") - - -
[docs]class CmdDice(default_cmds.MuxCommand): - """ - roll dice - - Usage: - dice[/switch] <nr>d<sides> [modifier] [success condition] - - Switch: - hidden - tell the room the roll is being done, but don't show the result - secret - don't inform the room about neither roll nor result - - Examples: - dice 3d6 + 4 - dice 1d100 - 2 < 50 - - This will roll the given number of dice with given sides and modifiers. - So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result, - then add 3 to the total'. - Accepted modifiers are +, -, * and /. - A success condition is given as normal Python conditionals - (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed - only if the final result is above 8. If a success condition is given, the - outcome (pass/fail) will be echoed along with how much it succeeded/failed - with. The hidden/secret switches will hide all or parts of the roll from - everyone but the person rolling. - """ - - key = "dice" - aliases = ["roll", "@dice"] - locks = "cmd:all()" - -
[docs] def func(self): - """Mostly parsing for calling the dice roller function""" - - if not self.args: - self.caller.msg("Usage: @dice <nr>d<sides> [modifier] [conditional]") - return - argstring = "".join(str(arg) for arg in self.args) - - parts = [part for part in RE_PARTS.split(self.args) if part] - len_parts = len(parts) - modifier = None - conditional = None - - if len_parts < 3 or parts[1] != "d": - self.caller.msg( - "You must specify the die roll(s) as <nr>d<sides>." - " For example, 2d6 means rolling a 6-sided die 2 times." - ) - return - - # Limit the number of dice and sides a character can roll to prevent server slow down and crashes - ndicelimit = 10000 # Maximum number of dice - nsidelimit = 10000 # Maximum number of sides - if int(parts[0]) > ndicelimit or int(parts[2]) > nsidelimit: - self.caller.msg("The maximum roll allowed is %sd%s." % (ndicelimit, nsidelimit)) - return - - ndice, nsides = parts[0], parts[2] - if len_parts == 3: - # just something like 1d6 - pass - elif len_parts == 5: - # either e.g. 1d6 + 3 or something like 1d6 > 3 - if parts[3] in ("+", "-", "*", "/"): - modifier = (parts[3], parts[4]) - else: # assume it is a conditional - conditional = (parts[3], parts[4]) - elif len_parts == 7: - # the whole sequence, e.g. 1d6 + 3 > 5 - modifier = (parts[3], parts[4]) - conditional = (parts[5], parts[6]) - else: - # error - self.caller.msg("You must specify a valid die roll") - return - # do the roll - try: - result, outcome, diff, rolls = roll_dice( - ndice, nsides, modifier=modifier, conditional=conditional, return_tuple=True - ) - except ValueError: - self.caller.msg( - "You need to enter valid integer numbers, modifiers and operators." - " |w%s|n was not understood." % self.args - ) - return - # format output - if len(rolls) > 1: - rolls = ", ".join(str(roll) for roll in rolls[:-1]) + " and " + str(rolls[-1]) - else: - rolls = rolls[0] - if outcome is None: - outcomestring = "" - elif outcome: - outcomestring = " This is a |gsuccess|n (by %s)." % diff - else: - outcomestring = " This is a |rfailure|n (by %s)." % diff - yourollstring = "You roll %s%s." - roomrollstring = "%s rolls %s%s." - resultstring = " Roll(s): %s. Total result is |w%s|n." - - if "secret" in self.switches: - # don't echo to the room at all - string = yourollstring % (argstring, " (secret, not echoed)") - string += "\n" + resultstring % (rolls, result) - string += outcomestring + " (not echoed)" - self.caller.msg(string) - elif "hidden" in self.switches: - # announce the roll to the room, result only to caller - string = yourollstring % (argstring, " (hidden)") - self.caller.msg(string) - string = roomrollstring % (self.caller.key, argstring, " (hidden)") - self.caller.location.msg_contents(string, exclude=self.caller) - # handle result - string = resultstring % (rolls, result) - string += outcomestring + " (not echoed)" - self.caller.msg(string) - else: - # normal roll - string = yourollstring % (argstring, "") - self.caller.msg(string) - string = roomrollstring % (self.caller.key, argstring, "") - self.caller.location.msg_contents(string, exclude=self.caller) - string = resultstring % (rolls, result) - string += outcomestring - self.caller.location.msg_contents(string)
- - -
[docs]class DiceCmdSet(CmdSet): - """ - a small cmdset for testing purposes. - Add with @py self.cmdset.add("contrib.dice.DiceCmdSet") - """ - -
[docs] def at_cmdset_creation(self): - """Called when set is created""" - self.add(CmdDice())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/email_login.html b/docs/0.9.5/_modules/evennia/contrib/email_login.html deleted file mode 100644 index 1deff4616f..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/email_login.html +++ /dev/null @@ -1,469 +0,0 @@ - - - - - - - - evennia.contrib.email_login — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.email_login

-"""
-Email-based login system
-
-Evennia contrib - Griatch 2012
-
-
-This is a variant of the login system that requires an email-address
-instead of a username to login.
-
-This used to be the default Evennia login before replacing it with a
-more standard username + password system (having to supply an email
-for some reason caused a lot of confusion when people wanted to expand
-on it. The email is not strictly needed internally, nor is any
-confirmation email sent out anyway).
-
-
-Installation is simple:
-
-To your settings file, add/edit the line:
-
-```python
-CMDSET_UNLOGGEDIN = "contrib.email_login.UnloggedinCmdSet"
-```
-
-That's it. Reload the server and try to log in to see it.
-
-The initial login "graphic" will still not mention email addresses
-after this change. The login splashscreen is taken from strings in
-the module given by settings.CONNECTION_SCREEN_MODULE.
-
-"""
-import re
-from django.conf import settings
-from evennia.accounts.models import AccountDB
-from evennia.objects.models import ObjectDB
-from evennia.server.models import ServerConfig
-
-from evennia.commands.cmdset import CmdSet
-from evennia.utils import logger, utils, ansi
-from evennia.commands.default.muxcommand import MuxCommand
-from evennia.commands.cmdhandler import CMD_LOGINSTART
-from evennia.commands.default import (
-    unloggedin as default_unloggedin,
-)  # Used in CmdUnconnectedCreate
-
-# limit symbol import for API
-__all__ = (
-    "CmdUnconnectedConnect",
-    "CmdUnconnectedCreate",
-    "CmdUnconnectedQuit",
-    "CmdUnconnectedLook",
-    "CmdUnconnectedHelp",
-)
-
-MULTISESSION_MODE = settings.MULTISESSION_MODE
-CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
-CONNECTION_SCREEN = ""
-try:
-    CONNECTION_SCREEN = ansi.parse_ansi(utils.random_string_from_module(CONNECTION_SCREEN_MODULE))
-except Exception:
-    # malformed connection screen or no screen given
-    pass
-if not CONNECTION_SCREEN:
-    CONNECTION_SCREEN = (
-        "\nEvennia: Error in CONNECTION_SCREEN MODULE"
-        " (randomly picked connection screen variable is not a string). \nEnter 'help' for aid."
-    )
-
-
-
[docs]class CmdUnconnectedConnect(MuxCommand): - """ - Connect to the game. - - Usage (at login screen): - connect <email> <password> - - Use the create command to first create an account before logging in. - """ - - key = "connect" - aliases = ["conn", "con", "co"] - locks = "cmd:all()" # not really needed - -
[docs] def func(self): - """ - Uses the Django admin api. Note that unlogged-in commands - have a unique position in that their `func()` receives - a session object instead of a `source_object` like all - other types of logged-in commands (this is because - there is no object yet before the account has logged in) - """ - - session = self.caller - arglist = self.arglist - - if not arglist or len(arglist) < 2: - session.msg("\n\r Usage (without <>): connect <email> <password>") - return - email = arglist[0] - password = arglist[1] - - # Match an email address to an account. - account = AccountDB.objects.get_account_from_email(email) - # No accountname match - if not account: - string = "The email '%s' does not match any accounts." % email - string += "\n\r\n\rIf you are new you should first create a new account " - string += "using the 'create' command." - session.msg(string) - return - # We have at least one result, so we can check the password. - if not account[0].check_password(password): - session.msg("Incorrect password.") - return - - # Check IP and/or name bans - bans = ServerConfig.objects.conf("server_bans") - if bans and ( - any(tup[0] == account.name for tup in bans) - or any(tup[2].match(session.address[0]) for tup in bans if tup[2]) - ): - # this is a banned IP or name! - string = "|rYou have been banned and cannot continue from here." - string += "\nIf you feel this ban is in error, please email an admin.|x" - session.msg(string) - session.execute_cmd("quit") - return - - # actually do the login. This will call all hooks. - session.sessionhandler.login(session, account)
- - -
[docs]class CmdUnconnectedCreate(MuxCommand): - """ - Create a new account. - - Usage (at login screen): - create \"accountname\" <email> <password> - - This creates a new account account. - - """ - - key = "create" - aliases = ["cre", "cr"] - locks = "cmd:all()" - -
[docs] def parse(self): - """ - The parser must handle the multiple-word account - name enclosed in quotes: - connect "Long name with many words" my@myserv.com mypassw - """ - super().parse() - - self.accountinfo = [] - if len(self.arglist) < 3: - return - if len(self.arglist) > 3: - # this means we have a multi_word accountname. pop from the back. - password = self.arglist.pop() - email = self.arglist.pop() - # what remains is the accountname. - accountname = " ".join(self.arglist) - else: - accountname, email, password = self.arglist - - accountname = accountname.replace('"', "") # remove " - accountname = accountname.replace("'", "") - self.accountinfo = (accountname, email, password)
- -
[docs] def func(self): - """Do checks and create account""" - - session = self.caller - try: - accountname, email, password = self.accountinfo - except ValueError: - string = '\n\r Usage (without <>): create "<accountname>" <email> <password>' - session.msg(string) - return - if not email or not password: - session.msg("\n\r You have to supply an e-mail address followed by a password.") - return - if not utils.validate_email_address(email): - # check so the email at least looks ok. - session.msg("'%s' is not a valid e-mail address." % email) - return - # sanity checks - if not re.findall(r"^[\w. @+\-']+$", accountname) or not (0 < len(accountname) <= 30): - # this echoes the restrictions made by django's auth - # module (except not allowing spaces, for convenience of - # logging in). - string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only." - session.msg(string) - return - # strip excessive spaces in accountname - accountname = re.sub(r"\s+", " ", accountname).strip() - if AccountDB.objects.filter(username__iexact=accountname): - # account already exists (we also ignore capitalization here) - session.msg("Sorry, there is already an account with the name '%s'." % accountname) - return - if AccountDB.objects.get_account_from_email(email): - # email already set on an account - session.msg("Sorry, there is already an account with that email address.") - return - # Reserve accountnames found in GUEST_LIST - if settings.GUEST_LIST and accountname.lower() in ( - guest.lower() for guest in settings.GUEST_LIST - ): - string = "\n\r That name is reserved. Please choose another Accountname." - session.msg(string) - return - if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)): - string = ( - "\n\r Password should be longer than 3 characters. Letters, spaces, digits and @/./+/-/_/' only." - "\nFor best security, make it longer than 8 characters. You can also use a phrase of" - "\nmany words if you enclose the password in double quotes." - ) - session.msg(string) - return - - # Check IP and/or name bans - bans = ServerConfig.objects.conf("server_bans") - if bans and ( - any(tup[0] == accountname.lower() for tup in bans) - or any(tup[2].match(session.address) for tup in bans if tup[2]) - ): - # this is a banned IP or name! - string = ( - "|rYou have been banned and cannot continue from here." - "\nIf you feel this ban is in error, please email an admin.|x" - ) - session.msg(string) - session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") - return - - # everything's ok. Create the new player account. - try: - permissions = settings.PERMISSION_ACCOUNT_DEFAULT - typeclass = settings.BASE_CHARACTER_TYPECLASS - new_account = default_unloggedin._create_account( - session, accountname, password, permissions, email=email - ) - if new_account: - if MULTISESSION_MODE < 2: - default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - default_unloggedin._create_character( - session, new_account, typeclass, default_home, permissions - ) - # tell the caller everything went well. - string = "A new account '%s' was created. Welcome!" - if " " in accountname: - string += ( - "\n\nYou can now log in with the command 'connect \"%s\" <your password>'." - ) - else: - string += "\n\nYou can now log with the command 'connect %s <your password>'." - session.msg(string % (accountname, email)) - - except Exception: - # We are in the middle between logged in and -not, so we have - # to handle tracebacks ourselves at this point. If we don't, - # we won't see any errors at all. - session.msg("An error occurred. Please e-mail an admin if the problem persists.") - logger.log_trace() - raise
- - -
[docs]class CmdUnconnectedQuit(MuxCommand): - """ - We maintain a different version of the `quit` command - here for unconnected accounts for the sake of simplicity. The logged in - version is a bit more complicated. - """ - - key = "quit" - aliases = ["q", "qu"] - locks = "cmd:all()" - -
[docs] def func(self): - """Simply close the connection.""" - session = self.caller - session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
- - -
[docs]class CmdUnconnectedLook(MuxCommand): - """ - This is an unconnected version of the `look` command for simplicity. - - This is called by the server and kicks everything in gear. - All it does is display the connect screen. - """ - - key = CMD_LOGINSTART - aliases = ["look", "l"] - locks = "cmd:all()" - -
[docs] def func(self): - """Show the connect screen.""" - self.caller.msg(CONNECTION_SCREEN)
- - -
[docs]class CmdUnconnectedHelp(MuxCommand): - """ - This is an unconnected version of the help command, - for simplicity. It shows a pane of info. - """ - - key = "help" - aliases = ["h", "?"] - locks = "cmd:all()" - -
[docs] def func(self): - """Shows help""" - - string = """ -You are not yet logged into the game. Commands available at this point: - |wcreate, connect, look, help, quit|n - -To login to the system, you need to do one of the following: - -|w1)|n If you have no previous account, you need to use the 'create' - command like this: - - |wcreate "Anna the Barbarian" anna@myemail.com c67jHL8p|n - - It's always a good idea (not only here, but everywhere on the net) - to not use a regular word for your password. Make it longer than - 3 characters (ideally 6 or more) and mix numbers and capitalization - into it. - -|w2)|n If you have an account already, either because you just created - one in |w1)|n above or you are returning, use the 'connect' command: - - |wconnect anna@myemail.com c67jHL8p|n - - This should log you in. Run |whelp|n again once you're logged in - to get more aid. Hope you enjoy your stay! - -You can use the |wlook|n command if you want to see the connect screen again. -""" - self.caller.msg(string)
- - -# command set for the mux-like login - - -class UnloggedinCmdSet(CmdSet): - """ - Sets up the unlogged cmdset. - """ - - key = "Unloggedin" - priority = 0 - - def at_cmdset_creation(self): - """Populate the cmdset""" - self.add(CmdUnconnectedConnect()) - self.add(CmdUnconnectedCreate()) - self.add(CmdUnconnectedQuit()) - self.add(CmdUnconnectedLook()) - self.add(CmdUnconnectedHelp()) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/extended_room.html b/docs/0.9.5/_modules/evennia/contrib/extended_room.html deleted file mode 100644 index 7d068f7ad1..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/extended_room.html +++ /dev/null @@ -1,699 +0,0 @@ - - - - - - - - evennia.contrib.extended_room — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.extended_room

-"""
-Extended Room
-
-Evennia Contribution - Griatch 2012, vincent-lg 2019
-
-This is an extended Room typeclass for Evennia. It is supported
-by an extended `Look` command and an extended `desc` command, also
-in this module.
-
-
-Features:
-
-1) Time-changing description slots
-
-This allows to change the full description text the room shows
-depending on larger time variations. Four seasons (spring, summer,
-autumn and winter) are used by default. The season is calculated
-on-demand (no Script or timer needed) and updates the full text block.
-
-There is also a general description which is used as fallback if
-one or more of the seasonal descriptions are not set when their
-time comes.
-
-An updated `desc` command allows for setting seasonal descriptions.
-
-The room uses the `evennia.utils.gametime.GameTime` global script. This is
-started by default, but if you have deactivated it, you need to
-supply your own time keeping mechanism.
-
-
-2) In-description changing tags
-
-Within each seasonal (or general) description text, you can also embed
-time-of-day dependent sections. Text inside such a tag will only show
-during that particular time of day. The tags looks like `<timeslot> ...
-</timeslot>`. By default there are four timeslots per day - morning,
-afternoon, evening and night.
-
-
-3) Details
-
-The Extended Room can be "detailed" with special keywords. This makes
-use of a special `Look` command. Details are "virtual" targets to look
-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 <target>` command it
-will also look through the available details at the current location
-if applicable. The `@detail` command is used to change details.
-
-
-4) Extra commands
-
-  CmdExtendedRoomLook - look command supporting room details
-  CmdExtendedRoomDesc - desc command allowing to add seasonal descs,
-  CmdExtendedRoomDetail - command allowing to manipulate details in this room
-                    as well as listing them
-  CmdExtendedRoomGameTime - A simple `time` command, displaying the current
-                    time and season.
-
-
-Installation/testing:
-
-Adding the `ExtendedRoomCmdset` to the default character cmdset will add all
-new commands for use.
-
-In more detail, in mygame/commands/default_cmdsets.py:
-
-```
-...
-from evennia.contrib import extended_room   # <-new
-
-class CharacterCmdset(default_cmds.Character_CmdSet):
-    ...
-    def at_cmdset_creation(self):
-        ...
-        self.add(extended_room.ExtendedRoomCmdSet)  # <-new
-
-```
-
-Then reload to make the bew commands available. Note that they only work
-on rooms with the typeclass `ExtendedRoom`. Create new rooms with the right
-typeclass or use the `typeclass` command to swap existing rooms.
-
-"""
-
-
-import datetime
-import re
-from django.conf import settings
-from evennia import DefaultRoom
-from evennia import gametime
-from evennia import default_cmds
-from evennia import utils
-from evennia import CmdSet
-
-# error return function, needed by Extended Look command
-_AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
-
-# regexes for in-desc replacements
-RE_MORNING = re.compile(r"<morning>(.*?)</morning>", re.IGNORECASE)
-RE_AFTERNOON = re.compile(r"<afternoon>(.*?)</afternoon>", re.IGNORECASE)
-RE_EVENING = re.compile(r"<evening>(.*?)</evening>", re.IGNORECASE)
-RE_NIGHT = re.compile(r"<night>(.*?)</night>", re.IGNORECASE)
-# this map is just a faster way to select the right regexes (the first
-# regex in each tuple will be parsed, the following will always be weeded out)
-REGEXMAP = {
-    "morning": (RE_MORNING, RE_AFTERNOON, RE_EVENING, RE_NIGHT),
-    "afternoon": (RE_AFTERNOON, RE_MORNING, RE_EVENING, RE_NIGHT),
-    "evening": (RE_EVENING, RE_MORNING, RE_AFTERNOON, RE_NIGHT),
-    "night": (RE_NIGHT, RE_MORNING, RE_AFTERNOON, RE_EVENING),
-}
-
-# set up the seasons and time slots. This assumes gametime started at the
-# beginning of the year (so month 1 is equivalent to January), and that
-# one CAN divide the game's year into four seasons in the first place ...
-MONTHS_PER_YEAR = 12
-SEASONAL_BOUNDARIES = (3 / 12.0, 6 / 12.0, 9 / 12.0)
-HOURS_PER_DAY = 24
-DAY_BOUNDARIES = (0, 6 / 24.0, 12 / 24.0, 18 / 24.0)
-
-
-# implements the Extended Room
-
-
-
[docs]class ExtendedRoom(DefaultRoom): - """ - This room implements a more advanced `look` functionality depending on - time. It also allows for "details", together with a slightly modified - look command. - """ - -
[docs] def at_object_creation(self): - """Called when room is first created only.""" - self.db.spring_desc = "" - self.db.summer_desc = "" - self.db.autumn_desc = "" - self.db.winter_desc = "" - # the general desc is used as a fallback if a seasonal one is not set - self.db.general_desc = "" - # will be set dynamically. Can contain raw timeslot codes - self.db.raw_desc = "" - # this will be set dynamically at first look. Parsed for timeslot codes - self.db.desc = "" - # these will be filled later - self.ndb.last_season = None - self.ndb.last_timeslot = None - # detail storage - self.db.details = {}
- -
[docs] def get_time_and_season(self): - """ - Calculate the current time and season ids. - """ - # get the current time as parts of year and parts of day. - # we assume a standard calendar here and use 24h format. - timestamp = gametime.gametime(absolute=True) - # note that fromtimestamp includes the effects of server time zone! - datestamp = datetime.datetime.fromtimestamp(timestamp) - season = float(datestamp.month) / MONTHS_PER_YEAR - timeslot = float(datestamp.hour) / HOURS_PER_DAY - - # figure out which slots these represent - if SEASONAL_BOUNDARIES[0] <= season < SEASONAL_BOUNDARIES[1]: - curr_season = "spring" - elif SEASONAL_BOUNDARIES[1] <= season < SEASONAL_BOUNDARIES[2]: - curr_season = "summer" - elif SEASONAL_BOUNDARIES[2] <= season < 1.0 + SEASONAL_BOUNDARIES[0]: - curr_season = "autumn" - else: - curr_season = "winter" - - if DAY_BOUNDARIES[0] <= timeslot < DAY_BOUNDARIES[1]: - curr_timeslot = "night" - elif DAY_BOUNDARIES[1] <= timeslot < DAY_BOUNDARIES[2]: - curr_timeslot = "morning" - elif DAY_BOUNDARIES[2] <= timeslot < DAY_BOUNDARIES[3]: - curr_timeslot = "afternoon" - else: - curr_timeslot = "evening" - - return curr_season, curr_timeslot
- -
[docs] def replace_timeslots(self, raw_desc, curr_time): - """ - Filter so that only time markers `<timeslot>...</timeslot>` of - the correct timeslot remains in the description. - - Args: - raw_desc (str): The unmodified description. - curr_time (str): A timeslot identifier. - - Returns: - description (str): A possibly moified description. - - """ - if raw_desc: - regextuple = REGEXMAP[curr_time] - raw_desc = regextuple[0].sub(r"\1", raw_desc) - raw_desc = regextuple[1].sub("", raw_desc) - raw_desc = regextuple[2].sub("", raw_desc) - return regextuple[3].sub("", raw_desc) - return raw_desc
- -
[docs] def return_detail(self, key): - """ - This will attempt to match a "detail" to look for in the room. - - Args: - key (str): A detail identifier. - - Returns: - detail (str or None): A detail matching the given key. - - Notes: - A detail is a way to offer more things to look at in a room - without having to add new objects. For this to work, we - require a custom `look` command that allows for `look - <detail>` - the look command should defer to this method on - the current location (if it exists) before giving up on - finding the target. - - Details are not season-sensitive, but are parsed for timeslot - markers. - """ - try: - detail = self.db.details.get(key.lower(), None) - except AttributeError: - # this happens if no attribute details is set at all - return None - if detail: - season, timeslot = self.get_time_and_season() - detail = self.replace_timeslots(detail, timeslot) - return detail - return None
- -
[docs] 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}
- -
[docs] 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()]
- -
[docs] def return_appearance(self, looker, **kwargs): - """ - This is called when e.g. the look command wants to retrieve - the description of this object. - - Args: - looker (Object): The object looking at us. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - description (str): Our description. - - """ - # ensures that our description is current based on time/season - self.update_current_description() - # run the normal return_appearance method, now that desc is updated. - return super(ExtendedRoom, self).return_appearance(looker, **kwargs)
- -
[docs] def update_current_description(self): - """ - This will update the description of the room if the time or season - has changed since last checked. - """ - update = False - # get current time and season - curr_season, curr_timeslot = self.get_time_and_season() - # compare with previously stored slots - last_season = self.ndb.last_season - last_timeslot = self.ndb.last_timeslot - if curr_season != last_season: - # season changed. Load new desc, or a fallback. - new_raw_desc = self.attributes.get("%s_desc" % curr_season) - if new_raw_desc: - raw_desc = new_raw_desc - else: - # no seasonal desc set. Use fallback - raw_desc = self.db.general_desc or self.db.desc - self.db.raw_desc = raw_desc - self.ndb.last_season = curr_season - update = True - if curr_timeslot != last_timeslot: - # timeslot changed. Set update flag. - self.ndb.last_timeslot = curr_timeslot - update = True - if update: - # if anything changed we have to re-parse - # the raw_desc for time markers - # and re-save the description again. - self.db.desc = self.replace_timeslots(self.db.raw_desc, curr_timeslot)
- - -# Custom Look command supporting Room details. Add this to -# the Default cmdset to use. - - -
[docs]class CmdExtendedRoomLook(default_cmds.CmdLook): - """ - look - - Usage: - look - look <obj> - look <room detail> - look *<account> - - Observes your location, details at your location or objects in your vicinity. - """ - -
[docs] def func(self): - """ - Handle the looking - add fallback to details. - """ - caller = self.caller - args = self.args - if args: - looking_at_obj = caller.search( - args, - candidates=caller.location.contents + caller.contents, - use_nicks=True, - quiet=True, - ) - if not looking_at_obj: - # no object found. Check if there is a matching - # detail at location. - location = caller.location - if ( - location - and hasattr(location, "return_detail") - and callable(location.return_detail) - ): - detail = location.return_detail(args) - if detail: - # we found a detail instead. Show that. - caller.msg(detail) - return - # no detail found. Trigger delayed error messages - _AT_SEARCH_RESULT(looking_at_obj, caller, args, quiet=False) - return - else: - # we need to extract the match manually. - looking_at_obj = utils.make_iter(looking_at_obj)[0] - else: - looking_at_obj = caller.location - if not looking_at_obj: - caller.msg("You have no location to look at!") - return - - if not hasattr(looking_at_obj, "return_appearance"): - # this is likely due to us having an account instead - looking_at_obj = looking_at_obj.character - if not looking_at_obj.access(caller, "view"): - caller.msg("Could not find '%s'." % args) - return - # get object's appearance - caller.msg(looking_at_obj.return_appearance(caller)) - # the object's at_desc() method. - looking_at_obj.at_desc(looker=caller)
- - -# Custom build commands for setting seasonal descriptions -# and detailing extended rooms. - - -
[docs]class CmdExtendedRoomDesc(default_cmds.CmdDesc): - """ - `desc` - describe an object or room. - - Usage: - desc[/switch] [<obj> =] <description> - - Switches for `desc`: - spring - set description for <season> in current room. - summer - autumn - winter - - Sets the "desc" attribute on an object. If an object is not given, - describe the current room. - - You can also embed special time markers in your room description, like this: - - ``` - <night>In the darkness, the forest looks foreboding.</night>. - ``` - - 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 seasons and time-of-day slots only work on rooms in this - version of the `desc` command. - - """ - - aliases = ["describe"] - switch_options = () # Inherits from default_cmds.CmdDesc, but unused here - -
[docs] def reset_times(self, obj): - """By deleteting the caches we force a re-load.""" - obj.ndb.last_season = None - obj.ndb.last_timeslot = None
- -
[docs] def func(self): - """Define extended command""" - caller = self.caller - location = caller.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 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)
- - -
[docs]class CmdExtendedRoomDetail(default_cmds.MuxCommand): - - """ - sets a detail on a room - - Usage: - @detail[/del] <key> [= <description>] - @detail <key>;<alias>;... = 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" - -
[docs] 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 - - -
[docs]class CmdExtendedRoomGameTime(default_cmds.MuxCommand): - """ - Check the game time - - Usage: - time - - Shows the current in-game time and season. - """ - - key = "time" - locks = "cmd:all()" - help_category = "General" - -
[docs] def func(self): - """Reads time info from current room""" - location = self.caller.location - if not location or not hasattr(location, "get_time_and_season"): - self.caller.msg("No location available - you are outside time.") - else: - season, timeslot = location.get_time_and_season() - prep = "a" - if season == "autumn": - prep = "an" - self.caller.msg("It's %s %s day, in the %s." % (prep, season, timeslot))
- - -# CmdSet for easily install all commands - - -
[docs]class ExtendedRoomCmdSet(CmdSet): - """ - Groups the extended-room commands. - - """ - -
[docs] def at_cmdset_creation(self): - self.add(CmdExtendedRoomLook) - self.add(CmdExtendedRoomDesc) - self.add(CmdExtendedRoomDetail) - self.add(CmdExtendedRoomGameTime)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/fieldfill.html b/docs/0.9.5/_modules/evennia/contrib/fieldfill.html deleted file mode 100644 index 764107ece1..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/fieldfill.html +++ /dev/null @@ -1,822 +0,0 @@ - - - - - - - - evennia.contrib.fieldfill — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.fieldfill

-"""
-Easy fillable form
-
-Contrib - Tim Ashley Jenkins 2018
-
-This module contains a function that calls an easily customizable EvMenu - this
-menu presents the player with a fillable form, with fields that can be filled
-out in any order. Each field's value can be verified, with the function
-allowing easy checks for text and integer input, minimum and maximum values /
-character lengths, or can even be verified by a custom function. Once the form
-is submitted, the form's data is submitted as a dictionary to any callable of
-your choice.
-
-The function that initializes the fillable form menu is fairly simple, and
-includes the caller, the template for the form, and the callback(caller, result) to which the form
-data will be sent to upon submission.
-
-    init_fill_field(formtemplate, caller, formcallback)
-
-Form templates are defined as a list of dictionaries - each dictionary
-represents a field in the form, and contains the data for the field's name and
-behavior. For example, this basic form template will allow a player to fill out
-a brief character profile:
-
-    PROFILE_TEMPLATE = [
-    {"fieldname":"Name", "fieldtype":"text"},
-    {"fieldname":"Age", "fieldtype":"number"},
-    {"fieldname":"History", "fieldtype":"text"},
-    ]
-
-This will present the player with an EvMenu showing this basic form:
-
-      Name:
-       Age:
-   History:
-
-While in this menu, the player can assign a new value to any field with the
-syntax <field> = <new value>, like so:
-
-    > name = Ashley
-    Field 'Name' set to: Ashley
-
-Typing 'look' by itself will show the form and its current values.
-
-    > look
-
-      Name: Ashley
-       Age:
-   History:
-
-Number fields require an integer input, and will reject any text that can't
-be converted into an integer.
-
-    > age = youthful
-    Field 'Age' requires a number.
-    > age = 31
-    Field 'Age' set to: 31
-
-Form data is presented as an EvTable, so text of any length will wrap cleanly.
-
-    > history = EVERY MORNING I WAKE UP AND OPEN PALM SLAM[...]
-    Field 'History' set to: EVERY MORNING I WAKE UP AND[...]
-    > look
-
-      Name: Ashley
-       Age: 31
-   History: EVERY MORNING I WAKE UP AND OPEN PALM SLAM A VHS INTO THE SLOT.
-            IT'S CHRONICLES OF RIDDICK AND RIGHT THEN AND THERE I START DOING
-            THE MOVES ALONGSIDE WITH THE MAIN CHARACTER, RIDDICK. I DO EVERY
-            MOVE AND I DO EVERY MOVE HARD.
-
-When the player types 'submit' (or your specified submit command), the menu
-quits and the form's data is passed to your specified function as a dictionary,
-like so:
-
-    formdata = {"Name":"Ashley", "Age":31, "History":"EVERY MORNING I[...]"}
-
-You can do whatever you like with this data in your function - forms can be used
-to set data on a character, to help builders create objects, or for players to
-craft items or perform other complicated actions with many variables involved.
-
-The data that your form will accept can also be specified in your form template -
-let's say, for example, that you won't accept ages under 18 or over 100. You can
-do this by specifying "min" and "max" values in your field's dictionary:
-
-    PROFILE_TEMPLATE = [
-    {"fieldname":"Name", "fieldtype":"text"},
-    {"fieldname":"Age", "fieldtype":"number", "min":18, "max":100},
-    {"fieldname":"History", "fieldtype":"text"}
-    ]
-
-Now if the player tries to enter a value out of range, the form will not acept the
-given value.
-
-    > age = 10
-    Field 'Age' reqiures a minimum value of 18.
-    > age = 900
-    Field 'Age' has a maximum value of 100.
-
-Setting 'min' and 'max' for a text field will instead act as a minimum or
-maximum character length for the player's input.
-
-There are lots of ways to present the form to the player - fields can have default
-values or show a custom message in place of a blank value, and player input can be
-verified by a custom function, allowing for a great deal of flexibility. There
-is also an option for 'bool' fields, which accept only a True / False input and
-can be customized to represent the choice to the player however you like (E.G.
-Yes/No, On/Off, Enabled/Disabled, etc.)
-
-This module contains a simple example form that demonstrates all of the included
-functionality - a command that allows a player to compose a message to another
-online character and have it send after a custom delay. You can test it by
-importing this module in your game's default_cmdsets.py module and adding
-CmdTestMenu to your default character's command set.
-
-FIELD TEMPLATE KEYS:
-Required:
-    fieldname (str): Name of the field, as presented to the player.
-    fieldtype (str): Type of value required: 'text', 'number', or 'bool'.
-
-Optional:
-    max (int): Maximum character length (if text) or value (if number).
-    min (int): Minimum charater length (if text) or value (if number).
-    truestr (str): String for a 'True' value in a bool field.
-        (E.G. 'On', 'Enabled', 'Yes')
-    falsestr (str): String for a 'False' value in a bool field.
-        (E.G. 'Off', 'Disabled', 'No')
-    default (str): Initial value (blank if not given).
-    blankmsg (str): Message to show in place of value when field is blank.
-    cantclear (bool): Field can't be cleared if True.
-    required (bool): If True, form cannot be submitted while field is blank.
-    verifyfunc (callable): Name of a callable used to verify input - takes
-        (caller, value) as arguments. If the function returns True,
-        the player's input is considered valid - if it returns False,
-        the input is rejected. Any other value returned will act as
-        the field's new value, replacing the player's input. This
-        allows for values that aren't strings or integers (such as
-        object dbrefs). For boolean fields, return '0' or '1' to set
-        the field to False or True.
-"""
-
-from evennia.utils import evmenu, evtable, delay, list_to_string, logger
-from evennia import Command
-from evennia.server.sessionhandler import SESSIONS
-
-
-
[docs]class FieldEvMenu(evmenu.EvMenu): - """ - Custom EvMenu type with its own node formatter - removes extraneous lines - """ - -
[docs] def node_formatter(self, nodetext, optionstext): - """ - Formats the entirety of the node. - - Args: - nodetext (str): The node text as returned by `self.nodetext_formatter`. - optionstext (str): The options display as returned by `self.options_formatter`. - caller (Object, Account or None, optional): The caller of the node. - - Returns: - node (str): The formatted node to display. - - """ - # Only return node text, no options or separators - return nodetext
- - -
[docs]def init_fill_field( - formtemplate, - caller, - formcallback, - pretext="", - posttext="", - submitcmd="submit", - borderstyle="cells", - formhelptext=None, - persistent=False, - initial_formdata=None, -): - """ - Initializes a menu presenting a player with a fillable form - once the form - is submitted, the data will be passed as a dictionary to your chosen - function. - - Args: - formtemplate (list of dicts): The template for the form's fields. - caller (obj): Player who will be filling out the form. - formcallback (callable): Function to pass the completed form's data to. - - Options: - pretext (str): Text to put before the form in the menu. - posttext (str): Text to put after the form in the menu. - submitcmd (str): Command used to submit the form. - borderstyle (str): Form's EvTable border style. - formhelptext (str): Help text for the form menu (or default is provided). - persistent (bool): Whether to make the EvMenu persistent across reboots. - initial_formdata (dict): Initial data for the form - a blank form with - defaults specified in the template will be generated otherwise. - In the case of a form used to edit properties on an object or a - similar application, you may want to generate the initial form - data dynamically before calling init_fill_field. - """ - - # Initialize form data from the template if none provided - formdata = form_template_to_dict(formtemplate) - if initial_formdata: - formdata = initial_formdata - - # Provide default help text if none given - if formhelptext is None: - formhelptext = ( - "Available commands:|/" - "|w<field> = <new value>:|n Set given field to new value, replacing the old value|/" - "|wclear <field>:|n Clear the value in the given field, making it blank|/" - "|wlook|n: Show the form's current values|/" - "|whelp|n: Display this help screen|/" - "|wquit|n: Quit the form menu without submitting|/" - "|w%s|n: Submit this form and quit the menu" % submitcmd - ) - - # Pass kwargs to store data needed in the menu - kwargs = { - "formdata": formdata, - "formtemplate": formtemplate, - "formcallback": formcallback, - "pretext": pretext, - "posttext": posttext, - "submitcmd": submitcmd, - "borderstyle": borderstyle, - "formhelptext": formhelptext, - } - - # Initialize menu of selections - FieldEvMenu( - caller, - "evennia.contrib.fieldfill", - startnode="menunode_fieldfill", - auto_look=False, - persistent=persistent, - **kwargs, - )
- - - - - -
[docs]def form_template_to_dict(formtemplate): - """ - Initializes a dictionary of form data from the given list-of-dictionaries - form template, as formatted above. - - Args: - formtemplate (list of dicts): Tempate for the form to be initialized. - - Returns: - formdata (dict): Dictionary of initalized form data. - """ - formdata = {} - - for field in formtemplate: - # Value is blank by default - fieldvalue = None - if "default" in field: - # Add in default value if present - fieldvalue = field["default"] - formdata.update({field["fieldname"]: fieldvalue}) - - return formdata
- - -
[docs]def display_formdata(formtemplate, formdata, pretext="", posttext="", borderstyle="cells"): - """ - Displays a form's current data as a table. Used in the form menu. - - Args: - formtemplate (list of dicts): Template for the form - formdata (dict): Form's current data - - Options: - pretext (str): Text to put before the form table. - posttext (str): Text to put after the form table. - borderstyle (str): EvTable's border style. - """ - - formtable = evtable.EvTable(border=borderstyle, valign="t", maxwidth=80) - field_name_width = 5 - - for field in formtemplate: - new_fieldname = None - new_fieldvalue = None - # Get field name - new_fieldname = "|w" + field["fieldname"] + ":|n" - if len(field["fieldname"]) + 5 > field_name_width: - field_name_width = len(field["fieldname"]) + 5 - # Get field value - if formdata[field["fieldname"]] is not None: - new_fieldvalue = str(formdata[field["fieldname"]]) - # Use blank message if field is blank and once is present - if new_fieldvalue is None and "blankmsg" in field: - new_fieldvalue = "|x" + str(field["blankmsg"]) + "|n" - elif new_fieldvalue is None: - new_fieldvalue = " " - # Replace True and False values with truestr and falsestr from template - if formdata[field["fieldname"]] is True and "truestr" in field: - new_fieldvalue = field["truestr"] - elif formdata[field["fieldname"]] is False and "falsestr" in field: - new_fieldvalue = field["falsestr"] - # Add name and value to table - formtable.add_row(new_fieldname, new_fieldvalue) - - formtable.reformat_column(0, align="r", width=field_name_width) - - return pretext + "|/" + str(formtable) + "|/" + posttext
- - -# EXAMPLE FUNCTIONS / COMMAND STARTS HERE - - -
[docs]def verify_online_player(caller, value): - """ - Example 'verify function' that matches player input to an online character - or else rejects their input as invalid. - - Args: - caller (obj): Player entering the form data. - value (str): String player entered into the form, to be verified. - - Returns: - matched_character (obj or False): dbref to a currently logged in - character object - reference to the object will be stored in - the form instead of a string. Returns False if no match is - made. - """ - # Get a list of sessions - session_list = SESSIONS.get_sessions() - char_list = [] - matched_character = None - - # Get a list of online characters - for session in session_list: - if not session.logged_in: - # Skip over logged out characters - continue - # Append to our list of online characters otherwise - char_list.append(session.get_puppet()) - - # Match player input to a character name - for character in char_list: - if value.lower() == character.key.lower(): - matched_character = character - - # If input didn't match to a character - if not matched_character: - # Send the player an error message unique to this function - caller.msg("No character matching '%s' is online." % value) - # Returning False indicates the new value is not valid - return False - - # Returning anything besides True or False will replace the player's input with the returned - # value. In this case, the value becomes a reference to the character object. You can store data - # besides strings and integers in the 'formdata' dictionary this way! - return matched_character
- - -# Form template for the example 'delayed message' form -SAMPLE_FORM = [ - { - "fieldname": "Character", - "fieldtype": "text", - "max": 30, - "blankmsg": "(Name of an online player)", - "required": True, - "verifyfunc": verify_online_player, - }, - { - "fieldname": "Delay", - "fieldtype": "number", - "min": 3, - "max": 30, - "default": 10, - "cantclear": True, - }, - { - "fieldname": "Message", - "fieldtype": "text", - "min": 3, - "max": 200, - "blankmsg": "(Message up to 200 characters)", - }, - { - "fieldname": "Anonymous", - "fieldtype": "bool", - "truestr": "Yes", - "falsestr": "No", - "default": False, - }, -] - - -
[docs]class CmdTestMenu(Command): - """ - This test command will initialize a menu that presents you with a form. - You can fill out the fields of this form in any order, and then type in - 'send' to send a message to another online player, which will reach them - after a delay you specify. - - Usage: - <field> = <new value> - clear <field> - help - look - quit - send - """ - - key = "testmenu" - -
[docs] def func(self): - """ - This performs the actual command. - """ - pretext = ( - "|cSend a delayed message to another player ---------------------------------------|n" - ) - posttext = ( - "|c--------------------------------------------------------------------------------|n|/" - "Syntax: type |c<field> = <new value>|n to change the values of the form. Given|/" - "player must be currently logged in, delay is given in seconds. When you are|/" - "finished, type '|csend|n' to send the message.|/" - ) - - init_fill_field( - SAMPLE_FORM, - self.caller, - init_delayed_message, - pretext=pretext, - posttext=posttext, - submitcmd="send", - borderstyle="none", - )
- - -
[docs]def sendmessage(obj, text): - """ - Callback to send a message to a player. - - Args: - obj (obj): Player to message. - text (str): Message. - """ - obj.msg(text)
- - -
[docs]def init_delayed_message(caller, formdata): - """ - Initializes a delayed message, using data from the example form. - - Args: - caller (obj): Character submitting the message. - formdata (dict): Data from submitted form. - """ - # Retrieve data from the filled out form. - # We stored the character to message as an object ref using a verifyfunc - # So we don't have to do any more searching or matching here! - player_to_message = formdata["Character"] - message_delay = formdata["Delay"] - sender = str(caller) - if formdata["Anonymous"] is True: - sender = "anonymous" - message = ("Message from %s: " % sender) + str(formdata["Message"]) - - caller.msg("Message sent to %s!" % player_to_message) - # Make a deferred call to 'sendmessage' above. - delay(message_delay, sendmessage, player_to_message, message) - return
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/gendersub.html b/docs/0.9.5/_modules/evennia/contrib/gendersub.html deleted file mode 100644 index e6dc846247..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/gendersub.html +++ /dev/null @@ -1,263 +0,0 @@ - - - - - - - - evennia.contrib.gendersub — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.gendersub

-"""
-Gendersub
-
-Griatch 2015
-
-This is a simple gender-aware Character class for allowing users to
-insert custom markers in their text to indicate gender-aware
-messaging. It relies on a modified msg() and is meant as an
-inspiration and starting point to how to do stuff like this.
-
-An object can have the following genders:
- - male (he/his)
- - female (her/hers)
- - neutral (it/its)
- - ambiguous (they/them/their/theirs)
-
-When in use, messages can contain special tags to indicate pronouns gendered
-based on the one being addressed. Capitalization will be retained.
-
-- `|s`, `|S`: Subjective form: he, she, it, He, She, It, They
-- `|o`, `|O`: Objective form: him, her, it, Him, Her, It, Them
-- `|p`, `|P`: Possessive form: his, her, its, His, Her, Its, Their
-- `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
-
-For example,
-
-```
-char.msg("%s falls on |p face with a thud." % char.key)
-"Tom falls on his face with a thud"
-```
-
-The default gender is "ambiguous" (they/them/their/theirs).
-
-To use, have DefaultCharacter inherit from this, or change
-setting.DEFAULT_CHARACTER to point to this class.
-
-The `@gender` command is used to set the gender. It needs to be added to the
-default cmdset before it becomes available.
-
-"""
-
-import re
-from evennia.utils import logger
-from evennia import DefaultCharacter
-from evennia import Command
-
-# gender maps
-
-_GENDER_PRONOUN_MAP = {
-    "male": {"s": "he", "o": "him", "p": "his", "a": "his"},
-    "female": {"s": "she", "o": "her", "p": "her", "a": "hers"},
-    "neutral": {"s": "it", "o": "it", "p": "its", "a": "its"},
-    "ambiguous": {"s": "they", "o": "them", "p": "their", "a": "theirs"},
-}
-_RE_GENDER_PRONOUN = re.compile(r"(?<!\|)\|(?!\|)[sSoOpPaA]")
-
-# in-game command for setting the gender
-
-
-
[docs]class SetGender(Command): - """ - Sets gender on yourself - - Usage: - @gender male||female||neutral||ambiguous - - """ - - key = "@gender" - aliases = "@sex" - locks = "call:all()" - -
[docs] def func(self): - """ - Implements the command. - """ - caller = self.caller - arg = self.args.strip().lower() - if arg not in ("male", "female", "neutral", "ambiguous"): - caller.msg("Usage: @gender male||female||neutral||ambiguous") - return - caller.db.gender = arg - caller.msg("Your gender was set to %s." % arg)
- - -# Gender-aware character class - - -
[docs]class GenderCharacter(DefaultCharacter): - """ - This is a Character class aware of gender. - - """ - -
[docs] def at_object_creation(self): - """ - Called once when the object is created. - """ - super().at_object_creation() - self.db.gender = "ambiguous"
- - def _get_pronoun(self, regex_match): - """ - Get pronoun from the pronoun marker in the text. This is used as - the callable for the re.sub function. - - Args: - regex_match (MatchObject): the regular expression match. - - Notes: - - `|s`, `|S`: Subjective form: he, she, it, He, She, It, They - - `|o`, `|O`: Objective form: him, her, it, Him, Her, It, Them - - `|p`, `|P`: Possessive form: his, her, its, His, Her, Its, Their - - `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs - - """ - typ = regex_match.group()[1] # "s", "O" etc - gender = self.attributes.get("gender", default="ambiguous") - gender = gender if gender in ("male", "female", "neutral") else "ambiguous" - pronoun = _GENDER_PRONOUN_MAP[gender][typ.lower()] - return pronoun.capitalize() if typ.isupper() else pronoun - -
[docs] def msg(self, text=None, from_obj=None, session=None, **kwargs): - """ - Emits something to a session attached to the object. - Overloads the default msg() implementation to include - gender-aware markers in output. - - Args: - text (str or tuple, optional): The message to send. This - is treated internally like any send-command, so its - value can be a tuple if sending multiple arguments to - the `text` oob command. - from_obj (obj, optional): object that is sending. If - given, at_msg_send will be called - session (Session or list, optional): session or list of - sessions to relay to, if any. If set, will - force send regardless of MULTISESSION_MODE. - Notes: - `at_msg_receive` will be called on this Object. - All extra kwargs will be passed on to the protocol. - - """ - if text is None: - super().msg(from_obj=from_obj, session=session, **kwargs) - return - - try: - if text and isinstance(text, tuple): - text = (_RE_GENDER_PRONOUN.sub(self._get_pronoun, text[0]), *text[1:]) - else: - text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text) - except TypeError: - pass - except Exception as e: - logger.log_trace(e) - super().msg(text, from_obj=from_obj, session=session, **kwargs)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/health_bar.html b/docs/0.9.5/_modules/evennia/contrib/health_bar.html deleted file mode 100644 index 2dfe50f42a..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/health_bar.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - evennia.contrib.health_bar — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.health_bar

-"""
-Health Bar
-
-Contrib - Tim Ashley Jenkins 2017
-
-The function provided in this module lets you easily display visual
-bars or meters - "health bar" is merely the most obvious use for this,
-though these bars are highly customizable and can be used for any sort
-of appropriate data besides player health.
-
-Today's players may be more used to seeing statistics like health,
-stamina, magic, and etc. displayed as bars rather than bare numerical
-values, so using this module to present this data this way may make it
-more accessible. Keep in mind, however, that players may also be using
-a screen reader to connect to your game, which will not be able to
-represent the colors of the bar in any way. By default, the values
-represented are rendered as text inside the bar which can be read by
-screen readers.
-
-The health bar will account for current values above the maximum or
-below 0, rendering them as a completely full or empty bar with the
-values displayed within.
-"""
-
-
-
[docs]def display_meter( - cur_value, - max_value, - length=30, - fill_color=["R", "Y", "G"], - empty_color="B", - text_color="w", - align="left", - pre_text="", - post_text="", - show_values=True, -): - """ - Represents a current and maximum value given as a "bar" rendered with - ANSI or xterm256 background colors. - - Args: - cur_value (int): Current value to display - max_value (int): Maximum value to display - - Options: - length (int): Length of meter returned, in characters - fill_color (list): List of color codes for the full portion - of the bar, sans any sort of prefix - both ANSI and xterm256 - colors are usable. When the bar is empty, colors toward the - start of the list will be chosen - when the bar is full, colors - towards the end are picked. You can adjust the 'weights' of - the changing colors by adding multiple entries of the same - color - for example, if you only want the bar to change when - it's close to empty, you could supply ['R','Y','G','G','G'] - empty_color (str): Color code for the empty portion of the bar. - text_color (str): Color code for text inside the bar. - align (str): "left", "right", or "center" - alignment of text in the bar - pre_text (str): Text to put before the numbers in the bar - post_text (str): Text to put after the numbers in the bar - show_values (bool): If true, shows the numerical values represented by - the bar. It's highly recommended you keep this on, especially if - there's no info given in pre_text or post_text, as players on screen - readers will be unable to read the graphical aspect of the bar. - """ - # Start by building the base string. - num_text = "" - if show_values: - num_text = "%i / %i" % (cur_value, max_value) - bar_base_str = pre_text + num_text + post_text - # Cut down the length of the base string if needed - if len(bar_base_str) > length: - bar_base_str = bar_base_str[:length] - # Pad and align the bar base string - if align == "right": - bar_base_str = bar_base_str.rjust(length, " ") - elif align == "center": - bar_base_str = bar_base_str.center(length, " ") - else: - bar_base_str = bar_base_str.ljust(length, " ") - - if max_value < 1: # Prevent divide by zero - max_value = 1 - if cur_value < 0: # Prevent weirdly formatted 'negative bars' - cur_value = 0 - if cur_value > max_value: # Display overfull bars correctly - cur_value = max_value - - # Now it's time to determine where to put the color codes. - percent_full = float(cur_value) / float(max_value) - split_index = round(float(length) * percent_full) - # Determine point at which to split the bar - split_index = int(split_index) - - # Separate the bar string into full and empty portions - full_portion = bar_base_str[:split_index] - empty_portion = bar_base_str[split_index:] - - # Pick which fill color to use based on how full the bar is - fillcolor_index = float(len(fill_color)) * percent_full - fillcolor_index = max(0, int(round(fillcolor_index)) - 1) - fillcolor_code = "|[" + fill_color[fillcolor_index] - - # Make color codes for empty bar portion and text_color - emptycolor_code = "|[" + empty_color - textcolor_code = "|" + text_color - - # Assemble the final bar - final_bar = ( - fillcolor_code - + textcolor_code - + full_portion - + "|n" - + emptycolor_code - + textcolor_code - + empty_portion - + "|n" - ) - - return final_bar
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/ingame_python/callbackhandler.html b/docs/0.9.5/_modules/evennia/contrib/ingame_python/callbackhandler.html deleted file mode 100644 index c09ec3d5e1..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/ingame_python/callbackhandler.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - - - - - evennia.contrib.ingame_python.callbackhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.ingame_python.callbackhandler

-"""
-Module containing the CallbackHandler for individual objects.
-"""
-
-from collections import namedtuple
-
-
-
[docs]class CallbackHandler(object): - - """ - The callback handler for a specific object. - - The script that contains all callbacks will be reached through this - handler. This handler is therefore a shortcut to be used by - developers. This handler (accessible through `obj.callbacks`) is a - shortcut to manipulating callbacks within this object, getting, - adding, editing, deleting and calling them. - - """ - - script = None - -
[docs] def __init__(self, obj): - self.obj = obj
- -
[docs] def all(self): - """ - Return all callbacks linked to this object. - - Returns: - All callbacks in a dictionary callback_name: callback}. The callback - is returned as a namedtuple to simplify manipulation. - - """ - callbacks = {} - handler = type(self).script - if handler: - dicts = handler.get_callbacks(self.obj) - for callback_name, in_list in dicts.items(): - new_list = [] - for callback in in_list: - callback = self.format_callback(callback) - new_list.append(callback) - - if new_list: - callbacks[callback_name] = new_list - - return callbacks
- -
[docs] def get(self, callback_name): - """ - Return the callbacks associated with this name. - - Args: - callback_name (str): the name of the callback. - - Returns: - A list of callbacks associated with this object and of this name. - - Note: - This method returns a list of callback objects (namedtuple - representations). If the callback name cannot be found in the - object's callbacks, return an empty list. - - """ - return self.all().get(callback_name, [])
- -
[docs] def get_variable(self, variable_name): - """ - Return the variable value or None. - - Args: - variable_name (str): the name of the variable. - - Returns: - Either the variable's value or None. - - """ - handler = type(self).script - if handler: - return handler.get_variable(variable_name) - - return None
- -
[docs] def add(self, callback_name, code, author=None, valid=False, parameters=""): - """ - Add a new callback for this object. - - Args: - callback_name (str): the name of the callback to add. - code (str): the Python code associated with this callback. - author (Character or Account, optional): the author of the callback. - valid (bool, optional): should the callback be connected? - parameters (str, optional): optional parameters. - - Returns: - The callback definition that was added or None. - - """ - handler = type(self).script - if handler: - return self.format_callback( - handler.add_callback( - self.obj, callback_name, code, author=author, valid=valid, parameters=parameters - ) - )
- -
[docs] def edit(self, callback_name, number, code, author=None, valid=False): - """ - Edit an existing callback bound to this object. - - Args: - callback_name (str): the name of the callback to edit. - number (int): the callback number to be changed. - code (str): the Python code associated with this callback. - author (Character or Account, optional): the author of the callback. - valid (bool, optional): should the callback be connected? - - Returns: - The callback definition that was edited or None. - - Raises: - RuntimeError if the callback is locked. - - """ - handler = type(self).script - if handler: - return self.format_callback( - handler.edit_callback( - self.obj, callback_name, number, code, author=author, valid=valid - ) - )
- -
[docs] def remove(self, callback_name, number): - """ - Delete the specified callback bound to this object. - - Args: - callback_name (str): the name of the callback to delete. - number (int): the number of the callback to delete. - - Raises: - RuntimeError if the callback is locked. - - """ - handler = type(self).script - if handler: - handler.del_callback(self.obj, callback_name, number)
- -
[docs] def call(self, callback_name, *args, **kwargs): - """ - Call the specified callback(s) bound to this object. - - Args: - callback_name (str): the callback name to call. - *args: additional variables for this callback. - - Keyword Args: - number (int, optional): call just a specific callback. - parameters (str, optional): call a callback with parameters. - locals (dict, optional): a locals replacement. - - Returns: - True to report the callback was called without interruption, - False otherwise. If the callbackHandler isn't found, return - None. - - """ - handler = type(self).script - if handler: - return handler.call(self.obj, callback_name, *args, **kwargs) - - return None
- -
[docs] @staticmethod - def format_callback(callback): - """ - Return the callback namedtuple to represent the specified callback. - - Args: - callback (dict): the callback definition. - - The callback given in argument should be a dictionary containing - the expected fields for a callback (code, author, valid...). - - """ - if "obj" not in callback: - callback["obj"] = None - if "name" not in callback: - callback["name"] = "unknown" - if "number" not in callback: - callback["number"] = -1 - if "code" not in callback: - callback["code"] = "" - if "author" not in callback: - callback["author"] = None - if "valid" not in callback: - callback["valid"] = False - if "parameters" not in callback: - callback["parameters"] = "" - if "created_on" not in callback: - callback["created_on"] = None - if "updated_by" not in callback: - callback["updated_by"] = None - if "updated_on" not in callback: - callback["updated_on"] = None - - return Callback(**callback)
- - -Callback = namedtuple( - "Callback", - ( - "obj", - "name", - "number", - "code", - "author", - "valid", - "parameters", - "created_on", - "updated_by", - "updated_on", - ), -) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/ingame_python/commands.html b/docs/0.9.5/_modules/evennia/contrib/ingame_python/commands.html deleted file mode 100644 index 4169347e71..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/ingame_python/commands.html +++ /dev/null @@ -1,689 +0,0 @@ - - - - - - - - evennia.contrib.ingame_python.commands — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.ingame_python.commands

-"""
-Module containing the commands of the in-game Python system.
-"""
-
-from datetime import datetime
-
-from django.conf import settings
-from evennia import Command
-from evennia.utils.ansi import raw
-from evennia.utils.eveditor import EvEditor
-from evennia.utils.evtable import EvTable
-from evennia.utils.utils import class_from_module, time_format
-from evennia.contrib.ingame_python.utils import get_event_handler
-
-COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
-
-# Permissions
-WITH_VALIDATION = getattr(settings, "callbackS_WITH_VALIDATION", None)
-WITHOUT_VALIDATION = getattr(settings, "callbackS_WITHOUT_VALIDATION", "developer")
-VALIDATING = getattr(settings, "callbackS_VALIDATING", "developer")
-
-# Split help text
-BASIC_HELP = "Add, edit or delete callbacks."
-
-BASIC_USAGES = [
-    "@call <object name> [= <callback name>]",
-    "@call/add <object name> = <callback name> [parameters]",
-    "@call/edit <object name> = <callback name> [callback number]",
-    "@call/del <object name> = <callback name> [callback number]",
-    "@call/tasks [object name [= <callback name>]]",
-]
-
-BASIC_SWITCHES = [
-    "add    - add and edit a new callback",
-    "edit   - edit an existing callback",
-    "del    - delete an existing callback",
-    "tasks  - show the list of differed tasks",
-]
-
-VALIDATOR_USAGES = ["@call/accept [object name = <callback name> [callback number]]"]
-
-VALIDATOR_SWITCHES = ["accept - show callbacks to be validated or accept one"]
-
-BASIC_TEXT = """
-This command is used to manipulate callbacks.  A callback can be linked to
-an object, to fire at a specific moment.  You can use the command without
-switches to see what callbacks are active on an object:
-  @call self
-You can also specify a callback name if you want the list of callbacks
-associated with this object of this name:
-  @call north = can_traverse
-You can also add a number after the callback name to see details on one callback:
-  @call here = say 2
-You can also add, edit or remove callbacks using the add, edit or del switches.
-Additionally, you can see the list of differed tasks created by callbacks
-(chained events to be called) using the /tasks switch.
-"""
-
-VALIDATOR_TEXT = """
-You can also use this command to validate callbacks.  Depending on your game
-setting, some users might be allowed to add new callbacks, but these callbacks
-will not be fired until you accept them.  To see the callbacks needing
-validation, enter the /accept switch without argument:
-  @call/accept
-A table will show you the callbacks that are not validated yet, who created
-them and when.  You can then accept a specific callback:
-  @call here = enter 1
-Use the /del switch to remove callbacks that should not be connected.
-"""
-
-
-
[docs]class CmdCallback(COMMAND_DEFAULT_CLASS): - - """ - Command to edit callbacks. - """ - - key = "@call" - aliases = ["@callback", "@callbacks", "@calls"] - locks = "cmd:perm({})".format(VALIDATING) - if WITH_VALIDATION: - locks += " or perm({})".format(WITH_VALIDATION) - help_category = "Building" - -
[docs] def get_help(self, caller, cmdset): - """ - Return the help message for this command and this caller. - - The help text of this specific command will vary depending - on user permission. - - Args: - caller (Object or Account): the caller asking for help on the command. - cmdset (CmdSet): the command set (if you need additional commands). - - Returns: - docstring (str): the help text to provide the caller for this command. - - """ - lock = "perm({}) or perm(callbacks_validating)".format(VALIDATING) - validator = caller.locks.check_lockstring(caller, lock) - text = "\n" + BASIC_HELP + "\n\nUsages:\n " - - # Usages - text += "\n ".join(BASIC_USAGES) - if validator: - text += "\n " + "\n ".join(VALIDATOR_USAGES) - - # Switches - text += "\n\nSwitches:\n " - text += "\n ".join(BASIC_SWITCHES) - if validator: - text += "\n " + "\n ".join(VALIDATOR_SWITCHES) - - # Text - text += "\n" + BASIC_TEXT - if validator: - text += "\n" + VALIDATOR_TEXT - - return text
- -
[docs] def func(self): - """Command body.""" - caller = self.caller - lock = "perm({}) or perm(events_validating)".format(VALIDATING) - validator = caller.locks.check_lockstring(caller, lock) - lock = "perm({}) or perm(events_without_validation)".format(WITHOUT_VALIDATION) - autovalid = caller.locks.check_lockstring(caller, lock) - - # First and foremost, get the callback handler and set other variables - self.handler = get_event_handler() - self.obj = None - rhs = self.rhs or "" - self.callback_name, sep, self.parameters = rhs.partition(" ") - self.callback_name = self.callback_name.lower() - self.is_validator = validator - self.autovalid = autovalid - if self.handler is None: - caller.msg("The event handler is not running, can't " "access the event system.") - return - - # Before the equal sign, there is an object name or nothing - if self.lhs: - self.obj = caller.search(self.lhs) - if not self.obj: - return - - # Switches are mutually exclusive - switch = self.switches and self.switches[0] or "" - if switch in ("", "add", "edit", "del") and self.obj is None: - caller.msg("Specify an object's name or #ID.") - return - - if switch == "": - self.list_callbacks() - elif switch == "add": - self.add_callback() - elif switch == "edit": - self.edit_callback() - elif switch == "del": - self.del_callback() - elif switch == "accept" and validator: - self.accept_callback() - elif switch in ["tasks", "task"]: - self.list_tasks() - else: - caller.msg("Mutually exclusive or invalid switches were " "used, cannot proceed.")
- -
[docs] def list_callbacks(self): - """Display the list of callbacks connected to the object.""" - obj = self.obj - callback_name = self.callback_name - parameters = self.parameters - callbacks = self.handler.get_callbacks(obj) - types = self.handler.get_events(obj) - - if callback_name: - # Check that the callback name can be found in this object - created = callbacks.get(callback_name) - if created is None: - self.msg("No callback {} has been set on {}.".format(callback_name, obj)) - return - - if parameters: - # Check that the parameter points to an existing callback - try: - number = int(parameters) - 1 - assert number >= 0 - callback = callbacks[callback_name][number] - except (ValueError, AssertionError, IndexError): - self.msg( - "The callback {} {} cannot be found in {}.".format( - callback_name, parameters, obj - ) - ) - return - - # Display the callback's details - author = callback.get("author") - author = author.key if author else "|gUnknown|n" - updated_by = callback.get("updated_by") - updated_by = updated_by.key if updated_by else "|gUnknown|n" - created_on = callback.get("created_on") - created_on = ( - created_on.strftime("%Y-%m-%d %H:%M:%S") if created_on else "|gUnknown|n" - ) - updated_on = callback.get("updated_on") - updated_on = ( - updated_on.strftime("%Y-%m-%d %H:%M:%S") if updated_on else "|gUnknown|n" - ) - msg = "Callback {} {} of {}:".format(callback_name, parameters, obj) - msg += "\nCreated by {} on {}.".format(author, created_on) - msg += "\nUpdated by {} on {}".format(updated_by, updated_on) - - if self.is_validator: - if callback.get("valid"): - msg += "\nThis callback is |rconnected|n and active." - else: - msg += "\nThis callback |rhasn't been|n accepted yet." - - msg += "\nCallback code:\n" - msg += raw(callback["code"]) - self.msg(msg) - return - - # No parameter has been specified, display the table of callbacks - cols = ["Number", "Author", "Updated", "Param"] - if self.is_validator: - cols.append("Valid") - - table = EvTable(*cols, width=78) - table.reformat_column(0, align="r") - now = datetime.now() - for i, callback in enumerate(created): - author = callback.get("author") - author = author.key if author else "|gUnknown|n" - updated_on = callback.get("updated_on") - if updated_on is None: - updated_on = callback.get("created_on") - - if updated_on: - updated_on = "{} ago".format( - time_format((now - updated_on).total_seconds(), 4).capitalize() - ) - else: - updated_on = "|gUnknown|n" - parameters = callback.get("parameters", "") - - row = [str(i + 1), author, updated_on, parameters] - if self.is_validator: - row.append("Yes" if callback.get("valid") else "No") - table.add_row(*row) - - self.msg(str(table)) - else: - names = list(set(list(types.keys()) + list(callbacks.keys()))) - table = EvTable("Callback name", "Number", "Description", valign="t", width=78) - table.reformat_column(0, width=20) - table.reformat_column(1, width=10, align="r") - table.reformat_column(2, width=48) - for name in sorted(names): - number = len(callbacks.get(name, [])) - lines = sum(len(e["code"].splitlines()) for e in callbacks.get(name, [])) - no = "{} ({})".format(number, lines) - description = types.get(name, (None, "Chained event."))[1] - description = description.strip("\n").splitlines()[0] - table.add_row(name, no, description) - - self.msg(str(table))
- -
[docs] def add_callback(self): - """Add a callback.""" - obj = self.obj - callback_name = self.callback_name - types = self.handler.get_events(obj) - - # Check that the callback exists - if not callback_name.startswith("chain_") and callback_name not in types: - self.msg( - "The callback name {} can't be found in {} of " - "typeclass {}.".format(callback_name, obj, type(obj)) - ) - return - - definition = types.get(callback_name, (None, "Chained event.")) - description = definition[1] - self.msg(raw(description.strip("\n"))) - - # Open the editor - callback = self.handler.add_callback( - obj, callback_name, "", self.caller, False, parameters=self.parameters - ) - - # Lock this callback right away - self.handler.db.locked.append((obj, callback_name, callback["number"])) - - # Open the editor for this callback - self.caller.db._callback = callback - EvEditor( - self.caller, - loadfunc=_ev_load, - savefunc=_ev_save, - quitfunc=_ev_quit, - key="Callback {} of {}".format(callback_name, obj), - persistent=True, - codefunc=_ev_save, - )
- -
[docs] def edit_callback(self): - """Edit a callback.""" - obj = self.obj - callback_name = self.callback_name - parameters = self.parameters - callbacks = self.handler.get_callbacks(obj) - types = self.handler.get_events(obj) - - # If no callback name is specified, display the list of callbacks - if not callback_name: - self.list_callbacks() - return - - # Check that the callback exists - if callback_name not in callbacks: - self.msg("The callback name {} can't be found in {}.".format(callback_name, obj)) - return - - # If there's only one callback, just edit it - if len(callbacks[callback_name]) == 1: - number = 0 - callback = callbacks[callback_name][0] - else: - if not parameters: - self.msg("Which callback do you wish to edit? Specify a number.") - self.list_callbacks() - return - - # Check that the parameter points to an existing callback - try: - number = int(parameters) - 1 - assert number >= 0 - callback = callbacks[callback_name][number] - except (ValueError, AssertionError, IndexError): - self.msg( - "The callback {} {} cannot be found in {}.".format( - callback_name, parameters, obj - ) - ) - return - - # If caller can't edit without validation, forbid editing - # others' works - if not self.autovalid and callback["author"] is not self.caller: - self.msg("You cannot edit this callback created by someone else.") - return - - # If the callback is locked (edited by someone else) - if (obj, callback_name, number) in self.handler.db.locked: - self.msg("This callback is locked, you cannot edit it.") - return - - self.handler.db.locked.append((obj, callback_name, number)) - - # Check the definition of the callback - definition = types.get(callback_name, (None, "Chained event.")) - description = definition[1] - self.msg(raw(description.strip("\n"))) - - # Open the editor - callback = dict(callback) - self.caller.db._callback = callback - EvEditor( - self.caller, - loadfunc=_ev_load, - savefunc=_ev_save, - quitfunc=_ev_quit, - key="Callback {} of {}".format(callback_name, obj), - persistent=True, - codefunc=_ev_save, - )
- -
[docs] def del_callback(self): - """Delete a callback.""" - obj = self.obj - callback_name = self.callback_name - parameters = self.parameters - callbacks = self.handler.get_callbacks(obj) - types = self.handler.get_events(obj) - - # If no callback name is specified, display the list of callbacks - if not callback_name: - self.list_callbacks() - return - - # Check that the callback exists - if callback_name not in callbacks: - self.msg("The callback name {} can't be found in {}.".format(callback_name, obj)) - return - - # If there's only one callback, just delete it - if len(callbacks[callback_name]) == 1: - number = 0 - callback = callbacks[callback_name][0] - else: - if not parameters: - self.msg("Which callback do you wish to delete? Specify " "a number.") - self.list_callbacks() - return - - # Check that the parameter points to an existing callback - try: - number = int(parameters) - 1 - assert number >= 0 - callback = callbacks[callback_name][number] - except (ValueError, AssertionError, IndexError): - self.msg( - "The callback {} {} cannot be found in {}.".format( - callback_name, parameters, obj - ) - ) - return - - # If caller can't edit without validation, forbid deleting - # others' works - if not self.autovalid and callback["author"] is not self.caller: - self.msg("You cannot delete this callback created by someone else.") - return - - # If the callback is locked (edited by someone else) - if (obj, callback_name, number) in self.handler.db.locked: - self.msg("This callback is locked, you cannot delete it.") - return - - # Delete the callback - self.handler.del_callback(obj, callback_name, number) - self.msg("The callback {}[{}] of {} was deleted.".format(callback_name, number + 1, obj))
- -
[docs] def accept_callback(self): - """Accept a callback.""" - obj = self.obj - callback_name = self.callback_name - parameters = self.parameters - - # If no object, display the list of callbacks to be checked - if obj is None: - table = EvTable("ID", "Type", "Object", "Name", "Updated by", "On", width=78) - table.reformat_column(0, align="r") - now = datetime.now() - for obj, name, number in self.handler.db.to_valid: - callbacks = self.handler.get_callbacks(obj).get(name) - if callbacks is None: - continue - - try: - callback = callbacks[number] - except IndexError: - continue - - type_name = obj.typeclass_path.split(".")[-1] - by = callback.get("updated_by") - by = by.key if by else "|gUnknown|n" - updated_on = callback.get("updated_on") - if updated_on is None: - updated_on = callback.get("created_on") - - if updated_on: - updated_on = "{} ago".format( - time_format((now - updated_on).total_seconds(), 4).capitalize() - ) - else: - updated_on = "|gUnknown|n" - - table.add_row(obj.id, type_name, obj, name, by, updated_on) - self.msg(str(table)) - return - - # An object was specified - callbacks = self.handler.get_callbacks(obj) - types = self.handler.get_events(obj) - - # If no callback name is specified, display the list of callbacks - if not callback_name: - self.list_callbacks() - return - - # Check that the callback exists - if callback_name not in callbacks: - self.msg("The callback name {} can't be found in {}.".format(callback_name, obj)) - return - - if not parameters: - self.msg("Which callback do you wish to accept? Specify a number.") - self.list_callbacks() - return - - # Check that the parameter points to an existing callback - try: - number = int(parameters) - 1 - assert number >= 0 - callback = callbacks[callback_name][number] - except (ValueError, AssertionError, IndexError): - self.msg( - "The callback {} {} cannot be found in {}.".format(callback_name, parameters, obj) - ) - return - - # Accept the callback - if callback["valid"]: - self.msg("This callback has already been accepted.") - else: - self.handler.accept_callback(obj, callback_name, number) - self.msg( - "The callback {} {} of {} has been accepted.".format(callback_name, parameters, obj) - )
- -
[docs] def list_tasks(self): - """List the active tasks.""" - obj = self.obj - callback_name = self.callback_name - handler = self.handler - tasks = [(k, v[0], v[1], v[2]) for k, v in handler.db.tasks.items()] - if obj: - tasks = [task for task in tasks if task[2] is obj] - if callback_name: - tasks = [task for task in tasks if task[3] == callback_name] - - tasks.sort() - table = EvTable("ID", "Object", "Callback", "In", width=78) - table.reformat_column(0, align="r") - now = datetime.now() - for task_id, future, obj, callback_name in tasks: - key = obj.get_display_name(self.caller) - delta = time_format((future - now).total_seconds(), 1) - table.add_row(task_id, key, callback_name, delta) - - self.msg(str(table))
- - -# Private functions to handle editing - - -def _ev_load(caller): - return caller.db._callback and caller.db._callback.get("code", "") or "" - - -def _ev_save(caller, buf): - """Save and add the callback.""" - lock = "perm({}) or perm(events_without_validation)".format(WITHOUT_VALIDATION) - autovalid = caller.locks.check_lockstring(caller, lock) - callback = caller.db._callback - handler = get_event_handler() - if ( - not handler - or not callback - or not all(key in callback for key in ("obj", "name", "number", "valid")) - ): - caller.msg("Couldn't save this callback.") - return False - - if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked: - handler.db.locked.remove((callback["obj"], callback["name"], callback["number"])) - - handler.edit_callback( - callback["obj"], callback["name"], callback["number"], buf, caller, valid=autovalid - ) - return True - - -def _ev_quit(caller): - callback = caller.db._callback - handler = get_event_handler() - if ( - not handler - or not callback - or not all(key in callback for key in ("obj", "name", "number", "valid")) - ): - caller.msg("Couldn't save this callback.") - return False - - if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked: - handler.db.locked.remove((callback["obj"], callback["name"], callback["number"])) - - del caller.db._callback - caller.msg("Exited the code editor.") -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/ingame_python/eventfuncs.html b/docs/0.9.5/_modules/evennia/contrib/ingame_python/eventfuncs.html deleted file mode 100644 index 9527e95ed4..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/ingame_python/eventfuncs.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - evennia.contrib.ingame_python.eventfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.ingame_python.eventfuncs

-"""
-Module defining basic eventfuncs for the event system.
-
-Eventfuncs are just Python functions that can be used inside of calllbacks.
-
-"""
-
-from evennia import ObjectDB, ScriptDB
-from evennia.contrib.ingame_python.utils import InterruptEvent
-
-
-
[docs]def deny(): - """ - Deny, that is stop, the callback here. - - Notes: - This function will raise an exception to terminate the callback - in a controlled way. If you use this function in an event called - prior to a command, the command will be cancelled as well. Good - situations to use the `deny()` function are in events that begins - by `can_`, because they usually can be cancelled as easily as that. - - """ - raise InterruptEvent
- - -
[docs]def get(**kwargs): - """ - Return an object with the given search option or None if None is found. - - Keyword Args: - Any searchable data or property (id, db_key, db_location...). - - Returns: - The object found that meet these criteria for research, or - None if none is found. - - Notes: - This function is very useful to retrieve objects with a specific - ID. You know that room #32 exists, but you don't have it in - the callback variables. Quite simple: - room = get(id=32) - - This function doesn't perform a search on objects, but a direct - search in the database. It's recommended to use it for objects - you know exist, using their IDs or other unique attributes. - Looking for objects by key is possible (use `db_key` as an - argument) but remember several objects can share the same key. - - """ - try: - object = ObjectDB.objects.get(**kwargs) - except ObjectDB.DoesNotExist: - object = None - - return object
- - -
[docs]def call_event(obj, event_name, seconds=0): - """ - Call the specified event in X seconds. - - Args: - obj (Object): the typeclassed object containing the event. - event_name (str): the event name to be called. - seconds (int or float): the number of seconds to wait before calling - the event. - - Notes: - This eventfunc can be used to call other events from inside of an - event in a given time. This will create a pause between events. This - will not freeze the game, and you can expect characters to move - around (unless you prevent them from doing so). - - Variables that are accessible in your event using 'call()' will be - kept and passed on to the event to call. - - Chained callbacks are designed for this very purpose: they - are never called automatically by the game, rather, they need - to be called from inside another event. - - """ - script = type(obj.callbacks).script - if script: - # If seconds is 0, call the event immediately - if seconds == 0: - locals = dict(script.ndb.current_locals) - obj.callbacks.call(event_name, locals=locals) - else: - # Schedule the task - script.set_task(seconds, obj, event_name)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/ingame_python/scripts.html b/docs/0.9.5/_modules/evennia/contrib/ingame_python/scripts.html deleted file mode 100644 index 48932aec75..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/ingame_python/scripts.html +++ /dev/null @@ -1,774 +0,0 @@ - - - - - - - - evennia.contrib.ingame_python.scripts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.ingame_python.scripts

-"""
-Scripts for the in-game Python system.
-"""
-
-from datetime import datetime, timedelta
-from queue import Queue
-import re
-import sys
-import traceback
-
-from django.conf import settings
-from evennia import DefaultObject, DefaultScript, ChannelDB, ScriptDB
-from evennia import logger, ObjectDB
-from evennia.utils.ansi import raw
-from evennia.utils.create import create_channel
-from evennia.utils.dbserialize import dbserialize
-from evennia.utils.utils import all_from_module, delay, pypath_to_realpath
-from evennia.contrib.ingame_python.callbackhandler import CallbackHandler
-from evennia.contrib.ingame_python.utils import get_next_wait, EVENTS, InterruptEvent
-
-# Constants
-RE_LINE_ERROR = re.compile(r'^  File "\<string\>", line (\d+)')
-
-
-
[docs]class EventHandler(DefaultScript): - - """ - The event handler that contains all events in a global script. - - This script shouldn't be created more than once. It contains - event (in a non-persistent attribute) and callbacks (in a - persistent attribute). The script method would help adding, - editing and deleting these events and callbacks. - - """ - -
[docs] def at_script_creation(self): - """Hook called when the script is created.""" - self.key = "event_handler" - self.desc = "Global event handler" - self.persistent = True - - # Permanent data to be stored - self.db.callbacks = {} - self.db.to_valid = [] - self.db.locked = [] - - # Tasks - self.db.tasks = {}
- -
[docs] def at_start(self): - """Set up the event system when starting. - - Note that this hook is called every time the server restarts - (including when it's reloaded). This hook performs the following - tasks: - - - Create temporarily stored events. - - Generate locals (individual events' namespace). - - Load eventfuncs, including user-defined ones. - - Re-schedule tasks that aren't set to fire anymore. - - Effectively connect the handler to the main script. - - """ - self.ndb.events = {} - for typeclass, name, variables, help_text, custom_call, custom_add in EVENTS: - self.add_event(typeclass, name, variables, help_text, custom_call, custom_add) - - # Generate locals - self.ndb.current_locals = {} - self.ndb.fresh_locals = {} - addresses = ["evennia.contrib.ingame_python.eventfuncs"] - addresses.extend(getattr(settings, "EVENTFUNCS_LOCATIONS", ["world.eventfuncs"])) - for address in addresses: - if pypath_to_realpath(address): - self.ndb.fresh_locals.update(all_from_module(address)) - - # Restart the delayed tasks - now = datetime.now() - for task_id, definition in tuple(self.db.tasks.items()): - future, obj, event_name, locals = definition - seconds = (future - now).total_seconds() - if seconds < 0: - seconds = 0 - - delay(seconds, complete_task, task_id) - - # Place the script in the CallbackHandler - from evennia.contrib.ingame_python import typeclasses - - CallbackHandler.script = self - DefaultObject.callbacks = typeclasses.EventObject.callbacks - - # Create the channel if non-existent - try: - self.ndb.channel = ChannelDB.objects.get(db_key="everror") - except ChannelDB.DoesNotExist: - self.ndb.channel = create_channel( - "everror", - desc="Event errors", - locks="control:false();listen:perm(Builders);send:false()", - )
- -
[docs] def get_events(self, obj): - """ - Return a dictionary of events on this object. - - Args: - obj (Object or typeclass): the connected object or a general typeclass. - - Returns: - A dictionary of the object's events. - - Notes: - Events would define what the object can have as - callbacks. Note, however, that chained callbacks will not - appear in events and are handled separately. - - You can also request the events of a typeclass, not a - connected object. This is useful to get the global list - of events for a typeclass that has no object yet. - - """ - events = {} - all_events = self.ndb.events - classes = Queue() - if isinstance(obj, type): - classes.put(obj) - else: - classes.put(type(obj)) - - invalid = [] - while not classes.empty(): - typeclass = classes.get() - typeclass_name = typeclass.__module__ + "." + typeclass.__name__ - for key, etype in all_events.get(typeclass_name, {}).items(): - if key in invalid: - continue - if etype[0] is None: # Invalidate - invalid.append(key) - continue - if key not in events: - events[key] = etype - - # Look for the parent classes - for parent in typeclass.__bases__: - classes.put(parent) - - return events
- -
[docs] def get_variable(self, variable_name): - """ - Return the variable defined in the locals. - - This can be very useful to check the value of a variable that can be modified in an event, and whose value will be used in code. This system allows additional customization. - - Args: - variable_name (str): the name of the variable to return. - - Returns: - The variable if found in the locals. - None if not found in the locals. - - Note: - This will return the variable from the current locals. - Keep in mind that locals are shared between events. As - every event is called one by one, this doesn't pose - additional problems if you get the variable right after - an event has been executed. If, however, you differ, - there's no guarantee the variable will be here or will - mean the same thing. - - """ - return self.ndb.current_locals.get(variable_name)
- -
[docs] def get_callbacks(self, obj): - """ - Return a dictionary of the object's callbacks. - - Args: - obj (Object): the connected objects. - - Returns: - A dictionary of the object's callbacks. - - Note: - This method can be useful to override in some contexts, - when several objects would share callbacks. - - """ - obj_callbacks = self.db.callbacks.get(obj, {}) - callbacks = {} - for callback_name, callback_list in obj_callbacks.items(): - new_list = [] - for i, callback in enumerate(callback_list): - callback = dict(callback) - callback["obj"] = obj - callback["name"] = callback_name - callback["number"] = i - new_list.append(callback) - - if new_list: - callbacks[callback_name] = new_list - - return callbacks
- -
[docs] def add_callback(self, obj, callback_name, code, author=None, valid=False, parameters=""): - """ - Add the specified callback. - - Args: - obj (Object): the Evennia typeclassed object to be extended. - callback_name (str): the name of the callback to add. - code (str): the Python code associated with this callback. - author (Character or Account, optional): the author of the callback. - valid (bool, optional): should the callback be connected? - parameters (str, optional): optional parameters. - - Note: - This method doesn't check that the callback type exists. - - """ - obj_callbacks = self.db.callbacks.get(obj, {}) - if not obj_callbacks: - self.db.callbacks[obj] = {} - obj_callbacks = self.db.callbacks[obj] - - callbacks = obj_callbacks.get(callback_name, []) - if not callbacks: - obj_callbacks[callback_name] = [] - callbacks = obj_callbacks[callback_name] - - # Add the callback in the list - callbacks.append( - { - "created_on": datetime.now(), - "author": author, - "valid": valid, - "code": code, - "parameters": parameters, - } - ) - - # If not valid, set it in 'to_valid' - if not valid: - self.db.to_valid.append((obj, callback_name, len(callbacks) - 1)) - - # Call the custom_add if needed - custom_add = self.get_events(obj).get(callback_name, [None, None, None, None])[3] - if custom_add: - custom_add(obj, callback_name, len(callbacks) - 1, parameters) - - # Build the definition to return (a dictionary) - definition = dict(callbacks[-1]) - definition["obj"] = obj - definition["name"] = callback_name - definition["number"] = len(callbacks) - 1 - return definition
- -
[docs] def edit_callback(self, obj, callback_name, number, code, author=None, valid=False): - """ - Edit the specified callback. - - Args: - obj (Object): the Evennia typeclassed object to be edited. - callback_name (str): the name of the callback to edit. - number (int): the callback number to be changed. - code (str): the Python code associated with this callback. - author (Character or Account, optional): the author of the callback. - valid (bool, optional): should the callback be connected? - - Raises: - RuntimeError if the callback is locked. - - Note: - This method doesn't check that the callback type exists. - - """ - obj_callbacks = self.db.callbacks.get(obj, {}) - if not obj_callbacks: - self.db.callbacks[obj] = {} - obj_callbacks = self.db.callbacks[obj] - - callbacks = obj_callbacks.get(callback_name, []) - if not callbacks: - obj_callbacks[callback_name] = [] - callbacks = obj_callbacks[callback_name] - - # If locked, don't edit it - if (obj, callback_name, number) in self.db.locked: - raise RuntimeError("this callback is locked.") - - # Edit the callback - callbacks[number].update( - {"updated_on": datetime.now(), "updated_by": author, "valid": valid, "code": code} - ) - - # If not valid, set it in 'to_valid' - if not valid and (obj, callback_name, number) not in self.db.to_valid: - self.db.to_valid.append((obj, callback_name, number)) - elif valid and (obj, callback_name, number) in self.db.to_valid: - self.db.to_valid.remove((obj, callback_name, number)) - - # Build the definition to return (a dictionary) - definition = dict(callbacks[number]) - definition["obj"] = obj - definition["name"] = callback_name - definition["number"] = number - return definition
- -
[docs] def del_callback(self, obj, callback_name, number): - """ - Delete the specified callback. - - Args: - obj (Object): the typeclassed object containing the callback. - callback_name (str): the name of the callback to delete. - number (int): the number of the callback to delete. - - Raises: - RuntimeError if the callback is locked. - - """ - obj_callbacks = self.db.callbacks.get(obj, {}) - callbacks = obj_callbacks.get(callback_name, []) - - # If locked, don't edit it - if (obj, callback_name, number) in self.db.locked: - raise RuntimeError("this callback is locked.") - - # Delete the callback itself - try: - code = callbacks[number]["code"] - except IndexError: - return - else: - logger.log_info( - "Deleting callback {} {} of {}:\n{}".format(callback_name, number, obj, code) - ) - del callbacks[number] - - # Change IDs of callbacks to be validated - i = 0 - while i < len(self.db.to_valid): - t_obj, t_callback_name, t_number = self.db.to_valid[i] - if obj is t_obj and callback_name == t_callback_name: - if t_number == number: - # Strictly equal, delete the callback - del self.db.to_valid[i] - i -= 1 - elif t_number > number: - # Change the ID for this callback - self.db.to_valid.insert(i, (t_obj, t_callback_name, t_number - 1)) - del self.db.to_valid[i + 1] - i += 1 - - # Update locked callback - for i, line in enumerate(self.db.locked): - t_obj, t_callback_name, t_number = line - if obj is t_obj and callback_name == t_callback_name: - if number < t_number: - self.db.locked[i] = (t_obj, t_callback_name, t_number - 1) - - # Delete time-related callbacks associated with this object - for script in obj.scripts.all(): - if isinstance(script, TimecallbackScript): - if script.obj is obj and script.db.callback_name == callback_name: - if script.db.number == number: - script.stop() - elif script.db.number > number: - script.db.number -= 1
- -
[docs] def accept_callback(self, obj, callback_name, number): - """ - Valid a callback. - - Args: - obj (Object): the object containing the callback. - callback_name (str): the name of the callback. - number (int): the number of the callback. - - """ - obj_callbacks = self.db.callbacks.get(obj, {}) - callbacks = obj_callbacks.get(callback_name, []) - - # Accept and connect the callback - callbacks[number].update({"valid": True}) - if (obj, callback_name, number) in self.db.to_valid: - self.db.to_valid.remove((obj, callback_name, number))
- -
[docs] def call(self, obj, callback_name, *args, **kwargs): - """ - Call the connected callbacks. - - Args: - obj (Object): the Evennia typeclassed object. - callback_name (str): the callback name to call. - *args: additional variables for this callback. - - Keyword Args: - number (int, optional): call just a specific callback. - parameters (str, optional): call a callback with parameters. - locals (dict, optional): a locals replacement. - - Returns: - True to report the callback was called without interruption, - False otherwise. - - """ - # First, look for the callback type corresponding to this name - number = kwargs.get("number") - parameters = kwargs.get("parameters") - locals = kwargs.get("locals") - - # Errors should not pass silently - allowed = ("number", "parameters", "locals") - if any(k for k in kwargs if k not in allowed): - raise TypeError( - "Unknown keyword arguments were specified " "to call callbacks: {}".format(kwargs) - ) - - event = self.get_events(obj).get(callback_name) - if locals is None and not event: - logger.log_err( - "The callback {} for the object {} (typeclass " - "{}) can't be found".format(callback_name, obj, type(obj)) - ) - return False - - # Prepare the locals if necessary - if locals is None: - locals = self.ndb.fresh_locals.copy() - for i, variable in enumerate(event[0]): - try: - locals[variable] = args[i] - except IndexError: - logger.log_trace( - "callback {} of {} ({}): need variable " - "{} in position {}".format(callback_name, obj, type(obj), variable, i) - ) - return False - else: - locals = {key: value for key, value in locals.items()} - - callbacks = self.get_callbacks(obj).get(callback_name, []) - if event: - custom_call = event[2] - if custom_call: - callbacks = custom_call(callbacks, parameters) - - # Now execute all the valid callbacks linked at this address - self.ndb.current_locals = locals - for i, callback in enumerate(callbacks): - if not callback["valid"]: - continue - - if number is not None and callback["number"] != number: - continue - - try: - exec(callback["code"], locals, locals) - except InterruptEvent: - return False - except Exception: - etype, evalue, tb = sys.exc_info() - trace = traceback.format_exception(etype, evalue, tb) - self.handle_error(callback, trace) - - return True
- -
[docs] def handle_error(self, callback, trace): - """ - Handle an error in a callback. - - Args: - callback (dict): the callback representation. - trace (list): the traceback containing the exception. - - Notes: - This method can be useful to override to change the default - handling of errors. By default, the error message is sent to - the character who last updated the callback, if connected. - If not, display to the everror channel. - - """ - callback_name = callback["name"] - number = callback["number"] - obj = callback["obj"] - oid = obj.id - logger.log_err( - "An error occurred during the callback {} of " - "{} (#{}), number {}\n{}".format(callback_name, obj, oid, number + 1, "\n".join(trace)) - ) - - # Create the error message - line = "|runknown|n" - lineno = "|runknown|n" - for error in trace: - if error.startswith(' File "<string>", line '): - res = RE_LINE_ERROR.search(error) - if res: - lineno = int(res.group(1)) - - # Try to extract the line - try: - line = raw(callback["code"].splitlines()[lineno - 1]) - except IndexError: - continue - else: - break - - exc = raw(trace[-1].strip("\n").splitlines()[-1]) - err_msg = "Error in {} of {} (#{})[{}], line {}:" " {}\n{}".format( - callback_name, obj, oid, number + 1, lineno, line, exc - ) - - # Inform the last updater if connected - updater = callback.get("updated_by") - if updater is None: - updater = callback["created_by"] - - if updater and updater.sessions.all(): - updater.msg(err_msg) - else: - err_msg = "Error in {} of {} (#{})[{}], line {}:" " {}\n {}".format( - callback_name, obj, oid, number + 1, lineno, line, exc - ) - self.ndb.channel.msg(err_msg)
- -
[docs] def add_event(self, typeclass, name, variables, help_text, custom_call, custom_add): - """ - Add a new event for a defined typeclass. - - Args: - typeclass (str): the path leading to the typeclass. - name (str): the name of the event to add. - variables (list of str): list of variable names for this event. - help_text (str): the long help text of the event. - custom_call (callable or None): the function to be called - when the event fires. - custom_add (callable or None): the function to be called when - a callback is added. - - """ - if typeclass not in self.ndb.events: - self.ndb.events[typeclass] = {} - - events = self.ndb.events[typeclass] - if name not in events: - events[name] = (variables, help_text, custom_call, custom_add)
- -
[docs] def set_task(self, seconds, obj, callback_name): - """ - Set and schedule a task to run. - - Args: - seconds (int, float): the delay in seconds from now. - obj (Object): the typecalssed object connected to the event. - callback_name (str): the callback's name. - - Notes: - This method allows to schedule a "persistent" task. - 'utils.delay' is called, but a copy of the task is kept in - the event handler, and when the script restarts (after reload), - the differed delay is called again. - The dictionary of locals is frozen and will be available - again when the task runs. This feature, however, is limited - by the database: all data cannot be saved. Lambda functions, - class methods, objects inside an instance and so on will - not be kept in the locals dictionary. - - """ - now = datetime.now() - delta = timedelta(seconds=seconds) - - # Choose a free task_id - used_ids = list(self.db.tasks.keys()) - task_id = 1 - while task_id in used_ids: - task_id += 1 - - # Collect and freeze current locals - locals = {} - for key, value in self.ndb.current_locals.items(): - try: - dbserialize(value) - except TypeError: - continue - else: - locals[key] = value - - self.db.tasks[task_id] = (now + delta, obj, callback_name, locals) - delay(seconds, complete_task, task_id)
- - -# Script to call time-related events -
[docs]class TimeEventScript(DefaultScript): - - """Gametime-sensitive script.""" - -
[docs] def at_script_creation(self): - """The script is created.""" - self.start_delay = True - self.persistent = True - - # Script attributes - self.db.time_format = None - self.db.event_name = "time" - self.db.number = None
- -
[docs] def at_repeat(self): - """ - Call the event and reset interval. - - It is necessary to restart the script to reset its interval - only twice after a reload. When the script has undergone - down time, there's usually a slight shift in game time. Once - the script restarts once, it will set the average time it - needs for all its future intervals and should not need to be - restarted. In short, a script that is created shouldn't need - to restart more than once, and a script that is reloaded should - restart only twice. - - """ - if self.db.time_format: - # If the 'usual' time is set, use it - seconds = self.ndb.usual - if seconds is None: - seconds, usual, details = get_next_wait(self.db.time_format) - self.ndb.usual = usual - - if self.interval != seconds: - self.restart(interval=seconds) - - if self.db.event_name and self.db.number is not None: - obj = self.obj - if not obj.callbacks: - return - - event_name = self.db.event_name - number = self.db.number - obj.callbacks.call(event_name, obj, number=number)
- - -# Functions to manipulate tasks -
[docs]def complete_task(task_id): - """ - Mark the task in the event handler as complete. - - Args: - task_id (int): the task ID. - - Note: - This function should be called automatically for individual tasks. - - """ - try: - script = ScriptDB.objects.get(db_key="event_handler") - except ScriptDB.DoesNotExist: - logger.log_trace("Can't get the event handler.") - return - - if task_id not in script.db.tasks: - logger.log_err("The task #{} was scheduled, but it cannot be " "found".format(task_id)) - return - - delta, obj, callback_name, locals = script.db.tasks.pop(task_id) - script.call(obj, callback_name, locals=locals)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/ingame_python/tests.html b/docs/0.9.5/_modules/evennia/contrib/ingame_python/tests.html deleted file mode 100644 index 3d971a46ce..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/ingame_python/tests.html +++ /dev/null @@ -1,649 +0,0 @@ - - - - - - - - evennia.contrib.ingame_python.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.ingame_python.tests

-"""
-Module containing the test cases for the in-game Python system.
-"""
-
-from mock import Mock
-from textwrap import dedent
-
-from django.conf import settings
-from evennia import ScriptDB
-from evennia.commands.default.tests import CommandTest
-from evennia.objects.objects import ExitCommand
-from evennia.utils import ansi, utils
-from evennia.utils.create import create_object, create_script
-from evennia.utils.test_resources import EvenniaTest
-from evennia.contrib.ingame_python.commands import CmdCallback
-from evennia.contrib.ingame_python.callbackhandler import CallbackHandler
-
-# Force settings
-settings.EVENTS_CALENDAR = "standard"
-
-# Constants
-OLD_EVENTS = {}
-
-
-
[docs]class TestEventHandler(EvenniaTest): - - """ - Test cases of the event handler to add, edit or delete events. - """ - -
[docs] def setUp(self): - """Create the event handler.""" - super().setUp() - self.handler = create_script("evennia.contrib.ingame_python.scripts.EventHandler") - - # Copy old events if necessary - if OLD_EVENTS: - self.handler.ndb.events = dict(OLD_EVENTS) - - # Alter typeclasses - self.char1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter") - self.char2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter") - self.room1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom") - self.room2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom") - self.exit.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventExit")
- -
[docs] def tearDown(self): - """Stop the event handler.""" - OLD_EVENTS.clear() - OLD_EVENTS.update(self.handler.ndb.events) - self.handler.stop() - CallbackHandler.script = None - super().tearDown()
- -
[docs] def test_start(self): - """Simply make sure the handler runs with proper initial values.""" - self.assertEqual(self.handler.db.callbacks, {}) - self.assertEqual(self.handler.db.to_valid, []) - self.assertEqual(self.handler.db.locked, []) - self.assertEqual(self.handler.db.tasks, {}) - self.assertIsNotNone(self.handler.ndb.events)
- -
[docs] def test_add_validation(self): - """Add a callback while needing validation.""" - author = self.char1 - self.handler.add_callback( - self.room1, "dummy", "character.db.strength = 40", author=author, valid=False - ) - callback = self.handler.get_callbacks(self.room1).get("dummy") - callback = callback[0] - self.assertIsNotNone(callback) - self.assertEqual(callback["author"], author) - self.assertEqual(callback["valid"], False) - - # Since this callback is not valid, it should appear in 'to_valid' - self.assertIn((self.room1, "dummy", 0), self.handler.db.to_valid) - - # Run this dummy callback (shouldn't do anything) - self.char1.db.strength = 10 - locals = {"character": self.char1} - self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals)) - self.assertEqual(self.char1.db.strength, 10)
- -
[docs] def test_edit(self): - """Test editing a callback.""" - author = self.char1 - self.handler.add_callback( - self.room1, "dummy", "character.db.strength = 60", author=author, valid=True - ) - - # Edit it right away - self.handler.edit_callback( - self.room1, "dummy", 0, "character.db.strength = 65", author=self.char2, valid=True - ) - - # Check that the callback was written - callback = self.handler.get_callbacks(self.room1).get("dummy") - callback = callback[0] - self.assertIsNotNone(callback) - self.assertEqual(callback["author"], author) - self.assertEqual(callback["valid"], True) - self.assertEqual(callback["updated_by"], self.char2) - - # Run this dummy callback - self.char1.db.strength = 10 - locals = {"character": self.char1} - self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals)) - self.assertEqual(self.char1.db.strength, 65)
- -
[docs] def test_edit_validation(self): - """Edit a callback when validation isn't automatic.""" - author = self.char1 - self.handler.add_callback( - self.room1, "dummy", "character.db.strength = 70", author=author, valid=True - ) - - # Edit it right away - self.handler.edit_callback( - self.room1, "dummy", 0, "character.db.strength = 80", author=self.char2, valid=False - ) - - # Run this dummy callback (shouldn't do anything) - self.char1.db.strength = 10 - locals = {"character": self.char1} - self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals)) - self.assertEqual(self.char1.db.strength, 10)
- -
[docs] def test_del(self): - """Try to delete a callback.""" - # Add 3 callbacks - self.handler.add_callback( - self.room1, "dummy", "character.db.strength = 5", author=self.char1, valid=True - ) - self.handler.add_callback( - self.room1, "dummy", "character.db.strength = 8", author=self.char2, valid=False - ) - self.handler.add_callback( - self.room1, "dummy", "character.db.strength = 9", author=self.char1, valid=True - ) - - # Note that the second callback isn't valid - self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid) - - # Lock the third callback - self.handler.db.locked.append((self.room1, "dummy", 2)) - - # Delete the first callback - self.handler.del_callback(self.room1, "dummy", 0) - - # The callback #1 that was to valid should be #0 now - self.assertIn((self.room1, "dummy", 0), self.handler.db.to_valid) - self.assertNotIn((self.room1, "dummy", 1), self.handler.db.to_valid) - - # The lock has been updated too - self.assertIn((self.room1, "dummy", 1), self.handler.db.locked) - self.assertNotIn((self.room1, "dummy", 2), self.handler.db.locked) - - # Now delete the first (not valid) callback - self.handler.del_callback(self.room1, "dummy", 0) - self.assertEqual(self.handler.db.to_valid, []) - self.assertIn((self.room1, "dummy", 0), self.handler.db.locked) - self.assertNotIn((self.room1, "dummy", 1), self.handler.db.locked) - - # Call the remaining callback - self.char1.db.strength = 10 - locals = {"character": self.char1} - self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals)) - self.assertEqual(self.char1.db.strength, 9)
- -
[docs] def test_accept(self): - """Accept an callback.""" - # Add 2 callbacks - self.handler.add_callback( - self.room1, "dummy", "character.db.strength = 5", author=self.char1, valid=True - ) - self.handler.add_callback( - self.room1, "dummy", "character.db.strength = 8", author=self.char2, valid=False - ) - - # Note that the second callback isn't valid - self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid) - - # Accept the second callback - self.handler.accept_callback(self.room1, "dummy", 1) - callback = self.handler.get_callbacks(self.room1).get("dummy") - callback = callback[1] - self.assertIsNotNone(callback) - self.assertEqual(callback["valid"], True) - - # Call the dummy callback - self.char1.db.strength = 10 - locals = {"character": self.char1} - self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals)) - self.assertEqual(self.char1.db.strength, 8)
- -
[docs] def test_call(self): - """Test to call amore complex callback.""" - self.char1.key = "one" - self.char2.key = "two" - - # Add an callback - code = dedent( - """ - if character.key == "one": - character.db.health = 50 - else: - character.db.health = 0 - """.strip( - "\n" - ) - ) - self.handler.add_callback(self.room1, "dummy", code, author=self.char1, valid=True) - - # Call the dummy callback - self.assertTrue(self.handler.call(self.room1, "dummy", locals={"character": self.char1})) - self.assertEqual(self.char1.db.health, 50) - self.assertTrue(self.handler.call(self.room1, "dummy", locals={"character": self.char2})) - self.assertEqual(self.char2.db.health, 0)
- -
[docs] def test_handler(self): - """Test the object handler.""" - self.assertIsNotNone(self.char1.callbacks) - - # Add an callback - callback = self.room1.callbacks.add("dummy", "pass", author=self.char1, valid=True) - self.assertEqual(callback.obj, self.room1) - self.assertEqual(callback.name, "dummy") - self.assertEqual(callback.code, "pass") - self.assertEqual(callback.author, self.char1) - self.assertEqual(callback.valid, True) - self.assertIn([callback], list(self.room1.callbacks.all().values())) - - # Edit this very callback - new = self.room1.callbacks.edit( - "dummy", 0, "character.db.say = True", author=self.char1, valid=True - ) - self.assertIn([new], list(self.room1.callbacks.all().values())) - self.assertNotIn([callback], list(self.room1.callbacks.all().values())) - - # Try to call this callback - self.assertTrue(self.room1.callbacks.call("dummy", locals={"character": self.char2})) - self.assertTrue(self.char2.db.say) - - # Delete the callback - self.room1.callbacks.remove("dummy", 0) - self.assertEqual(self.room1.callbacks.all(), {})
- - -
[docs]class TestCmdCallback(CommandTest): - - """Test the @callback command.""" - -
[docs] def setUp(self): - """Create the callback handler.""" - super().setUp() - self.handler = create_script("evennia.contrib.ingame_python.scripts.EventHandler") - - # Copy old events if necessary - if OLD_EVENTS: - self.handler.ndb.events = dict(OLD_EVENTS) - - # Alter typeclasses - self.char1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter") - self.char2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter") - self.room1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom") - self.room2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom") - self.exit.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventExit")
- -
[docs] def tearDown(self): - """Stop the callback handler.""" - OLD_EVENTS.clear() - OLD_EVENTS.update(self.handler.ndb.events) - self.handler.stop() - for script in ScriptDB.objects.filter( - db_typeclass_path="evennia.contrib.ingame_python.scripts.TimeEventScript" - ): - script.stop() - - CallbackHandler.script = None - super().tearDown()
- -
[docs] def test_list(self): - """Test listing callbacks with different rights.""" - table = self.call(CmdCallback(), "out") - lines = table.splitlines()[3:-1] - self.assertNotEqual(lines, []) - - # Check that the second column only contains 0 (0) (no callback yet) - for line in lines: - cols = line.split("|") - self.assertIn(cols[2].strip(), ("0 (0)", "")) - - # Add some callback - self.handler.add_callback(self.exit, "traverse", "pass", author=self.char1, valid=True) - - # Try to obtain more details on a specific callback on exit - table = self.call(CmdCallback(), "out = traverse") - lines = table.splitlines()[3:-1] - self.assertEqual(len(lines), 1) - line = lines[0] - cols = line.split("|") - self.assertIn(cols[1].strip(), ("1", "")) - self.assertIn(cols[2].strip(), (str(self.char1), "")) - self.assertIn(cols[-1].strip(), ("Yes", "No", "")) - - # Run the same command with char2 - # char2 shouldn't see the last column (Valid) - table = self.call(CmdCallback(), "out = traverse", caller=self.char2) - lines = table.splitlines()[3:-1] - self.assertEqual(len(lines), 1) - line = lines[0] - cols = line.split("|") - self.assertEqual(cols[1].strip(), "1") - self.assertNotIn(cols[-1].strip(), ("Yes", "No")) - - # In any case, display the callback - # The last line should be "pass" (the callback code) - details = self.call(CmdCallback(), "out = traverse 1") - self.assertEqual(details.splitlines()[-1], "pass")
- -
[docs] def test_add(self): - """Test to add an callback.""" - self.call(CmdCallback(), "/add out = traverse") - editor = self.char1.ndb._eveditor - self.assertIsNotNone(editor) - - # Edit the callback - editor.update_buffer( - dedent( - """ - if character.key == "one": - character.msg("You can pass.") - else: - character.msg("You can't pass.") - deny() - """.strip( - "\n" - ) - ) - ) - editor.save_buffer() - editor.quit() - callback = self.exit.callbacks.get("traverse")[0] - self.assertEqual(callback.author, self.char1) - self.assertEqual(callback.valid, True) - self.assertTrue(len(callback.code) > 0) - - # We're going to try the same thing but with char2 - # char2 being a player for our test, the callback won't be validated. - self.call(CmdCallback(), "/add out = traverse", caller=self.char2) - editor = self.char2.ndb._eveditor - self.assertIsNotNone(editor) - - # Edit the callback - editor.update_buffer( - dedent( - """ - character.msg("No way.") - """.strip( - "\n" - ) - ) - ) - editor.save_buffer() - editor.quit() - callback = self.exit.callbacks.get("traverse")[1] - self.assertEqual(callback.author, self.char2) - self.assertEqual(callback.valid, False) - self.assertTrue(len(callback.code) > 0)
- -
[docs] def test_del(self): - """Add and remove an callback.""" - self.handler.add_callback(self.exit, "traverse", "pass", author=self.char1, valid=True) - - # Try to delete the callback - # char2 shouldn't be allowed to do so (that's not HIS callback) - self.call(CmdCallback(), "/del out = traverse 1", caller=self.char2) - self.assertTrue(len(self.handler.get_callbacks(self.exit).get("traverse", [])) == 1) - - # Now, char1 should be allowed to delete it - self.call(CmdCallback(), "/del out = traverse 1") - self.assertTrue(len(self.handler.get_callbacks(self.exit).get("traverse", [])) == 0)
- -
[docs] def test_lock(self): - """Test the lock of multiple editing.""" - self.call(CmdCallback(), "/add here = time 8:00", caller=self.char2) - self.assertIsNotNone(self.char2.ndb._eveditor) - - # Now ask char1 to edit - line = self.call(CmdCallback(), "/edit here = time 1") - self.assertIsNone(self.char1.ndb._eveditor) - - # Try to delete this callback while char2 is editing it - line = self.call(CmdCallback(), "/del here = time 1")
- -
[docs] def test_accept(self): - """Accept an callback.""" - self.call(CmdCallback(), "/add here = time 8:00", caller=self.char2) - editor = self.char2.ndb._eveditor - self.assertIsNotNone(editor) - - # Edit the callback - editor.update_buffer( - dedent( - """ - room.msg_contents("It's 8 PM, everybody up!") - """.strip( - "\n" - ) - ) - ) - editor.save_buffer() - editor.quit() - callback = self.room1.callbacks.get("time")[0] - self.assertEqual(callback.valid, False) - - # chars shouldn't be allowed to the callback - self.call(CmdCallback(), "/accept here = time 1", caller=self.char2) - callback = self.room1.callbacks.get("time")[0] - self.assertEqual(callback.valid, False) - - # char1 will accept the callback - self.call(CmdCallback(), "/accept here = time 1") - callback = self.room1.callbacks.get("time")[0] - self.assertEqual(callback.valid, True)
- - -
[docs]class TestDefaultCallbacks(CommandTest): - - """Test the default callbacks.""" - -
[docs] def setUp(self): - """Create the callback handler.""" - super().setUp() - self.handler = create_script("evennia.contrib.ingame_python.scripts.EventHandler") - - # Copy old events if necessary - if OLD_EVENTS: - self.handler.ndb.events = dict(OLD_EVENTS) - - # Alter typeclasses - self.char1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter") - self.char2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter") - self.room1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom") - self.room2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom") - self.exit.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventExit")
- -
[docs] def tearDown(self): - """Stop the callback handler.""" - OLD_EVENTS.clear() - OLD_EVENTS.update(self.handler.ndb.events) - self.handler.stop() - CallbackHandler.script = None - super().tearDown()
- -
[docs] def test_exit(self): - """Test the callbacks of an exit.""" - self.char1.key = "char1" - code = dedent( - """ - if character.key == "char1": - character.msg("You can leave.") - else: - character.msg("You cannot leave.") - deny() - """.strip( - "\n" - ) - ) - # Enforce self.exit.destination since swapping typeclass lose it - self.exit.destination = self.room2 - - # Try the can_traverse callback - self.handler.add_callback(self.exit, "can_traverse", code, author=self.char1, valid=True) - - # Have char1 move through the exit - self.call(ExitCommand(), "", "You can leave.", obj=self.exit) - self.assertIs(self.char1.location, self.room2) - - # Have char2 move through this exit - self.call(ExitCommand(), "", "You cannot leave.", obj=self.exit, caller=self.char2) - self.assertIs(self.char2.location, self.room1) - - # Try the traverse callback - self.handler.del_callback(self.exit, "can_traverse", 0) - self.handler.add_callback( - self.exit, "traverse", "character.msg('Fine!')", author=self.char1, valid=True - ) - - # Have char2 move through the exit - self.call(ExitCommand(), "", obj=self.exit, caller=self.char2) - self.assertIs(self.char2.location, self.room2) - self.handler.del_callback(self.exit, "traverse", 0) - - # Move char1 and char2 back - self.char1.location = self.room1 - self.char2.location = self.room1 - - # Test msg_arrive and msg_leave - code = 'message = "{character} goes out."' - self.handler.add_callback(self.exit, "msg_leave", code, author=self.char1, valid=True) - - # Have char1 move through the exit - old_msg = self.char2.msg - try: - self.char2.msg = Mock() - self.call(ExitCommand(), "", obj=self.exit) - stored_msg = [ - args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs)) - for name, args, kwargs in self.char2.msg.mock_calls - ] - # Get the first element of a tuple if msg received a tuple instead of a string - stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg] - returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True) - self.assertEqual(returned_msg, "char1 goes out.") - finally: - self.char2.msg = old_msg - - # Create a return exit - back = create_object( - "evennia.objects.objects.DefaultExit", - key="in", - location=self.room2, - destination=self.room1, - ) - code = 'message = "{character} goes in."' - self.handler.add_callback(self.exit, "msg_arrive", code, author=self.char1, valid=True) - - # Have char1 move through the exit - old_msg = self.char2.msg - try: - self.char2.msg = Mock() - self.call(ExitCommand(), "", obj=back) - stored_msg = [ - args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs)) - for name, args, kwargs in self.char2.msg.mock_calls - ] - # Get the first element of a tuple if msg received a tuple instead of a string - stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg] - returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True) - self.assertEqual(returned_msg, "char1 goes in.") - finally: - self.char2.msg = old_msg
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/ingame_python/utils.html b/docs/0.9.5/_modules/evennia/contrib/ingame_python/utils.html deleted file mode 100644 index fc9a023528..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/ingame_python/utils.html +++ /dev/null @@ -1,369 +0,0 @@ - - - - - - - - evennia.contrib.ingame_python.utils — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.ingame_python.utils

-"""
-Functions to extend the event system.
-
-These functions are to be used by developers to customize events and callbacks.
-
-"""
-
-from textwrap import dedent
-
-from django.conf import settings
-from evennia import logger
-from evennia import ScriptDB
-from evennia.utils.create import create_script
-from evennia.utils.gametime import real_seconds_until as standard_rsu
-from evennia.utils.utils import class_from_module
-from evennia.contrib.custom_gametime import UNITS
-from evennia.contrib.custom_gametime import gametime_to_realtime
-from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu
-
-# Temporary storage for events waiting for the script to be started
-EVENTS = []
-
-
-
[docs]def get_event_handler(): - """Return the event handler or None.""" - try: - script = ScriptDB.objects.get(db_key="event_handler") - except ScriptDB.DoesNotExist: - logger.log_trace("Can't get the event handler.") - script = None - - return script
- - -
[docs]def register_events(path_or_typeclass): - """ - Register the events in this typeclass. - - Args: - path_or_typeclass (str or type): the Python path leading to the - class containing events, or the class itself. - - Returns: - The typeclass itself. - - Notes: - This function will read events from the `_events` class variable - defined in the typeclass given in parameters. It will add - the events, either to the script if it exists, or to some - temporary storage, waiting for the script to be initialized. - - """ - if isinstance(path_or_typeclass, str): - typeclass = class_from_module(path_or_typeclass) - else: - typeclass = path_or_typeclass - - typeclass_name = typeclass.__module__ + "." + typeclass.__name__ - try: - storage = ScriptDB.objects.get(db_key="event_handler") - assert storage.is_active - assert storage.ndb.events is not None - except (ScriptDB.DoesNotExist, AssertionError): - storage = EVENTS - - # If the script is started, add the event directly. - # Otherwise, add it to the temporary storage. - for name, tup in getattr(typeclass, "_events", {}).items(): - if len(tup) == 4: - variables, help_text, custom_call, custom_add = tup - elif len(tup) == 3: - variables, help_text, custom_call = tup - custom_add = None - elif len(tup) == 2: - variables, help_text = tup - custom_call = None - custom_add = None - else: - variables = help_text = custom_call = custom_add = None - - if isinstance(storage, list): - storage.append((typeclass_name, name, variables, help_text, custom_call, custom_add)) - else: - storage.add_event(typeclass_name, name, variables, help_text, custom_call, custom_add) - - return typeclass
- - -# Custom callbacks for specific event types - - -
[docs]def get_next_wait(format): - """ - Get the length of time in seconds before format. - - Args: - format (str): a time format matching the set calendar. - - Returns: - until (int or float): the number of seconds until the event. - usual (int or float): the usual number of seconds between events. - format (str): a string format representing the time. - - Notes: - The time format could be something like "2018-01-08 12:00". The - number of units set in the calendar affects the way seconds are - calculated. - - """ - calendar = getattr(settings, "EVENTS_CALENDAR", None) - if calendar is None: - logger.log_err( - "A time-related event has been set whereas " - "the gametime calendar has not been set in the settings." - ) - return - elif calendar == "standard": - rsu = standard_rsu - units = ["min", "hour", "day", "month", "year"] - elif calendar == "custom": - rsu = custom_rsu - back = dict([(value, name) for name, value in UNITS.items()]) - sorted_units = sorted(back.items()) - del sorted_units[0] - units = [n for v, n in sorted_units] - - params = {} - for delimiter in ("-", ":"): - format = format.replace(delimiter, " ") - - pieces = list(reversed(format.split())) - details = [] - i = 0 - for uname in units: - try: - piece = pieces[i] - except IndexError: - break - - if not piece.isdigit(): - logger.log_trace( - "The time specified '{}' in {} isn't " "a valid number".format(piece, format) - ) - return - - # Convert the piece to int - piece = int(piece) - params[uname] = piece - details.append("{}={}".format(uname, piece)) - if i < len(units): - next_unit = units[i + 1] - else: - next_unit = None - i += 1 - - params["sec"] = 0 - details = " ".join(details) - until = rsu(**params) - usual = -1 - if next_unit: - kwargs = {next_unit: 1} - usual = gametime_to_realtime(**kwargs) - return until, usual, details
- - -
[docs]def time_event(obj, event_name, number, parameters): - """ - Create a time-related event. - - Args: - obj (Object): the object on which sits the event. - event_name (str): the event's name. - number (int): the number of the event. - parameters (str): the parameter of the event. - - """ - seconds, usual, key = get_next_wait(parameters) - script = create_script( - "evennia.contrib.ingame_python.scripts.TimeEventScript", interval=seconds, obj=obj - ) - script.key = key - script.desc = "event on {}".format(key) - script.db.time_format = parameters - script.db.number = number - script.ndb.usual = usual
- - -
[docs]def keyword_event(callbacks, parameters): - """ - Custom call for events with keywords (like push, or pull, or turn...). - - Args: - callbacks (list of dict): the list of callbacks to be called. - parameters (str): the actual parameters entered to trigger the callback. - - Returns: - A list containing the callback dictionaries to be called. - - Notes: - This function should be imported and added as a custom_call - parameter to add the event when the event supports keywords - as parameters. Keywords in parameters are one or more words - separated by a comma. For instance, a 'push 1, one' callback can - be set to trigger when the player 'push 1' or 'push one'. - - """ - key = parameters.strip().lower() - to_call = [] - for callback in callbacks: - keys = callback["parameters"] - if not keys or key in [p.strip().lower() for p in keys.split(",")]: - to_call.append(callback) - - return to_call
- - -
[docs]def phrase_event(callbacks, parameters): - """ - Custom call for events with keywords in sentences (like say or whisper). - - Args: - callbacks (list of dict): the list of callbacks to be called. - parameters (str): the actual parameters entered to trigger the callback. - - Returns: - A list containing the callback dictionaries to be called. - - Notes: - This function should be imported and added as a custom_call - parameter to add the event when the event supports keywords - in phrases as parameters. Keywords in parameters are one or more - words separated by a comma. For instance, a 'say yes, okay' callback - can be set to trigger when the player says something containing - either "yes" or "okay" (maybe 'say I don't like it, but okay'). - - """ - phrase = parameters.strip().lower() - # Remove punctuation marks - punctuations = ',.";?!' - for p in punctuations: - phrase = phrase.replace(p, " ") - words = phrase.split() - words = [w.strip("' ") for w in words if w.strip("' ")] - to_call = [] - for callback in callbacks: - keys = callback["parameters"] - if not keys or any(key.strip().lower() in words for key in keys.split(",")): - to_call.append(callback) - - return to_call
- - -
[docs]class InterruptEvent(RuntimeError): - - """ - Interrupt the current event. - - You shouldn't have to use this exception directly, probably use the - `deny()` function that handles it instead. - - """ - - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/mail.html b/docs/0.9.5/_modules/evennia/contrib/mail.html deleted file mode 100644 index 478421953c..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/mail.html +++ /dev/null @@ -1,465 +0,0 @@ - - - - - - - - evennia.contrib.mail — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.mail

-"""
-In-Game Mail system
-
-Evennia Contribution - grungies1138 2016
-
-A simple Brandymail style @mail system that uses the Msg class from Evennia
-Core. It has two Commands, both of which can be used on their own:
-   - CmdMail - this should sit on the Account cmdset and makes the @mail command
-    available both IC and OOC. Mails will always go to Accounts (other players).
-   - CmdMailCharacter - this should sit on the Character cmdset and makes the @mail
-    command ONLY available when puppeting a character. Mails will be sent to other
-    Characters only and will not be available when OOC.
-   - If adding *both* commands to their respective cmdsets, you'll get two separate
-    IC and OOC mailing systems, with different lists of mail for IC and OOC modes.
-
-Installation:
-
-Install one or both of the following (see above):
-
-- CmdMail (IC + OOC mail, sent between players)
-
-    # mygame/commands/default_cmds.py
-
-    from evennia.contrib import mail
-
-    # in AccountCmdSet.at_cmdset_creation:
-        self.add(mail.CmdMail())
-
-- CmdMailCharacter (optional, IC only mail, sent between characters)
-
-    # mygame/commands/default_cmds.py
-
-    from evennia.contrib import mail
-
-    # in CharacterCmdSet.at_cmdset_creation:
-        self.add(mail.CmdMailCharacter())
-
-Once installed, use `help mail` in game for help with the mail command. Use
-@ic/@ooc to switch in and out of IC/OOC modes.
-
-"""
-
-import re
-from evennia import ObjectDB, AccountDB
-from evennia import default_cmds
-from evennia.utils import create, evtable, make_iter, inherits_from, datetime_format
-from evennia.comms.models import Msg
-
-
-_HEAD_CHAR = "|015-|n"
-_SUB_HEAD_CHAR = "-"
-_WIDTH = 78
-
-
-
[docs]class CmdMail(default_cmds.MuxAccountCommand): - """ - Communicate with others by sending mail. - - Usage: - @mail - Displays all the mail an account has in their mailbox - @mail <#> - Displays a specific message - @mail <accounts>=<subject>/<message> - - Sends a message to the comma separated list of accounts. - @mail/delete <#> - Deletes a specific message - @mail/forward <account list>=<#>[/<Message>] - - Forwards an existing message to the specified list of accounts, - original message is delivered with optional Message prepended. - @mail/reply <#>=<message> - - Replies to a message #. Prepends message to the original - message text. - Switches: - delete - deletes a message - forward - forward a received message to another object with an optional message attached. - reply - Replies to a received message, appending the original message to the bottom. - Examples: - @mail 2 - @mail Griatch=New mail/Hey man, I am sending you a message! - @mail/delete 6 - @mail/forward feend78 Griatch=4/You guys should read this. - @mail/reply 9=Thanks for the info! - - """ - - key = "@mail" - aliases = ["mail"] - lock = "cmd:all()" - help_category = "General" - -
[docs] def parse(self): - """ - Add convenience check to know if caller is an Account or not since this cmd - will be able to add to either Object- or Account level. - - """ - super().parse() - self.caller_is_account = bool( - inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount") - )
- -
[docs] def search_targets(self, namelist): - """ - Search a list of targets of the same type as caller. - - Args: - caller (Object or Account): The type of object to search. - namelist (list): List of strings for objects to search for. - - Returns: - targetlist (Queryset): Any target matches. - - """ - nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist)) - if self.caller_is_account: - matches = AccountDB.objects.filter(username__iregex=nameregex) - else: - matches = ObjectDB.objects.filter(db_key__iregex=nameregex) - return matches
- -
[docs] def get_all_mail(self): - """ - Returns a list of all the messages where the caller is a recipient. These - are all messages tagged with tags of the `mail` category. - - Returns: - messages (QuerySet): Matching Msg objects. - - """ - if self.caller_is_account: - return Msg.objects.get_by_tag(category="mail").filter(db_receivers_accounts=self.caller) - else: - return Msg.objects.get_by_tag(category="mail").filter(db_receivers_objects=self.caller)
- -
[docs] def send_mail(self, recipients, subject, message, caller): - """ - Function for sending new mail. Also useful for sending notifications - from objects or systems. - - Args: - recipients (list): list of Account or Character objects to receive - the newly created mails. - subject (str): The header or subject of the message to be delivered. - message (str): The body of the message being sent. - caller (obj): The object (or Account or Character) that is sending the message. - - """ - for recipient in recipients: - recipient.msg("You have received a new @mail from %s" % caller) - new_message = create.create_message( - self.caller, message, receivers=recipient, header=subject - ) - new_message.tags.add("new", category="mail") - - if recipients: - caller.msg("You sent your message.") - return - else: - caller.msg("No valid target(s) found. Cannot send message.") - return
- -
[docs] def func(self): - """ - Do the main command functionality - """ - - subject = "" - body = "" - - if self.switches or self.args: - if "delete" in self.switches or "del" in self.switches: - try: - if not self.lhs: - self.caller.msg("No Message ID given. Unable to delete.") - return - else: - all_mail = self.get_all_mail() - mind_max = max(0, all_mail.count() - 1) - mind = max(0, min(mind_max, int(self.lhs) - 1)) - if all_mail[mind]: - mail = all_mail[mind] - question = "Delete message {} ({}) [Y]/N?".format(mind + 1, mail.header) - ret = yield (question) - # handle not ret, it will be None during unit testing - if not ret or ret.strip().upper() not in ("N", "No"): - all_mail[mind].delete() - self.caller.msg("Message %s deleted" % (mind + 1,)) - else: - self.caller.msg("Message not deleted.") - else: - raise IndexError - except IndexError: - self.caller.msg("That message does not exist.") - except ValueError: - self.caller.msg("Usage: @mail/delete <message ID>") - elif "forward" in self.switches or "fwd" in self.switches: - try: - if not self.rhs: - self.caller.msg( - "Cannot forward a message without a target list. " "Please try again." - ) - return - elif not self.lhs: - self.caller.msg("You must define a message to forward.") - return - else: - all_mail = self.get_all_mail() - mind_max = max(0, all_mail.count() - 1) - if "/" in self.rhs: - message_number, message = self.rhs.split("/", 1) - mind = max(0, min(mind_max, int(message_number) - 1)) - - if all_mail[mind]: - old_message = all_mail[mind] - - self.send_mail( - self.search_targets(self.lhslist), - "FWD: " + old_message.header, - message - + "\n---- Original Message ----\n" - + old_message.message, - self.caller, - ) - self.caller.msg("Message forwarded.") - else: - raise IndexError - else: - mind = max(0, min(mind_max, int(self.rhs) - 1)) - if all_mail[mind]: - old_message = all_mail[mind] - self.send_mail( - self.search_targets(self.lhslist), - "FWD: " + old_message.header, - "\n---- Original Message ----\n" + old_message.message, - self.caller, - ) - self.caller.msg("Message forwarded.") - old_message.tags.remove("new", category="mail") - old_message.tags.add("fwd", category="mail") - else: - raise IndexError - except IndexError: - self.caller.msg("Message does not exist.") - except ValueError: - self.caller.msg("Usage: @mail/forward <account list>=<#>[/<Message>]") - elif "reply" in self.switches or "rep" in self.switches: - try: - if not self.rhs: - self.caller.msg("You must define a message to reply to.") - return - elif not self.lhs: - self.caller.msg("You must supply a reply message") - return - else: - all_mail = self.get_all_mail() - mind_max = max(0, all_mail.count() - 1) - mind = max(0, min(mind_max, int(self.lhs) - 1)) - if all_mail[mind]: - old_message = all_mail[mind] - self.send_mail( - old_message.senders, - "RE: " + old_message.header, - self.rhs + "\n---- Original Message ----\n" + old_message.message, - self.caller, - ) - old_message.tags.remove("new", category="mail") - old_message.tags.add("-", category="mail") - return - else: - raise IndexError - except IndexError: - self.caller.msg("Message does not exist.") - except ValueError: - self.caller.msg("Usage: @mail/reply <#>=<message>") - else: - # normal send - if self.rhs: - if "/" in self.rhs: - subject, body = self.rhs.split("/", 1) - else: - body = self.rhs - self.send_mail(self.search_targets(self.lhslist), subject, body, self.caller) - else: - all_mail = self.get_all_mail() - mind_max = max(0, all_mail.count() - 1) - try: - mind = max(0, min(mind_max, int(self.lhs) - 1)) - message = all_mail[mind] - except (ValueError, IndexError): - self.caller.msg("'%s' is not a valid mail id." % self.lhs) - return - - messageForm = [] - if message: - messageForm.append(_HEAD_CHAR * _WIDTH) - messageForm.append( - "|wFrom:|n %s" % (message.senders[0].get_display_name(self.caller)) - ) - # note that we cannot use %-d format here since Windows does not support it - day = message.db_date_created.day - messageForm.append( - "|wSent:|n %s" - % message.db_date_created.strftime(f"%b {day}, %Y - %H:%M:%S") - ) - messageForm.append("|wSubject:|n %s" % message.header) - messageForm.append(_SUB_HEAD_CHAR * _WIDTH) - messageForm.append(message.message) - messageForm.append(_HEAD_CHAR * _WIDTH) - self.caller.msg("\n".join(messageForm)) - message.tags.remove("new", category="mail") - message.tags.add("-", category="mail") - - else: - # list messages - messages = self.get_all_mail() - - if messages: - table = evtable.EvTable( - "|wID|n", - "|wFrom|n", - "|wSubject|n", - "|wArrived|n", - "", - table=None, - border="header", - header_line_char=_SUB_HEAD_CHAR, - width=_WIDTH, - ) - index = 1 - for message in messages: - status = str(message.db_tags.last().db_key.upper()) - if status == "NEW": - status = "|gNEW|n" - - table.add_row( - index, - message.senders[0].get_display_name(self.caller), - message.header, - datetime_format(message.db_date_created), - status, - ) - index += 1 - - table.reformat_column(0, width=6) - table.reformat_column(1, width=18) - table.reformat_column(2, width=34) - table.reformat_column(3, width=13) - table.reformat_column(4, width=7) - - self.caller.msg(_HEAD_CHAR * _WIDTH) - self.caller.msg(str(table)) - self.caller.msg(_HEAD_CHAR * _WIDTH) - else: - self.caller.msg("There are no messages in your inbox.")
- - -# character - level version of the command - - -
[docs]class CmdMailCharacter(CmdMail): - account_caller = False
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/multidescer.html b/docs/0.9.5/_modules/evennia/contrib/multidescer.html deleted file mode 100644 index 31f632c6d4..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/multidescer.html +++ /dev/null @@ -1,376 +0,0 @@ - - - - - - - - evennia.contrib.multidescer — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.multidescer

-"""
-Evennia Mutltidescer
-
-Contrib - Griatch 2016
-
-A "multidescer" is a concept from the MUSH world. It allows for
-creating, managing and switching between multiple character
-descriptions. This multidescer will not require any changes to the
-Character class, rather it will use the `multidescs` Attribute (a
-list) and create it if it does not exist.
-
-This contrib also works well together with the rpsystem contrib (which
-also adds the short descriptions and the `sdesc` command).
-
-
-Installation:
-
-Edit `mygame/commands/default_cmdsets.py` and add
-`from evennia.contrib.multidescer import CmdMultiDesc` to the top.
-
-Next, look up the `at_cmdset_create` method of the `CharacterCmdSet`
-class and add a line `self.add(CmdMultiDesc())` to the end
-of it.
-
-Reload the server and you should have the +desc command available (it
-will replace the default `desc` command).
-
-"""
-import re
-from evennia import default_cmds
-from evennia.utils.utils import crop
-from evennia.utils.eveditor import EvEditor
-
-
-# regex for the set functionality
-_RE_KEYS = re.compile(r"([\w\s]+)(?:\+*?)", re.U + re.I)
-
-
-# Helper functions for the Command
-
-
-
[docs]class DescValidateError(ValueError): - "Used for tracebacks from desc systems" - pass
- - -def _update_store(caller, key=None, desc=None, delete=False, swapkey=None): - """ - Helper function for updating the database store. - - Args: - caller (Object): The caller of the command. - key (str): Description identifier - desc (str): Description text. - delete (bool): Delete given key. - swapkey (str): Swap list positions of `key` and this key. - - """ - if not caller.db.multidesc: - # initialize the multidesc attribute - caller.db.multidesc = [("caller", caller.db.desc or "")] - if not key: - return - lokey = key.lower() - match = [ind for ind, tup in enumerate(caller.db.multidesc) if tup[0] == lokey] - if match: - idesc = match[0] - if delete: - # delete entry - del caller.db.multidesc[idesc] - elif swapkey: - # swap positions - loswapkey = swapkey.lower() - swapmatch = [ind for ind, tup in enumerate(caller.db.multidesc) if tup[0] == loswapkey] - if swapmatch: - iswap = swapmatch[0] - if idesc == iswap: - raise DescValidateError("Swapping a key with itself does nothing.") - temp = caller.db.multidesc[idesc] - caller.db.multidesc[idesc] = caller.db.multidesc[iswap] - caller.db.multidesc[iswap] = temp - else: - raise DescValidateError("Description key '|w%s|n' not found." % swapkey) - elif desc: - # update in-place - caller.db.multidesc[idesc] = (lokey, desc) - else: - raise DescValidateError("No description was set.") - else: - # no matching key - if delete or swapkey: - raise DescValidateError("Description key '|w%s|n' not found." % key) - elif desc: - # insert new at the top of the stack - caller.db.multidesc.insert(0, (lokey, desc)) - else: - raise DescValidateError("No description was set.") - - -# eveditor save/load/quit functions - - -def _save_editor(caller, buffer): - "Called when the editor saves its contents" - key = caller.db._multidesc_editkey - _update_store(caller, key, buffer) - caller.msg("Saved description to key '%s'." % key) - return True - - -def _load_editor(caller): - "Called when the editor loads contents" - key = caller.db._multidesc_editkey - match = [ind for ind, tup in enumerate(caller.db.multidesc) if tup[0] == key] - if match: - return caller.db.multidesc[match[0]][1] - return "" - - -def _quit_editor(caller): - "Called when the editor quits" - del caller.db._multidesc_editkey - caller.msg("Exited editor.") - - -# The actual command class - - -
[docs]class CmdMultiDesc(default_cmds.MuxCommand): - """ - Manage multiple descriptions - - Usage: - +desc [key] - show current desc desc with <key> - +desc <key> = <text> - add/replace desc with <key> - +desc/list - list descriptions (abbreviated) - +desc/list/full - list descriptions (full texts) - +desc/edit <key> - add/edit desc <key> in line editor - +desc/del <key> - delete desc <key> - +desc/swap <key1>-<key2> - swap positions of <key1> and <key2> in list - +desc/set <key> [+key+...] - set desc as default or combine multiple descs - - Notes: - When combining multiple descs with +desc/set <key> + <key2> + ..., - any keys not matching an actual description will be inserted - as plain text. Use e.g. ansi line break ||/ to add a new - paragraph and + + or ansi space ||_ to add extra whitespace. - - """ - - key = "+desc" - aliases = ["desc"] - locks = "cmd:all()" - help_category = "General" - -
[docs] def func(self): - """ - Implements the multidescer. We will use `db.desc` for the - description in use and `db.multidesc` to store all descriptions. - """ - - caller = self.caller - args = self.args.strip() - switches = self.switches - - try: - if "list" in switches or "all" in switches: - # list all stored descriptions, either in full or cropped. - # Note that we list starting from 1, not from 0. - _update_store(caller) - do_crop = "full" not in switches - if do_crop: - outtext = [ - "|w%s:|n %s" % (key, crop(desc)) for key, desc in caller.db.multidesc - ] - else: - outtext = [ - "\n|w%s:|n|n\n%s\n%s" % (key, "-" * (len(key) + 1), desc) - for key, desc in caller.db.multidesc - ] - - caller.msg("|wStored descs:|n\n" + "\n".join(outtext)) - return - - elif "edit" in switches: - # Use the eveditor to edit/create the named description - if not args: - caller.msg("Usage: %s/edit key" % self.key) - return - - # this is used by the editor to know what to edit; it's deleted automatically - caller.db._multidesc_editkey = args - # start the editor - EvEditor( - caller, - loadfunc=_load_editor, - savefunc=_save_editor, - quitfunc=_quit_editor, - key="multidesc editor", - persistent=True, - ) - - elif "delete" in switches or "del" in switches: - # delete a multidesc entry. - if not args: - caller.msg("Usage: %s/delete key" % self.key) - return - _update_store(caller, args, delete=True) - caller.msg("Deleted description with key '%s'." % args) - - elif "swap" in switches or "switch" in switches or "reorder" in switches: - # Reorder list by swapping two entries. We expect numbers starting from 1 - keys = [arg for arg in args.split("-", 1)] - if not len(keys) == 2: - caller.msg("Usage: %s/swap key1-key2" % self.key) - return - key1, key2 = keys - # perform the swap - _update_store(caller, key1, swapkey=key2) - caller.msg("Swapped descs '%s' and '%s'." % (key1, key2)) - - elif "set" in switches: - # switches one (or more) of the multidescs to be the "active" description - _update_store(caller) - if not args: - caller.msg("Usage: %s/set key [+ key2 + key3 + ...]" % self.key) - return - new_desc = [] - multidesc = caller.db.multidesc - for key in args.split("+"): - notfound = True - lokey = key.strip().lower() - for mkey, desc in multidesc: - if lokey == mkey: - new_desc.append(desc) - notfound = False - continue - if notfound: - # if we get here, there is no desc match, we add it as a normal string - new_desc.append(key) - new_desc = "".join(new_desc) - caller.db.desc = new_desc - caller.msg("%s\n\n|wThe above was set as the current description.|n" % new_desc) - - elif self.rhs or "add" in switches: - # add text directly to a new entry or an existing one. - if not (self.lhs and self.rhs): - caller.msg("Usage: %s/add key = description" % self.key) - return - key, desc = self.lhs, self.rhs - _update_store(caller, key, desc) - caller.msg("Stored description '%s': \"%s\"" % (key, crop(desc))) - - else: - # display the current description or a numbered description - _update_store(caller) - if args: - key = args.lower() - multidesc = caller.db.multidesc - for mkey, desc in multidesc: - if key == mkey: - caller.msg("|wDecsription %s:|n\n%s" % (key, desc)) - return - caller.msg("Description key '%s' not found." % key) - else: - caller.msg("|wCurrent desc:|n\n%s" % caller.db.desc) - - except DescValidateError as err: - # This is triggered by _key_to_index - caller.msg(err)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/puzzles.html b/docs/0.9.5/_modules/evennia/contrib/puzzles.html deleted file mode 100644 index 80f83927dc..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/puzzles.html +++ /dev/null @@ -1,920 +0,0 @@ - - - - - - - - evennia.contrib.puzzles — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.puzzles

-"""
-Puzzles System - Provides a typeclass and commands for
-objects that can be combined (i.e. 'use'd) to produce
-new objects.
-
-Evennia contribution - Henddher 2018
-
-A Puzzle is a recipe of what objects (aka parts) must
-be combined by a player so a new set of objects
-(aka results) are automatically created.
-
-Consider this simple Puzzle:
-
-    orange, mango, yogurt, blender = fruit smoothie
-
-As a Builder:
-
-    @create/drop orange
-    @create/drop mango
-    @create/drop yogurt
-    @create/drop blender
-    @create/drop fruit smoothie
-
-    @puzzle smoothie, orange, mango, yogurt, blender = fruit smoothie
-    ...
-    Puzzle smoothie(#1234) created successfuly.
-
-    @destroy/force orange, mango, yogurt, blender, fruit smoothie
-
-    @armpuzzle #1234
-    Part orange is spawned at ...
-    Part mango is spawned at ...
-    ....
-    Puzzle smoothie(#1234) has been armed successfully
-
-As Player:
-
-    use orange, mango, yogurt, blender
-    ...
-    Genius, you blended all fruits to create a fruit smoothie!
-
-Details:
-
-Puzzles are created from existing objects. The given
-objects are introspected to create prototypes for the
-puzzle parts and results. These prototypes become the
-puzzle recipe. (See PuzzleRecipe and @puzzle
-command). Once the recipe is created, all parts and result
-can be disposed (i.e. destroyed).
-
-At a later time, a Builder or a Script can arm the puzzle
-and spawn all puzzle parts in their respective
-locations (See @armpuzzle).
-
-A regular player can collect the puzzle parts and combine
-them (See use command). If player has specified
-all pieces, the puzzle is considered solved and all
-its puzzle parts are destroyed while the puzzle results
-are spawened on their corresponding location.
-
-Installation:
-
-Add the PuzzleSystemCmdSet to all players.
-Alternatively:
-
-    @py self.cmdset.add('evennia.contrib.puzzles.PuzzleSystemCmdSet')
-
-"""
-
-import itertools
-from random import choice
-from evennia import create_script
-from evennia import CmdSet
-from evennia import DefaultScript
-from evennia import DefaultCharacter
-from evennia import DefaultRoom
-from evennia import DefaultExit
-from evennia.commands.default.muxcommand import MuxCommand
-from evennia.utils.utils import inherits_from
-from evennia.utils import search, utils, logger
-from evennia.prototypes.spawner import spawn
-
-# Tag used by puzzles
-_PUZZLES_TAG_CATEGORY = "puzzles"
-_PUZZLES_TAG_RECIPE = "puzzle_recipe"
-# puzzle part and puzzle result
-_PUZZLES_TAG_MEMBER = "puzzle_member"
-
-_PUZZLE_DEFAULT_FAIL_USE_MESSAGE = "You try to utilize %s but nothing happens ... something amiss?"
-_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = "You are a Genius!!!"
-_PUZZLE_DEFAULT_SUCCESS_USE_LOCATION_MESSAGE = "|c{caller}|n performs some kind of tribal dance and |y{result_names}|n seems to appear from thin air"
-
-# ----------- UTILITY FUNCTIONS ------------
-
-
-
[docs]def proto_def(obj, with_tags=True): - """ - Basic properties needed to spawn - and compare recipe with candidate part - """ - protodef = { - # TODO: Don't we need to honor ALL properties? attributes, contents, etc. - "prototype_key": "%s(%s)" % (obj.key, obj.dbref), - "key": obj.key, - "typeclass": obj.typeclass_path, - "desc": obj.db.desc, - "location": obj.location, - "home": obj.home, - "locks": ";".join(obj.locks.all()), - "permissions": obj.permissions.all()[:], - } - if with_tags: - tags = obj.tags.all(return_key_and_category=True) - tags = [(t[0], t[1], None) for t in tags] - tags.append((_PUZZLES_TAG_MEMBER, _PUZZLES_TAG_CATEGORY, None)) - protodef["tags"] = tags - return protodef
- - -
[docs]def maskout_protodef(protodef, mask): - """ - Returns a new protodef after removing protodef values based on mask - """ - protodef = dict(protodef) - for m in mask: - if m in protodef: - protodef.pop(m) - return protodef
- - -# Colorize the default success message -def _colorize_message(msg): - _i = 0 - _colors = ["|r", "|g", "|y"] - _msg = [] - for l in msg: - _msg += _colors[_i] + l - _i = (_i + 1) % len(_colors) - msg = "".join(_msg) + "|n" - return msg - - -_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = _colorize_message(_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE) - -# ------------------------------------------ - - -
[docs]class PuzzleRecipe(DefaultScript): - """ - Definition of a Puzzle Recipe - """ - -
[docs] def save_recipe(self, puzzle_name, parts, results): - self.db.puzzle_name = str(puzzle_name) - self.db.parts = tuple(parts) - self.db.results = tuple(results) - self.db.mask = tuple() - self.tags.add(_PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY) - self.db.use_success_message = _PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE - self.db.use_success_location_message = _PUZZLE_DEFAULT_SUCCESS_USE_LOCATION_MESSAGE
- - -
[docs]class CmdCreatePuzzleRecipe(MuxCommand): - """ - Creates a puzzle recipe. A puzzle consists of puzzle-parts that - the player can 'use' together to create a specified result. - - Usage: - @puzzle name,<part1[,part2,...>] = <result1[,result2,...]> - - Example: - create/drop balloon - create/drop glass of water - create/drop water balloon - @puzzle waterballon,balloon,glass of water = water balloon - @del ballon, glass of water, water balloon - @armpuzzle #1 - - Notes: - Each part and result are objects that must (temporarily) exist and be placed in their - corresponding location in order to create the puzzle. After the creation of the puzzle, - these objects are not needed anymore and can be deleted. Components of the puzzle - will be re-created by use of the `@armpuzzle` command later. - - """ - - key = "@puzzle" - aliases = "@puzzlerecipe" - locks = "cmd:perm(puzzle) or perm(Builder)" - help_category = "Puzzles" - - confirm = True - default_confirm = "no" - -
[docs] def func(self): - caller = self.caller - - if len(self.lhslist) < 2 or not self.rhs: - string = "Usage: @puzzle name,<part1[,...]> = <result1[,...]>" - caller.msg(string) - return - - puzzle_name = self.lhslist[0] - if len(puzzle_name) == 0: - caller.msg("Invalid puzzle name %r." % puzzle_name) - return - - # if there is another puzzle with same name - # warn user that parts and results will be - # interchangable - _puzzles = search.search_script_attribute(key="puzzle_name", value=puzzle_name) - _puzzles = list(filter(lambda p: isinstance(p, PuzzleRecipe), _puzzles)) - if _puzzles: - confirm = ( - "There are %d puzzles with the same name.\n" % len(_puzzles) - + "Its parts and results will be interchangeable.\n" - + "Continue yes/[no]? " - ) - answer = "" - while answer.strip().lower() not in ("y", "yes", "n", "no"): - answer = yield (confirm) - answer = self.default_confirm if answer == "" else answer - if answer.strip().lower() in ("n", "no"): - caller.msg("Cancelled: no puzzle created.") - return - - def is_valid_obj_location(obj): - valid = True - # Rooms are the only valid locations. - # TODO: other valid locations could be added here. - # Certain locations can be handled accordingly: e.g, - # a part is located in a character's inventory, - # perhaps will translate into the player character - # having the part in his/her inventory while being - # located in the same room where the builder was - # located. - # Parts and results may have different valid locations - if not inherits_from(obj.location, DefaultRoom): - caller.msg("Invalid location for %s" % (obj.key)) - valid = False - return valid - - def is_valid_part_location(part): - return is_valid_obj_location(part) - - def is_valid_result_location(part): - return is_valid_obj_location(part) - - def is_valid_inheritance(obj): - valid = ( - not inherits_from(obj, DefaultCharacter) - and not inherits_from(obj, DefaultRoom) - and not inherits_from(obj, DefaultExit) - ) - if not valid: - caller.msg("Invalid typeclass for %s" % (obj)) - return valid - - def is_valid_part(part): - return is_valid_inheritance(part) and is_valid_part_location(part) - - def is_valid_result(result): - return is_valid_inheritance(result) and is_valid_result_location(result) - - parts = [] - for objname in self.lhslist[1:]: - obj = caller.search(objname) - if not obj: - return - if not is_valid_part(obj): - return - parts.append(obj) - - results = [] - for objname in self.rhslist: - obj = caller.search(objname) - if not obj: - return - if not is_valid_result(obj): - return - results.append(obj) - - for part in parts: - caller.msg("Part %s(%s)" % (part.name, part.dbref)) - - for result in results: - caller.msg("Result %s(%s)" % (result.name, result.dbref)) - - proto_parts = [proto_def(obj) for obj in parts] - proto_results = [proto_def(obj) for obj in results] - - puzzle = create_script(PuzzleRecipe, key=puzzle_name, persistent=True) - puzzle.save_recipe(puzzle_name, proto_parts, proto_results) - puzzle.locks.add("control:id(%s) or perm(Builder)" % caller.dbref[1:]) - - caller.msg( - "Puzzle |y'%s' |w%s(%s)|n has been created |gsuccessfully|n." - % (puzzle.db.puzzle_name, puzzle.name, puzzle.dbref) - ) - - caller.msg( - "You may now dispose of all parts and results. \n" - "Use @puzzleedit #{dbref} to customize this puzzle further. \n" - "Use @armpuzzle #{dbref} to arm a new puzzle instance.".format(dbref=puzzle.dbref) - )
- - -
[docs]class CmdEditPuzzle(MuxCommand): - """ - Edits puzzle properties - - Usage: - @puzzleedit[/delete] <#dbref> - @puzzleedit <#dbref>/use_success_message = <Custom message> - @puzzleedit <#dbref>/use_success_location_message = <Custom message from {caller} producing {result_names}> - @puzzleedit <#dbref>/mask = attr1[,attr2,...]> - @puzzleedit[/addpart] <#dbref> = <obj[,obj2,...]> - @puzzleedit[/delpart] <#dbref> = <obj[,obj2,...]> - @puzzleedit[/addresult] <#dbref> = <obj[,obj2,...]> - @puzzleedit[/delresult] <#dbref> = <obj[,obj2,...]> - - Switches: - addpart - adds parts to the puzzle - delpart - removes parts from the puzzle - addresult - adds results to the puzzle - delresult - removes results from the puzzle - delete - deletes the recipe. Existing parts and results aren't modified - - mask - attributes to exclude during matching (e.g. location, desc, etc.) - use_success_location_message containing {result_names} and {caller} will - automatically be replaced with correct values. Both are optional. - - When removing parts/results, it's possible to remove all. - - """ - - key = "@puzzleedit" - locks = "cmd:perm(puzzleedit) or perm(Builder)" - help_category = "Puzzles" - -
[docs] def func(self): - self._USAGE = "Usage: @puzzleedit[/switches] <dbref>[/attribute = <value>]" - caller = self.caller - - if not self.lhslist: - caller.msg(self._USAGE) - return - - if "/" in self.lhslist[0]: - recipe_dbref, attr = self.lhslist[0].split("/") - else: - recipe_dbref = self.lhslist[0] - - if not utils.dbref(recipe_dbref): - caller.msg("A puzzle recipe's #dbref must be specified.\n" + self._USAGE) - return - - puzzle = search.search_script(recipe_dbref) - if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe): - caller.msg("%s(%s) is not a puzzle" % (puzzle[0].name, recipe_dbref)) - return - - puzzle = puzzle[0] - puzzle_name_id = "%s(%s)" % (puzzle.name, puzzle.dbref) - - if "delete" in self.switches: - if not (puzzle.access(caller, "control") or puzzle.access(caller, "delete")): - caller.msg("You don't have permission to delete %s." % puzzle_name_id) - return - - puzzle.delete() - caller.msg("%s was deleted" % puzzle_name_id) - return - - elif "addpart" in self.switches: - objs = self._get_objs() - if objs: - added = self._add_parts(objs, puzzle) - caller.msg("%s were added to parts" % (", ".join(added))) - return - - elif "delpart" in self.switches: - objs = self._get_objs() - if objs: - removed = self._remove_parts(objs, puzzle) - caller.msg("%s were removed from parts" % (", ".join(removed))) - return - - elif "addresult" in self.switches: - objs = self._get_objs() - if objs: - added = self._add_results(objs, puzzle) - caller.msg("%s were added to results" % (", ".join(added))) - return - - elif "delresult" in self.switches: - objs = self._get_objs() - if objs: - removed = self._remove_results(objs, puzzle) - caller.msg("%s were removed from results" % (", ".join(removed))) - return - - else: - # edit attributes - - if not (puzzle.access(caller, "control") or puzzle.access(caller, "edit")): - caller.msg("You don't have permission to edit %s." % puzzle_name_id) - return - - if attr == "use_success_message": - puzzle.db.use_success_message = self.rhs - caller.msg( - "%s use_success_message = %s\n" - % (puzzle_name_id, puzzle.db.use_success_message) - ) - return - elif attr == "use_success_location_message": - puzzle.db.use_success_location_message = self.rhs - caller.msg( - "%s use_success_location_message = %s\n" - % (puzzle_name_id, puzzle.db.use_success_location_message) - ) - return - elif attr == "mask": - puzzle.db.mask = tuple(self.rhslist) - caller.msg("%s mask = %r\n" % (puzzle_name_id, puzzle.db.mask)) - return
- - def _get_objs(self): - if not self.rhslist: - self.caller.msg(self._USAGE) - return - objs = [] - for o in self.rhslist: - obj = self.caller.search(o) - if obj: - objs.append(obj) - return objs - - def _add_objs_to(self, objs, to): - """Adds propto objs to the given set (parts or results)""" - added = [] - toobjs = list(to[:]) - for obj in objs: - protoobj = proto_def(obj) - toobjs.append(protoobj) - added.append(obj.key) - return added, toobjs - - def _remove_objs_from(self, objs, frm): - """Removes propto objs from the given set (parts or results)""" - removed = [] - fromobjs = list(frm[:]) - for obj in objs: - protoobj = proto_def(obj) - if protoobj in fromobjs: - fromobjs.remove(protoobj) - removed.append(obj.key) - return removed, fromobjs - - def _add_parts(self, objs, puzzle): - added, toobjs = self._add_objs_to(objs, puzzle.db.parts) - puzzle.db.parts = tuple(toobjs) - return added - - def _remove_parts(self, objs, puzzle): - removed, fromobjs = self._remove_objs_from(objs, puzzle.db.parts) - puzzle.db.parts = tuple(fromobjs) - return removed - - def _add_results(self, objs, puzzle): - added, toobjs = self._add_objs_to(objs, puzzle.db.results) - puzzle.db.results = tuple(toobjs) - return added - - def _remove_results(self, objs, puzzle): - removed, fromobjs = self._remove_objs_from(objs, puzzle.db.results) - puzzle.db.results = tuple(fromobjs) - return removed
- - -
[docs]class CmdArmPuzzle(MuxCommand): - """ - Arms a puzzle by spawning all its parts. - - Usage: - @armpuzzle <puzzle #dbref> - - Notes: - Create puzzles with `@puzzle`; get list of - defined puzzles using `@lspuzzlerecipes`. - - """ - - key = "@armpuzzle" - locks = "cmd:perm(armpuzzle) or perm(Builder)" - help_category = "Puzzles" - -
[docs] def func(self): - caller = self.caller - - if self.args is None or not utils.dbref(self.args): - caller.msg("A puzzle recipe's #dbref must be specified") - return - - puzzle = search.search_script(self.args) - if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe): - caller.msg("Invalid puzzle %r" % (self.args)) - return - - puzzle = puzzle[0] - caller.msg( - "Puzzle Recipe %s(%s) '%s' found.\nSpawning %d parts ..." - % (puzzle.name, puzzle.dbref, puzzle.db.puzzle_name, len(puzzle.db.parts)) - ) - - for proto_part in puzzle.db.parts: - part = spawn(proto_part)[0] - caller.msg( - "Part %s(%s) spawned and placed at %s(%s)" - % (part.name, part.dbref, part.location, part.location.dbref) - ) - part.tags.add(puzzle.db.puzzle_name, category=_PUZZLES_TAG_CATEGORY) - part.db.puzzle_name = puzzle.db.puzzle_name - - caller.msg("Puzzle armed |gsuccessfully|n.")
- - -def _lookups_parts_puzzlenames_protodefs(parts): - # Create lookup dicts by part's dbref and by puzzle_name(tags) - parts_dict = dict() - puzzlename_tags_dict = dict() - puzzle_ingredients = dict() - for part in parts: - parts_dict[part.dbref] = part - protodef = proto_def(part, with_tags=False) - # remove 'prototype_key' as it will prevent equality - del protodef["prototype_key"] - puzzle_ingredients[part.dbref] = protodef - tags_categories = part.tags.all(return_key_and_category=True) - for tag, category in tags_categories: - if category != _PUZZLES_TAG_CATEGORY: - continue - if tag not in puzzlename_tags_dict: - puzzlename_tags_dict[tag] = [] - puzzlename_tags_dict[tag].append(part.dbref) - return parts_dict, puzzlename_tags_dict, puzzle_ingredients - - -def _puzzles_by_names(names): - # Find all puzzles by puzzle name (i.e. tag name) - puzzles = [] - for puzzle_name in names: - _puzzles = search.search_script_attribute(key="puzzle_name", value=puzzle_name) - _puzzles = list(filter(lambda p: isinstance(p, PuzzleRecipe), _puzzles)) - if not _puzzles: - continue - else: - puzzles.extend(_puzzles) - return puzzles - - -def _matching_puzzles(puzzles, puzzlename_tags_dict, puzzle_ingredients): - # Check if parts can be combined to solve a puzzle - matched_puzzles = dict() - for puzzle in puzzles: - puzzle_protoparts = list(puzzle.db.parts[:]) - puzzle_mask = puzzle.db.mask[:] - # remove tags and prototype_key as they prevent equality - for i, puzzle_protopart in enumerate(puzzle_protoparts[:]): - del puzzle_protopart["tags"] - del puzzle_protopart["prototype_key"] - puzzle_protopart = maskout_protodef(puzzle_protopart, puzzle_mask) - puzzle_protoparts[i] = puzzle_protopart - - matched_dbrefparts = [] - parts_dbrefs = puzzlename_tags_dict[puzzle.db.puzzle_name] - for part_dbref in parts_dbrefs: - protopart = puzzle_ingredients[part_dbref] - protopart = maskout_protodef(protopart, puzzle_mask) - if protopart in puzzle_protoparts: - puzzle_protoparts.remove(protopart) - matched_dbrefparts.append(part_dbref) - else: - if len(puzzle_protoparts) == 0: - matched_puzzles[puzzle.dbref] = matched_dbrefparts - return matched_puzzles - - -
[docs]class CmdUsePuzzleParts(MuxCommand): - """ - Use an object, or a group of objects at once. - - - Example: - You look around you and see a pole, a long string, and a needle. - - use pole, long string, needle - - Genius! You built a fishing pole. - - - Usage: - use <obj1> [,obj2,...] - """ - - # Technical explanation - """ - Searches for all puzzles whose parts match the given set of objects. If there are matching - puzzles, the result objects are spawned in their corresponding location if all parts have been - passed in. - """ - - key = "use" - aliases = "combine" - locks = "cmd:pperm(use) or pperm(Player)" - help_category = "Puzzles" - -
[docs] def func(self): - caller = self.caller - - if not self.lhs: - caller.msg("Use what?") - return - - many = "these" if len(self.lhslist) > 1 else "this" - - # either all are parts, or abort finding matching puzzles - parts = [] - partnames = self.lhslist[:] - for partname in partnames: - part = caller.search( - partname, - multimatch_string="Which %s. There are many.\n" % (partname), - nofound_string="There is no %s around." % (partname), - ) - - if not part: - return - - if not part.tags.get(_PUZZLES_TAG_MEMBER, category=_PUZZLES_TAG_CATEGORY): - - # not a puzzle part ... abort - caller.msg("You have no idea how %s can be used" % (many)) - return - - # a valid part - parts.append(part) - - # Create lookup dicts by part's dbref and by puzzle_name(tags) - parts_dict, puzzlename_tags_dict, puzzle_ingredients = _lookups_parts_puzzlenames_protodefs( - parts - ) - - # Find all puzzles by puzzle name (i.e. tag name) - puzzles = _puzzles_by_names(puzzlename_tags_dict.keys()) - - logger.log_info("PUZZLES %r" % ([(p.dbref, p.db.puzzle_name) for p in puzzles])) - - # Create lookup dict of puzzles by dbref - puzzles_dict = dict((puzzle.dbref, puzzle) for puzzle in puzzles) - # Check if parts can be combined to solve a puzzle - matched_puzzles = _matching_puzzles(puzzles, puzzlename_tags_dict, puzzle_ingredients) - - if len(matched_puzzles) == 0: - # TODO: we could use part.fail_message instead, if there was one - # random part falls and lands on your feet - # random part hits you square on the face - caller.msg(_PUZZLE_DEFAULT_FAIL_USE_MESSAGE % (many)) - return - - puzzletuples = sorted(matched_puzzles.items(), key=lambda t: len(t[1]), reverse=True) - - logger.log_info("MATCHED PUZZLES %r" % (puzzletuples)) - - # sort all matched puzzles and pick largest one(s) - puzzledbref, matched_dbrefparts = puzzletuples[0] - nparts = len(matched_dbrefparts) - puzzle = puzzles_dict[puzzledbref] - largest_puzzles = list(itertools.takewhile(lambda t: len(t[1]) == nparts, puzzletuples)) - - # if there are more than one, choose one at random. - # we could show the names of all those that can be resolved - # but that would give away that there are other puzzles that - # can be resolved with the same parts. - # just hint how many. - if len(largest_puzzles) > 1: - caller.msg( - "Your gears start turning and %d different ideas come to your mind ...\n" - % (len(largest_puzzles)) - ) - puzzletuple = choice(largest_puzzles) - puzzle = puzzles_dict[puzzletuple[0]] - caller.msg("You try %s ..." % (puzzle.db.puzzle_name)) - - # got one, spawn its results - result_names = [] - for proto_result in puzzle.db.results: - result = spawn(proto_result)[0] - result.tags.add(puzzle.db.puzzle_name, category=_PUZZLES_TAG_CATEGORY) - result.db.puzzle_name = puzzle.db.puzzle_name - result_names.append(result.name) - - # Destroy all parts used - for dbref in matched_dbrefparts: - parts_dict[dbref].delete() - - result_names = ", ".join(result_names) - caller.msg(puzzle.db.use_success_message) - caller.location.msg_contents( - puzzle.db.use_success_location_message.format(caller=caller, result_names=result_names), - exclude=(caller,), - )
- - -
[docs]class CmdListPuzzleRecipes(MuxCommand): - """ - Searches for all puzzle recipes - - Usage: - @lspuzzlerecipes - """ - - key = "@lspuzzlerecipes" - locks = "cmd:perm(lspuzzlerecipes) or perm(Builder)" - help_category = "Puzzles" - -
[docs] def func(self): - caller = self.caller - - recipes = search.search_script_tag(_PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY) - - div = "-" * 60 - text = [div] - msgf_recipe = "Puzzle |y'%s' %s(%s)|n" - msgf_item = "%2s|c%15s|n: |w%s|n" - for recipe in recipes: - text.append(msgf_recipe % (recipe.db.puzzle_name, recipe.name, recipe.dbref)) - text.append("Success Caller message:\n" + recipe.db.use_success_message + "\n") - text.append( - "Success Location message:\n" + recipe.db.use_success_location_message + "\n" - ) - text.append("Mask:\n" + str(recipe.db.mask) + "\n") - text.append("Parts") - for protopart in recipe.db.parts[:]: - mark = "-" - for k, v in protopart.items(): - text.append(msgf_item % (mark, k, v)) - mark = "" - text.append("Results") - for protoresult in recipe.db.results[:]: - mark = "-" - for k, v in protoresult.items(): - text.append(msgf_item % (mark, k, v)) - mark = "" - else: - text.append(div) - text.append("Found |r%d|n puzzle(s)." % (len(recipes))) - text.append(div) - caller.msg("\n".join(text))
- - -
[docs]class CmdListArmedPuzzles(MuxCommand): - """ - Searches for all armed puzzles - - Usage: - @lsarmedpuzzles - """ - - key = "@lsarmedpuzzles" - locks = "cmd:perm(lsarmedpuzzles) or perm(Builder)" - help_category = "Puzzles" - -
[docs] def func(self): - caller = self.caller - - armed_puzzles = search.search_tag(_PUZZLES_TAG_MEMBER, category=_PUZZLES_TAG_CATEGORY) - - armed_puzzles = dict( - (k, list(g)) for k, g in itertools.groupby(armed_puzzles, lambda ap: ap.db.puzzle_name) - ) - - div = "-" * 60 - msgf_pznm = "Puzzle name: |y%s|n" - msgf_item = "|m%25s|w(%s)|n at |c%25s|w(%s)|n" - text = [div] - for pzname, items in armed_puzzles.items(): - text.append(msgf_pznm % (pzname)) - for item in items: - text.append( - msgf_item % (item.name, item.dbref, item.location.name, item.location.dbref) - ) - else: - text.append(div) - text.append("Found |r%d|n armed puzzle(s)." % (len(armed_puzzles))) - text.append(div) - caller.msg("\n".join(text))
- - -
[docs]class PuzzleSystemCmdSet(CmdSet): - """ - CmdSet to create, arm and resolve Puzzles - """ - -
[docs] def at_cmdset_creation(self): - super(PuzzleSystemCmdSet, self).at_cmdset_creation() - - self.add(CmdCreatePuzzleRecipe()) - self.add(CmdEditPuzzle()) - self.add(CmdArmPuzzle()) - self.add(CmdListPuzzleRecipes()) - self.add(CmdListArmedPuzzles()) - self.add(CmdUsePuzzleParts())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/random_string_generator.html b/docs/0.9.5/_modules/evennia/contrib/random_string_generator.html deleted file mode 100644 index 3d6aa00ef3..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/random_string_generator.html +++ /dev/null @@ -1,460 +0,0 @@ - - - - - - - - evennia.contrib.random_string_generator — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.random_string_generator

-"""
-Pseudo-random generator and registry
-
-Evennia contribution - Vincent Le Goff 2017
-
-This contrib can be used to generate pseudo-random strings of information
-with specific criteria.  You could, for instance, use it to generate
-phone numbers, license plate numbers, validation codes, non-sensivite
-passwords and so on.  The strings generated by the generator will be
-stored and won't be available again in order to avoid repetition.
-Here's a very simple example:
-
-```python
-from evennia.contrib.random_string_generator import RandomStringGenerator
-# Create a generator for phone numbers
-phone_generator = RandomStringGenerator("phone number", r"555-[0-9]{3}-[0-9]{4}")
-# Generate a phone number (555-XXX-XXXX with X as numbers)
-number = phone_generator.get()
-# `number` will contain something like: "555-981-2207"
-# If you call `phone_generator.get`, it won't give the same anymore.phone_generator.all()
-# Will return a list of all currently-used phone numbers
-phone_generator.remove("555-981-2207")
-# The number can be generated again
-```
-
-To use it, you will need to:
-
-1. Import the `RandomStringGenerator` class from the contrib.
-2. Create an instance of this class taking two arguments:
-   - The name of the gemerator (like "phone number", "license plate"...).
-   - The regular expression representing the expected results.
-3. Use the generator's `all`, `get` and `remove` methods as shown above.
-
-To understand how to read and create regular expressions, you can refer to
-[the documentation on the re module](https://docs.python.org/2/library/re.html).
-Some examples of regular expressions you could use:
-
-- `r"555-\d{3}-\d{4}"`: 555, a dash, 3 digits, another dash, 4 digits.
-- `r"[0-9]{3}[A-Z][0-9]{3}"`: 3 digits, a capital letter, 3 digits.
-- `r"[A-Za-z0-9]{8,15}"`: between 8 and 15 letters and digits.
-- ...
-
-Behind the scenes, a script is created to store the generated information
-for a single generator.  The `RandomStringGenerator` object will also
-read the regular expression you give to it to see what information is
-required (letters, digits, a more restricted class, simple characters...)...
-More complex regular expressions (with branches for instance) might not be
-available.
-
-"""
-
-from random import choice, randint, seed
-import re
-import string
-import time
-
-from evennia import DefaultScript, ScriptDB
-from evennia.utils.create import create_script
-
-
-
[docs]class RejectedRegex(RuntimeError): - - """The provided regular expression has been rejected. - - More details regarding why this error occurred will be provided in - the message. The usual reason is the provided regular expression is - not specific enough and could lead to inconsistent generating. - - """ - - pass
- - -
[docs]class ExhaustedGenerator(RuntimeError): - - """The generator hasn't any available strings to generate anymore.""" - - pass
- - -
[docs]class RandomStringGeneratorScript(DefaultScript): - - """ - The global script to hold all generators. - - It will be automatically created the first time `generate` is called - on a RandomStringGenerator object. - - """ - -
[docs] def at_script_creation(self): - """Hook called when the script is created.""" - self.key = "generator_script" - self.desc = "Global generator script" - self.persistent = True - - # Permanent data to be stored - self.db.generated = {}
- - -
[docs]class RandomStringGenerator(object): - - """ - A generator class to generate pseudo-random strings with a rule. - - The "rule" defining what the generator should provide in terms of - string is given as a regular expression when creating instances of - this class. You can use the `all` method to get all generated strings, - the `get` method to generate a new string, the `remove` method - to remove a generated string, or the `clear` method to remove all - generated strings. - - Bear in mind, however, that while the generated strings will be - stored to avoid repetition, the generator will not concern itself - with how the string is stored on the object you use. You probably - want to create a tag to mark this object. This is outside of the scope - of this class. - - """ - - # We keep the script as a class variable to optimize querying - # with multiple instandces - script = None - -
[docs] def __init__(self, name, regex): - """ - Create a new generator. - - Args: - name (str): name of the generator to create. - regex (str): regular expression describing the generator. - - Notes: - `name` should be an explicit name. If you use more than one - generator in your game, be sure to give them different names. - This name will be used to store the generated information - in the global script, and in case of errors. - - The regular expression should describe the generator, what - it should generate: a phone number, a license plate, a password - or something else. Regular expressions allow you to use - pretty advanced criteria, but be aware that some regular - expressions will be rejected if not specific enough. - - Raises: - RejectedRegex: the provided regular expression couldn't be - accepted as a valid generator description. - - """ - self.name = name - self.elements = [] - self.total = 1 - - # Analyze the regex if any - if regex: - self._find_elements(regex)
- - def __repr__(self): - return "<evennia.contrib.random_string_generator.RandomStringGenerator for {}>".format( - self.name - ) - - def _get_script(self): - """Get or create the script.""" - if type(self).script: - return type(self).script - - try: - script = ScriptDB.objects.get(db_key="generator_script") - except ScriptDB.DoesNotExist: - script = create_script("contrib.random_string_generator.RandomStringGeneratorScript") - - type(self).script = script - return script - - def _find_elements(self, regex): - """ - Find the elements described in the regular expression. This will - analyze the provided regular expression and try to find elements. - - Args: - regex (str): the regular expression. - - """ - self.total = 1 - self.elements = [] - tree = re.sre_parse.parse(regex).data - # `tree` contains a list of elements in the regular expression - for element in tree: - # `element` is also a list, the first element is a string - name = str(element[0]).lower() - desc = {"min": 1, "max": 1} - - # If `.`, break here - if name == "any": - raise RejectedRegex( - "the . definition is too broad, specify what you need more precisely" - ) - elif name == "at": - # Either the beginning or end, we ignore it - continue - elif name == "min_repeat": - raise RejectedRegex("you have to provide a maximum number of this character class") - elif name == "max_repeat": - desc["min"] = element[1][0] - desc["max"] = element[1][1] - desc["chars"] = self._find_literal(element[1][2][0]) - elif name == "in": - desc["chars"] = self._find_literal(element) - elif name == "literal": - desc["chars"] = self._find_literal(element) - else: - raise RejectedRegex("unhandled regex syntax:: {}".format(repr(name))) - - self.elements.append(desc) - self.total *= len(desc["chars"]) ** desc["max"] - - def _find_literal(self, element): - """Find the literal corresponding to a piece of regular expression.""" - name = str(element[0]).lower() - chars = [] - if name == "literal": - chars.append(chr(element[1])) - elif name == "in": - negate = False - if element[1][0][0] == "negate": - negate = True - chars = list(string.ascii_letters + string.digits) - - for part in element[1]: - if part[0] == "negate": - continue - - sublist = self._find_literal(part) - for char in sublist: - if negate: - if char in chars: - chars.remove(char) - else: - chars.append(char) - elif name == "range": - chars = [chr(i) for i in range(element[1][0], element[1][1] + 1)] - elif name == "category": - category = str(element[1]).lower() - if category == "category_digit": - chars = list(string.digits) - elif category == "category_word": - chars = list(string.letters) - else: - raise RejectedRegex("unknown category: {}".format(category)) - else: - raise RejectedRegex("cannot find the literal: {}".format(element[0])) - - return chars - -
[docs] def all(self): - """ - Return all generated strings for this generator. - - Returns: - strings (list of strr): the list of strings that are already - used. The strings that were generated first come first in the list. - - """ - script = self._get_script() - generated = list(script.db.generated.get(self.name, [])) - return generated
- -
[docs] def get(self, store=True, unique=True): - """ - Generate a pseudo-random string according to the regular expression. - - Args: - store (bool, optional): store the generated string in the script. - unique (bool, optional): keep on trying if the string is already used. - - Returns: - The newly-generated string. - - Raises: - ExhaustedGenerator: if there's no available string in this generator. - - Note: - Unless asked explicitly, the returned string can't repeat itself. - - """ - script = self._get_script() - generated = script.db.generated.get(self.name) - if generated is None: - script.db.generated[self.name] = [] - generated = script.db.generated[self.name] - - if len(generated) >= self.total: - raise ExhaustedGenerator - - # Generate a pseudo-random string that might be used already - result = "" - for element in self.elements: - number = randint(element["min"], element["max"]) - chars = element["chars"] - for index in range(number): - char = choice(chars) - result += char - - # If the string has already been generated, try again - if result in generated and unique: - # Change the random seed, incrementing it slowly - epoch = time.time() - while result in generated: - epoch += 1 - seed(epoch) - result = self.get(store=False, unique=False) - - if store: - generated.append(result) - - return result
- -
[docs] def remove(self, element): - """ - Remove a generated string from the list of stored strings. - - Args: - element (str): the string to remove from the list of generated strings. - - Raises: - ValueError: the specified value hasn't been generated and is not present. - - Note: - The specified string has to be present in the script (so - has to have been generated). It will remove this entry - from the script, so this string could be generated again by - calling the `get` method. - - """ - script = self._get_script() - generated = script.db.generated.get(self.name, []) - if element not in generated: - raise ValueError( - "the string {} isn't stored as generated by the generator {}".format( - element, self.name - ) - ) - - generated.remove(element)
- -
[docs] def clear(self): - """ - Clear the generator of all generated strings. - - """ - script = self._get_script() - generated = script.db.generated.get(self.name, []) - generated[:] = []
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/rplanguage.html b/docs/0.9.5/_modules/evennia/contrib/rplanguage.html deleted file mode 100644 index 9848ec58e6..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/rplanguage.html +++ /dev/null @@ -1,651 +0,0 @@ - - - - - - - - evennia.contrib.rplanguage — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.rplanguage

-"""
-Language and whisper obfuscation system
-
-Evennia contrib - Griatch 2015
-
-
-This module is intented to be used with an emoting system (such as
-contrib/rpsystem.py). It offers the ability to obfuscate spoken words
-in the game in various ways:
-
-- Language: The language functionality defines a pseudo-language map
-    to any number of languages.  The string will be obfuscated depending
-    on a scaling that (most likely) will be input as a weighted average of
-    the language skill of the speaker and listener.
-- Whisper: The whisper functionality will gradually "fade out" a
-    whisper along as scale 0-1, where the fading is based on gradually
-    removing sections of the whisper that is (supposedly) easier to
-    overhear (for example "s" sounds tend to be audible even when no other
-    meaning can be determined).
-
-Usage:
-
-    ```python
-    from evennia.contrib import rplanguage
-
-    # need to be done once, here we create the "default" lang
-    rplanguage.add_language()
-
-    say = "This is me talking."
-    whisper = "This is me whispering.
-
-    print rplanguage.obfuscate_language(say, level=0.0)
-    <<< "This is me talking."
-    print rplanguage.obfuscate_language(say, level=0.5)
-    <<< "This is me byngyry."
-    print rplanguage.obfuscate_language(say, level=1.0)
-    <<< "Daly ly sy byngyry."
-
-    result = rplanguage.obfuscate_whisper(whisper, level=0.0)
-    <<< "This is me whispering"
-    result = rplanguage.obfuscate_whisper(whisper, level=0.2)
-    <<< "This is m- whisp-ring"
-    result = rplanguage.obfuscate_whisper(whisper, level=0.5)
-    <<< "---s -s -- ---s------"
-    result = rplanguage.obfuscate_whisper(whisper, level=0.7)
-    <<< "---- -- -- ----------"
-    result = rplanguage.obfuscate_whisper(whisper, level=1.0)
-    <<< "..."
-
-    ```
-
-    To set up new languages, import and use the `add_language()`
-    helper method in this module. This allows you to customize the
-    "feel" of the semi-random language you are creating. Especially
-    the `word_length_variance` helps vary the length of translated
-    words compared to the original and can help change the "feel" for
-    the language you are creating. You can also add your own
-    dictionary and "fix" random words for a list of input words.
-
-    Below is an example of "elvish", using "rounder" vowels and sounds:
-
-    ```python
-    phonemes = "oi oh ee ae aa eh ah ao aw ay er ey ow ia ih iy " \
-               "oy ua uh uw y p b t d f v t dh s z sh zh ch jh k " \
-               "ng g m n l r w",
-    vowels = "eaoiuy"
-    grammar = "v vv vvc vcc vvcc cvvc vccv vvccv vcvccv vcvcvcc vvccvvcc " \
-              "vcvvccvvc cvcvvcvvcc vcvcvvccvcvv",
-    word_length_variance = 1
-    noun_postfix = "'la"
-    manual_translations = {"the":"y'e", "we":"uyi", "she":"semi", "he":"emi",
-                          "you": "do", 'me':'mi','i':'me', 'be':"hy'e", 'and':'y'}
-
-    rplanguage.add_language(key="elvish", phonemes=phonemes, grammar=grammar,
-                             word_length_variance=word_length_variance,
-                             noun_postfix=noun_postfix, vowels=vowels,
-                             manual_translations=manual_translations
-                             auto_translations="my_word_file.txt")
-
-    ```
-
-    This will produce a decicively more "rounded" and "soft" language
-    than the default one. The few manual_translations also make sure
-    to make it at least look superficially "reasonable".
-
-    The `auto_translations` keyword is useful, this accepts either a
-    list or a path to a file of words (one per line) to automatically
-    create fixed translations for according to the grammatical rules.
-    This allows to quickly build a large corpus of translated words
-    that never change (if this is desired).
-
-"""
-import re
-from random import choice, randint
-from collections import defaultdict
-from evennia import DefaultScript
-from evennia.utils import logger
-
-
-# ------------------------------------------------------------
-#
-# Obfuscate language
-#
-# ------------------------------------------------------------
-
-# default language grammar
-_PHONEMES = (
-    "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh "
-    "s z sh zh ch jh k ng g m n l r w"
-)
-_VOWELS = "eaoiuy"
-# these must be able to be constructed from phonemes (so for example,
-# if you have v here, there must exist at least one single-character
-# vowel phoneme defined above)
-_GRAMMAR = "v cv vc cvv vcc vcv cvcc vccv cvccv cvcvcc cvccvcv vccvccvc cvcvccvv cvcvcvcvv"
-
-_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.DOTALL + re.UNICODE
-_RE_GRAMMAR = re.compile(r"vv|cc|v|c", _RE_FLAGS)
-_RE_WORD = re.compile(r"\w+", _RE_FLAGS)
-_RE_EXTRA_CHARS = re.compile(r"\s+(?=\W)|[,.?;](?=[,.?;]|\s+[,.?;])", _RE_FLAGS)
-
-
-
[docs]class LanguageError(RuntimeError): - pass
- - -
[docs]class LanguageExistsError(LanguageError): - pass
- - -
[docs]class LanguageHandler(DefaultScript): - """ - This is a storage class that should usually not be created on its - own. It's automatically created by a call to `obfuscate_language` - or `add_language` below. - - Languages are implemented as a "logical" pseudo- consistent language - algorith here. The idea is that a language is built up from - phonemes. These are joined together according to a "grammar" of - possible phoneme- combinations and allowed characters. It may - sound simplistic, but this allows to easily make - "similar-sounding" languages. One can also custom-define a - dictionary of some common words to give further consistency. - Optionally, the system also allows an input list of common words - to be loaded and given random translations. These will be stored - to disk and will thus not change. This gives a decent "stability" - of the language but if the goal is to obfuscate, this may allow - players to eventually learn to understand the gist of a sentence - even if their characters can not. Any number of languages can be - created this way. - - This nonsense language will partially replace the actual spoken - language when so desired (usually because the speaker/listener - don't know the language well enough). - - """ - -
[docs] def at_script_creation(self): - "Called when script is first started" - self.key = "language_handler" - self.persistent = True - self.db.language_storage = {}
- -
[docs] def add( - self, - key="default", - phonemes=_PHONEMES, - grammar=_GRAMMAR, - word_length_variance=0, - noun_translate=False, - noun_prefix="", - noun_postfix="", - vowels=_VOWELS, - manual_translations=None, - auto_translations=None, - force=False, - ): - """ - Add a new language. Note that you generally only need to do - this once per language and that adding an existing language - will re-initialize all the random components to new permanent - values. - - Args: - key (str, optional): The name of the language. This - will be used as an identifier for the language so it - should be short and unique. - phonemes (str, optional): Space-separated string of all allowed - phonemes in this language. If either of the base phonemes - (c, v, cc, vv) are present in the grammar, the phoneme list must - at least include one example of each. - grammar (str): All allowed consonant (c) and vowel (v) combinations - allowed to build up words. Grammars are broken into the base phonemes - (c, v, cc, vv) prioritizing the longer bases. So cvv would be a - the c + vv (would allow for a word like 'die' whereas - cvcvccc would be c+v+c+v+cc+c (a word like 'galosch'). - word_length_variance (real): The variation of length of words. - 0 means a minimal variance, higher variance may mean words - have wildly varying length; this strongly affects how the - language "looks". - noun_translate (bool, optional): If a proper noun, identified as a - capitalized word, should be translated or not. By default they - will not, allowing for e.g. the names of characters to be understandable. - noun_prefix (str, optional): A prefix to go before every noun - in this language (if any). - noun_postfix (str, optuonal): A postfix to go after every noun - in this language (if any, usually best to avoid combining - with `noun_prefix` or language becomes very wordy). - vowels (str, optional): Every vowel allowed in this language. - manual_translations (dict, optional): This allows for custom-setting - certain words in the language to mean the same thing. It is - on the form `{real_word: fictional_word}`, for example - `{"the", "y'e"}` . - auto_translations (str or list, optional): These are lists - words that should be auto-translated with a random, but - fixed, translation. If a path to a file, this file should - contain a list of words to produce translations for, one - word per line. If a list, the list's elements should be - the words to translate. The `manual_translations` will - always override overlapping translations created - automatically. - force (bool, optional): Unless true, will not allow the addition - of a language that is already created. - - Raises: - LanguageExistsError: Raised if trying to adding a language - with a key that already exists, without `force` being set. - Notes: - The `word_file` is for example a word-frequency list for - the N most common words in the host language. The - translations will be random, but will be stored - persistently to always be the same. This allows for - building a quick, decently-sounding fictive language that - tend to produce the same "translation" (mostly) with the - same input sentence. - - """ - if key in self.db.language_storage and not force: - raise LanguageExistsError( - "Language is already created. Re-adding it will re-build" - " its dictionary map. Use 'force=True' keyword if you are sure." - ) - - # create grammar_component->phoneme mapping - # {"vv": ["ea", "oh", ...], ...} - grammar2phonemes = defaultdict(list) - for phoneme in phonemes.split(): - if re.search("\W", phoneme): - raise LanguageError("The phoneme '%s' contains an invalid character" % phoneme) - gram = "".join(["v" if char in vowels else "c" for char in phoneme]) - grammar2phonemes[gram].append(phoneme) - - # allowed grammar are grouped by length - gramdict = defaultdict(list) - for gram in grammar.split(): - if re.search("\W|(!=[cv])", gram): - raise LanguageError( - "The grammar '%s' is invalid (only 'c' and 'v' are allowed)" % gram - ) - gramdict[len(gram)].append(gram) - grammar = dict(gramdict) - - # create automatic translation - translation = {} - - if auto_translations: - if isinstance(auto_translations, str): - # path to a file rather than a list - with open(auto_translations, "r") as f: - auto_translations = f.readlines() - for word in auto_translations: - word = word.strip() - lword = len(word) - new_word = "" - wlen = max(0, lword + sum(randint(-1, 1) for i in range(word_length_variance))) - if wlen not in grammar: - # always create a translation, use random length - structure = choice(grammar[choice(list(grammar))]) - else: - # use the corresponding length - structure = choice(grammar[wlen]) - for match in _RE_GRAMMAR.finditer(structure): - new_word += choice(grammar2phonemes[match.group()]) - translation[word.lower()] = new_word.lower() - - if manual_translations: - # update with manual translations - translation.update( - dict((key.lower(), value.lower()) for key, value in manual_translations.items()) - ) - - # store data - storage = { - "translation": translation, - "grammar": grammar, - "grammar2phonemes": dict(grammar2phonemes), - "word_length_variance": word_length_variance, - "noun_translate": noun_translate, - "noun_prefix": noun_prefix, - "noun_postfix": noun_postfix, - } - self.db.language_storage[key] = storage
- - def _translate_sub(self, match): - """ - Replacer method called by re.sub when - traversing the language string. - - Args: - match (re.matchobj): Match object from regex. - - Returns: - converted word. - Notes: - Assumes self.lastword and self.level is available - on the object. - - """ - word = match.group() - lword = len(word) - - if len(word) <= self.level: - # below level. Don't translate - new_word = word - else: - # try to translate the word from dictionary - new_word = self.language["translation"].get(word.lower(), "") - if not new_word: - # no dictionary translation. Generate one - - # find out what preceeded this word - wpos = match.start() - preceeding = match.string[:wpos].strip() - start_sentence = preceeding.endswith((".", "!", "?")) or not preceeding - - # make up translation on the fly. Length can - # vary from un-translated word. - wlen = max( - 0, - lword - + sum(randint(-1, 1) for i in range(self.language["word_length_variance"])), - ) - grammar = self.language["grammar"] - if wlen not in grammar: - if randint(0, 1) == 0: - # this word has no direct translation! - wlen = 0 - new_word = "" - else: - # use random word length - wlen = choice(list(grammar.keys())) - - if wlen: - structure = choice(grammar[wlen]) - grammar2phonemes = self.language["grammar2phonemes"] - for match in _RE_GRAMMAR.finditer(structure): - # there are only four combinations: vv,cc,c,v - try: - new_word += choice(grammar2phonemes[match.group()]) - except KeyError: - logger.log_trace( - "You need to supply at least one example of each of " - "the four base phonemes (c, v, cc, vv)" - ) - # abort translation here - new_word = "" - break - - if word.istitle(): - title_word = "" - if not start_sentence and not self.language.get("noun_translate", False): - # don't translate what we identify as proper nouns (names) - title_word = word - elif new_word: - title_word = new_word - - if title_word: - # Regardless of if we translate or not, we will add the custom prefix/postfixes - new_word = "%s%s%s" % ( - self.language["noun_prefix"], - title_word.capitalize(), - self.language["noun_postfix"], - ) - - if len(word) > 1 and word.isupper(): - # keep LOUD words loud also when translated - new_word = new_word.upper() - return new_word - -
[docs] def translate(self, text, level=0.0, language="default"): - """ - Translate the text according to the given level. - - Args: - text (str): The text to translate - level (real): Value between 0.0 and 1.0, where - 0.0 means no obfuscation (text returned unchanged) and - 1.0 means full conversion of every word. The closer to - 1, the shorter words will be translated. - language (str): The language key identifier. - - Returns: - text (str): A translated string. - - """ - if level == 0.0: - # no translation - return text - language = self.db.language_storage.get(language, None) - if not language: - return text - self.language = language - - # configuring the translation - self.level = int(10 * (1.0 - max(0, min(level, 1.0)))) - translation = _RE_WORD.sub(self._translate_sub, text) - # the substitution may create too long empty spaces, remove those - return _RE_EXTRA_CHARS.sub("", translation)
- - -# Language access functions - -_LANGUAGE_HANDLER = None - - -
[docs]def obfuscate_language(text, level=0.0, language="default"): - """ - Main access method for the language parser. - - Args: - text (str): Text to obfuscate. - level (real, optional): A value from 0.0-1.0 determining - the level of obfuscation where 0 means no jobfuscation - (string returned unchanged) and 1.0 means the entire - string is obfuscated. - language (str, optional): The identifier of a language - the system understands. - - Returns: - translated (str): The translated text. - - """ - # initialize the language handler and cache it - global _LANGUAGE_HANDLER - if not _LANGUAGE_HANDLER: - try: - _LANGUAGE_HANDLER = LanguageHandler.objects.get(db_key="language_handler") - except LanguageHandler.DoesNotExist: - if not _LANGUAGE_HANDLER: - from evennia import create_script - - _LANGUAGE_HANDLER = create_script(LanguageHandler) - return _LANGUAGE_HANDLER.translate(text, level=level, language=language)
- - -
[docs]def add_language(**kwargs): - """ - Access function to creating a new language. See the docstring of - `LanguageHandler.add` for list of keyword arguments. - - """ - global _LANGUAGE_HANDLER - if not _LANGUAGE_HANDLER: - try: - _LANGUAGE_HANDLER = LanguageHandler.objects.get(db_key="language_handler") - except LanguageHandler.DoesNotExist: - if not _LANGUAGE_HANDLER: - from evennia import create_script - - _LANGUAGE_HANDLER = create_script(LanguageHandler) - _LANGUAGE_HANDLER.add(**kwargs)
- - -
[docs]def available_languages(): - """ - Returns all available language keys. - - Returns: - languages (list): List of key strings of all available - languages. - """ - global _LANGUAGE_HANDLER - if not _LANGUAGE_HANDLER: - try: - _LANGUAGE_HANDLER = LanguageHandler.objects.get(db_key="language_handler") - except LanguageHandler.DoesNotExist: - if not _LANGUAGE_HANDLER: - from evennia import create_script - - _LANGUAGE_HANDLER = create_script(LanguageHandler) - return list(_LANGUAGE_HANDLER.attributes.get("language_storage", {}))
- - -# ------------------------------------------------------------ -# -# Whisper obscuration -# -# This obsucration table is designed by obscuring certain -# vowels first, following by consonants that tend to be -# more audible over long distances, like s. Finally it -# does non-auditory replacements, like exclamation marks -# and capitalized letters (assumed to be spoken louder) that may still -# give a user some idea of the sentence structure. Then the word -# lengths are also obfuscated and finally the whisper # length itself. -# -# ------------------------------------------------------------ - - -_RE_WHISPER_OBSCURE = [ - re.compile(r"^$", _RE_FLAGS), # This is a Test! #0 full whisper - re.compile(r"[ae]", _RE_FLAGS), # This -s - Test! #1 add uy - re.compile(r"[aeuy]", _RE_FLAGS), # This -s - Test! #2 add oue - re.compile(r"[aeiouy]", _RE_FLAGS), # Th-s -s - T-st! #3 add all consonants - re.compile(r"[aeiouybdhjlmnpqrv]", _RE_FLAGS), # T--s -s - T-st! #4 add hard consonants - re.compile(r"[a-eg-rt-z]", _RE_FLAGS), # T--s -s - T-s-! #5 add all capitals - re.compile(r"[A-EG-RT-Za-eg-rt-z]", _RE_FLAGS), # ---s -s - --s-! #6 add f - re.compile(r"[A-EG-RT-Za-rt-z]", _RE_FLAGS), # ---s -s - --s-! #7 add s - re.compile(r"[A-EG-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #8 add capital F - re.compile(r"[A-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #9 add capital S - re.compile(r"[\w]", _RE_FLAGS), # ---- -- - ----! #10 non-alphanumerals - re.compile(r"[\S]", _RE_FLAGS), # ---- -- - ---- #11 words - re.compile(r"[\w\W]", _RE_FLAGS), # -------------- #12 whisper length - re.compile(r".*", _RE_FLAGS), -] # ... #13 (always same length) - - -
[docs]def obfuscate_whisper(whisper, level=0.0): - """ - Obfuscate whisper depending on a pre-calculated level - (that may depend on distance, listening skill etc) - - Args: - whisper (str): The whisper string to obscure. The - entire string will be considered in the obscuration. - level (real, optional): This is a value 0-1, where 0 - means not obscured (whisper returned unchanged) and 1 - means fully obscured. - - """ - level = min(max(0.0, level), 1.0) - olevel = int(13.0 * level) - if olevel == 13: - return "..." - else: - return _RE_WHISPER_OBSCURE[olevel].sub("-", whisper)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/rpsystem.html b/docs/0.9.5/_modules/evennia/contrib/rpsystem.html deleted file mode 100644 index 66e45959ff..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/rpsystem.html +++ /dev/null @@ -1,1732 +0,0 @@ - - - - - - - - evennia.contrib.rpsystem — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.rpsystem

-"""
-Roleplaying base system for Evennia
-
-Contribution - Griatch, 2015
-
-This module contains the ContribRPObject, ContribRPRoom and
-ContribRPCharacter typeclasses.  If you inherit your
-objects/rooms/character from these (or make them the defaults) from
-these you will get the following features:
-
-    - Objects/Rooms will get the ability to have poses and will report
-    the poses of items inside them (the latter most useful for Rooms).
-    - Characters will get poses and also sdescs (short descriptions)
-    that will be used instead of their keys. They will gain commands
-    for managing recognition (custom sdesc-replacement), masking
-    themselves as well as an advanced free-form emote command.
-
-To use, simply import the typclasses you want from this module and use
-them to create your objects, or set them to default.
-
-In more detail, This RP base system introduces the following features
-to a game, common to many RP-centric games:
-
-    - emote system using director stance emoting (names/sdescs).
-        This uses a customizable replacement noun (/me, @ etc) to
-        represent you in the emote. You can use /sdesc, /nick, /key or
-        /alias to reference objects in the room. You can use any
-        number of sdesc sub-parts to differentiate a local sdesc, or
-        use /1-sdesc etc to differentiate them. The emote also
-        identifies nested says.
-    - sdesc obscuration of real character names for use in emotes
-        and in any referencing such as object.search().  This relies
-        on an SdescHandler `sdesc` being set on the Character and
-        makes use of a custom Character.get_display_name hook. If
-        sdesc is not set, the character's `key` is used instead. This
-        is particularly used in the emoting system.
-    - recog system to assign your own nicknames to characters, can then
-        be used for referencing. The user may recog a user and assign
-        any personal nick to them. This will be shown in descriptions
-        and used to reference them. This is making use of the nick
-        functionality of Evennia.
-    - masks to hide your identity (using a simple lock).
-    - pose system to set room-persistent poses, visible in room
-        descriptions and when looking at the person/object.  This is a
-        simple Attribute that modifies how the characters is viewed when
-        in a room as sdesc + pose.
-    - in-emote says, including seamless integration with language
-        obscuration routine (such as contrib/rplanguage.py)
-
-Examples:
-
-> look
-Tavern
-The tavern is full of nice people
-
-*A tall man* is standing by the bar.
-
-Above is an example of a player with an sdesc "a tall man". It is also
-an example of a static *pose*: The "standing by the bar" has been set
-by the player of the tall man, so that people looking at him can tell
-at a glance what is going on.
-
-> emote /me looks at /tall and says "Hello!"
-
-I see:
-    Griatch looks at Tall man and says "Hello".
-Tall man (assuming his name is Tom) sees:
-    The godlike figure looks at Tom and says "Hello".
-
-Verbose Installation Instructions:
-
-    1. In typeclasses/character.py:
-       Import the `ContribRPCharacter` class:
-           `from evennia.contrib.rpsystem import ContribRPCharacter`
-       Inherit ContribRPCharacter:
-           Change "class Character(DefaultCharacter):" to
-           `class Character(ContribRPCharacter):`
-       If you have any overriden calls in `at_object_creation(self)`:
-           Add `super().at_object_creation()` as the top line.
-    2. In `typeclasses/rooms.py`:
-           Import the `ContribRPRoom` class:
-           `from evennia.contrib.rpsystem import ContribRPRoom`
-       Inherit `ContribRPRoom`:
-           Change `class Room(DefaultRoom):` to
-           `class Room(ContribRPRoom):`
-    3. In `typeclasses/objects.py`
-           Import the `ContribRPObject` class:
-           `from evennia.contrib.rpsystem import ContribRPObject`
-       Inherit `ContribRPObject`:
-           Change `class Object(DefaultObject):` to
-           `class Object(ContribRPObject):`
-    4. Reload the server (@reload or from console: "evennia reload")
-    5. Force typeclass updates as required. Example for your character:
-           @type/reset/force me = typeclasses.characters.Character
-
-"""
-import re
-from re import escape as re_escape
-import itertools
-from django.conf import settings
-from evennia import DefaultObject, DefaultCharacter, ObjectDB
-from evennia import Command, CmdSet
-from evennia import ansi
-from evennia.utils.utils import lazy_property, make_iter, variable_from_module
-
-_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
-# ------------------------------------------------------------
-# Emote parser
-# ------------------------------------------------------------
-
-# Settings
-
-# The prefix is the (single-character) symbol used to find the start
-# of a object reference, such as /tall (note that
-# the system will understand multi-word references like '/a tall man' too).
-_PREFIX = "/"
-
-# The num_sep is the (single-character) symbol used to separate the
-# sdesc from the number when  trying to separate identical sdescs from
-# one another. This is the same syntax used in the rest of Evennia, so
-# by default, multiple "tall" can be separated by entering 1-tall,
-# 2-tall etc.
-_NUM_SEP = "-"
-
-# Texts
-
-_EMOTE_NOMATCH_ERROR = """|RNo match for |r{ref}|R.|n"""
-
-_EMOTE_MULTIMATCH_ERROR = """|RMultiple possibilities for {ref}:
-    |r{reflist}|n"""
-
-_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE
-
-_RE_PREFIX = re.compile(r"^%s" % _PREFIX, re.UNICODE)
-
-# This regex will return groups (num, word), where num is an optional counter to
-# separate multimatches from one another and word is the first word in the
-# marker. So entering "/tall man" will return groups ("", "tall")
-# and "/2-tall man" will return groups ("2", "tall").
-_RE_OBJ_REF_START = re.compile(r"%s(?:([0-9]+)%s)*(\w+)" % (_PREFIX, _NUM_SEP), _RE_FLAGS)
-
-_RE_LEFT_BRACKETS = re.compile(r"\{+", _RE_FLAGS)
-_RE_RIGHT_BRACKETS = re.compile(r"\}+", _RE_FLAGS)
-# Reference markers are used internally when distributing the emote to
-# all that can see it. They are never seen by players and are on the form {#dbref}.
-_RE_REF = re.compile(r"\{+\#([0-9]+)\}+")
-
-# This regex is used to quickly reference one self in an emote.
-_RE_SELF_REF = re.compile(r"/me|@", _RE_FLAGS)
-
-# regex for non-alphanumberic end of a string
-_RE_CHAREND = re.compile(r"\W+$", _RE_FLAGS)
-
-# reference markers for language
-_RE_REF_LANG = re.compile(r"\{+\##([0-9]+)\}+")
-# language says in the emote are on the form "..." or langname"..." (no spaces).
-# this regex returns in groups (langname, say), where langname can be empty.
-_RE_LANGUAGE = re.compile(r"(?:\((\w+)\))*(\".+?\")")
-
-# the emote parser works in two steps:
-#  1) convert the incoming emote into an intermediary
-#     form with all object references mapped to ids.
-#  2) for every person seeing the emote, parse this
-#     intermediary form into the one valid for that char.
-
-
-
[docs]class EmoteError(Exception): - pass
- - -
[docs]class SdescError(Exception): - pass
- - -
[docs]class RecogError(Exception): - pass
- - -
[docs]class LanguageError(Exception): - pass
- - -def _dummy_process(text, *args, **kwargs): - "Pass-through processor" - return text - - -# emoting mechanisms - - -
[docs]def ordered_permutation_regex(sentence): - """ - Builds a regex that matches 'ordered permutations' of a sentence's - words. - - Args: - sentence (str): The sentence to build a match pattern to - - Returns: - regex (re object): Compiled regex object represented the - possible ordered permutations of the sentence, from longest to - shortest. - Example: - The sdesc_regex for an sdesc of " very tall man" will - result in the following allowed permutations, - regex-matched in inverse order of length (case-insensitive): - "the very tall man", "the very tall", "very tall man", - "very tall", "the very", "tall man", "the", "very", "tall", - and "man". - We also add regex to make sure it also accepts num-specifiers, - like /2-tall. - - """ - # escape {#nnn} markers from sentence, replace with nnn - sentence = _RE_REF.sub(r"\1", sentence) - # escape {##nnn} markers, replace with nnn - sentence = _RE_REF_LANG.sub(r"\1", sentence) - # escape self-ref marker from sentence - sentence = _RE_SELF_REF.sub(r"", sentence) - - # ordered permutation algorithm - words = sentence.split() - combinations = itertools.product((True, False), repeat=len(words)) - solution = [] - for combination in combinations: - comb = [] - for iword, word in enumerate(words): - if combination[iword]: - comb.append(word) - elif comb: - break - if comb: - solution.append( - _PREFIX - + r"[0-9]*%s*%s(?=\W|$)+" % (_NUM_SEP, re_escape(" ".join(comb)).rstrip("\\")) - ) - - # combine into a match regex, first matching the longest down to the shortest components - regex = r"|".join(sorted(set(solution), key=lambda item: (-len(item), item))) - return regex
- - -
[docs]def regex_tuple_from_key_alias(obj): - """ - This will build a regex tuple for any object, not just from those - with sdesc/recog handlers. It's used as a legacy mechanism for - being able to mix this contrib with objects not using sdescs, but - note that creating the ordered permutation regex dynamically for - every object will add computational overhead. - - Args: - obj (Object): This object's key and eventual aliases will - be used to build the tuple. - - Returns: - regex_tuple (tuple): A tuple - (ordered_permutation_regex, obj, key/alias) - - """ - return ( - re.compile(ordered_permutation_regex(" ".join([obj.key] + obj.aliases.all())), _RE_FLAGS), - obj, - obj.key, - )
- - -
[docs]def parse_language(speaker, emote): - """ - Parse the emote for language. This is - used with a plugin for handling languages. - - Args: - speaker (Object): The object speaking. - emote (str): An emote possibly containing - language references. - - Returns: - (emote, mapping) (tuple): A tuple where the - `emote` is the emote string with all says - (including quotes) replaced with reference - markers on the form {##n} where n is a running - number. The `mapping` is a dictionary between - the markers and a tuple (langname, saytext), where - langname can be None. - Raises: - rplanguage.LanguageError: If an invalid language was specified. - - Notes: - Note that no errors are raised if the wrong language identifier - is given. - This data, together with the identity of the speaker, is - intended to be used by the "listener" later, since with this - information the language skill of the speaker can be offset to - the language skill of the listener to determine how much - information is actually conveyed. - - """ - # escape mapping syntax on the form {##id} if it exists already in emote, - # if so it is replaced with just "id". - emote = _RE_REF_LANG.sub(r"\1", emote) - - errors = [] - mapping = {} - for imatch, say_match in enumerate(reversed(list(_RE_LANGUAGE.finditer(emote)))): - # process matches backwards to be able to replace - # in-place without messing up indexes for future matches - # note that saytext includes surrounding "...". - langname, saytext = say_match.groups() - istart, iend = say_match.start(), say_match.end() - # the key is simply the running match in the emote - key = "##%i" % imatch - # replace say with ref markers in emote - emote = emote[:istart] + "{%s}" % key + emote[iend:] - mapping[key] = (langname, saytext) - - if errors: - # catch errors and report - raise LanguageError("\n".join(errors)) - - # at this point all says have been replaced with {##nn} markers - # and mapping maps 1:1 to this. - return emote, mapping
- - -
[docs]def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False): - """ - Read a raw emote and parse it into an intermediary - format for distributing to all observers. - - Args: - sender (Object): The object sending the emote. This object's - recog data will be considered in the parsing. - candidates (iterable): A list of objects valid for referencing - in the emote. - string (str): The string (like an emote) we want to analyze for keywords. - search_mode (bool, optional): If `True`, the "emote" is a query string - we want to analyze. If so, the return value is changed. - - Returns: - (emote, mapping) (tuple): If `search_mode` is `False` - (default), a tuple where the emote is the emote string, with - all references replaced with internal-representation {#dbref} - markers and mapping is a dictionary `{"#dbref":obj, ...}`. - result (list): If `search_mode` is `True` we are - performing a search query on `string`, looking for a specific - object. A list with zero, one or more matches. - - Raises: - EmoteException: For various ref-matching errors. - - Notes: - The parser analyzes and should understand the following - _PREFIX-tagged structures in the emote: - - self-reference (/me) - - recogs (any part of it) stored on emoter, matching obj in `candidates`. - - sdesc (any part of it) from any obj in `candidates`. - - N-sdesc, N-recog separating multi-matches (1-tall, 2-tall) - - says, "..." are - - """ - # Load all candidate regex tuples [(regex, obj, sdesc/recog),...] - candidate_regexes = ( - ([(_RE_SELF_REF, sender, sender.sdesc.get())] if hasattr(sender, "sdesc") else []) - + ( - [sender.recog.get_regex_tuple(obj) for obj in candidates] - if hasattr(sender, "recog") - else [] - ) - + [obj.sdesc.get_regex_tuple() for obj in candidates if hasattr(obj, "sdesc")] - + [ - regex_tuple_from_key_alias(obj) # handle objects without sdescs - for obj in candidates - if not (hasattr(obj, "recog") and hasattr(obj, "sdesc")) - ] - ) - - # filter out non-found data - candidate_regexes = [tup for tup in candidate_regexes if tup] - - # escape mapping syntax on the form {#id} if it exists already in emote, - # if so it is replaced with just "id". - string = _RE_REF.sub(r"\1", string) - # escape loose { } brackets since this will clash with formatting - string = _RE_LEFT_BRACKETS.sub("{{", string) - string = _RE_RIGHT_BRACKETS.sub("}}", string) - - # we now loop over all references and analyze them - mapping = {} - errors = [] - obj = None - nmatches = 0 - for marker_match in reversed(list(_RE_OBJ_REF_START.finditer(string))): - # we scan backwards so we can replace in-situ without messing - # up later occurrences. Given a marker match, query from - # start index forward for all candidates. - - # first see if there is a number given (e.g. 1-tall) - num_identifier, _ = marker_match.groups("") # return "" if no match, rather than None - istart0 = marker_match.start() - istart = istart0 - - # loop over all candidate regexes and match against the string following the match - matches = ((reg.match(string[istart:]), obj, text) for reg, obj, text in candidate_regexes) - - # score matches by how long part of the string was matched - matches = [(match.end() if match else -1, obj, text) for match, obj, text in matches] - maxscore = max(score for score, obj, text in matches) - - # we have a valid maxscore, extract all matches with this value - bestmatches = [(obj, text) for score, obj, text in matches if maxscore == score != -1] - nmatches = len(bestmatches) - - if not nmatches: - # no matches - obj = None - nmatches = 0 - elif nmatches == 1: - # an exact match. - obj = bestmatches[0][0] - nmatches = 1 - elif all(bestmatches[0][0].id == obj.id for obj, text in bestmatches): - # multi-match but all matches actually reference the same - # obj (could happen with clashing recogs + sdescs) - obj = bestmatches[0][0] - nmatches = 1 - else: - # multi-match. - # was a numerical identifier given to help us separate the multi-match? - inum = min(max(0, int(num_identifier) - 1), nmatches - 1) if num_identifier else None - if inum is not None: - # A valid inum is given. Use this to separate data. - obj = bestmatches[inum][0] - nmatches = 1 - else: - # no identifier given - a real multimatch. - obj = bestmatches - - if search_mode: - # single-object search mode. Don't continue loop. - break - elif nmatches == 0: - errors.append(_EMOTE_NOMATCH_ERROR.format(ref=marker_match.group())) - elif nmatches == 1: - key = "#%i" % obj.id - string = string[:istart0] + "{%s}" % key + string[istart + maxscore :] - mapping[key] = obj - else: - refname = marker_match.group() - reflist = [ - "%s%s%s (%s%s)" - % ( - inum + 1, - _NUM_SEP, - _RE_PREFIX.sub("", refname), - text, - " (%s)" % sender.key if sender == ob else "", - ) - for inum, (ob, text) in enumerate(obj) - ] - errors.append( - _EMOTE_MULTIMATCH_ERROR.format( - ref=marker_match.group(), reflist="\n ".join(reflist) - ) - ) - if search_mode: - # return list of object(s) matching - if nmatches == 0: - return [] - elif nmatches == 1: - return [obj] - else: - return [tup[0] for tup in obj] - - if errors: - # make sure to not let errors through. - raise EmoteError("\n".join(errors)) - - # at this point all references have been replaced with {#xxx} markers and the mapping contains - # a 1:1 mapping between those inline markers and objects. - return string, mapping
- - -
[docs]def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): - """ - Main access function for distribute an emote. - - Args: - sender (Object): The one sending the emote. - receivers (iterable): Receivers of the emote. These - will also form the basis for which sdescs are - 'valid' to use in the emote. - emote (str): The raw emote string as input by emoter. - anonymous_add (str or None, optional): If `sender` is not - self-referencing in the emote, this will auto-add - `sender`'s data to the emote. Possible values are - - None: No auto-add at anonymous emote - - 'last': Add sender to the end of emote as [sender] - - 'first': Prepend sender to start of emote. - - """ - try: - emote, obj_mapping = parse_sdescs_and_recogs(sender, receivers, emote) - emote, language_mapping = parse_language(sender, emote) - except (EmoteError, LanguageError) as err: - # handle all error messages, don't hide actual coding errors - sender.msg(str(err)) - return - # we escape the object mappings since we'll do the language ones first - # (the text could have nested object mappings). - emote = _RE_REF.sub(r"{{#\1}}", emote) - # if anonymous_add is passed as a kwarg, collect and remove it from kwargs - if 'anonymous_add' in kwargs: - anonymous_add = kwargs.pop('anonymous_add') - if anonymous_add and not "#%i" % sender.id in obj_mapping: - # no self-reference in the emote - add to the end - key = "#%i" % sender.id - obj_mapping[key] = sender - if anonymous_add == "first": - possessive = "" if emote.startswith("'") else " " - emote = "%s%s%s" % ("{{%s}}" % key, possessive, emote) - else: - emote = "%s [%s]" % (emote, "{{%s}}" % key) - - # broadcast emote to everyone - for receiver in receivers: - # first handle the language mapping, which always produce different keys ##nn - receiver_lang_mapping = {} - try: - process_language = receiver.process_language - except AttributeError: - process_language = _dummy_process - for key, (langname, saytext) in language_mapping.items(): - # color says - receiver_lang_mapping[key] = process_language(saytext, sender, langname) - # map the language {##num} markers. This will convert the escaped sdesc markers on - # the form {{#num}} to {#num} markers ready to sdescmat in the next step. - sendemote = emote.format(**receiver_lang_mapping) - - # handle sdesc mappings. we make a temporary copy that we can modify - try: - process_sdesc = receiver.process_sdesc - except AttributeError: - process_sdesc = _dummy_process - - try: - process_recog = receiver.process_recog - except AttributeError: - process_recog = _dummy_process - - try: - recog_get = receiver.recog.get - receiver_sdesc_mapping = dict( - (ref, process_recog(recog_get(obj), obj)) for ref, obj in obj_mapping.items() - ) - except AttributeError: - receiver_sdesc_mapping = dict( - ( - ref, - process_sdesc(obj.sdesc.get(), obj) - if hasattr(obj, "sdesc") - else process_sdesc(obj.key, obj), - ) - for ref, obj in obj_mapping.items() - ) - # make sure receiver always sees their real name - rkey = "#%i" % receiver.id - if rkey in receiver_sdesc_mapping: - receiver_sdesc_mapping[rkey] = process_sdesc(receiver.key, receiver) - - # do the template replacement of the sdesc/recog {#num} markers - receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs)
- - -# ------------------------------------------------------------ -# Handlers for sdesc and recog -# ------------------------------------------------------------ - - -
[docs]class SdescHandler(object): - """ - This Handler wraps all operations with sdescs. We - need to use this since we do a lot preparations on - sdescs when updating them, in order for them to be - efficient to search for and query. - - The handler stores data in the following Attributes - - _sdesc - a string - _regex - an empty dictionary - - """ - -
[docs] def __init__(self, obj): - """ - Initialize the handler - - Args: - obj (Object): The entity on which this handler is stored. - - """ - self.obj = obj - self.sdesc = "" - self.sdesc_regex = "" - self._cache()
- - def _cache(self): - """ - Cache data from storage - - """ - self.sdesc = self.obj.attributes.get("_sdesc", default="") - sdesc_regex = self.obj.attributes.get("_sdesc_regex", default="") - self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) - -
[docs] def add(self, sdesc, max_length=60): - """ - Add a new sdesc to object, replacing the old one. - - Args: - sdesc (str): The sdesc to set. This may be stripped - of control sequences before setting. - max_length (int, optional): The max limit of the sdesc. - - Returns: - sdesc (str): The actually set sdesc. - - Raises: - SdescError: If the sdesc is empty, can not be set or is - longer than `max_length`. - - """ - # strip emote components from sdesc - sdesc = _RE_REF.sub( - r"\1", - _RE_REF_LANG.sub( - r"\1", - _RE_SELF_REF.sub(r"", _RE_LANGUAGE.sub(r"", _RE_OBJ_REF_START.sub(r"", sdesc))), - ), - ) - - # make an sdesc clean of ANSI codes - cleaned_sdesc = ansi.strip_ansi(sdesc) - - if not cleaned_sdesc: - raise SdescError("Short desc cannot be empty.") - - if len(cleaned_sdesc) > max_length: - raise SdescError( - "Short desc can max be %i chars long (was %i chars)." - % (max_length, len(cleaned_sdesc)) - ) - - # store to attributes - sdesc_regex = ordered_permutation_regex(cleaned_sdesc) - self.obj.attributes.add("_sdesc", sdesc) - self.obj.attributes.add("_sdesc_regex", sdesc_regex) - # local caching - self.sdesc = sdesc - self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) - - return sdesc
- -
[docs] def get(self): - """ - Simple getter. The sdesc should never be allowed to - be empty, but if it is we must fall back to the key. - - """ - return self.sdesc or self.obj.key
- -
[docs] def get_regex_tuple(self): - """ - Return data for sdesc/recog handling - - Returns: - tup (tuple): tuple (sdesc_regex, obj, sdesc) - - """ - return self.sdesc_regex, self.obj, self.sdesc
- - -
[docs]class RecogHandler(object): - """ - This handler manages the recognition mapping - of an Object. - - The handler stores data in Attributes as dictionaries of - the following names: - - _recog_ref2recog - _recog_obj2recog - _recog_obj2regex - - """ - -
[docs] def __init__(self, obj): - """ - Initialize the handler - - Args: - obj (Object): The entity on which this handler is stored. - - """ - self.obj = obj - # mappings - self.ref2recog = {} - self.obj2regex = {} - self.obj2recog = {} - self._cache()
- - def _cache(self): - """ - Load data to handler cache - """ - self.ref2recog = self.obj.attributes.get("_recog_ref2recog", default={}) - obj2regex = self.obj.attributes.get("_recog_obj2regex", default={}) - obj2recog = self.obj.attributes.get("_recog_obj2recog", default={}) - self.obj2regex = dict( - (obj, re.compile(regex, _RE_FLAGS)) for obj, regex in obj2regex.items() if obj - ) - self.obj2recog = dict((obj, recog) for obj, recog in obj2recog.items() if obj) - -
[docs] def add(self, obj, recog, max_length=60): - """ - Assign a custom recog (nick) to the given object. - - Args: - obj (Object): The object ot associate with the recog - string. This is usually determined from the sdesc in the - room by a call to parse_sdescs_and_recogs, but can also be - given. - recog (str): The replacement string to use with this object. - max_length (int, optional): The max length of the recog string. - - Returns: - recog (str): The (possibly cleaned up) recog string actually set. - - Raises: - SdescError: When recog could not be set or sdesc longer - than `max_length`. - - """ - if not obj.access(self.obj, "enable_recog", default=True): - raise SdescError("This person is unrecognizeable.") - - # strip emote components from recog - recog = _RE_REF.sub( - r"\1", - _RE_REF_LANG.sub( - r"\1", - _RE_SELF_REF.sub(r"", _RE_LANGUAGE.sub(r"", _RE_OBJ_REF_START.sub(r"", recog))), - ), - ) - - # make an recog clean of ANSI codes - cleaned_recog = ansi.strip_ansi(recog) - - if not cleaned_recog: - raise SdescError("Recog string cannot be empty.") - - if len(cleaned_recog) > max_length: - raise RecogError( - "Recog string cannot be longer than %i chars (was %i chars)" - % (max_length, len(cleaned_recog)) - ) - - # mapping #dbref:obj - key = "#%i" % obj.id - self.obj.attributes.get("_recog_ref2recog", default={})[key] = recog - self.obj.attributes.get("_recog_obj2recog", default={})[obj] = recog - regex = ordered_permutation_regex(cleaned_recog) - self.obj.attributes.get("_recog_obj2regex", default={})[obj] = regex - # local caching - self.ref2recog[key] = recog - self.obj2recog[obj] = recog - self.obj2regex[obj] = re.compile(regex, _RE_FLAGS) - return recog
- -
[docs] def get(self, obj): - """ - Get recog replacement string, if one exists, otherwise - get sdesc and as a last resort, the object's key. - - Args: - obj (Object): The object, whose sdesc to replace - Returns: - recog (str): The replacement string to use. - - Notes: - This method will respect a "enable_recog" lock set on - `obj` (True by default) in order to turn off recog - mechanism. This is useful for adding masks/hoods etc. - """ - if obj.access(self.obj, "enable_recog", default=True): - # check an eventual recog_masked lock on the object - # to avoid revealing masked characters. If lock - # does not exist, pass automatically. - return self.obj2recog.get(obj, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key) - else: - # recog_mask log not passed, disable recog - return obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key
- -
[docs] def all(self): - """ - Get a mapping of the recogs stored in handler. - - Returns: - recogs (dict): A mapping of {recog: obj} stored in handler. - - """ - return {self.obj2recog[obj]: obj for obj in self.obj2recog.keys()}
- -
[docs] def remove(self, obj): - """ - Clear recog for a given object. - - Args: - obj (Object): The object for which to remove recog. - """ - if obj in self.obj2recog: - del self.obj.db._recog_obj2recog[obj] - del self.obj.db._recog_obj2regex[obj] - del self.obj.db._recog_ref2recog["#%i" % obj.id] - self._cache()
- -
[docs] def get_regex_tuple(self, obj): - """ - Returns: - rec (tuple): Tuple (recog_regex, obj, recog) - """ - if obj in self.obj2recog and obj.access(self.obj, "enable_recog", default=True): - return self.obj2regex[obj], obj, self.obj2regex[obj] - return None
- - -# ------------------------------------------------------------ -# RP Commands -# ------------------------------------------------------------ - - -
[docs]class RPCommand(Command): - "simple parent" - -
[docs] def parse(self): - "strip extra whitespace" - self.args = self.args.strip()
- - -
[docs]class CmdEmote(RPCommand): # replaces the main emote - """ - Emote an action, allowing dynamic replacement of - text in the emote. - - Usage: - emote text - - Example: - emote /me looks around. - emote With a flurry /me attacks /tall man with his sword. - emote "Hello", /me says. - - Describes an event in the world. This allows the use of /ref - markers to replace with the short descriptions or recognized - strings of objects in the same room. These will be translated to - emotes to match each person seeing it. Use "..." for saying - things and langcode"..." without spaces to say something in - a different language. - - """ - - key = "emote" - aliases = [":"] - locks = "cmd:all()" - -
[docs] def func(self): - "Perform the emote." - if not self.args: - self.caller.msg("What do you want to do?") - else: - # we also include ourselves here. - emote = self.args - targets = self.caller.location.contents - if not emote.endswith((".", "?", "!")): # If emote is not punctuated, - emote = "%s." % emote # add a full-stop for good measure. - send_emote(self.caller, targets, emote, anonymous_add="first")
- - -
[docs]class CmdSay(RPCommand): # replaces standard say - """ - speak as your character - - Usage: - say <message> - - Talk to those in your current location. - """ - - key = "say" - aliases = ['"', "'"] - locks = "cmd:all()" - -
[docs] def func(self): - "Run the say command" - - caller = self.caller - - if not self.args: - caller.msg("Say what?") - return - - # calling the speech modifying hook - speech = caller.at_before_say(self.args) - # preparing the speech with sdesc/speech parsing. - targets = self.caller.location.contents - send_emote(self.caller, targets, speech, anonymous_add=None)
- - -
[docs]class CmdSdesc(RPCommand): # set/look at own sdesc - """ - Assign yourself a short description (sdesc). - - Usage: - sdesc <short description> - - Assigns a short description to yourself. - - """ - - key = "sdesc" - locks = "cmd:all()" - -
[docs] def func(self): - "Assign the sdesc" - caller = self.caller - if not self.args: - caller.msg("Usage: sdesc <sdesc-text>") - return - else: - # strip non-alfanum chars from end of sdesc - sdesc = _RE_CHAREND.sub("", self.args) - try: - sdesc = caller.sdesc.add(sdesc) - except SdescError as err: - caller.msg(err) - return - except AttributeError: - caller.msg(f"Cannot set sdesc on {caller.key}.") - return - caller.msg("%s's sdesc was set to '%s'." % (caller.key, sdesc))
- - -
[docs]class CmdPose(RPCommand): # set current pose and default pose - """ - Set a static pose - - Usage: - pose <pose> - pose default <pose> - pose reset - pose obj = <pose> - pose default obj = <pose> - pose reset obj = - - Examples: - pose leans against the tree - pose is talking to the barkeep. - pose box = is sitting on the floor. - - Set a static pose. This is the end of a full sentence that starts - with your sdesc. If no full stop is given, it will be added - automatically. The default pose is the pose you get when using - pose reset. Note that you can use sdescs/recogs to reference - people in your pose, but these always appear as that person's - sdesc in the emote, regardless of who is seeing it. - - """ - - key = "pose" - -
[docs] def parse(self): - """ - Extract the "default" alternative to the pose. - """ - args = self.args.strip() - default = args.startswith("default") - reset = args.startswith("reset") - if default: - args = re.sub(r"^default", "", args) - if reset: - args = re.sub(r"^reset", "", args) - target = None - if "=" in args: - target, args = [part.strip() for part in args.split("=", 1)] - - self.target = target - self.reset = reset - self.default = default - self.args = args.strip()
- -
[docs] def func(self): - "Create the pose" - caller = self.caller - pose = self.args - target = self.target - if not pose and not self.reset: - caller.msg("Usage: pose <pose-text> OR pose obj = <pose-text>") - return - - if not pose.endswith("."): - pose = "%s." % pose - if target: - # affect something else - target = caller.search(target) - if not target: - return - if not target.access(caller, "edit"): - caller.msg("You can't pose that.") - return - else: - target = caller - - if not target.attributes.has("pose"): - caller.msg("%s cannot be posed." % target.key) - return - - target_name = target.sdesc.get() if hasattr(target, "sdesc") else target.key - # set the pose - if self.reset: - pose = target.db.pose_default - target.db.pose = pose - elif self.default: - target.db.pose_default = pose - caller.msg("Default pose is now '%s %s'." % (target_name, pose)) - return - else: - # set the pose. We do one-time ref->sdesc mapping here. - parsed, mapping = parse_sdescs_and_recogs(caller, caller.location.contents, pose) - mapping = dict( - (ref, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key) - for ref, obj in mapping.items() - ) - pose = parsed.format(**mapping) - - if len(target_name) + len(pose) > 60: - caller.msg("Your pose '%s' is too long." % pose) - return - - target.db.pose = pose - - caller.msg("Pose will read '%s %s'." % (target_name, pose))
- - -
[docs]class CmdRecog(RPCommand): # assign personal alias to object in room - """ - Recognize another person in the same room. - - Usage: - recog - recog sdesc as alias - forget alias - - Example: - recog tall man as Griatch - forget griatch - - This will assign a personal alias for a person, or forget said alias. - Using the command without arguments will list all current recogs. - - """ - - key = "recog" - aliases = ["recognize", "forget"] - -
[docs] def parse(self): - "Parse for the sdesc as alias structure" - self.sdesc, self.alias = "", "" - if " as " in self.args: - self.sdesc, self.alias = [part.strip() for part in self.args.split(" as ", 2)] - elif self.args: - # try to split by space instead - try: - self.sdesc, self.alias = [part.strip() for part in self.args.split(None, 1)] - except ValueError: - self.sdesc, self.alias = self.args.strip(), ""
- -
[docs] def func(self): - "Assign the recog" - caller = self.caller - alias = self.alias.rstrip(".?!") - sdesc = self.sdesc - - recog_mode = self.cmdstring != "forget" and alias and sdesc - forget_mode = self.cmdstring == "forget" and sdesc - list_mode = not self.args - - if not (recog_mode or forget_mode or list_mode): - caller.msg("Usage: recog, recog <sdesc> as <alias> or forget <alias>") - return - - if list_mode: - # list all previously set recogs - all_recogs = caller.recog.all() - if not all_recogs: - caller.msg( - "You recognize no-one. " "(Use 'recog <sdesc> as <alias>' to recognize people." - ) - else: - # note that we don't skip those failing enable_recog lock here, - # because that would actually reveal more than we want. - lst = "\n".join( - " {} ({})".format(key, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key) - for key, obj in all_recogs.items() - ) - caller.msg( - f"Currently recognized (use 'recog <sdesc> as <alias>' to add " - f"new and 'forget <alias>' to remove):\n{lst}" - ) - return - - prefixed_sdesc = sdesc if sdesc.startswith(_PREFIX) else _PREFIX + sdesc - candidates = caller.location.contents - matches = parse_sdescs_and_recogs(caller, candidates, prefixed_sdesc, search_mode=True) - nmatches = len(matches) - # handle 0 and >1 matches - if nmatches == 0: - caller.msg(_EMOTE_NOMATCH_ERROR.format(ref=sdesc)) - elif nmatches > 1: - reflist = [ - "{}{}{} ({}{})".format( - inum + 1, - _NUM_SEP, - _RE_PREFIX.sub("", sdesc), - caller.recog.get(obj), - " (%s)" % caller.key if caller == obj else "", - ) - for inum, obj in enumerate(matches) - ] - caller.msg(_EMOTE_MULTIMATCH_ERROR.format(ref=sdesc, reflist="\n ".join(reflist))) - - else: - # one single match - obj = matches[0] - if not obj.access(self.obj, "enable_recog", default=True): - # don't apply recog if object doesn't allow it (e.g. by being masked). - caller.msg("It's impossible to recognize them.") - return - if forget_mode: - # remove existing recog - caller.recog.remove(obj) - caller.msg("%s will now know them only as '%s'." % (caller.key, obj.recog.get(obj))) - else: - # set recog - sdesc = obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key - try: - alias = caller.recog.add(obj, alias) - except RecogError as err: - caller.msg(err) - return - caller.msg("%s will now remember |w%s|n as |w%s|n." % (caller.key, sdesc, alias))
- - -
[docs]class CmdMask(RPCommand): - """ - Wear a mask - - Usage: - mask <new sdesc> - unmask - - This will put on a mask to hide your identity. When wearing - a mask, your sdesc will be replaced by the sdesc you pick and - people's recognitions of you will be disabled. - - """ - - key = "mask" - aliases = ["unmask"] - -
[docs] def func(self): - caller = self.caller - if self.cmdstring == "mask": - # wear a mask - if not self.args: - caller.msg("Usage: (un)mask sdesc") - return - if caller.db.unmasked_sdesc: - caller.msg("You are already wearing a mask.") - return - sdesc = _RE_CHAREND.sub("", self.args) - sdesc = "%s |H[masked]|n" % sdesc - if len(sdesc) > 60: - caller.msg("Your masked sdesc is too long.") - return - caller.db.unmasked_sdesc = caller.sdesc.get() - caller.locks.add("enable_recog:false()") - caller.sdesc.add(sdesc) - caller.msg("You wear a mask as '%s'." % sdesc) - else: - # unmask - old_sdesc = caller.db.unmasked_sdesc - if not old_sdesc: - caller.msg("You are not wearing a mask.") - return - del caller.db.unmasked_sdesc - caller.locks.remove("enable_recog") - caller.sdesc.add(old_sdesc) - caller.msg("You remove your mask and are again '%s'." % old_sdesc)
- - -
[docs]class RPSystemCmdSet(CmdSet): - """ - Mix-in for adding rp-commands to default cmdset. - """ - -
[docs] def at_cmdset_creation(self): - self.add(CmdEmote()) - self.add(CmdSay()) - self.add(CmdSdesc()) - self.add(CmdPose()) - self.add(CmdRecog()) - self.add(CmdMask())
- - -# ------------------------------------------------------------ -# RP typeclasses -# ------------------------------------------------------------ - - -
[docs]class ContribRPObject(DefaultObject): - """ - This class is meant as a mix-in or parent for objects in an - rp-heavy game. It implements the base functionality for poses. - """ - -
[docs] def at_object_creation(self): - """ - Called at initial creation. - """ - super().at_object_creation() - - # emoting/recog data - self.db.pose = "" - self.db.pose_default = "is here."
- -
[docs] def search( - self, - searchdata, - global_search=False, - use_nicks=True, - typeclass=None, - location=None, - attribute_name=None, - quiet=False, - exact=False, - candidates=None, - nofound_string=None, - multimatch_string=None, - use_dbref=None, - ): - """ - Returns an Object matching a search string/condition, taking - sdescs into account. - - Perform a standard object search in the database, handling - multiple results and lack thereof gracefully. By default, only - objects in the current `location` of `self` or its inventory are searched for. - - Args: - searchdata (str or obj): Primary search criterion. Will be matched - against `object.key` (with `object.aliases` second) unless - the keyword attribute_name specifies otherwise. - **Special strings:** - - `#<num>`: search by unique dbref. This is always - a global search. - - `me,self`: self-reference to this object - - `<num>-<string>` - can be used to differentiate - between multiple same-named matches - global_search (bool): Search all objects globally. This is overruled - by `location` keyword. - use_nicks (bool): Use nickname-replace (nicktype "object") on `searchdata`. - typeclass (str or Typeclass, or list of either): Limit search only - to `Objects` with this typeclass. May be a list of typeclasses - for a broader search. - location (Object or list): Specify a location or multiple locations - to search. Note that this is used to query the *contents* of a - location and will not match for the location itself - - if you want that, don't set this or use `candidates` to specify - exactly which objects should be searched. - attribute_name (str): Define which property to search. If set, no - key+alias search will be performed. This can be used - to search database fields (db_ will be automatically - appended), and if that fails, it will try to return - objects having Attributes with this name and value - equal to searchdata. A special use is to search for - "key" here if you want to do a key-search without - including aliases. - quiet (bool): don't display default error messages - this tells the - search method that the user wants to handle all errors - themselves. It also changes the return value type, see - below. - exact (bool): if unset (default) - prefers to match to beginning of - string rather than not matching at all. If set, requires - exact matching of entire string. - candidates (list of objects): this is an optional custom list of objects - to search (filter) between. It is ignored if `global_search` - is given. If not set, this list will automatically be defined - to include the location, the contents of location and the - caller's contents (inventory). - nofound_string (str): optional custom string for not-found error message. - multimatch_string (str): optional custom string for multimatch error header. - use_dbref (bool or None): If None, only turn off use_dbref if we are of a lower - permission than Builder. Otherwise, honor the True/False value. - - 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. - - Notes: - To find Accounts, use eg. `evennia.account_search`. If - `quiet=False`, error messages will be handled by - `settings.SEARCH_AT_RESULT` and echoed automatically (on - error, return will be `None`). If `quiet=True`, the error - messaging is assumed to be handled by the caller. - - """ - is_string = isinstance(searchdata, str) - - if is_string: - # searchdata is a string; wrap some common self-references - if searchdata.lower() in ("here",): - return [self.location] if quiet else self.location - if searchdata.lower() in ("me", "self"): - return [self] if quiet else self - - if use_nicks: - # do nick-replacement on search - searchdata = self.nicks.nickreplace( - searchdata, categories=("object", "account"), include_account=True - ) - - if global_search or ( - is_string - and searchdata.startswith("#") - and len(searchdata) > 1 - and searchdata[1:].isdigit() - ): - # only allow exact matching if searching the entire database - # or unique #dbrefs - exact = True - elif candidates is None: - # no custom candidates given - get them automatically - if location: - # location(s) were given - candidates = [] - for obj in make_iter(location): - candidates.extend(obj.contents) - else: - # local search. Candidates are taken from - # self.contents, self.location and - # self.location.contents - location = self.location - candidates = self.contents - if location: - candidates = candidates + [location] + location.contents - else: - # normally we don't need this since we are - # included in location.contents - candidates.append(self) - - # the sdesc-related substitution - is_builder = self.locks.check_lockstring(self, "perm(Builder)") - use_dbref = is_builder if use_dbref is None else use_dbref - - def search_obj(string): - "helper wrapper for searching" - return ObjectDB.objects.object_search( - string, - attribute_name=attribute_name, - typeclass=typeclass, - candidates=candidates, - exact=exact, - use_dbref=use_dbref, - ) - - if candidates: - candidates = parse_sdescs_and_recogs( - self, candidates, _PREFIX + searchdata, search_mode=True - ) - results = [] - for candidate in candidates: - # we search by candidate keys here; this allows full error - # management and use of all kwargs - we will use searchdata - # in eventual error reporting later (not their keys). Doing - # it like this e.g. allows for use of the typeclass kwarg - # limiter. - results.extend([obj for obj in search_obj(candidate.key) if obj not in results]) - - if not results and is_builder: - # builders get a chance to search only by key+alias - results = search_obj(searchdata) - else: - # global searches / #drefs end up here. Global searches are - # only done in code, so is controlled, #dbrefs are turned off - # for non-Builders. - results = search_obj(searchdata) - - if quiet: - return results - return _AT_SEARCH_RESULT( - results, - self, - query=searchdata, - nofound_string=nofound_string, - multimatch_string=multimatch_string, - )
- -
[docs] def get_display_name(self, looker, **kwargs): - """ - Displays the name of the object in a viewer-aware manner. - - Args: - looker (TypedObject): The object or account that is looking - at/getting inforamtion for this object. - - Keyword Args: - pose (bool): Include the pose (if available) in the return. - - Returns: - name (str): A string of the sdesc containing the name of the object, - if this is defined. - including the DBREF if this user is privileged to control - said object. - - Notes: - The RPObject version doesn't add color to its display. - - """ - idstr = "(#%s)" % self.id if self.access(looker, access_type="control") else "" - if looker == self: - sdesc = self.key - else: - try: - recog = looker.recog.get(self) - except AttributeError: - recog = None - sdesc = recog or (hasattr(self, "sdesc") and self.sdesc.get()) or self.key - pose = " %s" % (self.db.pose or "") if kwargs.get("pose", False) else "" - return "%s%s%s" % (sdesc, idstr, pose)
- -
[docs] def return_appearance(self, looker): - """ - This formats a description. It is the hook a 'look' command - should call. - - Args: - looker (Object): Object doing the looking. - """ - if not looker: - return "" - # get and identify all objects - visible = (con for con in self.contents if con != looker and con.access(looker, "view")) - exits, users, things = [], [], [] - for con in visible: - key = con.get_display_name(looker, pose=True) - if con.destination: - exits.append(key) - elif con.has_account: - users.append(key) - else: - things.append(key) - # get description, build string - string = "|c%s|n\n" % self.get_display_name(looker, pose=True) - desc = self.db.desc - if desc: - string += "%s" % desc - if exits: - string += "\n|wExits:|n " + ", ".join(exits) - if users or things: - string += "\n " + "\n ".join(users + things) - return string
- - -
[docs]class ContribRPRoom(ContribRPObject): - """ - Dummy inheritance for rooms. - """ - - pass
- - -
[docs]class ContribRPCharacter(DefaultCharacter, ContribRPObject): - """ - This is a character class that has poses, sdesc and recog. - """ - - # Handlers -
[docs] @lazy_property - def sdesc(self): - return SdescHandler(self)
- -
[docs] @lazy_property - def recog(self): - return RecogHandler(self)
- -
[docs] def get_display_name(self, looker, **kwargs): - """ - Displays the name of the object in a viewer-aware manner. - - Args: - looker (TypedObject): The object or account that is looking - at/getting inforamtion for this object. - - Keyword Args: - pose (bool): Include the pose (if available) in the return. - - Returns: - name (str): A string of the sdesc containing the name of the object, - if this is defined. - including the DBREF if this user is privileged to control - said object. - - Notes: - The RPCharacter version of this method colors its display to make - characters stand out from other objects. - - """ - idstr = "(#%s)" % self.id if self.access(looker, access_type="control") else "" - if looker == self: - sdesc = self.key - else: - try: - recog = looker.recog.get(self) - except AttributeError: - recog = None - sdesc = recog or (hasattr(self, "sdesc") and self.sdesc.get()) or self.key - pose = " %s" % (self.db.pose or "is here.") if kwargs.get("pose", False) else "" - return "|c%s|n%s%s" % (sdesc, idstr, pose)
- -
[docs] def at_object_creation(self): - """ - Called at initial creation. - """ - super().at_object_creation() - - self.db._sdesc = "" - self.db._sdesc_regex = "" - - self.db._recog_ref2recog = {} - self.db._recog_obj2regex = {} - self.db._recog_obj2recog = {} - - self.cmdset.add(RPSystemCmdSet, permanent=True) - # initializing sdesc - self.sdesc.add("A normal person")
- -
[docs] def at_before_say(self, message, **kwargs): - """ - Called before the object says or whispers anything, return modified message. - - Args: - message (str): The suggested say/whisper text spoken by self. - Keyword Args: - whisper (bool): If True, this is a whisper rather than a say. - - """ - if kwargs.get("whisper"): - return f'/me whispers "{message}"' - return f'/me says, "{message}"'
- -
[docs] def process_sdesc(self, sdesc, obj, **kwargs): - """ - Allows to customize how your sdesc is displayed (primarily by - changing colors). - - Args: - sdesc (str): The sdesc to display. - obj (Object): The object to which the adjoining sdesc - belongs. If this object is equal to yourself, then - you are viewing yourself (and sdesc is your key). - This is not used by default. - - Returns: - sdesc (str): The processed sdesc ready - for display. - - """ - return "|b%s|n" % sdesc
- -
[docs] def process_recog(self, recog, obj, **kwargs): - """ - Allows to customize how a recog string is displayed. - - Args: - recog (str): The recog string. It has already been - translated from the original sdesc at this point. - obj (Object): The object the recog:ed string belongs to. - This is not used by default. - - Returns: - recog (str): The modified recog string. - - """ - return self.process_sdesc(recog, obj)
- -
[docs] def process_language(self, text, speaker, language, **kwargs): - """ - Allows to process the spoken text, for example - by obfuscating language based on your and the - speaker's language skills. Also a good place to - put coloring. - - Args: - text (str): The text to process. - speaker (Object): The object delivering the text. - language (str): An identifier string for the language. - - Return: - text (str): The optionally processed text. - - Notes: - This is designed to work together with a string obfuscator - such as the `obfuscate_language` or `obfuscate_whisper` in - the evennia.contrib.rplanguage module. - - """ - return "%s|w%s|n" % ("|W(%s)" % language if language else "", text)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/security/auditing/outputs.html b/docs/0.9.5/_modules/evennia/contrib/security/auditing/outputs.html deleted file mode 100644 index c5df1764b5..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/security/auditing/outputs.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - evennia.contrib.security.auditing.outputs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.security.auditing.outputs

-"""
-Auditable Server Sessions - Example Outputs
-Example methods demonstrating output destinations for logs generated by
-audited server sessions.
-
-This is designed to be a single source of events for developers to customize
-and add any additional enhancements before events are written out-- i.e. if you
-want to keep a running list of what IPs a user logs in from on account/character
-objects, or if you want to perform geoip or ASN lookups on IPs before committing,
-or tag certain events with the results of a reputational lookup, this should be
-the easiest place to do it. Write a method and invoke it via
-`settings.AUDIT_CALLBACK` to have log data objects passed to it.
-
-Evennia contribution - Johnny 2017
-"""
-from evennia.utils.logger import log_file
-import json
-import syslog
-
-
-
[docs]def to_file(data): - """ - Writes dictionaries of data generated by an AuditedServerSession to files - in JSON format, bucketed by date. - - Uses Evennia's native logger and writes to the default - log directory (~/yourgame/server/logs/ or settings.LOG_DIR) - - Args: - data (dict): Parsed session transmission data. - - """ - # Bucket logs by day and remove objects before serialization - bucket = data.pop("objects")["time"].strftime("%Y-%m-%d") - - # Write it - log_file(json.dumps(data), filename="audit_%s.log" % bucket)
- - -
[docs]def to_syslog(data): - """ - Writes dictionaries of data generated by an AuditedServerSession to syslog. - - Takes advantage of your system's native logger and writes to wherever - you have it configured, which is independent of Evennia. - Linux systems tend to write to /var/log/syslog. - - If you're running rsyslog, you can configure it to dump and/or forward logs - to disk and/or an external data warehouse (recommended-- if your server is - compromised or taken down, losing your logs along with it is no help!). - - Args: - data (dict): Parsed session transmission data. - - """ - # Remove objects before serialization - data.pop("objects") - - # Write it out - syslog.syslog(json.dumps(data))
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/security/auditing/server.html b/docs/0.9.5/_modules/evennia/contrib/security/auditing/server.html deleted file mode 100644 index 4bb53aa735..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/security/auditing/server.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - - - evennia.contrib.security.auditing.server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.security.auditing.server

-"""
-Auditable Server Sessions:
-Extension of the stock ServerSession that yields objects representing
-user inputs and system outputs.
-
-Evennia contribution - Johnny 2017
-"""
-import os
-import re
-import socket
-
-from django.utils import timezone
-from django.conf import settings as ev_settings
-from evennia.utils import utils, logger, mod_import, get_evennia_version
-from evennia.server.serversession import ServerSession
-
-# Attributes governing auditing of commands and where to send log objects
-AUDIT_CALLBACK = getattr(
-    ev_settings, "AUDIT_CALLBACK", "evennia.contrib.security.auditing.outputs.to_file"
-)
-AUDIT_IN = getattr(ev_settings, "AUDIT_IN", False)
-AUDIT_OUT = getattr(ev_settings, "AUDIT_OUT", False)
-AUDIT_ALLOW_SPARSE = getattr(ev_settings, "AUDIT_ALLOW_SPARSE", False)
-AUDIT_MASKS = [
-    {"connect": r"^[@\s]*[connect]{5,8}\s+(\".+?\"|[^\s]+)\s+(?P<secret>.+)"},
-    {"connect": r"^[@\s]*[connect]{5,8}\s+(?P<secret>[\w]+)"},
-    {"create": r"^[^@]?[create]{5,6}\s+(\w+|\".+?\")\s+(?P<secret>[\w]+)"},
-    {"create": r"^[^@]?[create]{5,6}\s+(?P<secret>[\w]+)"},
-    {"userpassword": r"^[@\s]*[userpassword]{11,14}\s+(\w+|\".+?\")\s+=*\s*(?P<secret>[\w]+)"},
-    {"userpassword": r"^.*new password set to '(?P<secret>[^']+)'\."},
-    {"userpassword": r"^.* has changed your password to '(?P<secret>[^']+)'\."},
-    {"password": r"^[@\s]*[password]{6,9}\s+(?P<secret>.*)"},
-] + getattr(ev_settings, "AUDIT_MASKS", [])
-
-
-if AUDIT_CALLBACK:
-    try:
-        AUDIT_CALLBACK = getattr(
-            mod_import(".".join(AUDIT_CALLBACK.split(".")[:-1])), AUDIT_CALLBACK.split(".")[-1]
-        )
-        logger.log_sec("Auditing module online.")
-        logger.log_sec(
-            "Audit record User input: {}, output: {}.\n"
-            "Audit sparse recording: {}, Log callback: {}".format(
-                AUDIT_IN, AUDIT_OUT, AUDIT_ALLOW_SPARSE, AUDIT_CALLBACK
-            )
-        )
-    except Exception as e:
-        logger.log_err("Failed to activate Auditing module. %s" % e)
-
-
-
[docs]class AuditedServerSession(ServerSession): - """ - This particular implementation parses all server inputs and/or outputs and - passes a dict containing the parsed metadata to a callback method of your - creation. This is useful for recording player activity where necessary for - security auditing, usage analysis or post-incident forensic discovery. - - *** WARNING *** - All strings are recorded and stored in plaintext. This includes those strings - which might contain sensitive data (create, connect, @password). These commands - have their arguments masked by default, but you must mask or mask any - custom commands of your own that handle sensitive information. - - See README.md for installation/configuration instructions. - """ - -
[docs] def audit(self, **kwargs): - """ - Extracts messages and system data from a Session object upon message - send or receive. - - Keyword Args: - src (str): Source of data; 'client' or 'server'. Indicates direction. - text (str or list): Client sends messages to server in the form of - lists. Server sends messages to client as string. - - Returns: - log (dict): Dictionary object containing parsed system and user data - related to this message. - - """ - # Get time at start of processing - time_obj = timezone.now() - time_str = str(time_obj) - - session = self - src = kwargs.pop("src", "?") - bytecount = 0 - - # Do not log empty lines - if not kwargs: - return {} - - # Get current session's IP address - client_ip = session.address - - # Capture Account name and dbref together - account = session.get_account() - account_token = "" - if account: - account_token = "%s%s" % (account.key, account.dbref) - - # Capture Character name and dbref together - char = session.get_puppet() - char_token = "" - if char: - char_token = "%s%s" % (char.key, char.dbref) - - # Capture Room name and dbref together - room = None - room_token = "" - if char: - room = char.location - room_token = "%s%s" % (room.key, room.dbref) - - # Try to compile an input/output string - def drill(obj, bucket): - if isinstance(obj, dict): - return bucket - elif utils.is_iter(obj): - for sub_obj in obj: - bucket.extend(drill(sub_obj, [])) - else: - bucket.append(obj) - return bucket - - text = kwargs.pop("text", "") - if utils.is_iter(text): - text = "|".join(drill(text, [])) - - # Mask any PII in message, where possible - bytecount = len(text.encode("utf-8")) - text = self.mask(text) - - # Compile the IP, Account, Character, Room, and the message. - log = { - "time": time_str, - "hostname": socket.getfqdn(), - "application": "%s" % ev_settings.SERVERNAME, - "version": get_evennia_version(), - "pid": os.getpid(), - "direction": "SND" if src == "server" else "RCV", - "protocol": self.protocol_key, - "ip": client_ip, - "session": "session#%s" % self.sessid, - "account": account_token, - "character": char_token, - "room": room_token, - "text": text.strip(), - "bytes": bytecount, - "data": kwargs, - "objects": { - "time": time_obj, - "session": self, - "account": account, - "character": char, - "room": room, - }, - } - - # Remove any keys with blank values - if AUDIT_ALLOW_SPARSE is False: - log["data"] = {k: v for k, v in log["data"].items() if v} - log["objects"] = {k: v for k, v in log["objects"].items() if v} - log = {k: v for k, v in log.items() if v} - - return log
- -
[docs] def mask(self, msg): - """ - Masks potentially sensitive user information within messages before - writing to log. Recording cleartext password attempts is bad policy. - - Args: - msg (str): Raw text string sent from client <-> server - - Returns: - msg (str): Text string with sensitive information masked out. - - """ - # Check to see if the command is embedded within server output - _msg = msg - is_embedded = False - match = re.match(".*Command.*'(.+)'.*is not available.*", msg, flags=re.IGNORECASE) - if match: - msg = match.group(1).replace("\\", "") - submsg = msg - is_embedded = True - - for mask in AUDIT_MASKS: - for command, regex in mask.items(): - try: - match = re.match(regex, msg, flags=re.IGNORECASE) - except Exception as e: - logger.log_err(regex) - logger.log_err(e) - continue - - if match: - term = match.group("secret") - masked = re.sub(term, "*" * len(term.zfill(8)), msg) - - if is_embedded: - msg = re.sub( - submsg, "%s <Masked: %s>" % (masked, command), _msg, flags=re.IGNORECASE - ) - else: - msg = masked - - return msg - - return _msg
- -
[docs] def data_out(self, **kwargs): - """ - Generic hook for sending data out through the protocol. - - Keyword Args: - kwargs (any): Other data to the protocol. - - """ - if AUDIT_CALLBACK and AUDIT_OUT: - try: - log = self.audit(src="server", **kwargs) - if log: - AUDIT_CALLBACK(log) - except Exception as e: - logger.log_err(e) - - super(AuditedServerSession, self).data_out(**kwargs)
- -
[docs] def data_in(self, **kwargs): - """ - Hook for protocols to send incoming data to the engine. - - Keyword Args: - kwargs (any): Other data from the protocol. - - """ - if AUDIT_CALLBACK and AUDIT_IN: - try: - log = self.audit(src="client", **kwargs) - if log: - AUDIT_CALLBACK(log) - except Exception as e: - logger.log_err(e) - - super(AuditedServerSession, self).data_in(**kwargs)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/security/auditing/tests.html b/docs/0.9.5/_modules/evennia/contrib/security/auditing/tests.html deleted file mode 100644 index 08fccb1ebb..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/security/auditing/tests.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - evennia.contrib.security.auditing.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.security.auditing.tests

-"""
-Module containing the test cases for the Audit system.
-"""
-
-from anything import Anything
-from django.test import override_settings
-from django.conf import settings
-from evennia.utils.test_resources import EvenniaTest
-import re
-
-# Configure session auditing settings - TODO: This is bad practice that leaks over to other tests
-settings.AUDIT_CALLBACK = "evennia.security.contrib.auditing.outputs.to_syslog"
-settings.AUDIT_IN = True
-settings.AUDIT_OUT = True
-settings.AUDIT_ALLOW_SPARSE = True
-
-# Configure settings to use custom session - TODO: This is bad practice, changing global settings
-settings.SERVER_SESSION_CLASS = "evennia.contrib.security.auditing.server.AuditedServerSession"
-
-
-
[docs]class AuditingTest(EvenniaTest): -
[docs] def test_mask(self): - """ - Make sure the 'mask' function is properly masking potentially sensitive - information from strings. - """ - safe_cmds = ( - "/say hello to my little friend", - "@ccreate channel = for channeling", - "@create/drop some stuff", - "@create rock", - "@create a pretty shirt : evennia.contrib.clothing.Clothing", - "@charcreate johnnyefhiwuhefwhef", - 'Command "@logout" is not available. Maybe you meant "@color" or "@cboot"?', - '/me says, "what is the password?"', - "say the password is plugh", - # Unfortunately given the syntax, there is no way to discern the - # latter of these as sensitive - "@create pretty sunset" "@create johnny password123", - '{"text": "Command \'do stuff\' is not available. Type "help" for help."}', - ) - - for cmd in safe_cmds: - self.assertEqual(self.session.mask(cmd), cmd) - - unsafe_cmds = ( - ( - "something - new password set to 'asdfghjk'.", - "something - new password set to '********'.", - ), - ( - "someone has changed your password to 'something'.", - "someone has changed your password to '*********'.", - ), - ("connect johnny password123", "connect johnny ***********"), - ("concnct johnny password123", "concnct johnny ***********"), - ("concnct johnnypassword123", "concnct *****************"), - ('connect "johnny five" "password 123"', 'connect "johnny five" **************'), - ('connect johnny "password 123"', "connect johnny **************"), - ("create johnny password123", "create johnny ***********"), - ("@password password1234 = password2345", "@password ***************************"), - ("@password password1234 password2345", "@password *************************"), - ("@passwd password1234 = password2345", "@passwd ***************************"), - ("@userpassword johnny = password234", "@userpassword johnny = ***********"), - ("craete johnnypassword123", "craete *****************"), - ( - "Command 'conncect teddy teddy' is not available. Maybe you meant \"@encode\"?", - "Command 'conncect ******** ********' is not available. Maybe you meant \"@encode\"?", - ), - ( - "{'text': u'Command \\'conncect jsis dfiidf\\' is not available. Type \"help\" for help.'}", - "{'text': u'Command \\'conncect jsis ********\\' is not available. Type \"help\" for help.'}", - ), - ) - - for index, (unsafe, safe) in enumerate(unsafe_cmds): - self.assertEqual(re.sub(" <Masked: .+>", "", self.session.mask(unsafe)).strip(), safe) - - # Make sure scrubbing is not being abused to evade monitoring - secrets = [ - "say password password password; ive got a secret that i cant explain", - "whisper johnny = password\n let's lynch the landlord", - "say connect johnny password1234|the secret life of arabia", - "@password eval(\"__import__('os').system('clear')\", {'__builtins__':{}})", - ] - for secret in secrets: - self.assertEqual(self.session.mask(secret), secret)
- -
[docs] def test_audit(self): - """ - Make sure the 'audit' function is returning a dictionary based on values - parsed from the Session object. - """ - log = self.session.audit(src="client", text=[["hello"]]) - obj = { - k: v for k, v in log.items() if k in ("direction", "protocol", "application", "text") - } - self.assertEqual( - obj, - { - "direction": "RCV", - "protocol": "telnet", - "application": Anything, # this will change if running tests from the game dir - "text": "hello", - }, - ) - - # Make sure OOB data is being recorded - log = self.session.audit( - src="client", text="connect johnny password123", prompt="hp=20|st=10|ma=15", pane=2 - ) - self.assertEqual(log["text"], "connect johnny ***********") - self.assertEqual(log["data"]["prompt"], "hp=20|st=10|ma=15") - self.assertEqual(log["data"]["pane"], 2)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/simpledoor.html b/docs/0.9.5/_modules/evennia/contrib/simpledoor.html deleted file mode 100644 index 4fca5688a4..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/simpledoor.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - - evennia.contrib.simpledoor — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.simpledoor

-"""
-SimpleDoor
-
-Contribution - Griatch 2016
-
-A simple two-way exit that represents a door that can be opened and
-closed. Can easily be expanded from to make it lockable, destroyable
-etc.  Note that the simpledoor is based on Evennia locks, so it will
-not work for a superuser (which bypasses all locks) - the superuser
-will always appear to be able to close/open the door over and over
-without the locks stopping you. To use the door, use `@quell` or a
-non-superuser account.
-
-Installation:
-
-    Import this module in mygame/commands/default_cmdsets and add
-    the CmdOpen and CmdOpenCloseDoor commands to the CharacterCmdSet;
-    then reload the server.
-
-To try it out, `@dig` a new room and then use the (overloaded) `@open`
-commmand to open a new doorway to it like this:
-
-    @open doorway:contrib.simpledoor.SimpleDoor = otherroom
-
-You can then use `open doorway' and `close doorway` to change the open
-state. If you are not superuser (`@quell` yourself) you'll find you
-cannot pass through either side of the door once it's closed from the
-other side.
-
-"""
-
-from evennia import DefaultExit, default_cmds
-from evennia.utils.utils import inherits_from
-
-
-
[docs]class SimpleDoor(DefaultExit): - """ - A two-way exit "door" with some methods for affecting both "sides" - of the door at the same time. For example, set a lock on either of the two - sides using `exitname.setlock("traverse:false())` - - """ - -
[docs] def at_object_creation(self): - """ - Called the very first time the door is created. - - """ - self.db.return_exit = None
- -
[docs] def setlock(self, lockstring): - """ - Sets identical locks on both sides of the door. - - Args: - lockstring (str): A lockstring, like `"traverse:true()"`. - - """ - self.locks.add(lockstring) - self.db.return_exit.locks.add(lockstring)
- -
[docs] def setdesc(self, description): - """ - Sets identical descs on both sides of the door. - - Args: - setdesc (str): A description. - - """ - self.db.desc = description - self.db.return_exit.db.desc = description
- -
[docs] def delete(self): - """ - Deletes both sides of the door. - - """ - # we have to be careful to avoid a delete-loop. - if self.db.return_exit: - super().delete() - super().delete() - return True
- -
[docs] def at_failed_traverse(self, traversing_object): - """ - Called when door traverse: lock fails. - - Args: - traversing_object (Typeclassed entity): The object - attempting the traversal. - - """ - traversing_object.msg("%s is closed." % self.key)
- - -
[docs]class CmdOpen(default_cmds.CmdOpen): - __doc__ = default_cmds.CmdOpen.__doc__ - # overloading parts of the default CmdOpen command to support doors. - -
[docs] def create_exit(self, exit_name, location, destination, exit_aliases=None, typeclass=None): - """ - Simple wrapper for the default CmdOpen.create_exit - """ - # create a new exit as normal - new_exit = super().create_exit( - exit_name, location, destination, exit_aliases=exit_aliases, typeclass=typeclass - ) - if hasattr(self, "return_exit_already_created"): - # we don't create a return exit if it was already created (because - # we created a door) - del self.return_exit_already_created - return new_exit - if inherits_from(new_exit, SimpleDoor): - # a door - create its counterpart and make sure to turn off the default - # return-exit creation of CmdOpen - self.caller.msg( - "Note: A door-type exit was created - ignored eventual custom return-exit type." - ) - self.return_exit_already_created = True - back_exit = self.create_exit( - exit_name, destination, location, exit_aliases=exit_aliases, typeclass=typeclass - ) - new_exit.db.return_exit = back_exit - back_exit.db.return_exit = new_exit - return new_exit
- - -# A simple example of a command making use of the door exit class' -# functionality. One could easily expand it with functionality to -# operate on other types of open-able objects as needed. - - -
[docs]class CmdOpenCloseDoor(default_cmds.MuxCommand): - """ - Open and close a door - - Usage: - open <door> - close <door> - - """ - - key = "open" - aliases = ["close"] - locks = "cmd:all()" - help_category = "General" - -
[docs] def func(self): - "implement the door functionality" - if not self.args: - self.caller.msg("Usage: open||close <door>") - return - - door = self.caller.search(self.args) - if not door: - return - if not inherits_from(door, SimpleDoor): - self.caller.msg("This is not a door.") - return - - if self.cmdstring == "open": - if door.locks.check(self.caller, "traverse"): - self.caller.msg("%s is already open." % door.key) - else: - door.setlock("traverse:true()") - self.caller.msg("You open %s." % door.key) - else: # close - if not door.locks.check(self.caller, "traverse"): - self.caller.msg("%s is already closed." % door.key) - else: - door.setlock("traverse:false()") - self.caller.msg("You close %s." % door.key)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/slow_exit.html b/docs/0.9.5/_modules/evennia/contrib/slow_exit.html deleted file mode 100644 index c39142433f..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/slow_exit.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - evennia.contrib.slow_exit — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.slow_exit

-"""
-Slow Exit typeclass
-
-Contribution - Griatch 2014
-
-
-This is an example of an Exit-type that delays its traversal.This
-simulates slow movement, common in many different types of games. The
-contrib also contains two commands, CmdSetSpeed and CmdStop for changing
-the movement speed and abort an ongoing traversal, respectively.
-
-To try out an exit of this type, you could connect two existing rooms
-using something like this:
-
-@open north:contrib.slow_exit.SlowExit = <destination>
-
-
-Installation:
-
-To make this your new default exit, modify mygame/typeclasses/exits.py
-to import this module and change the default Exit class to inherit
-from SlowExit instead.
-
-To get the ability to change your speed and abort your movement,
-simply import and add CmdSetSpeed and CmdStop from this module to your
-default cmdset (see tutorials on how to do this if you are unsure).
-
-Notes:
-
-This implementation is efficient but not persistent; so incomplete
-movement will be lost in a server reload. This is acceptable for most
-game types - to simulate longer travel times (more than the couple of
-seconds assumed here), a more persistent variant using Scripts or the
-TickerHandler might be better.
-
-"""
-
-from evennia import DefaultExit, utils, Command
-
-MOVE_DELAY = {"stroll": 6, "walk": 4, "run": 2, "sprint": 1}
-
-
-
[docs]class SlowExit(DefaultExit): - """ - This overloads the way moving happens. - """ - -
[docs] def at_traverse(self, traversing_object, target_location): - """ - Implements the actual traversal, using utils.delay to delay the move_to. - """ - - # if the traverser has an Attribute move_speed, use that, - # otherwise default to "walk" speed - move_speed = traversing_object.db.move_speed or "walk" - move_delay = MOVE_DELAY.get(move_speed, 4) - - def move_callback(): - "This callback will be called by utils.delay after move_delay seconds." - source_location = traversing_object.location - if traversing_object.move_to(target_location): - self.at_after_traverse(traversing_object, source_location) - else: - if self.db.err_traverse: - # if exit has a better error message, let's use it. - self.caller.msg(self.db.err_traverse) - else: - # No shorthand error message. Call hook. - self.at_failed_traverse(traversing_object) - - traversing_object.msg("You start moving %s at a %s." % (self.key, move_speed)) - # create a delayed movement - t = utils.delay(move_delay, move_callback) - # we store the deferred on the character, this will allow us - # to abort the movement. We must use an ndb here since - # deferreds cannot be pickled. - traversing_object.ndb.currently_moving = t
- - -# -# set speed - command -# - -SPEED_DESCS = {"stroll": "strolling", "walk": "walking", "run": "running", "sprint": "sprinting"} - - -
[docs]class CmdSetSpeed(Command): - """ - set your movement speed - - Usage: - setspeed stroll|walk|run|sprint - - This will set your movement speed, determining how long time - it takes to traverse exits. If no speed is set, 'walk' speed - is assumed. - """ - - key = "setspeed" - -
[docs] def func(self): - """ - Simply sets an Attribute used by the SlowExit above. - """ - speed = self.args.lower().strip() - if speed not in SPEED_DESCS: - self.caller.msg("Usage: setspeed stroll||walk||run||sprint") - elif self.caller.db.move_speed == speed: - self.caller.msg("You are already %s." % SPEED_DESCS[speed]) - else: - self.caller.db.move_speed = speed - self.caller.msg("You are now %s." % SPEED_DESCS[speed])
- - -# -# stop moving - command -# - - -
[docs]class CmdStop(Command): - """ - stop moving - - Usage: - stop - - Stops the current movement, if any. - """ - - key = "stop" - -
[docs] def func(self): - """ - This is a very simple command, using the - stored deferred from the exit traversal above. - """ - currently_moving = self.caller.ndb.currently_moving - if currently_moving and not currently_moving.called: - currently_moving.cancel() - self.caller.msg("You stop moving.") - for observer in self.caller.location.contents_get(self.caller): - observer.msg("%s stops." % self.caller.get_display_name(observer)) - else: - self.caller.msg("You are not moving.")
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/talking_npc.html b/docs/0.9.5/_modules/evennia/contrib/talking_npc.html deleted file mode 100644 index a3a5c83872..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/talking_npc.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - - - evennia.contrib.talking_npc — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.talking_npc

-"""
-Evennia Talkative NPC
-
-Contribution - Griatch 2011, grungies1138, 2016
-
-This is a static NPC object capable of holding a simple menu-driven
-conversation. It's just meant as an example. Create it by creating an
-object of typeclass contrib.talking_npc.TalkingNPC, For example using
-@create:
-
-    @create/drop John : contrib.talking_npc.TalkingNPC
-
-Walk up to it and give the talk command to strike up a conversation.
-If there are many talkative npcs in the same room you will get to
-choose which one's talk command to call (Evennia handles this
-automatically). This use of EvMenu is very simplistic; See EvMenu for
-a lot more complex possibilities.
-
-
-"""
-
-from evennia import DefaultObject, CmdSet, default_cmds
-from evennia.utils.evmenu import EvMenu
-
-
-# Menu implementing the dialogue tree
-
-
-
-
-
-
[docs]def info1(caller): - text = "'Oh, Evennia is where you are right now! Don't you feel the power?'" - - options = ( - {"desc": "Sure, *I* do, not sure how you do though. You are just an NPC.", "goto": "info3"}, - {"desc": "Sure I do. What's yer name, NPC?", "goto": "info2"}, - {"desc": "Ok, bye for now then.", "goto": "END"}, - ) - - return text, options
- - -
[docs]def info2(caller): - text = "'My name is not really important ... I'm just an NPC after all.'" - - options = ( - {"desc": "I didn't really want to know it anyhow.", "goto": "info3"}, - {"desc": "Okay then, so what's this 'Evennia' thing about?", "goto": "info1"}, - ) - - return text, options
- - -
[docs]def info3(caller): - text = "'Well ... I'm sort of busy so, have to go. NPC business. Important stuff. You wouldn't understand.'" - - options = ( - {"desc": "Oookay ... I won't keep you. Bye.", "goto": "END"}, - {"desc": "Wait, why don't you tell me your name first?", "goto": "info2"}, - ) - - return text, options
- - -
[docs]def END(caller): - text = "'Goodbye, then.'" - - options = () - - return text, options
- - -# -# The talk command (sits on the NPC) -# - - -
[docs]class CmdTalk(default_cmds.MuxCommand): - """ - Talks to an npc - - Usage: - talk - - This command is only available if a talkative non-player-character - (NPC) is actually present. It will strike up a conversation with - that NPC and give you options on what to talk about. - """ - - key = "talk" - locks = "cmd:all()" - help_category = "General" - -
[docs] def func(self): - "Implements the command." - - # self.obj is the NPC this is defined on - self.caller.msg("(You walk up and talk to %s.)" % self.obj.key) - - # Initiate the menu. Change this if you are putting this on - # some other custom NPC class. - EvMenu(self.caller, "evennia.contrib.talking_npc", startnode="menu_start_node")
- - -
[docs]class TalkingCmdSet(CmdSet): - "Stores the talk command." - key = "talkingcmdset" - -
[docs] def at_cmdset_creation(self): - "populates the cmdset" - self.add(CmdTalk())
- - -
[docs]class TalkingNPC(DefaultObject): - """ - This implements a simple Object using the talk command and using - the conversation defined above. - """ - -
[docs] def at_object_creation(self): - "This is called when object is first created." - self.db.desc = "This is a talkative NPC." - # assign the talk command to npc - self.cmdset.add_default(TalkingCmdSet, permanent=True)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tree_select.html b/docs/0.9.5/_modules/evennia/contrib/tree_select.html deleted file mode 100644 index 4730ff0ca5..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tree_select.html +++ /dev/null @@ -1,683 +0,0 @@ - - - - - - - - evennia.contrib.tree_select — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tree_select

-"""
-Easy menu selection tree
-
-Contrib - Tim Ashley Jenkins 2017
-
-This module allows you to create and initialize an entire branching EvMenu
-instance with nothing but a multi-line string passed to one function.
-
-EvMenu is incredibly powerful and flexible, but using it for simple menus
-can often be fairly cumbersome - a simple menu that can branch into five
-categories would require six nodes, each with options represented as a list
-of dictionaries.
-
-This module provides a function, init_tree_selection, which acts as a frontend
-for EvMenu, dynamically sourcing the options from a multi-line string you provide.
-For example, if you define a string as such:
-
-    TEST_MENU = '''Foo
-    Bar
-    Baz
-    Qux'''
-
-And then use TEST_MENU as the 'treestr' source when you call init_tree_selection
-on a player:
-
-    init_tree_selection(TEST_MENU, caller, callback)
-
-The player will be presented with an EvMenu, like so:
-
-    ___________________________
-
-    Make your selection:
-    ___________________________
-
-    Foo
-    Bar
-    Baz
-    Qux
-
-Making a selection will pass the selection's key to the specified callback as a
-string along with the caller, as well as the index of the selection (the line number
-on the source string) along with the source string for the tree itself.
-
-In addition to specifying selections on the menu, you can also specify categories.
-Categories are indicated by putting options below it preceded with a '-' character.
-If a selection is a category, then choosing it will bring up a new menu node, prompting
-the player to select between those options, or to go back to the previous menu. In
-addition, categories are marked by default with a '[+]' at the end of their key. Both
-this marker and the option to go back can be disabled.
-
-Categories can be nested in other categories as well - just go another '-' deeper. You
-can do this as many times as you like. There's no hard limit to the number of
-categories you can go down.
-
-For example, let's add some more options to our menu, turning 'Bar' into a category.
-
-    TEST_MENU = '''Foo
-    Bar
-    -You've got to know
-    --When to hold em
-    --When to fold em
-    --When to walk away
-    Baz
-    Qux'''
-
-Now when we call the menu, we can see that 'Bar' has become a category instead of a
-selectable option.
-
-    _______________________________
-
-    Make your selection:
-    _______________________________
-
-    Foo
-    Bar [+]
-    Baz
-    Qux
-
-Note the [+] next to 'Bar'. If we select 'Bar', it'll show us the option listed under it.
-
-    ________________________________________________________________
-
-    Bar
-    ________________________________________________________________
-
-    You've got to know [+]
-    << Go Back: Return to the previous menu.
-
-Just the one option, which is a category itself, and the option to go back, which will
-take us back to the previous menu. Let's select 'You've got to know'.
-
-    ________________________________________________________________
-
-    You've got to know
-    ________________________________________________________________
-
-    When to hold em
-    When to fold em
-    When to walk away
-    << Go Back: Return to the previous menu.
-
-Now we see the three options listed under it, too. We can select one of them or use 'Go
-Back' to return to the 'Bar' menu we were just at before. It's very simple to make a
-branching tree of selections!
-
-One last thing - you can set the descriptions for the various options simply by adding a
-':' character followed by the description to the option's line. For example, let's add a
-description to 'Baz' in our menu:
-
-    TEST_MENU = '''Foo
-    Bar
-    -You've got to know
-    --When to hold em
-    --When to fold em
-    --When to walk away
-    Baz: Look at this one: the best option.
-    Qux'''
-
-Now we see that the Baz option has a description attached that's separate from its key:
-
-    _______________________________________________________________
-
-    Make your selection:
-    _______________________________________________________________
-
-    Foo
-    Bar [+]
-    Baz: Look at this one: the best option.
-    Qux
-
-Once the player makes a selection - let's say, 'Foo' - the menu will terminate and call
-your specified callback with the selection, like so:
-
-    callback(caller, TEST_MENU, 0, "Foo")
-
-The index of the selection is given along with a string containing the selection's key.
-That way, if you have two selections in the menu with the same key, you can still
-differentiate between them.
-
-And that's all there is to it! For simple branching-tree selections, using this system is
-much easier than manually creating EvMenu nodes. It also makes generating menus with dynamic
-options much easier - since the source of the menu tree is just a string, you could easily
-generate that string procedurally before passing it to the init_tree_selection function.
-For example, if a player casts a spell or does an attack without specifying a target, instead
-of giving them an error, you could present them with a list of valid targets to select by
-generating a multi-line string of targets and passing it to init_tree_selection, with the
-callable performing the maneuver once a selection is made.
-
-This selection system only works for simple branching trees - doing anything really complicated
-like jumping between categories or prompting for arbitrary input would still require a full
-EvMenu implementation. For simple selections, however, I'm sure you will find using this function
-to be much easier!
-
-Included in this module is a sample menu and function which will let a player change the color
-of their name - feel free to mess with it to get a feel for how this system works by importing
-this module in your game's default_cmdsets.py module and adding CmdNameColor to your default
-character's command set.
-"""
-
-from evennia.utils import evmenu
-from evennia.utils.logger import log_trace
-from evennia import Command
-
-
-
[docs]def init_tree_selection( - treestr, - caller, - callback, - index=None, - mark_category=True, - go_back=True, - cmd_on_exit="look", - start_text="Make your selection:", -): - """ - Prompts a player to select an option from a menu tree given as a multi-line string. - - Args: - treestr (str): Multi-lne string representing menu options - caller (obj): Player to initialize the menu for - callback (callable): Function to run when a selection is made. Must take 4 args: - caller (obj): Caller given above - treestr (str): Menu tree string given above - index (int): Index of final selection - selection (str): Key of final selection - - Options: - index (int or None): Index to start the menu at, or None for top level - mark_category (bool): If True, marks categories with a [+] symbol in the menu - go_back (bool): If True, present an option to go back to previous categories - start_text (str): Text to display at the top level of the menu - cmd_on_exit(str): Command to enter when the menu exits - 'look' by default - - - Notes: - This function will initialize an instance of EvMenu with options generated - dynamically from the source string, and passes the menu user's selection to - a function of your choosing. The EvMenu is made of a single, repeating node, - which will call itself over and over at different levels of the menu tree as - categories are selected. - - Once a non-category selection is made, the user's selection will be passed to - the given callable, both as a string and as an index number. The index is given - to ensure every selection has a unique identifier, so that selections with the - same key in different categories can be distinguished between. - - The menus called by this function are not persistent and cannot perform - complicated tasks like prompt for arbitrary input or jump multiple category - levels at once - you'll have to use EvMenu itself if you want to take full - advantage of its features. - """ - - # Pass kwargs to store data needed in the menu - kwargs = { - "index": index, - "mark_category": mark_category, - "go_back": go_back, - "treestr": treestr, - "callback": callback, - "start_text": start_text, - } - - # Initialize menu of selections - evmenu.EvMenu( - caller, - "evennia.contrib.tree_select", - startnode="menunode_treeselect", - startnode_input=None, - cmd_on_exit=cmd_on_exit, - **kwargs, - )
- - -
[docs]def dashcount(entry): - """ - Counts the number of dashes at the beginning of a string. This - is needed to determine the depth of options in categories. - - Args: - entry (str): String to count the dashes at the start of - - Returns: - dashes (int): Number of dashes at the start - """ - dashes = 0 - for char in entry: - if char == "-": - dashes += 1 - else: - return dashes - return dashes
- - -
[docs]def is_category(treestr, index): - """ - Determines whether an option in a tree string is a category by - whether or not there are additional options below it. - - Args: - treestr (str): Multi-line string representing menu options - index (int): Which line of the string to test - - Returns: - is_category (bool): Whether the option is a category - """ - opt_list = treestr.split("\n") - # Not a category if it's the last one in the list - if index == len(opt_list) - 1: - return False - # Not a category if next option is not one level deeper - return not bool(dashcount(opt_list[index + 1]) != dashcount(opt_list[index]) + 1)
- - -
[docs]def parse_opts(treestr, category_index=None): - """ - Parses a tree string and given index into a list of options. If - category_index is none, returns all the options at the top level of - the menu. If category_index corresponds to a category, returns a list - of options under that category. If category_index corresponds to - an option that is not a category, it's a selection and returns True. - - Args: - treestr (str): Multi-line string representing menu options - category_index (int): Index of category or None for top level - - Returns: - kept_opts (list or True): Either a list of options in the selected - category or True if a selection was made - """ - dash_depth = 0 - opt_list = treestr.split("\n") - kept_opts = [] - - # If a category index is given - if category_index != None: - # If given index is not a category, it's a selection - return True. - if not is_category(treestr, category_index): - return True - # Otherwise, change the dash depth to match the new category. - dash_depth = dashcount(opt_list[category_index]) + 1 - # Delete everything before the category index - opt_list = opt_list[category_index + 1 :] - - # Keep every option (referenced by index) at the appropriate depth - cur_index = 0 - for option in opt_list: - if dashcount(option) == dash_depth: - if category_index == None: - kept_opts.append((cur_index, option[dash_depth:])) - else: - kept_opts.append((cur_index + category_index + 1, option[dash_depth:])) - # Exits the loop if leaving a category - if dashcount(option) < dash_depth: - return kept_opts - cur_index += 1 - return kept_opts
- - -
[docs]def index_to_selection(treestr, index, desc=False): - """ - Given a menu tree string and an index, returns the corresponding selection's - name as a string. If 'desc' is set to True, will return the selection's - description as a string instead. - - Args: - treestr (str): Multi-line string representing menu options - index (int): Index to convert to selection key or description - - Options: - desc (bool): If true, returns description instead of key - - Returns: - selection (str): Selection key or description if 'desc' is set - """ - opt_list = treestr.split("\n") - # Fetch the given line - selection = opt_list[index] - # Strip out the dashes at the start - selection = selection[dashcount(selection) :] - # Separate out description, if any - if ":" in selection: - # Split string into key and description - selection = selection.split(":", 1) - selection[1] = selection[1].strip(" ") - else: - # If no description given, set description to None - selection = [selection, None] - if not desc: - return selection[0] - else: - return selection[1]
- - -
[docs]def go_up_one_category(treestr, index): - """ - Given a menu tree string and an index, returns the category that the given option - belongs to. Used for the 'go back' option. - - Args: - treestr (str): Multi-line string representing menu options - index (int): Index to determine the parent category of - - Returns: - parent_category (int): Index of parent category - """ - opt_list = treestr.split("\n") - # Get the number of dashes deep the given index is - dash_level = dashcount(opt_list[index]) - # Delete everything after the current index - opt_list = opt_list[: index + 1] - - # If there's no dash, return 'None' to return to base menu - if dash_level == 0: - return None - current_index = index - # Go up through each option until we find one that's one category above - for selection in reversed(opt_list): - if dashcount(selection) == dash_level - 1: - return current_index - current_index -= 1
- - -
[docs]def optlist_to_menuoptions(treestr, optlist, index, mark_category, go_back): - """ - Takes a list of options processed by parse_opts and turns it into - a list/dictionary of menu options for use in menunode_treeselect. - - Args: - treestr (str): Multi-line string representing menu options - optlist (list): List of options to convert to EvMenu's option format - index (int): Index of current category - mark_category (bool): Whether or not to mark categories with [+] - go_back (bool): Whether or not to add an option to go back in the menu - - Returns: - menuoptions (list of dicts): List of menu options formatted for use - in EvMenu, each passing a different "newindex" kwarg that changes - the menu level or makes a selection - """ - - menuoptions = [] - cur_index = 0 - for option in optlist: - index_to_add = optlist[cur_index][0] - menuitem = {} - keystr = index_to_selection(treestr, index_to_add) - if mark_category and is_category(treestr, index_to_add): - # Add the [+] to the key if marking categories, and the key by itself as an alias - menuitem["key"] = [keystr + " [+]", keystr] - else: - menuitem["key"] = keystr - # Get the option's description - desc = index_to_selection(treestr, index_to_add, desc=True) - if desc: - menuitem["desc"] = desc - # Passing 'newindex' as a kwarg to the node is how we move through the menu! - menuitem["goto"] = ["menunode_treeselect", {"newindex": index_to_add}] - menuoptions.append(menuitem) - cur_index += 1 - # Add option to go back, if needed - if index != None and go_back == True: - gobackitem = { - "key": ["<< Go Back", "go back", "back"], - "desc": "Return to the previous menu.", - "goto": ["menunode_treeselect", {"newindex": go_up_one_category(treestr, index)}], - } - menuoptions.append(gobackitem) - return menuoptions
- - - - - -# The rest of this module is for the example menu and command! It'll change the color of your name. - -""" -Here's an example string that you can initialize a menu from. Note the dashes at -the beginning of each line - that's how menu option depth and hierarchy is determined. -""" - -NAMECOLOR_MENU = """Set name color: Choose a color for your name! --Red shades: Various shades of |511red|n ---Red: |511Set your name to Red|n ---Pink: |533Set your name to Pink|n ---Maroon: |301Set your name to Maroon|n --Orange shades: Various shades of |531orange|n ---Orange: |531Set your name to Orange|n ---Brown: |321Set your name to Brown|n ---Sienna: |420Set your name to Sienna|n --Yellow shades: Various shades of |551yellow|n ---Yellow: |551Set your name to Yellow|n ---Gold: |540Set your name to Gold|n ---Dandelion: |553Set your name to Dandelion|n --Green shades: Various shades of |141green|n ---Green: |141Set your name to Green|n ---Lime: |350Set your name to Lime|n ---Forest: |032Set your name to Forest|n --Blue shades: Various shades of |115blue|n ---Blue: |115Set your name to Blue|n ---Cyan: |155Set your name to Cyan|n ---Navy: |113Set your name to Navy|n --Purple shades: Various shades of |415purple|n ---Purple: |415Set your name to Purple|n ---Lavender: |535Set your name to Lavender|n ---Fuchsia: |503Set your name to Fuchsia|n -Remove name color: Remove your name color, if any""" - - -
[docs]class CmdNameColor(Command): - """ - Set or remove a special color on your name. Just an example for the - easy menu selection tree contrib. - """ - - key = "namecolor" - -
[docs] def func(self): - # This is all you have to do to initialize a menu! - init_tree_selection( - NAMECOLOR_MENU, self.caller, change_name_color, start_text="Name color options:" - )
- - -
[docs]def change_name_color(caller, treestr, index, selection): - """ - Changes a player's name color. - - Args: - caller (obj): Character whose name to color. - treestr (str): String for the color change menu - unused - index (int): Index of menu selection - unused - selection (str): Selection made from the name color menu - used - to determine the color the player chose. - """ - - # Store the caller's uncolored name - if not caller.db.uncolored_name: - caller.db.uncolored_name = caller.key - - # Dictionary matching color selection names to color codes - colordict = { - "Red": "|511", - "Pink": "|533", - "Maroon": "|301", - "Orange": "|531", - "Brown": "|321", - "Sienna": "|420", - "Yellow": "|551", - "Gold": "|540", - "Dandelion": "|553", - "Green": "|141", - "Lime": "|350", - "Forest": "|032", - "Blue": "|115", - "Cyan": "|155", - "Navy": "|113", - "Purple": "|415", - "Lavender": "|535", - "Fuchsia": "|503", - } - - # I know this probably isn't the best way to do this. It's just an example! - if selection == "Remove name color": # Player chose to remove their name color - caller.key = caller.db.uncolored_name - caller.msg("Name color removed.") - elif selection in colordict: - newcolor = colordict[selection] # Retrieve color code based on menu selection - caller.key = newcolor + caller.db.uncolored_name + "|n" # Add color code to caller's name - caller.msg(newcolor + ("Name color changed to %s!" % selection) + "|n")
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_basic.html b/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_basic.html deleted file mode 100644 index 5d45f3725b..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_basic.html +++ /dev/null @@ -1,886 +0,0 @@ - - - - - - - - evennia.contrib.turnbattle.tb_basic — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.turnbattle.tb_basic

-"""
-Simple turn-based combat system
-
-Contrib - Tim Ashley Jenkins 2017
-
-This is a framework for a simple turn-based combat system, similar
-to those used in D&D-style tabletop role playing games. It allows
-any character to start a fight in a room, at which point initiative
-is rolled and a turn order is established. Each participant in combat
-has a limited time to decide their action for that turn (30 seconds by
-default), and combat progresses through the turn order, looping through
-the participants until the fight ends.
-
-Only simple rolls for attacking are implemented here, but this system
-is easily extensible and can be used as the foundation for implementing
-the rules from your turn-based tabletop game of choice or making your
-own battle system.
-
-To install and test, import this module's TBBasicCharacter object into
-your game's character.py module:
-
-    from evennia.contrib.turnbattle.tb_basic import TBBasicCharacter
-
-And change your game's character typeclass to inherit from TBBasicCharacter
-instead of the default:
-
-    class Character(TBBasicCharacter):
-
-Next, import this module into your default_cmdsets.py module:
-
-    from evennia.contrib.turnbattle import tb_basic
-
-And add the battle command set to your default command set:
-
-    #
-    # any commands you add below will overload the default ones.
-    #
-    self.add(tb_basic.BattleCmdSet())
-
-This module is meant to be heavily expanded on, so you may want to copy it
-to your game's 'world' folder and modify it there rather than importing it
-in your game and using it as-is.
-"""
-
-from random import randint
-from evennia import DefaultCharacter, Command, default_cmds, DefaultScript
-from evennia.commands.default.help import CmdHelp
-
-"""
-----------------------------------------------------------------------------
-OPTIONS
-----------------------------------------------------------------------------
-"""
-
-TURN_TIMEOUT = 30  # Time before turns automatically end, in seconds
-ACTIONS_PER_TURN = 1  # Number of actions allowed per turn
-
-"""
-----------------------------------------------------------------------------
-COMBAT FUNCTIONS START HERE
-----------------------------------------------------------------------------
-"""
-
-
-
[docs]def roll_init(character): - """ - Rolls a number between 1-1000 to determine initiative. - - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. - """ - return randint(1, 1000)
- - -
[docs]def get_attack(attacker, defender): - """ - Returns a value for an attack roll. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. - - Notes: - By default, returns a random integer from 1 to 100 without using any - properties from either the attacker or defender. - - This can easily be expanded to return a value based on characters stats, - equipment, and abilities. This is why the attacker and defender are passed - to this function, even though nothing from either one are used in this example. - """ - # For this example, just return a random integer up to 100. - attack_value = randint(1, 100) - return attack_value
- - -
[docs]def get_defense(attacker, defender): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. - - Notes: - By default, returns 50, not taking any properties of the defender or - attacker into account. - - As above, this can be expanded upon based on character stats and equipment. - """ - # For this example, just return 50, for about a 50/50 chance of hit. - defense_value = 50 - return defense_value
- - -
[docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - By default, returns a random integer from 15 to 25 without using any - properties from either the attacker or defender. - - Again, this can be expanded upon. - """ - # For this example, just generate a number between 15 and 25. - damage_value = randint(15, 25) - return damage_value
- - -
[docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. - - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
- - -
[docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
- - -
[docs]def resolve_attack(attacker, defender, attack_value=None, defense_value=None): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Notes: - Even though the attack and defense values are calculated - extremely simply, they are separated out into their own functions - so that they are easier to expand upon. - """ - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents("%s's attack misses %s!" % (attacker, defender)) - else: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - attacker.location.msg_contents( - "%s hits %s for %i damage!" % (attacker, defender, damage_value) - ) - apply_damage(defender, damage_value) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
- - -
[docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
- - -
[docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
- - -
[docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
- - -
[docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
- - -""" ----------------------------------------------------------------------------- -CHARACTER TYPECLASS ----------------------------------------------------------------------------- -""" - - -
[docs]class TBBasicCharacter(DefaultCharacter): - """ - A character able to participate in turn-based combat. Has attributes for current - and maximum HP, and access to combat commands. - """ - -
[docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - """ - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - """ - Adds attributes for a character's current and maximum HP. - We're just going to set this value at '100' by default. - - You may want to expand this to include various 'stats' that - can be changed at creation and factor into combat calculations. - """
- -
[docs] def at_before_move(self, destination): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
- - -""" ----------------------------------------------------------------------------- -SCRIPTS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class TBBasicTurnHandler(DefaultScript): - """ - This is the script that handles the progression of combat through turns. - On creation (when a fight is started) it adds all combat-ready characters - to its roster and then sorts them into a turn order. There can only be one - fight going on in a single room at a time, so the script is assigned to a - room as its object. - - Fights persist until only one participant is left with any HP or all - remaining participants choose to end the combat with the 'disengage' command. - """ - -
[docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
- -
[docs] def at_stop(self): - """ - Called at script termination. - """ - for fighter in self.db.fighters: - combat_cleanup(fighter) # Clean up the combat attributes for every fighter. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
- -
[docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
- -
[docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. - - Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
- -
[docs] def start_turn(self, character): - """ - Readies a character for the start of their turn by replenishing their - available actions and notifying them that their turn has come up. - - Args: - character (obj): Character to be readied. - - Notes: - Here, you only get one action per turn, but you might want to allow more than - one per turn, or even grant a number of actions based on a character's - attributes. You can even add multiple different kinds of actions, I.E. actions - separated for movement, by adding "character.db.combat_movesleft = 3" or - something similar. - """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Prompt the character for their turn and give some information. - character.msg("|wIt's your turn! You have %i HP remaining.|n" % character.db.hp)
- -
[docs] def next_turn(self): - """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - newchar = self.db.fighters[self.db.turn] # Note the new character - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn.
- -
[docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. - - Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
- -
[docs] def join_fight(self, character): - """ - Adds a new character to a fight already in progress. - - Args: - character (obj): Character to be added to the fight. - """ - # Inserts the fighter to the turn order, right behind whoever's turn it currently is. - self.db.fighters.insert(self.db.turn, character) - # Tick the turn counter forward one to compensate. - self.db.turn += 1 - # Initialize the character like you do at the start. - self.initialize_for_combat(character)
- - -""" ----------------------------------------------------------------------------- -COMMANDS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class CmdFight(Command): - """ - Starts a fight with everyone in the same room as you. - - Usage: - fight - - When you start a fight, everyone in the room who is able to - fight is added to combat, and a turn order is randomly rolled. - When it's your turn, you can attack other characters. - """ - - key = "fight" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.turnbattle.tb_basic.TBBasicTurnHandler")
- # Remember you'll have to change the path to the script if you copy this code to your own modules! - - -
[docs]class CmdAttack(Command): - """ - Attacks another character. - - Usage: - attack <target> - - When in a fight, you may attack another character. The attack has - a chance to hit, and if successful, will deal damage. - """ - - key = "attack" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender) - spend_action(self.caller, 1, action_name="attack") # Use up one action.
- - -
[docs]class CmdPass(Command): - """ - Passes on your turn. - - Usage: - pass - - When in a fight, you can use this command to end your turn early, even - if there are still any actions you can take. - """ - - key = "pass" - aliases = ["wait", "hold"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
- - -
[docs]class CmdDisengage(Command): - """ - Passes your turn and attempts to end combat. - - Usage: - disengage - - Ends your turn early and signals that you're trying to end - the fight. If all participants in a fight disengage, the - fight ends. - """ - - key = "disengage" - aliases = ["spare"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
- - -
[docs]class CmdRest(Command): - """ - Recovers damage. - - Usage: - rest - - Resting recovers your HP to its maximum, but you can only - rest if you're not in a fight. - """ - - key = "rest" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - - if is_in_combat(self.caller): # If you're in combat - self.caller.msg("You can't rest while you're in combat.") - return - - self.caller.db.hp = self.caller.db.max_hp # Set current HP to maximum - self.caller.location.msg_contents("%s rests to recover HP." % self.caller) - """ - You'll probably want to replace this with your own system for recovering HP. - """
- - -
[docs]class CmdCombatHelp(CmdHelp): - """ - View help or a list of topics - - Usage: - help <topic or command> - help list - help all - - This will search for help on commands and other - topics related to the game. - """ - - # Just like the default help command, but will give quick - # tips on combat when used in a fight with no arguments. - -
[docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack a target, attempting to deal damage.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - ) - else: - super().func() # Call the default help command
- - -
[docs]class BattleCmdSet(default_cmds.CharacterCmdSet): - """ - This command set includes all the commmands used in the battle system. - """ - - key = "DefaultCharacter" - -
[docs] def at_cmdset_creation(self): - """ - Populates the cmdset - """ - self.add(CmdFight()) - self.add(CmdAttack()) - self.add(CmdRest()) - self.add(CmdPass()) - self.add(CmdDisengage()) - self.add(CmdCombatHelp())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_equip.html b/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_equip.html deleted file mode 100644 index 163f773ab8..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_equip.html +++ /dev/null @@ -1,1244 +0,0 @@ - - - - - - - - evennia.contrib.turnbattle.tb_equip — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.turnbattle.tb_equip

-"""
-Simple turn-based combat system with equipment
-
-Contrib - Tim Ashley Jenkins 2017
-
-This is a version of the 'turnbattle' contrib with a basic system for
-weapons and armor implemented. Weapons can have unique damage ranges
-and accuracy modifiers, while armor can reduce incoming damage and
-change one's chance of getting hit. The 'wield' command is used to
-equip weapons and the 'don' command is used to equip armor.
-
-Some prototypes are included at the end of this module - feel free to
-copy them into your game's prototypes.py module in your 'world' folder
-and create them with the @spawn command. (See the tutorial for using
-the @spawn command for details.)
-
-For the example equipment given, heavier weapons deal more damage
-but are less accurate, while light weapons are more accurate but
-deal less damage. Similarly, heavy armor reduces incoming damage by
-a lot but increases your chance of getting hit, while light armor is
-easier to dodge in but reduces incoming damage less. Light weapons are
-more effective against lightly armored opponents and heavy weapons are
-more damaging against heavily armored foes, but heavy weapons and armor
-are slightly better than light weapons and armor overall.
-
-This is a fairly bare implementation of equipment that is meant to be
-expanded to fit your game - weapon and armor slots, damage types and
-damage bonuses, etc. should be fairly simple to implement according to
-the rules of your preferred system or the needs of your own game.
-
-To install and test, import this module's TBEquipCharacter object into
-your game's character.py module:
-
-    from evennia.contrib.turnbattle.tb_equip import TBEquipCharacter
-
-And change your game's character typeclass to inherit from TBEquipCharacter
-instead of the default:
-
-    class Character(TBEquipCharacter):
-
-Next, import this module into your default_cmdsets.py module:
-
-    from evennia.contrib.turnbattle import tb_equip
-
-And add the battle command set to your default command set:
-
-    #
-    # any commands you add below will overload the default ones.
-    #
-    self.add(tb_equip.BattleCmdSet())
-
-This module is meant to be heavily expanded on, so you may want to copy it
-to your game's 'world' folder and modify it there rather than importing it
-in your game and using it as-is.
-"""
-
-from random import randint
-from evennia import DefaultCharacter, Command, default_cmds, DefaultScript, DefaultObject
-from evennia.commands.default.help import CmdHelp
-
-"""
-----------------------------------------------------------------------------
-OPTIONS
-----------------------------------------------------------------------------
-"""
-
-TURN_TIMEOUT = 30  # Time before turns automatically end, in seconds
-ACTIONS_PER_TURN = 1  # Number of actions allowed per turn
-
-"""
-----------------------------------------------------------------------------
-COMBAT FUNCTIONS START HERE
-----------------------------------------------------------------------------
-"""
-
-
-
[docs]def roll_init(character): - """ - Rolls a number between 1-1000 to determine initiative. - - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. - """ - return randint(1, 1000)
- - -
[docs]def get_attack(attacker, defender): - """ - Returns a value for an attack roll. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. - - Notes: - In this example, a weapon's accuracy bonus is factored into the attack - roll. Lighter weapons are more accurate but less damaging, and heavier - weapons are less accurate but deal more damage. Of course, you can - change this paradigm completely in your own game. - """ - # Start with a roll from 1 to 100. - attack_value = randint(1, 100) - accuracy_bonus = 0 - # If armed, add weapon's accuracy bonus. - if attacker.db.wielded_weapon: - weapon = attacker.db.wielded_weapon - accuracy_bonus += weapon.db.accuracy_bonus - # If unarmed, use character's unarmed accuracy bonus. - else: - accuracy_bonus += attacker.db.unarmed_accuracy - # Add the accuracy bonus to the attack roll. - attack_value += accuracy_bonus - return attack_value
- - -
[docs]def get_defense(attacker, defender): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. - - Notes: - Characters are given a default defense value of 50 which can be - modified up or down by armor. In this example, wearing armor actually - makes you a little easier to hit, but reduces incoming damage. - """ - # Start with a defense value of 50 for a 50/50 chance to hit. - defense_value = 50 - # Modify this value based on defender's armor. - if defender.db.worn_armor: - armor = defender.db.worn_armor - defense_value += armor.db.defense_modifier - return defense_value
- - -
[docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - Damage is determined by the attacker's wielded weapon, or the attacker's - unarmed damage range if no weapon is wielded. Incoming damage is reduced - by the defender's armor. - """ - damage_value = 0 - # Generate a damage value from wielded weapon if armed - if attacker.db.wielded_weapon: - weapon = attacker.db.wielded_weapon - # Roll between minimum and maximum damage - damage_value = randint(weapon.db.damage_range[0], weapon.db.damage_range[1]) - # Use attacker's unarmed damage otherwise - else: - damage_value = randint( - attacker.db.unarmed_damage_range[0], attacker.db.unarmed_damage_range[1] - ) - # If defender is armored, reduce incoming damage - if defender.db.worn_armor: - armor = defender.db.worn_armor - damage_value -= armor.db.damage_reduction - # Make sure minimum damage is 0 - if damage_value < 0: - damage_value = 0 - return damage_value
- - -
[docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. - - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
- - -
[docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
- - -
[docs]def resolve_attack(attacker, defender, attack_value=None, defense_value=None): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Notes: - Even though the attack and defense values are calculated - extremely simply, they are separated out into their own functions - so that they are easier to expand upon. - """ - # Get the attacker's weapon type to reference in combat messages. - attackers_weapon = "attack" - if attacker.db.wielded_weapon: - weapon = attacker.db.wielded_weapon - attackers_weapon = weapon.db.weapon_type_name - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents( - "%s's %s misses %s!" % (attacker, attackers_weapon, defender) - ) - else: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - if damage_value > 0: - attacker.location.msg_contents( - "%s's %s strikes %s for %i damage!" - % (attacker, attackers_weapon, defender, damage_value) - ) - else: - attacker.location.msg_contents( - "%s's %s bounces harmlessly off %s!" % (attacker, attackers_weapon, defender) - ) - apply_damage(defender, damage_value) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
- - -
[docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
- - -
[docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
- - -
[docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
- - -
[docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
- - -""" ----------------------------------------------------------------------------- -SCRIPTS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class TBEquipTurnHandler(DefaultScript): - """ - This is the script that handles the progression of combat through turns. - On creation (when a fight is started) it adds all combat-ready characters - to its roster and then sorts them into a turn order. There can only be one - fight going on in a single room at a time, so the script is assigned to a - room as its object. - - Fights persist until only one participant is left with any HP or all - remaining participants choose to end the combat with the 'disengage' command. - """ - -
[docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
- -
[docs] def at_stop(self): - """ - Called at script termination. - """ - for fighter in self.db.fighters: - combat_cleanup(fighter) # Clean up the combat attributes for every fighter. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
- -
[docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
- -
[docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. - - Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
- -
[docs] def start_turn(self, character): - """ - Readies a character for the start of their turn by replenishing their - available actions and notifying them that their turn has come up. - - Args: - character (obj): Character to be readied. - - Notes: - Here, you only get one action per turn, but you might want to allow more than - one per turn, or even grant a number of actions based on a character's - attributes. You can even add multiple different kinds of actions, I.E. actions - separated for movement, by adding "character.db.combat_movesleft = 3" or - something similar. - """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Prompt the character for their turn and give some information. - character.msg("|wIt's your turn! You have %i HP remaining.|n" % character.db.hp)
- -
[docs] def next_turn(self): - """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - newchar = self.db.fighters[self.db.turn] # Note the new character - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn.
- -
[docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. - - Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
- -
[docs] def join_fight(self, character): - """ - Adds a new character to a fight already in progress. - - Args: - character (obj): Character to be added to the fight. - """ - # Inserts the fighter to the turn order, right behind whoever's turn it currently is. - self.db.fighters.insert(self.db.turn, character) - # Tick the turn counter forward one to compensate. - self.db.turn += 1 - # Initialize the character like you do at the start. - self.initialize_for_combat(character)
- - -""" ----------------------------------------------------------------------------- -TYPECLASSES START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class TBEWeapon(DefaultObject): - """ - A weapon which can be wielded in combat with the 'wield' command. - """ - -
[docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - """ - self.db.damage_range = (15, 25) # Minimum and maximum damage on hit - self.db.accuracy_bonus = 0 # Bonus to attack rolls (or penalty if negative) - self.db.weapon_type_name = ( - "weapon" # Single word for weapon - I.E. "dagger", "staff", "scimitar" - )
- -
[docs] def at_drop(self, dropper): - """ - Stop being wielded if dropped. - """ - if dropper.db.wielded_weapon == self: - dropper.db.wielded_weapon = None - dropper.location.msg_contents("%s stops wielding %s." % (dropper, self))
- -
[docs] def at_give(self, giver, getter): - """ - Stop being wielded if given. - """ - if giver.db.wielded_weapon == self: - giver.db.wielded_weapon = None - giver.location.msg_contents("%s stops wielding %s." % (giver, self))
- - -
[docs]class TBEArmor(DefaultObject): - """ - A set of armor which can be worn with the 'don' command. - """ - -
[docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - """ - self.db.damage_reduction = 4 # Amount of incoming damage reduced by armor - self.db.defense_modifier = ( - -4 - ) # Amount to modify defense value (pos = harder to hit, neg = easier)
- -
[docs] def at_before_drop(self, dropper): - """ - Can't drop in combat. - """ - if is_in_combat(dropper): - dropper.msg("You can't doff armor in a fight!") - return False - return True
- -
[docs] def at_drop(self, dropper): - """ - Stop being wielded if dropped. - """ - if dropper.db.worn_armor == self: - dropper.db.worn_armor = None - dropper.location.msg_contents("%s removes %s." % (dropper, self))
- -
[docs] def at_before_give(self, giver, getter): - """ - Can't give away in combat. - """ - if is_in_combat(giver): - dropper.msg("You can't doff armor in a fight!") - return False - return True
- -
[docs] def at_give(self, giver, getter): - """ - Stop being wielded if given. - """ - if giver.db.worn_armor == self: - giver.db.worn_armor = None - giver.location.msg_contents("%s removes %s." % (giver, self))
- - -
[docs]class TBEquipCharacter(DefaultCharacter): - """ - A character able to participate in turn-based combat. Has attributes for current - and maximum HP, and access to combat commands. - """ - -
[docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - """ - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - self.db.wielded_weapon = None # Currently used weapon - self.db.worn_armor = None # Currently worn armor - self.db.unarmed_damage_range = (5, 15) # Minimum and maximum unarmed damage - self.db.unarmed_accuracy = 30 # Accuracy bonus for unarmed attacks - - """ - Adds attributes for a character's current and maximum HP. - We're just going to set this value at '100' by default. - - You may want to expand this to include various 'stats' that - can be changed at creation and factor into combat calculations. - """
- -
[docs] def at_before_move(self, destination): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
- - -""" ----------------------------------------------------------------------------- -COMMANDS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class CmdFight(Command): - """ - Starts a fight with everyone in the same room as you. - - Usage: - fight - - When you start a fight, everyone in the room who is able to - fight is added to combat, and a turn order is randomly rolled. - When it's your turn, you can attack other characters. - """ - - key = "fight" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.turnbattle.tb_equip.TBEquipTurnHandler")
- # Remember you'll have to change the path to the script if you copy this code to your own modules! - - -
[docs]class CmdAttack(Command): - """ - Attacks another character. - - Usage: - attack <target> - - When in a fight, you may attack another character. The attack has - a chance to hit, and if successful, will deal damage. - """ - - key = "attack" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender) - spend_action(self.caller, 1, action_name="attack") # Use up one action.
- - -
[docs]class CmdPass(Command): - """ - Passes on your turn. - - Usage: - pass - - When in a fight, you can use this command to end your turn early, even - if there are still any actions you can take. - """ - - key = "pass" - aliases = ["wait", "hold"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
- - -
[docs]class CmdDisengage(Command): - """ - Passes your turn and attempts to end combat. - - Usage: - disengage - - Ends your turn early and signals that you're trying to end - the fight. If all participants in a fight disengage, the - fight ends. - """ - - key = "disengage" - aliases = ["spare"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
- - -
[docs]class CmdRest(Command): - """ - Recovers damage. - - Usage: - rest - - Resting recovers your HP to its maximum, but you can only - rest if you're not in a fight. - """ - - key = "rest" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - - if is_in_combat(self.caller): # If you're in combat - self.caller.msg("You can't rest while you're in combat.") - return - - self.caller.db.hp = self.caller.db.max_hp # Set current HP to maximum - self.caller.location.msg_contents("%s rests to recover HP." % self.caller) - """ - You'll probably want to replace this with your own system for recovering HP. - """
- - -
[docs]class CmdCombatHelp(CmdHelp): - """ - View help or a list of topics - - Usage: - help <topic or command> - help list - help all - - This will search for help on commands and other - topics related to the game. - """ - - # Just like the default help command, but will give quick - # tips on combat when used in a fight with no arguments. - -
[docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack a target, attempting to deal damage.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - ) - else: - super().func() # Call the default help command
- - -
[docs]class CmdWield(Command): - """ - Wield a weapon you are carrying - - Usage: - wield <weapon> - - Select a weapon you are carrying to wield in combat. If - you are already wielding another weapon, you will switch - to the weapon you specify instead. Using this command in - combat will spend your action for your turn. Use the - "unwield" command to stop wielding any weapon you are - currently wielding. - """ - - key = "wield" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - # If in combat, check to see if it's your turn. - if is_in_combat(self.caller): - if not is_turn(self.caller): - self.caller.msg("You can only do that on your turn.") - return - if not self.args: - self.caller.msg("Usage: wield <obj>") - return - weapon = self.caller.search(self.args, candidates=self.caller.contents) - if not weapon: - return - if not weapon.is_typeclass("evennia.contrib.turnbattle.tb_equip.TBEWeapon", exact=True): - self.caller.msg("That's not a weapon!") - # Remember to update the path to the weapon typeclass if you move this module! - return - - if not self.caller.db.wielded_weapon: - self.caller.db.wielded_weapon = weapon - self.caller.location.msg_contents("%s wields %s." % (self.caller, weapon)) - else: - old_weapon = self.caller.db.wielded_weapon - self.caller.db.wielded_weapon = weapon - self.caller.location.msg_contents( - "%s lowers %s and wields %s." % (self.caller, old_weapon, weapon) - ) - # Spend an action if in combat. - if is_in_combat(self.caller): - spend_action(self.caller, 1, action_name="wield") # Use up one action.
- - -
[docs]class CmdUnwield(Command): - """ - Stop wielding a weapon. - - Usage: - unwield - - After using this command, you will stop wielding any - weapon you are currently wielding and become unarmed. - """ - - key = "unwield" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - # If in combat, check to see if it's your turn. - if is_in_combat(self.caller): - if not is_turn(self.caller): - self.caller.msg("You can only do that on your turn.") - return - if not self.caller.db.wielded_weapon: - self.caller.msg("You aren't wielding a weapon!") - else: - old_weapon = self.caller.db.wielded_weapon - self.caller.db.wielded_weapon = None - self.caller.location.msg_contents("%s lowers %s." % (self.caller, old_weapon))
- - -
[docs]class CmdDon(Command): - """ - Don armor that you are carrying - - Usage: - don <armor> - - Select armor to wear in combat. You can't use this - command in the middle of a fight. Use the "doff" - command to remove any armor you are wearing. - """ - - key = "don" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - # Can't do this in combat - if is_in_combat(self.caller): - self.caller.msg("You can't don armor in a fight!") - return - if not self.args: - self.caller.msg("Usage: don <obj>") - return - armor = self.caller.search(self.args, candidates=self.caller.contents) - if not armor: - return - if not armor.is_typeclass("evennia.contrib.turnbattle.tb_equip.TBEArmor", exact=True): - self.caller.msg("That's not armor!") - # Remember to update the path to the armor typeclass if you move this module! - return - - if not self.caller.db.worn_armor: - self.caller.db.worn_armor = armor - self.caller.location.msg_contents("%s dons %s." % (self.caller, armor)) - else: - old_armor = self.caller.db.worn_armor - self.caller.db.worn_armor = armor - self.caller.location.msg_contents( - "%s removes %s and dons %s." % (self.caller, old_armor, armor) - )
- - -
[docs]class CmdDoff(Command): - """ - Stop wearing armor. - - Usage: - doff - - After using this command, you will stop wearing any - armor you are currently using and become unarmored. - You can't use this command in combat. - """ - - key = "doff" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - # Can't do this in combat - if is_in_combat(self.caller): - self.caller.msg("You can't doff armor in a fight!") - return - if not self.caller.db.worn_armor: - self.caller.msg("You aren't wearing any armor!") - else: - old_armor = self.caller.db.worn_armor - self.caller.db.worn_armor = None - self.caller.location.msg_contents("%s removes %s." % (self.caller, old_armor))
- - -
[docs]class BattleCmdSet(default_cmds.CharacterCmdSet): - """ - This command set includes all the commmands used in the battle system. - """ - - key = "DefaultCharacter" - -
[docs] def at_cmdset_creation(self): - """ - Populates the cmdset - """ - self.add(CmdFight()) - self.add(CmdAttack()) - self.add(CmdRest()) - self.add(CmdPass()) - self.add(CmdDisengage()) - self.add(CmdCombatHelp()) - self.add(CmdWield()) - self.add(CmdUnwield()) - self.add(CmdDon()) - self.add(CmdDoff())
- - -""" ----------------------------------------------------------------------------- -PROTOTYPES START HERE ----------------------------------------------------------------------------- -""" - -BASEWEAPON = {"typeclass": "evennia.contrib.turnbattle.tb_equip.TBEWeapon"} - -BASEARMOR = {"typeclass": "evennia.contrib.turnbattle.tb_equip.TBEArmor"} - -DAGGER = { - "prototype": "BASEWEAPON", - "damage_range": (10, 20), - "accuracy_bonus": 30, - "key": "a thin steel dagger", - "weapon_type_name": "dagger", -} - -BROADSWORD = { - "prototype": "BASEWEAPON", - "damage_range": (15, 30), - "accuracy_bonus": 15, - "key": "an iron broadsword", - "weapon_type_name": "broadsword", -} - -GREATSWORD = { - "prototype": "BASEWEAPON", - "damage_range": (20, 40), - "accuracy_bonus": 0, - "key": "a rune-etched greatsword", - "weapon_type_name": "greatsword", -} - -LEATHERARMOR = { - "prototype": "BASEARMOR", - "damage_reduction": 2, - "defense_modifier": -2, - "key": "a suit of leather armor", -} - -SCALEMAIL = { - "prototype": "BASEARMOR", - "damage_reduction": 4, - "defense_modifier": -4, - "key": "a suit of scale mail", -} - -PLATEMAIL = { - "prototype": "BASEARMOR", - "damage_reduction": 6, - "defense_modifier": -6, - "key": "a suit of plate mail", -} -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_items.html b/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_items.html deleted file mode 100644 index 56fdcb1073..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_items.html +++ /dev/null @@ -1,1563 +0,0 @@ - - - - - - - - evennia.contrib.turnbattle.tb_items — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.turnbattle.tb_items

-"""
-Simple turn-based combat system with items and status effects
-
-Contrib - Tim Ashley Jenkins 2017
-
-This is a version of the 'turnbattle' combat system that includes
-conditions and usable items, which can instill these conditions, cure
-them, or do just about anything else.
-
-Conditions are stored on characters as a dictionary, where the key
-is the name of the condition and the value is a list of two items:
-an integer representing the number of turns left until the condition
-runs out, and the character upon whose turn the condition timer is
-ticked down. Unlike most combat-related attributes, conditions aren't
-wiped once combat ends - if out of combat, they tick down in real time
-instead.
-
-This module includes a number of example conditions:
-
-    Regeneration: Character recovers HP every turn
-    Poisoned: Character loses HP every turn
-    Accuracy Up: +25 to character's attack rolls
-    Accuracy Down: -25 to character's attack rolls
-    Damage Up: +5 to character's damage
-    Damage Down: -5 to character's damage
-    Defense Up: +15 to character's defense
-    Defense Down: -15 to character's defense
-    Haste: +1 action per turn
-    Paralyzed: No actions per turn
-    Frightened: Character can't use the 'attack' command
-
-Since conditions can have a wide variety of effects, their code is
-scattered throughout the other functions wherever they may apply.
-
-Items aren't given any sort of special typeclass - instead, whether or
-not an object counts as an item is determined by its attributes. To make
-an object into an item, it must have the attribute 'item_func', with
-the value given as a callable - this is the function that will be called
-when an item is used. Other properties of the item, such as how many
-uses it has, whether it's destroyed when its uses are depleted, and such
-can be specified on the item as well, but they are optional.
-
-To install and test, import this module's TBItemsCharacter object into
-your game's character.py module:
-
-    from evennia.contrib.turnbattle.tb_items import TBItemsCharacter
-
-And change your game's character typeclass to inherit from TBItemsCharacter
-instead of the default:
-
-    class Character(TBItemsCharacter):
-
-Next, import this module into your default_cmdsets.py module:
-
-    from evennia.contrib.turnbattle import tb_items
-
-And add the battle command set to your default command set:
-
-    #
-    # any commands you add below will overload the default ones.
-    #
-    self.add(tb_items.BattleCmdSet())
-
-This module is meant to be heavily expanded on, so you may want to copy it
-to your game's 'world' folder and modify it there rather than importing it
-in your game and using it as-is.
-"""
-
-from random import randint
-from evennia import DefaultCharacter, Command, default_cmds, DefaultScript
-from evennia.commands.default.muxcommand import MuxCommand
-from evennia.commands.default.help import CmdHelp
-from evennia.prototypes.spawner import spawn
-from evennia import TICKER_HANDLER as tickerhandler
-
-"""
-----------------------------------------------------------------------------
-OPTIONS
-----------------------------------------------------------------------------
-"""
-
-TURN_TIMEOUT = 30  # Time before turns automatically end, in seconds
-ACTIONS_PER_TURN = 1  # Number of actions allowed per turn
-NONCOMBAT_TURN_TIME = 30  # Time per turn count out of combat
-
-# Condition options start here.
-# If you need to make changes to how your conditions work later,
-# it's best to put the easily tweakable values all in one place!
-
-REGEN_RATE = (4, 8)  # Min and max HP regen for Regeneration
-POISON_RATE = (4, 8)  # Min and max damage for Poisoned
-ACC_UP_MOD = 25  # Accuracy Up attack roll bonus
-ACC_DOWN_MOD = -25  # Accuracy Down attack roll penalty
-DMG_UP_MOD = 5  # Damage Up damage roll bonus
-DMG_DOWN_MOD = -5  # Damage Down damage roll penalty
-DEF_UP_MOD = 15  # Defense Up defense bonus
-DEF_DOWN_MOD = -15  # Defense Down defense penalty
-
-"""
-----------------------------------------------------------------------------
-COMBAT FUNCTIONS START HERE
-----------------------------------------------------------------------------
-"""
-
-
-
[docs]def roll_init(character): - """ - Rolls a number between 1-1000 to determine initiative. - - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. - """ - return randint(1, 1000)
- - -
[docs]def get_attack(attacker, defender): - """ - Returns a value for an attack roll. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. - - Notes: - This is where conditions affecting attack rolls are applied, as well. - Accuracy Up and Accuracy Down are also accounted for in itemfunc_attack(), - so that attack items' accuracy is affected as well. - """ - # For this example, just return a random integer up to 100. - attack_value = randint(1, 100) - # Add to the roll if the attacker has the "Accuracy Up" condition. - if "Accuracy Up" in attacker.db.conditions: - attack_value += ACC_UP_MOD - # Subtract from the roll if the attack has the "Accuracy Down" condition. - if "Accuracy Down" in attacker.db.conditions: - attack_value += ACC_DOWN_MOD - return attack_value
- - -
[docs]def get_defense(attacker, defender): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. - - Notes: - This is where conditions affecting defense are accounted for. - """ - # For this example, just return 50, for about a 50/50 chance of hit. - defense_value = 50 - # Add to defense if the defender has the "Defense Up" condition. - if "Defense Up" in defender.db.conditions: - defense_value += DEF_UP_MOD - # Subtract from defense if the defender has the "Defense Down" condition. - if "Defense Down" in defender.db.conditions: - defense_value += DEF_DOWN_MOD - return defense_value
- - -
[docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - This is where conditions affecting damage are accounted for. Since attack items - roll their own damage in itemfunc_attack(), their damage is unaffected by any - conditions. - """ - # For this example, just generate a number between 15 and 25. - damage_value = randint(15, 25) - # Add to damage roll if attacker has the "Damage Up" condition. - if "Damage Up" in attacker.db.conditions: - damage_value += DMG_UP_MOD - # Subtract from the roll if the attacker has the "Damage Down" condition. - if "Damage Down" in attacker.db.conditions: - damage_value += DMG_DOWN_MOD - return damage_value
- - -
[docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. - - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
- - -
[docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
- - -
[docs]def resolve_attack( - attacker, - defender, - attack_value=None, - defense_value=None, - damage_value=None, - inflict_condition=[], -): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Options: - attack_value (int): Override for attack roll - defense_value (int): Override for defense value - damage_value (int): Override for damage value - inflict_condition (list): Conditions to inflict upon hit, a - list of tuples formated as (condition(str), duration(int)) - - Notes: - This function is called by normal attacks as well as attacks - made with items. - """ - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents("%s's attack misses %s!" % (attacker, defender)) - else: - if not damage_value: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - attacker.location.msg_contents( - "%s hits %s for %i damage!" % (attacker, defender, damage_value) - ) - apply_damage(defender, damage_value) - # Inflict conditions on hit, if any specified - for condition in inflict_condition: - add_condition(defender, attacker, condition[0], condition[1]) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
- - -
[docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
- - -
[docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
- - -
[docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
- - -
[docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
- - -
[docs]def spend_item_use(item, user): - """ - Spends one use on an item with limited uses. - - Args: - item (obj): Item being used - user (obj): Character using the item - - Notes: - If item.db.item_consumable is 'True', the item is destroyed if it - runs out of uses - if it's a string instead of 'True', it will also - spawn a new object as residue, using the value of item.db.item_consumable - as the name of the prototype to spawn. - """ - item.db.item_uses -= 1 # Spend one use - - if item.db.item_uses > 0: # Has uses remaining - # Inform the player - user.msg("%s has %i uses remaining." % (item.key.capitalize(), item.db.item_uses)) - - else: # All uses spent - - if not item.db.item_consumable: # Item isn't consumable - # Just inform the player that the uses are gone - user.msg("%s has no uses remaining." % item.key.capitalize()) - - else: # If item is consumable - if item.db.item_consumable == True: # If the value is 'True', just destroy the item - user.msg("%s has been consumed." % item.key.capitalize()) - item.delete() # Delete the spent item - - else: # If a string, use value of item_consumable to spawn an object in its place - residue = spawn({"prototype": item.db.item_consumable})[0] # Spawn the residue - residue.location = item.location # Move the residue to the same place as the item - user.msg("After using %s, you are left with %s." % (item, residue)) - item.delete() # Delete the spent item
- - -
[docs]def use_item(user, item, target): - """ - Performs the action of using an item. - - Args: - user (obj): Character using the item - item (obj): Item being used - target (obj): Target of the item use - """ - # If item is self only and no target given, set target to self. - if item.db.item_selfonly and target == None: - target = user - - # If item is self only, abort use if used on others. - if item.db.item_selfonly and user != target: - user.msg("%s can only be used on yourself." % item) - return - - # Set kwargs to pass to item_func - kwargs = {} - if item.db.item_kwargs: - kwargs = item.db.item_kwargs - - # Match item_func string to function - try: - item_func = ITEMFUNCS[item.db.item_func] - except KeyError: # If item_func string doesn't match to a function in ITEMFUNCS - user.msg("ERROR: %s not defined in ITEMFUNCS" % item.db.item_func) - return - - # Call the item function - abort if it returns False, indicating an error. - # This performs the actual action of using the item. - # Regardless of what the function returns (if anything), it's still executed. - if item_func(item, user, target, **kwargs) == False: - return - - # If we haven't returned yet, we assume the item was used successfully. - # Spend one use if item has limited uses - if item.db.item_uses: - spend_item_use(item, user) - - # Spend an action if in combat - if is_in_combat(user): - spend_action(user, 1, action_name="item")
- - -
[docs]def condition_tickdown(character, turnchar): - """ - Ticks down the duration of conditions on a character at the start of a given character's turn. - - Args: - character (obj): Character to tick down the conditions of - turnchar (obj): Character whose turn it currently is - - Notes: - In combat, this is called on every fighter at the start of every character's turn. Out of - combat, it's instead called when a character's at_update() hook is called, which is every - 30 seconds by default. - """ - - for key in character.db.conditions: - # The first value is the remaining turns - the second value is whose turn to count down on. - condition_duration = character.db.conditions[key][0] - condition_turnchar = character.db.conditions[key][1] - # If the duration is 'True', then the condition doesn't tick down - it lasts indefinitely. - if not condition_duration is True: - # Count down if the given turn character matches the condition's turn character. - if condition_turnchar == turnchar: - character.db.conditions[key][0] -= 1 - if character.db.conditions[key][0] <= 0: - # If the duration is brought down to 0, remove the condition and inform everyone. - character.location.msg_contents( - "%s no longer has the '%s' condition." % (str(character), str(key)) - ) - del character.db.conditions[key]
- - -
[docs]def add_condition(character, turnchar, condition, duration): - """ - Adds a condition to a fighter. - - Args: - character (obj): Character to give the condition to - turnchar (obj): Character whose turn to tick down the condition on in combat - condition (str): Name of the condition - duration (int or True): Number of turns the condition lasts, or True for indefinite - """ - # The first value is the remaining turns - the second value is whose turn to count down on. - character.db.conditions.update({condition: [duration, turnchar]}) - # Tell everyone! - character.location.msg_contents("%s gains the '%s' condition." % (character, condition))
- - -""" ----------------------------------------------------------------------------- -CHARACTER TYPECLASS ----------------------------------------------------------------------------- -""" - - -
[docs]class TBItemsCharacter(DefaultCharacter): - """ - A character able to participate in turn-based combat. Has attributes for current - and maximum HP, and access to combat commands. - """ - -
[docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - """ - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - self.db.conditions = {} # Set empty dict for conditions - # Subscribe character to the ticker handler - tickerhandler.add(NONCOMBAT_TURN_TIME, self.at_update, idstring="update") - """ - Adds attributes for a character's current and maximum HP. - We're just going to set this value at '100' by default. - - An empty dictionary is created to store conditions later, - and the character is subscribed to the Ticker Handler, which - will call at_update() on the character, with the interval - specified by NONCOMBAT_TURN_TIME above. This is used to tick - down conditions out of combat. - - You may want to expand this to include various 'stats' that - can be changed at creation and factor into combat calculations. - """
- -
[docs] def at_before_move(self, destination): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
- -
[docs] def at_turn_start(self): - """ - Hook called at the beginning of this character's turn in combat. - """ - # Prompt the character for their turn and give some information. - self.msg("|wIt's your turn! You have %i HP remaining.|n" % self.db.hp) - - # Apply conditions that fire at the start of each turn. - self.apply_turn_conditions()
- -
[docs] def apply_turn_conditions(self): - """ - Applies the effect of conditions that occur at the start of each - turn in combat, or every 30 seconds out of combat. - """ - # Regeneration: restores 4 to 8 HP at the start of character's turn - if "Regeneration" in self.db.conditions: - to_heal = randint(REGEN_RATE[0], REGEN_RAGE[1]) # Restore HP - if self.db.hp + to_heal > self.db.max_hp: - to_heal = self.db.max_hp - self.db.hp # Cap healing to max HP - self.db.hp += to_heal - self.location.msg_contents("%s regains %i HP from Regeneration." % (self, to_heal)) - - # Poisoned: does 4 to 8 damage at the start of character's turn - if "Poisoned" in self.db.conditions: - to_hurt = randint(POISON_RATE[0], POISON_RATE[1]) # Deal damage - apply_damage(self, to_hurt) - self.location.msg_contents("%s takes %i damage from being Poisoned." % (self, to_hurt)) - if self.db.hp <= 0: - # Call at_defeat if poison defeats the character - at_defeat(self) - - # Haste: Gain an extra action in combat. - if is_in_combat(self) and "Haste" in self.db.conditions: - self.db.combat_actionsleft += 1 - self.msg("You gain an extra action this turn from Haste!") - - # Paralyzed: Have no actions in combat. - if is_in_combat(self) and "Paralyzed" in self.db.conditions: - self.db.combat_actionsleft = 0 - self.location.msg_contents("%s is Paralyzed, and can't act this turn!" % self) - self.db.combat_turnhandler.turn_end_check(self)
- -
[docs] def at_update(self): - """ - Fires every 30 seconds. - """ - if not is_in_combat(self): # Not in combat - # Change all conditions to update on character's turn. - for key in self.db.conditions: - self.db.conditions[key][1] = self - # Apply conditions that fire every turn - self.apply_turn_conditions() - # Tick down condition durations - condition_tickdown(self, self)
- - -
[docs]class TBItemsCharacterTest(TBItemsCharacter): - """ - Just like the TBItemsCharacter, but doesn't subscribe to the TickerHandler. - This makes it easier to run unit tests on. - """ - -
[docs] def at_object_creation(self): - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - self.db.conditions = {} # Set empty dict for conditions
- - -""" ----------------------------------------------------------------------------- -SCRIPTS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class TBItemsTurnHandler(DefaultScript): - """ - This is the script that handles the progression of combat through turns. - On creation (when a fight is started) it adds all combat-ready characters - to its roster and then sorts them into a turn order. There can only be one - fight going on in a single room at a time, so the script is assigned to a - room as its object. - - Fights persist until only one participant is left with any HP or all - remaining participants choose to end the combat with the 'disengage' command. - """ - -
[docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
- -
[docs] def at_stop(self): - """ - Called at script termination. - """ - for fighter in self.db.fighters: - combat_cleanup(fighter) # Clean up the combat attributes for every fighter. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
- -
[docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
- -
[docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. - - Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
- -
[docs] def start_turn(self, character): - """ - Readies a character for the start of their turn by replenishing their - available actions and notifying them that their turn has come up. - - Args: - character (obj): Character to be readied. - - Notes: - Here, you only get one action per turn, but you might want to allow more than - one per turn, or even grant a number of actions based on a character's - attributes. You can even add multiple different kinds of actions, I.E. actions - separated for movement, by adding "character.db.combat_movesleft = 3" or - something similar. - """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Call character's at_turn_start() hook. - character.at_turn_start()
- -
[docs] def next_turn(self): - """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - - newchar = self.db.fighters[self.db.turn] # Note the new character - - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn. - - # Count down condition timers. - for fighter in self.db.fighters: - condition_tickdown(fighter, newchar)
- -
[docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. - - Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
- -
[docs] def join_fight(self, character): - """ - Adds a new character to a fight already in progress. - - Args: - character (obj): Character to be added to the fight. - """ - # Inserts the fighter to the turn order, right behind whoever's turn it currently is. - self.db.fighters.insert(self.db.turn, character) - # Tick the turn counter forward one to compensate. - self.db.turn += 1 - # Initialize the character like you do at the start. - self.initialize_for_combat(character)
- - -""" ----------------------------------------------------------------------------- -COMMANDS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class CmdFight(Command): - """ - Starts a fight with everyone in the same room as you. - - Usage: - fight - - When you start a fight, everyone in the room who is able to - fight is added to combat, and a turn order is randomly rolled. - When it's your turn, you can attack other characters. - """ - - key = "fight" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.turnbattle.tb_items.TBItemsTurnHandler")
- # Remember you'll have to change the path to the script if you copy this code to your own modules! - - -
[docs]class CmdAttack(Command): - """ - Attacks another character. - - Usage: - attack <target> - - When in a fight, you may attack another character. The attack has - a chance to hit, and if successful, will deal damage. - """ - - key = "attack" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - if "Frightened" in self.caller.db.conditions: # Can't attack if frightened - self.caller.msg("You're too frightened to attack!") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender) - spend_action(self.caller, 1, action_name="attack") # Use up one action.
- - -
[docs]class CmdPass(Command): - """ - Passes on your turn. - - Usage: - pass - - When in a fight, you can use this command to end your turn early, even - if there are still any actions you can take. - """ - - key = "pass" - aliases = ["wait", "hold"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
- - -
[docs]class CmdDisengage(Command): - """ - Passes your turn and attempts to end combat. - - Usage: - disengage - - Ends your turn early and signals that you're trying to end - the fight. If all participants in a fight disengage, the - fight ends. - """ - - key = "disengage" - aliases = ["spare"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
- - -
[docs]class CmdRest(Command): - """ - Recovers damage. - - Usage: - rest - - Resting recovers your HP to its maximum, but you can only - rest if you're not in a fight. - """ - - key = "rest" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - - if is_in_combat(self.caller): # If you're in combat - self.caller.msg("You can't rest while you're in combat.") - return - - self.caller.db.hp = self.caller.db.max_hp # Set current HP to maximum - self.caller.location.msg_contents("%s rests to recover HP." % self.caller) - """ - You'll probably want to replace this with your own system for recovering HP. - """
- - -
[docs]class CmdCombatHelp(CmdHelp): - """ - View help or a list of topics - - Usage: - help <topic or command> - help list - help all - - This will search for help on commands and other - topics related to the game. - """ - - # Just like the default help command, but will give quick - # tips on combat when used in a fight with no arguments. - -
[docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack a target, attempting to deal damage.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - + "|wUse:|n Use an item you're carrying." - ) - else: - super(CmdCombatHelp, self).func() # Call the default help command
- - -
[docs]class CmdUse(MuxCommand): - """ - Use an item. - - Usage: - use <item> [= target] - - An item can have various function - looking at the item may - provide information as to its effects. Some items can be used - to attack others, and as such can only be used in combat. - """ - - key = "use" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - # Search for item - item = self.caller.search(self.lhs, candidates=self.caller.contents) - if not item: - return - - # Search for target, if any is given - target = None - if self.rhs: - target = self.caller.search(self.rhs) - if not target: - return - - # If in combat, can only use items on your turn - if is_in_combat(self.caller): - if not is_turn(self.caller): - self.caller.msg("You can only use items on your turn.") - return - - if not item.db.item_func: # Object has no item_func, not usable - self.caller.msg("'%s' is not a usable item." % item.key.capitalize()) - return - - if item.attributes.has("item_uses"): # Item has limited uses - if item.db.item_uses <= 0: # Limited uses are spent - self.caller.msg("'%s' has no uses remaining." % item.key.capitalize()) - return - - # If everything checks out, call the use_item function - use_item(self.caller, item, target)
- - -
[docs]class BattleCmdSet(default_cmds.CharacterCmdSet): - """ - This command set includes all the commmands used in the battle system. - """ - - key = "DefaultCharacter" - -
[docs] def at_cmdset_creation(self): - """ - Populates the cmdset - """ - self.add(CmdFight()) - self.add(CmdAttack()) - self.add(CmdRest()) - self.add(CmdPass()) - self.add(CmdDisengage()) - self.add(CmdCombatHelp()) - self.add(CmdUse())
- - -""" ----------------------------------------------------------------------------- -ITEM FUNCTIONS START HERE ----------------------------------------------------------------------------- - -These functions carry out the action of using an item - every item should -contain a db entry "item_func", with its value being a string that is -matched to one of these functions in the ITEMFUNCS dictionary below. - -Every item function must take the following arguments: - item (obj): The item being used - user (obj): The character using the item - target (obj): The target of the item use - -Item functions must also accept **kwargs - these keyword arguments can be -used to define how different items that use the same function can have -different effects (for example, different attack items doing different -amounts of damage). - -Each function below contains a description of what kwargs the function will -take and the effect they have on the result. -""" - - -
[docs]def itemfunc_heal(item, user, target, **kwargs): - """ - Item function that heals HP. - - kwargs: - min_healing(int): Minimum amount of HP recovered - max_healing(int): Maximum amount of HP recovered - """ - if not target: - target = user # Target user if none specified - - if not target.attributes.has("max_hp"): # Has no HP to speak of - user.msg("You can't use %s on that." % item) - return False # Returning false aborts the item use - - if target.db.hp >= target.db.max_hp: - user.msg("%s is already at full health." % target) - return False - - min_healing = 20 - max_healing = 40 - - # Retrieve healing range from kwargs, if present - if "healing_range" in kwargs: - min_healing = kwargs["healing_range"][0] - max_healing = kwargs["healing_range"][1] - - to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp - if target.db.hp + to_heal > target.db.max_hp: - to_heal = target.db.max_hp - target.db.hp # Cap healing to max HP - target.db.hp += to_heal - - user.location.msg_contents("%s uses %s! %s regains %i HP!" % (user, item, target, to_heal))
- - -
[docs]def itemfunc_add_condition(item, user, target, **kwargs): - """ - Item function that gives the target one or more conditions. - - kwargs: - conditions (list): Conditions added by the item - formatted as a list of tuples: (condition (str), duration (int or True)) - - Notes: - Should mostly be used for beneficial conditions - use itemfunc_attack - for an item that can give an enemy a harmful condition. - """ - conditions = [("Regeneration", 5)] - - if not target: - target = user # Target user if none specified - - if not target.attributes.has("max_hp"): # Is not a fighter - user.msg("You can't use %s on that." % item) - return False # Returning false aborts the item use - - # Retrieve condition / duration from kwargs, if present - if "conditions" in kwargs: - conditions = kwargs["conditions"] - - user.location.msg_contents("%s uses %s!" % (user, item)) - - # Add conditions to the target - for condition in conditions: - add_condition(target, user, condition[0], condition[1])
- - -
[docs]def itemfunc_cure_condition(item, user, target, **kwargs): - """ - Item function that'll remove given conditions from a target. - - kwargs: - to_cure(list): List of conditions (str) that the item cures when used - """ - to_cure = ["Poisoned"] - - if not target: - target = user # Target user if none specified - - if not target.attributes.has("max_hp"): # Is not a fighter - user.msg("You can't use %s on that." % item) - return False # Returning false aborts the item use - - # Retrieve condition(s) to cure from kwargs, if present - if "to_cure" in kwargs: - to_cure = kwargs["to_cure"] - - item_msg = "%s uses %s! " % (user, item) - - for key in target.db.conditions: - if key in to_cure: - # If condition specified in to_cure, remove it. - item_msg += "%s no longer has the '%s' condition. " % (str(target), str(key)) - del target.db.conditions[key] - - user.location.msg_contents(item_msg)
- - -
[docs]def itemfunc_attack(item, user, target, **kwargs): - """ - Item function that attacks a target. - - kwargs: - min_damage(int): Minimum damage dealt by the attack - max_damage(int): Maximum damage dealth by the attack - accuracy(int): Bonus / penalty to attack accuracy roll - inflict_condition(list): List of conditions inflicted on hit, - formatted as a (str, int) tuple containing condition name - and duration. - - Notes: - Calls resolve_attack at the end. - """ - if not is_in_combat(user): - user.msg("You can only use that in combat.") - return False # Returning false aborts the item use - - if not target: - user.msg("You have to specify a target to use %s! (use <item> = <target>)" % item) - return False - - if target == user: - user.msg("You can't attack yourself!") - return False - - if not target.db.hp: # Has no HP - user.msg("You can't use %s on that." % item) - return False - - min_damage = 20 - max_damage = 40 - accuracy = 0 - inflict_condition = [] - - # Retrieve values from kwargs, if present - if "damage_range" in kwargs: - min_damage = kwargs["damage_range"][0] - max_damage = kwargs["damage_range"][1] - if "accuracy" in kwargs: - accuracy = kwargs["accuracy"] - if "inflict_condition" in kwargs: - inflict_condition = kwargs["inflict_condition"] - - # Roll attack and damage - attack_value = randint(1, 100) + accuracy - damage_value = randint(min_damage, max_damage) - - # Account for "Accuracy Up" and "Accuracy Down" conditions - if "Accuracy Up" in user.db.conditions: - attack_value += 25 - if "Accuracy Down" in user.db.conditions: - attack_value -= 25 - - user.location.msg_contents("%s attacks %s with %s!" % (user, target, item)) - resolve_attack( - user, - target, - attack_value=attack_value, - damage_value=damage_value, - inflict_condition=inflict_condition, - )
- - -# Match strings to item functions here. We can't store callables on -# prototypes, so we store a string instead, matching that string to -# a callable in this dictionary. -ITEMFUNCS = { - "heal": itemfunc_heal, - "attack": itemfunc_attack, - "add_condition": itemfunc_add_condition, - "cure_condition": itemfunc_cure_condition, -} - -""" ----------------------------------------------------------------------------- -PROTOTYPES START HERE ----------------------------------------------------------------------------- - -You can paste these prototypes into your game's prototypes.py module in your -/world/ folder, and use the spawner to create them - they serve as examples -of items you can make and a handy way to demonstrate the system for -conditions as well. - -Items don't have any particular typeclass - any object with a db entry -"item_func" that references one of the functions given above can be used as -an item with the 'use' command. - -Only "item_func" is required, but item behavior can be further modified by -specifying any of the following: - - item_uses (int): If defined, item has a limited number of uses - - item_selfonly (bool): If True, user can only use the item on themself - - item_consumable(True or str): If True, item is destroyed when it runs - out of uses. If a string is given, the item will spawn a new - object as it's destroyed, with the string specifying what prototype - to spawn. - - item_kwargs (dict): Keyword arguments to pass to the function defined in - item_func. Unique to each function, and can be used to make multiple - items using the same function work differently. -""" - -MEDKIT = { - "key": "a medical kit", - "aliases": ["medkit"], - "desc": "A standard medical kit. It can be used a few times to heal wounds.", - "item_func": "heal", - "item_uses": 3, - "item_consumable": True, - "item_kwargs": {"healing_range": (15, 25)}, -} - -GLASS_BOTTLE = {"key": "a glass bottle", "desc": "An empty glass bottle."} - -HEALTH_POTION = { - "key": "a health potion", - "desc": "A glass bottle full of a mystical potion that heals wounds when used.", - "item_func": "heal", - "item_uses": 1, - "item_consumable": "GLASS_BOTTLE", - "item_kwargs": {"healing_range": (35, 50)}, -} - -REGEN_POTION = { - "key": "a regeneration potion", - "desc": "A glass bottle full of a mystical potion that regenerates wounds over time.", - "item_func": "add_condition", - "item_uses": 1, - "item_consumable": "GLASS_BOTTLE", - "item_kwargs": {"conditions": [("Regeneration", 10)]}, -} - -HASTE_POTION = { - "key": "a haste potion", - "desc": "A glass bottle full of a mystical potion that hastens its user.", - "item_func": "add_condition", - "item_uses": 1, - "item_consumable": "GLASS_BOTTLE", - "item_kwargs": {"conditions": [("Haste", 10)]}, -} - -BOMB = { - "key": "a rotund bomb", - "desc": "A large black sphere with a fuse at the end. Can be used on enemies in combat.", - "item_func": "attack", - "item_uses": 1, - "item_consumable": True, - "item_kwargs": {"damage_range": (25, 40), "accuracy": 25}, -} - -POISON_DART = { - "key": "a poison dart", - "desc": "A thin dart coated in deadly poison. Can be used on enemies in combat", - "item_func": "attack", - "item_uses": 1, - "item_consumable": True, - "item_kwargs": { - "damage_range": (5, 10), - "accuracy": 25, - "inflict_condition": [("Poisoned", 10)], - }, -} - -TASER = { - "key": "a taser", - "desc": "A device that can be used to paralyze enemies in combat.", - "item_func": "attack", - "item_kwargs": { - "damage_range": (10, 20), - "accuracy": 0, - "inflict_condition": [("Paralyzed", 1)], - }, -} - -GHOST_GUN = { - "key": "a ghost gun", - "desc": "A gun that fires scary ghosts at people. Anyone hit by a ghost becomes frightened.", - "item_func": "attack", - "item_uses": 6, - "item_kwargs": { - "damage_range": (5, 10), - "accuracy": 15, - "inflict_condition": [("Frightened", 1)], - }, -} - -ANTIDOTE_POTION = { - "key": "an antidote potion", - "desc": "A glass bottle full of a mystical potion that cures poison when used.", - "item_func": "cure_condition", - "item_uses": 1, - "item_consumable": "GLASS_BOTTLE", - "item_kwargs": {"to_cure": ["Poisoned"]}, -} - -AMULET_OF_MIGHT = { - "key": "The Amulet of Might", - "desc": "The one who holds this amulet can call upon its power to gain great strength.", - "item_func": "add_condition", - "item_selfonly": True, - "item_kwargs": {"conditions": [("Damage Up", 3), ("Accuracy Up", 3), ("Defense Up", 3)]}, -} - -AMULET_OF_WEAKNESS = { - "key": "The Amulet of Weakness", - "desc": "The one who holds this amulet can call upon its power to gain great weakness. It's not a terribly useful artifact.", - "item_func": "add_condition", - "item_selfonly": True, - "item_kwargs": {"conditions": [("Damage Down", 3), ("Accuracy Down", 3), ("Defense Down", 3)]}, -} -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_magic.html b/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_magic.html deleted file mode 100644 index 5698f5baa8..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_magic.html +++ /dev/null @@ -1,1485 +0,0 @@ - - - - - - - - evennia.contrib.turnbattle.tb_magic — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.turnbattle.tb_magic

-"""
-Simple turn-based combat system with spell casting
-
-Contrib - Tim Ashley Jenkins 2017
-
-This is a version of the 'turnbattle' contrib that includes a basic,
-expandable framework for a 'magic system', whereby players can spend
-a limited resource (MP) to achieve a wide variety of effects, both in
-and out of combat. This does not have to strictly be a system for
-magic - it can easily be re-flavored to any other sort of resource
-based mechanic, like psionic powers, special moves and stamina, and
-so forth.
-
-In this system, spells are learned by name with the 'learnspell'
-command, and then used with the 'cast' command. Spells can be cast in or
-out of combat - some spells can only be cast in combat, some can only be
-cast outside of combat, and some can be cast any time. However, if you
-are in combat, you can only cast a spell on your turn, and doing so will
-typically use an action (as specified in the spell's funciton).
-
-Spells are defined at the end of the module in a database that's a
-dictionary of dictionaries - each spell is matched by name to a function,
-along with various parameters that restrict when the spell can be used and
-what the spell can be cast on. Included is a small variety of spells that
-damage opponents and heal HP, as well as one that creates an object.
-
-Because a spell can call any function, a spell can be made to do just
-about anything at all. The SPELLS dictionary at the bottom of the module
-even allows kwargs to be passed to the spell function, so that the same
-function can be re-used for multiple similar spells.
-
-Spells in this system work on a very basic resource: MP, which is spent
-when casting spells and restored by resting. It shouldn't be too difficult
-to modify this system to use spell slots, some physical fuel or resource,
-or whatever else your game requires.
-
-To install and test, import this module's TBMagicCharacter object into
-your game's character.py module:
-
-    from evennia.contrib.turnbattle.tb_magic import TBMagicCharacter
-
-And change your game's character typeclass to inherit from TBMagicCharacter
-instead of the default:
-
-    class Character(TBMagicCharacter):
-
-Note: If your character already existed you need to also make sure
-to re-run the creation hooks on it to set the needed Attributes.
-Use `update self` to try on yourself or use py to call `at_object_creation()`
-on all existing Characters.
-
-
-Next, import this module into your default_cmdsets.py module:
-
-    from evennia.contrib.turnbattle import tb_magic
-
-And add the battle command set to your default command set:
-
-    #
-    # any commands you add below will overload the default ones.
-    #
-    self.add(tb_magic.BattleCmdSet())
-
-This module is meant to be heavily expanded on, so you may want to copy it
-to your game's 'world' folder and modify it there rather than importing it
-in your game and using it as-is.
-"""
-
-from random import randint
-from evennia import DefaultCharacter, Command, default_cmds, DefaultScript, create_object
-from evennia.commands.default.muxcommand import MuxCommand
-from evennia.commands.default.help import CmdHelp
-
-"""
-----------------------------------------------------------------------------
-OPTIONS
-----------------------------------------------------------------------------
-"""
-
-TURN_TIMEOUT = 30  # Time before turns automatically end, in seconds
-ACTIONS_PER_TURN = 1  # Number of actions allowed per turn
-
-"""
-----------------------------------------------------------------------------
-COMBAT FUNCTIONS START HERE
-----------------------------------------------------------------------------
-"""
-
-
-
[docs]def roll_init(character): - """ - Rolls a number between 1-1000 to determine initiative. - - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. - """ - return randint(1, 1000)
- - -
[docs]def get_attack(attacker, defender): - """ - Returns a value for an attack roll. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. - - Notes: - By default, returns a random integer from 1 to 100 without using any - properties from either the attacker or defender. - - This can easily be expanded to return a value based on characters stats, - equipment, and abilities. This is why the attacker and defender are passed - to this function, even though nothing from either one are used in this example. - """ - # For this example, just return a random integer up to 100. - attack_value = randint(1, 100) - return attack_value
- - -
[docs]def get_defense(attacker, defender): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. - - Notes: - By default, returns 50, not taking any properties of the defender or - attacker into account. - - As above, this can be expanded upon based on character stats and equipment. - """ - # For this example, just return 50, for about a 50/50 chance of hit. - defense_value = 50 - return defense_value
- - -
[docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - By default, returns a random integer from 15 to 25 without using any - properties from either the attacker or defender. - - Again, this can be expanded upon. - """ - # For this example, just generate a number between 15 and 25. - damage_value = randint(15, 25) - return damage_value
- - -
[docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. - - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
- - -
[docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
- - -
[docs]def resolve_attack(attacker, defender, attack_value=None, defense_value=None): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Notes: - Even though the attack and defense values are calculated - extremely simply, they are separated out into their own functions - so that they are easier to expand upon. - """ - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents("%s's attack misses %s!" % (attacker, defender)) - else: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - attacker.location.msg_contents( - "%s hits %s for %i damage!" % (attacker, defender, damage_value) - ) - apply_damage(defender, damage_value) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
- - -
[docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
- - -
[docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
- - -
[docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
- - -
[docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if not is_in_combat(character): - return - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
- - -""" ----------------------------------------------------------------------------- -CHARACTER TYPECLASS ----------------------------------------------------------------------------- -""" - - -
[docs]class TBMagicCharacter(DefaultCharacter): - """ - A character able to participate in turn-based combat. Has attributes for current - and maximum HP, and access to combat commands. - """ - -
[docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - - Adds attributes for a character's current and maximum HP. - We're just going to set this value at '100' by default. - - You may want to expand this to include various 'stats' that - can be changed at creation and factor into combat calculations. - """ - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - self.db.spells_known = [] # Set empty spells known list - self.db.max_mp = 20 # Set maximum MP to 20 - self.db.mp = self.db.max_mp # Set current MP to maximum
- -
[docs] def at_before_move(self, destination): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
- - -""" ----------------------------------------------------------------------------- -SCRIPTS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class TBMagicTurnHandler(DefaultScript): - """ - This is the script that handles the progression of combat through turns. - On creation (when a fight is started) it adds all combat-ready characters - to its roster and then sorts them into a turn order. There can only be one - fight going on in a single room at a time, so the script is assigned to a - room as its object. - - Fights persist until only one participant is left with any HP or all - remaining participants choose to end the combat with the 'disengage' command. - """ - -
[docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
- -
[docs] def at_stop(self): - """ - Called at script termination. - """ - for fighter in self.db.fighters: - combat_cleanup(fighter) # Clean up the combat attributes for every fighter. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
- -
[docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
- -
[docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. - - Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
- -
[docs] def start_turn(self, character): - """ - Readies a character for the start of their turn by replenishing their - available actions and notifying them that their turn has come up. - - Args: - character (obj): Character to be readied. - - Notes: - Here, you only get one action per turn, but you might want to allow more than - one per turn, or even grant a number of actions based on a character's - attributes. You can even add multiple different kinds of actions, I.E. actions - separated for movement, by adding "character.db.combat_movesleft = 3" or - something similar. - """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Prompt the character for their turn and give some information. - character.msg("|wIt's your turn! You have %i HP remaining.|n" % character.db.hp)
- -
[docs] def next_turn(self): - """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - newchar = self.db.fighters[self.db.turn] # Note the new character - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn.
- -
[docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. - - Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
- -
[docs] def join_fight(self, character): - """ - Adds a new character to a fight already in progress. - - Args: - character (obj): Character to be added to the fight. - """ - # Inserts the fighter to the turn order, right behind whoever's turn it currently is. - self.db.fighters.insert(self.db.turn, character) - # Tick the turn counter forward one to compensate. - self.db.turn += 1 - # Initialize the character like you do at the start. - self.initialize_for_combat(character)
- - -""" ----------------------------------------------------------------------------- -COMMANDS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class CmdFight(Command): - """ - Starts a fight with everyone in the same room as you. - - Usage: - fight - - When you start a fight, everyone in the room who is able to - fight is added to combat, and a turn order is randomly rolled. - When it's your turn, you can attack other characters. - """ - - key = "fight" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.turnbattle.tb_magic.TBMagicTurnHandler")
- # Remember you'll have to change the path to the script if you copy this code to your own modules! - - -
[docs]class CmdAttack(Command): - """ - Attacks another character. - - Usage: - attack <target> - - When in a fight, you may attack another character. The attack has - a chance to hit, and if successful, will deal damage. - """ - - key = "attack" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender) - spend_action(self.caller, 1, action_name="attack") # Use up one action.
- - -
[docs]class CmdPass(Command): - """ - Passes on your turn. - - Usage: - pass - - When in a fight, you can use this command to end your turn early, even - if there are still any actions you can take. - """ - - key = "pass" - aliases = ["wait", "hold"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
- - -
[docs]class CmdDisengage(Command): - """ - Passes your turn and attempts to end combat. - - Usage: - disengage - - Ends your turn early and signals that you're trying to end - the fight. If all participants in a fight disengage, the - fight ends. - """ - - key = "disengage" - aliases = ["spare"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
- - -
[docs]class CmdLearnSpell(Command): - """ - Learn a magic spell. - - Usage: - learnspell <spell name> - - Adds a spell by name to your list of spells known. - - The following spells are provided as examples: - - |wmagic missile|n (3 MP): Fires three missiles that never miss. Can target - up to three different enemies. - - |wflame shot|n (3 MP): Shoots a high-damage jet of flame at one target. - - |wcure wounds|n (5 MP): Heals damage on one target. - - |wmass cure wounds|n (10 MP): Like 'cure wounds', but can heal up to 5 - targets at once. - - |wfull heal|n (12 MP): Heals one target back to full HP. - - |wcactus conjuration|n (2 MP): Creates a cactus. - """ - - key = "learnspell" - help_category = "magic" - -
[docs] def func(self): - """ - This performs the actual command. - """ - spell_list = sorted(SPELLS.keys()) - args = self.args.lower() - args = args.strip(" ") - caller = self.caller - spell_to_learn = [] - - if not args or len(args) < 3: # No spell given - caller.msg("Usage: learnspell <spell name>") - return - - for spell in spell_list: # Match inputs to spells - if args in spell.lower(): - spell_to_learn.append(spell) - - if spell_to_learn == []: # No spells matched - caller.msg("There is no spell with that name.") - return - if len(spell_to_learn) > 1: # More than one match - matched_spells = ", ".join(spell_to_learn) - caller.msg("Which spell do you mean: %s?" % matched_spells) - return - - if len(spell_to_learn) == 1: # If one match, extract the string - spell_to_learn = spell_to_learn[0] - - if spell_to_learn not in self.caller.db.spells_known: # If the spell isn't known... - caller.db.spells_known.append(spell_to_learn) # ...then add the spell to the character - caller.msg("You learn the spell '%s'!" % spell_to_learn) - return - if spell_to_learn in self.caller.db.spells_known: # Already has the spell specified - caller.msg("You already know the spell '%s'!" % spell_to_learn) - """ - You will almost definitely want to replace this with your own system - for learning spells, perhaps tied to character advancement or finding - items in the game world that spells can be learned from. - """
- - -
[docs]class CmdCast(MuxCommand): - """ - Cast a magic spell that you know, provided you have the MP - to spend on its casting. - - Usage: - cast <spellname> [= <target1>, <target2>, etc...] - - Some spells can be cast on multiple targets, some can be cast - on only yourself, and some don't need a target specified at all. - Typing 'cast' by itself will give you a list of spells you know. - """ - - key = "cast" - help_category = "magic" - -
[docs] def func(self): - """ - This performs the actual command. - - Note: This is a quite long command, since it has to cope with all - the different circumstances in which you may or may not be able - to cast a spell. None of the spell's effects are handled by the - command - all the command does is verify that the player's input - is valid for the spell being cast and then call the spell's - function. - """ - caller = self.caller - - if not self.lhs or len(self.lhs) < 3: # No spell name given - caller.msg("Usage: cast <spell name> = <target>, <target2>, ...") - if not caller.db.spells_known: - caller.msg("You don't know any spells.") - return - else: - caller.db.spells_known = sorted(caller.db.spells_known) - spells_known_msg = "You know the following spells:|/" + "|/".join( - caller.db.spells_known - ) - caller.msg(spells_known_msg) # List the spells the player knows - return - - spellname = self.lhs.lower() - spell_to_cast = [] - spell_targets = [] - - if not self.rhs: - spell_targets = [] - elif self.rhs.lower() in ["me", "self", "myself"]: - spell_targets = [caller] - elif len(self.rhs) > 2: - spell_targets = self.rhslist - - for spell in caller.db.spells_known: # Match inputs to spells - if self.lhs in spell.lower(): - spell_to_cast.append(spell) - - if spell_to_cast == []: # No spells matched - caller.msg("You don't know a spell of that name.") - return - if len(spell_to_cast) > 1: # More than one match - matched_spells = ", ".join(spell_to_cast) - caller.msg("Which spell do you mean: %s?" % matched_spells) - return - - if len(spell_to_cast) == 1: # If one match, extract the string - spell_to_cast = spell_to_cast[0] - - if spell_to_cast not in SPELLS: # Spell isn't defined - caller.msg("ERROR: Spell %s is undefined" % spell_to_cast) - return - - # Time to extract some info from the chosen spell! - spelldata = SPELLS[spell_to_cast] - - # Add in some default data if optional parameters aren't specified - if "combat_spell" not in spelldata: - spelldata.update({"combat_spell": True}) - if "noncombat_spell" not in spelldata: - spelldata.update({"noncombat_spell": True}) - if "max_targets" not in spelldata: - spelldata.update({"max_targets": 1}) - - # Store any superfluous options as kwargs to pass to the spell function - kwargs = {} - spelldata_opts = [ - "spellfunc", - "target", - "cost", - "combat_spell", - "noncombat_spell", - "max_targets", - ] - for key in spelldata: - if key not in spelldata_opts: - kwargs.update({key: spelldata[key]}) - - # If caster doesn't have enough MP to cover the spell's cost, give error and return - if spelldata["cost"] > caller.db.mp: - caller.msg("You don't have enough MP to cast '%s'." % spell_to_cast) - return - - # If in combat and the spell isn't a combat spell, give error message and return - if spelldata["combat_spell"] == False and is_in_combat(caller): - caller.msg("You can't use the spell '%s' in combat." % spell_to_cast) - return - - # If not in combat and the spell isn't a non-combat spell, error ms and return. - if spelldata["noncombat_spell"] == False and is_in_combat(caller) == False: - caller.msg("You can't use the spell '%s' outside of combat." % spell_to_cast) - return - - # If spell takes no targets and one is given, give error message and return - if len(spell_targets) > 0 and spelldata["target"] == "none": - caller.msg("The spell '%s' isn't cast on a target." % spell_to_cast) - return - - # If no target is given and spell requires a target, give error message - if spelldata["target"] not in ["self", "none"]: - if len(spell_targets) == 0: - caller.msg("The spell '%s' requires a target." % spell_to_cast) - return - - # If more targets given than maximum, give error message - if len(spell_targets) > spelldata["max_targets"]: - targplural = "target" - if spelldata["max_targets"] > 1: - targplural = "targets" - caller.msg( - "The spell '%s' can only be cast on %i %s." - % (spell_to_cast, spelldata["max_targets"], targplural) - ) - return - - # Set up our candidates for targets - target_candidates = [] - - # If spell targets 'any' or 'other', any object in caster's inventory or location - # can be targeted by the spell. - if spelldata["target"] in ["any", "other"]: - target_candidates = caller.location.contents + caller.contents - - # If spell targets 'anyobj', only non-character objects can be targeted. - if spelldata["target"] == "anyobj": - prefilter_candidates = caller.location.contents + caller.contents - for thing in prefilter_candidates: - if not thing.attributes.has("max_hp"): # Has no max HP, isn't a fighter - target_candidates.append(thing) - - # If spell targets 'anychar' or 'otherchar', only characters can be targeted. - if spelldata["target"] in ["anychar", "otherchar"]: - prefilter_candidates = caller.location.contents - for thing in prefilter_candidates: - if thing.attributes.has("max_hp"): # Has max HP, is a fighter - target_candidates.append(thing) - - # Now, match each entry in spell_targets to an object in the search candidates - matched_targets = [] - for target in spell_targets: - match = caller.search(target, candidates=target_candidates) - matched_targets.append(match) - spell_targets = matched_targets - - # If no target is given and the spell's target is 'self', set target to self - if len(spell_targets) == 0 and spelldata["target"] == "self": - spell_targets = [caller] - - # Give error message if trying to cast an "other" target spell on yourself - if spelldata["target"] in ["other", "otherchar"]: - if caller in spell_targets: - caller.msg("You can't cast '%s' on yourself." % spell_to_cast) - return - - # Return if "None" in target list, indicating failed match - if None in spell_targets: - # No need to give an error message, as 'search' gives one by default. - return - - # Give error message if repeats in target list - if len(spell_targets) != len(set(spell_targets)): - caller.msg("You can't specify the same target more than once!") - return - - # Finally, we can cast the spell itself. Note that MP is not deducted here! - try: - spelldata["spellfunc"]( - caller, spell_to_cast, spell_targets, spelldata["cost"], **kwargs - ) - except Exception: - log_trace("Error in callback for spell: %s." % spell_to_cast)
- - -
[docs]class CmdRest(Command): - """ - Recovers damage and restores MP. - - Usage: - rest - - Resting recovers your HP and MP to their maximum, but you can - only rest if you're not in a fight. - """ - - key = "rest" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - - if is_in_combat(self.caller): # If you're in combat - self.caller.msg("You can't rest while you're in combat.") - return - - self.caller.db.hp = self.caller.db.max_hp # Set current HP to maximum - self.caller.db.mp = self.caller.db.max_mp # Set current MP to maximum - self.caller.location.msg_contents("%s rests to recover HP and MP." % self.caller)
- # You'll probably want to replace this with your own system for recovering HP and MP. - - -
[docs]class CmdStatus(Command): - """ - Gives combat information. - - Usage: - status - - Shows your current and maximum HP and your distance from - other targets in combat. - """ - - key = "status" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - char = self.caller - - if not char.db.max_hp: # Character not initialized, IE in unit tests - char.db.hp = 100 - char.db.max_hp = 100 - char.db.spells_known = [] - char.db.max_mp = 20 - char.db.mp = char.db.max_mp - - char.msg( - "You have %i / %i HP and %i / %i MP." - % (char.db.hp, char.db.max_hp, char.db.mp, char.db.max_mp) - )
- - -
[docs]class CmdCombatHelp(CmdHelp): - """ - View help or a list of topics - - Usage: - help <topic or command> - help list - help all - - This will search for help on commands and other - topics related to the game. - """ - - # Just like the default help command, but will give quick - # tips on combat when used in a fight with no arguments. - -
[docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack a target, attempting to deal damage.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - ) - else: - super(CmdCombatHelp, self).func() # Call the default help command
- - -
[docs]class BattleCmdSet(default_cmds.CharacterCmdSet): - """ - This command set includes all the commmands used in the battle system. - """ - - key = "DefaultCharacter" - -
[docs] def at_cmdset_creation(self): - """ - Populates the cmdset - """ - self.add(CmdFight()) - self.add(CmdAttack()) - self.add(CmdRest()) - self.add(CmdPass()) - self.add(CmdDisengage()) - self.add(CmdCombatHelp()) - self.add(CmdLearnSpell()) - self.add(CmdCast()) - self.add(CmdStatus())
- - -""" ----------------------------------------------------------------------------- -SPELL FUNCTIONS START HERE ----------------------------------------------------------------------------- - -These are the functions that are called by the 'cast' command to perform the -effects of various spells. Which spells execute which functions and what -parameters are passed to them are specified at the bottom of the module, in -the 'SPELLS' dictionary. - -All of these functions take the same arguments: - caster (obj): Character casting the spell - spell_name (str): Name of the spell being cast - targets (list): List of objects targeted by the spell - cost (int): MP cost of casting the spell - -These functions also all accept **kwargs, and how these are used is specified -in the docstring for each function. -""" - - -
[docs]def spell_healing(caster, spell_name, targets, cost, **kwargs): - """ - Spell that restores HP to a target or targets. - - kwargs: - healing_range (tuple): Minimum and maximum amount healed to - each target. (20, 40) by default. - """ - spell_msg = "%s casts %s!" % (caster, spell_name) - - min_healing = 20 - max_healing = 40 - - # Retrieve healing range from kwargs, if present - if "healing_range" in kwargs: - min_healing = kwargs["healing_range"][0] - max_healing = kwargs["healing_range"][1] - - for character in targets: - to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp - if character.db.hp + to_heal > character.db.max_hp: - to_heal = character.db.max_hp - character.db.hp # Cap healing to max HP - character.db.hp += to_heal - spell_msg += " %s regains %i HP!" % (character, to_heal) - - caster.db.mp -= cost # Deduct MP cost - - caster.location.msg_contents(spell_msg) # Message the room with spell results - - if is_in_combat(caster): # Spend action if in combat - spend_action(caster, 1, action_name="cast")
- - -
[docs]def spell_attack(caster, spell_name, targets, cost, **kwargs): - """ - Spell that deals damage in combat. Similar to resolve_attack. - - kwargs: - attack_name (tuple): Single and plural describing the sort of - attack or projectile that strikes each enemy. - damage_range (tuple): Minimum and maximum damage dealt by the - spell. (10, 20) by default. - accuracy (int): Modifier to the spell's attack roll, determining - an increased or decreased chance to hit. 0 by default. - attack_count (int): How many individual attacks are made as part - of the spell. If the number of attacks exceeds the number of - targets, the first target specified will be attacked more - than once. Just 1 by default - if the attack_count is less - than the number targets given, each target will only be - attacked once. - """ - spell_msg = "%s casts %s!" % (caster, spell_name) - - atkname_single = "The spell" - atkname_plural = "spells" - min_damage = 10 - max_damage = 20 - accuracy = 0 - attack_count = 1 - - # Retrieve some variables from kwargs, if present - if "attack_name" in kwargs: - atkname_single = kwargs["attack_name"][0] - atkname_plural = kwargs["attack_name"][1] - if "damage_range" in kwargs: - min_damage = kwargs["damage_range"][0] - max_damage = kwargs["damage_range"][1] - if "accuracy" in kwargs: - accuracy = kwargs["accuracy"] - if "attack_count" in kwargs: - attack_count = kwargs["attack_count"] - - to_attack = [] - # If there are more attacks than targets given, attack first target multiple times - if len(targets) < attack_count: - to_attack = to_attack + targets - extra_attacks = attack_count - len(targets) - for n in range(extra_attacks): - to_attack.insert(0, targets[0]) - else: - to_attack = to_attack + targets - - # Set up dictionaries to track number of hits and total damage - total_hits = {} - total_damage = {} - for fighter in targets: - total_hits.update({fighter: 0}) - total_damage.update({fighter: 0}) - - # Resolve attack for each target - for fighter in to_attack: - attack_value = randint(1, 100) + accuracy # Spell attack roll - defense_value = get_defense(caster, fighter) - if attack_value >= defense_value: - spell_dmg = randint(min_damage, max_damage) # Get spell damage - total_hits[fighter] += 1 - total_damage[fighter] += spell_dmg - - for fighter in targets: - # Construct combat message - if total_hits[fighter] == 0: - spell_msg += " The spell misses %s!" % fighter - elif total_hits[fighter] > 0: - attack_count_str = atkname_single + " hits" - if total_hits[fighter] > 1: - attack_count_str = "%i %s hit" % (total_hits[fighter], atkname_plural) - spell_msg += " %s %s for %i damage!" % ( - attack_count_str, - fighter, - total_damage[fighter], - ) - - caster.db.mp -= cost # Deduct MP cost - - caster.location.msg_contents(spell_msg) # Message the room with spell results - - for fighter in targets: - # Apply damage - apply_damage(fighter, total_damage[fighter]) - # If fighter HP is reduced to 0 or less, call at_defeat. - if fighter.db.hp <= 0: - at_defeat(fighter) - - if is_in_combat(caster): # Spend action if in combat - spend_action(caster, 1, action_name="cast")
- - -
[docs]def spell_conjure(caster, spell_name, targets, cost, **kwargs): - """ - Spell that creates an object. - - kwargs: - obj_key (str): Key of the created object. - obj_desc (str): Desc of the created object. - obj_typeclass (str): Typeclass path of the object. - - If you want to make more use of this particular spell funciton, - you may want to modify it to use the spawner (in evennia.utils.spawner) - instead of creating objects directly. - """ - - obj_key = "a nondescript object" - obj_desc = "A perfectly generic object." - obj_typeclass = "evennia.objects.objects.DefaultObject" - - # Retrieve some variables from kwargs, if present - if "obj_key" in kwargs: - obj_key = kwargs["obj_key"] - if "obj_desc" in kwargs: - obj_desc = kwargs["obj_desc"] - if "obj_typeclass" in kwargs: - obj_typeclass = kwargs["obj_typeclass"] - - conjured_obj = create_object( - obj_typeclass, key=obj_key, location=caster.location - ) # Create object - conjured_obj.db.desc = obj_desc # Add object desc - - caster.db.mp -= cost # Deduct MP cost - - # Message the room to announce the creation of the object - caster.location.msg_contents( - "%s casts %s, and %s appears!" % (caster, spell_name, conjured_obj) - )
- - -""" ----------------------------------------------------------------------------- -SPELL DEFINITIONS START HERE ----------------------------------------------------------------------------- -In this section, each spell is matched to a function, and given parameters -that determine its MP cost, valid type and number of targets, and what -function casting the spell executes. - -This data is given as a dictionary of dictionaries - the key of each entry -is the spell's name, and the value is a dictionary of various options and -parameters, some of which are required and others which are optional. - -Required values for spells: - - cost (int): MP cost of casting the spell - target (str): Valid targets for the spell. Can be any of: - "none" - No target needed - "self" - Self only - "any" - Any object - "anyobj" - Any object that isn't a character - "anychar" - Any character - "other" - Any object excluding the caster - "otherchar" - Any character excluding the caster - spellfunc (callable): Function that performs the action of the spell. - Must take the following arguments: caster (obj), spell_name (str), - targets (list), and cost (int), as well as **kwargs. - -Optional values for spells: - - combat_spell (bool): If the spell can be cast in combat. True by default. - noncombat_spell (bool): If the spell can be cast out of combat. True by default. - max_targets (int): Maximum number of objects that can be targeted by the spell. - 1 by default - unused if target is "none" or "self" - -Any other values specified besides the above will be passed as kwargs to 'spellfunc'. -You can use kwargs to effectively re-use the same function for different but similar -spells - for example, 'magic missile' and 'flame shot' use the same function, but -behave differently, as they have different damage ranges, accuracy, amount of attacks -made as part of the spell, and so forth. If you make your spell functions flexible -enough, you can make a wide variety of spells just by adding more entries to this -dictionary. -""" - -SPELLS = { - "magic missile": { - "spellfunc": spell_attack, - "target": "otherchar", - "cost": 3, - "noncombat_spell": False, - "max_targets": 3, - "attack_name": ("A bolt", "bolts"), - "damage_range": (4, 7), - "accuracy": 999, - "attack_count": 3, - }, - "flame shot": { - "spellfunc": spell_attack, - "target": "otherchar", - "cost": 3, - "noncombat_spell": False, - "attack_name": ("A jet of flame", "jets of flame"), - "damage_range": (25, 35), - }, - "cure wounds": {"spellfunc": spell_healing, "target": "anychar", "cost": 5}, - "mass cure wounds": { - "spellfunc": spell_healing, - "target": "anychar", - "cost": 10, - "max_targets": 5, - }, - "full heal": { - "spellfunc": spell_healing, - "target": "anychar", - "cost": 12, - "healing_range": (100, 100), - }, - "cactus conjuration": { - "spellfunc": spell_conjure, - "target": "none", - "cost": 2, - "combat_spell": False, - "obj_key": "a cactus", - "obj_desc": "An ordinary green cactus with little spines.", - }, -} -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_range.html b/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_range.html deleted file mode 100644 index 9c78bebaac..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/turnbattle/tb_range.html +++ /dev/null @@ -1,1542 +0,0 @@ - - - - - - - - evennia.contrib.turnbattle.tb_range — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.turnbattle.tb_range

-"""
-Simple turn-based combat system with range and movement
-
-Contrib - Tim Ashley Jenkins 2017
-
-This is a version of the 'turnbattle' contrib that includes a system
-for abstract movement and positioning in combat, including distinction
-between melee and ranged attacks. In this system, a fighter or object's
-exact position is not recorded - only their relative distance to other
-actors in combat.
-
-In this example, the distance between two objects in combat is expressed
-as an integer value: 0 for "engaged" objects that are right next to each
-other, 1 for "reach" which is for objects that are near each other but
-not directly adjacent, and 2 for "range" for objects that are far apart.
-
-When combat starts, all fighters are at reach with each other and other
-objects, and at range from any exits. On a fighter's turn, they can use
-the "approach" command to move closer to an object, or the "withdraw"
-command to move further away from an object, either of which takes an
-action in combat. In this example, fighters are given two actions per
-turn, allowing them to move and attack in the same round, or to attack
-twice or move twice.
-
-When you move toward an object, you will also move toward anything else
-that's close to your target - the same goes for moving away from a target,
-which will also move you away from anything close to your target. Moving
-toward one target may also move you away from anything you're already
-close to, but withdrawing from a target will never inadvertently bring
-you closer to anything else.
-
-In this example, there are two attack commands. 'Attack' can only hit
-targets that are 'engaged' (range 0) with you. 'Shoot' can hit any target
-on the field, but cannot be used if you are engaged with any other fighters.
-In addition, strikes made with the 'attack' command are more accurate than
-'shoot' attacks. This is only to provide an example of how melee and ranged
-attacks can be made to work differently - you can, of course, modify this
-to fit your rules system.
-
-When in combat, the ranges of objects are also accounted for - you can't
-pick up an object unless you're engaged with it, and can't give an object
-to another fighter without being engaged with them either. Dropped objects
-are automatically assigned a range of 'engaged' with the fighter who dropped
-them. Additionally, giving or getting an object will take an action in combat.
-Dropping an object does not take an action, but can only be done on your turn.
-
-When combat ends, all range values are erased and all restrictions on getting
-or getting objects are lifted - distances are no longer tracked and objects in
-the same room can be considered to be in the same space, as is the default
-behavior of Evennia and most MUDs.
-
-This system allows for strategies in combat involving movement and
-positioning to be implemented in your battle system without the use of
-a 'grid' of coordinates, which can be difficult and clunky to navigate
-in text and disadvantageous to players who use screen readers. This loose,
-narrative method of tracking position is based around how the matter is
-handled in tabletop RPGs played without a grid - typically, a character's
-exact position in a room isn't important, only their relative distance to
-other actors.
-
-You may wish to expand this system with a method of distinguishing allies
-from enemies (to prevent allied characters from blocking your ranged attacks)
-as well as some method by which melee-focused characters can prevent enemies
-from withdrawing or punish them from doing so, such as by granting "attacks of
-opportunity" or something similar. If you wish, you can also expand the breadth
-of values allowed for range - rather than just 0, 1, and 2, you can allow ranges
-to go up to much higher values, and give attacks and movements more varying
-values for distance for a more granular system. You may also want to implement
-a system for fleeing or changing rooms in combat by approaching exits, which
-are objects placed in the range field like any other.
-
-To install and test, import this module's TBRangeCharacter object into
-your game's character.py module:
-
-    from evennia.contrib.turnbattle.tb_range import TBRangeCharacter
-
-And change your game's character typeclass to inherit from TBRangeCharacter
-instead of the default:
-
-    class Character(TBRangeCharacter):
-
-Do the same thing in your game's objects.py module for TBRangeObject:
-
-    from evennia.contrib.turnbattle.tb_range import TBRangeObject
-    class Object(TBRangeObject):
-
-Next, import this module into your default_cmdsets.py module:
-
-    from evennia.contrib.turnbattle import tb_range
-
-And add the battle command set to your default command set:
-
-    #
-    # any commands you add below will overload the default ones.
-    #
-    self.add(tb_range.BattleCmdSet())
-
-This module is meant to be heavily expanded on, so you may want to copy it
-to your game's 'world' folder and modify it there rather than importing it
-in your game and using it as-is.
-"""
-
-from random import randint
-from evennia import DefaultCharacter, DefaultObject, Command, default_cmds, DefaultScript
-from evennia.commands.default.help import CmdHelp
-
-"""
-----------------------------------------------------------------------------
-OPTIONS
-----------------------------------------------------------------------------
-"""
-
-TURN_TIMEOUT = 30  # Time before turns automatically end, in seconds
-ACTIONS_PER_TURN = 2  # Number of actions allowed per turn
-
-"""
-----------------------------------------------------------------------------
-COMBAT FUNCTIONS START HERE
-----------------------------------------------------------------------------
-"""
-
-
-
[docs]def roll_init(character): - """ - Rolls a number between 1-1000 to determine initiative. - - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. - """ - return randint(1, 1000)
- - -
[docs]def get_attack(attacker, defender, attack_type): - """ - Returns a value for an attack roll. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - attack_type (str): Type of attack ('melee' or 'ranged') - - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. - - Notes: - By default, generates a random integer from 1 to 100 without using any - properties from either the attacker or defender, and modifies the result - based on whether it's for a melee or ranged attack. - - This can easily be expanded to return a value based on characters stats, - equipment, and abilities. This is why the attacker and defender are passed - to this function, even though nothing from either one are used in this example. - """ - # For this example, just return a random integer up to 100. - attack_value = randint(1, 100) - # Make melee attacks more accurate, ranged attacks less accurate - if attack_type == "melee": - attack_value += 15 - if attack_type == "ranged": - attack_value -= 15 - return attack_value
- - -
[docs]def get_defense(attacker, defender, attack_type): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - attack_type (str): Type of attack ('melee' or 'ranged') - - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. - - Notes: - By default, returns 50, not taking any properties of the defender or - attacker into account. - - As above, this can be expanded upon based on character stats and equipment. - """ - # For this example, just return 50, for about a 50/50 chance of hit. - defense_value = 50 - return defense_value
- - -
[docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - By default, returns a random integer from 15 to 25 without using any - properties from either the attacker or defender. - - Again, this can be expanded upon. - """ - # For this example, just generate a number between 15 and 25. - damage_value = randint(15, 25) - return damage_value
- - -
[docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. - - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
- - -
[docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
- - -
[docs]def resolve_attack(attacker, defender, attack_type, attack_value=None, defense_value=None): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - attack_type (str): Type of attack (melee or ranged) - - Notes: - Even though the attack and defense values are calculated - extremely simply, they are separated out into their own functions - so that they are easier to expand upon. - """ - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender, attack_type) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender, attack_type) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents( - "%s's %s attack misses %s!" % (attacker, attack_type, defender) - ) - else: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - attacker.location.msg_contents( - "%s hits %s with a %s attack for %i damage!" - % (attacker, defender, attack_type, damage_value) - ) - apply_damage(defender, damage_value) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
- - -
[docs]def get_range(obj1, obj2): - """ - Gets the combat range between two objects. - - Args: - obj1 (obj): First object - obj2 (obj): Second object - - Returns: - range (int or None): Distance between two objects or None if not applicable - """ - # Return None if not applicable. - if not obj1.db.combat_range: - return None - if not obj2.db.combat_range: - return None - if obj1 not in obj2.db.combat_range: - return None - if obj2 not in obj1.db.combat_range: - return None - # Return the range between the two objects. - return obj1.db.combat_range[obj2]
- - -
[docs]def distance_inc(mover, target): - """ - Function that increases distance in range field between mover and target. - - Args: - mover (obj): The object moving - target (obj): The object to be moved away from - """ - mover.db.combat_range[target] += 1 - target.db.combat_range[mover] = mover.db.combat_range[target] - # Set a cap of 2: - if get_range(mover, target) > 2: - target.db.combat_range[mover] = 2 - mover.db.combat_range[target] = 2
- - -
[docs]def approach(mover, target): - """ - Manages a character's whole approach, including changes in ranges to other characters. - - Args: - mover (obj): The object moving - target (obj): The object to be moved toward - - Notes: - The mover will also automatically move toward any objects that are closer to the - target than the mover is. The mover will also move away from anything they started - out close to. - """ - - def distance_dec(mover, target): - """ - Helper function that decreases distance in range field between mover and target. - - Args: - mover (obj): The object moving - target (obj): The object to be moved toward - """ - mover.db.combat_range[target] -= 1 - target.db.combat_range[mover] = mover.db.combat_range[target] - # If this brings mover to range 0 (Engaged): - if get_range(mover, target) <= 0: - # Reset range to each other to 0 and copy target's ranges to mover. - target.db.combat_range[mover] = 0 - mover.db.combat_range = target.db.combat_range - # Assure everything else has the same distance from the mover and target, now that they're together - for thing in mover.location.contents: - if thing != mover and thing != target: - thing.db.combat_range[mover] = thing.db.combat_range[target] - - contents = mover.location.contents - - for thing in contents: - if thing != mover and thing != target: - # Move closer to each object closer to the target than you. - if get_range(mover, thing) > get_range(target, thing): - distance_dec(mover, thing) - # Move further from each object that's further from you than from the target. - if get_range(mover, thing) < get_range(target, thing): - distance_inc(mover, thing) - # Lastly, move closer to your target. - distance_dec(mover, target)
- - -
[docs]def withdraw(mover, target): - """ - Manages a character's whole withdrawal, including changes in ranges to other characters. - - Args: - mover (obj): The object moving - target (obj): The object to be moved away from - - Notes: - The mover will also automatically move away from objects that are close to the target - of their withdrawl. The mover will never inadvertently move toward anything else while - withdrawing - they can be considered to be moving to open space. - """ - - contents = mover.location.contents - - for thing in contents: - if thing != mover and thing != target: - # Move away from each object closer to the target than you, if it's also closer to you than you are to the target. - if get_range(mover, thing) >= get_range(target, thing) and get_range( - mover, thing - ) < get_range(mover, target): - distance_inc(mover, thing) - # Move away from anything your target is engaged with - if get_range(target, thing) == 0: - distance_inc(mover, thing) - # Move away from anything you're engaged with. - if get_range(mover, thing) == 0: - distance_inc(mover, thing) - # Then, move away from your target. - distance_inc(mover, target)
- - -
[docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
- - -
[docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
- - -
[docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
- - -
[docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
- - -
[docs]def combat_status_message(fighter): - """ - Sends a message to a player with their current HP and - distances to other fighters and objects. Called at turn - start and by the 'status' command. - """ - if not fighter.db.max_hp: - fighter.db.hp = 100 - fighter.db.max_hp = 100 - - status_msg = "HP Remaining: %i / %i" % (fighter.db.hp, fighter.db.max_hp) - - if not is_in_combat(fighter): - fighter.msg(status_msg) - return - - engaged_obj = [] - reach_obj = [] - range_obj = [] - - for thing in fighter.db.combat_range: - if thing != fighter: - if fighter.db.combat_range[thing] == 0: - engaged_obj.append(thing) - if fighter.db.combat_range[thing] == 1: - reach_obj.append(thing) - if fighter.db.combat_range[thing] > 1: - range_obj.append(thing) - - if engaged_obj: - status_msg += "|/Engaged targets: %s" % ", ".join(obj.key for obj in engaged_obj) - if reach_obj: - status_msg += "|/Reach targets: %s" % ", ".join(obj.key for obj in reach_obj) - if range_obj: - status_msg += "|/Ranged targets: %s" % ", ".join(obj.key for obj in range_obj) - - fighter.msg(status_msg)
- - -""" ----------------------------------------------------------------------------- -SCRIPTS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class TBRangeTurnHandler(DefaultScript): - """ - This is the script that handles the progression of combat through turns. - On creation (when a fight is started) it adds all combat-ready characters - to its roster and then sorts them into a turn order. There can only be one - fight going on in a single room at a time, so the script is assigned to a - room as its object. - - Fights persist until only one participant is left with any HP or all - remaining participants choose to end the combat with the 'disengage' - command. - """ - -
[docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Initialize range field for all objects in the room - for thing in self.obj.contents: - self.init_range(thing) - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
- -
[docs] def at_stop(self): - """ - Called at script termination. - """ - for thing in self.obj.contents: - combat_cleanup(thing) # Clean up the combat attributes for every object in the room. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
- -
[docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
- -
[docs] def init_range(self, to_init): - """ - Initializes range values for an object at the start of a fight. - - Args: - to_init (object): Object to initialize range field for. - """ - rangedict = {} - # Get a list of objects in the room. - objectlist = self.obj.contents - for thing in objectlist: - # Object always at distance 0 from itself - if thing == to_init: - rangedict.update({thing: 0}) - else: - if thing.destination or to_init.destination: - # Start exits at range 2 to put them at the 'edges' - rangedict.update({thing: 2}) - else: - # Start objects at range 1 from other objects - rangedict.update({thing: 1}) - to_init.db.combat_range = rangedict
- -
[docs] def join_rangefield(self, to_init, anchor_obj=None, add_distance=0): - """ - Adds a new object to the range field of a fight in progress. - - Args: - to_init (object): Object to initialize range field for. - - Keyword Args: - anchor_obj (object): Object to copy range values from, or None for a random object. - add_distance (int): Distance to put between to_init object and anchor object. - - """ - # Get a list of room's contents without to_init object. - contents = self.obj.contents - contents.remove(to_init) - # If no anchor object given, pick one in the room at random. - if not anchor_obj: - anchor_obj = contents[randint(0, (len(contents) - 1))] - # Copy the range values from the anchor object. - to_init.db.combat_range = anchor_obj.db.combat_range - # Add the new object to everyone else's ranges. - for thing in contents: - new_objects_range = thing.db.combat_range[anchor_obj] - thing.db.combat_range.update({to_init: new_objects_range}) - # Set the new object's range to itself to 0. - to_init.db.combat_range.update({to_init: 0}) - # Add additional distance from anchor object, if any. - for n in range(add_distance): - withdraw(to_init, anchor_obj)
- -
[docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. - - Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
- -
[docs] def start_turn(self, character): - """ - Readies a character for the start of their turn by replenishing their - available actions and notifying them that their turn has come up. - - Args: - character (obj): Character to be readied. - - Notes: - In this example, characters are given two actions per turn. This allows - characters to both move and attack in the same turn (or, alternately, - move twice or attack twice). - """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Prompt the character for their turn and give some information. - character.msg("|wIt's your turn!|n") - combat_status_message(character)
- -
[docs] def next_turn(self): - """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - newchar = self.db.fighters[self.db.turn] # Note the new character - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn.
- -
[docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. - - Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
- -
[docs] def join_fight(self, character): - """ - Adds a new character to a fight already in progress. - - Args: - character (obj): Character to be added to the fight. - """ - # Inserts the fighter to the turn order, right behind whoever's turn it currently is. - self.db.fighters.insert(self.db.turn, character) - # Tick the turn counter forward one to compensate. - self.db.turn += 1 - # Initialize the character like you do at the start. - self.initialize_for_combat(character) - # Add the character to the rangefield, at range from everyone, if they're not on it already. - if not character.db.combat_range: - self.join_rangefield(character, add_distance=2)
- - -""" ----------------------------------------------------------------------------- -TYPECLASSES START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class TBRangeCharacter(DefaultCharacter): - """ - A character able to participate in turn-based combat. Has attributes for current - and maximum HP, and access to combat commands. - """ - -
[docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - """ - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - """ - Adds attributes for a character's current and maximum HP. - We're just going to set this value at '100' by default. - - You may want to expand this to include various 'stats' that - can be changed at creation and factor into combat calculations. - """
- -
[docs] def at_before_move(self, destination): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
- - -
[docs]class TBRangeObject(DefaultObject): - """ - An object that is assigned range values in combat. Getting, giving, and dropping - the object has restrictions in combat - you must be next to an object to get it, - must be next to your target to give them something, and can only interact with - objects on your own turn. - """ - -
[docs] def at_before_drop(self, dropper): - """ - Called by the default `drop` command before this object has been - dropped. - - Args: - dropper (Object): The object which will drop this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - shoulddrop (bool): If the object should be dropped or not. - - Notes: - If this method returns False/None, the dropping is cancelled - before it is even started. - - """ - # Can't drop something if in combat and it's not your turn - if is_in_combat(dropper) and not is_turn(dropper): - dropper.msg("You can only drop things on your turn!") - return False - return True
- -
[docs] def at_drop(self, dropper): - """ - Called by the default `drop` command when this object has been - dropped. - - Args: - dropper (Object): The object which just dropped this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - This hook cannot stop the drop from happening. Use - permissions or the at_before_drop() hook for that. - - """ - # If dropper is currently in combat - if dropper.location.db.combat_turnhandler: - # Object joins the range field - self.db.combat_range = {} - dropper.location.db.combat_turnhandler.join_rangefield(self, anchor_obj=dropper)
- -
[docs] def at_before_get(self, getter): - """ - Called by the default `get` command before this object has been - picked up. - - Args: - getter (Object): The object about to get this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - shouldget (bool): If the object should be gotten or not. - - Notes: - If this method returns False/None, the getting is cancelled - before it is even started. - """ - # Restrictions for getting in combat - if is_in_combat(getter): - if not is_turn(getter): # Not your turn - getter.msg("You can only get things on your turn!") - return False - if get_range(self, getter) > 0: # Too far away - getter.msg("You aren't close enough to get that! (see: help approach)") - return False - return True
- -
[docs] def at_get(self, getter): - """ - Called by the default `get` command when this object has been - picked up. - - Args: - getter (Object): The object getting this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - This hook cannot stop the pickup from happening. Use - permissions or the at_before_get() hook for that. - - """ - # If gotten, erase range values - if self.db.combat_range: - del self.db.combat_range - # Remove this object from everyone's range fields - for thing in getter.location.contents: - if thing.db.combat_range: - if self in thing.db.combat_range: - thing.db.combat_range.pop(self, None) - # If in combat, getter spends an action - if is_in_combat(getter): - spend_action(getter, 1, action_name="get") # Use up one action.
- -
[docs] def at_before_give(self, giver, getter): - """ - Called by the default `give` command before this object has been - given. - - Args: - giver (Object): The object about to give this object. - getter (Object): The object about to get this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - shouldgive (bool): If the object should be given or not. - - Notes: - If this method returns False/None, the giving is cancelled - before it is even started. - - """ - # Restrictions for giving in combat - if is_in_combat(giver): - if not is_turn(giver): # Not your turn - giver.msg("You can only give things on your turn!") - return False - if get_range(giver, getter) > 0: # Too far away from target - giver.msg( - "You aren't close enough to give things to %s! (see: help approach)" % getter - ) - return False - return True
- -
[docs] def at_give(self, giver, getter): - """ - Called by the default `give` command when this object has been - given. - - Args: - giver (Object): The object giving this object. - getter (Object): The object getting this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - This hook cannot stop the give from happening. Use - permissions or the at_before_give() hook for that. - - """ - # Spend an action if in combat - if is_in_combat(giver): - spend_action(giver, 1, action_name="give") # Use up one action.
- - -""" ----------------------------------------------------------------------------- -COMMANDS START HERE ----------------------------------------------------------------------------- -""" - - -
[docs]class CmdFight(Command): - """ - Starts a fight with everyone in the same room as you. - - Usage: - fight - - When you start a fight, everyone in the room who is able to - fight is added to combat, and a turn order is randomly rolled. - When it's your turn, you can attack other characters. - """ - - key = "fight" - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.turnbattle.tb_range.TBRangeTurnHandler")
- # Remember you'll have to change the path to the script if you copy this code to your own modules! - - -
[docs]class CmdAttack(Command): - """ - Attacks another character in melee. - - Usage: - attack <target> - - When in a fight, you may attack another character. The attack has - a chance to hit, and if successful, will deal damage. You can only - attack engaged targets - that is, targets that are right next to - you. Use the 'approach' command to get closer to a target. - """ - - key = "attack" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - if not get_range(attacker, defender) == 0: # Target isn't in melee - self.caller.msg( - "%s is too far away to attack - you need to get closer! (see: help approach)" - % defender - ) - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender, "melee") - spend_action(self.caller, 1, action_name="attack") # Use up one action.
- - -
[docs]class CmdShoot(Command): - """ - Attacks another character from range. - - Usage: - shoot <target> - - When in a fight, you may shoot another character. The attack has - a chance to hit, and if successful, will deal damage. You can attack - any target in combat by shooting, but can't shoot if there are any - targets engaged with you. Use the 'withdraw' command to retreat from - nearby enemies. - """ - - key = "shoot" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - # Test to see if there are any nearby enemy targets. - in_melee = [] - for target in attacker.db.combat_range: - # Object is engaged and has HP - if get_range(attacker, defender) == 0 and target.db.hp and target != self.caller: - in_melee.append(target) # Add to list of targets in melee - - if len(in_melee) > 0: - self.caller.msg( - "You can't shoot because there are fighters engaged with you (%s) - you need to retreat! (see: help withdraw)" - % ", ".join(obj.key for obj in in_melee) - ) - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender, "ranged") - spend_action(self.caller, 1, action_name="attack") # Use up one action.
- - -
[docs]class CmdApproach(Command): - """ - Approaches an object. - - Usage: - approach <target> - - Move one space toward a character or object. You can only attack - characters you are 0 spaces away from. - """ - - key = "approach" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - - if not is_in_combat(self.caller): # If not in combat, can't approach. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't approach. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't approach if you have no HP. - self.caller.msg("You can't move, you've been defeated.") - return - - mover = self.caller - target = self.caller.search(self.args) - - if not target: # No valid target given. - return - - if not target.db.combat_range: # Target object is not on the range field - self.caller.msg("You can't move toward that!") - return - - if mover == target: # Target and mover are the same - self.caller.msg("You can't move toward yourself!") - return - - if get_range(mover, target) <= 0: # Already engaged with target - self.caller.msg("You're already next to that target!") - return - - # If everything checks out, call the approach resolving function. - approach(mover, target) - mover.location.msg_contents("%s moves toward %s." % (mover, target)) - spend_action(self.caller, 1, action_name="move") # Use up one action.
- - -
[docs]class CmdWithdraw(Command): - """ - Moves away from an object. - - Usage: - withdraw <target> - - Move one space away from a character or object. - """ - - key = "withdraw" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - - if not is_in_combat(self.caller): # If not in combat, can't withdraw. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't withdraw. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't withdraw if you have no HP. - self.caller.msg("You can't move, you've been defeated.") - return - - mover = self.caller - target = self.caller.search(self.args) - - if not target: # No valid target given. - return - - if not target.db.combat_range: # Target object is not on the range field - self.caller.msg("You can't move away from that!") - return - - if mover == target: # Target and mover are the same - self.caller.msg("You can't move away from yourself!") - return - - if mover.db.combat_range[target] >= 3: # Already at maximum distance - self.caller.msg("You're as far as you can get from that target!") - return - - # If everything checks out, call the approach resolving function. - withdraw(mover, target) - mover.location.msg_contents("%s moves away from %s." % (mover, target)) - spend_action(self.caller, 1, action_name="move") # Use up one action.
- - -
[docs]class CmdPass(Command): - """ - Passes on your turn. - - Usage: - pass - - When in a fight, you can use this command to end your turn early, even - if there are still any actions you can take. - """ - - key = "pass" - aliases = ["wait", "hold"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
- - -
[docs]class CmdDisengage(Command): - """ - Passes your turn and attempts to end combat. - - Usage: - disengage - - Ends your turn early and signals that you're trying to end - the fight. If all participants in a fight disengage, the - fight ends. - """ - - key = "disengage" - aliases = ["spare"] - help_category = "combat" - -
[docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
- - -
[docs]class CmdRest(Command): - """ - Recovers damage. - - Usage: - rest - - Resting recovers your HP to its maximum, but you can only - rest if you're not in a fight. - """ - - key = "rest" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - - if is_in_combat(self.caller): # If you're in combat - self.caller.msg("You can't rest while you're in combat.") - return - - self.caller.db.hp = self.caller.db.max_hp # Set current HP to maximum - self.caller.location.msg_contents("%s rests to recover HP." % self.caller) - """ - You'll probably want to replace this with your own system for recovering HP. - """
- - -
[docs]class CmdStatus(Command): - """ - Gives combat information. - - Usage: - status - - Shows your current and maximum HP and your distance from - other targets in combat. - """ - - key = "status" - help_category = "combat" - -
[docs] def func(self): - "This performs the actual command." - combat_status_message(self.caller)
- - -
[docs]class CmdCombatHelp(CmdHelp): - """ - View help or a list of topics - - Usage: - help <topic or command> - help list - help all - - This will search for help on commands and other - topics related to the game. - """ - - # Just like the default help command, but will give quick - # tips on combat when used in a fight with no arguments. - -
[docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack an engaged target, attempting to deal damage.|/" - + "|wShoot:|n Attack from a distance, if not engaged with other fighters.|/" - + "|wApproach:|n Move one step cloer to a target.|/" - + "|wWithdraw:|n Move one step away from a target.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wStatus:|n View current HP and ranges to other targets.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - ) - else: - super(CmdCombatHelp, self).func() # Call the default help command
- - -
[docs]class BattleCmdSet(default_cmds.CharacterCmdSet): - """ - This command set includes all the commmands used in the battle system. - """ - - key = "DefaultCharacter" - -
[docs] def at_cmdset_creation(self): - """ - Populates the cmdset - """ - self.add(CmdFight()) - self.add(CmdAttack()) - self.add(CmdShoot()) - self.add(CmdRest()) - self.add(CmdPass()) - self.add(CmdDisengage()) - self.add(CmdApproach()) - self.add(CmdWithdraw()) - self.add(CmdStatus()) - self.add(CmdCombatHelp())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/bodyfunctions.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/bodyfunctions.html deleted file mode 100644 index 030d7d34f3..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/bodyfunctions.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_examples.bodyfunctions — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_examples.bodyfunctions

-"""
-Example script for testing. This adds a simple timer that has your
-character make observations and notices at irregular intervals.
-
-To test, use
-  @script me = tutorial_examples.bodyfunctions.BodyFunctions
-
-The script will only send messages to the object it is stored on, so
-make sure to put it on yourself or you won't see any messages!
-
-"""
-import random
-from evennia import DefaultScript
-
-
-
[docs]class BodyFunctions(DefaultScript): - """ - This class defines the script itself - """ - -
[docs] def at_script_creation(self): - self.key = "bodyfunction" - self.desc = "Adds various timed events to a character." - self.interval = 20 # seconds - # self.repeats = 5 # repeat only a certain number of times - self.start_delay = True # wait self.interval until first call
- # self.persistent = True - -
[docs] def at_repeat(self): - """ - This gets called every self.interval seconds. We make - a random check here so as to only return 33% of the time. - """ - if random.random() < 0.66: - # no message this time - return - self.send_random_message()
- -
[docs] def send_random_message(self): - rand = random.random() - # return a random message - if rand < 0.1: - string = "You tap your foot, looking around." - elif rand < 0.2: - string = "You have an itch. Hard to reach too." - elif rand < 0.3: - string = ( - "You think you hear someone behind you. ... but when you look there's noone there." - ) - elif rand < 0.4: - string = "You inspect your fingernails. Nothing to report." - elif rand < 0.5: - string = "You cough discreetly into your hand." - elif rand < 0.6: - string = "You scratch your head, looking around." - elif rand < 0.7: - string = "You blink, forgetting what it was you were going to do." - elif rand < 0.8: - string = "You feel lonely all of a sudden." - elif rand < 0.9: - string = "You get a great idea. Of course you won't tell anyone." - else: - string = "You suddenly realize how much you love Evennia!" - - # echo the message to the object - self.obj.msg(string)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/cmdset_red_button.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/cmdset_red_button.html deleted file mode 100644 index 83191f700b..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/cmdset_red_button.html +++ /dev/null @@ -1,441 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_examples.cmdset_red_button — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_examples.cmdset_red_button

-"""
-This defines the cmdset for the red_button. Here we have defined
-the commands and the cmdset in the same module, but if you
-have many different commands to merge it is often better
-to define the cmdset separately, picking and choosing from
-among the available commands as to what should be included in the
-cmdset - this way you can often re-use the commands too.
-"""
-
-import random
-from evennia import Command, CmdSet
-
-# Some simple commands for the red button
-
-# ------------------------------------------------------------
-# Commands defined on the red button
-# ------------------------------------------------------------
-
-
-
[docs]class CmdNudge(Command): - """ - Try to nudge the button's lid - - Usage: - nudge lid - - This command will have you try to - push the lid of the button away. - """ - - key = "nudge lid" # two-word command name! - aliases = ["nudge"] - locks = "cmd:all()" - -
[docs] def func(self): - """ - nudge the lid. Random chance of success to open it. - """ - rand = random.random() - if rand < 0.5: - self.caller.msg("You nudge at the lid. It seems stuck.") - elif rand < 0.7: - self.caller.msg("You move the lid back and forth. It won't budge.") - else: - self.caller.msg("You manage to get a nail under the lid.") - self.caller.execute_cmd("open lid")
- - -
[docs]class CmdPush(Command): - """ - Push the red button - - Usage: - push button - - """ - - key = "push button" - aliases = ["push", "press button", "press"] - locks = "cmd:all()" - -
[docs] def func(self): - """ - Note that we choose to implement this with checking for - if the lid is open/closed. This is because this command - is likely to be tried regardless of the state of the lid. - - An alternative would be to make two versions of this command - and tuck them into the cmdset linked to the Open and Closed - lid-state respectively. - - """ - - if self.obj.db.lid_open: - string = "You reach out to press the big red button ..." - string += "\n\nA BOOM! A bright light blinds you!" - string += "\nThe world goes dark ..." - self.caller.msg(string) - self.caller.location.msg_contents( - "%s presses the button. BOOM! %s is blinded by a flash!" - % (self.caller.name, self.caller.name), - exclude=self.caller, - ) - # the button's method will handle all setup of scripts etc. - self.obj.press_button(self.caller) - else: - string = "You cannot push the button - there is a glass lid covering it." - self.caller.msg(string)
- - -
[docs]class CmdSmashGlass(Command): - """ - smash glass - - Usage: - smash glass - - Try to smash the glass of the button. - """ - - key = "smash glass" - aliases = ["smash lid", "break lid", "smash"] - locks = "cmd:all()" - -
[docs] def func(self): - """ - The lid won't open, but there is a small chance - of causing the lamp to break. - """ - rand = random.random() - - if rand < 0.2: - string = "You smash your hand against the glass" - string += " with all your might. The lid won't budge" - string += " but you cause quite the tremor through the button's mount." - string += "\nIt looks like the button's lamp stopped working for the time being." - self.obj.lamp_works = False - elif rand < 0.6: - string = "You hit the lid hard. It doesn't move an inch." - else: - string = "You place a well-aimed fist against the glass of the lid." - string += " Unfortunately all you get is a pain in your hand. Maybe" - string += " you should just try to open the lid instead?" - self.caller.msg(string) - self.caller.location.msg_contents( - "%s tries to smash the glass of the button." % (self.caller.name), exclude=self.caller - )
- - -
[docs]class CmdOpenLid(Command): - """ - open lid - - Usage: - open lid - - """ - - key = "open lid" - aliases = ["open button", "open"] - locks = "cmd:all()" - -
[docs] def func(self): - "simply call the right function." - - if self.obj.db.lid_locked: - self.caller.msg("This lid seems locked in place for the moment.") - return - - string = "\nA ticking sound is heard, like a winding mechanism. Seems " - string += "the lid will soon close again." - self.caller.msg(string) - self.caller.location.msg_contents( - "%s opens the lid of the button." % (self.caller.name), exclude=self.caller - ) - # add the relevant cmdsets to button - self.obj.cmdset.add(LidClosedCmdSet) - # call object method - self.obj.open_lid()
- - -
[docs]class CmdCloseLid(Command): - """ - close the lid - - Usage: - close lid - - Closes the lid of the red button. - """ - - key = "close lid" - aliases = ["close"] - locks = "cmd:all()" - -
[docs] def func(self): - "Close the lid" - - self.obj.close_lid() - - # this will clean out scripts dependent on lid being open. - self.caller.msg("You close the button's lid. It clicks back into place.") - self.caller.location.msg_contents( - "%s closes the button's lid." % (self.caller.name), exclude=self.caller - )
- - -
[docs]class CmdBlindLook(Command): - """ - Looking around in darkness - - Usage: - look <obj> - - ... not that there's much to see in the dark. - - """ - - key = "look" - aliases = ["l", "get", "examine", "ex", "feel", "listen"] - locks = "cmd:all()" - -
[docs] def func(self): - "This replaces all the senses when blinded." - - # we decide what to reply based on which command was - # actually tried - - if self.cmdstring == "get": - string = "You fumble around blindly without finding anything." - elif self.cmdstring == "examine": - string = "You try to examine your surroundings, but can't see a thing." - elif self.cmdstring == "listen": - string = "You are deafened by the boom." - elif self.cmdstring == "feel": - string = "You fumble around, hands outstretched. You bump your knee." - else: - # trying to look - string = "You are temporarily blinded by the flash. " - string += "Until it wears off, all you can do is feel around blindly." - self.caller.msg(string) - self.caller.location.msg_contents( - "%s stumbles around, blinded." % (self.caller.name), exclude=self.caller - )
- - -
[docs]class CmdBlindHelp(Command): - """ - Help function while in the blinded state - - Usage: - help - - """ - - key = "help" - aliases = "h" - locks = "cmd:all()" - -
[docs] def func(self): - "Give a message." - self.caller.msg("You are beyond help ... until you can see again.")
- - -# --------------------------------------------------------------- -# Command sets for the red button -# --------------------------------------------------------------- - -# We next tuck these commands into their respective command sets. -# (note that we are overdoing the cdmset separation a bit here -# to show how it works). - - -
[docs]class DefaultCmdSet(CmdSet): - """ - The default cmdset always sits - on the button object and whereas other - command sets may be added/merge onto it - and hide it, removing them will always - bring it back. It's added to the object - using obj.cmdset.add_default(). - """ - - key = "RedButtonDefault" - mergetype = "Union" # this is default, we don't really need to put it here. - -
[docs] def at_cmdset_creation(self): - "Init the cmdset" - self.add(CmdPush())
- - -
[docs]class LidClosedCmdSet(CmdSet): - """ - A simple cmdset tied to the redbutton object. - - It contains the commands that launches the other - command sets, making the red button a self-contained - item (i.e. you don't have to manually add any - scripts etc to it when creating it). - """ - - key = "LidClosedCmdSet" - # default Union is used *except* if we are adding to a - # cmdset named LidOpenCmdSet - this one we replace - # completely. - key_mergetype = {"LidOpenCmdSet": "Replace"} - -
[docs] def at_cmdset_creation(self): - "Populates the cmdset when it is instantiated." - self.add(CmdNudge()) - self.add(CmdSmashGlass()) - self.add(CmdOpenLid())
- - -
[docs]class LidOpenCmdSet(CmdSet): - """ - This is the opposite of the Closed cmdset. - """ - - key = "LidOpenCmdSet" - # default Union is used *except* if we are adding to a - # cmdset named LidClosedCmdSet - this one we replace - # completely. - key_mergetype = {"LidClosedCmdSet": "Replace"} - -
[docs] def at_cmdset_creation(self): - "setup the cmdset (just one command)" - self.add(CmdCloseLid())
- - -
[docs]class BlindCmdSet(CmdSet): - """ - This is the cmdset added to the *account* when - the button is pushed. - """ - - key = "BlindCmdSet" - # we want it to completely replace all normal commands - # until the timed script removes it again. - mergetype = "Replace" - # we want to stop the account from walking around - # in this blinded state, so we hide all exits too. - # (channel commands will still work). - no_exits = True # keep account in the same room - no_objs = True # don't allow object commands - -
[docs] def at_cmdset_creation(self): - "Setup the blind cmdset" - from evennia.commands.default.general import CmdSay - from evennia.commands.default.general import CmdPose - - self.add(CmdSay()) - self.add(CmdPose()) - self.add(CmdBlindLook()) - self.add(CmdBlindHelp())
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/red_button.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/red_button.html deleted file mode 100644 index 6f7c658d08..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/red_button.html +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_examples.red_button — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_examples.red_button

-"""
-
-This is a more advanced example object. It combines functions from
-script.examples as well as commands.examples to make an interactive
-button typeclass.
-
-Create this button with
-
- @create/drop examples.red_button.RedButton
-
-Note that you must drop the button before you can see its messages!
-"""
-import random
-from evennia import DefaultObject
-from evennia.contrib.tutorial_examples import red_button_scripts as scriptexamples
-from evennia.contrib.tutorial_examples import cmdset_red_button as cmdsetexamples
-
-#
-# Definition of the object itself
-#
-
-
-
[docs]class RedButton(DefaultObject): - """ - This class describes an evil red button. It will use the script - definition in contrib/examples/red_button_scripts to blink at regular - intervals. It also uses a series of script and commands to handle - pushing the button and causing effects when doing so. - - The following attributes can be set on the button: - desc_lid_open - description when lid is open - desc_lid_closed - description when lid is closed - desc_lamp_broken - description when lamp is broken - - """ - -
[docs] def at_object_creation(self): - """ - This function is called when object is created. Use this - instead of e.g. __init__. - """ - # store desc (default, you can change this at creation time) - desc = "This is a large red button, inviting yet evil-looking. " - desc += "A closed glass lid protects it." - self.db.desc = desc - - # We have to define all the variables the scripts - # are checking/using *before* adding the scripts or - # they might be deactivated before even starting! - self.db.lid_open = False - self.db.lamp_works = True - self.db.lid_locked = False - - self.cmdset.add_default(cmdsetexamples.DefaultCmdSet, permanent=True) - - # since the cmdsets relevant to the button are added 'on the fly', - # we need to setup custom scripts to do this for us (also, these scripts - # check so they are valid (i.e. the lid is actually still closed)). - # The AddClosedCmdSet script makes sure to add the Closed-cmdset. - self.scripts.add(scriptexamples.ClosedLidState) - # the script EventBlinkButton makes the button blink regularly. - self.scripts.add(scriptexamples.BlinkButtonEvent)
- - # state-changing methods - -
[docs] def open_lid(self): - """ - Opens the glass lid and start the timer so it will soon close - again. - - """ - - if self.db.lid_open: - return - desc = self.db.desc_lid_open - if not desc: - desc = "This is a large red button, inviting yet evil-looking. " - desc += "Its glass cover is open and the button exposed." - self.db.desc = desc - self.db.lid_open = True - - # with the lid open, we validate scripts; this will clean out - # scripts that depend on the lid to be closed. - self.scripts.validate() - # now add new scripts that define the open-lid state - self.scripts.add(scriptexamples.OpenLidState) - # we also add a scripted event that will close the lid after a while. - # (this one cleans itself after being called once) - self.scripts.add(scriptexamples.CloseLidEvent)
- -
[docs] def close_lid(self): - """ - Close the glass lid. This validates all scripts on the button, - which means that scripts only being valid when the lid is open - will go away automatically. - - """ - - if not self.db.lid_open: - return - desc = self.db.desc_lid_closed - if not desc: - desc = "This is a large red button, inviting yet evil-looking. " - desc += "Its glass cover is closed, protecting it." - self.db.desc = desc - self.db.lid_open = False - - # clean out scripts depending on lid to be open - self.scripts.validate() - # add scripts related to the closed state - self.scripts.add(scriptexamples.ClosedLidState)
- -
[docs] def break_lamp(self, feedback=True): - """ - Breaks the lamp in the button, stopping it from blinking. - - Args: - feedback (bool): Show a message about breaking the lamp. - - """ - self.db.lamp_works = False - desc = self.db.desc_lamp_broken - if not desc: - self.db.desc += "\nThe big red button has stopped blinking for the time being." - else: - self.db.desc = desc - - if feedback and self.location: - self.location.msg_contents("The lamp flickers, the button going dark.") - self.scripts.validate()
- -
[docs] def press_button(self, pobject): - """ - Someone was foolish enough to press the button! - - Args: - pobject (Object): The person pressing the button - - """ - # deactivate the button so it won't flash/close lid etc. - self.scripts.add(scriptexamples.DeactivateButtonEvent) - # blind the person pressing the button. Note that this - # script is set on the *character* pressing the button! - pobject.scripts.add(scriptexamples.BlindedState)
- - # script-related methods - -
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/red_button_scripts.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/red_button_scripts.html deleted file mode 100644 index 4075c3d2b4..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/red_button_scripts.html +++ /dev/null @@ -1,391 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_examples.red_button_scripts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_examples.red_button_scripts

-"""
-Example of scripts.
-
-These are scripts intended for a particular object - the
-red_button object type in contrib/examples. A few variations
-on uses of scripts are included.
-
-"""
-from evennia import DefaultScript
-from evennia.contrib.tutorial_examples import cmdset_red_button as cmdsetexamples
-
-#
-# Scripts as state-managers
-#
-# Scripts have many uses, one of which is to statically
-# make changes when a particular state of an object changes.
-# There is no "timer" involved in this case (although there could be),
-# whenever the script determines it is "invalid", it simply shuts down
-# along with all the things it controls.
-#
-# To show as many features as possible of the script and cmdset systems,
-# we will use three scripts controlling one state each of the red_button,
-# each with its own set of commands, handled by cmdsets - one for when
-# the button has its lid open, and one for when it is closed and a
-# last one for when the player pushed the button and gets blinded by
-# a bright light. The last one also has a timer component that allows it
-# to remove itself after a while (and the player recovers their eyesight).
-
-
-
[docs]class ClosedLidState(DefaultScript): - """ - This manages the cmdset for the "closed" button state. What this - means is that while this script is valid, we add the RedButtonClosed - cmdset to it (with commands like open, nudge lid etc) - """ - -
[docs] def at_script_creation(self): - "Called when script first created." - self.key = "closed_lid_script" - self.desc = "Script that manages the closed-state cmdsets for red button." - self.persistent = True
- -
[docs] def at_start(self): - """ - This is called once every server restart, so we want to add the - (memory-resident) cmdset to the object here. is_valid is automatically - checked so we don't need to worry about adding the script to an - open lid. - """ - # All we do is add the cmdset for the closed state. - self.obj.cmdset.add(cmdsetexamples.LidClosedCmdSet)
- -
[docs] def is_valid(self): - """ - The script is only valid while the lid is closed. - self.obj is the red_button on which this script is defined. - """ - return not self.obj.db.lid_open
- -
[docs] def at_stop(self): - """ - When the script stops we must make sure to clean up after us. - - """ - self.obj.cmdset.delete(cmdsetexamples.LidClosedCmdSet)
- - -
[docs]class OpenLidState(DefaultScript): - """ - This manages the cmdset for the "open" button state. This will add - the RedButtonOpen - """ - -
[docs] def at_script_creation(self): - "Called when script first created." - self.key = "open_lid_script" - self.desc = "Script that manages the opened-state cmdsets for red button." - self.persistent = True
- -
[docs] def at_start(self): - """ - This is called once every server restart, so we want to add the - (memory-resident) cmdset to the object here. is_valid is - automatically checked, so we don't need to worry about - adding the cmdset to a closed lid-button. - """ - self.obj.cmdset.add(cmdsetexamples.LidOpenCmdSet)
- -
[docs] def is_valid(self): - """ - The script is only valid while the lid is open. - self.obj is the red_button on which this script is defined. - """ - return self.obj.db.lid_open
- -
[docs] def at_stop(self): - """ - When the script stops (like if the lid is closed again) - we must make sure to clean up after us. - """ - self.obj.cmdset.delete(cmdsetexamples.LidOpenCmdSet)
- - -
[docs]class BlindedState(DefaultScript): - """ - This is a timed state. - - This adds a (very limited) cmdset TO THE ACCOUNT, during a certain time, - after which the script will close and all functions are - restored. It's up to the function starting the script to actually - set it on the right account object. - """ - -
[docs] def at_script_creation(self): - """ - We set up the script here. - """ - self.key = "temporary_blinder" - self.desc = "Temporarily blinds the account for a little while." - self.interval = 20 # seconds - self.start_delay = True # we don't want it to stop until after 20s. - self.repeats = 1 # this will go away after interval seconds. - self.persistent = False # we will ditch this if server goes down
- -
[docs] def at_start(self): - """ - We want to add the cmdset to the linked object. - - Note that the RedButtonBlind cmdset is defined to completly - replace the other cmdsets on the stack while it is active - (this means that while blinded, only operations in this cmdset - will be possible for the account to perform). It is however - not persistent, so should there be a bug in it, we just need - to restart the server to clear out of it during development. - """ - self.obj.cmdset.add(cmdsetexamples.BlindCmdSet)
- -
[docs] def at_stop(self): - """ - It's important that we clear out that blinded cmdset - when we are done! - """ - self.obj.msg("You blink feverishly as your eyesight slowly returns.") - self.obj.location.msg_contents( - "%s seems to be recovering their eyesight." % self.obj.name, exclude=self.obj - ) - self.obj.cmdset.delete() # this will clear the latest added cmdset,
- # (which is the blinded one). - - -# -# Timer/Event-like Scripts -# -# Scripts can also work like timers, or "events". Below we -# define three such timed events that makes the button a little -# more "alive" - one that makes the button blink menacingly, another -# that makes the lid covering the button slide back after a while. -# - - -
[docs]class CloseLidEvent(DefaultScript): - """ - This event closes the glass lid over the button - some time after it was opened. It's a one-off - script that should be started/created when the - lid is opened. - """ - -
[docs] def at_script_creation(self): - """ - Called when script object is first created. Sets things up. - We want to have a lid on the button that the user can pull - aside in order to make the button 'pressable'. But after a set - time that lid should auto-close again, making the button safe - from pressing (and deleting this command). - """ - self.key = "lid_closer" - self.desc = "Closes lid on a red buttons" - self.interval = 20 # seconds - self.start_delay = True # we want to pospone the launch. - self.repeats = 1 # we only close the lid once - self.persistent = True # even if the server crashes in those 20 seconds,
- # the lid will still close once the game restarts. - -
[docs] def is_valid(self): - """ - This script can only operate if the lid is open; if it - is already closed, the script is clearly invalid. - - Note that we are here relying on an self.obj being - defined (and being a RedButton object) - this we should be able to - expect since this type of script is always tied to one individual - red button object and not having it would be an error. - """ - return self.obj.db.lid_open
- -
[docs] def at_repeat(self): - """ - Called after self.interval seconds. It closes the lid. Before this method is - called, self.is_valid() is automatically checked, so there is no need to - check this manually. - """ - self.obj.close_lid()
- - -
[docs]class BlinkButtonEvent(DefaultScript): - """ - This timed script lets the button flash at regular intervals. - """ - -
[docs] def at_script_creation(self): - """ - Sets things up. We want the button's lamp to blink at - regular intervals, unless it's broken (can happen - if you try to smash the glass, say). - """ - self.key = "blink_button" - self.desc = "Blinks red buttons" - self.interval = 35 # seconds - self.start_delay = False # blink right away - self.persistent = True # keep blinking also after server reboot
- -
[docs] def is_valid(self): - """ - Button will keep blinking unless it is broken. - """ - return self.obj.db.lamp_works
- -
[docs] def at_repeat(self): - """ - Called every self.interval seconds. Makes the lamp in - the button blink. - """ - self.obj.blink()
- - -
[docs]class DeactivateButtonEvent(DefaultScript): - """ - This deactivates the button for a short while (it won't blink, won't - close its lid etc). It is meant to be called when the button is pushed - and run as long as the blinded effect lasts. We cannot put these methods - in the AddBlindedCmdSet script since that script is defined on the *account* - whereas this one must be defined on the *button*. - """ - -
[docs] def at_script_creation(self): - """ - Sets things up. - """ - self.key = "deactivate_button" - self.desc = "Deactivate red button temporarily" - self.interval = 21 # seconds - self.start_delay = True # wait with the first repeat for self.interval seconds. - self.persistent = True - self.repeats = 1 # only do this once
- -
[docs] def at_start(self): - """ - Deactivate the button. Observe that this method is always - called directly, regardless of the value of self.start_delay - (that just controls when at_repeat() is called) - """ - # closing the lid will also add the ClosedState script - self.obj.close_lid() - # lock the lid so other accounts can't access it until the - # first one's effect has worn off. - self.obj.db.lid_locked = True - # breaking the lamp also sets a correct desc - self.obj.break_lamp(feedback=False)
- -
[docs] def at_repeat(self): - """ - When this is called, reset the functionality of the button. - """ - # restore button's desc. - - self.obj.db.lamp_works = True - desc = "This is a large red button, inviting yet evil-looking. " - desc += "Its glass cover is closed, protecting it." - self.db.desc = desc - # re-activate the blink button event. - self.obj.scripts.add(BlinkButtonEvent) - # unlock the lid - self.obj.db.lid_locked = False - self.obj.scripts.validate()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/tests.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/tests.html deleted file mode 100644 index bfcefd0730..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_examples/tests.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_examples.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_examples.tests

-from mock import Mock, patch
-
-from evennia.utils.test_resources import EvenniaTest
-
-from .bodyfunctions import BodyFunctions
-
-
-
[docs]@patch("evennia.contrib.tutorial_examples.bodyfunctions.random") -class TestBodyFunctions(EvenniaTest): - script_typeclass = BodyFunctions - -
[docs] def setUp(self): - super(TestBodyFunctions, self).setUp() - self.script.obj = self.char1
- -
[docs] def tearDown(self): - super(TestBodyFunctions, self).tearDown() - # if we forget to stop the script, DirtyReactorAggregateError will be raised - self.script.stop()
- -
[docs] def test_at_repeat(self, mock_random): - """test that no message will be sent when below the 66% threshold""" - mock_random.random = Mock(return_value=0.5) - old_func = self.script.send_random_message - self.script.send_random_message = Mock() - self.script.at_repeat() - self.script.send_random_message.assert_not_called() - # test that random message will be sent - mock_random.random = Mock(return_value=0.7) - self.script.at_repeat() - self.script.send_random_message.assert_called() - self.script.send_random_message = old_func
- -
[docs] def test_send_random_message(self, mock_random): - """Test that correct message is sent for each random value""" - old_func = self.char1.msg - self.char1.msg = Mock() - # test each of the values - mock_random.random = Mock(return_value=0.05) - self.script.send_random_message() - self.char1.msg.assert_called_with("You tap your foot, looking around.") - mock_random.random = Mock(return_value=0.15) - self.script.send_random_message() - self.char1.msg.assert_called_with("You have an itch. Hard to reach too.") - mock_random.random = Mock(return_value=0.25) - self.script.send_random_message() - self.char1.msg.assert_called_with( - "You think you hear someone behind you. ... " "but when you look there's noone there." - ) - mock_random.random = Mock(return_value=0.35) - self.script.send_random_message() - self.char1.msg.assert_called_with("You inspect your fingernails. Nothing to report.") - mock_random.random = Mock(return_value=0.45) - self.script.send_random_message() - self.char1.msg.assert_called_with("You cough discreetly into your hand.") - mock_random.random = Mock(return_value=0.55) - self.script.send_random_message() - self.char1.msg.assert_called_with("You scratch your head, looking around.") - mock_random.random = Mock(return_value=0.65) - self.script.send_random_message() - self.char1.msg.assert_called_with("You blink, forgetting what it was you were going to do.") - mock_random.random = Mock(return_value=0.75) - self.script.send_random_message() - self.char1.msg.assert_called_with("You feel lonely all of a sudden.") - mock_random.random = Mock(return_value=0.85) - self.script.send_random_message() - self.char1.msg.assert_called_with("You get a great idea. Of course you won't tell anyone.") - mock_random.random = Mock(return_value=0.95) - self.script.send_random_message() - self.char1.msg.assert_called_with("You suddenly realize how much you love Evennia!") - self.char1.msg = old_func
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_world/intro_menu.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_world/intro_menu.html deleted file mode 100644 index 4ef78b258c..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_world/intro_menu.html +++ /dev/null @@ -1,887 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_world.intro_menu — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_world.intro_menu

-"""
-Intro menu / game tutor
-
-Evennia contrib - Griatch 2020
-
-This contrib is an intro-menu for general MUD and evennia usage using the
-EvMenu menu-templating system.
-
-EvMenu templating is a way to create a menu using a string-format instead
-of creating all nodes manually. Of course, for full functionality one must
-still create the goto-callbacks.
-
-"""
-
-from evennia import create_object
-from evennia import CmdSet
-from evennia.utils.evmenu import parse_menu_template, EvMenu
-
-# Goto callbacks and helper resources for the menu
-
-
-
[docs]def do_nothing(caller, raw_string, **kwargs): - """ - Re-runs the current node - """ - return None
- - -
[docs]def send_testing_tagged(caller, raw_string, **kwargs): - """ - Test to send a message to a pane tagged with 'testing' in the webclient. - - """ - caller.msg( - ( - "This is a message tagged with 'testing' and " - "should appear in the pane you selected!\n " - f"You wrote: '{raw_string}'", - {"type": "testing"}, - ) - ) - return None
- - -# Resources for the first help-command demo - - -
[docs]class DemoCommandSetHelp(CmdSet): - """ - Demo the help command - """ - - key = "Help Demo Set" - priority = 2 - -
[docs] def at_cmdset_creation(self): - from evennia import default_cmds - - self.add(default_cmds.CmdHelp())
- - -
[docs]def goto_command_demo_help(caller, raw_string, **kwargs): - "Sets things up before going to the help-demo node" - _maintain_demo_room(caller, delete=True) - caller.cmdset.remove(DemoCommandSetRoom) - caller.cmdset.remove(DemoCommandSetComms) - caller.cmdset.add(DemoCommandSetHelp) # TODO - make persistent - return kwargs.get("gotonode") or "command_demo_help"
- - -# Resources for the comms demo - - -
[docs]class DemoCommandSetComms(CmdSet): - """ - Demo communications - """ - - key = "Color Demo Set" - priority = 2 - no_exits = True - no_objs = True - -
[docs] def at_cmdset_creation(self): - from evennia import default_cmds - - self.add(default_cmds.CmdHelp()) - self.add(default_cmds.CmdSay()) - self.add(default_cmds.CmdPose()) - self.add(default_cmds.CmdPage()) - self.add(default_cmds.CmdColorTest())
- - -
[docs]def goto_command_demo_comms(caller, raw_string, **kwargs): - """ - Setup and go to the color demo node. - """ - caller.cmdset.remove(DemoCommandSetHelp) - caller.cmdset.remove(DemoCommandSetRoom) - caller.cmdset.add(DemoCommandSetComms) - return kwargs.get("gotonode") or "comms_demo_start"
- - -# Resources for the room demo - -_ROOM_DESC = """ -This is a small and comfortable wood cabin. Bright sunlight is shining in -through the windows. - -Use |ylook sign|n or |yl sign|n to examine the wooden sign nailed to the wall. - -""" - -_SIGN_DESC = """ -The small sign reads: - - Good! Now try '|ylook small|n'. - - ... You'll get a multi-match error! There are two things that 'small' could - refer to here - the 'small wooden sign' or the 'small, cozy cabin' itself. You will - get a list of the possibilities. - - You could either tell Evennia which one you wanted by picking a unique part - of their name (like '|ylook cozy|n') or use the number in the list to pick - the one you want, like this: - - |ylook 2-small|n - - As long as what you write is uniquely identifying you can be lazy and not - write the full name of the thing you want to look at. Try '|ylook bo|n', - '|yl co|n' or '|yl 1-sm|n'! - - ... Oh, and if you see database-ids like (#1245) by the name of objects, - it's because you are playing with Builder-privileges or higher. Regular - players will not see the numbers. - - Next try |ylook door|n. - -""" - -_DOOR_DESC_OUT = """ -This is a solid wooden door leading to the outside of the cabin. Some -text is written on it: - - This is an |wexit|n. An exit is often named by its compass-direction like - |weast|n, |wwest|n, |wnorthwest|n and so on, but it could be named - anything, like this door. To use the exit, you just write its name. So by - writing |ydoor|n you will leave the cabin. - -""" - -_DOOR_DESC_IN = """ -This is a solid wooden door leading to the inside of the cabin. On -are some carved text: - - This exit leads back into the cabin. An exit is just like any object, - so while has a name, it can also have aliases. To get back inside - you can both write |ydoor|n but also |yin|n. - -""" - -_MEADOW_DESC = """ -This is a lush meadow, just outside a cozy cabin. It's surrounded -by trees and sunlight filters down from a clear blue sky. - -There is a |wstone|n here. Try looking at it! - -""" - -_STONE_DESC = """ -This is a fist-sized stone covered in runes: - - To pick me up, use - - |yget stone|n - - You can see what you carry with the |yinventory|n (|yi|n). - - To drop me again, just write - - |ydrop stone|n - - Use |ynext|n when you are done exploring and want to - continue with the tutorial. - -""" - - -def _maintain_demo_room(caller, delete=False): - """ - Handle the creation/cleanup of demo assets. We store them - on the character and clean them when leaving the menu later. - """ - # this is a tuple (room, obj) - roomdata = caller.db.tutorial_world_demo_room_data - - if delete: - if roomdata: - # we delete directly for simplicity. We need to delete - # in specific order to avoid deleting rooms moves - # its contents to their default home-location - prev_loc, room1, sign, room2, stone, door_out, door_in = roomdata - caller.location = prev_loc - sign.delete() - stone.delete() - door_out.delete() - door_in.delete() - room1.delete() - room2.delete() - del caller.db.tutorial_world_demo_room_data - elif not roomdata: - # create and describe the cabin and box - room1 = create_object("evennia.objects.objects.DefaultRoom", key="A small, cozy cabin") - room1.db.desc = _ROOM_DESC.lstrip() - sign = create_object( - "evennia.objects.objects.DefaultObject", key="small wooden sign", location=room1 - ) - sign.db.desc = _SIGN_DESC.strip() - sign.locks.add("get:false()") - sign.db.get_err_msg = "The sign is nailed to the wall. It's not budging." - - # create and describe the meadow and stone - room2 = create_object("evennia.objects.objects.DefaultRoom", key="A lush summer meadow") - room2.db.desc = _MEADOW_DESC.lstrip() - stone = create_object( - "evennia.objects.objects.DefaultObject", key="carved stone", location=room2 - ) - stone.db.desc = _STONE_DESC.strip() - - # make the linking exits - door_out = create_object( - "evennia.objects.objects.DefaultExit", - key="Door", - location=room1, - destination=room2, - locks=["get:false()"], - ) - door_out.db.desc = _DOOR_DESC_OUT.strip() - door_in = create_object( - "evennia.objects.objects.DefaultExit", - key="entrance to the cabin", - aliases=["door", "in", "entrance"], - location=room2, - destination=room1, - locks=["get:false()"], - ) - door_in.db.desc = _DOOR_DESC_IN.strip() - - # store references for easy removal later - caller.db.tutorial_world_demo_room_data = ( - caller.location, - room1, - sign, - room2, - stone, - door_out, - door_in, - ) - # move caller into room - caller.location = room1 - - -
[docs]class DemoCommandSetRoom(CmdSet): - """ - Demo some general in-game commands command. - """ - - key = "Room Demo Set" - priority = 2 - no_exits = False - no_objs = False - -
[docs] def at_cmdset_creation(self): - from evennia import default_cmds - - self.add(default_cmds.CmdHelp()) - self.add(default_cmds.CmdLook()) - self.add(default_cmds.CmdGet()) - self.add(default_cmds.CmdDrop()) - self.add(default_cmds.CmdInventory()) - self.add(default_cmds.CmdExamine()) - self.add(default_cmds.CmdPy())
- - -
[docs]def goto_command_demo_room(caller, raw_string, **kwargs): - """ - Setup and go to the demo-room node. Generates a little 2-room environment - for testing out some commands. - """ - _maintain_demo_room(caller) - caller.cmdset.remove(DemoCommandSetHelp) - caller.cmdset.remove(DemoCommandSetComms) - caller.cmdset.add(DemoCommandSetRoom) - return "command_demo_room"
- - -
[docs]def goto_cleanup_cmdsets(caller, raw_strings, **kwargs): - """ - Cleanup all cmdsets. - """ - caller.cmdset.remove(DemoCommandSetHelp) - caller.cmdset.remove(DemoCommandSetComms) - caller.cmdset.remove(DemoCommandSetRoom) - return kwargs.get("gotonode")
- - -# register all callables that can be used in the menu template - -GOTO_CALLABLES = { - "send_testing_tagged": send_testing_tagged, - "do_nothing": do_nothing, - "goto_command_demo_help": goto_command_demo_help, - "goto_command_demo_comms": goto_command_demo_comms, - "goto_command_demo_room": goto_command_demo_room, - "goto_cleanup_cmdsets": goto_cleanup_cmdsets, -} - - -# Main menu definition - -MENU_TEMPLATE = """ - -## NODE start - -|g** Evennia introduction wizard **|n - -If you feel lost you can learn some of the basics of how to play a text-based -game here. You can also learn a little about the system and how to find more -help. You can exit this tutorial-wizard at any time by entering '|yq|n' or '|yquit|n'. - -Press |y<return>|n or write |ynext|n to step forward. Or select a number to jump to. - -## OPTIONS - - 1 (next);1;next;n: What is a MUD/MU*? -> about_muds - 2: About Evennia -> about_evennia - 3: Using the webclient -> using webclient - 4: The help command -> goto_command_demo_help() - 5: Communicating with others -> goto_command_demo_help(gotonode='talk on channels') - 6: Using colors -> goto_command_demo_comms(gotonode='testing_colors') - 7: Moving and exploring -> goto_command_demo_room() - 8: Conclusions & next steps-> conclusions - >: about_muds - -# --------------------------------------------------------------------------------- - -## NODE about_muds - -|g** About MUDs **|n - -The term '|wMUD|n' stands for Multi-user-Dungeon or -Dimension. A MUD is -primarily played by inserting text |wcommands|n and getting text back. - -MUDS were the |wprecursors|n to graphical MMORPG-style games like World of -Warcraft. While not as mainstream as they once were, comparing a text-game to a -graphical game is like comparing a book to a movie - it's just a different -experience altogether. - -MUDs are |wdifferent|n from Interactive Fiction (IF) in that they are multiplayer -and usually has a consistent game world with many stories and protagonists -acting at the same time. - -Like there are many different styles of graphical MMOs, there are |wmany -variations|n of MUDs: They can be slow-paced or fast. They can cover fantasy, -sci-fi, horror or other genres. They can allow PvP or not and be casual or -hardcore, strategic, tactical, turn-based or play in real-time. - -Whereas 'MUD' is arguably the most well-known term, there are other terms -centered around particular game engines - such as MUSH, MOO, MUX, MUCK, LPMuds, -ROMs, Diku and others. Many people that played MUDs in the past used one of -these existing families of text game-servers, whether they knew it or not. - -|cEvennia|n is a newer text game engine designed to emulate almost any existing -gaming style you like and possibly any new ones you can come up with! - -## OPTIONS - - next;n: About Evennia -> about_evennia - back to start;back;start;t: start - >: about_evennia - -# --------------------------------------------------------------------------------- - -## NODE about_evennia - -|g** About Evennia **|n - -|cEvennia|n is a Python game engine for creating multiplayer online text-games -(aka MUDs, MUSHes, MUX, MOOs...). It is open-source and |wfree to use|n, also for -commercial projects (BSD license). - -Out of the box, Evennia provides a |wworking, if empty game|n. Whereas you can play -via traditional telnet MUD-clients, the server runs your game's website and -offers a |wHTML5 webclient|n so that people can play your game in their browser -without downloading anything extra. - -Evennia deliberately |wdoes not|n hard-code any game-specific things like -combat-systems, races, skills, etc. They would not match what just you wanted -anyway! Whereas we do have optional contribs with many examples, most of our -users use them as inspiration to make their own thing. - -Evennia is developed entirely in |wPython|n, using modern developer practices. -The advantage of text is that even a solo developer or small team can -realistically make a competitive multiplayer game (as compared to a graphical -MMORPG which is one of the most expensive game types in existence to develop). -Many also use Evennia as a |wfun way to learn Python|n! - -## OPTIONS - - next;n: Using the webclient -> using webclient - back;b: About MUDs -> about_muds - >: using webclient - -# --------------------------------------------------------------------------------- - -## NODE using webclient - -|g** Using the Webclient **|n - -|RNote: This is only relevant if you use Evennia's HTML5 web client. If you use a -third-party (telnet) mud-client, you can skip this section.|n - -Evennia's web client is (for a local install) found by pointing your browser to - - |yhttp://localhost:4001/webclient|n - -For a live example, the public Evennia demo can be found at - - |yhttps://demo.evennia.com/webclient|n - -The web client starts out having two panes - the input-pane for entering commands -and the main window. - -- Use |y<Return>|n (or click the arrow on the right) to send your input. -- Use |yShift + <up/down-arrow>|n to step back and forth in your command-history. -- Use |yShift + <Return>|n to add a new line to your input without sending. - -There is also some |wextra|n info to learn about customizing the webclient. - -## OPTIONS - - extra: Customizing the webclient -> customizing the webclient - next;n: Playing the game -> goto_command_demo_help() - back;b: About Evennia -> about_evennia - back to start;start: start - >: goto_command_demo_help() - -# --------------------------------------------------------------------------------- - -# this is a dead-end 'leaf' of the menu - -## NODE customizing the webclient - -|g** Extra hints on customizing the Webclient **|n - -|y1)|n The panes of the webclient can be resized and you can create additional panes. - -- Press the little plus (|w+|n) sign in the top left and a new tab will appear. -- Click and drag the tab and pull it far to the right and release when it creates two - panes next to each other. - -|y2)|n You can have certain server output only appear in certain panes. - -- In your new rightmost pane, click the diamond (⯁) symbol at the top. -- Unselect everything and make sure to select "testing". -- Click the diamond again so the menu closes. -- Next, write "|ytest Hello world!|n". A test-text should appear in your rightmost pane! - -|y3)|n You can customize general webclient settings by pressing the cogwheel in the upper -left corner. It allows to change things like font and if the client should play sound. - -The "message routing" allows for rerouting text matching a certain regular expression (regex) -to a web client pane with a specific tag that you set yourself. - -|y4)|n Close the right-hand pane with the |wX|n in the rop right corner. - -## OPTIONS - - back;b: using webclient - > test *: send tagged message to new pane -> send_testing_tagged() - >: using webclient - -# --------------------------------------------------------------------------------- - -# we get here via goto_command_demo_help() - -## NODE command_demo_help - -|g** Playing the game **|n - -Evennia has about |w90 default commands|n. They include useful administration/building -commands and a few limited "in-game" commands to serve as examples. They are intended -to be changed, extended and modified as you please. - -First to try is |yhelp|n. This lists all commands |wcurrently|n available to you. - -Use |yhelp <topic>|n to get specific help. Try |yhelp help|n to get help on using -the help command. For your game you could add help about your game, lore, rules etc -as well. - -At the moment you only have |whelp|n and some |wChannel Names|n (the '<menu commands>' -is just a placeholder to indicate you are using this menu). - -We'll add more commands as we get to them in this tutorial - but we'll only -cover a small handful. Once you exit you'll find a lot more! Now let's try -those channels ... - -## OPTIONS - - next;n: Talk on Channels -> talk on channels - back;b: Using the webclient -> goto_cleanup_cmdsets(gotonode='using webclient') - back to start;start: start - >: talk on channels - -# --------------------------------------------------------------------------------- - -## NODE talk on channels - -|g** Talk on Channels **|n - -|wChannels|n are like in-game chatrooms. The |wChannel Names|n help-category -holds the names of the channels available to you right now. One such channel is -|wpublic|n. Use |yhelp public|n to see how to use it. Try it: - - |ypublic Hello World!|n - -This will send a message to the |wpublic|n channel where everyone on that -channel can see it. If someone else is on your server, you may get a reply! - -Evennia can link its in-game channels to external chat networks. This allows -you to talk with people not actually logged into the game. For -example, the online Evennia-demo links its |wpublic|n channel to the #evennia -IRC support channel. - -## OPTIONS - - next;n: Talk to people in-game -> goto_command_demo_comms() - back;b: Finding help -> goto_command_demo_help() - back to start;start: start - >: goto_command_demo_comms() - -# --------------------------------------------------------------------------------- - -# we get here via goto_command_demo_comms() - -## NODE comms_demo_start - -|g** Talk to people in-game **|n - -You can also chat with people inside the game. If you try |yhelp|n now you'll -find you have a few more commands available for trying this out. - - |ysay Hello there!|n - |y'Hello there!|n - -|wsay|n is used to talk to people in the same location you are. Everyone in the -room will see what you have to say. A single quote |y'|n is a convenient shortcut. - - |ypose smiles|n - |y:smiles|n - -|wpose|n (or |wemote|n) describes what you do to those nearby. This is a very simple -command by default, but it can be extended to much more complex parsing in order to -include other people/objects in the emote, reference things by a short-description etc. - -## OPTIONS - - next;n: Paging people -> paging_people - back;b: Talk on Channels -> goto_command_demo_help(gotonode='talk on channels') - back to start;start: start - >: paging_people - -# --------------------------------------------------------------------------------- - -## NODE paging_people - -|g** Paging people **|n - -Halfway between talking on a |wChannel|n and chatting in your current location -with |wsay|n and |wpose|n, you can also |wpage|n people. This is like a private -message only they can see. - - |ypage <name> = Hello there! - page <name1>, <name2> = Hello both of you!|n - -If you are alone on the server, put your own name as |w<name>|n to test it and -page yourself. Write just |ypage|n to see your latest pages. This will also show -you if anyone paged you while you were offline. - -(By the way - depending on which games you are used to, you may think that the -use of |y=|n above is strange. This is a MUSH/MUX-style of syntax. For your own -game you can change the |wpose|n command to work however you prefer). - -## OPTIONS - - next;n: Using colors -> testing_colors - back;b: Talk to people in-game -> comms_demo_start - back to start;start: start - >: testing_colors - -# --------------------------------------------------------------------------------- - -## NODE testing_colors - -|g** U|rs|yi|gn|wg |c|yc|wo|rl|bo|gr|cs |g**|n - -You can add color in your text by the help of tags. However, remember that not -everyone will see your colors - it depends on their client (and some use -screenreaders). Using color can also make text harder to read. So use it -sparingly. - -To start coloring something |rred|n, add a ||r (red) marker and then -end with ||n (to go back to neutral/no-color): - - |ysay This is a ||rred||n text! - say This is a ||Rdark red||n text!|n - -You can also change the background: - - |ysay This is a ||[x||bblue text on a light-grey background!|n - -There are 16 base colors and as many background colors (called ANSI colors). Some -clients also supports so-called Xterm256 which gives a total of 256 colors. These are -given as |w||rgb|n, where r, g, b are the components of red, green and blue from 0-5: - - |ysay This is ||050solid green!|n - |ysay This is ||520an orange color!|n - |ysay This is ||[005||555white on bright blue background!|n - -If you don't see the expected colors from the above examples, it's because your -client does not support it - try out the Evennia webclient instead. To see all -color codes printed, try - - |ycolor ansi - |ycolor xterm - -## OPTIONS - - next;n: Moving and Exploring -> goto_command_demo_room() - back;b: Paging people -> goto_command_demo_comms(gotonode='paging_people') - back to start;start: start - >: goto_command_demo_room() - -# --------------------------------------------------------------------------------- - -# we get here via goto_command_demo_room() - -## NODE command_demo_room - -|gMoving and Exploring|n - -For exploring the game, a very important command is '|ylook|n'. It's also -abbreviated '|yl|n' since it's used so much. Looking displays/redisplays your -current location. You can also use it to look closer at items in the world. So -far in this tutorial, using 'look' would just redisplay the menu. - -Try |ylook|n now. You have been quietly transported to a sunny cabin to look -around in. Explore a little and use |ynext|n when you are done. - -## OPTIONS - - next;n: Conclusions -> conclusions - back;b: Channel commands -> goto_command_demo_comms(gotonode='testing_colors') - back to start;start: start - >: conclusions - -# --------------------------------------------------------------------------------- - -## NODE conclusions - -|gConclusions|n - -That concludes this little quick-intro to using the base game commands of -Evennia. With this you should be able to continue exploring and also find help -if you get stuck! - -Write |ynext|n to end this wizard and continue to the tutorial-world quest! -If you want there is also some |wextra|n info for where to go beyond that. - -## OPTIONS - - extra: Where to go next -> post scriptum - next;next;n: End -> end - back;b: Moving and Exploring -> goto_command_demo_room() - back to start;start: start - >: end - -# --------------------------------------------------------------------------------- - -## NODE post scriptum - -|gWhere to next?|n - -After playing through the tutorial-world quest, if you aim to make a game with -Evennia you are wise to take a look at the |wEvennia documentation|n at - - |yhttps://www.evennia.com/docs/latest - -- You can start by trying to build some stuff by following the |wBuilder quick-start|n: - - |yhttps://www.evennia.com/docs/latest/Building-Quickstart|n - -- The tutorial-world may or may not be your cup of tea, but it does show off - several |wuseful tools|n of Evennia. You may want to check out how it works: - - |yhttps://www.evennia.com/docs/latest/Tutorial-World-Introduction|n - -- You can then continue looking through the |wTutorials|n and pick one that - fits your level of understanding. - - |yhttps://www.evennia.com/docs/latest/Tutorials|n - -- Make sure to |wjoin our forum|n and connect to our |wsupport chat|n! The - Evennia community is very active and friendly and no question is too simple. - You will often quickly get help. You can everything you need linked from - - |yhttps://www.evennia.com|n - -# --------------------------------------------------------------------------------- - -## OPTIONS - -back: conclusions ->: conclusions - - -## NODE end - -|gGood luck!|n - -""" - - -# ------------------------------------------------------------------------------------------- -# -# EvMenu implementation and access function -# -# ------------------------------------------------------------------------------------------- - - -
[docs]class TutorialEvMenu(EvMenu): - """ - Custom EvMenu for displaying the intro-menu - """ - -
[docs] def close_menu(self): - """Custom cleanup actions when closing menu""" - self.caller.cmdset.remove(DemoCommandSetHelp) - self.caller.cmdset.remove(DemoCommandSetRoom) - self.caller.cmdset.remove(DemoCommandSetComms) - _maintain_demo_room(self.caller, delete=True) - super().close_menu()
- -
[docs] def options_formatter(self, optionslist): - - navigation_keys = ("next", "back", "back to start") - - other = [] - navigation = [] - for key, desc in optionslist: - if key in navigation_keys: - desc = f" ({desc})" if desc else "" - navigation.append(f"|lc{key}|lt|w{key}|n|le{desc}") - else: - other.append((key, desc)) - navigation = ( - (" " + " |W|||n ".join(navigation) + " |W|||n " + "|wQ|Wuit|n") if navigation else "" - ) - other = super().options_formatter(other) - sep = "\n\n" if navigation and other else "" - - return f"{navigation}{sep}{other}"
- - -
[docs]def init_menu(caller): - """ - Call to initialize the menu. - - """ - menutree = parse_menu_template(caller, MENU_TEMPLATE, GOTO_CALLABLES) - TutorialEvMenu(caller, menutree)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_world/mob.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_world/mob.html deleted file mode 100644 index efaf70b828..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_world/mob.html +++ /dev/null @@ -1,547 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_world.mob — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_world.mob

-"""
-This module implements a simple mobile object with
-a very rudimentary AI as well as an aggressive enemy
-object based on that mobile class.
-
-"""
-
-import random
-
-from evennia import TICKER_HANDLER
-from evennia import search_object
-from evennia import Command, CmdSet
-from evennia import logger
-from evennia.contrib.tutorial_world import objects as tut_objects
-
-
-
[docs]class CmdMobOnOff(Command): - """ - Activates/deactivates Mob - - Usage: - mobon <mob> - moboff <mob> - - This turns the mob from active (alive) mode - to inactive (dead) mode. It is used during - building to activate the mob once it's - prepared. - """ - - key = "mobon" - aliases = "moboff" - locks = "cmd:superuser()" - -
[docs] def func(self): - """ - Uses the mob's set_alive/set_dead methods - to turn on/off the mob." - """ - if not self.args: - self.caller.msg("Usage: mobon||moboff <mob>") - return - mob = self.caller.search(self.args) - if not mob: - return - if self.cmdstring == "mobon": - mob.set_alive() - else: - mob.set_dead()
- - -
[docs]class MobCmdSet(CmdSet): - """ - Holds the admin command controlling the mob - """ - -
[docs] def at_cmdset_creation(self): - self.add(CmdMobOnOff())
- - -
[docs]class Mob(tut_objects.TutorialObject): - """ - This is a state-machine AI mobile. It has several states which are - controlled from setting various Attributes. All default to True: - - patrolling: if set, the mob will move randomly - from room to room, but preferring to not return - the way it came. If unset, the mob will remain - stationary (idling) until attacked. - aggressive: if set, will attack Characters in - the same room using whatever Weapon it - carries (see tutorial_world.objects.Weapon). - if unset, the mob will never engage in combat - no matter what. - hunting: if set, the mob will pursue enemies trying - to flee from it, so it can enter combat. If unset, - it will return to patrolling/idling if fled from. - immortal: If set, the mob cannot take any damage. - irregular_echoes: list of strings the mob generates at irregular intervals. - desc_alive: the physical description while alive - desc_dead: the physical descripion while dead - send_defeated_to: unique key/alias for location to send defeated enemies to - defeat_msg: message to echo to defeated opponent - defeat_msg_room: message to echo to room. Accepts %s as the name of the defeated. - hit_msg: message to echo when this mob is hit. Accepts %s for the mob's key. - weapon_ineffective_msg: message to echo for useless attacks - death_msg: message to echo to room when this mob dies. - patrolling_pace: how many seconds per tick, when patrolling - aggressive_pace: -"- attacking - hunting_pace: -"- hunting - death_pace: -"- returning to life when dead - - field 'home' - the home location should set to someplace inside - the patrolling area. The mob will use this if it should - happen to roam into a room with no exits. - - """ - -
[docs] def at_init(self): - """ - When initialized from cache (after a server reboot), set up - the AI state. - """ - # The AI state machine (not persistent). - self.ndb.is_patrolling = self.db.patrolling and not self.db.is_dead - self.ndb.is_attacking = False - self.ndb.is_hunting = False - self.ndb.is_immortal = self.db.immortal or self.db.is_dead
- -
[docs] def at_object_creation(self): - """ - Called the first time the object is created. - We set up the base properties and flags here. - """ - self.cmdset.add(MobCmdSet, permanent=True) - # Main AI flags. We start in dead mode so we don't have to - # chase the mob around when building. - self.db.patrolling = True - self.db.aggressive = True - self.db.immortal = False - # db-store if it is dead or not - self.db.is_dead = True - # specifies how much damage we divide away from non-magic weapons - self.db.damage_resistance = 100.0 - # pace (number of seconds between ticks) for - # the respective modes. - self.db.patrolling_pace = 6 - self.db.aggressive_pace = 2 - self.db.hunting_pace = 1 - self.db.death_pace = 100 # stay dead for 100 seconds - - # we store the call to the tickerhandler - # so we can easily deactivate the last - # ticker subscription when we switch. - # since we will use the same idstring - # throughout we only need to save the - # previous interval we used. - self.db.last_ticker_interval = None - - # store two separate descriptions, one for alive and - # one for dead (corpse) - self.db.desc_alive = "This is a moving object." - self.db.desc_dead = "A dead body." - - # health stats - self.db.full_health = 20 - self.db.health = 20 - - # when this mob defeats someone, we move the character off to - # some other place (Dark Cell in the tutorial). - self.db.send_defeated_to = "dark cell" - # text to echo to the defeated foe. - self.db.defeat_msg = "You fall to the ground." - self.db.defeat_msg_room = "%s falls to the ground." - self.db.weapon_ineffective_msg = ( - "Your weapon just passes through your enemy, causing almost no effect!" - ) - - self.db.death_msg = "After the last hit %s evaporates." % self.key - self.db.hit_msg = "%s wails, shudders and writhes." % self.key - self.db.irregular_msgs = ["the enemy looks about.", "the enemy changes stance."] - - self.db.tutorial_info = "This is an object with simple state AI, using a ticker to move."
- - def _set_ticker(self, interval, hook_key, stop=False): - """ - Set how often the given hook key should - be "ticked". - - Args: - interval (int): The number of seconds - between ticks - hook_key (str): The name of the method - (on this mob) to call every interval - seconds. - stop (bool, optional): Just stop the - last ticker without starting a new one. - With this set, the interval and hook_key - arguments are unused. - - In order to only have one ticker - running at a time, we make sure to store the - previous ticker subscription so that we can - easily find and stop it before setting a - new one. The tickerhandler is persistent so - we need to remember this across reloads. - - """ - idstring = "tutorial_mob" # this doesn't change - last_interval = self.db.last_ticker_interval - last_hook_key = self.db.last_hook_key - if last_interval and last_hook_key: - # we have a previous subscription, kill this first. - TICKER_HANDLER.remove( - interval=last_interval, callback=getattr(self, last_hook_key), idstring=idstring - ) - self.db.last_ticker_interval = interval - self.db.last_hook_key = hook_key - if not stop: - # set the new ticker - TICKER_HANDLER.add( - interval=interval, callback=getattr(self, hook_key), idstring=idstring - ) - - def _find_target(self, location): - """ - Scan the given location for suitable targets (this is defined - as Characters) to attack. Will ignore superusers. - - Args: - location (Object): the room to scan. - - Returns: - The first suitable target found. - - """ - targets = [ - obj - for obj in location.contents_get(exclude=self) - if obj.has_account and not obj.is_superuser - ] - return targets[0] if targets else None - -
[docs] def set_alive(self, *args, **kwargs): - """ - Set the mob to "alive" mode. This effectively - resurrects it from the dead state. - """ - self.db.health = self.db.full_health - self.db.is_dead = False - self.db.desc = self.db.desc_alive - self.ndb.is_immortal = self.db.immortal - self.ndb.is_patrolling = self.db.patrolling - if not self.location: - self.move_to(self.home) - if self.db.patrolling: - self.start_patrolling()
- -
[docs] def set_dead(self): - """ - Set the mob to "dead" mode. This turns it off - and makes sure it can take no more damage. - It also starts a ticker for when it will return. - """ - self.db.is_dead = True - self.location = None - self.ndb.is_patrolling = False - self.ndb.is_attacking = False - self.ndb.is_hunting = False - self.ndb.is_immortal = True - # we shall return after some time - self._set_ticker(self.db.death_pace, "set_alive")
- -
[docs] def start_idle(self): - """ - Starts just standing around. This will kill - the ticker and do nothing more. - """ - self._set_ticker(None, None, stop=True)
- -
[docs] def start_patrolling(self): - """ - Start the patrolling state by - registering us with the ticker-handler - at a leasurely pace. - """ - if not self.db.patrolling: - self.start_idle() - return - self._set_ticker(self.db.patrolling_pace, "do_patrol") - self.ndb.is_patrolling = True - self.ndb.is_hunting = False - self.ndb.is_attacking = False - # for the tutorial, we also heal the mob in this mode - self.db.health = self.db.full_health
- -
[docs] def start_hunting(self): - """ - Start the hunting state - """ - if not self.db.hunting: - self.start_patrolling() - return - self._set_ticker(self.db.hunting_pace, "do_hunt") - self.ndb.is_patrolling = False - self.ndb.is_hunting = True - self.ndb.is_attacking = False
- -
[docs] def start_attacking(self): - """ - Start the attacking state - """ - if not self.db.aggressive: - self.start_hunting() - return - self._set_ticker(self.db.aggressive_pace, "do_attack") - self.ndb.is_patrolling = False - self.ndb.is_hunting = False - self.ndb.is_attacking = True
- -
[docs] def do_patrol(self, *args, **kwargs): - """ - Called repeatedly during patrolling mode. In this mode, the - mob scans its surroundings and randomly chooses a viable exit. - One should lock exits with the traverse:has_account() lock in - order to block the mob from moving outside its area while - allowing account-controlled characters to move normally. - """ - if random.random() < 0.01 and self.db.irregular_msgs: - self.location.msg_contents(random.choice(self.db.irregular_msgs)) - if self.db.aggressive: - # first check if there are any targets in the room. - target = self._find_target(self.location) - if target: - self.start_attacking() - return - # no target found, look for an exit. - exits = [exi for exi in self.location.exits if exi.access(self, "traverse")] - if exits: - # randomly pick an exit - exit = random.choice(exits) - # move there. - self.move_to(exit.destination) - else: - # no exits! teleport to home to get away. - self.move_to(self.home)
- -
[docs] def do_hunting(self, *args, **kwargs): - """ - Called regularly when in hunting mode. In hunting mode the mob - scans adjacent rooms for enemies and moves towards them to - attack if possible. - """ - if random.random() < 0.01 and self.db.irregular_msgs: - self.location.msg_contents(random.choice(self.db.irregular_msgs)) - if self.db.aggressive: - # first check if there are any targets in the room. - target = self._find_target(self.location) - if target: - self.start_attacking() - return - # no targets found, scan surrounding rooms - exits = [exi for exi in self.location.exits if exi.access(self, "traverse")] - if exits: - # scan the exits destination for targets - for exit in exits: - target = self._find_target(exit.destination) - if target: - # a target found. Move there. - self.move_to(exit.destination) - return - # if we get to this point we lost our - # prey. Resume patrolling. - self.start_patrolling() - else: - # no exits! teleport to home to get away. - self.move_to(self.home)
- -
[docs] def do_attack(self, *args, **kwargs): - """ - Called regularly when in attacking mode. In attacking mode - the mob will bring its weapons to bear on any targets - in the room. - """ - if random.random() < 0.01 and self.db.irregular_msgs: - self.location.msg_contents(random.choice(self.db.irregular_msgs)) - # first make sure we have a target - target = self._find_target(self.location) - if not target: - # no target, start looking for one - self.start_hunting() - return - - # we use the same attack commands as defined in - # tutorial_world.objects.Weapon, assuming that - # the mob is given a Weapon to attack with. - attack_cmd = random.choice(("thrust", "pierce", "stab", "slash", "chop")) - self.execute_cmd("%s %s" % (attack_cmd, target)) - - if target.db.health is None: - # This is not an attackable target - logger.log_err(f"{self.key} found {target} had an `health` attribute of `None`.") - return - - # analyze the current state - if target.db.health <= 0: - # we reduced the target to <= 0 health. Move them to the - # defeated room - target.msg(self.db.defeat_msg) - self.location.msg_contents(self.db.defeat_msg_room % target.key, exclude=target) - send_defeated_to = search_object(self.db.send_defeated_to) - if send_defeated_to: - target.move_to(send_defeated_to[0], quiet=True) - else: - logger.log_err( - "Mob: mob.db.send_defeated_to not found: %s" % self.db.send_defeated_to - )
- - # response methods - called by other objects - -
[docs] def at_hit(self, weapon, attacker, damage): - """ - Someone landed a hit on us. Check our status - and start attacking if not already doing so. - """ - if self.db.health is None: - # health not set - this can't be damaged. - attacker.msg(self.db.weapon_ineffective_msg) - return - - if not self.ndb.is_immortal: - if not weapon.db.magic: - # not a magic weapon - divide away magic resistance - damage /= self.db.damage_resistance - attacker.msg(self.db.weapon_ineffective_msg) - else: - self.location.msg_contents(self.db.hit_msg) - self.db.health -= damage - - # analyze the result - if self.db.health <= 0: - # we are dead! - attacker.msg(self.db.death_msg) - self.set_dead() - else: - # still alive, start attack if not already attacking - if self.db.aggressive and not self.ndb.is_attacking: - self.start_attacking()
- -
[docs] def at_new_arrival(self, new_character): - """ - This is triggered whenever a new character enters the room. - This is called by the TutorialRoom the mob stands in and - allows it to be aware of changes immediately without needing - to poll for them all the time. For example, the mob can react - right away, also when patrolling on a very slow ticker. - """ - # the room actually already checked all we need, so - # we know it is a valid target. - if self.db.aggressive and not self.ndb.is_attacking: - self.start_attacking()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_world/objects.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_world/objects.html deleted file mode 100644 index fb742f6a3c..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_world/objects.html +++ /dev/null @@ -1,1291 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_world.objects — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_world.objects

-"""
-TutorialWorld - basic objects - Griatch 2011
-
-This module holds all "dead" object definitions for
-the tutorial world. Object-commands and -cmdsets
-are also defined here, together with the object.
-
-Objects:
-
-TutorialObject
-
-TutorialReadable
-TutorialClimbable
-Obelisk
-LightSource
-CrumblingWall
-Weapon
-WeaponRack
-
-"""
-
-import random
-
-from evennia import DefaultObject, DefaultExit, Command, CmdSet
-from evennia.utils import search, delay, dedent
-from evennia.prototypes.spawner import spawn
-
-# -------------------------------------------------------------
-#
-# TutorialObject
-#
-# The TutorialObject is the base class for all items
-# in the tutorial. They have an attribute "tutorial_info"
-# on them that the global tutorial command can use to extract
-# interesting behind-the scenes information about the object.
-#
-# TutorialObjects may also be "reset". What the reset means
-# is up to the object. It can be the resetting of the world
-# itself, or the removal of an inventory item from a
-# character's inventory when leaving the tutorial, for example.
-#
-# -------------------------------------------------------------
-
-
-
[docs]class TutorialObject(DefaultObject): - """ - This is the baseclass for all objects in the tutorial. - """ - -
[docs] def at_object_creation(self): - """Called when the object is first created.""" - super().at_object_creation() - self.db.tutorial_info = "No tutorial info is available for this object."
- -
[docs] def reset(self): - """Resets the object, whatever that may mean.""" - self.location = self.home
- - -# ------------------------------------------------------------- -# -# Readable - an object that can be "read" -# -# ------------------------------------------------------------- - -# -# Read command -# - - -
[docs]class CmdRead(Command): - """ - Usage: - read [obj] - - Read some text of a readable object. - """ - - key = "read" - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """ - Implements the read command. This simply looks for an - Attribute "readable_text" on the object and displays that. - """ - - if self.args: - obj = self.caller.search(self.args.strip()) - else: - obj = self.obj - if not obj: - return - # we want an attribute read_text to be defined. - readtext = obj.db.readable_text - if readtext: - string = "You read |C%s|n:\n %s" % (obj.key, readtext) - else: - string = "There is nothing to read on %s." % obj.key - self.caller.msg(string)
- - -
[docs]class CmdSetReadable(CmdSet): - """ - A CmdSet for readables. - """ - -
[docs] def at_cmdset_creation(self): - """ - Called when the cmdset is created. - """ - self.add(CmdRead())
- - -
[docs]class TutorialReadable(TutorialObject): - """ - This simple object defines some attributes and - """ - -
[docs] def at_object_creation(self): - """ - Called when object is created. We make sure to set the needed - Attribute and add the readable cmdset. - """ - super().at_object_creation() - self.db.tutorial_info = ( - "This is an object with a 'read' command defined in a command set on itself." - ) - self.db.readable_text = "There is no text written on %s." % self.key - # define a command on the object. - self.cmdset.add_default(CmdSetReadable, permanent=True)
- - -# ------------------------------------------------------------- -# -# Climbable object -# -# The climbable object works so that once climbed, it sets -# a flag on the climber to show that it was climbed. A simple -# command 'climb' handles the actual climbing. The memory -# of what was last climbed is used in a simple puzzle in the -# tutorial. -# -# ------------------------------------------------------------- - - -
[docs]class CmdClimb(Command): - """ - Climb an object - - Usage: - climb <object> - - This allows you to climb. - """ - - key = "climb" - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """Implements function""" - - if not self.args: - self.caller.msg("What do you want to climb?") - return - obj = self.caller.search(self.args.strip()) - if not obj: - return - if obj != self.obj: - self.caller.msg("Try as you might, you cannot climb that.") - return - ostring = self.obj.db.climb_text - if not ostring: - ostring = "You climb %s. Having looked around, you climb down again." % self.obj.name - self.caller.msg(ostring) - # set a tag on the caller to remember that we climbed. - self.caller.tags.add("tutorial_climbed_tree", category="tutorial_world")
- - -
[docs]class CmdSetClimbable(CmdSet): - """Climbing cmdset""" - -
[docs] def at_cmdset_creation(self): - """populate set""" - self.add(CmdClimb())
- - -
[docs]class TutorialClimbable(TutorialObject): - """ - A climbable object. All that is special about it is that it has - the "climb" command available on it. - """ - -
[docs] def at_object_creation(self): - """Called at initial creation only""" - self.cmdset.add_default(CmdSetClimbable, permanent=True)
- - -# ------------------------------------------------------------- -# -# Obelisk - a unique item -# -# The Obelisk is an object with a modified return_appearance method -# that causes it to look slightly different every time one looks at it. -# Since what you actually see is a part of a game puzzle, the act of -# looking also stores a key attribute on the looking object (different -# depending on which text you saw) for later reference. -# -# ------------------------------------------------------------- - - -
[docs]class Obelisk(TutorialObject): - """ - This object changes its description randomly, and which is shown - determines which order "clue id" is stored on the Character for - future puzzles. - - Important Attribute: - puzzle_descs (list): list of descriptions. One of these is - picked randomly when this object is looked at and its index - in the list is used as a key for to solve the puzzle. - - """ - -
[docs] def at_object_creation(self): - """Called when object is created.""" - super().at_object_creation() - self.db.tutorial_info = ( - "This object changes its desc randomly, and makes sure to remember which one you saw." - ) - self.db.puzzle_descs = ["You see a normal stone slab"] - # make sure this can never be picked up - self.locks.add("get:false()")
- -
[docs] def return_appearance(self, caller): - """ - This hook is called by the look command to get the description - of the object. We overload it with our own version. - """ - # randomly get the index for one of the descriptions - descs = self.db.puzzle_descs - clueindex = random.randint(0, len(descs) - 1) - # set this description, with the random extra - string = ( - "The surface of the obelisk seem to waver, shift and writhe under your gaze, with " - "different scenes and structures appearing whenever you look at it. " - ) - self.db.desc = string + descs[clueindex] - # remember that this was the clue we got. The Puzzle room will - # look for this later to determine if you should be teleported - # or not. - caller.db.puzzle_clue = clueindex - # call the parent function as normal (this will use - # the new desc Attribute we just set) - return super().return_appearance(caller)
- - -# ------------------------------------------------------------- -# -# LightSource -# -# This object emits light. Once it has been turned on it -# cannot be turned off. When it burns out it will delete -# itself. -# -# This could be implemented using a single-repeat Script or by -# registering with the TickerHandler. We do it simpler by -# using the delay() utility function. This is very simple -# to use but does not survive a server @reload. Because of -# where the light matters (in the Dark Room where you can -# find new light sources easily), this is okay here. -# -# ------------------------------------------------------------- - - -
[docs]class CmdLight(Command): - """ - Creates light where there was none. Something to burn. - """ - - key = "on" - aliases = ["light", "burn"] - # only allow this command if command.obj is carried by caller. - locks = "cmd:holds()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """ - Implements the light command. Since this command is designed - to sit on a "lightable" object, we operate only on self.obj. - """ - - if self.obj.light(): - self.caller.msg("You light %s." % self.obj.key) - self.caller.location.msg_contents( - "%s lights %s!" % (self.caller, self.obj.key), exclude=[self.caller] - ) - else: - self.caller.msg("%s is already burning." % self.obj.key)
- - -
[docs]class CmdSetLight(CmdSet): - """CmdSet for the lightsource commands""" - - key = "lightsource_cmdset" - # this is higher than the dark cmdset - important! - priority = 3 - -
[docs] def at_cmdset_creation(self): - """called at cmdset creation""" - self.add(CmdLight())
- - -
[docs]class LightSource(TutorialObject): - """ - This implements a light source object. - - When burned out, the object will be deleted. - """ - -
[docs] def at_init(self): - """ - If this is called with the Attribute is_giving_light already - set, we know that the timer got killed by a server - reload/reboot before it had time to finish. So we kill it here - instead. This is the price we pay for the simplicity of the - non-persistent delay() method. - """ - if self.db.is_giving_light: - self.delete()
- -
[docs] def at_object_creation(self): - """Called when object is first created.""" - super().at_object_creation() - self.db.tutorial_info = ( - "This object can be lit to create light. It has a timeout for how long it burns." - ) - self.db.is_giving_light = False - self.db.burntime = 60 * 3 # 3 minutes - # this is the default desc, it can of course be customized - # when created. - self.db.desc = "A splinter of wood with remnants of resin on it, enough for burning." - # add the Light command - self.cmdset.add_default(CmdSetLight, permanent=True)
- - def _burnout(self): - """ - This is called when this light source burns out. We make no - use of the return value. - """ - # delete ourselves from the database - self.db.is_giving_light = False - try: - self.location.location.msg_contents( - "%s's %s flickers and dies." % (self.location, self.key), exclude=self.location - ) - self.location.msg("Your %s flickers and dies." % self.key) - self.location.location.check_light_state() - except AttributeError: - try: - self.location.msg_contents("A %s on the floor flickers and dies." % self.key) - self.location.location.check_light_state() - except AttributeError: - # Mainly happens if we happen to be in a None location - pass - self.delete() - -
[docs] def light(self): - """ - Light this object - this is called by Light command. - """ - if self.db.is_giving_light: - return False - # burn for 3 minutes before calling _burnout - self.db.is_giving_light = True - # if we are in a dark room, trigger its light check - try: - self.location.location.check_light_state() - except AttributeError: - try: - # maybe we are directly in the room - self.location.check_light_state() - except AttributeError: - # we are in a None location - pass - finally: - # start the burn timer. When it runs out, self._burnout - # will be called. We store the deferred so it can be - # killed in unittesting. - self.deferred = delay(60 * 3, self._burnout) - return True
- - -# ------------------------------------------------------------- -# -# Crumbling wall - unique exit -# -# This implements a simple puzzle exit that needs to be -# accessed with commands before one can get to traverse it. -# -# The puzzle-part is simply to move roots (that have -# presumably covered the wall) aside until a button for a -# secret door is revealed. The original position of the -# roots blocks the button, so they have to be moved to a certain -# position - when they have, the "press button" command -# is made available and the Exit is made traversable. -# -# ------------------------------------------------------------- - -# There are four roots - two horizontal and two vertically -# running roots. Each can have three positions: top/middle/bottom -# and left/middle/right respectively. There can be any number of -# roots hanging through the middle position, but only one each -# along the sides. The goal is to make the center position clear. -# (yes, it's really as simple as it sounds, just move the roots -# to each side to "win". This is just a tutorial, remember?) -# -# The ShiftRoot command depends on the root object having an -# Attribute root_pos (a dictionary) to describe the current -# position of the roots. - - -
[docs]class CmdShiftRoot(Command): - """ - Shifts roots around. - - Usage: - shift blue root left/right - shift red root left/right - shift yellow root up/down - shift green root up/down - - """ - - key = "shift" - aliases = ["shiftroot", "push", "pull", "move"] - # we only allow to use this command while the - # room is properly lit, so we lock it to the - # setting of Attribute "is_lit" on our location. - locks = "cmd:locattr(is_lit)" - help_category = "TutorialWorld" - -
[docs] def parse(self): - """ - Custom parser; split input by spaces for simplicity. - """ - self.arglist = self.args.strip().split()
- -
[docs] def func(self): - """ - Implement the command. - blue/red - vertical roots - yellow/green - horizontal roots - """ - - if not self.arglist: - self.caller.msg("What do you want to move, and in what direction?") - return - - if "root" in self.arglist: - # we clean out the use of the word "root" - self.arglist.remove("root") - - # we accept arguments on the form <color> <direction> - - if not len(self.arglist) > 1: - self.caller.msg( - "You must define which colour of root you want to move, and in which direction." - ) - return - - color = self.arglist[0].lower() - direction = self.arglist[1].lower() - - # get current root positions dict - root_pos = self.obj.db.root_pos - - if color not in root_pos: - self.caller.msg("No such root to move.") - return - - # first, vertical roots (red/blue) - can be moved left/right - if color == "red": - if direction == "left": - root_pos[color] = max(-1, root_pos[color] - 1) - self.caller.msg("You shift the reddish root to the left.") - if root_pos[color] != 0 and root_pos[color] == root_pos["blue"]: - root_pos["blue"] += 1 - self.caller.msg( - "The root with blue flowers gets in the way and is pushed to the right." - ) - elif direction == "right": - root_pos[color] = min(1, root_pos[color] + 1) - self.caller.msg("You shove the reddish root to the right.") - if root_pos[color] != 0 and root_pos[color] == root_pos["blue"]: - root_pos["blue"] -= 1 - self.caller.msg( - "The root with blue flowers gets in the way and is pushed to the left." - ) - else: - self.caller.msg( - "The root hangs straight down - you can only move it left or right." - ) - elif color == "blue": - if direction == "left": - root_pos[color] = max(-1, root_pos[color] - 1) - self.caller.msg("You shift the root with small blue flowers to the left.") - if root_pos[color] != 0 and root_pos[color] == root_pos["red"]: - root_pos["red"] += 1 - self.caller.msg( - "The reddish root is too big to fit as well, so that one falls away to the left." - ) - elif direction == "right": - root_pos[color] = min(1, root_pos[color] + 1) - self.caller.msg("You shove the root adorned with small blue flowers to the right.") - if root_pos[color] != 0 and root_pos[color] == root_pos["red"]: - root_pos["red"] -= 1 - self.caller.msg( - "The thick reddish root gets in the way and is pushed back to the left." - ) - else: - self.caller.msg( - "The root hangs straight down - you can only move it left or right." - ) - - # now the horizontal roots (yellow/green). They can be moved up/down - elif color == "yellow": - if direction == "up": - root_pos[color] = max(-1, root_pos[color] - 1) - self.caller.msg("You shift the root with small yellow flowers upwards.") - if root_pos[color] != 0 and root_pos[color] == root_pos["green"]: - root_pos["green"] += 1 - self.caller.msg("The green weedy root falls down.") - elif direction == "down": - root_pos[color] = min(1, root_pos[color] + 1) - self.caller.msg("You shove the root adorned with small yellow flowers downwards.") - if root_pos[color] != 0 and root_pos[color] == root_pos["green"]: - root_pos["green"] -= 1 - self.caller.msg("The weedy green root is shifted upwards to make room.") - else: - self.caller.msg("The root hangs across the wall - you can only move it up or down.") - elif color == "green": - if direction == "up": - root_pos[color] = max(-1, root_pos[color] - 1) - self.caller.msg("You shift the weedy green root upwards.") - if root_pos[color] != 0 and root_pos[color] == root_pos["yellow"]: - root_pos["yellow"] += 1 - self.caller.msg("The root with yellow flowers falls down.") - elif direction == "down": - root_pos[color] = min(1, root_pos[color] + 1) - self.caller.msg("You shove the weedy green root downwards.") - if root_pos[color] != 0 and root_pos[color] == root_pos["yellow"]: - root_pos["yellow"] -= 1 - self.caller.msg( - "The root with yellow flowers gets in the way and is pushed upwards." - ) - else: - self.caller.msg("The root hangs across the wall - you can only move it up or down.") - - # we have moved the root. Store new position - self.obj.db.root_pos = root_pos - - # Check victory condition - if list(root_pos.values()).count(0) == 0: # no roots in middle position - # This will affect the cmd: lock of CmdPressButton - self.obj.db.button_exposed = True - self.caller.msg("Holding aside the root you think you notice something behind it ...")
- - -
[docs]class CmdPressButton(Command): - """ - Presses a button. - """ - - key = "press" - aliases = ["press button", "button", "push button"] - # only accessible if the button was found and there is light. This checks - # the Attribute button_exposed on the Wall object so that - # you can only push the button when the puzzle is solved. It also - # checks the is_lit Attribute on the location. - locks = "cmd:objattr(button_exposed) and objlocattr(is_lit)" - help_category = "TutorialWorld" - -
[docs] def func(self): - """Implements the command""" - - if self.caller.db.crumbling_wall_found_exit: - # we already pushed the button - self.caller.msg( - "The button folded away when the secret passage opened. You cannot push it again." - ) - return - - # pushing the button - string = ( - "You move your fingers over the suspicious depression, then gives it a " - "decisive push. First nothing happens, then there is a rumble and a hidden " - "|wpassage|n opens, dust and pebbles rumbling as part of the wall moves aside." - ) - self.caller.msg(string) - string = ( - "%s moves their fingers over the suspicious depression, then gives it a " - "decisive push. First nothing happens, then there is a rumble and a hidden " - "|wpassage|n opens, dust and pebbles rumbling as part of the wall moves aside." - ) - self.caller.location.msg_contents(string % self.caller.key, exclude=self.caller) - if not self.obj.open_wall(): - self.caller.msg("The exit leads nowhere, there's just more stone behind it ...")
- - -
[docs]class CmdSetCrumblingWall(CmdSet): - """Group the commands for crumblingWall""" - - key = "crumblingwall_cmdset" - priority = 2 - -
[docs] def at_cmdset_creation(self): - """called when object is first created.""" - self.add(CmdShiftRoot()) - self.add(CmdPressButton())
- - -
[docs]class CrumblingWall(TutorialObject, DefaultExit): - """ - This is a custom Exit. - - The CrumblingWall can be examined in various ways, but only if a - lit light source is in the room. The traversal itself is blocked - by a traverse: lock on the exit that only allows passage if a - certain attribute is set on the trying account. - - Important attribute - destination - this property must be set to make this a valid exit - whenever the button is pushed (this hides it as an exit - until it actually is) - """ - -
[docs] def at_init(self): - """ - Called when object is recalled from cache. - """ - self.reset()
- -
[docs] def at_object_creation(self): - """called when the object is first created.""" - super().at_object_creation() - - self.aliases.add(["secret passage", "passage", "crack", "opening", "secret"]) - - # starting root positions. H1/H2 are the horizontally hanging roots, - # V1/V2 the vertically hanging ones. Each can have three positions: - # (-1, 0, 1) where 0 means the middle position. yellow/green are - # horizontal roots and red/blue vertical, all may have value 0, but n - # ever any other identical value. - self.db.root_pos = {"yellow": 0, "green": 0, "red": 0, "blue": 0} - - # flags controlling the puzzle victory conditions - self.db.button_exposed = False - self.db.exit_open = False - - # this is not even an Exit until it has a proper destination, and we won't assign - # that until it is actually open. Until then we store the destination here. This - # should be given a reasonable value at creation! - self.db.destination = "#2" - - # we lock this Exit so that one can only execute commands on it - # if its location is lit and only traverse it once the Attribute - # exit_open is set to True. - self.locks.add("cmd:locattr(is_lit);traverse:objattr(exit_open)") - # set cmdset - self.cmdset.add(CmdSetCrumblingWall, permanent=True)
- -
[docs] def open_wall(self): - """ - This method is called by the push button command once the puzzle - is solved. It opens the wall and sets a timer for it to reset - itself. - """ - # this will make it into a proper exit (this returns a list) - eloc = search.search_object(self.db.destination) - if not eloc: - return False - else: - self.destination = eloc[0] - self.db.exit_open = True - # start a 45 second timer before closing again. We store the deferred so it can be - # killed in unittesting. - self.deferred = delay(45, self.reset) - return True
- - def _translate_position(self, root, ipos): - """Translates the position into words""" - rootnames = { - "red": "The |rreddish|n vertical-hanging root ", - "blue": "The thick vertical root with |bblue|n flowers ", - "yellow": "The thin horizontal-hanging root with |yyellow|n flowers ", - "green": "The weedy |ggreen|n horizontal root ", - } - vpos = { - -1: "hangs far to the |wleft|n on the wall.", - 0: "hangs straight down the |wmiddle|n of the wall.", - 1: "hangs far to the |wright|n of the wall.", - } - hpos = { - -1: "covers the |wupper|n part of the wall.", - 0: "passes right over the |wmiddle|n of the wall.", - 1: "nearly touches the floor, near the |wbottom|n of the wall.", - } - - if root in ("yellow", "green"): - string = rootnames[root] + hpos[ipos] - else: - string = rootnames[root] + vpos[ipos] - return string - -
[docs] def return_appearance(self, caller): - """ - This is called when someone looks at the wall. We need to echo the - current root positions. - """ - if self.db.button_exposed: - # we found the button by moving the roots - result = [ - "Having moved all the roots aside, you find that the center of the wall, " - "previously hidden by the vegetation, hid a curious square depression. It was maybe once " - "concealed and made to look a part of the wall, but with the crumbling of stone around it, " - "it's now easily identifiable as some sort of button." - ] - elif self.db.exit_open: - # we pressed the button; the exit is open - result = [ - "With the button pressed, a crack has opened in the root-covered wall, just wide enough " - "to squeeze through. A cold draft is coming from the hole and you get the feeling the " - "opening may close again soon." - ] - else: - # puzzle not solved yet. - result = [ - "The wall is old and covered with roots that here and there have permeated the stone. " - "The roots (or whatever they are - some of them are covered in small nondescript flowers) " - "crisscross the wall, making it hard to clearly see its stony surface. Maybe you could " - "try to |wshift|n or |wmove|n them (like '|wshift red up|n').\n" - ] - # display the root positions to help with the puzzle - for key, pos in self.db.root_pos.items(): - result.append("\n" + self._translate_position(key, pos)) - self.db.desc = "".join(result) - - # call the parent to continue execution (will use the desc we just set) - return super().return_appearance(caller)
- -
[docs] def at_after_traverse(self, traverser, source_location): - """ - This is called after we traversed this exit. Cleans up and resets - the puzzle. - """ - del traverser.db.crumbling_wall_found_buttothe - del traverser.db.crumbling_wall_found_exit - self.reset()
- -
[docs] def at_failed_traverse(self, traverser): - """This is called if the account fails to pass the Exit.""" - traverser.msg("No matter how you try, you cannot force yourself through %s." % self.key)
- -
[docs] def reset(self): - """ - Called by tutorial world runner, or whenever someone successfully - traversed the Exit. - """ - self.location.msg_contents( - "The secret door closes abruptly, roots falling back into place." - ) - - # reset the flags and remove the exit destination - self.db.button_exposed = False - self.db.exit_open = False - self.destination = None - - # Reset the roots with some random starting positions for the roots: - start_pos = [ - {"yellow": 1, "green": 0, "red": 0, "blue": 0}, - {"yellow": 0, "green": 0, "red": 0, "blue": 0}, - {"yellow": 0, "green": 1, "red": -1, "blue": 0}, - {"yellow": 1, "green": 0, "red": 0, "blue": 0}, - {"yellow": 0, "green": 0, "red": 0, "blue": 1}, - ] - self.db.root_pos = random.choice(start_pos)
- - -# ------------------------------------------------------------- -# -# Weapon - object type -# -# A weapon is necessary in order to fight in the tutorial -# world. A weapon (which here is assumed to be a bladed -# melee weapon for close combat) has three commands, -# stab, slash and defend. Weapons also have a property "magic" -# to determine if they are usable against certain enemies. -# -# Since Characters don't have special skills in the tutorial, -# we let the weapon itself determine how easy/hard it is -# to hit with it, and how much damage it can do. -# -# ------------------------------------------------------------- - - -
[docs]class CmdAttack(Command): - """ - Attack the enemy. Commands: - - stab <enemy> - slash <enemy> - parry - - stab - (thrust) makes a lot of damage but is harder to hit with. - slash - is easier to land, but does not make as much damage. - parry - forgoes your attack but will make you harder to hit on next - enemy attack. - - """ - - # this is an example of implementing many commands as a single - # command class, using the given command alias to separate between them. - - key = "attack" - aliases = [ - "hit", - "kill", - "fight", - "thrust", - "pierce", - "stab", - "slash", - "chop", - "bash", - "parry", - "defend", - ] - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """Implements the stab""" - - cmdstring = self.cmdstring - - if cmdstring in ("attack", "fight"): - string = "How do you want to fight? Choose one of 'stab', 'slash' or 'defend'." - self.caller.msg(string) - return - - # parry mode - if cmdstring in ("parry", "defend"): - string = ( - "You raise your weapon in a defensive pose, ready to block the next enemy attack." - ) - self.caller.msg(string) - self.caller.db.combat_parry_mode = True - self.caller.location.msg_contents( - "%s takes a defensive stance" % self.caller, exclude=[self.caller] - ) - return - - if not self.args: - self.caller.msg("Who do you attack?") - return - target = self.caller.search(self.args.strip()) - if not target: - return - - if cmdstring in ("thrust", "pierce", "stab"): - hit = float(self.obj.db.hit) * 0.7 # modified due to stab - damage = self.obj.db.damage * 2 # modified due to stab - string = "You stab with %s. " % self.obj.key - tstring = "%s stabs at you with %s. " % (self.caller.key, self.obj.key) - ostring = "%s stabs at %s with %s. " % (self.caller.key, target.key, self.obj.key) - self.caller.db.combat_parry_mode = False - elif cmdstring in ("slash", "chop", "bash"): - hit = float(self.obj.db.hit) # un modified due to slash - damage = self.obj.db.damage # un modified due to slash - string = "You slash with %s. " % self.obj.key - tstring = "%s slash at you with %s. " % (self.caller.key, self.obj.key) - ostring = "%s slash at %s with %s. " % (self.caller.key, target.key, self.obj.key) - self.caller.db.combat_parry_mode = False - else: - self.caller.msg( - "You fumble with your weapon, unsure of whether to stab, slash or parry ..." - ) - self.caller.location.msg_contents( - "%s fumbles with their weapon." % self.caller, exclude=self.caller - ) - self.caller.db.combat_parry_mode = False - return - - if target.db.combat_parry_mode: - # target is defensive; even harder to hit! - target.msg("|GYou defend, trying to avoid the attack.|n") - hit *= 0.5 - - if random.random() <= hit: - self.caller.msg(string + "|gIt's a hit!|n") - target.msg(tstring + "|rIt's a hit!|n") - self.caller.location.msg_contents( - ostring + "It's a hit!", exclude=[target, self.caller] - ) - - # call enemy hook - if hasattr(target, "at_hit"): - # should return True if target is defeated, False otherwise. - target.at_hit(self.obj, self.caller, damage) - return - elif target.db.health: - target.db.health -= damage - else: - # sorry, impossible to fight this enemy ... - self.caller.msg("The enemy seems unaffected.") - return - else: - self.caller.msg(string + "|rYou miss.|n") - target.msg(tstring + "|gThey miss you.|n") - self.caller.location.msg_contents(ostring + "They miss.", exclude=[target, self.caller])
- - -
[docs]class CmdSetWeapon(CmdSet): - """Holds the attack command.""" - -
[docs] def at_cmdset_creation(self): - """called at first object creation.""" - self.add(CmdAttack())
- - -
[docs]class Weapon(TutorialObject): - """ - This defines a bladed weapon. - - Important attributes (set at creation): - hit - chance to hit (0-1) - parry - chance to parry (0-1) - damage - base damage given (modified by hit success and - type of attack) (0-10) - - """ - -
[docs] def at_object_creation(self): - """Called at first creation of the object""" - super().at_object_creation() - self.db.hit = 0.4 # hit chance - self.db.parry = 0.8 # parry chance - self.db.damage = 1.0 - self.db.magic = False - self.cmdset.add_default(CmdSetWeapon, permanent=True)
- -
[docs] def reset(self): - """ - When reset, the weapon is simply deleted, unless it has a place - to return to. - """ - if self.location.has_account and self.home == self.location: - self.location.msg_contents( - "%s suddenly and magically fades into nothingness, as if it was never there ..." - % self.key - ) - self.delete() - else: - self.location = self.home
- - -# ------------------------------------------------------------- -# -# Weapon rack - spawns weapons -# -# This is a spawner mechanism that creates custom weapons from a -# spawner prototype dictionary. Note that we only create a single typeclass -# (Weapon) yet customize all these different weapons using the spawner. -# The spawner dictionaries could easily sit in separate modules and be -# used to create unique and interesting variations of typeclassed -# objects. -# -# ------------------------------------------------------------- - -WEAPON_PROTOTYPES = { - "weapon": { - "typeclass": "evennia.contrib.tutorial_world.objects.Weapon", - "key": "Weapon", - "hit": 0.2, - "parry": 0.2, - "damage": 1.0, - "magic": False, - "desc": "A generic blade.", - }, - "knife": { - "prototype_parent": "weapon", - "aliases": "sword", - "key": "Kitchen knife", - "desc": "A rusty kitchen knife. Better than nothing.", - "damage": 3, - }, - "dagger": { - "prototype_parent": "knife", - "key": "Rusty dagger", - "aliases": ["knife", "dagger"], - "desc": "A double-edged dagger with a nicked edge and a wooden handle.", - "hit": 0.25, - }, - "sword": { - "prototype_parent": "weapon", - "key": "Rusty sword", - "aliases": ["sword"], - "desc": "A rusty shortsword. It has a leather-wrapped handle covered i food grease.", - "hit": 0.3, - "damage": 5, - "parry": 0.5, - }, - "club": { - "prototype_parent": "weapon", - "key": "Club", - "desc": "A heavy wooden club, little more than a heavy branch.", - "hit": 0.4, - "damage": 6, - "parry": 0.2, - }, - "axe": { - "prototype_parent": "weapon", - "key": "Axe", - "desc": "A woodcutter's axe with a keen edge.", - "hit": 0.4, - "damage": 6, - "parry": 0.2, - }, - "ornate longsword": { - "prototype_parent": "sword", - "key": "Ornate longsword", - "desc": "A fine longsword with some swirling patterns on the handle.", - "hit": 0.5, - "magic": True, - "damage": 5, - }, - "warhammer": { - "prototype_parent": "club", - "key": "Silver Warhammer", - "aliases": ["hammer", "warhammer", "war"], - "desc": "A heavy war hammer with silver ornaments. This huge weapon causes massive damage - if you can hit.", - "hit": 0.4, - "magic": True, - "damage": 8, - }, - "rune axe": { - "prototype_parent": "axe", - "key": "Runeaxe", - "aliases": ["axe"], - "hit": 0.4, - "magic": True, - "damage": 6, - }, - "thruning": { - "prototype_parent": "ornate longsword", - "key": "Broadsword named Thruning", - "desc": "This heavy bladed weapon is marked with the name 'Thruning'. It is very powerful in skilled hands.", - "hit": 0.6, - "parry": 0.6, - "damage": 7, - }, - "slayer waraxe": { - "prototype_parent": "rune axe", - "key": "Slayer waraxe", - "aliases": ["waraxe", "war", "slayer"], - "desc": "A huge double-bladed axe marked with the runes for 'Slayer'." - " It has more runic inscriptions on its head, which you cannot decipher.", - "hit": 0.7, - "damage": 8, - }, - "ghostblade": { - "prototype_parent": "ornate longsword", - "key": "The Ghostblade", - "aliases": ["blade", "ghost"], - "desc": "This massive sword is large as you are tall, yet seems to weigh almost nothing." - " It's almost like it's not really there.", - "hit": 0.9, - "parry": 0.8, - "damage": 10, - }, - "hawkblade": { - "prototype_parent": "ghostblade", - "key": "The Hawkblade", - "aliases": ["hawk", "blade"], - "desc": "The weapon of a long-dead heroine and a more civilized age," - " the hawk-shaped hilt of this blade almost has a life of its own.", - "hit": 0.85, - "parry": 0.7, - "damage": 11, - }, -} - - -
[docs]class CmdGetWeapon(Command): - """ - Usage: - get weapon - - This will try to obtain a weapon from the container. - """ - - key = "get weapon" - aliases = "get weapon" - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """ - Get a weapon from the container. It will - itself handle all messages. - """ - self.obj.produce_weapon(self.caller)
- - -
[docs]class CmdSetWeaponRack(CmdSet): - """ - The cmdset for the rack. - """ - - key = "weaponrack_cmdset" - -
[docs] def at_cmdset_creation(self): - """Called at first creation of cmdset""" - self.add(CmdGetWeapon())
- - -
[docs]class WeaponRack(TutorialObject): - """ - This object represents a weapon store. When people use the - "get weapon" command on this rack, it will produce one - random weapon from among those registered to exist - on it. This will also set a property on the character - to make sure they can't get more than one at a time. - - Attributes to set on this object: - available_weapons: list of prototype-keys from - WEAPON_PROTOTYPES, the weapons available in this rack. - no_more_weapons_msg - error message to return to accounts - who already got one weapon from the rack and tries to - grab another one. - - """ - -
[docs] def at_object_creation(self): - """ - called at creation - """ - self.cmdset.add_default(CmdSetWeaponRack, permanent=True) - self.db.rack_id = "weaponrack_1" - # these are prototype names from the prototype - # dictionary above. - self.db.get_weapon_msg = dedent( - """ - You find |c%s|n. While carrying this weapon, these actions are available: - |wstab/thrust/pierce <target>|n - poke at the enemy. More damage but harder to hit. - |wslash/chop/bash <target>|n - swipe at the enemy. Less damage but easier to hit. - |wdefend/parry|n - protect yourself and make yourself harder to hit.) - """ - ).strip() - - self.db.no_more_weapons_msg = "you find nothing else of use." - self.db.available_weapons = ["knife", "dagger", "sword", "club"]
- -
[docs] def produce_weapon(self, caller): - """ - This will produce a new weapon from the rack, - assuming the caller hasn't already gotten one. When - doing so, the caller will get Tagged with the id - of this rack, to make sure they cannot keep - pulling weapons from it indefinitely. - """ - rack_id = self.db.rack_id - if caller.tags.get(rack_id, category="tutorial_world"): - caller.msg(self.db.no_more_weapons_msg) - else: - prototype = random.choice(self.db.available_weapons) - # use the spawner to create a new Weapon from the - # spawner dictionary, tag the caller - wpn = spawn(WEAPON_PROTOTYPES[prototype], prototype_parents=WEAPON_PROTOTYPES)[0] - caller.tags.add(rack_id, category="tutorial_world") - wpn.location = caller - caller.msg(self.db.get_weapon_msg % wpn.key)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/tutorial_world/rooms.html b/docs/0.9.5/_modules/evennia/contrib/tutorial_world/rooms.html deleted file mode 100644 index edca26d2ad..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/tutorial_world/rooms.html +++ /dev/null @@ -1,1282 +0,0 @@ - - - - - - - - evennia.contrib.tutorial_world.rooms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.tutorial_world.rooms

-"""
-
-Room Typeclasses for the TutorialWorld.
-
-This defines special types of Rooms available in the tutorial. To keep
-everything in one place we define them together with the custom
-commands needed to control them. Those commands could also have been
-in a separate module (e.g. if they could have been re-used elsewhere.)
-
-"""
-
-
-import random
-from evennia import TICKER_HANDLER
-from evennia import CmdSet, Command, DefaultRoom
-from evennia import utils, create_object, search_object
-from evennia import syscmdkeys, default_cmds
-from evennia.contrib.tutorial_world.objects import LightSource
-
-# the system error-handling module is defined in the settings. We load the
-# given setting here using utils.object_from_module. This way we can use
-# it regardless of if we change settings later.
-from django.conf import settings
-
-_SEARCH_AT_RESULT = utils.object_from_module(settings.SEARCH_AT_RESULT)
-
-# -------------------------------------------------------------
-#
-# Tutorial room - parent room class
-#
-# This room is the parent of all rooms in the tutorial.
-# It defines a tutorial command on itself (available to
-# all those who are in a tutorial room).
-#
-# -------------------------------------------------------------
-
-#
-# Special command available in all tutorial rooms
-
-
-
[docs]class CmdTutorial(Command): - """ - Get help during the tutorial - - Usage: - tutorial [obj] - - This command allows you to get behind-the-scenes info - about an object or the current location. - - """ - - key = "tutorial" - aliases = ["tut"] - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """ - All we do is to scan the current location for an Attribute - called `tutorial_info` and display that. - """ - - caller = self.caller - - if not self.args: - target = self.obj # this is the room the command is defined on - else: - target = caller.search(self.args.strip()) - if not target: - return - helptext = target.db.tutorial_info or "" - - if helptext: - helptext = f" |G{helptext}|n" - else: - helptext = " |RSorry, there is no tutorial help available here.|n" - helptext += "\n\n (Write 'give up' if you want to abandon your quest.)" - caller.msg(helptext)
- - -# for the @detail command we inherit from MuxCommand, since -# we want to make use of MuxCommand's pre-parsing of '=' in the -# argument. -
[docs]class CmdTutorialSetDetail(default_cmds.MuxCommand): - """ - sets a detail on a room - - Usage: - @detail <key> = <description> - @detail <key>;<alias>;... = description - - Example: - @detail walls = The walls are covered in ... - @detail castle;ruin;tower = The distant ruin ... - - This sets a "detail" on the object this command is defined on - (TutorialRoom for this tutorial). This detail can be accessed with - the TutorialRoomLook command sitting on TutorialRoom objects (details - are set as a simple dictionary on the room). This is a Builder command. - - We custom parse the key for the ;-separator in order to create - multiple aliases to the detail all at once. - """ - - key = "@detail" - locks = "cmd:perm(Builder)" - help_category = "TutorialWorld" - -
[docs] def func(self): - """ - All this does is to check if the object has - the set_detail method and uses it. - """ - if not self.args or not self.rhs: - self.caller.msg("Usage: @detail key = description") - return - if not hasattr(self.obj, "set_detail"): - self.caller.msg("Details cannot be set on %s." % self.obj) - 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) - self.obj.set_detail(key, self.rhs) - self.caller.msg("Detail set: '%s': '%s'" % (self.lhs, self.rhs))
- - -
[docs]class CmdTutorialLook(default_cmds.CmdLook): - """ - looks at the room and on details - - Usage: - look <obj> - look <room detail> - look *<account> - - Observes your location, details at your location or objects - in your vicinity. - - Tutorial: This is a child of the default Look command, that also - allows us to look at "details" in the room. These details are - things to examine and offers some extra description without - actually having to be actual database objects. It uses the - return_detail() hook on TutorialRooms for this. - """ - - # we don't need to specify key/locks etc, this is already - # set by the parent. - help_category = "TutorialWorld" - -
[docs] def func(self): - """ - Handle the looking. This is a copy of the default look - code except for adding in the details. - """ - caller = self.caller - args = self.args - if args: - # we use quiet=True to turn off automatic error reporting. - # This tells search that we want to handle error messages - # ourself. This also means the search function will always - # return a list (with 0, 1 or more elements) rather than - # result/None. - looking_at_obj = caller.search( - args, - # note: excludes room/room aliases - candidates=caller.location.contents + caller.contents, - use_nicks=True, - quiet=True, - ) - if len(looking_at_obj) != 1: - # no target found or more than one target found (multimatch) - # look for a detail that may match - detail = self.obj.return_detail(args) - if detail: - self.caller.msg(detail) - return - else: - # no detail found, delegate our result to the normal - # error message handler. - _SEARCH_AT_RESULT(looking_at_obj, caller, args) - return - else: - # we found a match, extract it from the list and carry on - # normally with the look handling. - looking_at_obj = looking_at_obj[0] - - else: - looking_at_obj = caller.location - if not looking_at_obj: - caller.msg("You have no location to look at!") - return - - if not hasattr(looking_at_obj, "return_appearance"): - # this is likely due to us having an account instead - looking_at_obj = looking_at_obj.character - if not looking_at_obj.access(caller, "view"): - caller.msg("Could not find '%s'." % args) - return - # get object's appearance - caller.msg(looking_at_obj.return_appearance(caller)) - # the object's at_desc() method. - looking_at_obj.at_desc(looker=caller) - return
- - -
[docs]class CmdTutorialGiveUp(default_cmds.MuxCommand): - """ - Give up the tutorial-world quest and return to Limbo, the start room of the - server. - - """ - - key = "give up" - aliases = ["abort"] - -
[docs] def func(self): - outro_room = OutroRoom.objects.all() - if outro_room: - outro_room = outro_room[0] - else: - self.caller.msg( - "That didn't work (seems like a bug). " - "Try to use the |wteleport|n command instead." - ) - return - - self.caller.move_to(outro_room)
- - -
[docs]class TutorialRoomCmdSet(CmdSet): - """ - Implements the simple tutorial cmdset. This will overload the look - command in the default CharacterCmdSet since it has a higher - priority (ChracterCmdSet has prio 0) - """ - - key = "tutorial_cmdset" - priority = 1 - -
[docs] def at_cmdset_creation(self): - """add the tutorial-room commands""" - self.add(CmdTutorial()) - self.add(CmdTutorialSetDetail()) - self.add(CmdTutorialLook()) - self.add(CmdTutorialGiveUp())
- - -
[docs]class TutorialRoom(DefaultRoom): - """ - This is the base room type for all rooms in the tutorial world. - It defines a cmdset on itself for reading tutorial info about the location. - """ - -
[docs] def at_object_creation(self): - """Called when room is first created""" - self.db.tutorial_info = ( - "This is a tutorial room. It allows you to use the 'tutorial' command." - ) - self.cmdset.add_default(TutorialRoomCmdSet)
- -
[docs] def at_object_receive(self, new_arrival, source_location): - """ - When an object enter a tutorial room we tell other objects in - the room about it by trying to call a hook on them. The Mob object - uses this to cheaply get notified of enemies without having - to constantly scan for them. - - Args: - new_arrival (Object): the object that just entered this room. - source_location (Object): the previous location of new_arrival. - - """ - if new_arrival.has_account and not new_arrival.is_superuser: - # this is a character - for obj in self.contents_get(exclude=new_arrival): - if hasattr(obj, "at_new_arrival"): - obj.at_new_arrival(new_arrival)
- -
[docs] def return_detail(self, detailkey): - """ - This looks for an Attribute "obj_details" and possibly - returns the value of it. - - Args: - detailkey (str): The detail being looked at. This is - case-insensitive. - - """ - details = self.db.details - if details: - return details.get(detailkey.lower(), None)
- -
[docs] 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}
- - -# ------------------------------------------------------------- -# -# Weather room - room with a ticker -# -# ------------------------------------------------------------- - -# These are rainy weather strings -WEATHER_STRINGS = ( - "The rain coming down from the iron-grey sky intensifies.", - "A gust of wind throws the rain right in your face. Despite your cloak you shiver.", - "The rainfall eases a bit and the sky momentarily brightens.", - "For a moment it looks like the rain is slowing, then it begins anew with renewed force.", - "The rain pummels you with large, heavy drops. You hear the rumble of thunder in the distance.", - "The wind is picking up, howling around you, throwing water droplets in your face. It's cold.", - "Bright fingers of lightning flash over the sky, moments later followed by a deafening rumble.", - "It rains so hard you can hardly see your hand in front of you. You'll soon be drenched to the bone.", - "Lightning strikes in several thundering bolts, striking the trees in the forest to your west.", - "You hear the distant howl of what sounds like some sort of dog or wolf.", - "Large clouds rush across the sky, throwing their load of rain over the world.", -) - - -
[docs]class WeatherRoom(TutorialRoom): - """ - This should probably better be called a rainy room... - - This sets up an outdoor room typeclass. At irregular intervals, - the effects of weather will show in the room. Outdoor rooms should - inherit from this. - - """ - -
[docs] def at_object_creation(self): - """ - Called when object is first created. - We set up a ticker to update this room regularly. - - Note that we could in principle also use a Script to manage - the ticking of the room; the TickerHandler works fine for - simple things like this though. - """ - super().at_object_creation() - # subscribe ourselves to a ticker to repeatedly call the hook - # "update_weather" on this object. The interval is randomized - # so as to not have all weather rooms update at the same time. - self.db.interval = random.randint(50, 70) - TICKER_HANDLER.add( - interval=self.db.interval, callback=self.update_weather, idstring="tutorial" - ) - # this is parsed by the 'tutorial' command on TutorialRooms. - self.db.tutorial_info = "This room has a Script running that has it echo a weather-related message at irregular intervals."
- -
[docs] def update_weather(self, *args, **kwargs): - """ - Called by the tickerhandler at regular intervals. Even so, we - only update 20% of the time, picking a random weather message - when we do. The tickerhandler requires that this hook accepts - any arguments and keyword arguments (hence the *args, **kwargs - even though we don't actually use them in this example) - """ - if random.random() < 0.2: - # only update 20 % of the time - self.msg_contents("|w%s|n" % random.choice(WEATHER_STRINGS))
- - -SUPERUSER_WARNING = ( - "\nWARNING: You are playing as a superuser ({name}). Use the {quell} command to\n" - "play without superuser privileges (many functions and puzzles ignore the \n" - "presence of a superuser, making this mode useful for exploring things behind \n" - "the scenes later).\n" -) - -# ------------------------------------------------------------ -# -# Intro Room - unique room -# -# This room marks the start of the tutorial. It sets up properties on -# the player char that is needed for the tutorial. -# -# ------------------------------------------------------------- - - -
[docs]class CmdEvenniaIntro(Command): - """ - Start the Evennia intro wizard. - - Usage: - intro - - """ - - key = "intro" - -
[docs] def func(self): - from .intro_menu import init_menu - - # quell also superusers - if self.caller.account: - self.caller.account.execute_cmd("quell") - self.caller.msg("(Auto-quelling)") - init_menu(self.caller)
- - -
[docs]class CmdSetEvenniaIntro(CmdSet): - key = "Evennia Intro StartSet" - -
[docs] def at_cmdset_creation(self): - self.add(CmdEvenniaIntro())
- - -
[docs]class IntroRoom(TutorialRoom): - """ - Intro room - - properties to customize: - char_health - integer > 0 (default 20) - """ - -
[docs] def at_object_creation(self): - """ - Called when the room is first created. - """ - super().at_object_creation() - self.db.tutorial_info = ( - "The first room of the tutorial. " - "This assigns the health Attribute to " - "the account." - ) - self.cmdset.add(CmdSetEvenniaIntro, permanent=True)
- -
[docs] def at_object_receive(self, character, source_location): - """ - Assign properties on characters - """ - - # setup character for the tutorial - health = self.db.char_health or 20 - - if character.has_account: - character.db.health = health - character.db.health_max = health - - if character.is_superuser: - string = "-" * 78 + SUPERUSER_WARNING + "-" * 78 - character.msg("|r%s|n" % string.format(name=character.key, quell="|wquell|r")) - else: - # quell user - if character.account: - character.account.execute_cmd("quell") - character.msg("(Auto-quelling while in tutorial-world)")
- - -# ------------------------------------------------------------- -# -# Bridge - unique room -# -# Defines a special west-eastward "bridge"-room, a large room that takes -# several steps to cross. It is complete with custom commands and a -# chance of falling off the bridge. This room has no regular exits, -# instead the exitings are handled by custom commands set on the account -# upon first entering the room. -# -# Since one can enter the bridge room from both ends, it is -# divided into five steps: -# westroom <- 0 1 2 3 4 -> eastroom -# -# ------------------------------------------------------------- - - -
[docs]class CmdEast(Command): - """ - Go eastwards across the bridge. - - Tutorial info: - This command relies on the caller having two Attributes - (assigned by the room when entering): - - east_exit: a unique name or dbref to the room to go to - when exiting east. - - west_exit: a unique name or dbref to the room to go to - when exiting west. - The room must also have the following Attributes - - tutorial_bridge_posistion: the current position on - on the bridge, 0 - 4. - - """ - - key = "east" - aliases = ["e"] - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """move one step eastwards""" - caller = self.caller - - bridge_step = min(5, caller.db.tutorial_bridge_position + 1) - - if bridge_step > 4: - # we have reached the far east end of the bridge. - # Move to the east room. - eexit = search_object(self.obj.db.east_exit) - if eexit: - caller.move_to(eexit[0]) - else: - caller.msg("No east exit was found for this room. Contact an admin.") - return - caller.db.tutorial_bridge_position = bridge_step - # since we are really in one room, we have to notify others - # in the room when we move. - caller.location.msg_contents( - "%s steps eastwards across the bridge." % caller.name, exclude=caller - ) - caller.execute_cmd("look")
- - -# go back across the bridge -
[docs]class CmdWest(Command): - """ - Go westwards across the bridge. - - Tutorial info: - This command relies on the caller having two Attributes - (assigned by the room when entering): - - east_exit: a unique name or dbref to the room to go to - when exiting east. - - west_exit: a unique name or dbref to the room to go to - when exiting west. - The room must also have the following property: - - tutorial_bridge_posistion: the current position on - on the bridge, 0 - 4. - - """ - - key = "west" - aliases = ["w"] - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """move one step westwards""" - caller = self.caller - - bridge_step = max(-1, caller.db.tutorial_bridge_position - 1) - - if bridge_step < 0: - # we have reached the far west end of the bridge. - # Move to the west room. - wexit = search_object(self.obj.db.west_exit) - if wexit: - caller.move_to(wexit[0]) - else: - caller.msg("No west exit was found for this room. Contact an admin.") - return - caller.db.tutorial_bridge_position = bridge_step - # since we are really in one room, we have to notify others - # in the room when we move. - caller.location.msg_contents( - "%s steps westwards across the bridge." % caller.name, exclude=caller - ) - caller.execute_cmd("look")
- - -BRIDGE_POS_MESSAGES = ( - "You are standing |wvery close to the the bridge's western foundation|n." - " If you go west you will be back on solid ground ...", - "The bridge slopes precariously where it extends eastwards" - " towards the lowest point - the center point of the hang bridge.", - "You are |whalfways|n out on the unstable bridge.", - "The bridge slopes precariously where it extends westwards" - " towards the lowest point - the center point of the hang bridge.", - "You are standing |wvery close to the bridge's eastern foundation|n." - " If you go east you will be back on solid ground ...", -) -BRIDGE_MOODS = ( - "The bridge sways in the wind.", - "The hanging bridge creaks dangerously.", - "You clasp the ropes firmly as the bridge sways and creaks under you.", - "From the castle you hear a distant howling sound, like that of a large dog or other beast.", - "The bridge creaks under your feet. Those planks does not seem very sturdy.", - "Far below you the ocean roars and throws its waves against the cliff," - " as if trying its best to reach you.", - "Parts of the bridge come loose behind you, falling into the chasm far below!", - "A gust of wind causes the bridge to sway precariously.", - "Under your feet a plank comes loose, tumbling down. For a moment you dangle over the abyss ...", - "The section of rope you hold onto crumble in your hands," - " parts of it breaking apart. You sway trying to regain balance.", -) - -FALL_MESSAGE = ( - "Suddenly the plank you stand on gives way under your feet! You fall!" - "\nYou try to grab hold of an adjoining plank, but all you manage to do is to " - "divert your fall westwards, towards the cliff face. This is going to hurt ... " - "\n ... The world goes dark ...\n\n" -) - - -
[docs]class CmdLookBridge(Command): - """ - looks around at the bridge. - - Tutorial info: - This command assumes that the room has an Attribute - "fall_exit", a unique name or dbref to the place they end upp - if they fall off the bridge. - """ - - key = "look" - aliases = ["l"] - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """Looking around, including a chance to fall.""" - caller = self.caller - bridge_position = self.caller.db.tutorial_bridge_position - # this command is defined on the room, so we get it through self.obj - location = self.obj - # randomize the look-echo - message = "|c%s|n\n%s\n%s" % ( - location.key, - BRIDGE_POS_MESSAGES[bridge_position], - random.choice(BRIDGE_MOODS), - ) - - chars = [obj for obj in self.obj.contents_get(exclude=caller) if obj.has_account] - if chars: - # we create the You see: message manually here - message += "\n You see: %s" % ", ".join("|c%s|n" % char.key for char in chars) - self.caller.msg(message) - - # there is a chance that we fall if we are on the western or central - # part of the bridge. - if bridge_position < 3 and random.random() < 0.05 and not self.caller.is_superuser: - # we fall 5% of time. - fall_exit = search_object(self.obj.db.fall_exit) - if fall_exit: - self.caller.msg("|r%s|n" % FALL_MESSAGE) - self.caller.move_to(fall_exit[0], quiet=True) - # inform others on the bridge - self.obj.msg_contents( - "A plank gives way under %s's feet and " - "they fall from the bridge!" % self.caller.key - )
- - -# custom help command -
[docs]class CmdBridgeHelp(Command): - """ - Overwritten help command while on the bridge. - """ - - key = "help" - aliases = ["h", "?"] - locks = "cmd:all()" - help_category = "Tutorial world" - -
[docs] def func(self): - """Implements the command.""" - string = ( - "You are trying hard not to fall off the bridge ..." - "\n\nWhat you can do is trying to cross the bridge |weast|n" - " or try to get back to the mainland |wwest|n)." - ) - self.caller.msg(string)
- - -
[docs]class BridgeCmdSet(CmdSet): - """This groups the bridge commands. We will store it on the room.""" - - key = "Bridge commands" - priority = 2 # this gives it precedence over the normal look/help commands. - -
[docs] def at_cmdset_creation(self): - """Called at first cmdset creation""" - self.add(CmdTutorial()) - self.add(CmdEast()) - self.add(CmdWest()) - self.add(CmdLookBridge()) - self.add(CmdBridgeHelp())
- - -BRIDGE_WEATHER = ( - "The rain intensifies, making the planks of the bridge even more slippery.", - "A gust of wind throws the rain right in your face.", - "The rainfall eases a bit and the sky momentarily brightens.", - "The bridge shakes under the thunder of a closeby thunder strike.", - "The rain pummels you with large, heavy drops. You hear the distinct howl of a large hound in the distance.", - "The wind is picking up, howling around you and causing the bridge to sway from side to side.", - "Some sort of large bird sweeps by overhead, giving off an eery screech. Soon it has disappeared in the gloom.", - "The bridge sways from side to side in the wind.", - "Below you a particularly large wave crashes into the rocks.", - "From the ruin you hear a distant, otherwordly howl. Or maybe it was just the wind.", -) - - -
[docs]class BridgeRoom(WeatherRoom): - """ - The bridge room implements an unsafe bridge. It also enters the player into - a state where they get new commands so as to try to cross the bridge. - - We want this to result in the account getting a special set of - commands related to crossing the bridge. The result is that it - will take several steps to cross it, despite it being represented - by only a single room. - - We divide the bridge into steps: - - self.db.west_exit - - | - - self.db.east_exit - 0 1 2 3 4 - - The position is handled by a variable stored on the character - when entering and giving special move commands will - increase/decrease the counter until the bridge is crossed. - - We also has self.db.fall_exit, which points to a gathering - location to end up if we happen to fall off the bridge (used by - the CmdLookBridge command). - - """ - -
[docs] def at_object_creation(self): - """Setups the room""" - # this will start the weather room's ticker and tell - # it to call update_weather regularly. - super().at_object_creation() - # this identifies the exits from the room (should be the command - # needed to leave through that exit). These are defaults, but you - # could of course also change them after the room has been created. - self.db.west_exit = "cliff" - self.db.east_exit = "gate" - self.db.fall_exit = "cliffledge" - # add the cmdset on the room. - self.cmdset.add(BridgeCmdSet, permanent=True) - # since the default Character's at_look() will access the room's - # return_description (this skips the cmdset) when - # first entering it, we need to explicitly turn off the room - # as a normal view target - once inside, our own look will - # handle all return messages. - self.locks.add("view:false()")
- -
[docs] def update_weather(self, *args, **kwargs): - """ - This is called at irregular intervals and makes the passage - over the bridge a little more interesting. - """ - if random.random() < 80: - # send a message most of the time - self.msg_contents("|w%s|n" % random.choice(BRIDGE_WEATHER))
- -
[docs] def at_object_receive(self, character, source_location): - """ - This hook is called by the engine whenever the player is moved - into this room. - """ - if character.has_account: - # we only run this if the entered object is indeed a player object. - # check so our east/west exits are correctly defined. - wexit = search_object(self.db.west_exit) - eexit = search_object(self.db.east_exit) - fexit = search_object(self.db.fall_exit) - if not (wexit and eexit and fexit): - character.msg( - "The bridge's exits are not properly configured. " - "Contact an admin. Forcing west-end placement." - ) - character.db.tutorial_bridge_position = 0 - return - if source_location == eexit[0]: - # we assume we enter from the same room we will exit to - character.db.tutorial_bridge_position = 4 - else: - # if not from the east, then from the west! - character.db.tutorial_bridge_position = 0 - character.execute_cmd("look")
- -
[docs] def at_object_leave(self, character, target_location): - """ - This is triggered when the player leaves the bridge room. - """ - if character.has_account: - # clean up the position attribute - del character.db.tutorial_bridge_position
- - -# ------------------------------------------------------------------------------- -# -# Dark Room - a room with states -# -# This room limits the movemenets of its denizens unless they carry an active -# LightSource object (LightSource is defined in -# tutorialworld.objects.LightSource) -# -# ------------------------------------------------------------------------------- - - -DARK_MESSAGES = ( - "It is pitch black. You are likely to be eaten by a grue.", - "It's pitch black. You fumble around but cannot find anything.", - "You don't see a thing. You feel around, managing to bump your fingers hard against something. Ouch!", - "You don't see a thing! Blindly grasping the air around you, you find nothing.", - "It's totally dark here. You almost stumble over some un-evenness in the ground.", - "You are completely blind. For a moment you think you hear someone breathing nearby ... " - "\n ... surely you must be mistaken.", - "Blind, you think you find some sort of object on the ground, but it turns out to be just a stone.", - "Blind, you bump into a wall. The wall seems to be covered with some sort of vegetation," - " but its too damp to burn.", - "You can't see anything, but the air is damp. It feels like you are far underground.", -) - -ALREADY_LIGHTSOURCE = ( - "You don't want to stumble around in blindness anymore. You already " - "found what you need. Let's get light already!" -) - -FOUND_LIGHTSOURCE = ( - "Your fingers bump against a splinter of wood in a corner." - " It smells of resin and seems dry enough to burn! " - "You pick it up, holding it firmly. Now you just need to" - " |wlight|n it using the flint and steel you carry with you." -) - - -
[docs]class CmdLookDark(Command): - """ - Look around in darkness - - Usage: - look - - Look around in the darkness, trying - to find something. - """ - - key = "look" - aliases = ["l", "feel", "search", "feel around", "fiddle"] - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """ - Implement the command. - - This works both as a look and a search command; there is a - random chance of eventually finding a light source. - """ - caller = self.caller - - # count how many searches we've done - nr_searches = caller.ndb.dark_searches - if nr_searches is None: - nr_searches = 0 - caller.ndb.dark_searches = nr_searches - - if nr_searches < 4 and random.random() < 0.90: - # we don't find anything - caller.msg(random.choice(DARK_MESSAGES)) - caller.ndb.dark_searches += 1 - else: - # we could have found something! - if any(obj for obj in caller.contents if utils.inherits_from(obj, LightSource)): - # we already carry a LightSource object. - caller.msg(ALREADY_LIGHTSOURCE) - else: - # don't have a light source, create a new one. - create_object(LightSource, key="splinter", location=caller) - caller.msg(FOUND_LIGHTSOURCE)
- - -
[docs]class CmdDarkHelp(Command): - """ - Help command for the dark state. - """ - - key = "help" - locks = "cmd:all()" - help_category = "TutorialWorld" - -
[docs] def func(self): - """ - Replace the the help command with a not-so-useful help - """ - string = ( - "Can't help you until you find some light! Try looking/feeling around for something to burn. " - "You shouldn't give up even if you don't find anything right away." - ) - self.caller.msg(string)
- - -
[docs]class CmdDarkNoMatch(Command): - """ - This is a system command. Commands with special keys are used to - override special sitations in the game. The CMD_NOMATCH is used - when the given command is not found in the current command set (it - replaces Evennia's default behavior or offering command - suggestions) - """ - - key = syscmdkeys.CMD_NOMATCH - locks = "cmd:all()" - -
[docs] def func(self): - """Implements the command.""" - self.caller.msg( - "Until you find some light, there's not much you can do. " - "Try feeling around, maybe you'll find something helpful!" - )
- - -
[docs]class DarkCmdSet(CmdSet): - """ - Groups the commands of the dark room together. We also import the - default say command here so that players can still talk in the - darkness. - - We give the cmdset the mergetype "Replace" to make sure it - completely replaces whichever command set it is merged onto - (usually the default cmdset) - """ - - key = "darkroom_cmdset" - mergetype = "Replace" - priority = 2 - -
[docs] def at_cmdset_creation(self): - """populate the cmdset.""" - self.add(CmdTutorial()) - self.add(CmdLookDark()) - self.add(CmdDarkHelp()) - self.add(CmdDarkNoMatch()) - self.add(default_cmds.CmdSay()) - self.add(default_cmds.CmdQuit()) - self.add(default_cmds.CmdHome())
- - -
[docs]class DarkRoom(TutorialRoom): - """ - A dark room. This tries to start the DarkState script on all - objects entering. The script is responsible for making sure it is - valid (that is, that there is no light source shining in the room). - - The is_lit Attribute is used to define if the room is currently lit - or not, so as to properly echo state changes. - - Since this room (in the tutorial) is meant as a sort of catch-all, - we also make sure to heal characters ending up here, since they - may have been beaten up by the ghostly apparition at this point. - - """ - -
[docs] def at_object_creation(self): - """ - Called when object is first created. - """ - super().at_object_creation() - self.db.tutorial_info = "This is a room with custom command sets on itself." - # the room starts dark. - self.db.is_lit = False - self.cmdset.add(DarkCmdSet, permanent=True)
- -
[docs] def at_init(self): - """ - Called when room is first recached (such as after a reload) - """ - self.check_light_state()
- - def _carries_light(self, obj): - """ - Checks if the given object carries anything that gives light. - - Note that we do NOT look for a specific LightSource typeclass, - but for the Attribute is_giving_light - this makes it easy to - later add other types of light-giving items. We also accept - if there is a light-giving object in the room overall (like if - a splinter was dropped in the room) - """ - return ( - obj.is_superuser - or obj.db.is_giving_light - or any(o for o in obj.contents if o.db.is_giving_light) - ) - - def _heal(self, character): - """ - Heal a character. - """ - health = character.db.health_max or 20 - character.db.health = health - -
[docs] def check_light_state(self, exclude=None): - """ - This method checks if there are any light sources in the room. - If there isn't it makes sure to add the dark cmdset to all - characters in the room. It is called whenever characters enter - the room and also by the Light sources when they turn on. - - Args: - exclude (Object): An object to not include in the light check. - """ - if any(self._carries_light(obj) for obj in self.contents if obj != exclude): - self.locks.add("view:all()") - self.cmdset.remove(DarkCmdSet) - self.db.is_lit = True - for char in (obj for obj in self.contents if obj.has_account): - # this won't do anything if it is already removed - char.msg("The room is lit up.") - else: - # noone is carrying light - darken the room - self.db.is_lit = False - self.locks.add("view:false()") - self.cmdset.add(DarkCmdSet, permanent=True) - for char in (obj for obj in self.contents if obj.has_account): - if char.is_superuser: - char.msg("You are Superuser, so you are not affected by the dark state.") - else: - # put players in darkness - char.msg("The room is completely dark.")
- -
[docs] def at_object_receive(self, obj, source_location): - """ - Called when an object enters the room. - """ - if obj.has_account: - # a puppeted object, that is, a Character - self._heal(obj) - # in case the new guy carries light with them - self.check_light_state()
- -
[docs] def at_object_leave(self, obj, target_location): - """ - In case people leave with the light, we make sure to clear the - DarkCmdSet if necessary. This also works if they are - teleported away. - """ - # since this hook is called while the object is still in the room, - # we exclude it from the light check, to ignore any light sources - # it may be carrying. - self.check_light_state(exclude=obj)
- - -# ------------------------------------------------------------- -# -# Teleport room - puzzles solution -# -# This is a sort of puzzle room that requires a certain -# attribute on the entering character to be the same as -# an attribute of the room. If not, the character will -# be teleported away to a target location. This is used -# by the Obelisk - grave chamber puzzle, where one must -# have looked at the obelisk to get an attribute set on -# oneself, and then pick the grave chamber with the -# matching imagery for this attribute. -# -# ------------------------------------------------------------- - - -
[docs]class TeleportRoom(TutorialRoom): - """ - Teleporter - puzzle room. - - Important attributes (set at creation): - puzzle_key - which attr to look for on character - puzzle_value - what char.db.puzzle_key must be set to - success_teleport_to - where to teleport in case if success - success_teleport_msg - message to echo while teleporting to success - failure_teleport_to - where to teleport to in case of failure - failure_teleport_msg - message to echo while teleporting to failure - - """ - -
[docs] def at_object_creation(self): - """Called at first creation""" - super().at_object_creation() - # what character.db.puzzle_clue must be set to, to avoid teleportation. - self.db.puzzle_value = 1 - # target of successful teleportation. Can be a dbref or a - # unique room name. - self.db.success_teleport_msg = "You are successful!" - self.db.success_teleport_to = "treasure room" - # the target of the failure teleportation. - self.db.failure_teleport_msg = "You fail!" - self.db.failure_teleport_to = "dark cell"
- -
[docs] def at_object_receive(self, character, source_location): - """ - This hook is called by the engine whenever the player is moved into - this room. - """ - if not character.has_account: - # only act on player characters. - return - # determine if the puzzle is a success or not - is_success = str(character.db.puzzle_clue) == str(self.db.puzzle_value) - teleport_to = self.db.success_teleport_to if is_success else self.db.failure_teleport_to - # note that this returns a list - results = search_object(teleport_to) - if not results or len(results) > 1: - # we cannot move anywhere since no valid target was found. - character.msg("no valid teleport target for %s was found." % teleport_to) - return - if character.is_superuser: - # superusers don't get teleported - character.msg("Superuser block: You would have been teleported to %s." % results[0]) - return - # perform the teleport - if is_success: - character.msg(self.db.success_teleport_msg) - else: - character.msg(self.db.failure_teleport_msg) - # teleport quietly to the new place - character.move_to(results[0], quiet=True, move_hooks=False) - # we have to call this manually since we turn off move_hooks - # - this is necessary to make the target dark room aware of an - # already carried light. - results[0].at_object_receive(character, self)
- - -# ------------------------------------------------------------- -# -# Outro room - unique exit room -# -# Cleans up the character from all tutorial-related properties. -# -# ------------------------------------------------------------- - - -
[docs]class OutroRoom(TutorialRoom): - """ - Outro room. - - Called when exiting the tutorial, cleans the - character of tutorial-related attributes. - - """ - -
[docs] def at_object_creation(self): - """ - Called when the room is first created. - """ - super().at_object_creation() - self.db.tutorial_info = ( - "The last room of the tutorial. " - "This cleans up all temporary Attributes " - "the tutorial may have assigned to the " - "character." - )
- -
[docs] def at_object_receive(self, character, source_location): - """ - Do cleanup. - """ - if character.has_account: - del character.db.health_max - del character.db.health - del character.db.last_climbed - del character.db.puzzle_clue - del character.db.combat_parry_mode - del character.db.tutorial_bridge_position - for obj in character.contents: - if obj.typeclass_path.startswith("evennia.contrib.tutorial_world"): - obj.delete() - character.tags.clear(category="tutorial_world")
- -
[docs] def at_object_leave(self, character, destination): - if character.account: - character.account.execute_cmd("unquell")
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/unixcommand.html b/docs/0.9.5/_modules/evennia/contrib/unixcommand.html deleted file mode 100644 index ad9f470f50..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/unixcommand.html +++ /dev/null @@ -1,401 +0,0 @@ - - - - - - - - evennia.contrib.unixcommand — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.unixcommand

-"""
-Unix-like Command style parent
-
-Evennia contribution, Vincent Le Geoff 2017
-
-This module contains a command class that allows for unix-style command syntax in-game, using
---options, positional arguments and stuff like -n 10 etc similarly to a unix command. It might not
-the best syntax for the average player but can be really useful for builders when they need to have
-a single command do many things with many options. It uses the ArgumentParser from Python's standard
-library under the hood.
-
-To use, inherit `UnixCommand` from this module from your own commands. You need
-to override two methods:
-
-- The `init_parser` method, which adds options to the parser. Note that you should normally
-    *not* override the normal `parse` method when inheriting from `UnixCommand`.
-- The `func` method, called to execute the command once parsed (like any Command).
-
-Here's a short example:
-
-```python
-class CmdPlant(UnixCommand):
-
-    '''
-    Plant a tree or plant.
-
-    This command is used to plant something in the room you are in.
-
-    Examples:
-      plant orange -a 8
-      plant strawberry --hidden
-      plant potato --hidden --age 5
-
-    '''
-
-    key = "plant"
-
-    def init_parser(self):
-        "Add the arguments to the parser."
-        # 'self.parser' inherits `argparse.ArgumentParser`
-        self.parser.add_argument("key",
-                help="the key of the plant to be planted here")
-        self.parser.add_argument("-a", "--age", type=int,
-                default=1, help="the age of the plant to be planted")
-        self.parser.add_argument("--hidden", action="store_true",
-                help="should the newly-planted plant be hidden to players?")
-
-    def func(self):
-        "func is called only if the parser succeeded."
-        # 'self.opts' contains the parsed options
-        key = self.opts.key
-        age = self.opts.age
-        hidden = self.opts.hidden
-        self.msg("Going to plant '{}', age={}, hidden={}.".format(
-                key, age, hidden))
-```
-
-To see the full power of argparse and the types of supported options, visit
-[the documentation of argparse](https://docs.python.org/2/library/argparse.html).
-
-"""
-
-import argparse
-import shlex
-from textwrap import dedent
-
-from evennia import Command, InterruptCommand
-from evennia.utils.ansi import raw
-
-
-
[docs]class ParseError(Exception): - - """An error occurred during parsing.""" - - pass
- - -
[docs]class UnixCommandParser(argparse.ArgumentParser): - - """A modifier command parser for unix commands. - - This parser is used to replace `argparse.ArgumentParser`. It - is aware of the command calling it, and can more easily report to - the caller. Some features (like the "brutal exit" of the original - parser) are disabled or replaced. This parser is used by UnixCommand - and creating one directly isn't recommended nor necessary. Even - adding a sub-command will use this replaced parser automatically. - - """ - -
[docs] def __init__(self, prog, description="", epilog="", command=None, **kwargs): - """ - Build a UnixCommandParser with a link to the command using it. - - Args: - prog (str): the program name (usually the command key). - description (str): a very brief line to show in the usage text. - epilog (str): the epilog to show below options. - command (Command): the command calling the parser. - - Keyword Args: - Additional keyword arguments are directly sent to - `argparse.ArgumentParser`. You will find them on the - [parser's documentation](https://docs.python.org/2/library/argparse.html). - - Note: - It's doubtful you would need to create this parser manually. - The `UnixCommand` does that automatically. If you create - sub-commands, this class will be used. - - """ - prog = prog or command.key - super().__init__( - prog=prog, description=description, conflict_handler="resolve", add_help=False, **kwargs - ) - self.command = command - self.post_help = epilog - - def n_exit(code=None, msg=None): - raise ParseError(msg) - - self.exit = n_exit - - # Replace the -h/--help - self.add_argument( - "-h", "--hel", nargs=0, action=HelpAction, help="display the command help" - )
- -
[docs] def format_usage(self): - """Return the usage line. - - Note: - This method is present to return the raw-escaped usage line, - in order to avoid unintentional color codes. - - """ - return raw(super().format_usage())
- -
[docs] def format_help(self): - """Return the parser help, including its epilog. - - Note: - This method is present to return the raw-escaped help, - in order to avoid unintentional color codes. Color codes - in the epilog (the command docstring) are supported. - - """ - autohelp = raw(super().format_help()) - return "\n" + autohelp + "\n" + self.post_help
- -
[docs] def print_usage(self, file=None): - """Print the usage to the caller. - - Args: - file (file-object): not used here, the caller is used. - - Note: - This method will override `argparse.ArgumentParser`'s in order - to not display the help on stdout or stderr, but to the - command's caller. - - """ - if self.command: - self.command.msg(self.format_usage().strip())
- -
[docs] def print_help(self, file=None): - """Print the help to the caller. - - Args: - file (file-object): not used here, the caller is used. - - Note: - This method will override `argparse.ArgumentParser`'s in order - to not display the help on stdout or stderr, but to the - command's caller. - - """ - if self.command: - self.command.msg(self.format_help().strip())
- - -
[docs]class HelpAction(argparse.Action): - - """Override the -h/--help action in the default parser. - - Using the default -h/--help will call the exit function in different - ways, preventing the entire help message to be provided. Hence - this override. - - """ - - def __call__(self, parser, namespace, values, option_string=None): - """If asked for help, display to the caller.""" - if parser.command: - parser.command.msg(parser.format_help().strip()) - parser.exit(0, "")
- - -
[docs]class UnixCommand(Command): - """ - Unix-type commands, supporting short and long options. - - This command syntax uses the Unix-style commands with short options - (-X) and long options (--something). The `argparse` module is - used to parse the command. - - In order to use it, you should override two methods: - - `init_parser`: this method is called when the command is created. - It can be used to set options in the parser. `self.parser` - contains the `argparse.ArgumentParser`, so you can add arguments - here. - - `func`: this method is called to execute the command, but after - the parser has checked the arguments given to it are valid. - You can access the namespace of valid arguments in `self.opts` - at this point. - - The help of UnixCommands is derived from the docstring, in a - slightly different way than usual: the first line of the docstring - is used to represent the program description (the very short - line at the top of the help message). The other lines below are - used as the program's "epilog", displayed below the options. It - means in your docstring, you don't have to write the options. - They will be automatically provided by the parser and displayed - accordingly. The `argparse` module provides a default '-h' or - '--help' option on the command. Typing |whelp commandname|n will - display the same as |wcommandname -h|n, though this behavior can - be changed. - - """ - -
[docs] def __init__(self, **kwargs): - """ - The lockhandler works the same as for objects. - optional kwargs will be set as properties on the Command at runtime, - overloading evential same-named class properties. - - """ - super().__init__(**kwargs) - - # Create the empty UnixCommandParser, inheriting argparse.ArgumentParser - lines = dedent(self.__doc__.strip("\n")).splitlines() - description = lines[0].strip() - epilog = "\n".join(lines[1:]).strip() - self.parser = UnixCommandParser(None, description, epilog, command=self) - - # Fill the argument parser - self.init_parser()
- -
[docs] def init_parser(self): - """ - Configure the argument parser, adding in options. - - Note: - This method is to be overridden in order to add options - to the argument parser. Use `self.parser`, which contains - the `argparse.ArgumentParser`. You can, for instance, - use its `add_argument` method. - - """ - pass
- -
[docs] def func(self): - """Override to handle the command execution.""" - pass
- -
[docs] def get_help(self, caller, cmdset): - """ - Return the help message for this command and this caller. - - Args: - caller (Object or Player): the caller asking for help on the command. - cmdset (CmdSet): the command set (if you need additional commands). - - Returns: - docstring (str): the help text to provide the caller for this command. - - """ - return self.parser.format_help()
- -
[docs] def parse(self): - """ - Process arguments provided in `self.args`. - - Note: - You should not override this method. Consider overriding - `init_parser` instead. - - """ - try: - self.opts = self.parser.parse_args(shlex.split(self.args)) - except ParseError as err: - msg = str(err) - if msg: - self.msg(msg) - raise InterruptCommand
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/contrib/wilderness.html b/docs/0.9.5/_modules/evennia/contrib/wilderness.html deleted file mode 100644 index 646330336b..0000000000 --- a/docs/0.9.5/_modules/evennia/contrib/wilderness.html +++ /dev/null @@ -1,883 +0,0 @@ - - - - - - - - evennia.contrib.wilderness — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.contrib.wilderness

-"""
-Wilderness system
-
-Evennia contrib - titeuf87 2017
-
-This contrib provides a wilderness map. This is an area that can be huge where
-the rooms are mostly similar, except for some small cosmetic changes like the
-room name.
-
-Usage:
-
-    This contrib does not provide any commands. Instead the @py command can be
-    used.
-
-    A wilderness map needs to created first. There can be different maps, all
-    with their own name. If no name is provided, then a default one is used. Internally,
-    the wilderness is stored as a Script with the name you specify. If you don't
-    specify the name, a script named "default" will be created and used.
-
-    @py from evennia.contrib import wilderness; wilderness.create_wilderness()
-
-    Once created, it is possible to move into that wilderness map:
-
-    @py from evennia.contrib import wilderness; wilderness.enter_wilderness(me)
-
-    All coordinates used by the wilderness map are in the format of `(x, y)`
-    tuples. x goes from left to right and y goes from bottom to top. So `(0, 0)`
-    is the bottom left corner of the map.
-
-
-Customisation:
-
-    The defaults, while useable, are meant to be customised. When creating a
-    new wilderness map it is possible to give a "map provider": this is a
-    python object that is smart enough to create the map.
-
-    The default provider, WildernessMapProvider, just creates a grid area that
-    is unlimited in size.
-    This WildernessMapProvider can be subclassed to create more interesting
-    maps and also to customize the room/exit typeclass used.
-
-    There is also no command that allows players to enter the wilderness. This
-    still needs to be added: it can be a command or an exit, depending on your
-    needs.
-
-Customisation example:
-
-    To give an example of how to customize, we will create a very simple (and
-    small) wilderness map that is shaped like a pyramid. The map will be
-    provided as a string: a "." symbol is a location we can walk on.
-
-    Let's create a file world/pyramid.py:
-
-    ```python
-    map_str = \"\"\"
-         .
-        ...
-       .....
-      .......
-    \"\"\"
-
-    from evennia.contrib import wilderness
-
-    class PyramidMapProvider(wilderness.WildernessMapProvider):
-
-        def is_valid_coordinates(self, wilderness, coordinates):
-            "Validates if these coordinates are inside the map"
-            x, y = coordinates
-            try:
-                lines = map_str.split("\n")
-                # The reverse is needed because otherwise the pyramid will be
-                # upside down
-                lines.reverse()
-                line = lines[y]
-                column = line[x]
-                return column == "."
-            except IndexError:
-                return False
-
-        def get_location_name(self, coordinates):
-            "Set the location name"
-            x, y = coordinates
-            if y == 3:
-                return "Atop the pyramid."
-            else:
-                return "Inside a pyramid."
-
-        def at_prepare_room(self, coordinates, caller, room):
-            "Any other changes done to the room before showing it"
-            x, y = coordinates
-            desc = "This is a room in the pyramid."
-            if y == 3 :
-                desc = "You can see far and wide from the top of the pyramid."
-            room.db.desc = desc
-    ```
-
-    Now we can use our new pyramid-shaped wilderness map. From inside Evennia we
-    create a new wilderness (with the name "default") but using our new map provider:
-
-    ```
-    @py from world import pyramid as p; p.wilderness.create_wilderness(mapprovider=p.PyramidMapProvider())
-
-    @py from evennia.contrib import wilderness; wilderness.enter_wilderness(me, coordinates=(4, 1))
-
-    ```
-Implementation details:
-
-    When a character moves into the wilderness, they get their own room. If
-    they move, instead of moving the character, the room changes to match the
-    new coordinates.
-    If a character meets another character in the wilderness, then their room
-    merges. When one of the character leaves again, they each get their own
-    separate rooms.
-    Rooms are created as needed. Unneeded rooms are stored away to avoid the
-    overhead cost of creating new rooms again in the future.
-
-"""
-
-from evennia import DefaultRoom, DefaultExit, DefaultScript
-from evennia import create_object, create_script
-from evennia.utils import inherits_from
-
-
-
[docs]def create_wilderness(name="default", mapprovider=None): - """ - Creates a new wilderness map. Does nothing if a wilderness map already - exists with the same name. - - Args: - name (str, optional): the name to use for that wilderness map - mapprovider (WildernessMap instance, optional): an instance of a - WildernessMap class (or subclass) that will be used to provide the - layout of this wilderness map. If none is provided, the default - infinite grid map will be used. - - """ - if WildernessScript.objects.filter(db_key=name).exists(): - # Don't create two wildernesses with the same name - return - - if not mapprovider: - mapprovider = WildernessMapProvider() - script = create_script(WildernessScript, key=name) - script.db.mapprovider = mapprovider
- - -
[docs]def enter_wilderness(obj, coordinates=(0, 0), name="default"): - """ - Moves obj into the wilderness. The wilderness needs to exist first and the - provided coordinates needs to be valid inside that wilderness. - - Args: - obj (object): the object to move into the wilderness - coordinates (tuple), optional): the coordinates to move obj to into - the wilderness. If not provided, defaults (0, 0) - name (str, optional): name of the wilderness map, if not using the - default one - - Returns: - bool: True if obj successfully moved into the wilderness. - """ - if not WildernessScript.objects.filter(db_key=name).exists(): - return False - - script = WildernessScript.objects.get(db_key=name) - if script.is_valid_coordinates(coordinates): - script.move_obj(obj, coordinates) - return True - else: - return False
- - -
[docs]def get_new_coordinates(coordinates, direction): - """ - Returns the coordinates of direction applied to the provided coordinates. - - Args: - coordinates: tuple of (x, y) - direction: a direction string (like "northeast") - - Returns: - tuple: tuple of (x, y) coordinates - """ - x, y = coordinates - - if direction in ("north", "northwest", "northeast"): - y += 1 - if direction in ("south", "southwest", "southeast"): - y -= 1 - if direction in ("northwest", "west", "southwest"): - x -= 1 - if direction in ("northeast", "east", "southeast"): - x += 1 - - return (x, y)
- - -
[docs]class WildernessScript(DefaultScript): - """ - This is the main "handler" for the wilderness system: inside here the - coordinates of every item currently inside the wilderness is stored. This - script is responsible for creating rooms as needed and storing rooms away - into storage when they are not needed anymore. - """ - -
[docs] def at_script_creation(self): - """ - Only called once, when the script is created. This is a default Evennia - hook. - """ - self.persistent = True - - # Store the coordinates of every item that is inside the wilderness - # Key: object, Value: (x, y) - self.db.itemcoordinates = {} - - # Store the rooms that are used as views into the wilderness - # Key: (x, y), Value: room object - self.db.rooms = {} - - # Created rooms that are not needed anymore are stored there. This - # allows quick retrieval if a new room is needed without having to - # create it. - self.db.unused_rooms = []
- - @property - def mapprovider(self): - """ - Shortcut property to the map provider. - - Returns: - MapProvider: the mapprovider used with this wilderness - """ - return self.db.mapprovider - - @property - def itemcoordinates(self): - """ - Returns a dictionary with the coordinates of every item inside this - wilderness map. The key is the item, the value are the coordinates as - (x, y) tuple. - - Returns: - {item: coordinates} - """ - return self.db.itemcoordinates - -
[docs] def at_start(self): - """ - Called when the script is started and also after server reloads. - """ - for coordinates, room in self.db.rooms.items(): - room.ndb.wildernessscript = self - room.ndb.active_coordinates = coordinates - for item in list(self.db.itemcoordinates.keys()): - # Items deleted from the wilderness leave None type 'ghosts' - # that must be cleaned out - if item is None: - del self.db.itemcoordinates[item] - continue - item.ndb.wilderness = self
- -
[docs] def is_valid_coordinates(self, coordinates): - """ - Returns True if coordinates are valid (and can be travelled to). - Otherwise returns False - - Args: - coordinates (tuple): coordinates as (x, y) tuple - - Returns: - bool: True if the coordinates are valid - """ - return self.mapprovider.is_valid_coordinates(self, coordinates)
- -
[docs] def get_obj_coordinates(self, obj): - """ - Returns the coordinates of obj in the wilderness. - - Returns (x, y) - - Args: - obj (object): an object inside the wilderness - - Returns: - tuple: (x, y) tuple of where obj is located - """ - return self.itemcoordinates[obj]
- -
[docs] def get_objs_at_coordinates(self, coordinates): - """ - Returns a list of every object at certain coordinates. - - Imeplementation detail: this uses a naive iteration through every - object inside the wilderness which could cause slow downs when there - are a lot of objects in the map. - - Args: - coordinates (tuple): a coordinate tuple like (x, y) - - Returns: - [Object, ]: list of Objects at coordinates - """ - result = [] - for item, item_coordinates in list(self.itemcoordinates.items()): - # Items deleted from the wilderness leave None type 'ghosts' - # that must be cleaned out - if item is None: - del self.db.itemcoordinates[item] - continue - if coordinates == item_coordinates: - result.append(item) - return result
- -
[docs] def move_obj(self, obj, new_coordinates): - """ - Moves obj to new coordinates in this wilderness. - - Args: - obj (object): the object to move - new_coordinates (tuple): tuple of (x, y) where to move obj to. - """ - # Update the position of this obj in the wilderness - self.itemcoordinates[obj] = new_coordinates - old_room = obj.location - - # Remove the obj's location. This is needed so that the object does not - # appear in its old room should that room be deleted. - obj.location = None - - try: - # See if we already have a room for that location - room = self.db.rooms[new_coordinates] - # There is. Try to destroy the old_room if it is not needed anymore - self._destroy_room(old_room) - except KeyError: - # There is no room yet at new_location - if (old_room and not inherits_from(old_room, WildernessRoom)) or (not old_room): - # Obj doesn't originally come from a wilderness room. - # We'll create a new one then. - room = self._create_room(new_coordinates, obj) - else: - # Obj does come from another wilderness room - create_new_room = False - - if old_room.wilderness != self: - # ... but that other wilderness room belongs to another - # wilderness map - create_new_room = True - old_room.wilderness.at_after_object_leave(obj) - else: - for item in old_room.contents: - if item.has_account: - # There is still a player in the old room. - # Let's create a new room and not touch that old - # room. - create_new_room = True - break - - if create_new_room: - # Create a new room to hold obj, not touching any obj's in - # the old room - room = self._create_room(new_coordinates, obj) - else: - # The old_room is empty: we are just going to reuse that - # room instead of creating a new one - room = old_room - - room.set_active_coordinates(new_coordinates, obj) - obj.location = room - obj.ndb.wilderness = self
- - def _create_room(self, coordinates, report_to): - """ - Gets a new WildernessRoom to be used for the provided coordinates. - - It first tries to retrieve a room out of storage. If there are no rooms - left a new one will be created. - - Args: - coordinates (tuple): coordinate tuple of (x, y) - report_to (object): the obj to return error messages to - """ - if self.db.unused_rooms: - # There is still unused rooms stored in storage, let's get one of - # those - room = self.db.unused_rooms.pop() - else: - # No more unused rooms...time to make a new one. - - # First, create the room - room = create_object( - typeclass=self.mapprovider.room_typeclass, key="Wilderness", report_to=report_to - ) - - # Then the exits - exits = [ - ("north", "n"), - ("northeast", "ne"), - ("east", "e"), - ("southeast", "se"), - ("south", "s"), - ("southwest", "sw"), - ("west", "w"), - ("northwest", "nw"), - ] - for key, alias in exits: - create_object( - typeclass=self.mapprovider.exit_typeclass, - key=key, - aliases=[alias], - location=room, - destination=room, - report_to=report_to, - ) - - room.ndb.active_coordinates = coordinates - room.ndb.wildernessscript = self - self.db.rooms[coordinates] = room - - return room - - def _destroy_room(self, room): - """ - Moves a room back to storage. If room is not a WildernessRoom or there - is a player inside the room, then this does nothing. - - Args: - room (WildernessRoom): the room to put in storage - """ - if not room or not inherits_from(room, WildernessRoom): - return - - for item in room.contents: - if item.has_account: - # There is still a character in that room. We can't get rid of - # it just yet - break - else: - # No characters left in the room. - - # Clear the location of every obj in that room first - for item in room.contents: - if item.destination and item.destination == room: - # Ignore the exits, they stay in the room - continue - item.location = None - - # Then delete its reference - del self.db.rooms[room.ndb.active_coordinates] - # And finally put this room away in storage - self.db.unused_rooms.append(room) - -
[docs] def at_after_object_leave(self, obj): - """ - Called after an object left this wilderness map. Used for cleaning up. - - Args: - obj (object): the object that left - """ - # Remove that obj from the wilderness's coordinates dict - loc = self.db.itemcoordinates[obj] - del self.db.itemcoordinates[obj] - - # And see if we can put that room away into storage. - room = self.db.rooms[loc] - self._destroy_room(room)
- - -
[docs]class WildernessRoom(DefaultRoom): - """ - This is a single room inside the wilderness. This room provides a "view" - into the wilderness map. When an account moves around, instead of going to - another room as with traditional rooms, they stay in the same room but the - room itself changes to display another area of the wilderness. - """ - - @property - def wilderness(self): - """ - Shortcut property to the wilderness script this room belongs to. - - Returns: - WildernessScript: the WildernessScript attached to this room - """ - return self.ndb.wildernessscript - - @property - def location_name(self): - """ - Returns the name of the wilderness at this room's coordinates. - - Returns: - name (str) - """ - return self.wilderness.mapprovider.get_location_name(self.coordinates) - - @property - def coordinates(self): - """ - Returns the coordinates of this room into the wilderness. - - Returns: - tuple: (x, y) coordinates of where this room is inside the - wilderness. - """ - return self.ndb.active_coordinates - -
[docs] def at_object_receive(self, moved_obj, source_location): - """ - Called after an object has been moved into this object. This is a - default Evennia hook. - - Args: - moved_obj (Object): The object moved into this one. - source_location (Object): Where `moved_obj` came from. - """ - if isinstance(moved_obj, WildernessExit): - # Ignore exits looping back to themselves: those are the regular - # n, ne, ... exits. - return - - itemcoords = self.wilderness.db.itemcoordinates - if moved_obj in itemcoords: - # This object was already in the wilderness. We need to make sure - # it goes to the correct room it belongs to. - # Otherwise the following issue can come up: - # 1) Player 1 and Player 2 share a room - # 2) Player 1 disconnects - # 3) Player 2 moves around - # 4) Player 1 reconnects - # Player 1 will end up in player 2's room, which has the wrong - # coordinates - - coordinates = itemcoords[moved_obj] - # Setting the location to None is important here so that we always - # get a "fresh" room - moved_obj.location = None - self.wilderness.move_obj(moved_obj, coordinates) - else: - # This object wasn't in the wilderness yet. Let's add it. - itemcoords[moved_obj] = self.coordinates
- -
[docs] def at_object_leave(self, moved_obj, target_location): - """ - Called just before an object leaves from inside this object. This is a - default Evennia hook. - - Args: - moved_obj (Object): The object leaving - target_location (Object): Where `moved_obj` is going. - - """ - self.wilderness.at_after_object_leave(moved_obj)
- -
[docs] def set_active_coordinates(self, new_coordinates, obj): - """ - Changes this room to show the wilderness map from other coordinates. - - Args: - new_coordinates (tuple): coordinates as tuple of (x, y) - obj (Object): the object that moved into this room and caused the - coordinates to change - """ - # Remove the reference for the old coordinates... - rooms = self.wilderness.db.rooms - del rooms[self.coordinates] - # ...and add it for the new coordinates. - self.ndb.active_coordinates = new_coordinates - rooms[self.coordinates] = self - - # Every obj inside this room will get its location set to None - for item in self.contents: - if not item.destination or item.destination != item.location: - item.location = None - # And every obj matching the new coordinates will get its location set - # to this room - for item in self.wilderness.get_objs_at_coordinates(new_coordinates): - item.location = self - - # Fix the lockfuncs for the exit so we can't go where we're not - # supposed to go - for exit in self.exits: - if exit.destination != self: - continue - x, y = get_new_coordinates(new_coordinates, exit.key) - valid = self.wilderness.is_valid_coordinates((x, y)) - - if valid: - exit.locks.add("traverse:true();view:true()") - else: - exit.locks.add("traverse:false();view:false()") - - # Finally call the at_prepare_room hook to give a chance to further - # customise it - self.wilderness.mapprovider.at_prepare_room(new_coordinates, obj, self)
- -
[docs] def get_display_name(self, looker, **kwargs): - """ - Displays the name of the object in a viewer-aware manner. - - Args: - looker (TypedObject): The object or account that is looking - at/getting inforamtion for this object. - - Returns: - name (str): A string containing the name of the object, - including the DBREF if this user is privileged to control - said object and also its coordinates into the wilderness map. - - Notes: - This function could be extended to change how object names - appear to users in character, but be wary. This function - does not change an object's keys or aliases when - searching, and is expected to produce something useful for - builders. - """ - if self.locks.check_lockstring(looker, "perm(Builder)"): - name = "{}(#{})".format(self.location_name, self.id) - else: - name = self.location_name - - name += " {0}".format(self.coordinates) - return name
- - -
[docs]class WildernessExit(DefaultExit): - """ - This is an Exit object used inside a WildernessRoom. Instead of changing - the location of an Object traversing through it (like a traditional exit - would do) it changes the coordinates of that traversing Object inside - the wilderness map. - """ - - @property - def wilderness(self): - """ - Shortcut property to the wilderness script. - - Returns: - WildernessScript: the WildernessScript attached to this exit's room - """ - return self.location.wilderness - - @property - def mapprovider(self): - """ - Shortcut property to the map provider. - - Returns: - MapProvider object: the mapprovider object used with this - wilderness map. - """ - return self.wilderness.mapprovider - -
[docs] def at_traverse_coordinates(self, traversing_object, current_coordinates, new_coordinates): - """ - Called when an object wants to travel from one place inside the - wilderness to another place inside the wilderness. - - If this returns True, then the traversing can happen. Otherwise it will - be blocked. - - This method is similar how the `at_traverse` works on normal exits. - - Args: - traversing_object (Object): The object doing the travelling. - current_coordinates (tuple): (x, y) coordinates where - `traversing_object` currently is. - new_coordinates (tuple): (x, y) coordinates of where - `traversing_object` wants to travel to. - - Returns: - bool: True if traversing_object is allowed to traverse - """ - return True
- -
[docs] def at_traverse(self, traversing_object, target_location): - """ - This implements the actual traversal. The traverse lock has - already been checked (in the Exit command) at this point. - - Args: - traversing_object (Object): Object traversing us. - target_location (Object): Where target is going. - - Returns: - bool: True if the traverse is allowed to happen - - """ - itemcoordinates = self.location.wilderness.db.itemcoordinates - - current_coordinates = itemcoordinates[traversing_object] - new_coordinates = get_new_coordinates(current_coordinates, self.key) - - if not self.at_traverse_coordinates( - traversing_object, current_coordinates, new_coordinates - ): - return False - - if not traversing_object.at_before_move(None): - return False - traversing_object.location.msg_contents( - "{} leaves to {}".format(traversing_object.key, new_coordinates), - exclude=[traversing_object], - ) - - self.location.wilderness.move_obj(traversing_object, new_coordinates) - - traversing_object.location.msg_contents( - "{} arrives from {}".format(traversing_object.key, current_coordinates), - exclude=[traversing_object], - ) - - traversing_object.at_after_move(None) - return True
- - -
[docs]class WildernessMapProvider(object): - """ - Default Wilderness Map provider. - - This is a simple provider that just creates an infinite large grid area. - """ - - room_typeclass = WildernessRoom - exit_typeclass = WildernessExit - -
[docs] def is_valid_coordinates(self, wilderness, coordinates): - """Returns True if coordinates is valid and can be walked to. - - Args: - wilderness: the wilderness script - coordinates (tuple): the coordinates to check as (x, y) tuple. - - Returns: - bool: True if the coordinates are valid - """ - x, y = coordinates - if x < 0: - return False - if y < 0: - return False - - return True
- -
[docs] def get_location_name(self, coordinates): - """ - Returns a name for the position at coordinates. - - Args: - coordinates (tuple): the coordinates as (x, y) tuple. - - Returns: - name (str) - """ - return "The wilderness"
- -
[docs] def at_prepare_room(self, coordinates, caller, room): - """ - Called when a room gets activated for certain coordinates. This happens - after every object is moved in it. - This can be used to set a custom room desc for instance or run other - customisations on the room. - - Args: - coordinates (tuple): the coordinates as (x, y) where room is - located at - caller (Object): the object that moved into this room - room (WildernessRoom): the room object that will be used at that - wilderness location - Example: - An example use of this would to plug in a randomizer to show different - descriptions for different coordinates, or place a treasure at a special - coordinate. - """ - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/help/admin.html b/docs/0.9.5/_modules/evennia/help/admin.html deleted file mode 100644 index 3e53237ac6..0000000000 --- a/docs/0.9.5/_modules/evennia/help/admin.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - - - evennia.help.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.help.admin

-"""
-This defines how to edit help entries in Admin.
-"""
-from django import forms
-from django.contrib import admin
-from evennia.help.models import HelpEntry
-from evennia.typeclasses.admin import TagInline
-
-
-
[docs]class HelpTagInline(TagInline): - model = HelpEntry.db_tags.through - related_field = "helpentry"
- - -
[docs]class HelpEntryForm(forms.ModelForm): - "Defines how to display the help entry" - -
[docs] class Meta(object): - model = HelpEntry - fields = "__all__"
- - db_help_category = forms.CharField( - label="Help category", initial="General", help_text="organizes help entries in lists" - ) - db_lock_storage = forms.CharField( - label="Locks", - initial="view:all()", - required=False, - widget=forms.TextInput(attrs={"size": "40"}), - )
- - -
[docs]class HelpEntryAdmin(admin.ModelAdmin): - "Sets up the admin manaager for help entries" - inlines = [HelpTagInline] - list_display = ("id", "db_key", "db_help_category", "db_lock_storage") - list_display_links = ("id", "db_key") - search_fields = ["^db_key", "db_entrytext"] - ordering = ["db_help_category", "db_key"] - save_as = True - save_on_top = True - list_select_related = True - - form = HelpEntryForm - fieldsets = ( - ( - None, - { - "fields": (("db_key", "db_help_category"), "db_entrytext", "db_lock_storage"), - "description": "Sets a Help entry. Set lock to <i>view:all()</I> unless you want to restrict it.", - }, - ), - )
- - -admin.site.register(HelpEntry, HelpEntryAdmin) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/help/manager.html b/docs/0.9.5/_modules/evennia/help/manager.html deleted file mode 100644 index 3fa8c502aa..0000000000 --- a/docs/0.9.5/_modules/evennia/help/manager.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - - - - evennia.help.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.help.manager

-"""
-Custom manager for HelpEntry objects.
-"""
-from django.db import models
-from evennia.utils import logger, utils
-from evennia.typeclasses.managers import TypedObjectManager
-
-__all__ = ("HelpEntryManager",)
-
-
-
[docs]class HelpEntryManager(TypedObjectManager): - """ - This HelpEntryManager implements methods for searching - and manipulating HelpEntries directly from the database. - - These methods will all return database objects - (or QuerySets) directly. - - Evennia-specific: - find_topicmatch - find_apropos - find_topicsuggestions - find_topics_with_category - all_to_category - search_help (equivalent to evennia.search_helpentry) - - """ - -
[docs] def find_topicmatch(self, topicstr, exact=False): - """ - Searches for matching topics or aliases based on player's - input. - - Args: - topcistr (str): Help topic to search for. - exact (bool, optional): Require exact match - (non-case-sensitive). If `False` (default), match - sub-parts of the string. - - Returns: - matches (HelpEntries): Query results. - - """ - dbref = utils.dbref(topicstr) - if dbref: - return self.filter(id=dbref) - topics = self.filter(db_key__iexact=topicstr) - if not topics: - topics = self.get_by_alias(topicstr) - if not topics and not exact: - topics = self.filter(db_key__istartswith=topicstr) - if not topics: - topics = self.filter(db_key__icontains=topicstr) - return topics
- -
[docs] def find_apropos(self, topicstr): - """ - Do a very loose search, returning all help entries containing - the search criterion in their titles. - - Args: - topicstr (str): Search criterion. - - Returns: - matches (HelpEntries): Query results. - - """ - return self.filter(db_key__icontains=topicstr)
- -
[docs] def find_topicsuggestions(self, topicstr): - """ - Do a fuzzy match, preferably within the category of the - current topic. - - Args: - topicstr (str): Search criterion. - - Returns: - matches (Helpentries): Query results. - - """ - return self.filter(db_key__icontains=topicstr).exclude(db_key__iexact=topicstr)
- -
[docs] def find_topics_with_category(self, help_category): - """ - Search topics having a particular category. - - Args: - help_category (str): Category query criterion. - - Returns: - matches (HelpEntries): Query results. - - """ - return self.filter(db_help_category__iexact=help_category)
- -
[docs] def get_all_topics(self): - """ - Get all topics. - - Returns: - all (HelpEntries): All topics. - - """ - return self.all()
- -
[docs] def get_all_categories(self): - """ - Return all defined category names with at least one topic in - them. - - Returns: - matches (list): Unique list of category names across all - topics. - - """ - return list(set(topic.help_category for topic in self.all()))
- -
[docs] def all_to_category(self, default_category): - """ - Shifts all help entries in database to default_category. This - action cannot be reverted. It is used primarily by the engine - when importing a default help database, making sure this ends - up in one easily separated category. - - Args: - default_category (str): Category to move entries to. - - """ - topics = self.all() - for topic in topics: - topic.help_category = default_category - topic.save() - string = _("Help database moved to category {default_category}").format( - default_category=default_category - ) - logger.log_info(string)
- -
[docs] def search_help(self, ostring, help_category=None): - """ - Retrieve a search entry object. - - Args: - ostring (str): The help topic to look for. - category (str): Limit the search to a particular help topic - - """ - ostring = ostring.strip().lower() - if help_category: - return self.filter(db_key__iexact=ostring, db_help_category__iexact=help_category) - else: - return self.filter(db_key__iexact=ostring)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/help/models.html b/docs/0.9.5/_modules/evennia/help/models.html deleted file mode 100644 index cf14edab15..0000000000 --- a/docs/0.9.5/_modules/evennia/help/models.html +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - - - evennia.help.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.help.models

-"""
-Models for the help system.
-
-The database-tied help system is only half of Evennia's help
-functionality, the other one being the auto-generated command help
-that is created on the fly from each command's `__doc__` string. The
-persistent database system defined here is intended for all other
-forms of help that do not concern commands, like information about the
-game world, policy info, rules and similar.
-
-"""
-from django.contrib.contenttypes.models import ContentType
-from django.db import models
-from django.urls import reverse
-from django.utils.text import slugify
-
-from evennia.utils.idmapper.models import SharedMemoryModel
-from evennia.help.manager import HelpEntryManager
-from evennia.typeclasses.models import Tag, TagHandler, AliasHandler
-from evennia.locks.lockhandler import LockHandler
-from evennia.utils.utils import lazy_property
-
-__all__ = ("HelpEntry",)
-
-
-# ------------------------------------------------------------
-#
-# HelpEntry
-#
-# ------------------------------------------------------------
-
-
-
[docs]class HelpEntry(SharedMemoryModel): - """ - A generic help entry. - - An HelpEntry object has the following properties defined: - key - main name of entry - help_category - which category entry belongs to (defaults to General) - entrytext - the actual help text - permissions - perm strings - - Method: - access - - """ - - # - # HelpEntry Database Model setup - # - # - # These database fields are all set using their corresponding properties, - # named same as the field, but withtout the db_* prefix. - - # title of the help entry - db_key = models.CharField( - "help key", max_length=255, unique=True, help_text="key to search for" - ) - # help category - db_help_category = models.CharField( - "help category", - max_length=255, - default="General", - help_text="organizes help entries in lists", - ) - # the actual help entry text, in any formatting. - db_entrytext = models.TextField( - "help entry", blank=True, help_text="the main body of help text" - ) - # lock string storage - db_lock_storage = models.TextField("locks", blank=True, help_text="normally view:all().") - # tags are primarily used for permissions - db_tags = models.ManyToManyField( - Tag, - blank=True, - help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.", - ) - # (deprecated, only here to allow MUX helpfile load (don't use otherwise)). - # TODO: remove this when not needed anymore. - db_staff_only = models.BooleanField(default=False) - - # Database manager - objects = HelpEntryManager() - _is_deleted = False - - # lazy-loaded handlers - -
[docs] @lazy_property - def locks(self): - return LockHandler(self)
- -
[docs] @lazy_property - def tags(self): - return TagHandler(self)
- -
[docs] @lazy_property - def aliases(self): - return AliasHandler(self)
- - class Meta(object): - "Define Django meta options" - verbose_name = "Help Entry" - verbose_name_plural = "Help Entries" - - # - # - # HelpEntry main class methods - # - # - - def __str__(self): - return self.key - - def __repr__(self): - return "%s" % self.key - -
[docs] def access(self, accessing_obj, access_type="read", default=False): - """ - Determines if another object has permission to access. - accessing_obj - object trying to access this one - access_type - type of access sought - default - what to return if no lock of access_type was found - """ - return self.locks.check(accessing_obj, access_type=access_type, default=default)
- - # - # Web/Django methods - # - -
[docs] def web_get_admin_url(self): - """ - Returns the URI path for the Django Admin page for this object. - - ex. Account#1 = '/admin/accounts/accountdb/1/change/' - - Returns: - path (str): URI path to Django Admin page for object. - - """ - content_type = ContentType.objects.get_for_model(self.__class__) - return reverse( - "admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,) - )
- -
[docs] @classmethod - def web_get_create_url(cls): - """ - Returns the URI path for a View that allows users to create new - instances of this object. - - ex. Chargen = '/characters/create/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-create' would be referenced by this method. - - ex. - url(r'characters/create/', ChargenView.as_view(), name='character-create') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can create new objects is the - developer's responsibility. - - Returns: - path (str): URI path to object creation page, if defined. - - """ - try: - return reverse("%s-create" % slugify(cls._meta.verbose_name)) - except: - return "#"
- -
[docs] def web_get_detail_url(self): - """ - Returns the URI path for a View that allows users to view details for - this object. - - ex. Oscar (Character) = '/characters/oscar/1/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-detail' would be referenced by this method. - - ex. - url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', - CharDetailView.as_view(), name='character-detail') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can view this object is the developer's - responsibility. - - Returns: - path (str): URI path to object detail page, if defined. - - """ - try: - return reverse( - "%s-detail" % slugify(self._meta.verbose_name), - kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)}, - ) - except Exception as e: - print(e) - return "#"
- -
[docs] def web_get_update_url(self): - """ - Returns the URI path for a View that allows users to update this - object. - - ex. Oscar (Character) = '/characters/oscar/1/change/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-update' would be referenced by this method. - - ex. - url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$', - CharUpdateView.as_view(), name='character-update') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can modify objects is the developer's - responsibility. - - Returns: - path (str): URI path to object update page, if defined. - - """ - try: - return reverse( - "%s-update" % slugify(self._meta.verbose_name), - kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)}, - ) - except: - return "#"
- -
[docs] def web_get_delete_url(self): - """ - Returns the URI path for a View that allows users to delete this object. - - ex. Oscar (Character) = '/characters/oscar/1/delete/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-detail' would be referenced by this method. - - ex. - url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$', - CharDeleteView.as_view(), name='character-delete') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can delete this object is the developer's - responsibility. - - Returns: - path (str): URI path to object deletion page, if defined. - - """ - try: - return reverse( - "%s-delete" % slugify(self._meta.verbose_name), - kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)}, - ) - except: - return "#"
- - # Used by Django Sites/Admin - get_absolute_url = web_get_detail_url
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/locks/lockfuncs.html b/docs/0.9.5/_modules/evennia/locks/lockfuncs.html deleted file mode 100644 index 210ae3a318..0000000000 --- a/docs/0.9.5/_modules/evennia/locks/lockfuncs.html +++ /dev/null @@ -1,809 +0,0 @@ - - - - - - - - evennia.locks.lockfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.locks.lockfuncs

-"""
-This module provides a set of permission lock functions for use
-with Evennia's permissions system.
-
-To call these locks, make sure this module is included in the
-settings tuple `PERMISSION_FUNC_MODULES` then define a lock on the form
-'<access_type>:func(args)' and add it to the object's lockhandler.
-Run the `access()` method of the handler to execute the lock check.
-
-Note that `accessing_obj` and `accessed_obj` can be any object type
-with a lock variable/field, so be careful to not expect
-a certain object type.
-
-
-**Appendix: MUX locks**
-
-Below is a list nicked from the MUX help file on the locks available
-in standard MUX. Most of these are not relevant to core Evennia since
-locks in Evennia are considerably more flexible and can be implemented
-on an individual command/typeclass basis rather than as globally
-available like the MUX ones. So many of these are not available in
-basic Evennia, but could all be implemented easily if needed for the
-individual game.
-
-```
-MUX Name:      Affects:        Effect:
-----------------------------------------------------------------------
-DefaultLock:   Exits:          controls who may traverse the exit to
-                               its destination.
-                                 Evennia: "traverse:<lockfunc()>"
-               Rooms:          controls whether the account sees the
-                               SUCC or FAIL message for the room
-                               following the room description when
-                               looking at the room.
-                                 Evennia: Custom typeclass
-               Accounts/Things: controls who may GET the object.
-                                 Evennia: "get:<lockfunc()"
- EnterLock:    Accounts/Things: controls who may ENTER the object
-                                 Evennia:
- GetFromLock:  All but Exits:  controls who may gets things from a
-                               given location.
-                                 Evennia:
- GiveLock:     Accounts/Things: controls who may give the object.
-                                 Evennia:
- LeaveLock:    Accounts/Things: controls who may LEAVE the object.
-                                 Evennia:
- LinkLock:     All but Exits:  controls who may link to the location
-                               if the location is LINK_OK (for linking
-                               exits or setting drop-tos) or ABODE (for
-                               setting homes)
-                                 Evennia:
- MailLock:     Accounts:        controls who may @mail the account.
-                               Evennia:
- OpenLock:     All but Exits:  controls who may open an exit.
-                                 Evennia:
- PageLock:     Accounts:        controls who may page the account.
-                                 Evennia: "send:<lockfunc()>"
- ParentLock:   All:            controls who may make @parent links to
-                               the object.
-                                 Evennia: Typeclasses and
-                               "puppet:<lockstring()>"
- ReceiveLock:  Accounts/Things: controls who may give things to the
-                               object.
-                                 Evennia:
- SpeechLock:   All but Exits:  controls who may speak in that location
-                                 Evennia:
- TeloutLock:   All but Exits:  controls who may teleport out of the
-                               location.
-                                 Evennia:
- TportLock:    Rooms/Things:   controls who may teleport there
-                                 Evennia:
- UseLock:      All but Exits:  controls who may USE the object, GIVE
-                               the object money and have the PAY
-                               attributes run, have their messages
-                               heard and possibly acted on by LISTEN
-                               and AxHEAR, and invoke $-commands
-                               stored on the object.
-                                 Evennia: Commands and Cmdsets.
- DropLock:     All but rooms:  controls who may drop that object.
-                                 Evennia:
- VisibleLock:  All:            Controls object visibility when the
-                               object is not dark and the looker
-                               passes the lock. In DARK locations, the
-                               object must also be set LIGHT and the
-                               viewer must pass the VisibleLock.
-                                 Evennia: Room typeclass with
-                                          Dark/light script
-```
-"""
-
-
-from ast import literal_eval
-from django.conf import settings
-from evennia.utils import utils
-
-_PERMISSION_HIERARCHY = [pe.lower() for pe in settings.PERMISSION_HIERARCHY]
-# also accept different plural forms
-_PERMISSION_HIERARCHY_PLURAL = [
-    pe + "s" if not pe.endswith("s") else pe for pe in _PERMISSION_HIERARCHY
-]
-
-
-def _to_account(accessing_obj):
-    "Helper function. Makes sure an accessing object is an account object"
-    if utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject"):
-        # an object. Convert to account.
-        accessing_obj = accessing_obj.account
-    return accessing_obj
-
-
-# lock functions
-
-
-
[docs]def true(*args, **kwargs): - "Always returns True." - return True
- - -
[docs]def all(*args, **kwargs): - return True
- - -
[docs]def false(*args, **kwargs): - "Always returns False" - return False
- - -
[docs]def none(*args, **kwargs): - return False
- - -
[docs]def self(accessing_obj, accessed_obj, *args, **kwargs): - """ - Check if accessing_obj is the same as accessed_obj - - Usage: - self() - - This can be used to lock specifically only to - the same object that the lock is defined on. - """ - return accessing_obj == accessed_obj
- - -
[docs]def perm(accessing_obj, accessed_obj, *args, **kwargs): - """ - The basic permission-checker. Ignores case. - - Usage: - perm(<permission>) - - where <permission> is the permission accessing_obj must - have in order to pass the lock. - - If the given permission is part of settings.PERMISSION_HIERARCHY, - permission is also granted to all ranks higher up in the hierarchy. - - If accessing_object is an Object controlled by an Account, the - permissions of the Account is used unless the Attribute _quell - is set to True on the Object. In this case however, the - LOWEST hieararcy-permission of the Account/Object-pair will be used - (this is order to avoid Accounts potentially escalating their own permissions - by use of a higher-level Object) - - """ - # this allows the perm_above lockfunc to make use of this function too - try: - permission = args[0].lower() - perms_object = accessing_obj.permissions.all() - except (AttributeError, IndexError): - return False - - gtmode = kwargs.pop("_greater_than", False) - is_quell = False - - account = ( - utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject") - and accessing_obj.account - ) - # check object perms (note that accessing_obj could be an Account too) - perms_account = [] - if account: - perms_account = account.permissions.all() - is_quell = account.attributes.get("_quell") - - # Check hirarchy matches; handle both singular/plural forms in hierarchy - hpos_target = None - if permission in _PERMISSION_HIERARCHY: - hpos_target = _PERMISSION_HIERARCHY.index(permission) - if permission.endswith("s") and permission[:-1] in _PERMISSION_HIERARCHY: - hpos_target = _PERMISSION_HIERARCHY.index(permission[:-1]) - if hpos_target is not None: - # hieratchy match - hpos_account = -1 - hpos_object = -1 - - if account: - # we have an account puppeting this object. We must check what perms it has - perms_account_single = [p[:-1] if p.endswith("s") else p for p in perms_account] - hpos_account = [ - hpos - for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) - if hperm in perms_account_single - ] - hpos_account = hpos_account and hpos_account[-1] or -1 - - if not account or is_quell: - # only get the object-level perms if there is no account or quelling - perms_object_single = [p[:-1] if p.endswith("s") else p for p in perms_object] - hpos_object = [ - hpos - for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) - if hperm in perms_object_single - ] - hpos_object = hpos_object and hpos_object[-1] or -1 - - if account and is_quell: - # quell mode: use smallest perm from account and object - if gtmode: - return hpos_target < min(hpos_account, hpos_object) - else: - return hpos_target <= min(hpos_account, hpos_object) - elif account: - # use account perm - if gtmode: - return hpos_target < hpos_account - else: - return hpos_target <= hpos_account - else: - # use object perm - if gtmode: - return hpos_target < hpos_object - else: - return hpos_target <= hpos_object - else: - # no hierarchy match - check direct matches - if account: - # account exists, check it first unless quelled - if is_quell and permission in perms_object: - return True - elif permission in perms_account: - return True - elif permission in perms_object: - return True - - return False
- - -
[docs]def perm_above(accessing_obj, accessed_obj, *args, **kwargs): - """ - Only allow objects with a permission *higher* in the permission - hierarchy than the one given. If there is no such higher rank, - it's assumed we refer to superuser. If no hierarchy is defined, - this function has no meaning and returns False. - """ - kwargs["_greater_than"] = True - return perm(accessing_obj, accessed_obj, *args, **kwargs)
- - -
[docs]def pperm(accessing_obj, accessed_obj, *args, **kwargs): - """ - The basic permission-checker only for Account objects. Ignores case. - - Usage: - pperm(<permission>) - - where <permission> is the permission accessing_obj must - have in order to pass the lock. If the given permission - is part of _PERMISSION_HIERARCHY, permission is also granted - to all ranks higher up in the hierarchy. - """ - return perm(_to_account(accessing_obj), accessed_obj, *args, **kwargs)
- - -
[docs]def pperm_above(accessing_obj, accessed_obj, *args, **kwargs): - """ - Only allow Account objects with a permission *higher* in the permission - hierarchy than the one given. If there is no such higher rank, - it's assumed we refer to superuser. If no hierarchy is defined, - this function has no meaning and returns False. - """ - return perm_above(_to_account(accessing_obj), accessed_obj, *args, **kwargs)
- - -
[docs]def dbref(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - dbref(3) - - This lock type checks if the checking object - has a particular dbref. Note that this only - works for checking objects that are stored - in the database (e.g. not for commands) - """ - if not args: - return False - try: - dbr = int(args[0].strip().strip("#")) - except ValueError: - return False - if hasattr(accessing_obj, "dbid"): - return dbr == accessing_obj.dbid - return False
- - -
[docs]def pdbref(accessing_obj, accessed_obj, *args, **kwargs): - """ - Same as dbref, but making sure accessing_obj is an account. - """ - return dbref(_to_account(accessing_obj), accessed_obj, *args, **kwargs)
- - -
[docs]def id(accessing_obj, accessed_obj, *args, **kwargs): - "Alias to dbref" - return dbref(accessing_obj, accessed_obj, *args, **kwargs)
- - -
[docs]def pid(accessing_obj, accessed_obj, *args, **kwargs): - "Alias to dbref, for Accounts" - return dbref(_to_account(accessing_obj), accessed_obj, *args, **kwargs)
- - -# this is more efficient than multiple if ... elif statments -CF_MAPPING = { - "eq": lambda val1, val2: val1 == val2 or str(val1) == str(val2) or float(val1) == float(val2), - "gt": lambda val1, val2: float(val1) > float(val2), - "lt": lambda val1, val2: float(val1) < float(val2), - "ge": lambda val1, val2: float(val1) >= float(val2), - "le": lambda val1, val2: float(val1) <= float(val2), - "ne": lambda val1, val2: float(val1) != float(val2), - "default": lambda val1, val2: False, -} - - -
[docs]def attr(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - attr(attrname) - attr(attrname, value) - attr(attrname, value, compare=type) - - where compare's type is one of (eq,gt,lt,ge,le,ne) and signifies - how the value should be compared with one on accessing_obj (so - compare=gt means the accessing_obj must have a value greater than - the one given). - - Searches attributes *and* properties stored on the accessing_obj. - if accessing_obj has a property "obj", then this is used as - accessing_obj (this makes this usable for Commands too) - - The first form works like a flag - if the attribute/property - exists on the object, the value is checked for True/False. The - second form also requires that the value of the attribute/property - matches. Note that all retrieved values will be converted to - strings before doing the comparison. - """ - # deal with arguments - if not args: - return False - attrname = args[0].strip() - value = None - if len(args) > 1: - value = args[1].strip() - compare = "eq" - if kwargs: - compare = kwargs.get("compare", "eq") - - def valcompare(val1, val2, typ="eq"): - "compare based on type" - try: - return CF_MAPPING.get(typ, CF_MAPPING["default"])(val1, val2) - except Exception: - # this might happen if we try to compare two things that - # cannot be compared - return False - - if hasattr(accessing_obj, "obj"): - # NOTE: this is relevant for Commands. It may clash with scripts - # (they have Attributes and .obj) , but are scripts really - # used so that one ever wants to check the property on the - # Script rather than on its owner? - accessing_obj = accessing_obj.obj - - # first, look for normal properties on the object trying to gain access - if hasattr(accessing_obj, attrname): - if value: - return valcompare(str(getattr(accessing_obj, attrname)), value, compare) - # will return Fail on False value etc - return bool(getattr(accessing_obj, attrname)) - # check attributes, if they exist - if hasattr(accessing_obj, "attributes") and accessing_obj.attributes.has(attrname): - if value: - return hasattr(accessing_obj, "attributes") and valcompare( - accessing_obj.attributes.get(attrname), value, compare - ) - # fails on False/None values - return bool(accessing_obj.attributes.get(attrname)) - return False
- - -
[docs]def objattr(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - objattr(attrname) - objattr(attrname, value) - objattr(attrname, value, compare=type) - - Works like attr, except it looks for an attribute on - accessed_obj instead. - - """ - return attr(accessed_obj, accessed_obj, *args, **kwargs)
- - -
[docs]def locattr(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - locattr(attrname) - locattr(attrname, value) - locattr(attrname, value, compare=type) - - Works like attr, except it looks for an attribute on - accessing_obj.location, if such an entity exists. - - if accessing_obj has a property ".obj" (such as is the case for a - Command), then accessing_obj.obj.location is used instead. - - """ - if hasattr(accessing_obj, "obj"): - accessing_obj = accessing_obj.obj - if hasattr(accessing_obj, "location"): - return attr(accessing_obj.location, accessed_obj, *args, **kwargs) - return False
- - -
[docs]def objlocattr(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - locattr(attrname) - locattr(attrname, value) - locattr(attrname, value, compare=type) - - Works like attr, except it looks for an attribute on - accessed_obj.location, if such an entity exists. - - if accessed_obj has a property ".obj" (such as is the case for a - Command), then accessing_obj.obj.location is used instead. - - """ - if hasattr(accessed_obj, "obj"): - accessed_obj = accessed_obj.obj - if hasattr(accessed_obj, "location"): - return attr(accessed_obj.location, accessed_obj, *args, **kwargs) - return False
- - -
[docs]def attr_eq(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - attr_gt(attrname, 54) - """ - return attr(accessing_obj, accessed_obj, *args, **kwargs)
- - -
[docs]def attr_gt(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - attr_gt(attrname, 54) - - Only true if access_obj's attribute > the value given. - """ - return attr(accessing_obj, accessed_obj, *args, **{"compare": "gt"})
- - -
[docs]def attr_ge(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - attr_gt(attrname, 54) - - Only true if access_obj's attribute >= the value given. - """ - return attr(accessing_obj, accessed_obj, *args, **{"compare": "ge"})
- - -
[docs]def attr_lt(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - attr_gt(attrname, 54) - - Only true if access_obj's attribute < the value given. - """ - return attr(accessing_obj, accessed_obj, *args, **{"compare": "lt"})
- - -
[docs]def attr_le(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - attr_gt(attrname, 54) - - Only true if access_obj's attribute <= the value given. - """ - return attr(accessing_obj, accessed_obj, *args, **{"compare": "le"})
- - -
[docs]def attr_ne(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - attr_gt(attrname, 54) - - Only true if access_obj's attribute != the value given. - """ - return attr(accessing_obj, accessed_obj, *args, **{"compare": "ne"})
- - -
[docs]def tag(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - tag(tagkey) - tag(tagkey, category) - - Only true if accessing_obj has the specified tag and optional - category. - If accessing_obj has the ".obj" property (such as is the case for - a command), then accessing_obj.obj is used instead. - """ - if hasattr(accessing_obj, "obj"): - accessing_obj = accessing_obj.obj - tagkey = args[0] if args else None - category = args[1] if len(args) > 1 else None - return bool(accessing_obj.tags.get(tagkey, category=category))
- - -
[docs]def objtag(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - objtag(tagkey) - objtag(tagkey, category) - - Only true if accessed_obj has the specified tag and optional - category. - """ - if hasattr(accessed_obj, "obj"): - accessed_obj = accessed_obj.obj - tagkey = args[0] if args else None - category = args[1] if len(args) > 1 else None - return bool(accessed_obj.tags.get(tagkey, category=category))
- - -
[docs]def inside(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - inside() - - True if accessing_obj is 'inside' accessing_obj. Note that this only checks - one level down. So if if the lock is on a room, you will pass but not your - inventory (since their location is you, not the locked object). If you - want also nested objects to pass the lock, use the `insiderecursive` - lockfunc. - """ - if hasattr(accessed_obj, "obj"): - accessed_obj = accessed_obj.obj - return accessing_obj.location == accessed_obj
- - -
[docs]def inside_rec(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - inside_rec() - - True if accessing_obj is inside the accessed obj, at up to 10 levels - of recursion (so if this lock is on a room, then an object inside a box - in your inventory will also pass the lock). - """ - - if hasattr(accessed_obj, "obj"): - accessed_obj = accessed_obj.obj - - def _recursive_inside(obj, accessed_obj, lvl=1): - if obj.location: - if obj.location == accessed_obj: - return True - elif lvl >= 10: - # avoid infinite recursions - return False - else: - return _recursive_inside(obj.location, accessed_obj, lvl + 1) - return False - - return _recursive_inside(accessing_obj, accessed_obj)
- - -
[docs]def holds(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: - holds() checks if accessed_obj or accessed_obj.obj - is held by accessing_obj - holds(key/dbref) checks if accessing_obj holds an object - with given key/dbref - holds(attrname, value) checks if accessing_obj holds an - object with the given attrname and value - - This is passed if accessed_obj is carried by accessing_obj (that is, - accessed_obj.location == accessing_obj), or if accessing_obj itself holds - an object matching the given key. - """ - try: - # commands and scripts don't have contents, so we are usually looking - # for the contents of their .obj property instead (i.e. the object the - # command/script is attached to). - contents = accessing_obj.contents - except AttributeError: - try: - contents = accessing_obj.obj.contents - except AttributeError: - return False - - def check_holds(objid): - # helper function. Compares both dbrefs and keys/aliases. - objid = str(objid) - dbref = utils.dbref(objid, reqhash=False) - if dbref and any((True for obj in contents if obj.dbid == dbref)): - return True - objid = objid.lower() - return any( - ( - True - for obj in contents - if obj.key.lower() == objid or objid in [al.lower() for al in obj.aliases.all()] - ) - ) - - if not args: - # holds() - check if accessed_obj or accessed_obj.obj is held by accessing_obj - try: - if check_holds(accessed_obj.dbid): - return True - except Exception: - # we need to catch any trouble here - pass - return hasattr(accessed_obj, "obj") and check_holds(accessed_obj.obj.dbid) - if len(args) == 1: - # command is holds(dbref/key) - check if given objname/dbref is held by accessing_ob - return check_holds(args[0]) - elif len(args) > 1: - # command is holds(attrname, value) check if any held object has the given attribute and value - for obj in contents: - if obj.attributes.get(args[0]) == args[1]: - return True - return False
- - -
[docs]def superuser(*args, **kwargs): - """ - Only accepts an accesing_obj that is superuser (e.g. user #1) - - Since a superuser would not ever reach this check (superusers - bypass the lock entirely), any user who gets this far cannot be a - superuser, hence we just return False. :) - """ - return False
- - -
[docs]def has_account(accessing_obj, accessed_obj, *args, **kwargs): - """ - Only returns true if accessing_obj has_account is true, that is, - this is an account-controlled object. It fails on actual accounts! - - This is a useful lock for traverse-locking Exits to restrain NPC - mobiles from moving outside their areas. - """ - return hasattr(accessing_obj, "has_account") and accessing_obj.has_account
- - -
[docs]def serversetting(accessing_obj, accessed_obj, *args, **kwargs): - """ - Only returns true if the Evennia settings exists, alternatively has - a certain value. - - Usage: - serversetting(IRC_ENABLED) - serversetting(BASE_SCRIPT_PATH, ['types']) - - A given True/False or integers will be converted properly. Note that - everything will enter this function as strings, so they have to be - unpacked to their real value. We only support basic properties. - """ - if not args or not args[0]: - return False - if len(args) < 2: - setting = args[0] - val = "True" - else: - setting, val = args[0], args[1] - # convert - try: - val = literal_eval(val) - except Exception: - # we swallow errors here, lockfuncs has noone to report to - return False - - if setting in settings._wrapped.__dict__: - return settings._wrapped.__dict__[setting] == val - return False
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/locks/lockhandler.html b/docs/0.9.5/_modules/evennia/locks/lockhandler.html deleted file mode 100644 index 20fcdf03cc..0000000000 --- a/docs/0.9.5/_modules/evennia/locks/lockhandler.html +++ /dev/null @@ -1,863 +0,0 @@ - - - - - - - - evennia.locks.lockhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.locks.lockhandler

-"""
-A *lock* defines access to a particular subsystem or property of
-Evennia. For example, the "owner" property can be impmemented as a
-lock. Or the disability to lift an object or to ban users.
-
-
-A lock consists of three parts:
-
- - access_type - this defines what kind of access this lock regulates. This
-   just a string.
- - function call - this is one or many calls to functions that will determine
-   if the lock is passed or not.
- - lock function(s). These are regular python functions with a special
-   set of allowed arguments. They should always return a boolean depending
-   on if they allow access or not.
-
-A lock function is defined by existing in one of the modules
-listed by settings.LOCK_FUNC_MODULES. It should also always
-take four arguments looking like this:
-
-   funcname(accessing_obj, accessed_obj, *args, **kwargs):
-        [...]
-
-The accessing object is the object wanting to gain access.
-The accessed object is the object this lock resides on
-args and kwargs will hold optional arguments and/or keyword arguments
-to the function as a list and a dictionary respectively.
-
-Example:
-
-   perm(accessing_obj, accessed_obj, *args, **kwargs):
-       "Checking if the object has a particular, desired permission"
-       if args:
-           desired_perm = args[0]
-           return desired_perm in accessing_obj.permissions.all()
-       return False
-
-Lock functions should most often be pretty general and ideally possible to
-re-use and combine in various ways to build clever locks.
-
-
-
-Lock definition ("Lock string")
-
-A lock definition is a string with a special syntax. It is added to
-each object's lockhandler, making that lock available from then on.
-
-The lock definition looks like this:
-
- 'access_type:[NOT] func1(args)[ AND|OR][NOT] func2() ...'
-
-That is, the access_type, a colon followed by calls to lock functions
-combined with AND or OR. NOT negates the result of the following call.
-
-Example:
-
- We want to limit who may edit a particular object (let's call this access_type
-for 'edit', it depends on what the command is looking for). We want this to
-only work for those with the Permission 'Builder'. So we use our lock
-function above and define it like this:
-
-  'edit:perm(Builder)'
-
-Here, the lock-function perm() will be called with the string
-'Builder' (accessing_obj and accessed_obj are added automatically,
-you only need to add the args/kwargs, if any).
-
-If we wanted to make sure the accessing object was BOTH a Builder and a
-GoodGuy, we could use AND:
-
-  'edit:perm(Builder) AND perm(GoodGuy)'
-
-To allow EITHER Builder and GoodGuys, we replace AND with OR. perm() is just
-one example, the lock function can do anything and compare any properties of
-the calling object to decide if the lock is passed or not.
-
-  'lift:attrib(very_strong) AND NOT attrib(bad_back)'
-
-To make these work, add the string to the lockhandler of the object you want
-to apply the lock to:
-
-  obj.lockhandler.add('edit:perm(Builder)')
-
-From then on, a command that wants to check for 'edit' access on this
-object would do something like this:
-
-  if not target_obj.lockhandler.has_perm(caller, 'edit'):
-      caller.msg("Sorry, you cannot edit that.")
-
-All objects also has a shortcut called 'access' that is recommended to
-use instead:
-
-  if not target_obj.access(caller, 'edit'):
-      caller.msg("Sorry, you cannot edit that.")
-
-
-Permissions
-
-Permissions are just text strings stored in a comma-separated list on
-typeclassed objects. The default perm() lock function uses them,
-taking into account settings.PERMISSION_HIERARCHY. Also, the
-restricted @perm command sets them, but otherwise they are identical
-to any other identifier you can use.
-
-"""
-
-import re
-from django.conf import settings
-from evennia.utils import logger, utils
-from django.utils.translation import gettext as _
-
-__all__ = ("LockHandler", "LockException")
-
-WARNING_LOG = settings.LOCKWARNING_LOG_FILE
-_LOCK_HANDLER = None
-
-
-#
-# Exception class. This will be raised
-# by errors in lock definitions.
-#
-
-
-
[docs]class LockException(Exception): - """ - Raised during an error in a lock. - """ - - pass
- - -# -# Cached lock functions -# - -_LOCKFUNCS = {} - - -def _cache_lockfuncs(): - """ - Updates the cache. - """ - global _LOCKFUNCS - _LOCKFUNCS = {} - for modulepath in settings.LOCK_FUNC_MODULES: - _LOCKFUNCS.update(utils.callables_from_module(modulepath)) - - -# -# pre-compiled regular expressions -# - - -_RE_FUNCS = re.compile(r"\w+\([^)]*\)") -_RE_SEPS = re.compile(r"(?<=[ )])AND(?=\s)|(?<=[ )])OR(?=\s)|(?<=[ )])NOT(?=\s)") -_RE_OK = re.compile(r"%s|and|or|not") - - -# -# -# Lock handler -# -# - - -
[docs]class LockHandler(object): - """ - This handler should be attached to all objects implementing - permission checks, under the property 'lockhandler'. - - """ - -
[docs] def __init__(self, obj): - """ - Loads and pre-caches all relevant locks and their functions. - - Args: - obj (object): The object on which the lockhandler is - defined. - - """ - if not _LOCKFUNCS: - _cache_lockfuncs() - self.obj = obj - self.locks = {} - try: - self.reset() - except LockException as err: - logger.log_trace(err)
- - def __str__(self): - return ";".join(self.locks[key][2] for key in sorted(self.locks)) - - def _log_error(self, message): - "Try to log errors back to object" - raise LockException(message) - - def _parse_lockstring(self, storage_lockstring): - """ - Helper function. This is normally only called when the - lockstring is cached and does preliminary checking. locks are - stored as a string - - atype:[NOT] lock()[[ AND|OR [NOT] lock()[...]];atype... - - Args: - storage_locksring (str): The lockstring to parse. - - """ - locks = {} - if not storage_lockstring: - return locks - duplicates = 0 - elist = [] # errors - wlist = [] # warnings - for raw_lockstring in storage_lockstring.split(";"): - if not raw_lockstring: - continue - lock_funcs = [] - try: - access_type, rhs = (part.strip() for part in raw_lockstring.split(":", 1)) - except ValueError: - logger.log_trace() - return locks - - # parse the lock functions and separators - funclist = _RE_FUNCS.findall(rhs) - evalstring = rhs - for pattern in ("AND", "OR", "NOT"): - evalstring = re.sub(r"\b%s\b" % pattern, pattern.lower(), evalstring) - nfuncs = len(funclist) - for funcstring in funclist: - funcname, rest = (part.strip().strip(")") for part in funcstring.split("(", 1)) - func = _LOCKFUNCS.get(funcname, None) - if not callable(func): - elist.append(_("Lock: lock-function '%s' is not available.") % funcstring) - continue - args = list(arg.strip() for arg in rest.split(",") if arg and "=" not in arg) - kwargs = dict( - [ - (part.strip() for part in arg.split("=", 1)) - for arg in rest.split(",") - if arg and "=" in arg - ] - ) - lock_funcs.append((func, args, kwargs)) - evalstring = evalstring.replace(funcstring, "%s") - if len(lock_funcs) < nfuncs: - continue - try: - # purge the eval string of any superfluous items, then test it - evalstring = " ".join(_RE_OK.findall(evalstring)) - eval(evalstring % tuple(True for func in funclist), {}, {}) - except Exception: - elist.append( - _("Lock: definition '{lock_string}' has syntax errors.").format( - lock_string=raw_lockstring - ) - ) - continue - if access_type in locks: - duplicates += 1 - wlist.append( - _( - "LockHandler on %(obj)s: access type '%(access_type)s' changed from '%(source)s' to '%(goal)s' " - % { - "obj": self.obj, - "access_type": access_type, - "source": locks[access_type][2], - "goal": raw_lockstring, - } - ) - ) - locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring) - if wlist and WARNING_LOG: - # a warning text was set, it's not an error, so only report - logger.log_file("\n".join(wlist), WARNING_LOG) - if elist: - # an error text was set, raise exception. - raise LockException("\n".join(elist)) - # return the gathered locks in an easily executable form - return locks - - def _cache_locks(self, storage_lockstring): - """ - Store data - """ - self.locks = self._parse_lockstring(storage_lockstring) - - def _save_locks(self): - """ - Store locks to obj - """ - self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()]) - -
[docs] def cache_lock_bypass(self, obj): - """ - We cache superuser bypass checks here for efficiency. This - needs to be re-run when an account is assigned to a character. - We need to grant access to superusers. We need to check both - directly on the object (accounts), through obj.account and using - the get_account() method (this sits on serversessions, in some - rare cases where a check is done before the login process has - yet been fully finalized) - - Args: - obj (object): This is checked for the `is_superuser` property. - - """ - self.lock_bypass = hasattr(obj, "is_superuser") and obj.is_superuser
- -
[docs] def add(self, lockstring, validate_only=False): - """ - Add a new lockstring to handler. - - Args: - lockstring (str or list): A string on the form - `"<access_type>:<functions>"`. Multiple access types - should be separated by semicolon (`;`). Alternatively, - a list with lockstrings. - validate_only (bool, optional): If True, validate the lockstring but - don't actually store it. - Returns: - success (bool): The outcome of the addition, `False` on - error. If `validate_only` is True, this will be a tuple - (bool, error), for pass/fail and a string error. - - """ - if isinstance(lockstring, str): - lockdefs = lockstring.split(";") - else: - lockdefs = [lockdef for locks in lockstring for lockdef in locks.split(";")] - lockstring = ";".join(lockdefs) - - err = "" - # sanity checks - for lockdef in lockdefs: - if ":" not in lockdef: - err = _("Lock: '{lockdef}' contains no colon (:).").format(lockdef=lockdef) - if validate_only: - return False, err - else: - self._log_error(err) - return False - access_type, rhs = [part.strip() for part in lockdef.split(":", 1)] - if not access_type: - err = _( - "Lock: '{lockdef}' has no access_type " "(left-side of colon is empty)." - ).format(lockdef=lockdef) - if validate_only: - return False, err - else: - self._log_error(err) - return False - if rhs.count("(") != rhs.count(")"): - err = _("Lock: '{lockdef}' has mismatched parentheses.").format(lockdef=lockdef) - if validate_only: - return False, err - else: - self._log_error(err) - return False - if not _RE_FUNCS.findall(rhs): - err = _("Lock: '{lockdef}' has no valid lock functions.").format(lockdef=lockdef) - if validate_only: - return False, err - else: - self._log_error(err) - return False - if validate_only: - return True, None - # get the lock string - storage_lockstring = self.obj.lock_storage - if storage_lockstring: - storage_lockstring = storage_lockstring + ";" + lockstring - else: - storage_lockstring = lockstring - # cache the locks will get rid of eventual doublets - self._cache_locks(storage_lockstring) - self._save_locks() - return True
- -
[docs] def validate(self, lockstring): - """ - Validate lockstring syntactically, without saving it. - - Args: - lockstring (str): Lockstring to validate. - Returns: - valid (bool): If validation passed or not. - - """ - return self.add(lockstring, validate_only=True)
- -
[docs] def replace(self, lockstring): - """ - Replaces the lockstring entirely. - - Args: - lockstring (str): The new lock definition. - - Return: - success (bool): False if an error occurred. - - Raises: - LockException: If a critical error occurred. - If so, the old string is recovered. - - """ - old_lockstring = str(self) - self.clear() - try: - return self.add(lockstring) - except LockException: - self.add(old_lockstring) - raise
- -
[docs] def get(self, access_type=None): - """ - Get the full lockstring or the lockstring of a particular - access type. - - Args: - access_type (str, optional): - - Returns: - lockstring (str): The matched lockstring, or the full - lockstring if no access_type was given. - """ - - if access_type: - return self.locks.get(access_type, ["", "", ""])[2] - return str(self)
- -
[docs] def all(self): - """ - Return all lockstrings - - Returns: - lockstrings (list): All separate lockstrings - - """ - return str(self).split(";")
- -
[docs] def remove(self, access_type): - """ - Remove a particular lock from the handler - - Args: - access_type (str): The type of lock to remove. - - Returns: - success (bool): If the access_type was not found - in the lock, this returns `False`. - - """ - if access_type in self.locks: - del self.locks[access_type] - self._save_locks() - return True - return False
- - delete = remove # alias for historical reasons - -
[docs] def clear(self): - """ - Remove all locks in the handler. - - """ - self.locks = {} - self.lock_storage = "" - self._save_locks()
- -
[docs] def reset(self): - """ - Set the reset flag, so the the lock will be re-cached at next - checking. This is usually called by @reload. - - """ - self._cache_locks(self.obj.lock_storage) - self.cache_lock_bypass(self.obj)
- -
[docs] def append(self, access_type, lockstring, op="or"): - """ - Append a lock definition to access_type if it doesn't already exist. - - Args: - access_type (str): Access type. - lockstring (str): A valid lockstring, without the operator to - link it to an eventual existing lockstring. - op (str): An operator 'and', 'or', 'and not', 'or not' used - for appending the lockstring to an existing access-type. - Note: - The most common use of this method is for use in commands where - the user can specify their own lockstrings. This method allows - the system to auto-add things like Admin-override access. - - """ - old_lockstring = self.get(access_type) - if not lockstring.strip().lower() in old_lockstring.lower(): - lockstring = "{old} {op} {new}".format( - old=old_lockstring, op=op, new=lockstring.strip() - ) - self.add(lockstring)
- -
[docs] def check(self, accessing_obj, access_type, default=False, no_superuser_bypass=False): - """ - Checks a lock of the correct type by passing execution off to - the lock function(s). - - Args: - accessing_obj (object): The object seeking access. - access_type (str): The type of access wanted. - default (bool, optional): If no suitable lock type is - found, default to this result. - no_superuser_bypass (bool): Don't use this unless you - really, really need to, it makes supersusers susceptible - to the lock check. - - Notes: - A lock is executed in the follwoing way: - - Parsing the lockstring, we (during cache) extract the valid - lock functions and store their function objects in the right - order along with their args/kwargs. These are now executed in - sequence, creating a list of True/False values. This is put - into the evalstring, which is a string of AND/OR/NOT entries - separated by placeholders where each function result should - go. We just put those results in and evaluate the string to - get a final, combined True/False value for the lockstring. - - The important bit with this solution is that the full - lockstring is never blindly evaluated, and thus there (should - be) no way to sneak in malign code in it. Only "safe" lock - functions (as defined by your settings) are executed. - - """ - try: - # check if the lock should be bypassed (e.g. superuser status) - if accessing_obj.locks.lock_bypass and not no_superuser_bypass: - return True - except AttributeError: - # happens before session is initiated. - if not no_superuser_bypass and ( - (hasattr(accessing_obj, "is_superuser") and accessing_obj.is_superuser) - or ( - hasattr(accessing_obj, "account") - and hasattr(accessing_obj.account, "is_superuser") - and accessing_obj.account.is_superuser - ) - or ( - hasattr(accessing_obj, "get_account") - and ( - not accessing_obj.get_account() or accessing_obj.get_account().is_superuser - ) - ) - ): - return True - - # no superuser or bypass -> normal lock operation - if access_type in self.locks: - # we have a lock, test it. - evalstring, func_tup, raw_string = self.locks[access_type] - # execute all lock funcs in the correct order, producing a tuple of True/False results. - true_false = tuple( - bool(tup[0](accessing_obj, self.obj, *tup[1], **tup[2])) for tup in func_tup - ) - # the True/False tuple goes into evalstring, which combines them - # with AND/OR/NOT in order to get the final result. - return eval(evalstring % true_false) - else: - return default
- - def _eval_access_type(self, accessing_obj, locks, access_type): - """ - Helper method for evaluating the access type using eval(). - - Args: - accessing_obj (object): Object seeking access. - locks (dict): The pre-parsed representation of all access-types. - access_type (str): An access-type key to evaluate. - - """ - evalstring, func_tup, raw_string = locks[access_type] - true_false = tuple(tup[0](accessing_obj, self.obj, *tup[1], **tup[2]) for tup in func_tup) - return eval(evalstring % true_false) - -
[docs] def check_lockstring( - self, accessing_obj, lockstring, no_superuser_bypass=False, default=False, access_type=None - ): - """ - Do a direct check against a lockstring ('atype:func()..'), - without any intermediary storage on the accessed object. - - Args: - accessing_obj (object or None): The object seeking access. - Importantly, this can be left unset if the lock functions - don't access it, no updating or storage of locks are made - against this object in this method. - lockstring (str): Lock string to check, on the form - `"access_type:lock_definition"` where the `access_type` - part can potentially be set to a dummy value to just check - a lock condition. - no_superuser_bypass (bool, optional): Force superusers to heed lock. - default (bool, optional): Fallback result to use if `access_type` is set - but no such `access_type` is found in the given `lockstring`. - access_type (str, bool): If set, only this access_type will be looked up - among the locks defined by `lockstring`. - - Return: - access (bool): If check is passed or not. - - """ - try: - if accessing_obj.locks.lock_bypass and not no_superuser_bypass: - return True - except AttributeError: - if no_superuser_bypass and ( - (hasattr(accessing_obj, "is_superuser") and accessing_obj.is_superuser) - or ( - hasattr(accessing_obj, "account") - and hasattr(accessing_obj.account, "is_superuser") - and accessing_obj.account.is_superuser - ) - or ( - hasattr(accessing_obj, "get_account") - and ( - not accessing_obj.get_account() or accessing_obj.get_account().is_superuser - ) - ) - ): - return True - if ":" not in lockstring: - lockstring = "%s:%s" % ("_dummy", lockstring) - - locks = self._parse_lockstring(lockstring) - - if access_type: - if access_type not in locks: - return default - else: - return self._eval_access_type(accessing_obj, locks, access_type) - else: - # if no access types was given and multiple locks were - # embedded in the lockstring we assume all must be true - return all( - self._eval_access_type(accessing_obj, locks, access_type) for access_type in locks - )
- - -# convenience access function - -# dummy to be able to call check_lockstring from the outside - - -class _ObjDummy: - lock_storage = "" - - -def check_lockstring( - accessing_obj, lockstring, no_superuser_bypass=False, default=False, access_type=None -): - """ - Do a direct check against a lockstring ('atype:func()..'), - without any intermediary storage on the accessed object. - - Args: - accessing_obj (object or None): The object seeking access. - Importantly, this can be left unset if the lock functions - don't access it, no updating or storage of locks are made - against this object in this method. - lockstring (str): Lock string to check, on the form - `"access_type:lock_definition"` where the `access_type` - part can potentially be set to a dummy value to just check - a lock condition. - no_superuser_bypass (bool, optional): Force superusers to heed lock. - default (bool, optional): Fallback result to use if `access_type` is set - but no such `access_type` is found in the given `lockstring`. - access_type (str, bool): If set, only this access_type will be looked up - among the locks defined by `lockstring`. - - Return: - access (bool): If check is passed or not. - - """ - global _LOCK_HANDLER - if not _LOCK_HANDLER: - _LOCK_HANDLER = LockHandler(_ObjDummy()) - return _LOCK_HANDLER.check_lockstring( - accessing_obj, - lockstring, - no_superuser_bypass=no_superuser_bypass, - default=default, - access_type=access_type, - ) - - -def validate_lockstring(lockstring): - """ - Validate so lockstring is on a valid form. - - Args: - lockstring (str): Lockstring to validate. - - Returns: - is_valid (bool): If the lockstring is valid or not. - error (str or None): A string describing the error, or None - if no error was found. - - """ - global _LOCK_HANDLER - if not _LOCK_HANDLER: - _LOCK_HANDLER = LockHandler(_ObjDummy()) - return _LOCK_HANDLER.validate(lockstring) - - -def get_all_lockfuncs(): - """ - Get a dict of available lock funcs. - - Returns: - lockfuncs (dict): Mapping {lockfuncname:func}. - - """ - if not _LOCKFUNCS: - _cache_lockfuncs() - return _LOCKFUNCS - - -def _test(): - # testing - - class TestObj(object): - pass - - import pdb - - obj1 = TestObj() - obj2 = TestObj() - - # obj1.lock_storage = "owner:dbref(#4);edit:dbref(#5) or perm(Admin);examine:perm(Builder);delete:perm(Admin);get:all()" - # obj1.lock_storage = "cmd:all();admin:id(1);listen:all();send:all()" - obj1.lock_storage = "listen:perm(Developer)" - - pdb.set_trace() - obj1.locks = LockHandler(obj1) - obj2.permissions.add("Developer") - obj2.id = 4 - - # obj1.locks.add("edit:attr(test)") - - print("comparing obj2.permissions (%s) vs obj1.locks (%s)" % (obj2.permissions, obj1.locks)) - print(obj1.locks.check(obj2, "owner")) - print(obj1.locks.check(obj2, "edit")) - print(obj1.locks.check(obj2, "examine")) - print(obj1.locks.check(obj2, "delete")) - print(obj1.locks.check(obj2, "get")) - print(obj1.locks.check(obj2, "listen")) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/objects/admin.html b/docs/0.9.5/_modules/evennia/objects/admin.html deleted file mode 100644 index 0f334bcbdd..0000000000 --- a/docs/0.9.5/_modules/evennia/objects/admin.html +++ /dev/null @@ -1,303 +0,0 @@ - - - - - - - - evennia.objects.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.objects.admin

-#
-# This sets up how models are displayed
-# in the web admin interface.
-#
-from django import forms
-from django.conf import settings
-from django.contrib import admin
-from evennia.typeclasses.admin import AttributeInline, TagInline
-from evennia.objects.models import ObjectDB
-from django.contrib.admin.utils import flatten_fieldsets
-from django.utils.translation import gettext as _
-
-
-
[docs]class ObjectAttributeInline(AttributeInline): - """ - Defines inline descriptions of Attributes (experimental) - - """ - - model = ObjectDB.db_attributes.through - related_field = "objectdb"
- - -
[docs]class ObjectTagInline(TagInline): - """ - Defines inline descriptions of Tags (experimental) - - """ - - model = ObjectDB.db_tags.through - related_field = "objectdb"
- - -
[docs]class ObjectCreateForm(forms.ModelForm): - """ - This form details the look of the fields. - - """ - -
[docs] class Meta(object): - model = ObjectDB - fields = "__all__"
- - db_key = forms.CharField( - label="Name/Key", - widget=forms.TextInput(attrs={"size": "78"}), - help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. " - "If creating a Character, check so the name is unique among characters!", - ) - db_typeclass_path = forms.CharField( - label="Typeclass", - initial=settings.BASE_OBJECT_TYPECLASS, - widget=forms.TextInput(attrs={"size": "78"}), - help_text="This defines what 'type' of entity this is. This variable holds a " - "Python path to a module with a valid Evennia Typeclass. If you are " - "creating a Character you should use the typeclass defined by " - "settings.BASE_CHARACTER_TYPECLASS or one derived from that.", - ) - db_cmdset_storage = forms.CharField( - label="CmdSet", - initial="", - required=False, - widget=forms.TextInput(attrs={"size": "78"}), - help_text="Most non-character objects don't need a cmdset" - " and can leave this field blank.", - ) - raw_id_fields = ("db_destination", "db_location", "db_home")
- - -
[docs]class ObjectEditForm(ObjectCreateForm): - """ - Form used for editing. Extends the create one with more fields - - """ - -
[docs] class Meta(object): - fields = "__all__"
- - db_lock_storage = forms.CharField( - label="Locks", - required=False, - widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}), - help_text="In-game lock definition string. If not given, defaults will be used. " - "This string should be on the form " - "<i>type:lockfunction(args);type2:lockfunction2(args);...", - )
- - -
[docs]class ObjectDBAdmin(admin.ModelAdmin): - """ - Describes the admin page for Objects. - - """ - - inlines = [ObjectTagInline, ObjectAttributeInline] - list_display = ("id", "db_key", "db_account", "db_typeclass_path") - list_display_links = ("id", "db_key") - ordering = ["db_account", "db_typeclass_path", "id"] - search_fields = ["=id", "^db_key", "db_typeclass_path", "^db_account__db_key"] - raw_id_fields = ("db_destination", "db_location", "db_home") - - save_as = True - save_on_top = True - list_select_related = True - list_filter = ("db_typeclass_path",) - - # editing fields setup - - form = ObjectEditForm - fieldsets = ( - ( - None, - { - "fields": ( - ("db_key", "db_typeclass_path"), - ("db_lock_storage",), - ("db_location", "db_home"), - "db_destination", - "db_cmdset_storage", - ) - }, - ), - ) - - add_form = ObjectCreateForm - add_fieldsets = ( - ( - None, - { - "fields": ( - ("db_key", "db_typeclass_path"), - ("db_location", "db_home"), - "db_destination", - "db_cmdset_storage", - ) - }, - ), - ) - -
[docs] def get_fieldsets(self, request, obj=None): - """ - Return fieldsets. - - Args: - request (Request): Incoming request. - obj (ObjectDB, optional): Database object. - """ - if not obj: - return self.add_fieldsets - return super().get_fieldsets(request, obj)
- -
[docs] def get_form(self, request, obj=None, **kwargs): - """ - Use special form during creation. - - Args: - request (Request): Incoming request. - obj (Object, optional): Database object. - - """ - defaults = {} - if obj is None: - defaults.update( - {"form": self.add_form, "fields": flatten_fieldsets(self.add_fieldsets)} - ) - defaults.update(kwargs) - return super().get_form(request, obj, **defaults)
- -
[docs] def save_model(self, request, obj, form, change): - """ - Model-save hook. - - Args: - request (Request): Incoming request. - obj (Object): Database object. - form (Form): Form instance. - change (bool): If this is a change or a new object. - - """ - obj.save() - if not change: - # adding a new object - # have to call init with typeclass passed to it - obj.set_class_from_typeclass(typeclass_path=obj.db_typeclass_path) - obj.basetype_setup() - obj.basetype_posthook_setup() - obj.at_object_creation() - obj.at_init()
- -
[docs] def response_add(self, request, obj, post_url_continue=None): - from django.http import HttpResponseRedirect - from django.urls import reverse - - return HttpResponseRedirect(reverse("admin:objects_objectdb_change", args=[obj.id]))
- - -admin.site.register(ObjectDB, ObjectDBAdmin) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/objects/manager.html b/docs/0.9.5/_modules/evennia/objects/manager.html deleted file mode 100644 index 03d28b0c02..0000000000 --- a/docs/0.9.5/_modules/evennia/objects/manager.html +++ /dev/null @@ -1,705 +0,0 @@ - - - - - - - - evennia.objects.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.objects.manager

-"""
-Custom manager for Objects.
-"""
-import re
-from itertools import chain
-from django.db.models import Q
-from django.conf import settings
-from django.db.models.fields import exceptions
-from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
-from evennia.utils.utils import is_iter, make_iter, string_partial_matching
-
-__all__ = ("ObjectManager", "ObjectDBManager")
-_GA = object.__getattribute__
-
-# delayed import
-_ATTR = None
-
-_MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U)
-
-# Try to use a custom way to parse id-tagged multimatches.
-
-
-
[docs]class ObjectDBManager(TypedObjectManager): - """ - This ObjectManager implements methods for searching - and manipulating Objects directly from the database. - - Evennia-specific search methods (will return Typeclasses or - lists of Typeclasses, whereas Django-general methods will return - Querysets or database objects). - - dbref (converter) - get_id (alias: dbref_search) - get_dbref_range - object_totals - typeclass_search - get_object_with_account - get_objs_with_key_and_typeclass - get_objs_with_attr - get_objs_with_attr_match - get_objs_with_db_property - get_objs_with_db_property_match - get_objs_with_key_or_alias - get_contents - object_search (interface to many of the above methods, - equivalent to evennia.search_object) - copy_object - - """ - - # - # ObjectManager Get methods - # - - # account related - -
[docs] def get_object_with_account(self, ostring, exact=True, candidates=None): - """ - Search for an object based on its account's name or dbref. - - Args: - ostring (str or int): Search criterion or dbref. Searching - for an account is sometimes initiated by appending an `*` to - the beginning of the search criterion (e.g. in - local_and_global_search). This is stripped here. - exact (bool, optional): Require an exact account match. - candidates (list, optional): Only search among this list of possible - object candidates. - - Return: - match (query): Matching query. - - """ - ostring = str(ostring).lstrip("*") - # simplest case - search by dbref - dbref = self.dbref(ostring) - if dbref: - try: - return self.get(db_account__id=dbref) - except self.model.DoesNotExist: - pass - - # not a dbref. Search by name. - cand_restriction = ( - candidates is not None - and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) - or Q() - ) - if exact: - return self.filter(cand_restriction & Q(db_account__username__iexact=ostring)).order_by( - "id" - ) - else: # fuzzy matching - obj_cands = self.select_related().filter( - cand_restriction & Q(db_account__username__istartswith=ostring) - ) - acct_cands = [obj.account for obj in obj_cands] - - if obj_cands: - index_matches = string_partial_matching( - [acct.key for acct in acct_cands], ostring, ret_index=True - ) - acct_cands = [acct_cands[i].id for i in index_matches] - return obj_cands.filter(db_account__id__in=acct_cands).order_by("id")
- -
[docs] def get_objs_with_key_and_typeclass(self, oname, otypeclass_path, candidates=None): - """ - Returns objects based on simultaneous key and typeclass match. - - Args: - oname (str): Object key to search for - otypeclass_path (str): Full Python path to tyepclass to search for - candidates (list, optional): Only match among the given list of candidates. - - Returns: - matches (query): The matching objects. - """ - cand_restriction = ( - candidates is not None - and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) - or Q() - ) - return self.filter( - cand_restriction & Q(db_key__iexact=oname, db_typeclass_path__exact=otypeclass_path) - ).order_by("id")
- - # attr/property related - -
[docs] def get_objs_with_attr(self, attribute_name, candidates=None): - """ - Get objects based on having a certain Attribute defined. - - Args: - attribute_name (str): Attribute name to search for. - candidates (list, optional): Only match among the given list of object - candidates. - - Returns: - matches (query): All objects having the given attribute_name defined at all. - - """ - cand_restriction = ( - candidates is not None and Q(id__in=[obj.id for obj in candidates]) or Q() - ) - return self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)).order_by( - "id" - )
- -
[docs] def get_objs_with_attr_value( - self, attribute_name, attribute_value, candidates=None, typeclasses=None - ): - """ - Get all objects having the given attrname set to the given value. - - Args: - attribute_name (str): Attribute key to search for. - attribute_value (any): Attribute value to search for. This can also be database objects. - candidates (list, optional): Candidate objects to limit search to. - typeclasses (list, optional): Python pats to restrict matches with. - - Returns: - matches (query): Objects fullfilling both the `attribute_name` and - `attribute_value` criterions. - - Notes: - This uses the Attribute's PickledField to transparently search the database by matching - the internal representation. This is reasonably effective but since Attribute values - cannot be indexed, searching by Attribute key is to be preferred whenever possible. - - """ - cand_restriction = ( - candidates is not None - and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) - or Q() - ) - type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() - - results = self.filter( - cand_restriction - & type_restriction - & Q(db_attributes__db_key=attribute_name) - & Q(db_attributes__db_value=attribute_value) - ).order_by("id") - return results
- -
[docs] def get_objs_with_db_property(self, property_name, candidates=None): - """ - Get all objects having a given db field property. - - Args: - property_name (str): The name of the field to match for. - candidates (list, optional): Only search among th egiven candidates. - - Returns: - matches (list): The found matches. - - """ - property_name = "db_%s" % property_name.lstrip("db_") - cand_restriction = ( - candidates is not None - and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) - or Q() - ) - querykwargs = {property_name: None} - try: - return list(self.filter(cand_restriction).exclude(Q(**querykwargs)).order_by("id")) - except exceptions.FieldError: - return []
- -
[docs] def get_objs_with_db_property_value( - self, property_name, property_value, candidates=None, typeclasses=None - ): - """ - Get objects with a specific field name and value. - - Args: - property_name (str): Field name to search for. - property_value (any): Value required for field with `property_name` to have. - candidates (list, optional): List of objects to limit search to. - typeclasses (list, optional): List of typeclass-path strings to restrict matches with - - """ - if isinstance(property_name, str): - if not property_name.startswith("db_"): - property_name = "db_%s" % property_name - querykwargs = {property_name: property_value} - cand_restriction = ( - candidates is not None - and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) - or Q() - ) - type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() - try: - return list( - self.filter(cand_restriction & type_restriction & Q(**querykwargs)).order_by("id") - ) - except exceptions.FieldError: - return [] - except ValueError: - from evennia.utils import logger - - logger.log_err( - "The property '%s' does not support search criteria of the type %s." - % (property_name, type(property_value)) - ) - return []
- -
[docs] def get_contents(self, location, excludeobj=None): - """ - Get all objects that has a location set to this one. - - Args: - location (Object): Where to get contents from. - excludeobj (Object or list, optional): One or more objects - to exclude from the match. - - Returns: - contents (query): Matching contents, without excludeobj, if given. - """ - exclude_restriction = ( - Q(pk__in=[_GA(obj, "id") for obj in make_iter(excludeobj)]) if excludeobj else Q() - ) - return self.filter(db_location=location).exclude(exclude_restriction).order_by("id")
- -
[docs] def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None, typeclasses=None): - """ - Args: - ostring (str): A search criterion. - exact (bool, optional): Require exact match of ostring - (still case-insensitive). If `False`, will do fuzzy matching - using `evennia.utils.utils.string_partial_matching` algorithm. - candidates (list): Only match among these candidates. - typeclasses (list): Only match objects with typeclasses having thess path strings. - - Returns: - matches (query): A list of matches of length 0, 1 or more. - """ - if not isinstance(ostring, str): - if hasattr(ostring, "key"): - ostring = ostring.key - else: - return [] - if is_iter(candidates) and not len(candidates): - # if candidates is an empty iterable there can be no matches - # Exit early. - return [] - - # build query objects - candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] - cand_restriction = candidates is not None and Q(pk__in=candidates_id) or Q() - type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() - if exact: - # exact match - do direct search - return ( - ( - self.filter( - cand_restriction - & type_restriction - & ( - Q(db_key__iexact=ostring) - | Q(db_tags__db_key__iexact=ostring) - & Q(db_tags__db_tagtype__iexact="alias") - ) - ) - ) - .distinct() - .order_by("id") - ) - elif candidates: - # fuzzy with candidates - search_candidates = ( - self.filter(cand_restriction & type_restriction).distinct().order_by("id") - ) - else: - # fuzzy without supplied candidates - we select our own candidates - search_candidates = ( - self.filter( - type_restriction - & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring)) - ) - .distinct() - .order_by("id") - ) - # fuzzy matching - key_strings = search_candidates.values_list("db_key", flat=True).order_by("id") - - index_matches = string_partial_matching(key_strings, ostring, ret_index=True) - if index_matches: - # a match by key - return [obj for ind, obj in enumerate(search_candidates) if ind in index_matches] - else: - # match by alias rather than by key - search_candidates = search_candidates.filter( - db_tags__db_tagtype__iexact="alias", db_tags__db_key__icontains=ostring - ).distinct() - alias_strings = [] - alias_candidates = [] - # TODO create the alias_strings and alias_candidates lists more efficiently? - for candidate in search_candidates: - for alias in candidate.aliases.all(): - alias_strings.append(alias) - alias_candidates.append(candidate) - index_matches = string_partial_matching(alias_strings, ostring, ret_index=True) - if index_matches: - # it's possible to have multiple matches to the same Object, we must weed those out - return list({alias_candidates[ind] for ind in index_matches}) - return []
- - # main search methods and helper functions - -
[docs] def search_object( - self, - searchdata, - attribute_name=None, - typeclass=None, - candidates=None, - exact=True, - use_dbref=True, - ): - """ - Search as an object globally or in a list of candidates and - return results. The result is always an Object. Always returns - a list. - - Args: - searchdata (str or Object): The entity to match for. This is - usually a key string but may also be an object itself. - By default (if no `attribute_name` is set), this will - search `object.key` and `object.aliases` in order. - Can also be on the form #dbref, which will (if - `exact=True`) be matched against primary key. - attribute_name (str): Use this named Attribute to - match searchdata against, instead of the defaults. If - this is the name of a database field (with or without - the `db_` prefix), that will be matched too. - typeclass (str or TypeClass): restrict matches to objects - having this typeclass. This will help speed up global - searches. - candidates (list): If supplied, search will - only be performed among the candidates in this list. A - common list of candidates is the contents of the - current location searched. - exact (bool): Match names/aliases exactly or partially. - Partial matching matches the beginning of words in the - names/aliases, using a matching routine to separate - multiple matches in names with multiple components (so - "bi sw" will match "Big sword"). Since this is more - expensive than exact matching, it is recommended to be - used together with the `candidates` keyword to limit the - number of possibilities. This value has no meaning if - searching for attributes/properties. - use_dbref (bool): If False, bypass direct lookup of a string - on the form #dbref and treat it like any string. - - Returns: - matches (list): Matching objects - - """ - - def _searcher(searchdata, candidates, typeclass, exact=False): - """ - Helper method for searching objects. `typeclass` is only used - for global searching (no candidates) - """ - if attribute_name: - # attribute/property search (always exact). - matches = self.get_objs_with_db_property_value( - attribute_name, searchdata, candidates=candidates, typeclasses=typeclass - ) - if matches: - return matches - return self.get_objs_with_attr_value( - attribute_name, searchdata, candidates=candidates, typeclasses=typeclass - ) - else: - # normal key/alias search - return self.get_objs_with_key_or_alias( - searchdata, exact=exact, candidates=candidates, typeclasses=typeclass - ) - - if not searchdata and searchdata != 0: - return [] - - if typeclass: - # typeclass may also be a list - typeclasses = make_iter(typeclass) - for i, typeclass in enumerate(make_iter(typeclasses)): - if callable(typeclass): - typeclasses[i] = "%s.%s" % (typeclass.__module__, typeclass.__name__) - else: - typeclasses[i] = "%s" % typeclass - typeclass = typeclasses - - if candidates is not None: - if not candidates: - # candidates is the empty list. This should mean no matches can ever be acquired. - return [] - # Convenience check to make sure candidates are really dbobjs - candidates = [cand for cand in make_iter(candidates) if cand] - if typeclass: - candidates = [ - cand for cand in candidates if _GA(cand, "db_typeclass_path") in typeclass - ] - - dbref = not attribute_name and exact and use_dbref and self.dbref(searchdata) - if dbref: - # Easiest case - dbref matching (always exact) - dbref_match = self.dbref_search(dbref) - if dbref_match: - if not candidates or dbref_match in candidates: - return [dbref_match] - else: - return [] - - # Search through all possibilities. - match_number = None - # always run first check exact - we don't want partial matches - # if on the form of 1-keyword etc. - matches = _searcher(searchdata, candidates, typeclass, exact=True) - if not matches: - # no matches found - check if we are dealing with N-keyword - # query - if so, strip it. - match = _MULTIMATCH_REGEX.match(str(searchdata)) - match_number = None - if match: - # strips the number - match_number, searchdata = match.group("number"), match.group("name") - match_number = int(match_number) - 1 - if match_number is not None or not exact: - # run search again, with the exactness set by call - matches = _searcher(searchdata, candidates, typeclass, exact=exact) - - # deal with result - if len(matches) == 1 and match_number is not None and match_number != 0: - # this indicates trying to get a single match with a match-number - # targeting some higher-number match (like 2-box when there is only - # one box in the room). This leads to a no-match. - matches = [] - elif len(matches) > 1 and match_number is not None: - # multiple matches, but a number was given to separate them - if 0 <= match_number < len(matches): - # limit to one match - matches = [matches[match_number]] - else: - # a number was given outside of range. This means a no-match. - matches = [] - - # return a list (possibly empty) - return matches
- - # alias for backwards compatibility - object_search = search_object - search = search_object - - # - # ObjectManager Copy method - -
[docs] def copy_object( - self, - original_object, - new_key=None, - new_location=None, - new_home=None, - new_permissions=None, - new_locks=None, - new_aliases=None, - new_destination=None, - ): - """ - Create and return a new object as a copy of the original object. All - will be identical to the original except for the arguments given - specifically to this method. Object contents will not be copied. - - Args: - original_object (Object): The object to make a copy from. - new_key (str, optional): Name of the copy, if different - from the original. - new_location (Object, optional): Alternate location. - new_home (Object, optional): Change the home location - new_aliases (list, optional): Give alternate object - aliases as a list of strings. - new_destination (Object, optional): Used only by exits. - - Returns: - copy (Object or None): The copy of `original_object`, - optionally modified as per the ingoing keyword - arguments. `None` if an error was encountered. - - """ - - # get all the object's stats - typeclass_path = original_object.typeclass_path - if not new_key: - new_key = original_object.key - if not new_location: - new_location = original_object.location - if not new_home: - new_home = original_object.home - if not new_aliases: - new_aliases = original_object.aliases.all() - if not new_locks: - new_locks = original_object.db_lock_storage - if not new_permissions: - new_permissions = original_object.permissions.all() - if not new_destination: - new_destination = original_object.destination - - # create new object - from evennia.utils import create - from evennia.scripts.models import ScriptDB - - new_object = create.create_object( - typeclass_path, - key=new_key, - location=new_location, - home=new_home, - permissions=new_permissions, - locks=new_locks, - aliases=new_aliases, - destination=new_destination, - ) - if not new_object: - return None - - # copy over all attributes from old to new. - attrs = ( - (a.key, a.value, a.category, a.lock_storage) for a in original_object.attributes.all() - ) - new_object.attributes.batch_add(*attrs) - - # copy over all cmdsets, if any - for icmdset, cmdset in enumerate(original_object.cmdset.all()): - if icmdset == 0: - new_object.cmdset.add_default(cmdset) - else: - new_object.cmdset.add(cmdset) - - # copy over all scripts, if any - for script in original_object.scripts.all(): - ScriptDB.objects.copy_script(script, new_obj=new_object) - - # copy over all tags, if any - tags = ( - (t.db_key, t.db_category, t.db_data) for t in original_object.tags.all(return_objs=True) - ) - new_object.tags.batch_add(*tags) - - return new_object
- -
[docs] def clear_all_sessids(self): - """ - Clear the db_sessid field of all objects having also the - db_account field set. - """ - self.filter(db_sessid__isnull=False).update(db_sessid=None)
- - -
[docs]class ObjectManager(ObjectDBManager, TypeclassManager): - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/objects/models.html b/docs/0.9.5/_modules/evennia/objects/models.html deleted file mode 100644 index 0d88cb7d53..0000000000 --- a/docs/0.9.5/_modules/evennia/objects/models.html +++ /dev/null @@ -1,470 +0,0 @@ - - - - - - - - evennia.objects.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.objects.models

-"""
-This module defines the database models for all in-game objects, that
-is, all objects that has an actual existence in-game.
-
-Each database object is 'decorated' with a 'typeclass', a normal
-python class that implements all the various logics needed by the game
-in question. Objects created of this class transparently communicate
-with its related database object for storing all attributes. The
-admin should usually not have to deal directly with this database
-object layer.
-
-Attributes are separate objects that store values persistently onto
-the database object. Like everything else, they can be accessed
-transparently through the decorating TypeClass.
-"""
-from django.conf import settings
-from django.db import models
-from django.core.exceptions import ObjectDoesNotExist
-from django.core.validators import validate_comma_separated_integer_list
-
-from evennia.typeclasses.models import TypedObject
-from evennia.objects.manager import ObjectDBManager
-from evennia.utils import logger
-from evennia.utils.utils import make_iter, dbref, lazy_property
-
-
-
[docs]class ContentsHandler: - """ - Handles and caches the contents of an object to avoid excessive - lookups (this is done very often due to cmdhandler needing to look - for object-cmdsets). It is stored on the 'contents_cache' property - of the ObjectDB. - """ - -
[docs] def __init__(self, obj): - """ - Sets up the contents handler. - - Args: - obj (Object): The object on which the - handler is defined - - """ - self.obj = obj - self._pkcache = {} - self._idcache = obj.__class__.__instance_cache__ - self.init()
- -
[docs] def init(self): - """ - Re-initialize the content cache - - """ - self._pkcache.update( - dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk) - )
- -
[docs] def get(self, exclude=None): - """ - Return the contents of the cache. - - Args: - exclude (Object or list of Object): object(s) to ignore - - Returns: - objects (list): the Objects inside this location - - """ - if exclude: - pks = [pk for pk in self._pkcache if pk not in [excl.pk for excl in make_iter(exclude)]] - else: - pks = self._pkcache - try: - return [self._idcache[pk] for pk in pks] - except KeyError: - # this can happen if the idmapper cache was cleared for an object - # in the contents cache. If so we need to re-initialize and try again. - self.init() - try: - return [self._idcache[pk] for pk in pks] - except KeyError: - # this means the central instance_cache was totally flushed. - # Re-fetching from database will rebuild the necessary parts of the cache - # for next fetch. - return list(ObjectDB.objects.filter(db_location=self.obj))
- -
[docs] def add(self, obj): - """ - Add a new object to this location - - Args: - obj (Object): object to add - - """ - self._pkcache[obj.pk] = None
- -
[docs] def remove(self, obj): - """ - Remove object from this location - - Args: - obj (Object): object to remove - - """ - self._pkcache.pop(obj.pk, None)
- -
[docs] def clear(self): - """ - Clear the contents cache and re-initialize - - """ - self._pkcache = {} - self.init()
- - -# ------------------------------------------------------------- -# -# ObjectDB -# -# ------------------------------------------------------------- - - -
[docs]class ObjectDB(TypedObject): - """ - All objects in the game use the ObjectDB model to store - data in the database. This is handled transparently through - the typeclass system. - - Note that the base objectdb is very simple, with - few defined fields. Use attributes to extend your - type class with new database-stored variables. - - The TypedObject supplies the following (inherited) properties: - - - key - main name - - name - alias for key - - db_typeclass_path - the path to the decorating typeclass - - db_date_created - time stamp of object creation - - permissions - perm strings - - locks - lock definitions (handler) - - dbref - #id of object - - db - persistent attribute storage - - ndb - non-persistent attribute storage - - The ObjectDB adds the following properties: - - - account - optional connected account (always together with sessid) - - sessid - optional connection session id (always together with account) - - location - in-game location of object - - home - safety location for object (handler) - - scripts - scripts assigned to object (handler from typeclass) - - cmdset - active cmdset on object (handler from typeclass) - - aliases - aliases for this object (property) - - nicks - nicknames for *other* things in Evennia (handler) - - sessions - sessions connected to this object (see also account) - - has_account - bool if an active account is currently connected - - contents - other objects having this object as location - - exits - exits from this object - - """ - - # - # ObjectDB Database model setup - # - # - # inherited fields (from TypedObject): - # db_key (also 'name' works), db_typeclass_path, db_date_created, - # db_permissions - # - # These databse fields (including the inherited ones) should normally be - # managed by their corresponding wrapper properties, named same as the - # field, but without the db_* prefix (e.g. the db_key field is set with - # self.key instead). The wrappers are created at the metaclass level and - # will automatically save and cache the data more efficiently. - - # If this is a character object, the account is connected here. - db_account = models.ForeignKey( - "accounts.AccountDB", - null=True, - verbose_name="account", - on_delete=models.SET_NULL, - help_text="an Account connected to this object, if any.", - ) - - # the session id associated with this account, if any - db_sessid = models.CharField( - null=True, - max_length=32, - validators=[validate_comma_separated_integer_list], - verbose_name="session id", - help_text="csv list of session ids of connected Account, if any.", - ) - # The location in the game world. Since this one is likely - # to change often, we set this with the 'location' property - # to transparently handle Typeclassing. - db_location = models.ForeignKey( - "self", - related_name="locations_set", - db_index=True, - on_delete=models.SET_NULL, - blank=True, - null=True, - verbose_name="game location", - ) - # a safety location, this usually don't change much. - db_home = models.ForeignKey( - "self", - related_name="homes_set", - on_delete=models.SET_NULL, - blank=True, - null=True, - verbose_name="home location", - ) - # destination of this object - primarily used by exits. - db_destination = models.ForeignKey( - "self", - related_name="destinations_set", - db_index=True, - on_delete=models.SET_NULL, - blank=True, - null=True, - verbose_name="destination", - help_text="a destination, used only by exit objects.", - ) - # database storage of persistant cmdsets. - db_cmdset_storage = models.CharField( - "cmdset", - max_length=255, - null=True, - blank=True, - help_text="optional python path to a cmdset class.", - ) - - # Database manager - objects = ObjectDBManager() - - # defaults - __settingsclasspath__ = settings.BASE_OBJECT_TYPECLASS - __defaultclasspath__ = "evennia.objects.objects.DefaultObject" - __applabel__ = "objects" - -
[docs] @lazy_property - def contents_cache(self): - return ContentsHandler(self)
- - # cmdset_storage property handling - def __cmdset_storage_get(self): - """getter""" - storage = self.db_cmdset_storage - return [path.strip() for path in storage.split(",")] if storage else [] - - def __cmdset_storage_set(self, value): - """setter""" - self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value)) - self.save(update_fields=["db_cmdset_storage"]) - - def __cmdset_storage_del(self): - """deleter""" - self.db_cmdset_storage = None - self.save(update_fields=["db_cmdset_storage"]) - - cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del) - - # location getsetter - def __location_get(self): - """Get location""" - return self.db_location - - def __location_set(self, location): - """Set location, checking for loops and allowing dbref""" - if isinstance(location, (str, int)): - # allow setting of #dbref - dbid = dbref(location, reqhash=False) - if dbid: - try: - location = ObjectDB.objects.get(id=dbid) - except ObjectDoesNotExist: - # maybe it is just a name that happens to look like a dbid - pass - try: - - def is_loc_loop(loc, depth=0): - """Recursively traverse target location, trying to catch a loop.""" - if depth > 10: - return None - elif loc == self: - raise RuntimeError - elif loc is None: - raise RuntimeWarning - return is_loc_loop(loc.db_location, depth + 1) - - try: - is_loc_loop(location) - except RuntimeWarning: - # we caught an infinite location loop! - # (location1 is in location2 which is in location1 ...) - pass - - # if we get to this point we are ready to change location - - old_location = self.db_location - - # this is checked in _db_db_location_post_save below - self._safe_contents_update = True - - # actually set the field (this will error if location is invalid) - self.db_location = location - self.save(update_fields=["db_location"]) - - # remove the safe flag - del self._safe_contents_update - - # update the contents cache - if old_location: - old_location.contents_cache.remove(self) - if self.db_location: - self.db_location.contents_cache.add(self) - - except RuntimeError: - errmsg = "Error: %s.location = %s creates a location loop." % (self.key, location) - raise RuntimeError(errmsg) - except Exception as e: - errmsg = "Error (%s): %s is not a valid location." % (str(e), location) - raise RuntimeError(errmsg) - return - - def __location_del(self): - """Cleanly delete the location reference""" - self.db_location = None - self.save(update_fields=["db_location"]) - - location = property(__location_get, __location_set, __location_del) - -
[docs] def at_db_location_postsave(self, new): - """ - This is called automatically after the location field was - saved, no matter how. It checks for a variable - _safe_contents_update to know if the save was triggered via - the location handler (which updates the contents cache) or - not. - - Args: - new (bool): Set if this location has not yet been saved before. - - """ - if not hasattr(self, "_safe_contents_update"): - # changed/set outside of the location handler - if new: - # if new, there is no previous location to worry about - if self.db_location: - self.db_location.contents_cache.add(self) - else: - # Since we cannot know at this point was old_location was, we - # trigger a full-on contents_cache update here. - logger.log_warn( - "db_location direct save triggered contents_cache.init() for all objects!" - ) - [o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()]
- - class Meta(object): - """Define Django meta options""" - - verbose_name = "Object" - verbose_name_plural = "Objects"
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/objects/objects.html b/docs/0.9.5/_modules/evennia/objects/objects.html deleted file mode 100644 index 6397ccf8e7..0000000000 --- a/docs/0.9.5/_modules/evennia/objects/objects.html +++ /dev/null @@ -1,2738 +0,0 @@ - - - - - - - - evennia.objects.objects — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.objects.objects

-"""
-This module defines the basic `DefaultObject` and its children
-`DefaultCharacter`, `DefaultAccount`, `DefaultRoom` and `DefaultExit`.
-These are the (default) starting points for all in-game visible
-entities.
-
-"""
-import time
-import inflect
-from collections import defaultdict
-
-from django.conf import settings
-
-from evennia.typeclasses.models import TypeclassBase
-from evennia.typeclasses.attributes import NickHandler
-from evennia.objects.manager import ObjectManager
-from evennia.objects.models import ObjectDB
-from evennia.scripts.scripthandler import ScriptHandler
-from evennia.commands import cmdset, command
-from evennia.commands.cmdsethandler import CmdSetHandler
-from evennia.utils import create
-from evennia.utils import search
-from evennia.utils import logger
-from evennia.utils import ansi
-from evennia.utils.utils import (
-    class_from_module,
-    variable_from_module,
-    lazy_property,
-    make_iter,
-    is_iter,
-    list_to_string,
-    to_str,
-)
-from django.utils.translation import gettext as _
-
-_INFLECT = inflect.engine()
-_MULTISESSION_MODE = settings.MULTISESSION_MODE
-
-_ScriptDB = None
-_SESSIONS = None
-_CMDHANDLER = None
-
-_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
-_COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
-# the sessid_max is based on the length of the db_sessid csv field (excluding commas)
-_SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1
-
-
-
[docs]class ObjectSessionHandler(object): - """ - Handles the get/setting of the sessid - comma-separated integer field - """ - -
[docs] def __init__(self, obj): - """ - Initializes the handler. - - Args: - obj (Object): The object on which the handler is defined. - - """ - self.obj = obj - self._sessid_cache = [] - self._recache()
- - def _recache(self): - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - self._sessid_cache = list( - set(int(val) for val in (self.obj.db_sessid or "").split(",") if val) - ) - if any(sessid for sessid in self._sessid_cache if sessid not in _SESSIONS): - # cache is out of sync with sessionhandler! Only retain the ones in the handler. - self._sessid_cache = [sessid for sessid in self._sessid_cache if sessid in _SESSIONS] - self.obj.db_sessid = ",".join(str(val) for val in self._sessid_cache) - self.obj.save(update_fields=["db_sessid"]) - -
[docs] def get(self, sessid=None): - """ - Get the sessions linked to this Object. - - Args: - sessid (int, optional): A specific session id. - - Returns: - sessions (list): The sessions connected to this object. If `sessid` is given, - this is a list of one (or zero) elements. - - Notes: - Aliased to `self.all()`. - - """ - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - if sessid: - sessions = ( - [_SESSIONS[sessid] if sessid in _SESSIONS else None] - if sessid in self._sessid_cache - else [] - ) - else: - sessions = [ - _SESSIONS[ssid] if ssid in _SESSIONS else None for ssid in self._sessid_cache - ] - if None in sessions: - # this happens only if our cache has gone out of sync with the SessionHandler. - self._recache() - return self.get(sessid=sessid) - return sessions
- -
[docs] def all(self): - """ - Alias to get(), returning all sessions. - - Returns: - sessions (list): All sessions. - - """ - return self.get()
- -
[docs] def add(self, session): - """ - Add session to handler. - - Args: - session (Session or int): Session or session id to add. - - Notes: - We will only add a session/sessid if this actually also exists - in the the core sessionhandler. - - """ - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - try: - sessid = session.sessid - except AttributeError: - sessid = session - - sessid_cache = self._sessid_cache - if sessid in _SESSIONS and sessid not in sessid_cache: - if len(sessid_cache) >= _SESSID_MAX: - return - sessid_cache.append(sessid) - self.obj.db_sessid = ",".join(str(val) for val in sessid_cache) - self.obj.save(update_fields=["db_sessid"])
- -
[docs] def remove(self, session): - """ - Remove session from handler. - - Args: - session (Session or int): Session or session id to remove. - - """ - try: - sessid = session.sessid - except AttributeError: - sessid = session - - sessid_cache = self._sessid_cache - if sessid in sessid_cache: - sessid_cache.remove(sessid) - self.obj.db_sessid = ",".join(str(val) for val in sessid_cache) - self.obj.save(update_fields=["db_sessid"])
- -
[docs] def clear(self): - """ - Clear all handled sessids. - - """ - self._sessid_cache = [] - self.obj.db_sessid = None - self.obj.save(update_fields=["db_sessid"])
- -
[docs] def count(self): - """ - Get amount of sessions connected. - - Returns: - sesslen (int): Number of sessions handled. - - """ - return len(self._sessid_cache)
- - -# -# Base class to inherit from. - - -
[docs]class DefaultObject(ObjectDB, metaclass=TypeclassBase): - """ - This is the root typeclass object, representing all entities that - have an actual presence in-game. DefaultObjects generally have a - location. They can also be manipulated and looked at. Game - entities you define should inherit from DefaultObject at some distance. - - It is recommended to create children of this class using the - `evennia.create_object()` function rather than to initialize the class - directly - this will both set things up and efficiently save the object - without `obj.save()` having to be called explicitly. - - """ - - # lockstring of newly created objects, for easy overloading. - # Will be formatted with the appropriate attributes. - lockstring = "control:id({account_id}) or perm(Admin);delete:id({account_id}) or perm(Admin)" - - objects = ObjectManager() - - # on-object properties - -
[docs] @lazy_property - def cmdset(self): - return CmdSetHandler(self, True)
- -
[docs] @lazy_property - def scripts(self): - return ScriptHandler(self)
- -
[docs] @lazy_property - def nicks(self): - return NickHandler(self)
- -
[docs] @lazy_property - def sessions(self): - return ObjectSessionHandler(self)
- - @property - def is_connected(self): - # we get an error for objects subscribed to channels without this - if self.account: # seems sane to pass on the account - return self.account.is_connected - else: - return False - - @property - def has_account(self): - """ - Convenience property for checking if an active account is - currently connected to this object. - - """ - return self.sessions.count() - - @property - def is_superuser(self): - """ - Check if user has an account, and if so, if it is a superuser. - - """ - return ( - self.db_account - and self.db_account.is_superuser - and not self.db_account.attributes.get("_quell") - ) - -
[docs] def contents_get(self, exclude=None): - """ - Returns the contents of this object, i.e. all - objects that has this object set as its location. - This should be publically available. - - Args: - exclude (Object): Object to exclude from returned - contents list - - Returns: - contents (list): List of contents of this Object. - - Notes: - Also available as the `contents` property. - - """ - con = self.contents_cache.get(exclude=exclude) - # print "contents_get:", self, con, id(self), calledby() # DEBUG - return con
- -
[docs] def contents_set(self, *args): - "You cannot replace this property" - raise AttributeError( - "{}.contents is read-only. Use obj.move_to or " - "obj.location to move an object here.".format(self.__class__) - )
- - contents = property(contents_get, contents_set, contents_set) - - @property - def exits(self): - """ - Returns all exits from this object, i.e. all objects at this - location having the property destination != `None`. - """ - return [exi for exi in self.contents if exi.destination] - - # main methods - -
[docs] def get_display_name(self, looker, **kwargs): - """ - Displays the name of the object in a viewer-aware manner. - - Args: - looker (TypedObject): The object or account that is looking - at/getting inforamtion for this object. - - Returns: - name (str): A string containing the name of the object, - including the DBREF if this user is privileged to control - said object. - - Notes: - This function could be extended to change how object names - appear to users in character, but be wary. This function - does not change an object's keys or aliases when - searching, and is expected to produce something useful for - builders. - - """ - if self.locks.check_lockstring(looker, "perm(Builder)"): - return "{}(#{})".format(self.name, self.id) - return self.name
- -
[docs] def get_numbered_name(self, count, looker, **kwargs): - """ - Return the numbered (singular, plural) forms of this object's key. This is by default called - by return_appearance and is used for grouping multiple same-named of this object. Note that - this will be called on *every* member of a group even though the plural name will be only - shown once. Also the singular display version, such as 'an apple', 'a tree' is determined - from this method. - - Args: - count (int): Number of objects of this type - looker (Object): Onlooker. Not used by default. - Keyword Args: - key (str): Optional key to pluralize, if given, use this instead of the object's key. - Returns: - singular (str): The singular form to display. - plural (str): The determined plural form of the key, including the count. - """ - plural_category = "plural_key" - key = kwargs.get("key", self.key) - key = ansi.ANSIString(key) # this is needed to allow inflection of colored names - try: - plural = _INFLECT.plural(key, count) - plural = "{} {}".format(_INFLECT.number_to_words(count, threshold=12), plural) - except IndexError: - # this is raised by inflect if the input is not a proper noun - plural = key - singular = _INFLECT.an(key) - if not self.aliases.get(plural, category=plural_category): - # we need to wipe any old plurals/an/a in case key changed in the interrim - self.aliases.clear(category=plural_category) - self.aliases.add(plural, category=plural_category) - # save the singular form as an alias here too so we can display "an egg" and also - # look at 'an egg'. - self.aliases.add(singular, category=plural_category) - return singular, plural
- -
[docs] def search( - self, - searchdata, - global_search=False, - use_nicks=True, - typeclass=None, - location=None, - attribute_name=None, - quiet=False, - exact=False, - candidates=None, - nofound_string=None, - multimatch_string=None, - use_dbref=None, - ): - """ - Returns an Object matching a search string/condition - - Perform a standard object search in the database, handling - multiple results and lack thereof gracefully. By default, only - objects in the current `location` of `self` or its inventory are searched for. - - Args: - searchdata (str or obj): Primary search criterion. Will be matched - against `object.key` (with `object.aliases` second) unless - the keyword attribute_name specifies otherwise. - - Special strings: - - - `#<num>`: search by unique dbref. This is always - a global search. - - `me,self`: self-reference to this object - - `<num>-<string>` - can be used to differentiate - between multiple same-named matches. The exact form of this input - is given by `settings.SEARCH_MULTIMATCH_REGEX`. - - global_search (bool): Search all objects globally. This overrules 'location' data. - use_nicks (bool): Use nickname-replace (nicktype "object") on `searchdata`. - typeclass (str or Typeclass, or list of either): Limit search only - to `Objects` with this typeclass. May be a list of typeclasses - for a broader search. - location (Object or list): Specify a location or multiple locations - to search. Note that this is used to query the *contents* of a - location and will not match for the location itself - - if you want that, don't set this or use `candidates` to specify - exactly which objects should be searched. - attribute_name (str): Define which property to search. If set, no - key+alias search will be performed. This can be used - to search database fields (db_ will be automatically - prepended), and if that fails, it will try to return - objects having Attributes with this name and value - equal to searchdata. A special use is to search for - "key" here if you want to do a key-search without - including aliases. - quiet (bool): don't display default error messages - this tells the - search method that the user wants to handle all errors - themselves. It also changes the return value type, see - below. - exact (bool): if unset (default) - prefers to match to beginning of - string rather than not matching at all. If set, requires - exact matching of entire string. - candidates (list of objects): this is an optional custom list of objects - to search (filter) between. It is ignored if `global_search` - is given. If not set, this list will automatically be defined - to include the location, the contents of location and the - caller's contents (inventory). - nofound_string (str): optional custom string for not-found error message. - multimatch_string (str): optional custom string for multimatch error header. - use_dbref (bool or None, optional): If `True`, allow to enter e.g. a query "#123" - to find an object (globally) by its database-id 123. If `False`, the string "#123" - 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. - - Returns: - Object, None or list: Will return an `Object` or `None` if `quiet=False`. Will return a - list with 0, 1 or more matches if `quiet=True`. If `stacked` is a positive integer, this - list may contain all stacked identical matches. - - Notes: - To find Accounts, use eg. `evennia.account_search`. If - `quiet=False`, error messages will be handled by - `settings.SEARCH_AT_RESULT` and echoed automatically (on - error, return will be `None`). If `quiet=True`, the error - messaging is assumed to be handled by the caller. - - """ - is_string = isinstance(searchdata, str) - - if is_string: - # searchdata is a string; wrap some common self-references - if searchdata.lower() in ("here",): - return [self.location] if quiet else self.location - if searchdata.lower() in ("me", "self"): - return [self] if quiet else self - - if use_dbref is None: - use_dbref = self.locks.check_lockstring(self, "_dummy:perm(Builder)") - - if use_nicks: - # do nick-replacement on search - searchdata = self.nicks.nickreplace( - searchdata, categories=("object", "account"), include_account=True - ) - - if global_search or ( - is_string - and searchdata.startswith("#") - and len(searchdata) > 1 - and searchdata[1:].isdigit() - ): - # only allow exact matching if searching the entire database - # or unique #dbrefs - exact = True - candidates = None - - elif candidates is None: - # no custom candidates given - get them automatically - if location: - # location(s) were given - candidates = [] - for obj in make_iter(location): - candidates.extend(obj.contents) - else: - # local search. Candidates are taken from - # self.contents, self.location and - # self.location.contents - location = self.location - candidates = self.contents - if location: - candidates = candidates + [location] + location.contents - else: - # normally we don't need this since we are - # included in location.contents - candidates.append(self) - - results = ObjectDB.objects.object_search( - searchdata, - attribute_name=attribute_name, - typeclass=typeclass, - candidates=candidates, - exact=exact, - use_dbref=use_dbref, - ) - - if quiet: - return list(results) - return _AT_SEARCH_RESULT( - results, - self, - query=searchdata, - nofound_string=nofound_string, - multimatch_string=multimatch_string, - )
- -
[docs] def search_account(self, searchdata, quiet=False): - """ - Simple shortcut wrapper to search for accounts, not characters. - - Args: - searchdata (str): Search criterion - the key or dbref of the account - to search for. If this is "here" or "me", search - for the account connected to this object. - quiet (bool): Returns the results as a list rather than - echo eventual standard error messages. Default `False`. - - Returns: - result (Account, None or list): Just what is returned depends on - the `quiet` setting: - - `quiet=True`: No match or multumatch auto-echoes errors - to self.msg, then returns `None`. The esults are passed - through `settings.SEARCH_AT_RESULT` and - `settings.SEARCH_AT_MULTIMATCH_INPUT`. If there is a - unique match, this will be returned. - - `quiet=True`: No automatic error messaging is done, and - what is returned is always a list with 0, 1 or more - matching Accounts. - - """ - if isinstance(searchdata, str): - # searchdata is a string; wrap some common self-references - if searchdata.lower() in ("me", "self"): - return [self.account] if quiet else self.account - - results = search.search_account(searchdata) - - if quiet: - return results - return _AT_SEARCH_RESULT(results, self, query=searchdata)
- -
[docs] def execute_cmd(self, raw_string, session=None, **kwargs): - """ - Do something as this object. This is never called normally, - it's only used when wanting specifically to let an object be - the caller of a command. It makes use of nicks of eventual - connected accounts as well. - - Args: - raw_string (string): Raw command input - session (Session, optional): Session to - return results to - - Keyword Args: - Other keyword arguments will be added to the found command - object instace as variables before it executes. This is - unused by default Evennia but may be used to set flags and - change operating paramaters for commands at run-time. - - Returns: - defer (Deferred): This is an asynchronous Twisted object that - will not fire until the command has actually finished - executing. To overload this one needs to attach - callback functions to it, with addCallback(function). - This function will be called with an eventual return - value from the command execution. This return is not - used at all by Evennia by default, but might be useful - for coders intending to implement some sort of nested - command structure. - - """ - # break circular import issues - global _CMDHANDLER - if not _CMDHANDLER: - from evennia.commands.cmdhandler import cmdhandler as _CMDHANDLER - - # nick replacement - we require full-word matching. - # do text encoding conversion - raw_string = self.nicks.nickreplace( - raw_string, categories=("inputline", "channel"), include_account=True - ) - return _CMDHANDLER( - self, raw_string, callertype="object", session=session, **kwargs - )
- -
[docs] def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): - """ - Emits something to a session attached to the object. - - Args: - text (str or tuple, optional): The message to send. This - is treated internally like any send-command, so its - value can be a tuple if sending multiple arguments to - the `text` oob command. - from_obj (obj or list, optional): object that is sending. If - given, at_msg_send will be called. This value will be - passed on to the protocol. If iterable, will execute hook - on all entities in it. - session (Session or list, optional): Session or list of - Sessions to relay data to, if any. If set, will force send - to these sessions. If unset, who receives the message - depends on the MULTISESSION_MODE. - options (dict, optional): Message-specific option-value - pairs. These will be applied at the protocol level. - Keyword Args: - any (string or tuples): All kwarg keys not listed above - will be treated as send-command names and their arguments - (which can be a string or a tuple). - - Notes: - `at_msg_receive` will be called on this Object. - All extra kwargs will be passed on to the protocol. - - """ - # try send hooks - if from_obj: - for obj in make_iter(from_obj): - try: - obj.at_msg_send(text=text, to_obj=self, **kwargs) - except Exception: - logger.log_trace() - kwargs["options"] = options - try: - if not self.at_msg_receive(text=text, from_obj=from_obj, **kwargs): - # if at_msg_receive returns false, we abort message to this object - return - except Exception: - logger.log_trace() - - if text is not None: - if not (isinstance(text, str) or isinstance(text, tuple)): - # sanitize text before sending across the wire - try: - text = to_str(text) - except Exception: - text = repr(text) - kwargs["text"] = text - - # relay to session(s) - sessions = make_iter(session) if session else self.sessions.all() - for session in sessions: - session.data_out(**kwargs)
- -
[docs] def for_contents(self, func, exclude=None, **kwargs): - """ - Runs a function on every object contained within this one. - - Args: - func (callable): Function to call. This must have the - formal call sign func(obj, **kwargs), where obj is the - object currently being processed and `**kwargs` are - passed on from the call to `for_contents`. - exclude (list, optional): A list of object not to call the - function on. - - Keyword Args: - Keyword arguments will be passed to the function for all objects. - """ - contents = self.contents - if exclude: - exclude = make_iter(exclude) - contents = [obj for obj in contents if obj not in exclude] - for obj in contents: - func(obj, **kwargs)
- -
[docs] def msg_contents(self, text=None, exclude=None, from_obj=None, mapping=None, **kwargs): - """ - Emits a message to all objects inside this object. - - Args: - text (str or tuple): Message to send. If a tuple, this should be - on the valid OOB outmessage form `(message, {kwargs})`, - where kwargs are optional data passed to the `text` - outputfunc. - exclude (list, optional): A list of objects not to send to. - from_obj (Object, optional): An object designated as the - "sender" of the message. See `DefaultObject.msg()` for - more info. - mapping (dict, optional): A mapping of formatting keys - `{"key":<object>, "key2":<object2>,...}. The keys - must match `{key}` markers in the `text` if this is a string or - in the internal `message` if `text` is a tuple. These - formatting statements will be - replaced by the return of `<object>.get_display_name(looker)` - for every looker in contents that receives the - message. This allows for every object to potentially - get its own customized string. - Keyword Args: - Keyword arguments will be passed on to `obj.msg()` for all - messaged objects. - - Notes: - The `mapping` argument is required if `message` contains - {}-style format syntax. The keys of `mapping` should match - named format tokens, and its values will have their - `get_display_name()` function called for each object in - the room before substitution. If an item in the mapping does - not have `get_display_name()`, its string value will be used. - - Example: - Say Char is a Character object and Npc is an NPC object: - - char.location.msg_contents( - "{attacker} kicks {defender}", - mapping=dict(attacker=char, defender=npc), exclude=(char, npc)) - - This will result in everyone in the room seeing 'Char kicks NPC' - where everyone may potentially see different results for Char and Npc - depending on the results of `char.get_display_name(looker)` and - `npc.get_display_name(looker)` for each particular onlooker - - """ - # we also accept an outcommand on the form (message, {kwargs}) - is_outcmd = text and is_iter(text) - inmessage = text[0] if is_outcmd else text - outkwargs = text[1] if is_outcmd and len(text) > 1 else {} - - contents = self.contents - if exclude: - exclude = make_iter(exclude) - contents = [obj for obj in contents if obj not in exclude] - for obj in contents: - if mapping: - substitutions = { - t: sub.get_display_name(obj) if hasattr(sub, "get_display_name") else str(sub) - for t, sub in mapping.items() - } - outmessage = inmessage.format(**substitutions) - else: - outmessage = inmessage - obj.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
- -
[docs] def move_to( - self, - destination, - quiet=False, - emit_to_obj=None, - use_destination=True, - to_none=False, - move_hooks=True, - **kwargs, - ): - """ - Moves this object to a new location. - - Args: - destination (Object): Reference to the object to move to. This - can also be an exit object, in which case the - destination property is used as destination. - quiet (bool): If true, turn off the calling of the emit hooks - (announce_move_to/from etc) - emit_to_obj (Object): object to receive error messages - use_destination (bool): Default is for objects to use the "destination" - property of destinations as the target to move to. Turning off this - keyword allows objects to move "inside" exit objects. - to_none (bool): Allow destination to be None. Note that no hooks are run when - moving to a None location. If you want to run hooks, run them manually - (and make sure they can manage None locations). - move_hooks (bool): If False, turn off the calling of move-related hooks - (at_before/after_move etc) with quiet=True, this is as quiet a move - as can be done. - - Keyword Args: - Passed on to announce_move_to and announce_move_from hooks. - - Returns: - result (bool): True/False depending on if there were problems with the move. - This method may also return various error messages to the - `emit_to_obj`. - - Notes: - No access checks are done in this method, these should be handled before - calling `move_to`. - - The `DefaultObject` hooks called (if `move_hooks=True`) are, in order: - - 1. `self.at_before_move(destination)` (if this returns False, move is aborted) - 2. `source_location.at_object_leave(self, destination)` - 3. `self.announce_move_from(destination)` - 4. (move happens here) - 5. `self.announce_move_to(source_location)` - 6. `destination.at_object_receive(self, source_location)` - 7. `self.at_after_move(source_location)` - - """ - - def logerr(string="", err=None): - """Simple log helper method""" - logger.log_trace() - self.msg("%s%s" % (string, "" if err is None else " (%s)" % err)) - return - - errtxt = _("Couldn't perform move ('%s'). Contact an admin.") - if not emit_to_obj: - emit_to_obj = self - - if not destination: - if to_none: - # immediately move to None. There can be no hooks called since - # there is no destination to call them with. - self.location = None - return True - emit_to_obj.msg(_("The destination doesn't exist.")) - return False - if destination.destination and use_destination: - # traverse exits - destination = destination.destination - - # Before the move, call eventual pre-commands. - if move_hooks: - try: - if not self.at_before_move(destination): - return False - except Exception as err: - logerr(errtxt % "at_before_move()", err) - return False - - # Save the old location - source_location = self.location - - # Call hook on source location - if move_hooks and source_location: - try: - source_location.at_object_leave(self, destination) - except Exception as err: - logerr(errtxt % "at_object_leave()", err) - return False - - if not quiet: - # tell the old room we are leaving - try: - self.announce_move_from(destination, **kwargs) - except Exception as err: - logerr(errtxt % "at_announce_move()", err) - return False - - # Perform move - try: - self.location = destination - except Exception as err: - logerr(errtxt % "location change", err) - return False - - if not quiet: - # Tell the new room we are there. - try: - self.announce_move_to(source_location, **kwargs) - except Exception as err: - logerr(errtxt % "announce_move_to()", err) - return False - - if move_hooks: - # Perform eventual extra commands on the receiving location - # (the object has already arrived at this point) - try: - destination.at_object_receive(self, source_location) - except Exception as err: - logerr(errtxt % "at_object_receive()", err) - return False - - # Execute eventual extra commands on this object after moving it - # (usually calling 'look') - if move_hooks: - try: - self.at_after_move(source_location) - except Exception as err: - logerr(errtxt % "at_after_move", err) - return False - return True
- -
[docs] def clear_exits(self): - """ - Destroys all of the exits and any exits pointing to this - object as a destination. - """ - for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]: - out_exit.delete() - for in_exit in ObjectDB.objects.filter(db_destination=self): - in_exit.delete()
- -
[docs] def clear_contents(self): - """ - Moves all objects (accounts/things) to their home location or - to default home. - """ - # Gather up everything that thinks this is its location. - default_home_id = int(settings.DEFAULT_HOME.lstrip("#")) - try: - default_home = ObjectDB.objects.get(id=default_home_id) - if default_home.dbid == self.dbid: - # we are deleting default home! - default_home = None - except Exception: - string = _("Could not find default home '(#%d)'.") - logger.log_err(string % default_home_id) - default_home = None - - for obj in self.contents: - home = obj.home - # Obviously, we can't send it back to here. - if not home or (home and home.dbid == self.dbid): - obj.home = default_home - home = default_home - - # If for some reason it's still None... - if not home: - string = "Missing default home, '%s(#%d)' " - string += "now has a null location." - obj.location = None - obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin.")) - logger.log_err(string % (obj.name, obj.dbid)) - return - - if obj.has_account: - if home: - string = "Your current location has ceased to exist," - string += " moving you to %s(#%d)." - obj.msg(_(string) % (home.name, home.dbid)) - else: - # Famous last words: The account should never see this. - string = "This place should not exist ... contact an admin." - obj.msg(_(string)) - obj.move_to(home)
- -
[docs] @classmethod - def create(cls, key, account=None, **kwargs): - """ - Creates a basic object with default parameters, unless otherwise - specified or extended. - - Provides a friendlier interface to the utils.create_object() function. - - Args: - key (str): Name of the new object. - account (Account): Account to attribute this object to. - - Keyword Args: - description (str): Brief description for this object. - ip (str): IP address of creator (for object auditing). - - Returns: - object (Object): A newly created object of the given typeclass. - errors (list): A list of errors in string form, if any. - - """ - errors = [] - obj = None - - # Get IP address of creator, if available - ip = kwargs.pop("ip", "") - - # If no typeclass supplied, use this class - kwargs["typeclass"] = kwargs.pop("typeclass", cls) - - # Set the supplied key as the name of the intended object - kwargs["key"] = key - - # Get a supplied description, if any - description = kwargs.pop("description", "") - - # Create a sane lockstring if one wasn't supplied - lockstring = kwargs.get("locks") - if account and not lockstring: - lockstring = cls.lockstring.format(account_id=account.id) - kwargs["locks"] = lockstring - - # Create object - try: - obj = create.create_object(**kwargs) - - # Record creator id and creation IP - if ip: - obj.db.creator_ip = ip - if account: - obj.db.creator_id = account.id - - # Set description if there is none, or update it if provided - if description or not obj.db.desc: - desc = description if description else "You see nothing special." - obj.db.desc = desc - - except Exception as e: - errors.append("An error occurred while creating this '%s' object." % key) - logger.log_err(e) - - return obj, errors
- -
[docs] def copy(self, new_key=None, **kwargs): - """ - Makes an identical copy of this object, identical except for a - new dbref in the database. If you want to customize the copy - by changing some settings, use ObjectDB.object.copy_object() - directly. - - Args: - new_key (string): New key/name of copied object. If new_key is not - specified, the copy will be named <old_key>_copy by default. - Returns: - copy (Object): A copy of this object. - - """ - - def find_clone_key(): - """ - 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 - """ - key = self.key - num = sum( - 1 - for obj in self.location.contents - if obj.key.startswith(key) and obj.key.lstrip(key).isdigit() - ) - return "%s%03i" % (key, num) - - new_key = new_key or find_clone_key() - new_obj = ObjectDB.objects.copy_object(self, new_key=new_key, **kwargs) - self.at_object_post_copy(new_obj, **kwargs) - return new_obj
- -
[docs] def at_object_post_copy(self, new_obj, **kwargs): - """ - Called by DefaultObject.copy(). Meant to be overloaded. In case there's extra data not covered by - .copy(), this can be used to deal with it. - - Args: - new_obj (Object): The new Copy of this object. - - Returns: - None - """ - pass
- -
[docs] def delete(self): - """ - Deletes this object. Before deletion, this method makes sure - to move all contained objects to their respective home - locations, as well as clean up all exits to/from the object. - - Returns: - noerror (bool): Returns whether or not the delete completed - successfully or not. - - """ - global _ScriptDB - if not _ScriptDB: - from evennia.scripts.models import ScriptDB as _ScriptDB - - if not self.pk or not self.at_object_delete(): - # This object has already been deleted, - # or the pre-delete check return False - return False - - # See if we need to kick the account off. - - for session in self.sessions.all(): - session.msg(_("Your character {key} has been destroyed.").format(key=self.key)) - # no need to disconnect, Account just jumps to OOC mode. - # sever the connection (important!) - if self.account: - # Remove the object from playable characters list - if self in self.account.db._playable_characters: - self.account.db._playable_characters = [ - x for x in self.account.db._playable_characters if x != self - ] - for session in self.sessions.all(): - self.account.unpuppet_object(session) - - self.account = None - - for script in _ScriptDB.objects.get_all_scripts_on_obj(self): - script.stop() - - # Destroy any exits to and from this room, if any - self.clear_exits() - # Clear out any non-exit objects located within the object - self.clear_contents() - self.attributes.clear() - self.nicks.clear() - self.aliases.clear() - self.location = None # this updates contents_cache for our location - - # Perform the deletion of the object - super().delete() - return True
- -
[docs] def access( - self, accessing_obj, access_type="read", default=False, no_superuser_bypass=False, **kwargs - ): - """ - Determines if another object has permission to access this object - in whatever way. - - Args: - accessing_obj (Object): Object trying to access this one. - access_type (str, optional): Type of access sought. - default (bool, optional): What to return if no lock of access_type was found. - no_superuser_bypass (bool, optional): If `True`, don't skip - lock check for superuser (be careful with this one). - - Keyword Args: - Passed on to the at_access hook along with the result of the access check. - - """ - result = super().access( - accessing_obj, - access_type=access_type, - default=default, - no_superuser_bypass=no_superuser_bypass, - ) - self.at_access(result, accessing_obj, access_type, **kwargs) - return result
- - # - # Hook methods - # - -
[docs] def at_first_save(self): - """ - This is called by the typeclass system whenever an instance of - this class is saved for the first time. It is a generic hook - for calling the startup hooks for the various game entities. - When overloading you generally don't overload this but - overload the hooks called by this method. - - """ - self.basetype_setup() - self.at_object_creation() - - if hasattr(self, "_createdict"): - # this will only be set if the utils.create function - # was used to create the object. We want the create - # call's kwargs to override the values set by hooks. - cdict = self._createdict - updates = [] - if not cdict.get("key"): - if not self.db_key: - self.db_key = "#%i" % self.dbid - updates.append("db_key") - elif self.key != cdict.get("key"): - updates.append("db_key") - self.db_key = cdict["key"] - if cdict.get("location") and self.location != cdict["location"]: - self.db_location = cdict["location"] - updates.append("db_location") - if cdict.get("home") and self.home != cdict["home"]: - self.home = cdict["home"] - updates.append("db_home") - if cdict.get("destination") and self.destination != cdict["destination"]: - self.destination = cdict["destination"] - updates.append("db_destination") - if updates: - self.save(update_fields=updates) - - if cdict.get("permissions"): - self.permissions.batch_add(*cdict["permissions"]) - if cdict.get("locks"): - self.locks.add(cdict["locks"]) - if cdict.get("aliases"): - self.aliases.batch_add(*cdict["aliases"]) - if cdict.get("location"): - cdict["location"].at_object_receive(self, None) - self.at_after_move(None) - if cdict.get("tags"): - # this should be a list of tags, tuples (key, category) or (key, category, data) - self.tags.batch_add(*cdict["tags"]) - if cdict.get("attributes"): - # this should be tuples (key, val, ...) - self.attributes.batch_add(*cdict["attributes"]) - if cdict.get("nattributes"): - # this should be a dict of nattrname:value - for key, value in cdict["nattributes"]: - self.nattributes.add(key, value) - - del self._createdict - - self.basetype_posthook_setup()
- - # hooks called by the game engine # - -
[docs] def basetype_setup(self): - """ - This sets up the default properties of an Object, just before - the more general at_object_creation. - - You normally don't need to change this unless you change some - fundamental things like names of permission groups. - - """ - # the default security setup fallback for a generic - # object. Overload in child for a custom setup. Also creation - # commands may set this (create an item and you should be its - # controller, for example) - - self.locks.add( - ";".join( - [ - "control:perm(Developer)", # edit locks/permissions, delete - "examine:perm(Builder)", # examine properties - "view:all()", # look at object (visibility) - "edit:perm(Admin)", # edit properties/attributes - "delete:perm(Admin)", # delete object - "get:all()", # pick up object - "drop:holds()", # drop only that which you hold - "call:true()", # allow to call commands on this object - "tell:perm(Admin)", # allow emits to this object - "puppet:pperm(Developer)", - ] - ) - ) # lock down puppeting only to staff by default
- -
[docs] def basetype_posthook_setup(self): - """ - Called once, after basetype_setup and at_object_creation. This - should generally not be overloaded unless you are redefining - how a room/exit/object works. It allows for basetype-like - setup after the object is created. An example of this is - EXITs, who need to know keys, aliases, locks etc to set up - their exit-cmdsets. - - """ - pass
- -
[docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - - """ - pass
- -
[docs] def at_object_delete(self): - """ - Called just before the database object is permanently - delete()d from the database. If this method returns False, - deletion is aborted. - - """ - return True
- -
[docs] def at_init(self): - """ - This is always called whenever this object is initiated -- - that is, whenever it its typeclass is cached from memory. This - happens on-demand first time the object is used or activated - in some way after being created but also after each server - restart or reload. - - """ - pass
- -
[docs] def at_cmdset_get(self, **kwargs): - """ - Called just before cmdsets on this object are requested by the - command handler. If changes need to be done on the fly to the - cmdset before passing them on to the cmdhandler, this is the - place to do it. This is called also if the object currently - have no cmdsets. - - Keyword Args: - caller (Session, Object or Account): The caller requesting - this cmdset. - - """ - pass
- -
[docs] def at_pre_puppet(self, account, session=None, **kwargs): - """ - Called just before an Account connects to this object to puppet - it. - - Args: - account (Account): This is the connecting account. - session (Session): Session controlling the connection. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_post_puppet(self, **kwargs): - """ - Called just after puppeting has been completed and all - Account<->Object links have been established. - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - Note: - You can use `self.account` and `self.sessions.get()` to get - account and sessions at this point; the last entry in the - list from `self.sessions.get()` is the latest Session - puppeting this Object. - - """ - self.msg(f"You become |w{self.key}|n.") - self.account.db._last_puppet = self
- -
[docs] def at_pre_unpuppet(self, **kwargs): - """ - Called just before beginning to un-connect a puppeting from - this Account. - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - Note: - You can use `self.account` and `self.sessions.get()` to get - account and sessions at this point; the last entry in the - list from `self.sessions.get()` is the latest Session - puppeting this Object. - - """ - pass
- -
[docs] def at_post_unpuppet(self, account, session=None, **kwargs): - """ - Called just after the Account successfully disconnected from - this object, severing all connections. - - Args: - account (Account): The account object that just disconnected - from this object. - session (Session): Session id controlling the connection that - just disconnected. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_server_reload(self): - """ - This hook is called whenever the server is shutting down for - restart/reboot. If you want to, for example, save non-persistent - properties across a restart, this is the place to do it. - - """ - pass
- -
[docs] def at_server_shutdown(self): - """ - This hook is called whenever the server is shutting down fully - (i.e. not for a restart). - - """ - pass
- -
[docs] def at_access(self, result, accessing_obj, access_type, **kwargs): - """ - This is called with the result of an access call, along with - any kwargs used for that call. The return of this method does - not affect the result of the lock check. It can be used e.g. to - customize error messages in a central location or other effects - based on the access result. - - Args: - result (bool): The outcome of the access call. - accessing_obj (Object or Account): The entity trying to gain access. - access_type (str): The type of access that was requested. - - Keyword Args: - Not used by default, added for possible expandability in a - game. - - """ - pass
- - # hooks called when moving the object - -
[docs] def at_before_move(self, destination, **kwargs): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # return has_perm(self, destination, "can_move") - return True
- -
[docs] def announce_move_from(self, destination, msg=None, mapping=None, **kwargs): - """ - Called if the move is to be announced. This is - called while we are still standing in the old - location. - - Args: - destination (Object): The place we are going to. - msg (str, optional): a replacement message. - mapping (dict, optional): additional mapping objects. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - You can override this method and call its parent with a - message to simply change the default message. In the string, - you can use the following as mappings (between braces): - object: the object which is moving. - exit: the exit from which the object is moving (if found). - origin: the location of the object before the move. - destination: the location of the object after moving. - - """ - if not self.location: - return - if msg: - string = msg - else: - string = "{object} is leaving {origin}, heading for {destination}." - - location = self.location - exits = [ - o for o in location.contents if o.location is location and o.destination is destination - ] - if not mapping: - mapping = {} - - mapping.update( - { - "object": self, - "exit": exits[0] if exits else "somewhere", - "origin": location or "nowhere", - "destination": destination or "nowhere", - } - ) - - location.msg_contents(string, exclude=(self,), from_obj=self, mapping=mapping)
- -
[docs] def announce_move_to(self, source_location, msg=None, mapping=None, **kwargs): - """ - Called after the move if the move was not quiet. At this point - we are standing in the new location. - - Args: - source_location (Object): The place we came from - msg (str, optional): the replacement message if location. - mapping (dict, optional): additional mapping objects. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - You can override this method and call its parent with a - message to simply change the default message. In the string, - you can use the following as mappings (between braces): - object: the object which is moving. - exit: the exit from which the object is moving (if found). - origin: the location of the object before the move. - destination: the location of the object after moving. - - """ - - if not source_location and self.location.has_account: - # This was created from nowhere and added to an account's - # inventory; it's probably the result of a create command. - string = "You now have %s in your possession." % self.get_display_name(self.location) - self.location.msg(string) - return - - if source_location: - if msg: - string = msg - else: - string = "{object} arrives to {destination} from {origin}." - else: - string = "{object} arrives to {destination}." - - origin = source_location - destination = self.location - exits = [] - if origin: - exits = [ - o - for o in destination.contents - if o.location is destination and o.destination is origin - ] - - if not mapping: - mapping = {} - - mapping.update( - { - "object": self, - "exit": exits[0] if exits else "somewhere", - "origin": origin or "nowhere", - "destination": destination or "nowhere", - } - ) - - destination.msg_contents(string, exclude=(self,), from_obj=self, mapping=mapping)
- -
[docs] def at_after_move(self, source_location, **kwargs): - """ - Called after move has completed, regardless of quiet mode or - not. Allows changes to the object due to the location it is - now in. - - Args: - source_location (Object): Wwhere we came from. This may be `None`. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_object_leave(self, moved_obj, target_location, **kwargs): - """ - Called just before an object leaves from inside this object - - Args: - moved_obj (Object): The object leaving - target_location (Object): Where `moved_obj` is going. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_object_receive(self, moved_obj, source_location, **kwargs): - """ - Called after an object has been moved into this object. - - Args: - moved_obj (Object): The object moved into this one - source_location (Object): Where `moved_object` came from. - Note that this could be `None`. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_traverse(self, traversing_object, target_location, **kwargs): - """ - This hook is responsible for handling the actual traversal, - normally by calling - `traversing_object.move_to(target_location)`. It is normally - only implemented by Exit objects. If it returns False (usually - because `move_to` returned False), `at_after_traverse` below - should not be called and instead `at_failed_traverse` should be - called. - - Args: - traversing_object (Object): Object traversing us. - target_location (Object): Where target is going. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_after_traverse(self, traversing_object, source_location, **kwargs): - """ - Called just after an object successfully used this object to - traverse to another object (i.e. this object is a type of - Exit) - - Args: - traversing_object (Object): The object traversing us. - source_location (Object): Where `traversing_object` came from. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - The target location should normally be available as `self.destination`. - """ - pass
- -
[docs] def at_failed_traverse(self, traversing_object, **kwargs): - """ - This is called if an object fails to traverse this object for - some reason. - - Args: - traversing_object (Object): The object that failed traversing us. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - Using the default exits, this hook will not be called if an - Attribute `err_traverse` is defined - this will in that case be - read for an error string instead. - - """ - pass
- -
[docs] def at_msg_receive(self, text=None, from_obj=None, **kwargs): - """ - This hook is called whenever someone sends a message to this - object using the `msg` method. - - Note that from_obj may be None if the sender did not include - itself as an argument to the obj.msg() call - so you have to - check for this. . - - Consider this a pre-processing method before msg is passed on - to the user session. If this method returns False, the msg - will not be passed on. - - Args: - text (str, optional): The message received. - from_obj (any, optional): The object sending the message. - - Keyword Args: - This includes any keywords sent to the `msg` method. - - Returns: - receive (bool): If this message should be received. - - Notes: - If this method returns False, the `msg` operation - will abort without sending the message. - - """ - return True
- -
[docs] def at_msg_send(self, text=None, to_obj=None, **kwargs): - """ - This is a hook that is called when *this* object sends a - message to another object with `obj.msg(text, to_obj=obj)`. - - Args: - text (str, optional): Text to send. - to_obj (any, optional): The object to send to. - - Keyword Args: - Keywords passed from msg() - - Notes: - Since this method is executed by `from_obj`, if no `from_obj` - was passed to `DefaultCharacter.msg` this hook will never - get called. - - """ - pass
- - # hooks called by the default cmdset. - -
[docs] def return_appearance(self, looker, **kwargs): - """ - This formats a description. It is the hook a 'look' command - should call. - - Args: - looker (Object): Object doing the looking. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - """ - if not looker: - return "" - # get and identify all objects - visible = (con for con in self.contents if con != looker and con.access(looker, "view")) - exits, users, things = [], [], defaultdict(list) - for con in visible: - key = con.get_display_name(looker) - if con.destination: - exits.append(key) - elif con.has_account: - users.append("|c%s|n" % key) - else: - # things can be pluralized - things[key].append(con) - # get description, build string - string = "|c%s|n\n" % self.get_display_name(looker) - desc = self.db.desc - if desc: - string += "%s" % desc - if exits: - string += "\n|wExits:|n " + list_to_string(exits) - if users or things: - # handle pluralization of things (never pluralize users) - thing_strings = [] - for key, itemlist in sorted(things.items()): - nitem = len(itemlist) - if nitem == 1: - key, _ = itemlist[0].get_numbered_name(nitem, looker, key=key) - else: - key = [item.get_numbered_name(nitem, looker, key=key)[1] for item in itemlist][ - 0 - ] - thing_strings.append(key) - - string += "\n|wYou see:|n " + list_to_string(users + thing_strings) - - return string
- -
[docs] def at_look(self, target, **kwargs): - """ - Called when this object performs a look. It allows to - customize just what this means. It will not itself - send any data. - - Args: - target (Object): The target being looked at. This is - commonly an object or the current location. It will - be checked for the "view" type access. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call. This will be passed into - return_appearance, get_display_name and at_desc but is not used - by default. - - Returns: - lookstring (str): A ready-processed look string - potentially ready to return to the looker. - - """ - if not target.access(self, "view"): - try: - return "Could not view '%s'." % target.get_display_name(self, **kwargs) - except AttributeError: - return "Could not view '%s'." % target.key - - description = target.return_appearance(self, **kwargs) - - # the target's at_desc() method. - # this must be the last reference to target so it may delete itself when acted on. - target.at_desc(looker=self, **kwargs) - - return description
- -
[docs] def at_desc(self, looker=None, **kwargs): - """ - This is called whenever someone looks at this object. - - Args: - looker (Object, optional): The object requesting the description. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_before_get(self, getter, **kwargs): - """ - Called by the default `get` command before this object has been - picked up. - - Args: - getter (Object): The object about to get this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - shouldget (bool): If the object should be gotten or not. - - Notes: - If this method returns False/None, the getting is cancelled - before it is even started. - """ - return True
- -
[docs] def at_get(self, getter, **kwargs): - """ - Called by the default `get` command when this object has been - picked up. - - Args: - getter (Object): The object getting this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - This hook cannot stop the pickup from happening. Use - permissions or the at_before_get() hook for that. - - """ - pass
- -
[docs] def at_before_give(self, giver, getter, **kwargs): - """ - Called by the default `give` command before this object has been - given. - - Args: - giver (Object): The object about to give this object. - getter (Object): The object about to get this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - shouldgive (bool): If the object should be given or not. - - Notes: - If this method returns False/None, the giving is cancelled - before it is even started. - - """ - return True
- -
[docs] def at_give(self, giver, getter, **kwargs): - """ - Called by the default `give` command when this object has been - given. - - Args: - giver (Object): The object giving this object. - getter (Object): The object getting this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - This hook cannot stop the give from happening. Use - permissions or the at_before_give() hook for that. - - """ - pass
- -
[docs] def at_before_drop(self, dropper, **kwargs): - """ - Called by the default `drop` command before this object has been - dropped. - - Args: - dropper (Object): The object which will drop this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - shoulddrop (bool): If the object should be dropped or not. - - Notes: - If this method returns False/None, the dropping is cancelled - before it is even started. - - """ - if not self.locks.get("drop"): - # TODO: This if-statment will be removed in Evennia 1.0 - return True - if not self.access(dropper, "drop", default=False): - dropper.msg(f"You cannot drop {self.get_display_name(dropper)}") - return False - return True
- -
[docs] def at_drop(self, dropper, **kwargs): - """ - Called by the default `drop` command when this object has been - dropped. - - Args: - dropper (Object): The object which just dropped this object. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - This hook cannot stop the drop from happening. Use - permissions or the at_before_drop() hook for that. - - """ - pass
- -
[docs] def at_before_say(self, message, **kwargs): - """ - Before the object says something. - - This hook is by default used by the 'say' and 'whisper' - commands as used by this command it is called before the text - is said/whispered and can be used to customize the outgoing - text from the object. Returning `None` aborts the command. - - Args: - message (str): The suggested say/whisper text spoken by self. - Keyword Args: - whisper (bool): If True, this is a whisper rather than - a say. This is sent by the whisper command by default. - Other verbal commands could use this hook in similar - ways. - receivers (Object or iterable): If set, this is the target or targets for the say/whisper. - - Returns: - message (str): The (possibly modified) text to be spoken. - - """ - return message
- -
[docs] def at_say( - self, - message, - msg_self=None, - msg_location=None, - receivers=None, - msg_receivers=None, - **kwargs, - ): - """ - Display the actual say (or whisper) of self. - - This hook should display the actual say/whisper of the object in its - location. It should both alert the object (self) and its - location that some text is spoken. The overriding of messages or - `mapping` allows for simple customization of the hook without - re-writing it completely. - - Args: - message (str): The message to convey. - msg_self (bool or str, optional): If boolean True, echo `message` to self. If a string, - return that message. If False or unset, don't echo to self. - msg_location (str, optional): The message to echo to self's location. - receivers (Object or iterable, optional): An eventual receiver or receivers of the message - (by default only used by whispers). - msg_receivers(str): Specific message to pass to the receiver(s). This will parsed - with the {receiver} placeholder replaced with the given receiver. - Keyword Args: - whisper (bool): If this is a whisper rather than a say. Kwargs - can be used by other verbal commands in a similar way. - mapping (dict): Pass an additional mapping to the message. - - Notes: - - - Messages can contain {} markers. These are substituted against the values - passed in the `mapping` argument. - - msg_self = 'You say: "{speech}"' - msg_location = '{object} says: "{speech}"' - msg_receivers = '{object} whispers: "{speech}"' - - Supported markers by default: - {self}: text to self-reference with (default 'You') - {speech}: the text spoken/whispered by self. - {object}: the object speaking. - {receiver}: replaced with a single receiver only for strings meant for a specific - receiver (otherwise 'None'). - {all_receivers}: comma-separated list of all receivers, - if more than one, otherwise same as receiver - {location}: the location where object is. - - """ - msg_type = "say" - if kwargs.get("whisper", False): - # whisper mode - msg_type = "whisper" - msg_self = ( - '{self} whisper to {all_receivers}, "|n{speech}|n"' - if msg_self is True - else msg_self - ) - msg_receivers = msg_receivers or '{object} whispers: "|n{speech}|n"' - msg_location = None - else: - msg_self = '{self} say, "|n{speech}|n"' if msg_self is True else msg_self - msg_location = msg_location or '{object} says, "{speech}"' - msg_receivers = msg_receivers or message - - custom_mapping = kwargs.get("mapping", {}) - receivers = make_iter(receivers) if receivers else None - location = self.location - - if msg_self: - self_mapping = { - "self": "You", - "object": self.get_display_name(self), - "location": location.get_display_name(self) if location else None, - "receiver": None, - "all_receivers": ", ".join(recv.get_display_name(self) for recv in receivers) - if receivers - else None, - "speech": message, - } - self_mapping.update(custom_mapping) - self.msg(text=(msg_self.format(**self_mapping), {"type": msg_type}), from_obj=self) - - if receivers and msg_receivers: - receiver_mapping = { - "self": "You", - "object": None, - "location": None, - "receiver": None, - "all_receivers": None, - "speech": message, - } - for receiver in make_iter(receivers): - individual_mapping = { - "object": self.get_display_name(receiver), - "location": location.get_display_name(receiver), - "receiver": receiver.get_display_name(receiver), - "all_receivers": ", ".join(recv.get_display_name(recv) for recv in receivers) - if receivers - else None, - } - receiver_mapping.update(individual_mapping) - receiver_mapping.update(custom_mapping) - receiver.msg( - text=(msg_receivers.format(**receiver_mapping), {"type": msg_type}), - from_obj=self, - ) - - if self.location and msg_location: - location_mapping = { - "self": "You", - "object": self, - "location": location, - "all_receivers": ", ".join(str(recv) for recv in receivers) if receivers else None, - "receiver": None, - "speech": message, - } - location_mapping.update(custom_mapping) - exclude = [] - if msg_self: - exclude.append(self) - if receivers: - exclude.extend(receivers) - self.location.msg_contents( - text=(msg_location, {"type": msg_type}), - from_obj=self, - exclude=exclude, - mapping=location_mapping, - )
- - -# -# Base Character object -# - - -
[docs]class DefaultCharacter(DefaultObject): - """ - This implements an Object puppeted by a Session - that is, - a character avatar controlled by an account. - - """ - - # lockstring of newly created rooms, for easy overloading. - # Will be formatted with the appropriate attributes. - lockstring = ( - "puppet:id({character_id}) or pid({account_id}) or perm(Developer) or pperm(Developer);" - "delete:id({account_id}) or perm(Admin)" - ) - -
[docs] @classmethod - def create(cls, key, account=None, **kwargs): - """ - Creates a basic Character with default parameters, unless otherwise - specified or extended. - - Provides a friendlier interface to the utils.create_character() function. - - Args: - key (str): Name of the new Character. - account (obj, optional): Account to associate this Character with. - If unset supplying None-- it will - change the default lockset and skip creator attribution. - - Keyword Args: - description (str): Brief description for this object. - ip (str): IP address of creator (for object auditing). - All other kwargs will be passed into the create_object call. - - Returns: - character (Object): A newly created Character of the given typeclass. - errors (list): A list of errors in string form, if any. - - """ - errors = [] - obj = None - # Get IP address of creator, if available - ip = kwargs.pop("ip", "") - - # If no typeclass supplied, use this class - kwargs["typeclass"] = kwargs.pop("typeclass", cls) - - # Set the supplied key as the name of the intended object - kwargs["key"] = key - - # Get permissions - kwargs["permissions"] = kwargs.get("permissions", settings.PERMISSION_ACCOUNT_DEFAULT) - - # Get description if provided - description = kwargs.pop("description", "") - - # Get locks if provided - locks = kwargs.pop("locks", "") - - try: - # Check to make sure account does not have too many chars - if account: - if len(account.characters) >= settings.MAX_NR_CHARACTERS: - errors.append("There are too many characters associated with this account.") - return obj, errors - - # Create the Character - obj = create.create_object(**kwargs) - - # Record creator id and creation IP - if ip: - obj.db.creator_ip = ip - if account: - obj.db.creator_id = account.id - if obj not in account.characters: - account.db._playable_characters.append(obj) - - # Add locks - if not locks and account: - # Allow only the character itself and the creator account to puppet this character (and Developers). - locks = cls.lockstring.format(**{"character_id": obj.id, "account_id": account.id}) - elif not locks and not account: - locks = cls.lockstring.format(**{"character_id": obj.id, "account_id": -1}) - - obj.locks.add(locks) - - # If no description is set, set a default description - if description or not obj.db.desc: - obj.db.desc = description if description else "This is a character." - - except Exception as e: - errors.append("An error occurred while creating this '%s' object." % key) - logger.log_err(e) - - return obj, errors
- -
[docs] def basetype_setup(self): - """ - Setup character-specific security. - - You should normally not need to overload this, but if you do, - make sure to reproduce at least the two last commands in this - method (unless you want to fundamentally change how a - Character object works). - - """ - super().basetype_setup() - self.locks.add( - ";".join(["get:false()", "call:false()"]) # noone can pick up the character - ) # no commands can be called on character from outside - # add the default cmdset - self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
- -
[docs] def at_after_move(self, source_location, **kwargs): - """ - We make sure to look around after a move. - - """ - if self.location.access(self, "view"): - self.msg(self.at_look(self.location))
- -
[docs] def at_pre_puppet(self, account, session=None, **kwargs): - """ - Return the character from storage in None location in `at_post_unpuppet`. - Args: - account (Account): This is the connecting account. - session (Session): Session controlling the connection. - """ - if ( - self.location is None - ): # Make sure character's location is never None before being puppeted. - # Return to last location (or home, which should always exist), - self.location = self.db.prelogout_location if self.db.prelogout_location else self.home - self.location.at_object_receive( - self, None - ) # and trigger the location's reception hook. - if self.location: # If the character is verified to be somewhere, - self.db.prelogout_location = self.location # save location again to be sure. - else: - account.msg( - "|r%s has no location and no home is set.|n" % self, session=session - ) # Note to set home.
- -
[docs] def at_post_puppet(self, **kwargs): - """ - Called just after puppeting has been completed and all - Account<->Object links have been established. - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - Note: - You can use `self.account` and `self.sessions.get()` to get - account and sessions at this point; the last entry in the - list from `self.sessions.get()` is the latest Session - puppeting this Object. - - """ - self.msg("\nYou become |c%s|n.\n" % self.name) - self.msg((self.at_look(self.location), {"type": "look"}), options=None) - - def message(obj, from_obj): - obj.msg("%s has entered the game." % self.get_display_name(obj), from_obj=from_obj) - - self.location.for_contents(message, exclude=[self], from_obj=self)
- -
[docs] def at_post_unpuppet(self, account, session=None, **kwargs): - """ - We stove away the character when the account goes ooc/logs off, - otherwise the character object will remain in the room also - after the account logged off ("headless", so to say). - - Args: - account (Account): The account object that just disconnected - from this object. - session (Session): Session controlling the connection that - just disconnected. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - """ - if not self.sessions.count(): - # only remove this char from grid if no sessions control it anymore. - if self.location: - - def message(obj, from_obj): - obj.msg("%s has left the game." % self.get_display_name(obj), from_obj=from_obj) - - self.location.for_contents(message, exclude=[self], from_obj=self) - self.db.prelogout_location = self.location - self.location = None
- - @property - def idle_time(self): - """ - Returns the idle time of the least idle session in seconds. If - no sessions are connected it returns nothing. - """ - idle = [session.cmd_last_visible for session in self.sessions.all()] - if idle: - return time.time() - float(max(idle)) - return None - - @property - def connection_time(self): - """ - Returns the maximum connection time of all connected sessions - in seconds. Returns nothing if there are no sessions. - """ - conn = [session.conn_time for session in self.sessions.all()] - if conn: - return time.time() - float(min(conn)) - return None
- - -# -# Base Room object - - -
[docs]class DefaultRoom(DefaultObject): - """ - This is the base room object. It's just like any Object except its - location is always `None`. - """ - - # lockstring of newly created rooms, for easy overloading. - # Will be formatted with the {id} of the creating object. - lockstring = ( - "control:id({id}) or perm(Admin); " - "delete:id({id}) or perm(Admin); " - "edit:id({id}) or perm(Admin)" - ) - -
[docs] @classmethod - def create(cls, key, account=None, **kwargs): - """ - Creates a basic Room with default parameters, unless otherwise - specified or extended. - - Provides a friendlier interface to the utils.create_object() function. - - Args: - key (str): Name of the new Room. - account (obj, optional): Account to associate this Room with. If - given, it will be given specific control/edit permissions to this - object (along with normal Admin perms). If not given, default - - Keyword Args: - description (str): Brief description for this object. - ip (str): IP address of creator (for object auditing). - - Returns: - room (Object): A newly created Room of the given typeclass. - errors (list): A list of errors in string form, if any. - - """ - errors = [] - obj = None - - # Get IP address of creator, if available - ip = kwargs.pop("ip", "") - - # If no typeclass supplied, use this class - kwargs["typeclass"] = kwargs.pop("typeclass", cls) - - # Set the supplied key as the name of the intended object - kwargs["key"] = key - - # Get who to send errors to - kwargs["report_to"] = kwargs.pop("report_to", account) - - # Get description, if provided - description = kwargs.pop("description", "") - - # get locks if provided - locks = kwargs.pop("locks", "") - - try: - # Create the Room - obj = create.create_object(**kwargs) - - # Add locks - if not locks and account: - locks = cls.lockstring.format(**{"id": account.id}) - elif not locks and not account: - locks = cls.lockstring(**{"id": obj.id}) - - obj.locks.add(locks) - - # Record creator id and creation IP - if ip: - obj.db.creator_ip = ip - if account: - obj.db.creator_id = account.id - - # If no description is set, set a default description - if description or not obj.db.desc: - obj.db.desc = description if description else "This is a room." - - except Exception as e: - errors.append("An error occurred while creating this '%s' object." % key) - logger.log_err(e) - - return obj, errors
- -
[docs] def basetype_setup(self): - """ - Simple room setup setting locks to make sure the room - cannot be picked up. - - """ - - super().basetype_setup() - self.locks.add( - ";".join(["get:false()", "puppet:false()"]) - ) # would be weird to puppet a room ... - self.location = None
- - -# -# Default Exit command, used by the base exit object -# - -
[docs]class ExitCommand(_COMMAND_DEFAULT_CLASS): - """ - This is a command that simply cause the caller to traverse - the object it is attached to. - - """ - - obj = None - -
[docs] def func(self): - """ - Default exit traverse if no syscommand is defined. - """ - - if self.obj.access(self.caller, "traverse"): - # we may traverse the exit. - self.obj.at_traverse(self.caller, self.obj.destination) - else: - # exit is locked - if self.obj.db.err_traverse: - # if exit has a better error message, let's use it. - self.caller.msg(self.obj.db.err_traverse) - else: - # No shorthand error message. Call hook. - self.obj.at_failed_traverse(self.caller)
- -
[docs] def get_extra_info(self, caller, **kwargs): - """ - Shows a bit of information on where the exit leads. - - Args: - caller (Object): The object (usually a character) that entered an ambiguous command. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Returns: - A string with identifying information to disambiguate the command, conventionally with a preceding space. - """ - if self.obj.destination: - return " (exit to %s)" % self.obj.destination.get_display_name(caller) - else: - return " (%s)" % self.obj.get_display_name(caller)
- - -# -# Base Exit object - - -
[docs]class DefaultExit(DefaultObject): - """ - This is the base exit object - it connects a location to another. - This is done by the exit assigning a "command" on itself with the - same name as the exit object (to do this we need to remember to - re-create the command when the object is cached since it must be - created dynamically depending on what the exit is called). This - command (which has a high priority) will thus allow us to traverse - exits simply by giving the exit-object's name on its own. - - """ - - exit_command = ExitCommand - priority = 101 - - # lockstring of newly created exits, for easy overloading. - # Will be formatted with the {id} of the creating object. - lockstring = ( - "control:id({id}) or perm(Admin); " - "delete:id({id}) or perm(Admin); " - "edit:id({id}) or perm(Admin)" - ) - - # Helper classes and methods to implement the Exit. These need not - # be overloaded unless one want to change the foundation for how - # Exits work. See the end of the class for hook methods to overload. - -
[docs] def create_exit_cmdset(self, exidbobj): - """ - Helper function for creating an exit command set + command. - - The command of this cmdset has the same name as the Exit - object and allows the exit to react when the account enter the - exit's name, triggering the movement between rooms. - - Args: - exidbobj (Object): The DefaultExit object to base the command on. - - """ - - # create an exit command. We give the properties here, - # to always trigger metaclass preparations - cmd = self.exit_command( - key=exidbobj.db_key.strip().lower(), - aliases=exidbobj.aliases.all(), - locks=str(exidbobj.locks), - auto_help=False, - destination=exidbobj.db_destination, - arg_regex=r"^$", - is_exit=True, - obj=exidbobj, - ) - # create a cmdset - exit_cmdset = cmdset.CmdSet(None) - exit_cmdset.key = "ExitCmdSet" - exit_cmdset.priority = self.priority - exit_cmdset.duplicates = True - # add command to cmdset - exit_cmdset.add(cmd) - return exit_cmdset
- - # Command hooks - -
[docs] @classmethod - def create(cls, key, source, dest, account=None, **kwargs): - """ - Creates a basic Exit with default parameters, unless otherwise - specified or extended. - - Provides a friendlier interface to the utils.create_object() function. - - Args: - key (str): Name of the new Exit, as it should appear from the - source room. - account (obj): Account to associate this Exit with. - source (Room): The room to create this exit in. - dest (Room): The room to which this exit should go. - - Keyword Args: - description (str): Brief description for this object. - ip (str): IP address of creator (for object auditing). - - Returns: - exit (Object): A newly created Room of the given typeclass. - errors (list): A list of errors in string form, if any. - - """ - errors = [] - obj = None - - # Get IP address of creator, if available - ip = kwargs.pop("ip", "") - - # If no typeclass supplied, use this class - kwargs["typeclass"] = kwargs.pop("typeclass", cls) - - # Set the supplied key as the name of the intended object - kwargs["key"] = key - - # Get who to send errors to - kwargs["report_to"] = kwargs.pop("report_to", account) - - # Set to/from rooms - kwargs["location"] = source - kwargs["destination"] = dest - - description = kwargs.pop("description", "") - - locks = kwargs.get("locks", "") - - try: - # Create the Exit - obj = create.create_object(**kwargs) - - # Set appropriate locks - if not locks and account: - locks = cls.lockstring.format(**{"id": account.id}) - elif not locks and not account: - locks = cls.lockstring.format(**{"id": obj.id}) - obj.locks.add(locks) - - # Record creator id and creation IP - if ip: - obj.db.creator_ip = ip - if account: - obj.db.creator_id = account.id - - # If no description is set, set a default description - if description or not obj.db.desc: - obj.db.desc = description if description else "This is an exit." - - except Exception as e: - errors.append("An error occurred while creating this '%s' object." % key) - logger.log_err(e) - - return obj, errors
- -
[docs] def basetype_setup(self): - """ - Setup exit-security - - You should normally not need to overload this - if you do make - sure you include all the functionality in this method. - - """ - super().basetype_setup() - - # setting default locks (overload these in at_object_creation() - self.locks.add( - ";".join( - [ - "puppet:false()", # would be weird to puppet an exit ... - "traverse:all()", # who can pass through exit by default - "get:false()", # noone can pick up the exit - ] - ) - ) - - # an exit should have a destination (this is replaced at creation time) - if self.location: - self.destination = self.location
- -
[docs] def at_cmdset_get(self, **kwargs): - """ - Called just before cmdsets on this object are requested by the - command handler. If changes need to be done on the fly to the - cmdset before passing them on to the cmdhandler, this is the - place to do it. This is called also if the object currently - has no cmdsets. - - Keyword Args: - force_init (bool): If `True`, force a re-build of the cmdset - (for example to update aliases). - - """ - - if "force_init" in kwargs or not self.cmdset.has_cmdset("ExitCmdSet", must_be_default=True): - # we are resetting, or no exit-cmdset was set. Create one dynamically. - self.cmdset.add_default(self.create_exit_cmdset(self), permanent=False)
- -
[docs] def at_init(self): - """ - This is called when this objects is re-loaded from cache. When - that happens, we make sure to remove any old ExitCmdSet cmdset - (this most commonly occurs when renaming an existing exit) - """ - self.cmdset.remove_default()
- -
[docs] def at_traverse(self, traversing_object, target_location, **kwargs): - """ - This implements the actual traversal. The traverse lock has - already been checked (in the Exit command) at this point. - - Args: - traversing_object (Object): Object traversing us. - target_location (Object): Where target is going. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - source_location = traversing_object.location - if traversing_object.move_to(target_location): - self.at_after_traverse(traversing_object, source_location) - else: - if self.db.err_traverse: - # if exit has a better error message, let's use it. - traversing_object.msg(self.db.err_traverse) - else: - # No shorthand error message. Call hook. - self.at_failed_traverse(traversing_object)
- -
[docs] def at_failed_traverse(self, traversing_object, **kwargs): - """ - Overloads the default hook to implement a simple default error message. - - Args: - traversing_object (Object): The object that failed traversing us. - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - Notes: - Using the default exits, this hook will not be called if an - Attribute `err_traverse` is defined - this will in that case be - read for an error string instead. - - """ - traversing_object.msg("You cannot go there.")
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/prototypes/menus.html b/docs/0.9.5/_modules/evennia/prototypes/menus.html deleted file mode 100644 index 16a6583661..0000000000 --- a/docs/0.9.5/_modules/evennia/prototypes/menus.html +++ /dev/null @@ -1,2865 +0,0 @@ - - - - - - - - evennia.prototypes.menus — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.prototypes.menus

-"""
-
-OLC Prototype menu nodes
-
-"""
-
-import json
-import re
-from random import choice
-from django.db.models import Q
-from django.conf import settings
-from evennia.objects.models import ObjectDB
-from evennia.utils.evmenu import EvMenu, list_node
-from evennia.utils import evmore
-from evennia.utils.ansi import strip_ansi
-from evennia.utils import utils
-from evennia.locks.lockhandler import get_all_lockfuncs
-from evennia.prototypes import prototypes as protlib
-from evennia.prototypes import spawner
-
-# ------------------------------------------------------------
-#
-# OLC Prototype design menu
-#
-# ------------------------------------------------------------
-
-_MENU_CROP_WIDTH = 15
-_MENU_ATTR_LITERAL_EVAL_ERROR = (
-    "|rCritical Python syntax error in your value. Only primitive Python structures are allowed.\n"
-    "You also need to use correct Python syntax. Remember especially to put quotes around all "
-    "strings inside lists and dicts.|n"
-)
-
-
-# Helper functions
-
-
-def _get_menu_prototype(caller):
-    """Return currently active menu prototype."""
-    prototype = None
-    if hasattr(caller.ndb._menutree, "olc_prototype"):
-        prototype = caller.ndb._menutree.olc_prototype
-    if not prototype:
-        caller.ndb._menutree.olc_prototype = prototype = {}
-        caller.ndb._menutree.olc_new = True
-    return prototype
-
-
-def _get_flat_menu_prototype(caller, refresh=False, validate=False):
-    """Return prototype where parent values are included"""
-    flat_prototype = None
-    if not refresh and hasattr(caller.ndb._menutree, "olc_flat_prototype"):
-        flat_prototype = caller.ndb._menutree.olc_flat_prototype
-    if not flat_prototype:
-        prot = _get_menu_prototype(caller)
-        caller.ndb._menutree.olc_flat_prototype = flat_prototype = spawner.flatten_prototype(
-            prot, validate=validate
-        )
-    return flat_prototype
-
-
-def _get_unchanged_inherited(caller, protname):
-    """Return prototype values inherited from parent(s), which are not replaced in child"""
-    prototype = _get_menu_prototype(caller)
-    if protname in prototype:
-        return protname[protname], False
-    else:
-        flattened = _get_flat_menu_prototype(caller)
-        if protname in flattened:
-            return protname[protname], True
-    return None, False
-
-
-def _set_menu_prototype(caller, prototype):
-    """Set the prototype with existing one"""
-    caller.ndb._menutree.olc_prototype = prototype
-    caller.ndb._menutree.olc_new = False
-    return prototype
-
-
-def _is_new_prototype(caller):
-    """Check if prototype is marked as new or was loaded from a saved one."""
-    return hasattr(caller.ndb._menutree, "olc_new")
-
-
-def _format_option_value(prop, required=False, prototype=None, cropper=None):
-    """
-    Format wizard option values.
-
-    Args:
-        prop (str): Name or value to format.
-        required (bool, optional): The option is required.
-        prototype (dict, optional): If given, `prop` will be considered a key in this prototype.
-        cropper (callable, optional): A function to crop the value to a certain width.
-
-    Returns:
-        value (str): The formatted value.
-    """
-    if prototype is not None:
-        prop = prototype.get(prop, "")
-
-    out = prop
-    if callable(prop):
-        if hasattr(prop, "__name__"):
-            out = "<{}>".format(prop.__name__)
-        else:
-            out = repr(prop)
-    if utils.is_iter(prop):
-        out = ", ".join(str(pr) for pr in prop)
-    if not out and required:
-        out = "|runset"
-    if out:
-        return " ({}|n)".format(cropper(out) if cropper else utils.crop(out, _MENU_CROP_WIDTH))
-    return ""
-
-
-def _set_prototype_value(caller, field, value, parse=True):
-    """Set prototype's field in a safe way."""
-    prototype = _get_menu_prototype(caller)
-    prototype[field] = value
-    caller.ndb._menutree.olc_prototype = prototype
-    return prototype
-
-
-def _set_property(caller, raw_string, **kwargs):
-    """
-    Add or update a property. To be called by the 'goto' option variable.
-
-    Args:
-        caller (Object, Account): The user of the wizard.
-        raw_string (str): Input from user on given node - the new value to set.
-
-    Keyword Args:
-        test_parse (bool): If set (default True), parse raw_string for protfuncs and obj-refs and
-            try to run result through literal_eval. The parser will be run in 'testing' mode and any
-            parsing errors will shown to the user. Note that this is just for testing, the original
-            given string will be what is inserted.
-        prop (str): Property name to edit with `raw_string`.
-        processor (callable): Converts `raw_string` to a form suitable for saving.
-        next_node (str): Where to redirect to after this has run.
-
-    Returns:
-        next_node (str): Next node to go to.
-
-    """
-    prop = kwargs.get("prop", "prototype_key")
-    processor = kwargs.get("processor", None)
-    next_node = kwargs.get("next_node", None)
-
-    if callable(processor):
-        try:
-            value = processor(raw_string)
-        except Exception as err:
-            caller.msg(
-                "Could not set {prop} to {value} ({err})".format(
-                    prop=prop.replace("_", "-").capitalize(), value=raw_string, err=str(err)
-                )
-            )
-            # this means we'll re-run the current node.
-            return None
-    else:
-        value = raw_string
-
-    if not value:
-        return next_node
-
-    prototype = _set_prototype_value(caller, prop, value)
-    caller.ndb._menutree.olc_prototype = prototype
-
-    try:
-        # TODO simple way to get rid of the u'' markers in list reprs, remove this when on py3.
-        repr_value = json.dumps(value)
-    except Exception:
-        repr_value = value
-
-    out = [" Set {prop} to {value} ({typ}).".format(prop=prop, value=repr_value, typ=type(value))]
-
-    if kwargs.get("test_parse", True):
-        out.append(" Simulating prototype-func parsing ...")
-        err, parsed_value = protlib.protfunc_parser(value, testing=True)
-        if err:
-            out.append(" |yPython `literal_eval` warning: {}|n".format(err))
-        if parsed_value != value:
-            out.append(
-                " |g(Example-)value when parsed ({}):|n {}".format(type(parsed_value), parsed_value)
-            )
-        else:
-            out.append(" |gNo change when parsed.")
-
-    caller.msg("\n".join(out))
-
-    return next_node
-
-
-def _wizard_options(curr_node, prev_node, next_node, color="|W", search=False):
-    """Creates default navigation options available in the wizard."""
-    options = []
-    if prev_node:
-        options.append(
-            {
-                "key": ("|wB|Wack", "b"),
-                "desc": "{color}({node})|n".format(color=color, node=prev_node.replace("_", "-")),
-                "goto": "node_{}".format(prev_node),
-            }
-        )
-    if next_node:
-        options.append(
-            {
-                "key": ("|wF|Worward", "f"),
-                "desc": "{color}({node})|n".format(color=color, node=next_node.replace("_", "-")),
-                "goto": "node_{}".format(next_node),
-            }
-        )
-
-    options.append({"key": ("|wI|Wndex", "i"), "goto": "node_index"})
-
-    if curr_node:
-        options.append(
-            {
-                "key": ("|wV|Walidate prototype", "validate", "v"),
-                "goto": ("node_validate_prototype", {"back": curr_node}),
-            }
-        )
-        if search:
-            options.append(
-                {
-                    "key": ("|wSE|Warch objects", "search object", "search", "se"),
-                    "goto": ("node_search_object", {"back": curr_node}),
-                }
-            )
-
-    return options
-
-
-def _set_actioninfo(caller, string):
-    caller.ndb._menutree.actioninfo = string
-
-
-def _path_cropper(pythonpath):
-    "Crop path to only the last component"
-    return pythonpath.split(".")[-1]
-
-
-def _validate_prototype(prototype):
-    """Run validation on prototype"""
-
-    txt = protlib.prototype_to_str(prototype)
-    errors = "\n\n|g No validation errors found.|n (but errors could still happen at spawn-time)"
-    err = False
-    try:
-        # validate, don't spawn
-        spawner.spawn(prototype, only_validate=True)
-    except RuntimeError as exc:
-        errors = "\n\n|r{}|n".format(exc)
-        err = True
-    except RuntimeWarning as exc:
-        errors = "\n\n|y{}|n".format(exc)
-        err = True
-
-    text = txt + errors
-    return err, text
-
-
-def _format_protfuncs():
-    out = []
-    sorted_funcs = [
-        (key, func) for key, func in sorted(protlib.PROT_FUNCS.items(), key=lambda tup: tup[0])
-    ]
-    for protfunc_name, protfunc in sorted_funcs:
-        out.append(
-            "- |c${name}|n - |W{docs}".format(
-                name=protfunc_name,
-                docs=utils.justify(protfunc.__doc__.strip(), align="l", indent=10).strip(),
-            )
-        )
-    return "\n       ".join(out)
-
-
-def _format_lockfuncs():
-    out = []
-    sorted_funcs = [
-        (key, func) for key, func in sorted(get_all_lockfuncs().items(), key=lambda tup: tup[0])
-    ]
-    for lockfunc_name, lockfunc in sorted_funcs:
-        doc = (lockfunc.__doc__ or "").strip()
-        out.append(
-            "- |c${name}|n - |W{docs}".format(
-                name=lockfunc_name, docs=utils.justify(doc, align="l", indent=10).strip()
-            )
-        )
-    return "\n".join(out)
-
-
-def _format_list_actions(*args, **kwargs):
-    """Create footer text for nodes with extra list actions
-
-    Args:
-        actions (str): Available actions. The first letter of the action name will be assumed
-            to be a shortcut.
-    Keyword Args:
-        prefix (str): Default prefix to use.
-    Returns:
-        string (str): Formatted footer for adding to the node text.
-
-    """
-    actions = []
-    prefix = kwargs.get("prefix", "|WSelect with |w<num>|W. Other actions:|n ")
-    for action in args:
-        actions.append("|w{}|n|W{} |w<num>|n".format(action[0], action[1:]))
-    return prefix + " |W|||n ".join(actions)
-
-
-def _get_current_value(caller, keyname, comparer=None, formatter=str, only_inherit=False):
-    """
-    Return current value, marking if value comes from parent or set in this prototype.
-
-    Args:
-        keyname (str): Name of prototoype key to get current value of.
-        comparer (callable, optional): This will be called as comparer(prototype_value,
-            flattened_value) and is expected to return the value to show as the current
-            or inherited one. If not given, a straight comparison is used and what is returned
-            depends on the only_inherit setting.
-        formatter (callable, optional)): This will be called with the result of comparer.
-        only_inherit (bool, optional): If a current value should only be shown if all
-            the values are inherited from the prototype parent (otherwise, show an empty string).
-    Returns:
-        current (str): The current value.
-
-    """
-
-    def _default_comparer(protval, flatval):
-        if only_inherit:
-            return "" if protval else flatval
-        else:
-            return protval if protval else flatval
-
-    if not callable(comparer):
-        comparer = _default_comparer
-
-    prot = _get_menu_prototype(caller)
-    flat_prot = _get_flat_menu_prototype(caller)
-
-    out = ""
-    if keyname in prot:
-        if keyname in flat_prot:
-            out = formatter(comparer(prot[keyname], flat_prot[keyname]))
-            if only_inherit:
-                if str(out).strip():
-                    return "|WCurrent|n {} |W(|binherited|W):|n {}".format(keyname, out)
-                return ""
-            else:
-                if out:
-                    return "|WCurrent|n {}|W:|n {}".format(keyname, out)
-                return "|W[No {} set]|n".format(keyname)
-        elif only_inherit:
-            return ""
-        else:
-            out = formatter(prot[keyname])
-            return "|WCurrent|n {}|W:|n {}".format(keyname, out)
-    elif keyname in flat_prot:
-        out = formatter(flat_prot[keyname])
-        if out:
-            return "|WCurrent|n {} |W(|n|binherited|W):|n {}".format(keyname, out)
-        else:
-            return ""
-    elif only_inherit:
-        return ""
-    else:
-        return "|W[No {} set]|n".format(keyname)
-
-
-def _default_parse(raw_inp, choices, *args):
-    """
-    Helper to parse default input to a node decorated with the node_list decorator on
-    the form l1, l 2, look 1, etc. Spaces are ignored, as is case.
-
-    Args:
-        raw_inp (str): Input from the user.
-        choices (list): List of available options on the node listing (list of strings).
-        args (tuples): The available actions, each specifed as a tuple (name, alias, ...)
-    Returns:
-        choice (str): A choice among the choices, or None if no match was found.
-        action (str): The action operating on the choice, or None.
-
-    """
-    raw_inp = raw_inp.lower().strip()
-    mapping = {t.lower(): tup[0] for tup in args for t in tup}
-    match = re.match(r"(%s)\s*?(\d+)$" % "|".join(mapping.keys()), raw_inp)
-    if match:
-        action = mapping.get(match.group(1), None)
-        num = int(match.group(2)) - 1
-        num = num if 0 <= num < len(choices) else None
-        if action is not None and num is not None:
-            return choices[num], action
-    return None, None
-
-
-# Menu nodes ------------------------------
-
-# helper nodes
-
-# validate prototype (available as option from all nodes)
-
-
-
[docs]def node_validate_prototype(caller, raw_string, **kwargs): - """General node to view and validate a protototype""" - prototype = _get_flat_menu_prototype(caller, refresh=True, validate=False) - prev_node = kwargs.get("back", "index") - - _, text = _validate_prototype(prototype) - - helptext = """ - The validator checks if the prototype's various values are on the expected form. It also tests - any $protfuncs. - - """ - - text = (text, helptext) - - options = _wizard_options(None, prev_node, None) - options.append({"key": "_default", "goto": "node_" + prev_node}) - - return text, options
- - -# node examine_entity - - -
[docs]def node_examine_entity(caller, raw_string, **kwargs): - """ - General node to view a text and then return to previous node. Kwargs should contain "text" for - the text to show and 'back" pointing to the node to return to. - """ - text = kwargs.get("text", "Nothing was found here.") - helptext = "Use |wback|n to return to the previous node." - prev_node = kwargs.get("back", "index") - - text = (text, helptext) - - options = _wizard_options(None, prev_node, None) - options.append({"key": "_default", "goto": "node_" + prev_node}) - - return text, options
- - -# node object_search - - -def _search_object(caller): - "update search term based on query stored on menu; store match too" - try: - searchstring = caller.ndb._menutree.olc_search_object_term.strip() - caller.ndb._menutree.olc_search_object_matches = [] - except AttributeError: - return [] - - if not searchstring: - caller.msg("Must specify a search criterion.") - return [] - - is_dbref = utils.dbref(searchstring) - is_account = searchstring.startswith("*") - - if is_dbref or is_account: - - if is_dbref: - # a dbref search - results = caller.search(searchstring, global_search=True, quiet=True) - else: - # an account search - searchstring = searchstring.lstrip("*") - results = caller.search_account(searchstring, quiet=True) - else: - keyquery = Q(db_key__istartswith=searchstring) - aliasquery = Q( - db_tags__db_key__istartswith=searchstring, db_tags__db_tagtype__iexact="alias" - ) - results = ObjectDB.objects.filter(keyquery | aliasquery).distinct() - - caller.msg("Searching for '{}' ...".format(searchstring)) - caller.ndb._menutree.olc_search_object_matches = results - return ["{}(#{})".format(obj.key, obj.id) for obj in results] - - -def _object_search_select(caller, obj_entry, **kwargs): - choices = kwargs["available_choices"] - num = choices.index(obj_entry) - matches = caller.ndb._menutree.olc_search_object_matches - obj = matches[num] - - if not obj.access(caller, "examine"): - caller.msg("|rYou don't have 'examine' access on this object.|n") - del caller.ndb._menutree.olc_search_object_term - return "node_search_object" - - prot = spawner.prototype_from_object(obj) - txt = protlib.prototype_to_str(prot) - return "node_examine_entity", {"text": txt, "back": "search_object"} - - -def _object_search_actions(caller, raw_inp, **kwargs): - "All this does is to queue a search query" - choices = kwargs["available_choices"] - obj_entry, action = _default_parse( - raw_inp, choices, ("examine", "e"), ("create prototype from object", "create", "c") - ) - - raw_inp = raw_inp.strip() - - if obj_entry: - - num = choices.index(obj_entry) - matches = caller.ndb._menutree.olc_search_object_matches - obj = matches[num] - prot = spawner.prototype_from_object(obj) - - if action == "examine": - - if not obj.access(caller, "examine"): - caller.msg("\n|rYou don't have 'examine' access on this object.|n") - del caller.ndb._menutree.olc_search_object_term - return "node_search_object" - - txt = protlib.prototype_to_str(prot) - return "node_examine_entity", {"text": txt, "back": "search_object"} - else: - # load prototype - - if not obj.access(caller, "edit"): - caller.msg("|rYou don't have access to do this with this object.|n") - del caller.ndb._menutree.olc_search_object_term - return "node_search_object" - - _set_menu_prototype(caller, prot) - caller.msg("Created prototype from object.") - return "node_index" - elif raw_inp: - caller.ndb._menutree.olc_search_object_term = raw_inp - return "node_search_object", kwargs - else: - # empty input - exit back to previous node - prev_node = "node_" + kwargs.get("back", "index") - return prev_node - - -@list_node(_search_object, _object_search_select) -def node_search_object(caller, raw_inp, **kwargs): - """ - Node for searching for an existing object. - """ - try: - matches = caller.ndb._menutree.olc_search_object_matches - except AttributeError: - matches = [] - nmatches = len(matches) - prev_node = kwargs.get("back", "index") - - if matches: - text = """ - Found {num} match{post}. - - (|RWarning: creating a prototype will |roverwrite|r |Rthe current prototype!)|n""".format( - num=nmatches, post="es" if nmatches > 1 else "" - ) - _set_actioninfo( - caller, - _format_list_actions("examine", "create prototype from object", prefix="Actions: "), - ) - else: - text = "Enter search criterion." - - helptext = """ - You can search objects by specifying partial key, alias or its exact #dbref. Use *query to - search for an Account instead. - - Once having found any matches you can choose to examine it or use |ccreate prototype from - object|n. If doing the latter, a prototype will be calculated from the selected object and - loaded as the new 'current' prototype. This is useful for having a base to build from but be - careful you are not throwing away any existing, unsaved, prototype work! - """ - - text = (text, helptext) - - options = _wizard_options(None, prev_node, None) - options.append({"key": "_default", "goto": (_object_search_actions, {"back": prev_node})}) - - return text, options - - -# main index (start page) node - - -
[docs]def node_index(caller): - prototype = _get_menu_prototype(caller) - - text = """ - |c --- Prototype wizard --- |n - %s - - A |cprototype|n is a 'template' for |wspawning|n an in-game entity. A field of the prototype - can either be hard-coded, left empty or scripted using |w$protfuncs|n - for example to - randomize the value every time a new entity is spawned. The fields whose names start with - 'Prototype-' are not fields on the object itself but are used for prototype-inheritance, or - when saving and loading. - - Select prototype field to edit. If you are unsure, start from [|w1|n]. Enter [|wh|n]elp at - any menu node for more info. - - """ - helptxt = """ - |c- prototypes |n - - A prototype is really just a Python dictionary. When spawning, this dictionary is essentially - passed into `|wevennia.utils.create.create_object(**prototype)|n` to create a new object. By - using different prototypes you can customize instances of objects without having to do code - changes to their typeclass (something which requires code access). The classical example is - to spawn goblins with different names, looks, equipment and skill, each based on the same - `Goblin` typeclass. - - At any time you can [|wV|n]alidate that the prototype works correctly and use it to - [|wSP|n]awn a new entity. You can also [|wSA|n]ve|n your work, [|wLO|n]oad an existing - prototype to [|wSE|n]arch for existing objects to use as a base. Use [|wL|n]ook to re-show a - menu node. [|wQ|n]uit will always exit the menu and [|wH|n]elp will show context-sensitive - help. - - - |c- $protfuncs |n - - Prototype-functions (protfuncs) allow for limited scripting within a prototype. These are - entered as a string $funcname(arg, arg, ...) and are evaluated |wat the time of spawning|n - only. They can also be nested for combined effects. - - {pfuncs} - """.format( - pfuncs=_format_protfuncs() - ) - - # If a prototype is being edited, show its key and - # prototype_key under the title - loaded_prototype = "" - if "prototype_key" in prototype or "key" in prototype: - loaded_prototype = " --- Editing: |y{}({})|n --- ".format( - prototype.get("key", ""), prototype.get("prototype_key", "") - ) - text = text % (loaded_prototype) - - text = (text, helptxt) - - options = [] - options.append( - { - "desc": "|WPrototype-Key|n|n{}".format( - _format_option_value("Key", "prototype_key" not in prototype, prototype, None) - ), - "goto": "node_prototype_key", - } - ) - for key in ( - "Prototype_Parent", - "Typeclass", - "Key", - "Aliases", - "Attrs", - "Tags", - "Locks", - "Permissions", - "Location", - "Home", - "Destination", - ): - required = False - cropper = None - if key in ("Prototype_Parent", "Typeclass"): - required = ("prototype_parent" not in prototype) and ("typeclass" not in prototype) - if key == "Typeclass": - cropper = _path_cropper - options.append( - { - "desc": "{}{}|n{}".format( - "|W" if key == "Prototype_Parent" else "|w", - key.replace("_", "-"), - _format_option_value(key, required, prototype, cropper=cropper), - ), - "goto": "node_{}".format(key.lower()), - } - ) - required = False - for key in ("Desc", "Tags", "Locks"): - options.append( - { - "desc": "|WPrototype-{}|n|n{}".format( - key, _format_option_value(key, required, prototype, None) - ), - "goto": "node_prototype_{}".format(key.lower()), - } - ) - - options.extend( - ( - {"key": ("|wV|Walidate prototype", "validate", "v"), "goto": "node_validate_prototype"}, - {"key": ("|wSA|Wve prototype", "save", "sa"), "goto": "node_prototype_save"}, - {"key": ("|wSP|Wawn prototype", "spawn", "sp"), "goto": "node_prototype_spawn"}, - {"key": ("|wLO|Wad prototype", "load", "lo"), "goto": "node_prototype_load"}, - {"key": ("|wSE|Warch objects|n", "search", "se"), "goto": "node_search_object"}, - ) - ) - - return text, options
- - -# prototype_key node - - -def _check_prototype_key(caller, key): - old_prototype = protlib.search_prototype(key) - olc_new = _is_new_prototype(caller) - key = key.strip().lower() - if old_prototype: - old_prototype = old_prototype[0] - # we are starting a new prototype that matches an existing - if not caller.locks.check_lockstring( - caller, old_prototype["prototype_locks"], access_type="edit" - ): - # return to the node_prototype_key to try another key - caller.msg( - "Prototype '{key}' already exists and you don't " - "have permission to edit it.".format(key=key) - ) - return "node_prototype_key" - elif olc_new: - # we are selecting an existing prototype to edit. Reset to index. - del caller.ndb._menutree.olc_new - caller.ndb._menutree.olc_prototype = old_prototype - caller.msg("Prototype already exists. Reloading.") - return "node_index" - - return _set_property(caller, key, prop="prototype_key") - - -
[docs]def node_prototype_key(caller): - - text = """ - The |cPrototype-Key|n uniquely identifies the prototype and is |wmandatory|n. It is used to - find and use the prototype to spawn new entities. It is not case sensitive. - - (To set a new value, just write it and press enter) - - {current}""".format( - current=_get_current_value(caller, "prototype_key") - ) - - helptext = """ - The prototype-key is not itself used when spawnng the new object, but is only used for - managing, storing and loading the prototype. It must be globally unique, so existing keys - will be checked before a new key is accepted. If an existing key is picked, the existing - prototype will be loaded. - """ - - options = _wizard_options("prototype_key", "index", "prototype_parent") - options.append({"key": "_default", "goto": _check_prototype_key}) - - text = (text, helptext) - return text, options
- - -# prototype_parents node - - -def _all_prototype_parents(caller): - """Return prototype_key of all available prototypes for listing in menu""" - return [ - prototype["prototype_key"] - for prototype in protlib.search_prototype() - if "prototype_key" in prototype - ] - - -def _prototype_parent_actions(caller, raw_inp, **kwargs): - """Parse the default Convert prototype to a string representation for closer inspection""" - choices = kwargs.get("available_choices", []) - prototype_parent, action = _default_parse( - raw_inp, choices, ("examine", "e", "l"), ("add", "a"), ("remove", "r", "delete", "d") - ) - - if prototype_parent: - # a selection of parent was made - prototype_parent = protlib.search_prototype(key=prototype_parent)[0] - prototype_parent_key = prototype_parent["prototype_key"] - - # which action to apply on the selection - if action == "examine": - # examine the prototype - txt = protlib.prototype_to_str(prototype_parent) - kwargs["text"] = txt - kwargs["back"] = "prototype_parent" - return "node_examine_entity", kwargs - elif action == "add": - # add/append parent - prot = _get_menu_prototype(caller) - current_prot_parent = prot.get("prototype_parent", None) - if current_prot_parent: - current_prot_parent = utils.make_iter(current_prot_parent) - if prototype_parent_key in current_prot_parent: - caller.msg("Prototype_parent {} is already used.".format(prototype_parent_key)) - return "node_prototype_parent" - else: - current_prot_parent.append(prototype_parent_key) - caller.msg("Add prototype parent for multi-inheritance.") - else: - current_prot_parent = prototype_parent_key - try: - if prototype_parent: - spawner.flatten_prototype(prototype_parent, validate=True) - else: - raise RuntimeError("Not found.") - except RuntimeError as err: - caller.msg( - "Selected prototype-parent {} " - "caused Error(s):\n|r{}|n".format(prototype_parent, err) - ) - return "node_prototype_parent" - _set_prototype_value(caller, "prototype_parent", current_prot_parent) - _get_flat_menu_prototype(caller, refresh=True) - elif action == "remove": - # remove prototype parent - prot = _get_menu_prototype(caller) - current_prot_parent = prot.get("prototype_parent", None) - if current_prot_parent: - current_prot_parent = utils.make_iter(current_prot_parent) - try: - current_prot_parent.remove(prototype_parent_key) - _set_prototype_value(caller, "prototype_parent", current_prot_parent) - _get_flat_menu_prototype(caller, refresh=True) - caller.msg("Removed prototype parent {}.".format(prototype_parent_key)) - except ValueError: - caller.msg( - "|rPrototype-parent {} could not be removed.".format(prototype_parent_key) - ) - return "node_prototype_parent" - - -def _prototype_parent_select(caller, new_parent): - - ret = None - prototype_parent = protlib.search_prototype(new_parent) - try: - if prototype_parent: - spawner.flatten_prototype(prototype_parent[0], validate=True) - else: - raise RuntimeError("Not found.") - except RuntimeError as err: - caller.msg( - "Selected prototype-parent {} " "caused Error(s):\n|r{}|n".format(new_parent, err) - ) - else: - ret = _set_property( - caller, - new_parent, - prop="prototype_parent", - processor=str, - next_node="node_prototype_parent", - ) - _get_flat_menu_prototype(caller, refresh=True) - caller.msg("Selected prototype parent |c{}|n.".format(new_parent)) - return ret - - -@list_node(_all_prototype_parents, _prototype_parent_select) -def node_prototype_parent(caller): - prototype = _get_menu_prototype(caller) - - prot_parent_keys = prototype.get("prototype_parent") - - text = """ - The |cPrototype Parent|n allows you to |winherit|n prototype values from another named - prototype (given as that prototype's |wprototype_key|n). If not changing these values in - the current prototype, the parent's value will be used. Pick the available prototypes below. - - Note that somewhere in the prototype's parentage, a |ctypeclass|n must be specified. If no - parent is given, this prototype must define the typeclass (next menu node). - - {current} - """ - helptext = """ - Prototypes can inherit from one another. Changes in the child replace any values set in a - parent. The |wtypeclass|n key must exist |wsomewhere|n in the parent chain for the - prototype to be valid. - """ - - _set_actioninfo(caller, _format_list_actions("examine", "add", "remove")) - - ptexts = [] - if prot_parent_keys: - for pkey in utils.make_iter(prot_parent_keys): - prot_parent = protlib.search_prototype(pkey) - if prot_parent: - prot_parent = prot_parent[0] - ptexts.append( - "|c -- {pkey} -- |n\n{prot}".format( - pkey=pkey, prot=protlib.prototype_to_str(prot_parent) - ) - ) - else: - ptexts.append("Prototype parent |r{pkey} was not found.".format(pkey=pkey)) - - if not ptexts: - ptexts.append("[No prototype_parent set]") - - text = text.format(current="\n\n".join(ptexts)) - - text = (text, helptext) - - options = _wizard_options("prototype_parent", "prototype_key", "typeclass", color="|W") - options.append({"key": "_default", "goto": _prototype_parent_actions}) - - return text, options - - -# typeclasses node - - -def _all_typeclasses(caller): - """Get name of available typeclasses.""" - return list( - name - for name in sorted(utils.get_all_typeclasses("evennia.objects.models.ObjectDB").keys()) - if name != "evennia.objects.models.ObjectDB" - ) - - -def _typeclass_actions(caller, raw_inp, **kwargs): - """Parse actions for typeclass listing""" - - choices = kwargs.get("available_choices", []) - typeclass_path, action = _default_parse( - raw_inp, choices, ("examine", "e", "l"), ("remove", "r", "delete", "d") - ) - - if typeclass_path: - if action == "examine": - typeclass = utils.get_all_typeclasses().get(typeclass_path) - if typeclass: - docstr = [] - for line in typeclass.__doc__.split("\n"): - if line.strip(): - docstr.append(line) - elif docstr: - break - docstr = "\n".join(docstr) if docstr else "<empty>" - txt = ( - "Typeclass |c{typeclass_path}|n; " - "First paragraph of docstring:\n\n{docstring}".format( - typeclass_path=typeclass_path, docstring=docstr - ) - ) - else: - txt = "This is typeclass |y{}|n.".format(typeclass) - return "node_examine_entity", {"text": txt, "back": "typeclass"} - elif action == "remove": - prototype = _get_menu_prototype(caller) - old_typeclass = prototype.pop("typeclass", None) - if old_typeclass: - _set_menu_prototype(caller, prototype) - caller.msg("Cleared typeclass {}.".format(old_typeclass)) - else: - caller.msg("No typeclass to remove.") - return "node_typeclass" - - -def _typeclass_select(caller, typeclass): - """Select typeclass from list and add it to prototype. Return next node to go to.""" - ret = _set_property(caller, typeclass, prop="typeclass", processor=str) - caller.msg("Selected typeclass |c{}|n.".format(typeclass)) - return ret - - -@list_node(_all_typeclasses, _typeclass_select) -def node_typeclass(caller): - text = """ - The |cTypeclass|n defines what 'type' of object this is - the actual working code to use. - - All spawned objects must have a typeclass. If not given here, the typeclass must be set in - one of the prototype's |cparents|n. - - {current} - """.format( - current=_get_current_value(caller, "typeclass"), - actions="|WSelect with |w<num>|W. Other actions: " - "|we|Wxamine |w<num>|W, |wr|Wemove selection", - ) - - helptext = """ - A |nTypeclass|n is specified by the actual python-path to the class definition in the - Evennia code structure. - - Which |cAttributes|n, |cLocks|n and other properties have special - effects or expects certain values depend greatly on the code in play. - """ - - text = (text, helptext) - - options = _wizard_options("typeclass", "prototype_parent", "key", color="|W") - options.append({"key": "_default", "goto": _typeclass_actions}) - return text, options - - -# key node - - -
[docs]def node_key(caller): - text = """ - The |cKey|n is the given name of the object to spawn. This will retain the given case. - - {current} - """.format( - current=_get_current_value(caller, "key") - ) - - helptext = """ - The key should often not be identical for every spawned object. Using a randomising - $protfunc can be used, for example |c$choice(Alan, Tom, John)|n will give one of the three - names every time an object of this prototype is spawned. - - |c$protfuncs|n - {pfuncs} - """.format( - pfuncs=_format_protfuncs() - ) - - text = (text, helptext) - - options = _wizard_options("key", "typeclass", "aliases") - options.append( - { - "key": "_default", - "goto": (_set_property, dict(prop="key", processor=lambda s: s.strip())), - } - ) - return text, options
- - -# aliases node - - -def _all_aliases(caller): - "Get aliases in prototype" - prototype = _get_menu_prototype(caller) - return prototype.get("aliases", []) - - -def _aliases_select(caller, alias): - "Add numbers as aliases" - aliases = _all_aliases(caller) - try: - ind = str(aliases.index(alias) + 1) - if ind not in aliases: - aliases.append(ind) - _set_prototype_value(caller, "aliases", aliases) - caller.msg("Added alias '{}'.".format(ind)) - except (IndexError, ValueError) as err: - caller.msg("Error: {}".format(err)) - - return "node_aliases" - - -def _aliases_actions(caller, raw_inp, **kwargs): - """Parse actions for aliases listing""" - choices = kwargs.get("available_choices", []) - alias, action = _default_parse(raw_inp, choices, ("remove", "r", "delete", "d")) - - aliases = _all_aliases(caller) - if alias and action == "remove": - try: - aliases.remove(alias) - _set_prototype_value(caller, "aliases", aliases) - caller.msg("Removed alias '{}'.".format(alias)) - except ValueError: - caller.msg("No matching alias found to remove.") - else: - # if not a valid remove, add as a new alias - alias = raw_inp.lower().strip() - if alias and alias not in aliases: - aliases.append(alias) - _set_prototype_value(caller, "aliases", aliases) - caller.msg("Added alias '{}'.".format(alias)) - else: - caller.msg("Alias '{}' was already set.".format(alias)) - return "node_aliases" - - -@list_node(_all_aliases, _aliases_select) -def node_aliases(caller): - - text = """ - |cAliases|n are alternative ways to address an object, next to its |cKey|n. Aliases are not - case sensitive. - - {current} - """.format( - current=_get_current_value( - caller, - "aliases", - comparer=lambda propval, flatval: [al for al in flatval if al not in propval], - formatter=lambda lst: "\n" + ", ".join(lst), - only_inherit=True, - ) - ) - _set_actioninfo( - caller, _format_list_actions("remove", prefix="|w<text>|W to add new alias. Other action: ") - ) - - helptext = """ - Aliases are fixed alternative identifiers and are stored with the new object. - - |c$protfuncs|n - - {pfuncs} - """.format( - pfuncs=_format_protfuncs() - ) - - text = (text, helptext) - - options = _wizard_options("aliases", "key", "attrs") - options.append({"key": "_default", "goto": _aliases_actions}) - return text, options - - -# attributes node - - -def _caller_attrs(caller): - prototype = _get_menu_prototype(caller) - attrs = [ - "{}={}".format(tup[0], utils.crop(utils.to_str(tup[1]), width=10)) - for tup in prototype.get("attrs", []) - ] - return attrs - - -def _get_tup_by_attrname(caller, attrname): - prototype = _get_menu_prototype(caller) - attrs = prototype.get("attrs", []) - try: - inp = [tup[0] for tup in attrs].index(attrname) - return attrs[inp] - except ValueError: - return None - - -def _display_attribute(attr_tuple): - """Pretty-print attribute tuple""" - attrkey, value, category, locks = attr_tuple - value = protlib.protfunc_parser(value) - typ = type(value) - out = "{attrkey} |c=|n {value} |W({typ}{category}{locks})|n".format( - attrkey=attrkey, - value=value, - typ=typ, - category=", category={}".format(category) if category else "", - locks=", locks={}".format(";".join(locks)) if any(locks) else "", - ) - - return out - - -def _add_attr(caller, attr_string, **kwargs): - """ - Add new attribute, parsing input. - - Args: - caller (Object): Caller of menu. - attr_string (str): Input from user - attr is entered on these forms - attr = value - attr;category = value - attr;category;lockstring = value - Keyword Args: - delete (str): If this is set, attr_string is - considered the name of the attribute to delete and - no further parsing happens. - Returns: - result (str): Result string of action. - """ - attrname = "" - value = "" - category = None - locks = "" - - if "delete" in kwargs: - attrname = attr_string.lower().strip() - elif "=" in attr_string: - attrname, value = (part.strip() for part in attr_string.split("=", 1)) - attrname = attrname.lower() - nameparts = attrname.split(";", 2) - nparts = len(nameparts) - if nparts == 2: - attrname, category = nameparts - elif nparts > 2: - attrname, category, locks = nameparts - attr_tuple = (attrname, value, category, str(locks)) - - if attrname: - prot = _get_menu_prototype(caller) - attrs = prot.get("attrs", []) - - if "delete" in kwargs: - try: - ind = [tup[0] for tup in attrs].index(attrname) - del attrs[ind] - _set_prototype_value(caller, "attrs", attrs) - return "Removed Attribute '{}'".format(attrname) - except IndexError: - return "Attribute to delete not found." - - try: - # replace existing attribute with the same name in the prototype - ind = [tup[0] for tup in attrs].index(attrname) - attrs[ind] = attr_tuple - text = "Edited Attribute '{}'.".format(attrname) - except ValueError: - attrs.append(attr_tuple) - text = "Added Attribute " + _display_attribute(attr_tuple) - - _set_prototype_value(caller, "attrs", attrs) - else: - text = "Attribute must be given as 'attrname[;category;locks] = <value>'." - - return text - - -def _attr_select(caller, attrstr): - attrname, _ = attrstr.split("=", 1) - attrname = attrname.strip() - - attr_tup = _get_tup_by_attrname(caller, attrname) - if attr_tup: - return ("node_examine_entity", {"text": _display_attribute(attr_tup), "back": "attrs"}) - else: - caller.msg("Attribute not found.") - return "node_attrs" - - -def _attrs_actions(caller, raw_inp, **kwargs): - """Parse actions for attribute listing""" - choices = kwargs.get("available_choices", []) - attrstr, action = _default_parse( - raw_inp, choices, ("examine", "e"), ("remove", "r", "delete", "d") - ) - if attrstr is None: - attrstr = raw_inp - try: - attrname, _ = attrstr.split("=", 1) - except ValueError: - caller.msg("|rNeed to enter the attribute on the form attrname=value.|n") - return "node_attrs" - - attrname = attrname.strip() - attr_tup = _get_tup_by_attrname(caller, attrname) - - if action and attr_tup: - if action == "examine": - return ("node_examine_entity", {"text": _display_attribute(attr_tup), "back": "attrs"}) - elif action == "remove": - res = _add_attr(caller, attrname, delete=True) - caller.msg(res) - else: - res = _add_attr(caller, raw_inp) - caller.msg(res) - return "node_attrs" - - -@list_node(_caller_attrs, _attr_select) -def node_attrs(caller): - def _currentcmp(propval, flatval): - "match by key + category" - cmp1 = [(tup[0].lower(), tup[2].lower() if tup[2] else None) for tup in propval] - return [ - tup - for tup in flatval - if (tup[0].lower(), tup[2].lower() if tup[2] else None) not in cmp1 - ] - - text = """ - |cAttributes|n are custom properties of the object. Enter attributes on one of these forms: - - attrname=value - attrname;category=value - attrname;category;lockstring=value - - To give an attribute without a category but with a lockstring, leave that spot empty - (attrname;;lockstring=value). Attribute values can have embedded $protfuncs. - - {current} - """.format( - current=_get_current_value( - caller, - "attrs", - comparer=_currentcmp, - formatter=lambda lst: "\n" + "\n".join(_display_attribute(tup) for tup in lst), - only_inherit=True, - ) - ) - _set_actioninfo(caller, _format_list_actions("examine", "remove", prefix="Actions: ")) - - helptext = """ - Most commonly, Attributes don't need any categories or locks. If using locks, the lock-types - 'attredit' and 'attrread' are used to limit editing and viewing of the Attribute. Putting - the lock-type `attrcreate` in the |clocks|n prototype key can be used to restrict builders - from adding new Attributes. - - |c$protfuncs - - {pfuncs} - """.format( - pfuncs=_format_protfuncs() - ) - - text = (text, helptext) - - options = _wizard_options("attrs", "aliases", "tags") - options.append({"key": "_default", "goto": _attrs_actions}) - return text, options - - -# tags node - - -def _caller_tags(caller): - prototype = _get_menu_prototype(caller) - tags = [tup[0] for tup in prototype.get("tags", [])] - return tags - - -def _get_tup_by_tagname(caller, tagname): - prototype = _get_menu_prototype(caller) - tags = prototype.get("tags", []) - try: - inp = [tup[0] for tup in tags].index(tagname) - return tags[inp] - except ValueError: - return None - - -def _display_tag(tag_tuple): - """Pretty-print tag tuple""" - tagkey, category, data = tag_tuple - out = "Tag: '{tagkey}' (category: {category}{dat})".format( - tagkey=tagkey, category=category, dat=", data: {}".format(data) if data else "" - ) - return out - - -def _add_tag(caller, tag_string, **kwargs): - """ - Add tags to the system, parsing input - - Args: - caller (Object): Caller of menu. - tag_string (str): Input from user on one of these forms - tagname - tagname;category - tagname;category;data - - Keyword Args: - delete (str): If this is set, tag_string is considered - the name of the tag to delete. - - Returns: - result (str): Result string of action. - - """ - tag = tag_string.strip().lower() - category = None - data = "" - - if "delete" in kwargs: - tag = tag_string.lower().strip() - else: - nameparts = tag.split(";", 2) - ntuple = len(nameparts) - if ntuple == 2: - tag, category = nameparts - elif ntuple > 2: - tag, category, data = nameparts[:3] - - tag_tuple = (tag.lower(), category.lower() if category else None, data) - - if tag: - prot = _get_menu_prototype(caller) - tags = prot.get("tags", []) - - old_tag = _get_tup_by_tagname(caller, tag) - - if "delete" in kwargs: - - if old_tag: - tags.pop(tags.index(old_tag)) - text = "Removed Tag '{}'.".format(tag) - else: - text = "Found no Tag to remove." - elif not old_tag: - # a fresh, new tag - tags.append(tag_tuple) - text = "Added Tag '{}'".format(tag) - else: - # old tag exists; editing a tag means replacing old with new - ind = tags.index(old_tag) - tags[ind] = tag_tuple - text = "Edited Tag '{}'".format(tag) - - _set_prototype_value(caller, "tags", tags) - else: - text = "Tag must be given as 'tag[;category;data]'." - - return text - - -def _tag_select(caller, tagname): - tag_tup = _get_tup_by_tagname(caller, tagname) - if tag_tup: - return "node_examine_entity", {"text": _display_tag(tag_tup), "back": "attrs"} - else: - caller.msg("Tag not found.") - return "node_attrs" - - -def _tags_actions(caller, raw_inp, **kwargs): - """Parse actions for tags listing""" - choices = kwargs.get("available_choices", []) - tagname, action = _default_parse( - raw_inp, choices, ("examine", "e"), ("remove", "r", "delete", "d") - ) - - if tagname is None: - tagname = raw_inp.lower().strip() - - tag_tup = _get_tup_by_tagname(caller, tagname) - - if tag_tup: - if action == "examine": - return ("node_examine_entity", {"text": _display_tag(tag_tup), "back": "tags"}) - elif action == "remove": - res = _add_tag(caller, tagname, delete=True) - caller.msg(res) - else: - res = _add_tag(caller, raw_inp) - caller.msg(res) - return "node_tags" - - -@list_node(_caller_tags, _tag_select) -def node_tags(caller): - def _currentcmp(propval, flatval): - "match by key + category" - cmp1 = [(tup[0].lower(), tup[1].lower() if tup[2] else None) for tup in propval] - return [ - tup - for tup in flatval - if (tup[0].lower(), tup[1].lower() if tup[1] else None) not in cmp1 - ] - - text = """ - |cTags|n are used to group objects so they can quickly be found later. Enter tags on one of - the following forms: - tagname - tagname;category - tagname;category;data - - {current} - """.format( - current=_get_current_value( - caller, - "tags", - comparer=_currentcmp, - formatter=lambda lst: "\n" + "\n".join(_display_tag(tup) for tup in lst), - only_inherit=True, - ) - ) - _set_actioninfo(caller, _format_list_actions("examine", "remove", prefix="Actions: ")) - - helptext = """ - Tags are shared between all objects with that tag. So the 'data' field (which is not - commonly used) can only hold eventual info about the Tag itself, not about the individual - object on which it sits. - - All objects created with this prototype will automatically get assigned a tag named the same - as the |cprototype_key|n and with a category "{tag_category}". This allows the spawner to - optionally update previously spawned objects when their prototype changes. - """.format( - tag_category=protlib.PROTOTYPE_TAG_CATEGORY - ) - - text = (text, helptext) - options = _wizard_options("tags", "attrs", "locks") - options.append({"key": "_default", "goto": _tags_actions}) - return text, options - - -# locks node - - -def _caller_locks(caller): - locks = _get_menu_prototype(caller).get("locks", "") - return [lck for lck in locks.split(";") if lck] - - -def _locks_display(caller, lock): - return lock - - -def _lock_select(caller, lockstr): - return ("node_examine_entity", {"text": _locks_display(caller, lockstr), "back": "locks"}) - - -def _lock_add(caller, lock, **kwargs): - locks = _caller_locks(caller) - - try: - locktype, lockdef = lock.split(":", 1) - except ValueError: - return "Lockstring lacks ':'." - - locktype = locktype.strip().lower() - - if "delete" in kwargs: - try: - ind = locks.index(lock) - locks.pop(ind) - _set_prototype_value(caller, "locks", ";".join(locks), parse=False) - ret = "Lock {} deleted.".format(lock) - except ValueError: - ret = "No lock found to delete." - return ret - try: - locktypes = [lck.split(":", 1)[0].strip().lower() for lck in locks] - ind = locktypes.index(locktype) - locks[ind] = lock - ret = "Lock with locktype '{}' updated.".format(locktype) - except ValueError: - locks.append(lock) - ret = "Added lock '{}'.".format(lock) - _set_prototype_value(caller, "locks", ";".join(locks)) - return ret - - -def _locks_actions(caller, raw_inp, **kwargs): - choices = kwargs.get("available_choices", []) - lock, action = _default_parse( - raw_inp, choices, ("examine", "e"), ("remove", "r", "delete", "d") - ) - - if lock: - if action == "examine": - return ("node_examine_entity", {"text": _locks_display(caller, lock), "back": "locks"}) - elif action == "remove": - ret = _lock_add(caller, lock, delete=True) - caller.msg(ret) - else: - ret = _lock_add(caller, raw_inp) - caller.msg(ret) - - return "node_locks" - - -@list_node(_caller_locks, _lock_select) -def node_locks(caller): - def _currentcmp(propval, flatval): - "match by locktype" - cmp1 = [lck.split(":", 1)[0] for lck in propval.split(";")] - return ";".join(lstr for lstr in flatval.split(";") if lstr.split(":", 1)[0] not in cmp1) - - text = """ - The |cLock string|n defines limitations for accessing various properties of the object once - it's spawned. The string should be on one of the following forms: - - locktype:[NOT] lockfunc(args) - locktype: [NOT] lockfunc(args) [AND|OR|NOT] lockfunc(args) [AND|OR|NOT] ... - - {current}{action} - """.format( - current=_get_current_value( - caller, - "locks", - comparer=_currentcmp, - formatter=lambda lockstr: "\n".join( - _locks_display(caller, lstr) for lstr in lockstr.split(";") - ), - only_inherit=True, - ), - action=_format_list_actions("examine", "remove", prefix="Actions: "), - ) - - helptext = """ - Here is an example of two lock strings: - - edit:false() - call:tag(Foo) OR perm(Builder) - - Above locks limit two things, 'edit' and 'call'. Which lock types are actually checked - depend on the typeclass of the object being spawned. Here 'edit' is never allowed by anyone - while 'call' is allowed to all accessors with a |ctag|n 'Foo' OR which has the - |cPermission|n 'Builder'. - - |cAvailable lockfuncs:|n - - {lfuncs} - """.format( - lfuncs=_format_lockfuncs() - ) - - text = (text, helptext) - - options = _wizard_options("locks", "tags", "permissions") - options.append({"key": "_default", "goto": _locks_actions}) - - return text, options - - -# permissions node - - -def _caller_permissions(caller): - prototype = _get_menu_prototype(caller) - perms = prototype.get("permissions", []) - return perms - - -def _display_perm(caller, permission, only_hierarchy=False): - hierarchy = settings.PERMISSION_HIERARCHY - perm_low = permission.lower() - txt = "" - if perm_low in [prm.lower() for prm in hierarchy]: - txt = "Permission (in hieararchy): {}".format( - ", ".join( - [ - "|w[{}]|n".format(prm) if prm.lower() == perm_low else "|W{}|n".format(prm) - for prm in hierarchy - ] - ) - ) - elif not only_hierarchy: - txt = "Permission: '{}'".format(permission) - return txt - - -def _permission_select(caller, permission, **kwargs): - return ( - "node_examine_entity", - {"text": _display_perm(caller, permission), "back": "permissions"}, - ) - - -def _add_perm(caller, perm, **kwargs): - if perm: - perm_low = perm.lower() - perms = _caller_permissions(caller) - perms_low = [prm.lower() for prm in perms] - if "delete" in kwargs: - try: - ind = perms_low.index(perm_low) - del perms[ind] - text = "Removed Permission '{}'.".format(perm) - except ValueError: - text = "Found no Permission to remove." - else: - if perm_low in perms_low: - text = "Permission already set." - else: - perms.append(perm) - _set_prototype_value(caller, "permissions", perms) - text = "Added Permission '{}'".format(perm) - return text - - -def _permissions_actions(caller, raw_inp, **kwargs): - """Parse actions for permission listing""" - choices = kwargs.get("available_choices", []) - perm, action = _default_parse( - raw_inp, choices, ("examine", "e"), ("remove", "r", "delete", "d") - ) - - if perm: - if action == "examine": - return ( - "node_examine_entity", - {"text": _display_perm(caller, perm), "back": "permissions"}, - ) - elif action == "remove": - res = _add_perm(caller, perm, delete=True) - caller.msg(res) - else: - res = _add_perm(caller, raw_inp.strip()) - caller.msg(res) - return "node_permissions" - - -@list_node(_caller_permissions, _permission_select) -def node_permissions(caller): - def _currentcmp(pval, fval): - cmp1 = [perm.lower() for perm in pval] - return [perm for perm in fval if perm.lower() not in cmp1] - - text = """ - |cPermissions|n are simple strings used to grant access to this object. A permission is used - when a |clock|n is checked that contains the |wperm|n or |wpperm|n lock functions. Certain - permissions belong in the |cpermission hierarchy|n together with the |Wperm()|n lock - function. - - {current} - """.format( - current=_get_current_value( - caller, - "permissions", - comparer=_currentcmp, - formatter=lambda lst: "\n" + "\n".join(prm for prm in lst), - only_inherit=True, - ) - ) - _set_actioninfo(caller, _format_list_actions("examine", "remove", prefix="Actions: ")) - - helptext = """ - Any string can act as a permission as long as a lock is set to look for it. Depending on the - lock, having a permission could even be negative (i.e. the lock is only passed if you - |wdon't|n have the 'permission'). The most common permissions are the hierarchical - permissions: - - {permissions}. - - For example, a |clock|n string like "edit:perm(Builder)" will grant access to accessors - having the |cpermission|n "Builder" or higher. - """.format( - permissions=", ".join(settings.PERMISSION_HIERARCHY) - ) - - text = (text, helptext) - - options = _wizard_options("permissions", "locks", "location") - options.append({"key": "_default", "goto": _permissions_actions}) - - return text, options - - -# location node - - -
[docs]def node_location(caller): - - text = """ - The |cLocation|n of this object in the world. If not given, the object will spawn in the - inventory of |c{caller}|n by default. - - {current} - """.format( - caller=caller.key, current=_get_current_value(caller, "location") - ) - - helptext = """ - You get the most control by not specifying the location - you can then teleport the spawned - objects as needed later. Setting the location may be useful for quickly populating a given - location. One could also consider randomizing the location using a $protfunc. - - |c$protfuncs|n - {pfuncs} - """.format( - pfuncs=_format_protfuncs() - ) - - text = (text, helptext) - - options = _wizard_options("location", "permissions", "home", search=True) - options.append( - { - "key": "_default", - "goto": (_set_property, dict(prop="location", processor=lambda s: s.strip())), - } - ) - return text, options
- - -# home node - - -
[docs]def node_home(caller): - - text = """ - The |cHome|n location of an object is often only used as a backup - this is where the object - will be moved to if its location is deleted. The home location can also be used as an actual - home for characters to quickly move back to. - - If unset, the global home default (|w{default}|n) will be used. - - {current} - """.format( - default=settings.DEFAULT_HOME, current=_get_current_value(caller, "home") - ) - helptext = """ - The home can be given as a #dbref but can also be specified using the protfunc - '$obj(name)'. Use |wSE|nearch to find objects in the database. - - The home location is commonly not used except as a backup; using the global default is often - enough. - - |c$protfuncs|n - {pfuncs} - """.format( - pfuncs=_format_protfuncs() - ) - - text = (text, helptext) - - options = _wizard_options("home", "location", "destination", search=True) - options.append( - { - "key": "_default", - "goto": (_set_property, dict(prop="home", processor=lambda s: s.strip())), - } - ) - return text, options
- - -# destination node - - -
[docs]def node_destination(caller): - - text = """ - The object's |cDestination|n is generally only used by Exit-like objects to designate where - the exit 'leads to'. It's usually unset for all other types of objects. - - {current} - """.format( - current=_get_current_value(caller, "destination") - ) - - helptext = """ - The destination can be given as a #dbref but can also be specified using the protfunc - '$obj(name)'. Use |wSEearch to find objects in the database. - - |c$protfuncs|n - {pfuncs} - """.format( - pfuncs=_format_protfuncs() - ) - - text = (text, helptext) - - options = _wizard_options("destination", "home", "prototype_desc", search=True) - options.append( - { - "key": "_default", - "goto": (_set_property, dict(prop="destination", processor=lambda s: s.strip())), - } - ) - return text, options
- - -# prototype_desc node - - -
[docs]def node_prototype_desc(caller): - - text = """ - The |cPrototype-Description|n briefly describes the prototype when it's viewed in listings. - - {current} - """.format( - current=_get_current_value(caller, "prototype_desc") - ) - - helptext = """ - Giving a brief description helps you and others to locate the prototype for use later. - """ - - text = (text, helptext) - - options = _wizard_options("prototype_desc", "prototype_key", "prototype_tags") - options.append( - { - "key": "_default", - "goto": ( - _set_property, - dict( - prop="prototype_desc", - processor=lambda s: s.strip(), - next_node="node_prototype_desc", - ), - ), - } - ) - - return text, options
- - -# prototype_tags node - - -def _caller_prototype_tags(caller): - prototype = _get_menu_prototype(caller) - tags = prototype.get("prototype_tags", []) - tags = [tag[0] if isinstance(tag, tuple) else tag for tag in tags] - return tags - - -def _add_prototype_tag(caller, tag_string, **kwargs): - """ - Add prototype_tags to the system. We only support straight tags, no - categories (category is assigned automatically). - - Args: - caller (Object): Caller of menu. - tag_string (str): Input from user - only tagname - - Keyword Args: - delete (str): If this is set, tag_string is considered - the name of the tag to delete. - - Returns: - result (str): Result string of action. - - """ - tag = tag_string.strip().lower() - - if tag: - tags = _caller_prototype_tags(caller) - exists = tag in tags - - if "delete" in kwargs: - if exists: - tags.pop(tags.index(tag)) - text = "Removed Prototype-Tag '{}'.".format(tag) - else: - text = "Found no Prototype-Tag to remove." - elif not exists: - # a fresh, new tag - tags.append(tag) - text = "Added Prototype-Tag '{}'.".format(tag) - else: - text = "Prototype-Tag already added." - - _set_prototype_value(caller, "prototype_tags", tags) - else: - text = "No Prototype-Tag specified." - - return text - - -def _prototype_tag_select(caller, tagname): - caller.msg("Prototype-Tag: {}".format(tagname)) - return "node_prototype_tags" - - -def _prototype_tags_actions(caller, raw_inp, **kwargs): - """Parse actions for tags listing""" - choices = kwargs.get("available_choices", []) - tagname, action = _default_parse(raw_inp, choices, ("remove", "r", "delete", "d")) - - if tagname: - if action == "remove": - res = _add_prototype_tag(caller, tagname, delete=True) - caller.msg(res) - else: - res = _add_prototype_tag(caller, raw_inp.lower().strip()) - caller.msg(res) - return "node_prototype_tags" - - -@list_node(_caller_prototype_tags, _prototype_tag_select) -def node_prototype_tags(caller): - - text = """ - |cPrototype-Tags|n can be used to classify and find prototypes in listings Tag names are not - case-sensitive and can have not have a custom category. - - {current} - """.format( - current=_get_current_value( - caller, - "prototype_tags", - formatter=lambda lst: ", ".join(tg for tg in lst), - only_inherit=True, - ) - ) - _set_actioninfo( - caller, _format_list_actions("remove", prefix="|w<text>|n|W to add Tag. Other Action:|n ") - ) - helptext = """ - Using prototype-tags is a good way to organize and group large numbers of prototypes by - genre, type etc. Under the hood, prototypes' tags will all be stored with the category - '{tagmetacategory}'. - """.format( - tagmetacategory=protlib._PROTOTYPE_TAG_META_CATEGORY - ) - - text = (text, helptext) - - options = _wizard_options("prototype_tags", "prototype_desc", "prototype_locks") - options.append({"key": "_default", "goto": _prototype_tags_actions}) - - return text, options - - -# prototype_locks node - - -def _caller_prototype_locks(caller): - locks = _get_menu_prototype(caller).get("prototype_locks", "") - return [lck for lck in locks.split(";") if lck] - - -def _prototype_lock_select(caller, lockstr): - return ( - "node_examine_entity", - {"text": _locks_display(caller, lockstr), "back": "prototype_locks"}, - ) - - -def _prototype_lock_add(caller, lock, **kwargs): - locks = _caller_prototype_locks(caller) - - try: - locktype, lockdef = lock.split(":", 1) - except ValueError: - return "Lockstring lacks ':'." - - locktype = locktype.strip().lower() - - if "delete" in kwargs: - try: - ind = locks.index(lock) - locks.pop(ind) - _set_prototype_value(caller, "prototype_locks", ";".join(locks), parse=False) - ret = "Prototype-lock {} deleted.".format(lock) - except ValueError: - ret = "No Prototype-lock found to delete." - return ret - try: - locktypes = [lck.split(":", 1)[0].strip().lower() for lck in locks] - ind = locktypes.index(locktype) - locks[ind] = lock - ret = "Prototype-lock with locktype '{}' updated.".format(locktype) - except ValueError: - locks.append(lock) - ret = "Added Prototype-lock '{}'.".format(lock) - _set_prototype_value(caller, "prototype_locks", ";".join(locks)) - return ret - - -def _prototype_locks_actions(caller, raw_inp, **kwargs): - choices = kwargs.get("available_choices", []) - lock, action = _default_parse( - raw_inp, choices, ("examine", "e"), ("remove", "r", "delete", "d") - ) - - if lock: - if action == "examine": - return ("node_examine_entity", {"text": _locks_display(caller, lock), "back": "locks"}) - elif action == "remove": - ret = _prototype_lock_add(caller, lock.strip(), delete=True) - caller.msg(ret) - else: - ret = _prototype_lock_add(caller, raw_inp.strip()) - caller.msg(ret) - - return "node_prototype_locks" - - -@list_node(_caller_prototype_locks, _prototype_lock_select) -def node_prototype_locks(caller): - - text = """ - |cPrototype-Locks|n are used to limit access to this prototype when someone else is trying - to access it. By default any prototype can be edited only by the creator and by Admins while - they can be used by anyone with access to the spawn command. There are two valid lock types - the prototype access tools look for: - - - 'edit': Who can edit the prototype. - - 'spawn': Who can spawn new objects with this prototype. - - If unsure, keep the open defaults. - - {current} - """.format( - current=_get_current_value( - caller, - "prototype_locks", - formatter=lambda lstring: "\n".join( - _locks_display(caller, lstr) for lstr in lstring.split(";") - ), - only_inherit=True, - ) - ) - _set_actioninfo(caller, _format_list_actions("examine", "remove", prefix="Actions: ")) - - helptext = """ - Prototype locks can be used to vary access for different tiers of builders. It also allows - developers to produce 'base prototypes' only meant for builders to inherit and expand on - rather than tweak in-place. - """ - - text = (text, helptext) - - options = _wizard_options("prototype_locks", "prototype_tags", "index") - options.append({"key": "_default", "goto": _prototype_locks_actions}) - - return text, options - - -# update existing objects node - - -def _apply_diff(caller, **kwargs): - """update existing objects""" - prototype = kwargs["prototype"] - objects = kwargs["objects"] - back_node = kwargs["back_node"] - diff = kwargs.get("diff", None) - num_changed = spawner.batch_update_objects_with_prototype(prototype, diff=diff, objects=objects) - caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed)) - return back_node - - -def _keep_diff(caller, **kwargs): - """Change to KEEP setting for a given section of a diff""" - # from evennia import set_trace;set_trace(term_size=(182, 50)) - path = kwargs["path"] - diff = kwargs["diff"] - tmp = diff - for key in path[:-1]: - tmp = tmp[key] - tmp[path[-1]] = tuple(list(tmp[path[-1]][:-1]) + ["KEEP"]) - - -def _format_diff_text_and_options(diff, minimal=True, **kwargs): - """ - Reformat the diff in a way suitable for the olc menu. - - Args: - diff (dict): A diff as produced by `prototype_diff`. - minimal (bool, optional): Don't show KEEPs. - - Keyword Args: - any (any): Forwarded into the generated options as arguments to the callable. - - Returns: - texts (list): List of texts. - options (list): List of options dict. - - """ - valid_instructions = ("KEEP", "REMOVE", "ADD", "UPDATE") - - def _visualize(obj, rootname, get_name=False): - if utils.is_iter(obj): - if not obj: - return str(obj) - if get_name: - return obj[0] if obj[0] else "<unset>" - if rootname == "attrs": - return "{} |W=|n {} |W(category:|n {}|W, locks:|n {}|W)|n".format(*obj) - elif rootname == "tags": - return "{} |W(category:|n {}|W)|n".format(obj[0], obj[1]) - - return "{}".format(obj) - - def _parse_diffpart(diffpart, optnum, *args): - typ = type(diffpart) - texts = [] - options = [] - if typ == tuple and len(diffpart) == 3 and diffpart[2] in valid_instructions: - rootname = args[0] - old, new, instruction = diffpart - if instruction == "KEEP": - if not minimal: - texts.append(" |gKEEP|W:|n {old}".format(old=_visualize(old, rootname))) - else: - # instructions we should be able to revert by a menu choice - vold = _visualize(old, rootname) - vnew = _visualize(new, rootname) - vsep = "" if len(vold) < 78 else "\n" - - if instruction == "ADD": - texts.append( - " |c[{optnum}] |yADD|n: {new}".format( - optnum=optnum, new=_visualize(new, rootname) - ) - ) - elif instruction == "REMOVE" and not new: - if rootname == "tags" and old[1] == protlib.PROTOTYPE_TAG_CATEGORY: - # special exception for the prototype-tag mechanism - # this is added post-spawn automatically and should - # not be listed as REMOVE. - return texts, options, optnum - - texts.append( - " |c[{optnum}] |rREMOVE|n: {old}".format( - optnum=optnum, old=_visualize(old, rootname) - ) - ) - else: - vinst = "|y{}|n".format(instruction) - texts.append( - " |c[{num}] {inst}|W:|n {old} |W->|n{sep} {new}".format( - inst=vinst, num=optnum, old=vold, sep=vsep, new=vnew - ) - ) - options.append( - { - "key": str(optnum), - "desc": "|gKEEP|n ({}) {}".format( - rootname, _visualize(old, args[-1], get_name=True) - ), - "goto": (_keep_diff, dict((("path", args), ("diff", diff)), **kwargs)), - } - ) - optnum += 1 - else: - for key in sorted(list(diffpart.keys())): - subdiffpart = diffpart[key] - text, option, optnum = _parse_diffpart(subdiffpart, optnum, *(args + (key,))) - texts.extend(text) - options.extend(option) - return texts, options, optnum - - texts = [] - options = [] - # we use this to allow for skipping full KEEP instructions - optnum = 1 - - for root_key in sorted(diff): - diffpart = diff[root_key] - text, option, optnum = _parse_diffpart(diffpart, optnum, root_key) - heading = "- |w{}:|n ".format(root_key) - if text: - text = [heading + text[0]] + text[1:] - else: - text = [heading] - - texts.extend(text) - options.extend(option) - - return texts, options - - -
[docs]def node_apply_diff(caller, **kwargs): - """Offer options for updating objects""" - - def _keep_option(keyname, prototype, base_obj, obj_prototype, diff, objects, back_node): - """helper returning an option dict""" - options = { - "desc": "Keep {} as-is".format(keyname), - "goto": ( - _keep_diff, - { - "key": keyname, - "prototype": prototype, - "base_obj": base_obj, - "obj_prototype": obj_prototype, - "diff": diff, - "objects": objects, - "back_node": back_node, - }, - ), - } - return options - - prototype = kwargs.get("prototype", None) - update_objects = kwargs.get("objects", None) - back_node = kwargs.get("back_node", "node_index") - obj_prototype = kwargs.get("obj_prototype", None) - base_obj = kwargs.get("base_obj", None) - diff = kwargs.get("diff", None) - custom_location = kwargs.get("custom_location", None) - - if not update_objects: - text = "There are no existing objects to update." - options = {"key": "_default", "goto": back_node} - return text, options - - if not diff: - # use one random object as a reference to calculate a diff - base_obj = choice(update_objects) - - diff, obj_prototype = spawner.prototype_diff_from_object(prototype, base_obj) - - helptext = """ - This will go through all existing objects and apply the changes you accept. - - Be careful with this operation! The upgrade mechanism will try to automatically estimate - what changes need to be applied. But the estimate is |wonly based on the analysis of one - randomly selected object|n among all objects spawned by this prototype. If that object - happens to be unusual in some way the estimate will be off and may lead to unexpected - results for other objects. Always test your objects carefully after an upgrade and consider - being conservative (switch to KEEP) for things you are unsure of. For complex upgrades it - may be better to get help from an administrator with access to the `@py` command for doing - this manually. - - Note that the `location` will never be auto-adjusted because it's so rare to want to - homogenize the location of all object instances.""" - - if not custom_location: - diff.pop("location", None) - - txt, options = _format_diff_text_and_options( - diff, objects=update_objects, base_obj=base_obj, prototype=prototype - ) - - if options: - text = [ - "Suggested changes to {} objects. ".format(len(update_objects)), - "Showing random example obj to change: {name} ({dbref}))\n".format( - name=base_obj.key, dbref=base_obj.dbref - ), - ] + txt - options.extend( - [ - { - "key": ("|wu|Wpdate {} objects".format(len(update_objects)), "update", "u"), - "desc": "Update {} objects".format(len(update_objects)), - "goto": ( - _apply_diff, - { - "prototype": prototype, - "objects": update_objects, - "back_node": back_node, - "diff": diff, - "base_obj": base_obj, - }, - ), - }, - { - "key": ("|wr|Weset changes", "reset", "r"), - "goto": ( - "node_apply_diff", - {"prototype": prototype, "back_node": back_node, "objects": update_objects}, - ), - }, - ] - ) - else: - text = [ - "Analyzed a random sample object (out of {}) - " - "found no changes to apply.".format(len(update_objects)) - ] - - options.extend(_wizard_options("update_objects", back_node[5:], None)) - options.append({"key": "_default", "goto": back_node}) - - text = "\n".join(text) - text = (text, helptext) - - return text, options
- - -# prototype save node - - -
[docs]def node_prototype_save(caller, **kwargs): - """Save prototype to disk """ - # these are only set if we selected 'yes' to save on a previous pass - prototype = kwargs.get("prototype", None) - # set to True/False if answered, None if first pass - accept_save = kwargs.get("accept_save", None) - - if accept_save and prototype: - # we already validated and accepted the save, so this node acts as a goto callback and - # should now only return the next node - prototype_key = prototype.get("prototype_key") - try: - protlib.save_prototype(prototype) - except Exception as exc: - text = "|rCould not save:|n {}\n(press Return to continue)".format(exc) - options = {"key": "_default", "goto": "node_index"} - return text, options - - spawned_objects = protlib.search_objects_with_prototype(prototype_key) - nspawned = spawned_objects.count() - - text = ["|gPrototype saved.|n"] - - if nspawned: - text.append( - "\nDo you want to update {} object(s) " - "already using this prototype?".format(nspawned) - ) - options = ( - { - "key": ("|wY|Wes|n", "yes", "y"), - "desc": "Go to updating screen", - "goto": ( - "node_apply_diff", - { - "accept_update": True, - "objects": spawned_objects, - "prototype": prototype, - "back_node": "node_prototype_save", - }, - ), - }, - {"key": ("[|wN|Wo|n]", "n"), "desc": "Return to index", "goto": "node_index"}, - {"key": "_default", "goto": "node_index"}, - ) - else: - text.append("(press Return to continue)") - options = {"key": "_default", "goto": "node_index"} - - text = "\n".join(text) - - helptext = """ - Updating objects means that the spawner will find all objects previously created by this - prototype. You will be presented with a list of the changes the system will try to apply to - each of these objects and you can choose to customize that change if needed. If you have - done a lot of manual changes to your objects after spawning, you might want to update those - objects manually instead. - """ - - text = (text, helptext) - - return text, options - - # not validated yet - prototype = _get_menu_prototype(caller) - error, text = _validate_prototype(prototype) - - text = [text] - - if error: - # abort save - text.append( - "\n|yValidation errors were found. They need to be corrected before this prototype " - "can be saved (or used to spawn).|n" - ) - options = _wizard_options("prototype_save", "index", None) - options.append({"key": "_default", "goto": "node_index"}) - return "\n".join(text), options - - prototype_key = prototype["prototype_key"] - if protlib.search_prototype(prototype_key): - text.append( - "\nDo you want to save/overwrite the existing prototype '{name}'?".format( - name=prototype_key - ) - ) - else: - text.append("\nDo you want to save the prototype as '{name}'?".format(name=prototype_key)) - - text = "\n".join(text) - - helptext = """ - Saving the prototype makes it available for use later. It can also be used to inherit from, - by name. Depending on |cprototype-locks|n it also makes the prototype usable and/or - editable by others. Consider setting good |cPrototype-tags|n and to give a useful, brief - |cPrototype-desc|n to make the prototype easy to find later. - - """ - - text = (text, helptext) - - options = ( - { - "key": ("[|wY|Wes|n]", "yes", "y"), - "desc": "Save prototype", - "goto": ("node_prototype_save", {"accept_save": True, "prototype": prototype}), - }, - {"key": ("|wN|Wo|n", "n"), "desc": "Abort and return to Index", "goto": "node_index"}, - { - "key": "_default", - "goto": ("node_prototype_save", {"accept_save": True, "prototype": prototype}), - }, - ) - - return text, options
- - -# spawning node - - -def _spawn(caller, **kwargs): - """Spawn prototype""" - prototype = kwargs["prototype"].copy() - new_location = kwargs.get("location", None) - if new_location: - prototype["location"] = new_location - if not prototype.get("location"): - prototype["location"] = caller - - obj = spawner.spawn(prototype) - if obj: - obj = obj[0] - text = "|gNew instance|n {key} ({dbref}) |gspawned at location |n{loc}|n|g.|n".format( - key=obj.key, dbref=obj.dbref, loc=prototype["location"] - ) - else: - text = "|rError: Spawner did not return a new instance.|n" - return "node_examine_entity", {"text": text, "back": "prototype_spawn"} - - -
[docs]def node_prototype_spawn(caller, **kwargs): - """Submenu for spawning the prototype""" - - prototype = _get_menu_prototype(caller) - - already_validated = kwargs.get("already_validated", False) - - if already_validated: - error, text = None, [] - else: - error, text = _validate_prototype(prototype) - text = [text] - - if error: - text.append("\n|rPrototype validation failed. Correct the errors before spawning.|n") - options = _wizard_options("prototype_spawn", "index", None) - return "\n".join(text), options - - text = "\n".join(text) - - helptext = """ - Spawning is the act of instantiating a prototype into an actual object. As a new object is - spawned, every $protfunc in the prototype is called anew. Since this is a common thing to - do, you may also temporarily change the |clocation|n of this prototype to bypass whatever - value is set in the prototype. - - """ - text = (text, helptext) - - # show spawn submenu options - options = [] - prototype_key = prototype["prototype_key"] - location = prototype.get("location", None) - - if location: - options.append( - { - "desc": "Spawn in prototype's defined location ({loc})".format(loc=location), - "goto": ( - _spawn, - dict(prototype=prototype, location=location, custom_location=True), - ), - } - ) - caller_loc = caller.location - if location != caller_loc: - options.append( - { - "desc": "Spawn in {caller}'s location ({loc})".format( - caller=caller, loc=caller_loc - ), - "goto": (_spawn, dict(prototype=prototype, location=caller_loc)), - } - ) - if location != caller_loc != caller: - options.append( - { - "desc": "Spawn in {caller}'s inventory".format(caller=caller), - "goto": (_spawn, dict(prototype=prototype, location=caller)), - } - ) - - spawned_objects = protlib.search_objects_with_prototype(prototype_key) - nspawned = spawned_objects.count() - if spawned_objects: - options.append( - { - "desc": "Update {num} existing objects with this prototype".format(num=nspawned), - "goto": ( - "node_apply_diff", - { - "objects": list(spawned_objects), - "prototype": prototype, - "back_node": "node_prototype_spawn", - }, - ), - } - ) - options.extend(_wizard_options("prototype_spawn", "index", None)) - options.append({"key": "_default", "goto": "node_index"}) - - return text, options
- - -# prototype load node - - -def _prototype_load_select(caller, prototype_key): - matches = protlib.search_prototype(key=prototype_key) - if matches: - prototype = matches[0] - _set_menu_prototype(caller, prototype) - return ( - "node_examine_entity", - { - "text": "|gLoaded prototype {}.|n".format(prototype["prototype_key"]), - "back": "index", - }, - ) - else: - caller.msg("|rFailed to load prototype '{}'.".format(prototype_key)) - return None - - -def _prototype_load_actions(caller, raw_inp, **kwargs): - """Parse the default Convert prototype to a string representation for closer inspection""" - choices = kwargs.get("available_choices", []) - prototype, action = _default_parse( - raw_inp, choices, ("examine", "e", "l"), ("delete", "del", "d") - ) - - if prototype: - - # which action to apply on the selection - if action == "examine": - # examine the prototype - prototype = protlib.search_prototype(key=prototype)[0] - txt = protlib.prototype_to_str(prototype) - return "node_examine_entity", {"text": txt, "back": "prototype_load"} - elif action == "delete": - # delete prototype from disk - try: - protlib.delete_prototype(prototype, caller=caller) - except protlib.PermissionError as err: - txt = "|rDeletion error:|n {}".format(err) - else: - txt = "|gPrototype {} was deleted.|n".format(prototype) - return "node_examine_entity", {"text": txt, "back": "prototype_load"} - - return "node_prototype_load" - - -@list_node(_all_prototype_parents, _prototype_load_select) -def node_prototype_load(caller, **kwargs): - """Load prototype""" - - text = """ - Select a prototype to load. This will replace any prototype currently being edited! - """ - _set_actioninfo(caller, _format_list_actions("examine", "delete")) - - helptext = """ - Loading a prototype will load it and return you to the main index. It can be a good idea - to examine the prototype before loading it. - """ - - text = (text, helptext) - - options = _wizard_options("prototype_load", "index", None) - options.append({"key": "_default", "goto": _prototype_load_actions}) - - return text, options - - -# EvMenu definition, formatting and access functions - - -
[docs]class OLCMenu(EvMenu): - """ - A custom EvMenu with a different formatting for the options. - - """ - -
[docs] def nodetext_formatter(self, nodetext): - """ - Format the node text itself. - - """ - return super(OLCMenu, self).nodetext_formatter(nodetext)
- -
[docs] def options_formatter(self, optionlist): - """ - Split the options into two blocks - olc options and normal options - - """ - olc_keys = ( - "index", - "forward", - "back", - "previous", - "next", - "validate prototype", - "save prototype", - "load prototype", - "spawn prototype", - "search objects", - ) - actioninfo = self.actioninfo + "\n" if hasattr(self, "actioninfo") else "" - self.actioninfo = "" # important, or this could bleed over to other nodes - olc_options = [] - other_options = [] - for key, desc in optionlist: - raw_key = strip_ansi(key).lower() - if raw_key in olc_keys: - desc = " {}".format(desc) if desc else "" - olc_options.append("|lc{}|lt{}|le{}".format(raw_key, key, desc)) - else: - other_options.append((key, desc)) - - olc_options = ( - actioninfo + " |W|||n ".join(olc_options) + " |W|||n " + "|wQ|Wuit" - if olc_options - else "" - ) - other_options = super(OLCMenu, self).options_formatter(other_options) - sep = "\n\n" if olc_options and other_options else "" - - return "{}{}{}".format(olc_options, sep, other_options)
- -
[docs] def helptext_formatter(self, helptext): - """ - Show help text - """ - return "|c --- Help ---|n\n" + utils.dedent(helptext)
- -
[docs] def display_helptext(self): - evmore.msg(self.caller, self.helptext, session=self._session, exit_cmd="look")
- - -
[docs]def start_olc(caller, session=None, prototype=None): - """ - Start menu-driven olc system for prototypes. - - Args: - caller (Object or Account): The entity starting the menu. - session (Session, optional): The individual session to get data. - prototype (dict, optional): Given when editing an existing - prototype rather than creating a new one. - - """ - menudata = { - "node_index": node_index, - "node_validate_prototype": node_validate_prototype, - "node_examine_entity": node_examine_entity, - "node_search_object": node_search_object, - "node_prototype_key": node_prototype_key, - "node_prototype_parent": node_prototype_parent, - "node_typeclass": node_typeclass, - "node_key": node_key, - "node_aliases": node_aliases, - "node_attrs": node_attrs, - "node_tags": node_tags, - "node_locks": node_locks, - "node_permissions": node_permissions, - "node_location": node_location, - "node_home": node_home, - "node_destination": node_destination, - "node_apply_diff": node_apply_diff, - "node_prototype_desc": node_prototype_desc, - "node_prototype_tags": node_prototype_tags, - "node_prototype_locks": node_prototype_locks, - "node_prototype_load": node_prototype_load, - "node_prototype_save": node_prototype_save, - "node_prototype_spawn": node_prototype_spawn, - } - OLCMenu( - caller, - menudata, - startnode="node_index", - session=session, - olc_prototype=prototype, - debug=True, - )
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/prototypes/protfuncs.html b/docs/0.9.5/_modules/evennia/prototypes/protfuncs.html deleted file mode 100644 index 18dbd86392..0000000000 --- a/docs/0.9.5/_modules/evennia/prototypes/protfuncs.html +++ /dev/null @@ -1,452 +0,0 @@ - - - - - - - - evennia.prototypes.protfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.prototypes.protfuncs

-"""
-Protfuncs are function-strings embedded in a prototype and allows for a builder to create a
-prototype with custom logics without having access to Python. The Protfunc is parsed using the
-inlinefunc parser but is fired at the moment the spawning happens, using the creating object's
-session as input.
-
-In the prototype dict, the protfunc is specified as a string inside the prototype, e.g.:
-
-    { ...
-
-    "key": "$funcname(arg1, arg2, ...)"
-
-    ...  }
-
-and multiple functions can be nested (no keyword args are supported). The result will be used as the
-value for that prototype key for that individual spawn.
-
-Available protfuncs are callables in one of the modules of `settings.PROT_FUNC_MODULES`. They
-are specified as functions
-
-    def funcname (*args, **kwargs)
-
-where *args are the arguments given in the prototype, and **kwargs are inserted by Evennia:
-
-    - session (Session): The Session of the entity spawning using this prototype.
-    - prototype (dict): The dict this protfunc is a part of.
-    - current_key (str): The active key this value belongs to in the prototype.
-    - testing (bool): This is set if this function is called as part of the prototype validation; if
-        set, the protfunc should take care not to perform any persistent actions, such as operate on
-        objects or add things to the database.
-
-Any traceback raised by this function will be handled at the time of spawning and abort the spawn
-before any object is created/updated. It must otherwise return the value to store for the specified
-prototype key (this value must be possible to serialize in an Attribute).
-
-"""
-
-from ast import literal_eval
-from random import randint as base_randint, random as base_random, choice as base_choice
-import re
-
-from evennia.utils import search
-from evennia.utils.utils import justify as base_justify, is_iter, to_str
-
-_PROTLIB = None
-
-_RE_DBREF = re.compile(r"\#[0-9]+")
-
-
-# default protfuncs
-
-
-
[docs]def random(*args, **kwargs): - """ - Usage: $random() - Returns a random value in the interval [0, 1) - - """ - return base_random()
- - -
[docs]def randint(*args, **kwargs): - """ - Usage: $randint(start, end) - Returns random integer in interval [start, end] - - """ - if len(args) != 2: - raise TypeError("$randint needs two arguments - start and end.") - start, end = int(args[0]), int(args[1]) - return base_randint(start, end)
- - -
[docs]def left_justify(*args, **kwargs): - """ - Usage: $left_justify(<text>) - Returns <text> left-justified. - - """ - if args: - return base_justify(args[0], align="l") - return ""
- - -
[docs]def right_justify(*args, **kwargs): - """ - Usage: $right_justify(<text>) - Returns <text> right-justified across screen width. - - """ - if args: - return base_justify(args[0], align="r") - return ""
- - -
[docs]def center_justify(*args, **kwargs): - - """ - Usage: $center_justify(<text>) - Returns <text> centered in screen width. - - """ - if args: - return base_justify(args[0], align="c") - return ""
- - -
[docs]def choice(*args, **kwargs): - """ - Usage: $choice(val, val, val, ...) - Returns one of the values randomly - """ - if args: - return base_choice(args) - return ""
- - -
[docs]def full_justify(*args, **kwargs): - - """ - Usage: $full_justify(<text>) - Returns <text> filling up screen width by adding extra space. - - """ - if args: - return base_justify(args[0], align="f") - return ""
- - -
[docs]def protkey(*args, **kwargs): - """ - Usage: $protkey(<key>) - Returns the value of another key in this prototoype. Will raise an error if - the key is not found in this prototype. - - """ - if args: - prototype = kwargs["prototype"] - return prototype[args[0].strip()]
- - -
[docs]def add(*args, **kwargs): - """ - Usage: $add(val1, val2) - Returns the result of val1 + val2. Values must be - valid simple Python structures possible to add, - such as numbers, lists etc. - - """ - if len(args) > 1: - val1, val2 = args[0], args[1] - # try to convert to python structures, otherwise, keep as strings - try: - val1 = literal_eval(val1.strip()) - except Exception: - pass - try: - val2 = literal_eval(val2.strip()) - except Exception: - pass - return val1 + val2 - raise ValueError("$add requires two arguments.")
- - -
[docs]def sub(*args, **kwargs): - """ - Usage: $del(val1, val2) - Returns the value of val1 - val2. Values must be - valid simple Python structures possible to - subtract. - - """ - if len(args) > 1: - val1, val2 = args[0], args[1] - # try to convert to python structures, otherwise, keep as strings - try: - val1 = literal_eval(val1.strip()) - except Exception: - pass - try: - val2 = literal_eval(val2.strip()) - except Exception: - pass - return val1 - val2 - raise ValueError("$sub requires two arguments.")
- - -
[docs]def mult(*args, **kwargs): - """ - Usage: $mul(val1, val2) - Returns the value of val1 * val2. The values must be - valid simple Python structures possible to - multiply, like strings and/or numbers. - - """ - if len(args) > 1: - val1, val2 = args[0], args[1] - # try to convert to python structures, otherwise, keep as strings - try: - val1 = literal_eval(val1.strip()) - except Exception: - pass - try: - val2 = literal_eval(val2.strip()) - except Exception: - pass - return val1 * val2 - raise ValueError("$mul requires two arguments.")
- - -
[docs]def div(*args, **kwargs): - """ - Usage: $div(val1, val2) - Returns the value of val1 / val2. Values must be numbers and - the result is always a float. - - """ - if len(args) > 1: - val1, val2 = args[0], args[1] - # try to convert to python structures, otherwise, keep as strings - try: - val1 = literal_eval(val1.strip()) - except Exception: - pass - try: - val2 = literal_eval(val2.strip()) - except Exception: - pass - return val1 / float(val2) - raise ValueError("$mult requires two arguments.")
- - -
[docs]def toint(*args, **kwargs): - """ - Usage: $toint(<number>) - Returns <number> as an integer. - """ - if args: - val = args[0] - try: - return int(literal_eval(val.strip())) - except ValueError: - return val - raise ValueError("$toint requires one argument.")
- - -
[docs]def eval(*args, **kwargs): - """ - Usage $eval(<expression>) - Returns evaluation of a simple Python expression. The string may *only* consist of the following - Python literal structures: strings, numbers, tuples, lists, dicts, booleans, - and None. The strings can also contain #dbrefs. Escape embedded protfuncs as $$protfunc(..) - - those will then be evaluated *after* $eval. - - """ - global _PROTLIB - if not _PROTLIB: - from evennia.prototypes import prototypes as _PROTLIB - - string = ",".join(args) - struct = literal_eval(string) - - if isinstance(struct, str): - # we must shield the string, otherwise it will be merged as a string and future - # literal_evas will pick up e.g. '2' as something that should be converted to a number - struct = '"{}"'.format(struct) - - # convert any #dbrefs to objects (also in nested structures) - struct = _PROTLIB.value_to_obj_or_any(struct) - - return struct
- - -def _obj_search(*args, **kwargs): - "Helper function to search for an object" - - query = "".join(args) - session = kwargs.get("session", None) - return_list = kwargs.pop("return_list", False) - account = None - - if session: - account = session.account - - targets = search.search_object(query) - - if return_list: - retlist = [] - if account: - for target in targets: - if target.access(account, target, "control"): - retlist.append(target) - else: - retlist = targets - return retlist - else: - # single-match - if not targets: - raise ValueError("$obj: Query '{}' gave no matches.".format(query)) - if len(targets) > 1: - raise ValueError( - "$obj: Query '{query}' gave {nmatches} matches. Limit your " - "query or use $objlist instead.".format(query=query, nmatches=len(targets)) - ) - target = targets[0] - if account: - if not target.access(account, target, "control"): - raise ValueError( - "$obj: Obj {target}(#{dbref} cannot be added - " - "Account {account} does not have 'control' access.".format( - target=target.key, dbref=target.id, account=account - ) - ) - return target - - -
[docs]def obj(*args, **kwargs): - """ - Usage $obj(<query>) - Returns one Object searched globally by key, alias or #dbref. Error if more than one. - - """ - obj = _obj_search(return_list=False, *args, **kwargs) - if obj: - return "#{}".format(obj.id) - return "".join(args)
- - -
[docs]def objlist(*args, **kwargs): - """ - Usage $objlist(<query>) - Returns list with one or more Objects searched globally by key, alias or #dbref. - - """ - return ["#{}".format(obj.id) for obj in _obj_search(return_list=True, *args, **kwargs)]
- - -
[docs]def dbref(*args, **kwargs): - """ - Usage $dbref(<#dbref>) - Validate that a #dbref input is valid. - """ - if not args or len(args) < 1 or _RE_DBREF.match(args[0]) is None: - raise ValueError("$dbref requires a valid #dbref argument.") - - return obj(args[0])
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/prototypes/prototypes.html b/docs/0.9.5/_modules/evennia/prototypes/prototypes.html deleted file mode 100644 index 5fe5f772fe..0000000000 --- a/docs/0.9.5/_modules/evennia/prototypes/prototypes.html +++ /dev/null @@ -1,1078 +0,0 @@ - - - - - - - - evennia.prototypes.prototypes — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.prototypes.prototypes

-"""
-
-Handling storage of prototypes, both database-based ones (DBPrototypes) and those defined in modules
-(Read-only prototypes). Also contains utility functions, formatters and manager functions.
-
-"""
-
-import hashlib
-import time
-from ast import literal_eval
-from django.conf import settings
-from django.db.models import Q, Subquery
-from django.core.paginator import Paginator
-from evennia.scripts.scripts import DefaultScript
-from evennia.objects.models import ObjectDB
-from evennia.typeclasses.attributes import Attribute
-from evennia.utils.create import create_script
-from evennia.utils.evmore import EvMore
-from evennia.utils.utils import (
-    all_from_module,
-    make_iter,
-    is_iter,
-    dbid_to_obj,
-    callables_from_module,
-    get_all_typeclasses,
-    to_str,
-    dbref,
-    justify,
-    class_from_module,
-)
-from evennia.locks.lockhandler import validate_lockstring, check_lockstring
-from evennia.utils import logger
-from evennia.utils import inlinefuncs, dbserialize
-from evennia.utils.evtable import EvTable
-
-
-_MODULE_PROTOTYPE_MODULES = {}
-_MODULE_PROTOTYPES = {}
-_PROTOTYPE_META_NAMES = (
-    "prototype_key",
-    "prototype_desc",
-    "prototype_tags",
-    "prototype_locks",
-    "prototype_parent",
-)
-_PROTOTYPE_RESERVED_KEYS = _PROTOTYPE_META_NAMES + (
-    "key",
-    "aliases",
-    "typeclass",
-    "location",
-    "home",
-    "destination",
-    "permissions",
-    "locks",
-    "exec",
-    "tags",
-    "attrs",
-)
-PROTOTYPE_TAG_CATEGORY = "from_prototype"
-_PROTOTYPE_TAG_META_CATEGORY = "db_prototype"
-PROT_FUNCS = {}
-
-_PROTOTYPE_FALLBACK_LOCK = "spawn:all();edit:all()"
-
-
-
[docs]class PermissionError(RuntimeError): - pass
- - -
[docs]class ValidationError(RuntimeError): - """ - Raised on prototype validation errors - """ - - pass
- - -
[docs]def homogenize_prototype(prototype, custom_keys=None): - """ - Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form. - - - Args: - prototype (dict): Prototype. - custom_keys (list, optional): Custom keys which should not be interpreted as attrs, beyond - the default reserved keys. - - Returns: - homogenized (dict): Prototype where all non-identified keys grouped as attributes and other - homogenizations like adding missing prototype_keys and setting a default typeclass. - - """ - if not prototype or not isinstance(prototype, dict): - return {} - - reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ()) - - # correct cases of setting None for certain values - for protkey in prototype: - if prototype[protkey] is None: - if protkey in ("attrs", "tags", "prototype_tags"): - prototype[protkey] = [] - elif protkey in ("prototype_key", "prototype_desc"): - prototype[protkey] = "" - - attrs = list(prototype.get("attrs", [])) # break reference - tags = make_iter(prototype.get("tags", [])) - homogenized_tags = [] - - homogenized = {} - for key, val in prototype.items(): - if key in reserved: - if key == "tags": - for tag in tags: - if not is_iter(tag): - homogenized_tags.append((tag, None, None)) - else: - homogenized_tags.append(tag) - else: - homogenized[key] = val - else: - # unassigned keys -> attrs - attrs.append((key, val, None, "")) - if attrs: - homogenized["attrs"] = attrs - if homogenized_tags: - homogenized["tags"] = homogenized_tags - - # add required missing parts that had defaults before - - homogenized["prototype_key"] = homogenized.get( - "prototype_key", - # assign a random hash as key - "prototype-{}".format(hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:7]), - ) - homogenized["prototype_tags"] = homogenized.get("prototype_tags", []) - homogenized["prototype_locks"] = homogenized.get("prototype_lock", _PROTOTYPE_FALLBACK_LOCK) - homogenized["prototype_desc"] = homogenized.get("prototype_desc", "") - if "typeclass" not in prototype and "prototype_parent" not in prototype: - homogenized["typeclass"] = settings.BASE_OBJECT_TYPECLASS - - return homogenized
- - -# module-based prototypes - -
[docs]def load_module_prototypes(): - """ - This is called by `evennia.__init__` as Evennia initializes. It's important - to do this late so as to not interfere with evennia initialization. - - """ - for mod in settings.PROTOTYPE_MODULES: - # to remove a default prototype, override it with an empty dict. - # internally we store as (key, desc, locks, tags, prototype_dict) - prots = [] - for variable_name, prot in all_from_module(mod).items(): - if isinstance(prot, dict): - if "prototype_key" not in prot: - prot["prototype_key"] = variable_name.lower() - prots.append((prot["prototype_key"], homogenize_prototype(prot))) - # assign module path to each prototype_key for easy reference - _MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots}) - # make sure the prototype contains all meta info - for prototype_key, prot in prots: - actual_prot_key = prot.get("prototype_key", prototype_key).lower() - prot.update( - { - "prototype_key": actual_prot_key, - "prototype_desc": prot["prototype_desc"] if "prototype_desc" in prot else mod, - "prototype_locks": ( - prot["prototype_locks"] - if "prototype_locks" in prot - else "use:all();edit:false()" - ), - "prototype_tags": list( - set(list(make_iter(prot.get("prototype_tags", []))) + ["module"]) - ), - } - ) - _MODULE_PROTOTYPES[actual_prot_key] = prot
- - -# Db-based prototypes - - -
[docs]class DbPrototype(DefaultScript): - """ - This stores a single prototype, in an Attribute `prototype`. - """ - -
[docs] def at_script_creation(self): - self.key = "empty prototype" # prototype_key - self.desc = "A prototype" # prototype_desc (.tags are used for prototype_tags) - self.db.prototype = {} # actual prototype
- - @property - def prototype(self): - "Make sure to decouple from db!" - return dbserialize.deserialize(self.attributes.get("prototype", {})) - - @prototype.setter - def prototype(self, prototype): - self.attributes.add("prototype", prototype)
- - -# Prototype manager functions - - -
[docs]def save_prototype(prototype): - """ - Create/Store a prototype persistently. - - Args: - prototype (dict): The prototype to save. A `prototype_key` key is - required. - - Returns: - prototype (dict or None): The prototype stored using the given kwargs, None if deleting. - - Raises: - prototypes.ValidationError: If prototype does not validate. - - Note: - No edit/spawn locks will be checked here - if this function is called the caller - is expected to have valid permissions. - - """ - in_prototype = prototype - in_prototype = homogenize_prototype(in_prototype) - - def _to_batchtuple(inp, *args): - "build tuple suitable for batch-creation" - if is_iter(inp): - # already a tuple/list, use as-is - return inp - return (inp,) + args - - prototype_key = in_prototype.get("prototype_key") - if not prototype_key: - raise ValidationError("Prototype requires a prototype_key") - - prototype_key = str(prototype_key).lower() - - # we can't edit a prototype defined in a module - if prototype_key in _MODULE_PROTOTYPES: - mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A") - raise PermissionError( - "{} is a read-only prototype " "(defined as code in {}).".format(prototype_key, mod) - ) - - # make sure meta properties are included with defaults - in_prototype["prototype_desc"] = in_prototype.get( - "prototype_desc", prototype.get("prototype_desc", "") - ) - prototype_locks = in_prototype.get( - "prototype_locks", prototype.get("prototype_locks", _PROTOTYPE_FALLBACK_LOCK) - ) - is_valid, err = validate_lockstring(prototype_locks) - if not is_valid: - raise ValidationError("Lock error: {}".format(err)) - in_prototype["prototype_locks"] = prototype_locks - - prototype_tags = [ - _to_batchtuple(tag, _PROTOTYPE_TAG_META_CATEGORY) - for tag in make_iter( - in_prototype.get("prototype_tags", prototype.get("prototype_tags", [])) - ) - ] - in_prototype["prototype_tags"] = prototype_tags - - stored_prototype = DbPrototype.objects.filter(db_key=prototype_key) - if stored_prototype: - # edit existing prototype - stored_prototype = stored_prototype[0] - stored_prototype.desc = in_prototype["prototype_desc"] - if prototype_tags: - stored_prototype.tags.clear(category=PROTOTYPE_TAG_CATEGORY) - stored_prototype.tags.batch_add(*in_prototype["prototype_tags"]) - stored_prototype.locks.add(in_prototype["prototype_locks"]) - stored_prototype.attributes.add("prototype", in_prototype) - else: - # create a new prototype - stored_prototype = create_script( - DbPrototype, - key=prototype_key, - desc=in_prototype["prototype_desc"], - persistent=True, - locks=prototype_locks, - tags=in_prototype["prototype_tags"], - attributes=[("prototype", in_prototype)], - ) - return stored_prototype.prototype
- - -create_prototype = save_prototype # alias - - -
[docs]def delete_prototype(prototype_key, caller=None): - """ - Delete a stored prototype - - Args: - key (str): The persistent prototype to delete. - caller (Account or Object, optionsl): Caller aiming to delete a prototype. - Note that no locks will be checked if`caller` is not passed. - Returns: - success (bool): If deletion worked or not. - Raises: - PermissionError: If 'edit' lock was not passed or deletion failed for some other reason. - - """ - if prototype_key in _MODULE_PROTOTYPES: - mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key.lower(), "N/A") - raise PermissionError( - "{} is a read-only prototype " "(defined as code in {}).".format(prototype_key, mod) - ) - - stored_prototype = DbPrototype.objects.filter(db_key__iexact=prototype_key) - - if not stored_prototype: - raise PermissionError("Prototype {} was not found.".format(prototype_key)) - - stored_prototype = stored_prototype[0] - if caller: - if not stored_prototype.access(caller, "edit"): - raise PermissionError( - "{} needs explicit 'edit' permissions to " - "delete prototype {}.".format(caller, prototype_key) - ) - stored_prototype.delete() - return True
- - -
[docs]def search_prototype(key=None, tags=None, require_single=False, return_iterators=False): - """ - Find prototypes based on key and/or tags, or all prototypes. - - Keyword Args: - key (str): An exact or partial key to query for. - tags (str or list): Tag key or keys to query for. These - will always be applied with the 'db_protototype' - tag category. - require_single (bool): If set, raise KeyError if the result - was not found or if there are multiple matches. - return_iterators (bool): Optimized return for large numbers of db-prototypes. - If set, separate returns of module based prototypes and paginate - the db-prototype return. - - Return: - matches (list): Default return, all found prototype dicts. Empty list if - no match was found. Note that if neither `key` nor `tags` - were given, *all* available prototypes will be returned. - list, queryset: If `return_iterators` are found, this is a list of - module-based prototypes followed by a *paginated* queryset of - db-prototypes. - - Raises: - KeyError: If `require_single` is True and there are 0 or >1 matches. - - Note: - The available prototypes is a combination of those supplied in - PROTOTYPE_MODULES and those stored in the database. Note that if - tags are given and the prototype has no tags defined, it will not - be found as a match. - - """ - # prototype keys are always in lowecase - if key: - key = key.lower() - - # search module prototypes - - mod_matches = {} - if tags: - # use tags to limit selection - tagset = set(tags) - mod_matches = { - prototype_key: prototype - for prototype_key, prototype in _MODULE_PROTOTYPES.items() - if tagset.intersection(prototype.get("prototype_tags", [])) - } - else: - mod_matches = _MODULE_PROTOTYPES - - allow_fuzzy = True - if key: - if key in mod_matches: - # exact match - module_prototypes = [mod_matches[key]] - allow_fuzzy = False - else: - # fuzzy matching - module_prototypes = [ - prototype - for prototype_key, prototype in mod_matches.items() - if key in prototype_key - ] - else: - module_prototypes = [match for match in mod_matches.values()] - - # search db-stored prototypes - - if tags: - # exact match on tag(s) - tags = make_iter(tags) - tag_categories = ["db_prototype" for _ in tags] - db_matches = DbPrototype.objects.get_by_tag(tags, tag_categories) - else: - db_matches = DbPrototype.objects.all() - - if key: - # exact or partial match on key - exact_match = db_matches.filter(Q(db_key__iexact=key)).order_by("db_key") - if not exact_match and allow_fuzzy: - # try with partial match instead - db_matches = db_matches.filter(Q(db_key__icontains=key)).order_by("db_key") - else: - db_matches = exact_match - - # convert to prototype - db_ids = db_matches.values_list("id", flat=True) - db_matches = ( - Attribute.objects.filter(scriptdb__pk__in=db_ids, db_key="prototype") - .values_list("db_value", flat=True) - .order_by("scriptdb__db_key") - ) - if key and require_single: - nmodules = len(module_prototypes) - ndbprots = db_matches.count() - if nmodules + ndbprots != 1: - raise KeyError(f"Found {nmodules + ndbprots} matching prototypes {module_prototypes}.") - - if return_iterators: - # trying to get the entire set of prototypes - we must paginate - # the result instead of trying to fetch the entire set at once - return db_matches, module_prototypes - else: - # full fetch, no pagination (compatibility mode) - return list(db_matches) + module_prototypes
- - -
[docs]def search_objects_with_prototype(prototype_key): - """ - Retrieve all object instances created by a given prototype. - - Args: - prototype_key (str): The exact (and unique) prototype identifier to query for. - - Returns: - matches (Queryset): All matching objects spawned from this prototype. - - """ - return ObjectDB.objects.get_by_tag(key=prototype_key, category=PROTOTYPE_TAG_CATEGORY)
- - -
[docs]class PrototypeEvMore(EvMore): - """ - Listing 1000+ prototypes can be very slow. So we customize EvMore to - display an EvTable per paginated page rather than to try creating an - EvTable for the entire dataset and then paginate it. - """ - -
[docs] def __init__(self, caller, *args, session=None, **kwargs): - """Store some extra properties on the EvMore class""" - self.show_non_use = kwargs.pop("show_non_use", False) - self.show_non_edit = kwargs.pop("show_non_edit", False) - super().__init__(caller, *args, session=session, **kwargs)
- -
[docs] def init_pages(self, inp): - """ - This will be initialized with a tuple (mod_prototype_list, paginated_db_query) - and we must handle these separately since they cannot be paginated in the same - way. We will build the prototypes so that the db-prototypes come first (they - are likely the most volatile), followed by the mod-prototypes. - """ - dbprot_query, modprot_list = inp - # set the number of entries per page to half the reported height of the screen - # to account for long descs etc - dbprot_paged = Paginator(dbprot_query, max(1, int(self.height / 2))) - - # we separate the different types of data, so we track how many pages there are - # of each. - n_mod = len(modprot_list) - self._npages_mod = n_mod // self.height + (0 if n_mod % self.height == 0 else 1) - self._db_count = dbprot_paged.count - self._npages_db = dbprot_paged.num_pages if self._db_count > 0 else 0 - # total number of pages - self._npages = self._npages_mod + self._npages_db - self._data = (dbprot_paged, modprot_list) - self._paginator = self.prototype_paginator
- -
[docs] def prototype_paginator(self, pageno): - """ - The listing is separated in db/mod prototypes, so we need to figure out which - one to pick based on the page number. Also, pageno starts from 0. - """ - dbprot_pages, modprot_list = self._data - - if self._db_count and pageno < self._npages_db: - return dbprot_pages.page(pageno + 1) - else: - # get the correct slice, adjusted for the db-prototypes - pageno = max(0, pageno - self._npages_db) - return modprot_list[pageno * self.height : pageno * self.height + self.height]
- -
[docs] def page_formatter(self, page): - """Input is a queryset page from django.Paginator""" - caller = self._caller - - # get use-permissions of readonly attributes (edit is always False) - display_tuples = [] - - table = EvTable( - "|wKey|n", - "|wSpawn/Edit|n", - "|wTags|n", - "|wDesc|n", - border="tablecols", - crop=True, - width=self.width, - ) - - for prototype in page: - lock_use = caller.locks.check_lockstring( - caller, prototype.get("prototype_locks", ""), access_type="spawn", default=True - ) - if not self.show_non_use and not lock_use: - continue - if prototype.get("prototype_key", "") in _MODULE_PROTOTYPES: - lock_edit = False - else: - lock_edit = caller.locks.check_lockstring( - caller, prototype.get("prototype_locks", ""), access_type="edit", default=True - ) - if not self.show_non_edit and not lock_edit: - continue - ptags = [] - for ptag in prototype.get("prototype_tags", []): - if is_iter(ptag): - if len(ptag) > 1: - ptags.append("{}".format(ptag[0])) - else: - ptags.append(ptag[0]) - else: - ptags.append(str(ptag)) - - table.add_row( - prototype.get("prototype_key", "<unset>"), - "{}/{}".format("Y" if lock_use else "N", "Y" if lock_edit else "N"), - ", ".join(list(set(ptags))), - prototype.get("prototype_desc", "<unset>"), - ) - - return str(table)
- - -
[docs]def list_prototypes( - caller, key=None, tags=None, show_non_use=False, show_non_edit=True, session=None -): - """ - Collate a list of found prototypes based on search criteria and access. - - Args: - caller (Account or Object): The object requesting the list. - key (str, optional): Exact or partial prototype key to query for. - tags (str or list, optional): Tag key or keys to query for. - show_non_use (bool, optional): Show also prototypes the caller may not use. - show_non_edit (bool, optional): Show also prototypes the caller may not edit. - session (Session, optional): If given, this is used for display formatting. - Returns: - PrototypeEvMore: An EvMore subclass optimized for prototype listings. - None: If no matches were found. In this case the caller has already been notified. - - """ - # this allows us to pass lists of empty strings - tags = [tag for tag in make_iter(tags) if tag] - - dbprot_query, modprot_list = search_prototype(key, tags, return_iterators=True) - - if not dbprot_query and not modprot_list: - caller.msg("No prototypes found.", session=session) - return None - - # get specific prototype (one value or exception) - return PrototypeEvMore( - caller, - (dbprot_query, modprot_list), - session=session, - show_non_use=show_non_use, - show_non_edit=show_non_edit, - )
- - -
[docs]def validate_prototype( - prototype, protkey=None, protparents=None, is_prototype_base=True, strict=True, _flags=None -): - """ - Run validation on a prototype, checking for inifinite regress. - - Args: - prototype (dict): Prototype to validate. - protkey (str, optional): The name of the prototype definition. If not given, the prototype - dict needs to have the `prototype_key` field set. - protpartents (dict, optional): The available prototype parent library. If - note given this will be determined from settings/database. - is_prototype_base (bool, optional): We are trying to create a new object *based on this - object*. This means we can't allow 'mixin'-style prototypes without typeclass/parent - etc. - strict (bool, optional): If unset, don't require needed keys, only check against infinite - recursion etc. - _flags (dict, optional): Internal work dict that should not be set externally. - Raises: - RuntimeError: If prototype has invalid structure. - RuntimeWarning: If prototype has issues that would make it unsuitable to build an object - with (it may still be useful as a mix-in prototype). - - """ - assert isinstance(prototype, dict) - - if _flags is None: - _flags = {"visited": [], "depth": 0, "typeclass": False, "errors": [], "warnings": []} - - if not protparents: - protparents = { - prototype.get("prototype_key", "").lower(): prototype - for prototype in search_prototype() - } - - protkey = protkey and protkey.lower() or prototype.get("prototype_key", None) - - if strict and not bool(protkey): - _flags["errors"].append("Prototype lacks a `prototype_key`.") - protkey = "[UNSET]" - - typeclass = prototype.get("typeclass") - prototype_parent = prototype.get("prototype_parent", []) - - if strict and not (typeclass or prototype_parent): - if is_prototype_base: - _flags["errors"].append( - "Prototype {} requires `typeclass` " "or 'prototype_parent'.".format(protkey) - ) - else: - _flags["warnings"].append( - "Prototype {} can only be used as a mixin since it lacks " - "a typeclass or a prototype_parent.".format(protkey) - ) - - if strict and typeclass: - try: - class_from_module(typeclass) - except ImportError as err: - _flags["errors"].append( - "{}: Prototype {} is based on typeclass {}, which could not be imported!".format( - err, protkey, typeclass - ) - ) - - # recursively traverse prototype_parent chain - - for protstring in make_iter(prototype_parent): - protstring = protstring.lower() - if protkey is not None and protstring == protkey: - _flags["errors"].append("Prototype {} tries to parent itself.".format(protkey)) - protparent = protparents.get(protstring) - if not protparent: - _flags["errors"].append( - "Prototype {}'s prototype_parent '{}' was not found.".format(protkey, protstring) - ) - if id(prototype) in _flags["visited"]: - _flags["errors"].append( - "{} has infinite nesting of prototypes.".format(protkey or prototype) - ) - - if _flags["errors"]: - raise RuntimeError("Error: " + "\nError: ".join(_flags["errors"])) - _flags["visited"].append(id(prototype)) - _flags["depth"] += 1 - validate_prototype( - protparent, protstring, protparents, is_prototype_base=is_prototype_base, _flags=_flags - ) - _flags["visited"].pop() - _flags["depth"] -= 1 - - if typeclass and not _flags["typeclass"]: - _flags["typeclass"] = typeclass - - # if we get back to the current level without a typeclass it's an error. - if strict and is_prototype_base and _flags["depth"] <= 0 and not _flags["typeclass"]: - _flags["errors"].append( - "Prototype {} has no `typeclass` defined anywhere in its parent\n " - "chain. Add `typeclass`, or a `prototype_parent` pointing to a " - "prototype with a typeclass.".format(protkey) - ) - - if _flags["depth"] <= 0: - if _flags["errors"]: - raise RuntimeError("Error: " + "\nError: ".join(_flags["errors"])) - if _flags["warnings"]: - raise RuntimeWarning("Warning: " + "\nWarning: ".join(_flags["warnings"])) - - # make sure prototype_locks are set to defaults - prototype_locks = [ - lstring.split(":", 1) - for lstring in prototype.get("prototype_locks", "").split(";") - if ":" in lstring - ] - locktypes = [tup[0].strip() for tup in prototype_locks] - if "spawn" not in locktypes: - prototype_locks.append(("spawn", "all()")) - if "edit" not in locktypes: - prototype_locks.append(("edit", "all()")) - prototype_locks = ";".join(":".join(tup) for tup in prototype_locks) - prototype["prototype_locks"] = prototype_locks
- - -# Protfunc parsing (in-prototype functions) - -for mod in settings.PROT_FUNC_MODULES: - try: - callables = callables_from_module(mod) - PROT_FUNCS.update(callables) - except ImportError: - logger.log_trace() - raise - - -
[docs]def protfunc_parser(value, available_functions=None, testing=False, stacktrace=False, **kwargs): - """ - Parse a prototype value string for a protfunc and process it. - - Available protfuncs are specified as callables in one of the modules of - `settings.PROTFUNC_MODULES`, or specified on the command line. - - Args: - value (any): The value to test for a parseable protfunc. Only strings will be parsed for - protfuncs, all other types are returned as-is. - available_functions (dict, optional): Mapping of name:protfunction to use for this parsing. - If not set, use default sources. - testing (bool, optional): Passed to protfunc. If in a testing mode, some protfuncs may - behave differently. - stacktrace (bool, optional): If set, print the stack parsing process of the protfunc-parser. - - Keyword Args: - session (Session): Passed to protfunc. Session of the entity spawning the prototype. - protototype (dict): Passed to protfunc. The dict this protfunc is a part of. - current_key(str): Passed to protfunc. The key in the prototype that will hold this value. - any (any): Passed on to the protfunc. - - Returns: - testresult (tuple): If `testing` is set, returns a tuple (error, result) where error is - either None or a string detailing the error from protfunc_parser or seen when trying to - run `literal_eval` on the parsed string. - any (any): A structure to replace the string on the prototype level. If this is a - callable or a (callable, (args,)) structure, it will be executed as if one had supplied - it to the prototype directly. This structure is also passed through literal_eval so one - can get actual Python primitives out of it (not just strings). It will also identify - eventual object #dbrefs in the output from the protfunc. - - """ - if not isinstance(value, str): - return value - - available_functions = PROT_FUNCS if available_functions is None else available_functions - - result = inlinefuncs.parse_inlinefunc( - value, available_funcs=available_functions, stacktrace=stacktrace, testing=testing, **kwargs - ) - - err = None - try: - result = literal_eval(result) - except ValueError: - pass - except Exception as exc: - err = str(exc) - if testing: - return err, result - return result
- - -# Various prototype utilities - - -
[docs]def format_available_protfuncs(): - """ - Get all protfuncs in a pretty-formatted form. - - Args: - clr (str, optional): What coloration tag to use. - """ - out = [] - for protfunc_name, protfunc in PROT_FUNCS.items(): - out.append( - "- |c${name}|n - |W{docs}".format( - name=protfunc_name, docs=protfunc.__doc__.strip().replace("\n", "") - ) - ) - return justify("\n".join(out), indent=8)
- - -
[docs]def prototype_to_str(prototype): - """ - Format a prototype to a nice string representation. - - Args: - prototype (dict): The prototype. - """ - - prototype = homogenize_prototype(prototype) - - header = """ -|cprototype-key:|n {prototype_key}, |c-tags:|n {prototype_tags}, |c-locks:|n {prototype_locks}|n -|c-desc|n: {prototype_desc} -|cprototype-parent:|n {prototype_parent} - \n""".format( - prototype_key=prototype.get("prototype_key", "|r[UNSET](required)|n"), - prototype_tags=prototype.get("prototype_tags", "|wNone|n"), - prototype_locks=prototype.get("prototype_locks", "|wNone|n"), - prototype_desc=prototype.get("prototype_desc", "|wNone|n"), - prototype_parent=prototype.get("prototype_parent", "|wNone|n"), - ) - key = aliases = attrs = tags = locks = permissions = location = home = destination = "" - if "key" in prototype: - key = prototype["key"] - key = "|ckey:|n {key}".format(key=key) - if "aliases" in prototype: - aliases = prototype["aliases"] - aliases = "|caliases:|n {aliases}".format(aliases=", ".join(aliases)) - if "attrs" in prototype: - attrs = prototype["attrs"] - out = [] - for (attrkey, value, category, locks) in attrs: - locks = ", ".join(lock for lock in locks if lock) - category = "|ccategory:|n {}".format(category) if category else "" - cat_locks = "" - if category or locks: - cat_locks = " (|ccategory:|n {category}, ".format( - category=category if category else "|wNone|n" - ) - out.append( - "{attrkey}{cat_locks} |c=|n {value}".format( - attrkey=attrkey, - cat_locks=cat_locks, - locks=locks if locks else "|wNone|n", - value=value, - ) - ) - attrs = "|cattrs:|n\n {attrs}".format(attrs="\n ".join(out)) - if "tags" in prototype: - tags = prototype["tags"] - out = [] - for (tagkey, category, data) in tags: - out.append( - "{tagkey} (category: {category}{dat})".format( - tagkey=tagkey, category=category, dat=", data: {}".format(data) if data else "" - ) - ) - tags = "|ctags:|n\n {tags}".format(tags=", ".join(out)) - if "locks" in prototype: - locks = prototype["locks"] - locks = "|clocks:|n\n {locks}".format(locks=locks) - if "permissions" in prototype: - permissions = prototype["permissions"] - permissions = "|cpermissions:|n {perms}".format(perms=", ".join(permissions)) - if "location" in prototype: - location = prototype["location"] - location = "|clocation:|n {location}".format(location=location) - if "home" in prototype: - home = prototype["home"] - home = "|chome:|n {home}".format(home=home) - if "destination" in prototype: - destination = prototype["destination"] - destination = "|cdestination:|n {destination}".format(destination=destination) - - body = "\n".join( - part - for part in (key, aliases, attrs, tags, locks, permissions, location, home, destination) - if part - ) - - return header.lstrip() + body.strip()
- - -
[docs]def check_permission(prototype_key, action, default=True): - """ - Helper function to check access to actions on given prototype. - - Args: - prototype_key (str): The prototype to affect. - action (str): One of "spawn" or "edit". - default (str): If action is unknown or prototype has no locks - - Returns: - passes (bool): If permission for action is granted or not. - - """ - if action == "edit": - if prototype_key in _MODULE_PROTOTYPES: - mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A") - logger.log_err( - "{} is a read-only prototype " "(defined as code in {}).".format(prototype_key, mod) - ) - return False - - prototype = search_prototype(key=prototype_key) - if not prototype: - logger.log_err("Prototype {} not found.".format(prototype_key)) - return False - - lockstring = prototype.get("prototype_locks") - - if lockstring: - return check_lockstring(None, lockstring, default=default, access_type=action) - return default
- - -
[docs]def init_spawn_value(value, validator=None): - """ - Analyze the prototype value and produce a value useful at the point of spawning. - - Args: - value (any): This can be: - callable - will be called as callable() - (callable, (args,)) - will be called as callable(*args) - other - will be assigned depending on the variable type - validator (callable, optional): If given, this will be called with the value to - check and guarantee the outcome is of a given type. - - Returns: - any (any): The (potentially pre-processed value to use for this prototype key) - - """ - validator = validator if validator else lambda o: o - if callable(value): - value = validator(value()) - elif value and isinstance(value, (list, tuple)) and callable(value[0]): - # a structure (callable, (args, )) - args = value[1:] - value = validator(value[0](*make_iter(args))) - else: - value = validator(value) - result = protfunc_parser(value) - if result != value: - return validator(result) - return result
- - -
[docs]def value_to_obj_or_any(value): - "Convert value(s) to Object if possible, otherwise keep original value" - stype = type(value) - if is_iter(value): - if stype == dict: - return { - value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.items() - } - else: - return stype([value_to_obj_or_any(val) for val in value]) - obj = dbid_to_obj(value, ObjectDB) - return obj if obj is not None else value
- - -
[docs]def value_to_obj(value, force=True): - "Always convert value(s) to Object, or None" - stype = type(value) - if is_iter(value): - if stype == dict: - return {value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.iter()} - else: - return stype([value_to_obj_or_any(val) for val in value]) - return dbid_to_obj(value, ObjectDB)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/prototypes/spawner.html b/docs/0.9.5/_modules/evennia/prototypes/spawner.html deleted file mode 100644 index c9ef6e0655..0000000000 --- a/docs/0.9.5/_modules/evennia/prototypes/spawner.html +++ /dev/null @@ -1,1098 +0,0 @@ - - - - - - - - evennia.prototypes.spawner — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.prototypes.spawner

-"""
-Spawner
-
-The spawner takes input files containing object definitions in
-dictionary forms. These use a prototype architecture to define
-unique objects without having to make a Typeclass for each.
-
-There  main function is `spawn(*prototype)`, where the `prototype`
-is a dictionary like this:
-
-```python
-from evennia.prototypes import prototypes, spawner
-
-prot = {
- "prototype_key": "goblin",
- "typeclass": "types.objects.Monster",
- "key": "goblin grunt",
- "health": lambda: randint(20,30),
- "resists": ["cold", "poison"],
- "attacks": ["fists"],
- "weaknesses": ["fire", "light"]
- "tags": ["mob", "evil", ('greenskin','mob')]
- "attrs": [("weapon", "sword")]
-}
-# spawn something with the prototype
-goblin = spawner.spawn(prot)
-
-# make this into a db-saved prototype (optional)
-prot = prototypes.create_prototype(prot)
-
-```
-
-Possible keywords are:
-    prototype_key (str):  name of this prototype. This is used when storing prototypes and should
-        be unique. This should always be defined but for prototypes defined in modules, the
-        variable holding the prototype dict will become the prototype_key if it's not explicitly
-        given.
-    prototype_desc (str, optional): describes prototype in listings
-    prototype_locks (str, optional): locks for restricting access to this prototype. Locktypes
-        supported are 'edit' and 'use'.
-    prototype_tags(list, optional): List of tags or tuples (tag, category) used to group prototype
-        in listings
-    prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent prototype, or
-        a list of parents, for multiple left-to-right inheritance.
-    prototype: Deprecated. Same meaning as 'parent'.
-
-    typeclass (str or callable, optional): if not set, will use typeclass of parent prototype or use
-        `settings.BASE_OBJECT_TYPECLASS`
-    key (str or callable, optional): the name of the spawned object. If not given this will set to a
-        random hash
-    location (obj, str or callable, optional): location of the object - a valid object or #dbref
-    home (obj, str or callable, optional): valid object or #dbref
-    destination (obj, str or callable, optional): only valid for exits (object or #dbref)
-
-    permissions (str, list or callable, optional): which permissions for spawned object to have
-    locks (str or callable, optional): lock-string for the spawned object
-    aliases (str, list or callable, optional): Aliases for the spawned object
-    exec (str or callable, optional): this is a string of python code to execute or a list of such
-        codes.  This can be used e.g. to trigger custom handlers on the object. The execution
-        namespace contains 'evennia' for the library and 'obj'. All default spawn commands limit
-        this functionality to Developer/superusers. Usually it's better to use callables or
-        prototypefuncs instead of this.
-    tags (str, tuple, list or callable, optional): string or list of strings or tuples
-        `(tagstr, category)`. Plain strings will be result in tags with no category (default tags).
-    attrs (tuple, list or callable, optional): tuple or list of tuples of Attributes to add. This
-        form allows more complex Attributes to be set. Tuples at least specify `(key, value)`
-        but can also specify up to `(key, value, category, lockstring)`. If you want to specify a
-        lockstring but not a category, set the category to `None`.
-    ndb_<name> (any): value of a nattribute (ndb_ is stripped) - this is of limited use.
-    other (any): any other name is interpreted as the key of an Attribute with
-        its value. Such Attributes have no categories.
-
-Each value can also be a callable that takes no arguments. It should
-return the value to enter into the field and will be called every time
-the prototype is used to spawn an object. Note, if you want to store
-a callable in an Attribute, embed it in a tuple to the `args` keyword.
-
-By specifying the "prototype_parent" key, the prototype becomes a child of
-the given prototype, inheritng all prototype slots it does not explicitly
-define itself, while overloading those that it does specify.
-
-```python
-import random
-
-
-{
- "prototype_key": "goblin_wizard",
- "prototype_parent": "GOBLIN",
- "key": "goblin wizard",
- "spells": ["fire ball", "lighting bolt"]
- }
-
-GOBLIN_ARCHER = {
- "prototype_parent": "GOBLIN",
- "key": "goblin archer",
- "attack_skill": (random, (5, 10))"
- "attacks": ["short bow"]
-}
-```
-
-One can also have multiple prototypes. These are inherited from the
-left, with the ones further to the right taking precedence.
-
-```python
-ARCHWIZARD = {
- "attack": ["archwizard staff", "eye of doom"]
-
-GOBLIN_ARCHWIZARD = {
- "key" : "goblin archwizard"
- "prototype_parent": ("GOBLIN_WIZARD", "ARCHWIZARD"),
-}
-```
-
-The *goblin archwizard* will have some different attacks, but will
-otherwise have the same spells as a *goblin wizard* who in turn shares
-many traits with a normal *goblin*.
-
-
-Storage mechanism:
-
-This sets up a central storage for prototypes. The idea is to make these
-available in a repository for buildiers to use. Each prototype is stored
-in a Script so that it can be tagged for quick sorting/finding and locked for limiting
-access.
-
-This system also takes into consideration prototypes defined and stored in modules.
-Such prototypes are considered 'read-only' to the system and can only be modified
-in code. To replace a default prototype, add the same-name prototype in a
-custom module read later in the settings.PROTOTYPE_MODULES list. To remove a default
-prototype, override its name with an empty dict.
-
-
-"""
-
-
-import copy
-import hashlib
-import time
-
-from django.conf import settings
-
-import evennia
-from evennia.objects.models import ObjectDB
-from evennia.utils import logger
-from evennia.utils.utils import make_iter, is_iter
-from evennia.prototypes import prototypes as protlib
-from evennia.prototypes.prototypes import (
-    value_to_obj,
-    value_to_obj_or_any,
-    init_spawn_value,
-    PROTOTYPE_TAG_CATEGORY,
-)
-
-
-_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
-_PROTOTYPE_META_NAMES = ("prototype_key", "prototype_desc", "prototype_tags", "prototype_locks")
-_PROTOTYPE_ROOT_NAMES = (
-    "typeclass",
-    "key",
-    "aliases",
-    "attrs",
-    "tags",
-    "locks",
-    "permissions",
-    "location",
-    "home",
-    "destination",
-)
-_NON_CREATE_KWARGS = _CREATE_OBJECT_KWARGS + _PROTOTYPE_META_NAMES
-
-
-
[docs]class Unset: - """ - Helper class representing a non-set diff element. - - """ - - def __bool__(self): - return False - - def __str__(self): - return "<Unset>"
- - -# Helper - - -def _get_prototype(inprot, protparents, uninherited=None, _workprot=None): - """ - Recursively traverse a prototype dictionary, including multiple - inheritance. Use validate_prototype before this, we don't check - for infinite recursion here. - - Args: - inprot (dict): Prototype dict (the individual prototype, with no inheritance included). - protparents (dict): Available protparents, keyed by prototype_key. - uninherited (dict): Parts of prototype to not inherit. - _workprot (dict, optional): Work dict for the recursive algorithm. - - Returns: - merged (dict): A prototype where parent's have been merged as needed (the - `prototype_parent` key is removed). - - """ - - def _inherit_tags(old_tags, new_tags): - old = {(tup[0], tup[1]): tup for tup in old_tags} - new = {(tup[0], tup[1]): tup for tup in new_tags} - old.update(new) - return list(old.values()) - - def _inherit_attrs(old_attrs, new_attrs): - old = {(tup[0], tup[2]): tup for tup in old_attrs} - new = {(tup[0], tup[2]): tup for tup in new_attrs} - old.update(new) - return list(old.values()) - - _workprot = {} if _workprot is None else _workprot - if "prototype_parent" in inprot: - # move backwards through the inheritance - for prototype in make_iter(inprot["prototype_parent"]): - # Build the prot dictionary in reverse order, overloading - new_prot = _get_prototype( - protparents.get(prototype.lower(), {}), protparents, _workprot=_workprot - ) - - # attrs, tags have internal structure that should be inherited separately - new_prot["attrs"] = _inherit_attrs( - _workprot.get("attrs", {}), new_prot.get("attrs", {}) - ) - new_prot["tags"] = _inherit_tags(_workprot.get("tags", {}), new_prot.get("tags", {})) - - _workprot.update(new_prot) - # the inprot represents a higher level (a child prot), which should override parents - - inprot["attrs"] = _inherit_attrs(_workprot.get("attrs", {}), inprot.get("attrs", {})) - inprot["tags"] = _inherit_tags(_workprot.get("tags", {}), inprot.get("tags", {})) - _workprot.update(inprot) - if uninherited: - # put back the parts that should not be inherited - _workprot.update(uninherited) - _workprot.pop("prototype_parent", None) # we don't need this for spawning - return _workprot - - -
[docs]def flatten_prototype(prototype, validate=False): - """ - Produce a 'flattened' prototype, where all prototype parents in the inheritance tree have been - merged into a final prototype. - - Args: - prototype (dict): Prototype to flatten. Its `prototype_parent` field will be parsed. - validate (bool, optional): Validate for valid keys etc. - - Returns: - flattened (dict): The final, flattened prototype. - - """ - - if prototype: - prototype = protlib.homogenize_prototype(prototype) - protparents = {prot["prototype_key"].lower(): prot for prot in protlib.search_prototype()} - protlib.validate_prototype( - prototype, None, protparents, is_prototype_base=validate, strict=validate - ) - return _get_prototype( - prototype, protparents, uninherited={"prototype_key": prototype.get("prototype_key")} - ) - return {}
- - -# obj-related prototype functions - - -
[docs]def prototype_from_object(obj): - """ - Guess a minimal prototype from an existing object. - - Args: - obj (Object): An object to analyze. - - Returns: - prototype (dict): A prototype estimating the current state of the object. - - """ - # first, check if this object already has a prototype - - prot = obj.tags.get(category=PROTOTYPE_TAG_CATEGORY, return_list=True) - if prot: - prot = protlib.search_prototype(prot[0]) - - if not prot or len(prot) > 1: - # no unambiguous prototype found - build new prototype - prot = {} - prot["prototype_key"] = "From-Object-{}-{}".format( - obj.key, hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:7] - ) - prot["prototype_desc"] = "Built from {}".format(str(obj)) - prot["prototype_locks"] = "spawn:all();edit:all()" - prot["prototype_tags"] = [] - else: - prot = prot[0] - - prot["key"] = obj.db_key or hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:6] - prot["typeclass"] = obj.db_typeclass_path - - location = obj.db_location - if location: - prot["location"] = location.dbref - home = obj.db_home - if home: - prot["home"] = home.dbref - destination = obj.db_destination - if destination: - prot["destination"] = destination.dbref - locks = obj.locks.all() - if locks: - prot["locks"] = ";".join(locks) - perms = obj.permissions.get(return_list=True) - if perms: - prot["permissions"] = make_iter(perms) - aliases = obj.aliases.get(return_list=True) - if aliases: - prot["aliases"] = aliases - tags = sorted( - [(tag.db_key, tag.db_category, tag.db_data) for tag in obj.tags.all(return_objs=True)] - ) - if tags: - prot["tags"] = tags - attrs = sorted( - [ - (attr.key, attr.value, attr.category, ";".join(attr.locks.all())) - for attr in obj.attributes.all() - ] - ) - if attrs: - prot["attrs"] = attrs - - return prot
- - -
[docs]def prototype_diff(prototype1, prototype2, maxdepth=2, homogenize=False, implicit_keep=False): - """ - A 'detailed' diff specifies differences down to individual sub-sections - of the prototype, like individual attributes, permissions etc. It is used - by the menu to allow a user to customize what should be kept. - - Args: - prototype1 (dict): Original prototype. - prototype2 (dict): Comparison prototype. - maxdepth (int, optional): The maximum depth into the diff we go before treating the elements - of iterables as individual entities to compare. This is important since a single - attr/tag (for example) are represented by a tuple. - homogenize (bool, optional): Auto-homogenize both prototypes for the best comparison. - This is most useful for displaying. - implicit_keep (bool, optional): If set, the resulting diff will assume KEEP unless the new - prototype explicitly change them. That is, if a key exists in `prototype1` and - not in `prototype2`, it will not be REMOVEd but set to KEEP instead. This is particularly - useful for auto-generated prototypes when updating objects. - - Returns: - diff (dict): A structure detailing how to convert prototype1 to prototype2. All - nested structures are dicts with keys matching either the prototype's matching - key or the first element in the tuple describing the prototype value (so for - a tag tuple `(tagname, category)` the second-level key in the diff would be tagname). - The the bottom level of the diff consist of tuples `(old, new, instruction)`, where - instruction can be one of "REMOVE", "ADD", "UPDATE" or "KEEP". - - """ - _unset = Unset() - - def _recursive_diff(old, new, depth=0): - - old_type = type(old) - new_type = type(new) - - if old_type == new_type and not (old or new): - # both old and new are unset, like [] or None - return (None, None, "KEEP") - if old_type != new_type: - if old and not new: - if depth < maxdepth and old_type == dict: - return {key: (part, None, "REMOVE") for key, part in old.items()} - elif depth < maxdepth and is_iter(old): - return { - part[0] if is_iter(part) else part: (part, None, "REMOVE") for part in old - } - if isinstance(new, Unset) and implicit_keep: - # the new does not define any change, use implicit-keep - return (old, None, "KEEP") - return (old, new, "REMOVE") - elif not old and new: - if depth < maxdepth and new_type == dict: - return {key: (None, part, "ADD") for key, part in new.items()} - elif depth < maxdepth and is_iter(new): - return {part[0] if is_iter(part) else part: (None, part, "ADD") for part in new} - return (old, new, "ADD") - else: - # this condition should not occur in a standard diff - return (old, new, "UPDATE") - elif depth < maxdepth and new_type == dict: - all_keys = set(list(old.keys()) + list(new.keys())) - return { - key: _recursive_diff(old.get(key, _unset), new.get(key, _unset), depth=depth + 1) - for key in all_keys - } - elif depth < maxdepth and is_iter(new): - old_map = {part[0] if is_iter(part) else part: part for part in old} - new_map = {part[0] if is_iter(part) else part: part for part in new} - all_keys = set(list(old_map.keys()) + list(new_map.keys())) - return { - key: _recursive_diff( - old_map.get(key, _unset), new_map.get(key, _unset), depth=depth + 1 - ) - for key in all_keys - } - elif old != new: - return (old, new, "UPDATE") - else: - return (old, new, "KEEP") - - prot1 = protlib.homogenize_prototype(prototype1) if homogenize else prototype1 - prot2 = protlib.homogenize_prototype(prototype2) if homogenize else prototype2 - - diff = _recursive_diff(prot1, prot2) - - return diff
- - -
[docs]def flatten_diff(diff): - """ - For spawning, a 'detailed' diff is not necessary, rather we just want instructions on how to - handle each root key. - - Args: - diff (dict): Diff produced by `prototype_diff` and - possibly modified by the user. Note that also a pre-flattened diff will come out - unchanged by this function. - - Returns: - flattened_diff (dict): A flat structure detailing how to operate on each - root component of the prototype. - - Notes: - The flattened diff has the following possible instructions: - UPDATE, REPLACE, REMOVE - Many of the detailed diff's values can hold nested structures with their own - individual instructions. A detailed diff can have the following instructions: - REMOVE, ADD, UPDATE, KEEP - Here's how they are translated: - - All REMOVE -> REMOVE - - All ADD|UPDATE -> UPDATE - - All KEEP -> KEEP - - Mix KEEP, UPDATE, ADD -> UPDATE - - Mix REMOVE, KEEP, UPDATE, ADD -> REPLACE - """ - - valid_instructions = ("KEEP", "REMOVE", "ADD", "UPDATE") - - def _get_all_nested_diff_instructions(diffpart): - "Started for each root key, returns all instructions nested under it" - out = [] - typ = type(diffpart) - if typ == tuple and len(diffpart) == 3 and diffpart[2] in valid_instructions: - out = [diffpart[2]] - elif typ == dict: - # all other are dicts - for val in diffpart.values(): - out.extend(_get_all_nested_diff_instructions(val)) - else: - raise RuntimeError( - "Diff contains non-dicts that are not on the " - "form (old, new, inst): {}".format(diffpart) - ) - return out - - flat_diff = {} - - # flatten diff based on rules - for rootkey, diffpart in diff.items(): - insts = _get_all_nested_diff_instructions(diffpart) - if all(inst == "KEEP" for inst in insts): - rootinst = "KEEP" - elif all(inst in ("ADD", "UPDATE") for inst in insts): - rootinst = "UPDATE" - elif all(inst == "REMOVE" for inst in insts): - rootinst = "REMOVE" - elif "REMOVE" in insts: - rootinst = "REPLACE" - else: - rootinst = "UPDATE" - - flat_diff[rootkey] = rootinst - - return flat_diff
- - -
[docs]def prototype_diff_from_object(prototype, obj, implicit_keep=True): - """ - Get a simple diff for a prototype compared to an object which may or may not already have a - prototype (or has one but changed locally). For more complex migratations a manual diff may be - needed. - - Args: - prototype (dict): New prototype. - obj (Object): Object to compare prototype against. - - Returns: - diff (dict): Mapping for every prototype key: {"keyname": "REMOVE|UPDATE|KEEP", ...} - obj_prototype (dict): The prototype calculated for the given object. The diff is how to - convert this prototype into the new prototype. - implicit_keep (bool, optional): This is usually what one wants for object updating. When - set, this means the prototype diff will assume KEEP on differences - between the object-generated prototype and that which is not explicitly set in the - new prototype. This means e.g. that even though the object has a location, and the - prototype does not specify the location, it will not be unset. - - Notes: - The `diff` is on the following form: - - {"key": (old, new, "KEEP|REPLACE|UPDATE|REMOVE"), - "attrs": {"attrkey": (old, new, "KEEP|REPLACE|UPDATE|REMOVE"), - "attrkey": (old, new, "KEEP|REPLACE|UPDATE|REMOVE"), ...}, - "aliases": {"aliasname": (old, new, "KEEP...", ...}, - ... } - - """ - obj_prototype = prototype_from_object(obj) - diff = prototype_diff( - obj_prototype, protlib.homogenize_prototype(prototype), implicit_keep=implicit_keep - ) - return diff, obj_prototype
- - -
[docs]def format_diff(diff, minimal=True): - """ - Reformat a diff for presentation. This is a shortened version - of the olc _format_diff_text_and_options without the options. - - Args: - diff (dict): A diff as produced by `prototype_diff`. - minimal (bool, optional): Only show changes (remove KEEPs) - - Returns: - texts (str): The formatted text. - - """ - - valid_instructions = ("KEEP", "REMOVE", "ADD", "UPDATE") - - def _visualize(obj, rootname, get_name=False): - if is_iter(obj): - if not obj: - return str(obj) - if get_name: - return obj[0] if obj[0] else "<unset>" - if rootname == "attrs": - return "{} |w=|n {} |w(category:|n |n{}|w, locks:|n {}|w)|n".format(*obj) - elif rootname == "tags": - return "{} |w(category:|n {}|w)|n".format(obj[0], obj[1]) - return "{}".format(obj) - - def _parse_diffpart(diffpart, rootname): - typ = type(diffpart) - texts = [] - if typ == tuple and len(diffpart) == 3 and diffpart[2] in valid_instructions: - old, new, instruction = diffpart - if instruction == "KEEP": - if not minimal: - texts.append(" |gKEEP|n: {old}".format(old=_visualize(old, rootname))) - elif instruction == "ADD": - texts.append(" |yADD|n: {new}".format(new=_visualize(new, rootname))) - elif instruction == "REMOVE" and not new: - texts.append(" |rREMOVE|n: {old}".format(old=_visualize(old, rootname))) - else: - vold = _visualize(old, rootname) - vnew = _visualize(new, rootname) - vsep = "" if len(vold) < 78 else "\n" - vinst = " |rREMOVE|n" if instruction == "REMOVE" else "|y{}|n".format(instruction) - varrow = "|r->|n" if instruction == "REMOVE" else "|y->|n" - texts.append( - " {inst}|W:|n {old} |W{varrow}|n{sep} {new}".format( - inst=vinst, old=vold, varrow=varrow, sep=vsep, new=vnew - ) - ) - else: - for key in sorted(list(diffpart.keys())): - subdiffpart = diffpart[key] - text = _parse_diffpart(subdiffpart, rootname) - texts.extend(text) - return texts - - texts = [] - - for root_key in sorted(diff): - diffpart = diff[root_key] - text = _parse_diffpart(diffpart, root_key) - if text or not minimal: - heading = "- |w{}:|n\n".format(root_key) - if text: - text = [heading + text[0]] + text[1:] - else: - text = [heading] - - texts.extend(text) - - return "\n ".join(line for line in texts if line)
- - -
[docs]def batch_update_objects_with_prototype(prototype, diff=None, objects=None, exact=False): - """ - Update existing objects with the latest version of the prototype. - - Args: - prototype (str or dict): Either the `prototype_key` to use or the - prototype dict itself. - diff (dict, optional): This a diff structure that describes how to update the protototype. - If not given this will be constructed from the first object found. - objects (list, optional): List of objects to update. If not given, query for these - objects using the prototype's `prototype_key`. - exact (bool, optional): By default (`False`), keys not explicitly in the prototype will - not be applied to the object, but will be retained as-is. This is usually what is - expected - for example, one usually do not want to remove the object's location even - if it's not set in the prototype. With `exact=True`, all un-specified properties of the - objects will be removed if they exist. This will lead to a more accurate 1:1 correlation - between the object and the prototype but is usually impractical. - Returns: - changed (int): The number of objects that had changes applied to them. - - """ - prototype = protlib.homogenize_prototype(prototype) - - if isinstance(prototype, str): - new_prototype = protlib.search_prototype(prototype) - else: - new_prototype = prototype - - prototype_key = new_prototype["prototype_key"] - - if not objects: - objects = ObjectDB.objects.get_by_tag(prototype_key, category=PROTOTYPE_TAG_CATEGORY) - - if not objects: - return 0 - - if not diff: - diff, _ = prototype_diff_from_object(new_prototype, objects[0]) - - # make sure the diff is flattened - diff = flatten_diff(diff) - - changed = 0 - for obj in objects: - do_save = False - - old_prot_key = obj.tags.get(category=PROTOTYPE_TAG_CATEGORY, return_list=True) - old_prot_key = old_prot_key[0] if old_prot_key else None - - try: - for key, directive in diff.items(): - - if key not in new_prototype and not exact: - # we don't update the object if the prototype does not actually - # contain the key (the diff will report REMOVE but we ignore it - # since exact=False) - continue - - if directive in ("UPDATE", "REPLACE"): - - if key in _PROTOTYPE_META_NAMES: - # prototype meta keys are not stored on-object - continue - - val = new_prototype[key] - do_save = True - - if key == "key": - obj.db_key = init_spawn_value(val, str) - elif key == "typeclass": - obj.db_typeclass_path = init_spawn_value(val, str) - elif key == "location": - obj.db_location = init_spawn_value(val, value_to_obj) - elif key == "home": - obj.db_home = init_spawn_value(val, value_to_obj) - elif key == "destination": - obj.db_destination = init_spawn_value(val, value_to_obj) - elif key == "locks": - if directive == "REPLACE": - obj.locks.clear() - obj.locks.add(init_spawn_value(val, str)) - elif key == "permissions": - if directive == "REPLACE": - obj.permissions.clear() - obj.permissions.batch_add(*(init_spawn_value(perm, str) for perm in val)) - elif key == "aliases": - if directive == "REPLACE": - obj.aliases.clear() - obj.aliases.batch_add(*(init_spawn_value(alias, str) for alias in val)) - elif key == "tags": - if directive == "REPLACE": - obj.tags.clear() - obj.tags.batch_add( - *( - (init_spawn_value(ttag, str), tcategory, tdata) - for ttag, tcategory, tdata in val - ) - ) - elif key == "attrs": - if directive == "REPLACE": - obj.attributes.clear() - obj.attributes.batch_add( - *( - ( - init_spawn_value(akey, str), - init_spawn_value(aval, value_to_obj), - acategory, - alocks, - ) - for akey, aval, acategory, alocks in val - ) - ) - elif key == "exec": - # we don't auto-rerun exec statements, it would be huge security risk! - pass - else: - obj.attributes.add(key, init_spawn_value(val, value_to_obj)) - elif directive == "REMOVE": - do_save = True - if key == "key": - obj.db_key = "" - elif key == "typeclass": - # fall back to default - obj.db_typeclass_path = settings.BASE_OBJECT_TYPECLASS - elif key == "location": - obj.db_location = None - elif key == "home": - obj.db_home = None - elif key == "destination": - obj.db_destination = None - elif key == "locks": - obj.locks.clear() - elif key == "permissions": - obj.permissions.clear() - elif key == "aliases": - obj.aliases.clear() - elif key == "tags": - obj.tags.clear() - elif key == "attrs": - obj.attributes.clear() - elif key == "exec": - # we don't auto-rerun exec statements, it would be huge security risk! - pass - else: - obj.attributes.remove(key) - except Exception: - logger.log_trace(f"Failed to apply prototype '{prototype_key}' to {obj}.") - finally: - # we must always make sure to re-add the prototype tag - obj.tags.clear(category=PROTOTYPE_TAG_CATEGORY) - obj.tags.add(prototype_key, category=PROTOTYPE_TAG_CATEGORY) - - if do_save: - changed += 1 - obj.save() - - return changed
- - -
[docs]def batch_create_object(*objparams): - """ - This is a cut-down version of the create_object() function, - optimized for speed. It does NOT check and convert various input - so make sure the spawned Typeclass works before using this! - - Args: - objsparams (tuple): Each paremter tuple will create one object instance using the parameters - within. - The parameters should be given in the following order: - - `create_kwargs` (dict): For use as new_obj = `ObjectDB(**create_kwargs)`. - - `permissions` (str): Permission string used with `new_obj.batch_add(permission)`. - - `lockstring` (str): Lockstring used with `new_obj.locks.add(lockstring)`. - - `aliases` (list): A list of alias strings for - adding with `new_object.aliases.batch_add(*aliases)`. - - `nattributes` (list): list of tuples `(key, value)` to be loop-added to - add with `new_obj.nattributes.add(*tuple)`. - - `attributes` (list): list of tuples `(key, value[,category[,lockstring]])` for - adding with `new_obj.attributes.batch_add(*attributes)`. - - `tags` (list): list of tuples `(key, category)` for adding - with `new_obj.tags.batch_add(*tags)`. - - `execs` (list): Code strings to execute together with the creation - of each object. They will be executed with `evennia` and `obj` - (the newly created object) available in the namespace. Execution - will happend after all other properties have been assigned and - is intended for calling custom handlers etc. - - Returns: - objects (list): A list of created objects - - Notes: - The `exec` list will execute arbitrary python code so don't allow this to be available to - unprivileged users! - - """ - - # bulk create all objects in one go - - # unfortunately this doesn't work since bulk_create doesn't creates pks; - # the result would be duplicate objects at the next stage, so we comment - # it out for now: - # dbobjs = _ObjectDB.objects.bulk_create(dbobjs) - - objs = [] - for objparam in objparams: - - obj = ObjectDB(**objparam[0]) - - # setup - obj._createdict = { - "permissions": make_iter(objparam[1]), - "locks": objparam[2], - "aliases": make_iter(objparam[3]), - "nattributes": objparam[4], - "attributes": objparam[5], - "tags": make_iter(objparam[6]), - } - # this triggers all hooks - obj.save() - # run eventual extra code - for code in objparam[7]: - if code: - exec(code, {}, {"evennia": evennia, "obj": obj}) - objs.append(obj) - return objs
- - -# Spawner mechanism - - -
[docs]def spawn(*prototypes, **kwargs): - """ - Spawn a number of prototyped objects. - - Args: - prototypes (str or dict): Each argument should either be a - prototype_key (will be used to find the prototype) or a full prototype - dictionary. These will be batched-spawned as one object each. - Keyword Args: - prototype_modules (str or list): A python-path to a prototype - module, or a list of such paths. These will be used to build - the global protparents dictionary accessible by the input - prototypes. If not given, it will instead look for modules - defined by settings.PROTOTYPE_MODULES. - prototype_parents (dict): A dictionary holding a custom - prototype-parent dictionary. Will overload same-named - prototypes from prototype_modules. - return_parents (bool): Return a dict of the entire prototype-parent tree - available to this prototype (no object creation happens). This is a - merged result between the globally found protparents and whatever - custom `prototype_parents` are given to this function. - only_validate (bool): Only run validation of prototype/parents - (no object creation) and return the create-kwargs. - - Returns: - object (Object, dict or list): Spawned object(s). If `only_validate` is given, return - a list of the creation kwargs to build the object(s) without actually creating it. If - `return_parents` is set, instead return dict of prototype parents. - - """ - # search string (=prototype_key) from input - prototypes = [ - protlib.search_prototype(prot, require_single=True)[0] if isinstance(prot, str) else prot - for prot in prototypes - ] - - # get available protparents - protparents = {prot["prototype_key"].lower(): prot for prot in protlib.search_prototype()} - - if not kwargs.get("only_validate"): - # homogenization to be more lenient about prototype format when entering the prototype manually - prototypes = [protlib.homogenize_prototype(prot) for prot in prototypes] - - # overload module's protparents with specifically given protparents - # we allow prototype_key to be the key of the protparent dict, to allow for module-level - # prototype imports. We need to insert prototype_key in this case - for key, protparent in kwargs.get("prototype_parents", {}).items(): - key = str(key).lower() - protparent["prototype_key"] = str(protparent.get("prototype_key", key)).lower() - protparents[key] = protparent - - if "return_parents" in kwargs: - # only return the parents - return copy.deepcopy(protparents) - - objsparams = [] - for prototype in prototypes: - - protlib.validate_prototype(prototype, None, protparents, is_prototype_base=True) - prot = _get_prototype( - prototype, protparents, uninherited={"prototype_key": prototype.get("prototype_key")} - ) - if not prot: - continue - - # extract the keyword args we need to create the object itself. If we get a callable, - # call that to get the value (don't catch errors) - create_kwargs = {} - # we must always add a key, so if not given we use a shortened md5 hash. There is a (small) - # chance this is not unique but it should usually not be a problem. - val = prot.pop( - "key", - "Spawned-{}".format(hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:6]), - ) - create_kwargs["db_key"] = init_spawn_value(val, str) - - val = prot.pop("location", None) - create_kwargs["db_location"] = init_spawn_value(val, value_to_obj) - - val = prot.pop("home", settings.DEFAULT_HOME) - create_kwargs["db_home"] = init_spawn_value(val, value_to_obj) - - val = prot.pop("destination", None) - create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj) - - val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS) - create_kwargs["db_typeclass_path"] = init_spawn_value(val, str) - - # extract calls to handlers - val = prot.pop("permissions", []) - permission_string = init_spawn_value(val, make_iter) - val = prot.pop("locks", "") - lock_string = init_spawn_value(val, str) - val = prot.pop("aliases", []) - alias_string = init_spawn_value(val, make_iter) - - val = prot.pop("tags", []) - tags = [] - for (tag, category, data) in val: - tags.append((init_spawn_value(tag, str), category, data)) - - prototype_key = prototype.get("prototype_key", None) - if prototype_key: - # we make sure to add a tag identifying which prototype created this object - tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY)) - - val = prot.pop("exec", "") - execs = init_spawn_value(val, make_iter) - - # extract ndb assignments - nattributes = dict( - (key.split("_", 1)[1], init_spawn_value(val, value_to_obj)) - for key, val in prot.items() - if key.startswith("ndb_") - ) - - # the rest are attribute tuples (attrname, value, category, locks) - val = make_iter(prot.pop("attrs", [])) - attributes = [] - for (attrname, value, category, locks) in val: - attributes.append((attrname, init_spawn_value(value), category, locks)) - - simple_attributes = [] - for key, value in ( - (key, value) for key, value in prot.items() if not (key.startswith("ndb_")) - ): - # we don't support categories, nor locks for simple attributes - if key in _PROTOTYPE_META_NAMES: - continue - else: - simple_attributes.append( - (key, init_spawn_value(value, value_to_obj_or_any), None, None) - ) - - attributes = attributes + simple_attributes - attributes = [tup for tup in attributes if not tup[0] in _NON_CREATE_KWARGS] - - # pack for call into _batch_create_object - objsparams.append( - ( - create_kwargs, - permission_string, - lock_string, - alias_string, - nattributes, - attributes, - tags, - execs, - ) - ) - - if kwargs.get("only_validate"): - return objsparams - return batch_create_object(*objsparams)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/scripts/admin.html b/docs/0.9.5/_modules/evennia/scripts/admin.html deleted file mode 100644 index 7b7476b349..0000000000 --- a/docs/0.9.5/_modules/evennia/scripts/admin.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - evennia.scripts.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.scripts.admin

-#
-# This sets up how models are displayed
-# in the web admin interface.
-#
-from django.conf import settings
-
-from evennia.typeclasses.admin import AttributeInline, TagInline
-
-from evennia.scripts.models import ScriptDB
-from django.contrib import admin
-
-
-
[docs]class ScriptTagInline(TagInline): - """ - Inline script tags. - - """ - - model = ScriptDB.db_tags.through - related_field = "scriptdb"
- - -
[docs]class ScriptAttributeInline(AttributeInline): - """ - Inline attribute tags. - - """ - - model = ScriptDB.db_attributes.through - related_field = "scriptdb"
- - -
[docs]class ScriptDBAdmin(admin.ModelAdmin): - """ - Displaying the main Script page. - - """ - - list_display = ( - "id", - "db_key", - "db_typeclass_path", - "db_obj", - "db_interval", - "db_repeats", - "db_persistent", - ) - list_display_links = ("id", "db_key") - ordering = ["db_obj", "db_typeclass_path"] - search_fields = ["^db_key", "db_typeclass_path"] - save_as = True - save_on_top = True - list_select_related = True - raw_id_fields = ("db_obj",) - - fieldsets = ( - ( - None, - { - "fields": ( - ("db_key", "db_typeclass_path"), - "db_interval", - "db_repeats", - "db_start_delay", - "db_persistent", - "db_obj", - ) - }, - ), - ) - inlines = [ScriptTagInline, ScriptAttributeInline] - -
[docs] def save_model(self, request, obj, form, change): - """ - Model-save hook. - - Args: - request (Request): Incoming request. - obj (Object): Database object. - form (Form): Form instance. - change (bool): If this is a change or a new object. - - """ - obj.save() - if not change: - # adding a new object - # have to call init with typeclass passed to it - obj.set_class_from_typeclass(typeclass_path=obj.db_typeclass_path)
- - -admin.site.register(ScriptDB, ScriptDBAdmin) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/scripts/manager.html b/docs/0.9.5/_modules/evennia/scripts/manager.html deleted file mode 100644 index 9d1a1f3d8b..0000000000 --- a/docs/0.9.5/_modules/evennia/scripts/manager.html +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - - - evennia.scripts.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.scripts.manager

-"""
-The custom manager for Scripts.
-"""
-
-from django.db.models import Q
-from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
-from evennia.utils.utils import make_iter
-
-__all__ = ("ScriptManager", "ScriptDBManager")
-_GA = object.__getattribute__
-
-VALIDATE_ITERATION = 0
-
-
-
[docs]class ScriptDBManager(TypedObjectManager): - """ - This Scriptmanager implements methods for searching - and manipulating Scripts directly from the database. - - Evennia-specific search methods (will return Typeclasses or - lists of Typeclasses, whereas Django-general methods will return - Querysets or database objects). - - dbref (converter) - get_id (or dbref_search) - get_dbref_range - object_totals - typeclass_search - get_all_scripts_on_obj - get_all_scripts - delete_script - remove_non_persistent - validate - script_search (equivalent to evennia.search_script) - copy_script - - """ - -
[docs] def get_all_scripts_on_obj(self, obj, key=None): - """ - Find all Scripts related to a particular object. - - Args: - obj (Object): Object whose Scripts we are looking for. - key (str, optional): Script identifier - can be given as a - dbref or name string. If given, only scripts matching the - key on the object will be returned. - Returns: - matches (list): Matching scripts. - - """ - if not obj: - return [] - account = _GA(_GA(obj, "__dbclass__"), "__name__") == "AccountDB" - if key: - dbref = self.dbref(key) - if dbref or dbref == 0: - if account: - return self.filter(db_account=obj, id=dbref) - else: - return self.filter(db_obj=obj, id=dbref) - elif account: - return self.filter(db_account=obj, db_key=key) - else: - return self.filter(db_obj=obj, db_key=key) - elif account: - return self.filter(db_account=obj) - else: - return self.filter(db_obj=obj)
- -
[docs] def get_all_scripts(self, key=None): - """ - Get all scripts in the database. - - Args: - key (str or int, optional): Restrict result to only those - with matching key or dbref. - - Returns: - scripts (list): All scripts found, or those matching `key`. - - """ - if key: - script = [] - dbref = self.dbref(key) - if dbref: - return self.filter(id=dbref) - return self.filter(db_key__iexact=key.strip()) - return self.all()
- -
[docs] def delete_script(self, dbref): - """ - This stops and deletes a specific script directly from the - script database. - - Args: - dbref (int): Database unique id. - - Notes: - This might be needed for global scripts not tied to a - specific game object - - """ - scripts = self.get_id(dbref) - for script in make_iter(scripts): - script.stop()
- -
[docs] def remove_non_persistent(self, obj=None): - """ - This cleans up the script database of all non-persistent - scripts. It is called every time the server restarts. - - Args: - obj (Object, optional): Only remove non-persistent scripts - assigned to this object. - - """ - if obj: - to_stop = self.filter(db_obj=obj, db_persistent=False, db_is_active=True) - to_delete = self.filter(db_obj=obj, db_persistent=False, db_is_active=False) - else: - to_stop = self.filter(db_persistent=False, db_is_active=True) - to_delete = self.filter(db_persistent=False, db_is_active=False) - nr_deleted = to_stop.count() + to_delete.count() - for script in to_stop: - script.stop() - for script in to_delete: - script.delete() - return nr_deleted
- -
[docs] def validate(self, scripts=None, obj=None, key=None, dbref=None, init_mode=None): - """ - This will step through the script database and make sure - all objects run scripts that are still valid in the context - they are in. This is called by the game engine at regular - intervals but can also be initiated by player scripts. - - Only one of the arguments are supposed to be supplied - at a time, since they are exclusive to each other. - - Args: - scripts (list, optional): A list of script objects to - validate. - obj (Object, optional): Validate only scripts defined on - this object. - key (str): Validate only scripts with this key. - dbref (int): Validate only the single script with this - particular id. - init_mode (str, optional): This is used during server - upstart and can have three values: - - `None` (no init mode). Called during run. - - `"reset"` - server reboot. Kill non-persistent scripts - - `"reload"` - server reload. Keep non-persistent scripts. - Returns: - nr_started, nr_stopped (tuple): Statistics on how many objects - where started and stopped. - - Notes: - This method also makes sure start any scripts it validates - which should be harmless, since already-active scripts have - the property 'is_running' set and will be skipped. - - """ - - # we store a variable that tracks if we are calling a - # validation from within another validation (avoids - # loops). - - global VALIDATE_ITERATION - if VALIDATE_ITERATION > 0: - # we are in a nested validation. Exit. - VALIDATE_ITERATION -= 1 - return None, None - VALIDATE_ITERATION += 1 - - # not in a validation - loop. Validate as normal. - - nr_started = 0 - nr_stopped = 0 - - if init_mode: - if init_mode == "reset": - # special mode when server starts or object logs in. - # This deletes all non-persistent scripts from database - nr_stopped += self.remove_non_persistent(obj=obj) - # turn off the activity flag for all remaining scripts - scripts = self.get_all_scripts() - for script in scripts: - script.is_active = False - - elif not scripts: - # normal operation - if dbref and self.dbref(dbref, reqhash=False): - scripts = self.get_id(dbref) - elif obj: - scripts = self.get_all_scripts_on_obj(obj, key=key) - else: - scripts = self.get_all_scripts(key=key) - - if not scripts: - # no scripts available to validate - VALIDATE_ITERATION -= 1 - return None, None - - for script in scripts: - if script.is_valid(): - nr_started += script.start(force_restart=init_mode) - else: - script.stop() - nr_stopped += 1 - VALIDATE_ITERATION -= 1 - return nr_started, nr_stopped
- -
[docs] def search_script(self, ostring, obj=None, only_timed=False, typeclass=None): - """ - Search for a particular script. - - Args: - ostring (str): Search criterion - a script dbef or key. - obj (Object, optional): Limit search to scripts defined on - this object - only_timed (bool): Limit search only to scripts that run - on a timer. - typeclass (class or str): Typeclass or path to typeclass. - - """ - - ostring = ostring.strip() - - dbref = self.dbref(ostring) - if dbref: - # this is a dbref, try to find the script directly - dbref_match = self.dbref_search(dbref) - if dbref_match and not ( - (obj and obj != dbref_match.obj) or (only_timed and dbref_match.interval) - ): - return [dbref_match] - - if typeclass: - if callable(typeclass): - typeclass = "%s.%s" % (typeclass.__module__, typeclass.__name__) - else: - typeclass = "%s" % typeclass - - # not a dbref; normal search - obj_restriction = obj and Q(db_obj=obj) or Q() - timed_restriction = only_timed and Q(db_interval__gt=0) or Q() - typeclass_restriction = typeclass and Q(db_typeclass_path=typeclass) or Q() - scripts = self.filter( - timed_restriction & obj_restriction & typeclass_restriction & Q(db_key__iexact=ostring) - ) - return scripts
- - # back-compatibility alias - script_search = search_script - -
[docs] def copy_script(self, original_script, new_key=None, new_obj=None, new_locks=None): - """ - Make an identical copy of the original_script. - - Args: - original_script (Script): The Script to copy. - new_key (str, optional): Rename the copy. - new_obj (Object, optional): Place copy on different Object. - new_locks (str, optional): Give copy different locks from - the original. - - Returns: - script_copy (Script): A new Script instance, copied from - the original. - """ - typeclass = original_script.typeclass_path - new_key = new_key if new_key is not None else original_script.key - new_obj = new_obj if new_obj is not None else original_script.obj - new_locks = new_locks if new_locks is not None else original_script.db_lock_storage - - from evennia.utils import create - - new_script = create.create_script( - typeclass, key=new_key, obj=new_obj, locks=new_locks, autostart=True - ) - return new_script
- - -
[docs]class ScriptManager(ScriptDBManager, TypeclassManager): - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/scripts/models.html b/docs/0.9.5/_modules/evennia/scripts/models.html deleted file mode 100644 index 75f8b2367f..0000000000 --- a/docs/0.9.5/_modules/evennia/scripts/models.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - evennia.scripts.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.scripts.models

-"""
-Scripts are entities that perform some sort of action, either only
-once or repeatedly. They can be directly linked to a particular
-Evennia Object or be stand-alonw (in the latter case it is considered
-a 'global' script). Scripts can indicate both actions related to the
-game world as well as pure behind-the-scenes events and effects.
-Everything that has a time component in the game (i.e. is not
-hard-coded at startup or directly created/controlled by players) is
-handled by Scripts.
-
-Scripts have to check for themselves that they should be applied at a
-particular moment of time; this is handled by the is_valid() hook.
-Scripts can also implement at_start and at_end hooks for preparing and
-cleaning whatever effect they have had on the game object.
-
-Common examples of uses of Scripts:
-
-- Load the default cmdset to the account object's cmdhandler
-  when logging in.
-- Switch to a different state, such as entering a text editor,
-  start combat or enter a dark room.
-- Merge a new cmdset with the default one for changing which
-  commands are available at a particular time
-- Give the account/object a time-limited bonus/effect
-
-"""
-from django.conf import settings
-from django.db import models
-from django.core.exceptions import ObjectDoesNotExist
-from evennia.typeclasses.models import TypedObject
-from evennia.scripts.manager import ScriptDBManager
-from evennia.utils.utils import dbref, to_str
-
-__all__ = ("ScriptDB",)
-_GA = object.__getattribute__
-_SA = object.__setattr__
-
-
-# ------------------------------------------------------------
-#
-# ScriptDB
-#
-# ------------------------------------------------------------
-
-
-
[docs]class ScriptDB(TypedObject): - """ - The Script database representation. - - The TypedObject supplies the following (inherited) properties: - key - main name - name - alias for key - typeclass_path - the path to the decorating typeclass - typeclass - auto-linked typeclass - date_created - time stamp of object creation - permissions - perm strings - dbref - #id of object - db - persistent attribute storage - ndb - non-persistent attribute storage - - The ScriptDB adds the following properties: - desc - optional description of script - obj - the object the script is linked to, if any - account - the account the script is linked to (exclusive with obj) - interval - how often script should run - start_delay - if the script should start repeating right away - repeats - how many times the script should repeat - persistent - if script should survive a server reboot - is_active - bool if script is currently running - - """ - - # - # ScriptDB Database Model setup - # - # These database fields are all set using their corresponding properties, - # named same as the field, but withtou the db_* prefix. - - # inherited fields (from TypedObject): - # db_key, db_typeclass_path, db_date_created, db_permissions - - # optional description. - db_desc = models.CharField("desc", max_length=255, blank=True) - # A reference to the database object affected by this Script, if any. - db_obj = models.ForeignKey( - "objects.ObjectDB", - null=True, - blank=True, - on_delete=models.CASCADE, - verbose_name="scripted object", - help_text="the object to store this script on, if not a global script.", - ) - db_account = models.ForeignKey( - "accounts.AccountDB", - null=True, - blank=True, - on_delete=models.CASCADE, - verbose_name="scripted account", - help_text="the account to store this script on (should not be set if db_obj is set)", - ) - - # how often to run Script (secs). -1 means there is no timer - db_interval = models.IntegerField( - "interval", default=-1, help_text="how often to repeat script, in seconds. -1 means off." - ) - # start script right away or wait interval seconds first - db_start_delay = models.BooleanField( - "start delay", default=False, help_text="pause interval seconds before starting." - ) - # how many times this script is to be repeated, if interval!=0. - db_repeats = models.IntegerField("number of repeats", default=0, help_text="0 means off.") - # defines if this script should survive a reboot or not - db_persistent = models.BooleanField("survive server reboot", default=False) - # defines if this script has already been started in this session - db_is_active = models.BooleanField("script active", default=False) - - # Database manager - objects = ScriptDBManager() - - # defaults - __settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS - __defaultclasspath__ = "evennia.scripts.scripts.DefaultScript" - __applabel__ = "scripts" - - class Meta(object): - "Define Django meta options" - verbose_name = "Script" - - # - # - # ScriptDB class properties - # - # - - # obj property - def __get_obj(self): - """ - Property wrapper that homogenizes access to either the - db_account or db_obj field, using the same object property - name. - - """ - obj = _GA(self, "db_account") - if not obj: - obj = _GA(self, "db_obj") - return obj - - def __set_obj(self, value): - """ - Set account or obj to their right database field. If - a dbref is given, assume ObjectDB. - - """ - try: - value = _GA(value, "dbobj") - except AttributeError: - # deprecated ... - pass - if isinstance(value, (str, int)): - from evennia.objects.models import ObjectDB - - value = to_str(value) - if value.isdigit() or value.startswith("#"): - dbid = dbref(value, reqhash=False) - if dbid: - try: - value = ObjectDB.objects.get(id=dbid) - except ObjectDoesNotExist: - # maybe it is just a name that happens to look like a dbid - pass - if value.__class__.__name__ == "AccountDB": - fname = "db_account" - _SA(self, fname, value) - else: - fname = "db_obj" - _SA(self, fname, value) - # saving the field - _GA(self, "save")(update_fields=[fname]) - - obj = property(__get_obj, __set_obj) - object = property(__get_obj, __set_obj)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/scripts/monitorhandler.html b/docs/0.9.5/_modules/evennia/scripts/monitorhandler.html deleted file mode 100644 index 16e7465ff2..0000000000 --- a/docs/0.9.5/_modules/evennia/scripts/monitorhandler.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - - - evennia.scripts.monitorhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.scripts.monitorhandler

-"""
-Monitors - catch changes to model fields and Attributes.
-
-The MONITOR_HANDLER singleton from this module offers the following
-functionality:
-
-- Field-monitor - track a object's specific database field and perform
-    an action whenever that field *changes* for whatever reason.
-- Attribute-monitor tracks an object's specific Attribute and perform
-    an action whenever that Attribute *changes* for whatever reason.
-
-"""
-import inspect
-
-from collections import defaultdict
-from evennia.server.models import ServerConfig
-from evennia.utils.dbserialize import dbserialize, dbunserialize
-from evennia.utils import logger
-from evennia.utils import variable_from_module
-
-_SA = object.__setattr__
-_GA = object.__getattribute__
-_DA = object.__delattr__
-
-
-
[docs]class MonitorHandler(object): - """ - This is a resource singleton that allows for registering - callbacks for when a field or Attribute is updated (saved). - """ - -
[docs] def __init__(self): - """ - Initialize the handler. - """ - self.savekey = "_monitorhandler_save" - self.monitors = defaultdict(lambda: defaultdict(dict))
- -
[docs] def save(self): - """ - Store our monitors to the database. This is called - by the server process. - - Since dbserialize can't handle defaultdicts, we convert to an - intermediary save format ((obj,fieldname, idstring, callback, kwargs), ...) - - """ - savedata = [] - if self.monitors: - for obj in self.monitors: - for fieldname in self.monitors[obj]: - for idstring, (callback, persistent, kwargs) in self.monitors[obj][ - fieldname - ].items(): - path = "%s.%s" % (callback.__module__, callback.__name__) - savedata.append((obj, fieldname, idstring, path, persistent, kwargs)) - savedata = dbserialize(savedata) - ServerConfig.objects.conf(key=self.savekey, value=savedata)
- -
[docs] def restore(self, server_reload=True): - """ - Restore our monitors after a reload. This is called - by the server process. - - Args: - server_reload (bool, optional): If this is False, it means - the server went through a cold reboot and all - non-persistent tickers must be killed. - - """ - self.monitors = defaultdict(lambda: defaultdict(dict)) - restored_monitors = ServerConfig.objects.conf(key=self.savekey) - if restored_monitors: - restored_monitors = dbunserialize(restored_monitors) - for (obj, fieldname, idstring, path, persistent, kwargs) in restored_monitors: - try: - if not server_reload and not persistent: - # this monitor will not be restarted - continue - if "session" in kwargs and not kwargs["session"]: - # the session was removed because it no longer - # exists. Don't restart the monitor. - continue - modname, varname = path.rsplit(".", 1) - callback = variable_from_module(modname, varname) - - if obj and hasattr(obj, fieldname): - self.monitors[obj][fieldname][idstring] = (callback, persistent, kwargs) - except Exception: - continue - # make sure to clean data from database - ServerConfig.objects.conf(key=self.savekey, delete=True)
- -
[docs] def at_update(self, obj, fieldname): - """ - Called by the field/attribute as it saves. - - """ - to_delete = [] - if obj in self.monitors and fieldname in self.monitors[obj]: - for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].items(): - try: - callback(obj=obj, fieldname=fieldname, **kwargs) - except Exception: - to_delete.append((obj, fieldname, idstring)) - logger.log_trace("Monitor callback was removed.") - # we cleanup non-found monitors (has to be done after loop) - for (obj, fieldname, idstring) in to_delete: - del self.monitors[obj][fieldname][idstring]
- -
[docs] def add(self, obj, fieldname, callback, idstring="", persistent=False, **kwargs): - """ - Add monitoring to a given field or Attribute. A field must - be specified with the full db_* name or it will be assumed - to be an Attribute (so `db_key`, not just `key`). - - Args: - obj (Typeclassed Entity): The entity on which to monitor a - field or Attribute. - fieldname (str): Name of field (db_*) or Attribute to monitor. - callback (callable): A callable on the form `callable(**kwargs), - where kwargs holds keys fieldname and obj. - idstring (str, optional): An id to separate this monitor from other monitors - of the same field and object. - persistent (bool, optional): If False, the monitor will survive - a server reload but not a cold restart. This is default. - - Keyword Args: - session (Session): If this keyword is given, the monitorhandler will - correctly analyze it and remove the monitor if after a reload/reboot - the session is no longer valid. - any (any): Any other kwargs are passed on to the callback. Remember that - all kwargs must be possible to pickle! - - """ - if not fieldname.startswith("db_") or not hasattr(obj, fieldname): - # an Attribute - we track its db_value field - obj = obj.attributes.get(fieldname, return_obj=True) - if not obj: - return - fieldname = "db_value" - - # we try to serialize this data to test it's valid. Otherwise we won't accept it. - try: - if not inspect.isfunction(callback): - raise TypeError("callback is not a function.") - dbserialize((obj, fieldname, callback, idstring, persistent, kwargs)) - except Exception: - err = "Invalid monitor definition: \n" " (%s, %s, %s, %s, %s, %s)" % ( - obj, - fieldname, - callback, - idstring, - persistent, - kwargs, - ) - logger.log_trace(err) - else: - self.monitors[obj][fieldname][idstring] = (callback, persistent, kwargs)
- -
[docs] def remove(self, obj, fieldname, idstring=""): - """ - Remove a monitor. - """ - if not fieldname.startswith("db_") or not hasattr(obj, fieldname): - obj = obj.attributes.get(fieldname, return_obj=True) - if not obj: - return - fieldname = "db_value" - - idstring_dict = self.monitors[obj][fieldname] - if idstring in idstring_dict: - del self.monitors[obj][fieldname][idstring]
- -
[docs] def clear(self): - """ - Delete all monitors. - """ - self.monitors = defaultdict(lambda: defaultdict(dict))
- -
[docs] def all(self, obj=None): - """ - List all monitors or all monitors of a given object. - - Args: - obj (Object): The object on which to list all monitors. - - Returns: - monitors (list): The handled monitors. - - """ - output = [] - objs = [obj] if obj else self.monitors - - for obj in objs: - for fieldname in self.monitors[obj]: - for idstring, (callback, persistent, kwargs) in self.monitors[obj][ - fieldname - ].items(): - output.append((obj, fieldname, idstring, persistent, kwargs)) - return output
- - -# access object -MONITOR_HANDLER = MonitorHandler() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/scripts/scripthandler.html b/docs/0.9.5/_modules/evennia/scripts/scripthandler.html deleted file mode 100644 index 882039e44b..0000000000 --- a/docs/0.9.5/_modules/evennia/scripts/scripthandler.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - - evennia.scripts.scripthandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.scripts.scripthandler

-"""
-The script handler makes sure to check through all stored scripts to
-make sure they are still relevant. A scripthandler is automatically
-added to all game objects. You access it through the property
-`scripts` on the game object.
-
-"""
-from evennia.scripts.models import ScriptDB
-from evennia.utils import create
-from evennia.utils import logger
-
-from django.utils.translation import gettext as _
-
-
-
[docs]class ScriptHandler(object): - """ - Implements the handler. This sits on each game object. - - """ - -
[docs] def __init__(self, obj): - """ - Set up internal state. - - Args: - obj (Object): A reference to the object this handler is - attached to. - - """ - self.obj = obj
- - def __str__(self): - """ - List the scripts tied to this object. - - """ - scripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj) - string = "" - for script in scripts: - interval = "inf" - next_repeat = "inf" - repeats = "inf" - if script.interval > 0: - interval = script.interval - if script.repeats: - repeats = script.repeats - try: - next_repeat = script.time_until_next_repeat() - except Exception: - next_repeat = "?" - string += _( - "\n '%(key)s' (%(next_repeat)s/%(interval)s, %(repeats)s repeats): %(desc)s" - ) % { - "key": script.key, - "next_repeat": next_repeat, - "interval": interval, - "repeats": repeats, - "desc": script.desc, - } - return string.strip() - -
[docs] def add(self, scriptclass, key=None, autostart=True): - """ - Add a script to this object. - - Args: - scriptclass (Scriptclass, Script or str): Either a class - object inheriting from DefaultScript, an instantiated - script object or a python path to such a class object. - key (str, optional): Identifier for the script (often set - in script definition and listings) - autostart (bool, optional): Start the script upon adding it. - - """ - if self.obj.__dbclass__.__name__ == "AccountDB": - # we add to an Account, not an Object - script = create.create_script( - scriptclass, key=key, account=self.obj, autostart=autostart - ) - else: - # the normal - adding to an Object. We wait to autostart so we can differentiate - # a failing creation from a script that immediately starts/stops. - script = create.create_script(scriptclass, key=key, obj=self.obj, autostart=False) - if not script: - logger.log_err("Script %s failed to be created/started." % scriptclass) - return False - if autostart: - script.start() - if not script.id: - # this can happen if the script has repeats=1 or calls stop() in at_repeat. - logger.log_info( - "Script %s started and then immediately stopped; " - "it could probably be a normal function." % scriptclass - ) - return True
- -
[docs] def start(self, key): - """ - Find scripts and force-start them - - Args: - key (str): The script's key or dbref. - - Returns: - nr_started (int): The number of started scripts found. - - """ - scripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=key) - num = 0 - for script in scripts: - num += script.start() - return num
- -
[docs] def get(self, key): - """ - Search scripts on this object. - - Args: - key (str): Search criterion, the script's key or dbref. - - Returns: - scripts (list): The found scripts matching `key`. - - """ - return list(ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=key))
- -
[docs] def delete(self, key=None): - """ - Forcibly delete a script from this object. - - Args: - key (str, optional): A script key or the path to a script (in the - latter case all scripts with this path will be deleted!) - If no key is given, delete *all* scripts on the object! - - """ - delscripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=key) - if not delscripts: - delscripts = [ - script - for script in ScriptDB.objects.get_all_scripts_on_obj(self.obj) - if script.path == key - ] - num = 0 - for script in delscripts: - num += script.stop() - return num
- - # alias to delete - stop = delete - -
[docs] def all(self): - """ - Get all scripts stored in this handler. - - """ - return ScriptDB.objects.get_all_scripts_on_obj(self.obj)
- -
[docs] def validate(self, init_mode=False): - """ - Runs a validation on this object's scripts only. This should - be called regularly to crank the wheels. - - Args: - init_mode (str, optional): - This is used during server - upstart and can have three values: - - `False` (no init mode). Called during run. - - `"reset"` - server reboot. Kill non-persistent scripts - - `"reload"` - server reload. Keep non-persistent scripts. - - """ - ScriptDB.objects.validate(obj=self.obj, init_mode=init_mode)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/scripts/scripts.html b/docs/0.9.5/_modules/evennia/scripts/scripts.html deleted file mode 100644 index 8e269448c7..0000000000 --- a/docs/0.9.5/_modules/evennia/scripts/scripts.html +++ /dev/null @@ -1,822 +0,0 @@ - - - - - - - - evennia.scripts.scripts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.scripts.scripts

-"""
-This module defines Scripts, out-of-character entities that can store
-data both on themselves and on other objects while also having the
-ability to run timers.
-
-"""
-
-from twisted.internet.defer import Deferred, maybeDeferred
-from twisted.internet.task import LoopingCall
-from django.core.exceptions import ObjectDoesNotExist
-from django.utils.translation import gettext as _
-from evennia.typeclasses.models import TypeclassBase
-from evennia.scripts.models import ScriptDB
-from evennia.scripts.manager import ScriptManager
-from evennia.utils import create, logger
-
-__all__ = ["DefaultScript", "DoNothing", "Store"]
-
-
-FLUSHING_INSTANCES = False  # whether we're in the process of flushing scripts from the cache
-SCRIPT_FLUSH_TIMERS = {}  # stores timers for scripts that are currently being flushed
-
-
-def restart_scripts_after_flush():
-    """After instances are flushed, validate scripts so they're not dead for a long period of time"""
-    global FLUSHING_INSTANCES
-    ScriptDB.objects.validate()
-    FLUSHING_INSTANCES = False
-
-
-class ExtendedLoopingCall(LoopingCall):
-    """
-    LoopingCall that can start at a delay different
-    than `self.interval`.
-
-    """
-
-    start_delay = None
-    callcount = 0
-
-    def start(self, interval, now=True, start_delay=None, count_start=0):
-        """
-        Start running function every interval seconds.
-
-        This overloads the LoopingCall default by offering the
-        start_delay keyword and ability to repeat.
-
-        Args:
-            interval (int): Repeat interval in seconds.
-            now (bool, optional): Whether to start immediately or after
-                `start_delay` seconds.
-            start_delay (int): The number of seconds before starting.
-                If None, wait interval seconds. Only valid if `now` is `False`.
-                It is used as a way to start with a variable start time
-                after a pause.
-            count_start (int): Number of repeats to start at. The  count
-                goes up every time the system repeats. This is used to
-                implement something repeating `N` number of times etc.
-
-        Raises:
-            AssertError: if trying to start a task which is already running.
-            ValueError: If interval is set to an invalid value < 0.
-
-        Notes:
-            As opposed to Twisted's inbuilt count mechanism, this
-            system will count also if force_repeat() was called rather
-            than just the number of `interval` seconds since the start.
-            This allows us to force-step through a limited number of
-            steps if we want.
-
-        """
-        assert not self.running, "Tried to start an already running ExtendedLoopingCall."
-        if interval < 0:
-            raise ValueError("interval must be >= 0")
-        self.running = True
-        deferred = self._deferred = Deferred()
-        self.starttime = self.clock.seconds()
-        self.interval = interval
-        self._runAtStart = now
-        self.callcount = max(0, count_start)
-        self.start_delay = start_delay if start_delay is None else max(0, start_delay)
-
-        if now:
-            # run immediately
-            self()
-        elif start_delay is not None and start_delay >= 0:
-            # start after some time: for this to work we need to
-            # trick _scheduleFrom by temporarily setting a different
-            # self.interval for it to check.
-            real_interval, self.interval = self.interval, start_delay
-            self._scheduleFrom(self.starttime)
-            # re-set the actual interval (this will be picked up
-            # next time it runs
-            self.interval = real_interval
-        else:
-            self._scheduleFrom(self.starttime)
-        return deferred
-
-    def __call__(self):
-        """
-        Tick one step. We update callcount (tracks number of calls) as
-        well as null start_delay (needed in order to correctly
-        estimate next_call_time at all times).
-
-        """
-        self.callcount += 1
-        if self.start_delay:
-            self.start_delay = None
-            self.starttime = self.clock.seconds()
-        if self._deferred:
-            LoopingCall.__call__(self)
-
-    def force_repeat(self):
-        """
-        Force-fire the callback
-
-        Raises:
-            AssertionError: When trying to force a task that is not
-                running.
-
-        """
-        assert self.running, "Tried to fire an ExtendedLoopingCall that was not running."
-        self.call.cancel()
-        self.call = None
-        self.starttime = self.clock.seconds()
-        self()
-
-    def next_call_time(self):
-        """
-        Get the next call time. This also takes the eventual effect
-        of start_delay into account.
-
-        Returns:
-            next (int or None): The time in seconds until the next call. This
-                takes `start_delay` into account. Returns `None` if
-                the task is not running.
-
-        """
-        if self.running and self.interval > 0:
-            total_runtime = self.clock.seconds() - self.starttime
-            interval = self.start_delay or self.interval
-            return interval - (total_runtime % self.interval)
-
-
-class ScriptBase(ScriptDB, metaclass=TypeclassBase):
-    """
-    Base class for scripts. Don't inherit from this, inherit from the
-    class `DefaultScript` below instead.
-
-    """
-
-    objects = ScriptManager()
-
-    def __str__(self):
-        return "<{cls} {key}>".format(cls=self.__class__.__name__, key=self.key)
-
-    def __repr__(self):
-        return str(self)
-
-    def _start_task(self):
-        """
-        Start task runner.
-
-        """
-        if not self.ndb._task:
-            self.ndb._task = ExtendedLoopingCall(self._step_task)
-
-        if self.db._paused_time:
-            # the script was paused; restarting
-            callcount = self.db._paused_callcount or 0
-            self.ndb._task.start(
-                self.db_interval, now=False, start_delay=self.db._paused_time, count_start=callcount
-            )
-            del self.db._paused_time
-            del self.db._paused_repeats
-
-        elif not self.ndb._task.running:
-            # starting script anew
-            self.ndb._task.start(self.db_interval, now=not self.db_start_delay)
-
-    def _stop_task(self):
-        """
-        Stop task runner
-
-        """
-        task = self.ndb._task
-        if task and task.running:
-            task.stop()
-        self.ndb._task = None
-
-    def _step_errback(self, e):
-        """
-        Callback for runner errors
-
-        """
-        cname = self.__class__.__name__
-        estring = _(
-            "Script %(key)s(#%(dbid)s) of type '%(cname)s': at_repeat() error '%(err)s'."
-        ) % {"key": self.key, "dbid": self.dbid, "cname": cname, "err": e.getErrorMessage()}
-        try:
-            self.db_obj.msg(estring)
-        except Exception:
-            # we must not crash inside the errback, even if db_obj is None.
-            pass
-        logger.log_err(estring)
-
-    def _step_callback(self):
-        """
-        Step task runner. No try..except needed due to defer wrap.
-
-        """
-        if not self.ndb._task:
-            # if there is no task, we have no business using this method
-            return
-
-        if not self.is_valid():
-            self.stop()
-            return
-
-        # call hook
-        self.at_repeat()
-
-        # check repeats
-        if self.ndb._task:
-            # we need to check for the task in case stop() was called
-            # inside at_repeat() and it already went away.
-            callcount = self.ndb._task.callcount
-            maxcount = self.db_repeats
-            if maxcount > 0 and maxcount <= callcount:
-                self.stop()
-
-    def _step_task(self):
-        """
-        Step task. This groups error handling.
-        """
-        try:
-            return maybeDeferred(self._step_callback).addErrback(self._step_errback)
-        except Exception:
-            logger.log_trace()
-        return None
-
-    def at_script_creation(self):
-        """
-        Should be overridden in child.
-
-        """
-        pass
-
-    def at_first_save(self, **kwargs):
-        """
-        This is called after very first time this object is saved.
-        Generally, you don't need to overload this, but only the hooks
-        called by this method.
-
-        Args:
-            **kwargs (dict): Arbitrary, optional arguments for users
-                overriding the call (unused by default).
-
-        """
-        self.at_script_creation()
-
-        if hasattr(self, "_createdict"):
-            # this will only be set if the utils.create_script
-            # function was used to create the object. We want
-            # the create call's kwargs to override the values
-            # set by hooks.
-            cdict = self._createdict
-            updates = []
-            if not cdict.get("key"):
-                if not self.db_key:
-                    self.db_key = "#%i" % self.dbid
-                    updates.append("db_key")
-            elif self.db_key != cdict["key"]:
-                self.db_key = cdict["key"]
-                updates.append("db_key")
-            if cdict.get("interval") and self.interval != cdict["interval"]:
-                self.db_interval = max(0, cdict["interval"])
-                updates.append("db_interval")
-            if cdict.get("start_delay") and self.start_delay != cdict["start_delay"]:
-                self.db_start_delay = cdict["start_delay"]
-                updates.append("db_start_delay")
-            if cdict.get("repeats") and self.repeats != cdict["repeats"]:
-                self.db_repeats = max(0, cdict["repeats"])
-                updates.append("db_repeats")
-            if cdict.get("persistent") and self.persistent != cdict["persistent"]:
-                self.db_persistent = cdict["persistent"]
-                updates.append("db_persistent")
-            if cdict.get("desc") and self.desc != cdict["desc"]:
-                self.db_desc = cdict["desc"]
-                updates.append("db_desc")
-            if updates:
-                self.save(update_fields=updates)
-
-            if cdict.get("permissions"):
-                self.permissions.batch_add(*cdict["permissions"])
-            if cdict.get("locks"):
-                self.locks.add(cdict["locks"])
-            if cdict.get("tags"):
-                # this should be a list of tags, tuples (key, category) or (key, category, data)
-                self.tags.batch_add(*cdict["tags"])
-            if cdict.get("attributes"):
-                # this should be tuples (key, val, ...)
-                self.attributes.batch_add(*cdict["attributes"])
-            if cdict.get("nattributes"):
-                # this should be a dict of nattrname:value
-                for key, value in cdict["nattributes"]:
-                    self.nattributes.add(key, value)
-
-            if not cdict.get("autostart"):
-                # don't auto-start the script
-                return
-
-        # auto-start script (default)
-        self.start()
-
-
-
[docs]class DefaultScript(ScriptBase): - """ - This is the base TypeClass for all Scripts. Scripts describe - events, timers and states in game, they can have a time component - or describe a state that changes under certain conditions. - - """ - -
[docs] @classmethod - def create(cls, key, **kwargs): - """ - Provides a passthrough interface to the utils.create_script() function. - - Args: - key (str): Name of the new object. - - Returns: - object (Object): A newly created object of the given typeclass. - errors (list): A list of errors in string form, if any. - - """ - errors = [] - obj = None - - kwargs["key"] = key - - # If no typeclass supplied, use this class - kwargs["typeclass"] = kwargs.pop("typeclass", cls) - - try: - obj = create.create_script(**kwargs) - except Exception: - logger.log_trace() - errors.append("The script '%s' encountered errors and could not be created." % key) - - return obj, errors
- -
[docs] def at_script_creation(self): - """ - Only called once, when script is first created. - - """ - pass
- -
[docs] def time_until_next_repeat(self): - """ - Get time until the script fires it `at_repeat` hook again. - - Returns: - next (int): Time in seconds until the script runs again. - If not a timed script, return `None`. - - Notes: - This hook is not used in any way by the script's stepping - system; it's only here for the user to be able to check in - on their scripts and when they will next be run. - - """ - task = self.ndb._task - if task: - try: - return int(round(task.next_call_time())) - except TypeError: - pass - return None
- -
[docs] def remaining_repeats(self): - """ - Get the number of returning repeats for limited Scripts. - - Returns: - remaining (int or `None`): The number of repeats - remaining until the Script stops. Returns `None` - if it has unlimited repeats. - - """ - task = self.ndb._task - if task: - return max(0, self.db_repeats - task.callcount) - return None
- -
[docs] def at_idmapper_flush(self): - """If we're flushing this object, make sure the LoopingCall is gone too""" - ret = super(DefaultScript, self).at_idmapper_flush() - if ret and self.ndb._task: - try: - from twisted.internet import reactor - - global FLUSHING_INSTANCES - # store the current timers for the _task and stop it to avoid duplicates after cache flush - paused_time = self.ndb._task.next_call_time() - callcount = self.ndb._task.callcount - self._stop_task() - SCRIPT_FLUSH_TIMERS[self.id] = (paused_time, callcount) - # here we ensure that the restart call only happens once, not once per script - if not FLUSHING_INSTANCES: - FLUSHING_INSTANCES = True - reactor.callLater(2, restart_scripts_after_flush) - except Exception: - import traceback - - traceback.print_exc() - return ret
- -
[docs] def start(self, force_restart=False): - """ - Called every time the script is started (for persistent - scripts, this is usually once every server start) - - Args: - force_restart (bool, optional): Normally an already - started script will not be started again. if - `force_restart=True`, the script will always restart - the script, regardless of if it has started before. - - Returns: - result (int): 0 or 1 depending on if the script successfully - started or not. Used in counting. - - """ - if self.is_active and not force_restart: - # The script is already running, but make sure we have a _task if - # this is after a cache flush - if not self.ndb._task and self.db_interval > 0: - self.ndb._task = ExtendedLoopingCall(self._step_task) - try: - start_delay, callcount = SCRIPT_FLUSH_TIMERS[self.id] - del SCRIPT_FLUSH_TIMERS[self.id] - now = False - except (KeyError, ValueError, TypeError): - now = not self.db_start_delay - start_delay = None - callcount = 0 - self.ndb._task.start( - self.db_interval, now=now, start_delay=start_delay, count_start=callcount - ) - return 0 - - obj = self.obj - if obj: - # check so the scripted object is valid and initalized - try: - obj.cmdset - except AttributeError: - # this means the object is not initialized. - logger.log_trace() - self.is_active = False - return 0 - - # try to restart a paused script - try: - if self.unpause(manual_unpause=False): - return 1 - except RuntimeError: - # manually paused. - return 0 - - # start the script from scratch - self.is_active = True - try: - self.at_start() - except Exception: - logger.log_trace() - - if self.db_interval > 0: - self._start_task() - return 1
- -
[docs] def stop(self, kill=False): - """ - Called to stop the script from running. This also deletes the - script. - - Args: - kill (bool, optional): - Stop the script without - calling any relevant script hooks. - - Returns: - result (int): 0 if the script failed to stop, 1 otherwise. - Used in counting. - - """ - if not kill: - try: - self.at_stop() - except Exception: - logger.log_trace() - self._stop_task() - try: - self.delete() - except AssertionError: - logger.log_trace() - return 0 - except ObjectDoesNotExist: - return 0 - return 1
- -
[docs] def pause(self, manual_pause=True): - """ - This stops a running script and stores its active state. - It WILL NOT call the `at_stop()` hook. - - """ - self.db._manual_pause = manual_pause - if not self.db._paused_time: - # only allow pause if not already paused - task = self.ndb._task - if task: - self.db._paused_time = task.next_call_time() - self.db._paused_callcount = task.callcount - self._stop_task() - self.is_active = False
- -
[docs] def unpause(self, manual_unpause=True): - """ - Restart a paused script. This WILL call the `at_start()` hook. - - Args: - manual_unpause (bool, optional): This is False if unpause is - called by the server reload/reset mechanism. - Returns: - result (bool): True if unpause was triggered, False otherwise. - - Raises: - RuntimeError: If trying to automatically resart this script - (usually after a reset/reload), but it was manually paused, - and so should not the auto-unpaused. - - """ - if not manual_unpause and self.db._manual_pause: - # if this script was paused manually (by a direct call of pause), - # it cannot be automatically unpaused (e.g. by a @reload) - raise RuntimeError - - # Ensure that the script is fully unpaused, so that future calls - # to unpause do not raise a RuntimeError - self.db._manual_pause = False - - if self.db._paused_time: - # only unpause if previously paused - self.is_active = True - - try: - self.at_start() - except Exception: - logger.log_trace() - - self._start_task() - return True
- -
[docs] def restart(self, interval=None, repeats=None, start_delay=None): - """ - Restarts an already existing/running Script from the - beginning, optionally using different settings. This will - first call the stop hooks, and then the start hooks again. - Args: - interval (int, optional): Allows for changing the interval - of the Script. Given in seconds. if `None`, will use the already stored interval. - repeats (int, optional): The number of repeats. If unset, will - use the previous setting. - start_delay (bool, optional): If we should wait `interval` seconds - before starting or not. If `None`, re-use the previous setting. - - """ - try: - self.at_stop() - except Exception: - logger.log_trace() - self._stop_task() - self.is_active = False - # remove all pause flags - del self.db._paused_time - del self.db._manual_pause - del self.db._paused_callcount - # set new flags and start over - if interval is not None: - interval = max(0, interval) - self.interval = interval - if repeats is not None: - self.repeats = repeats - if start_delay is not None: - self.start_delay = start_delay - self.start()
- -
[docs] def reset_callcount(self, value=0): - """ - Reset the count of the number of calls done. - - Args: - value (int, optional): The repeat value to reset to. Default - is to set it all the way back to 0. - - Notes: - This is only useful if repeats != 0. - - """ - task = self.ndb._task - if task: - task.callcount = max(0, int(value))
- -
[docs] def force_repeat(self): - """ - Fire a premature triggering of the script callback. This - will reset the timer and count down repeats as if the script - had fired normally. - """ - task = self.ndb._task - if task: - task.force_repeat()
- -
[docs] def is_valid(self): - """ - Is called to check if the script is valid to run at this time. - Should return a boolean. The method is assumed to collect all - needed information from its related self.obj. - - """ - return not self._is_deleted
- -
[docs] def at_start(self, **kwargs): - """ - Called whenever the script is started, which for persistent - scripts is at least once every server start. It will also be - called when starting again after a pause (such as after a - server reload) - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_repeat(self, **kwargs): - """ - Called repeatedly if this Script is set to repeat regularly. - - Args: - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_stop(self, **kwargs): - """ - Called whenever when it's time for this script to stop (either - because is_valid returned False or it runs out of iterations) - - Args - **kwargs (dict): Arbitrary, optional arguments for users - overriding the call (unused by default). - - """ - pass
- -
[docs] def at_server_reload(self): - """ - This hook is called whenever the server is shutting down for - restart/reboot. If you want to, for example, save - non-persistent properties across a restart, this is the place - to do it. - """ - pass
- -
[docs] def at_server_shutdown(self): - """ - This hook is called whenever the server is shutting down fully - (i.e. not for a restart). - """ - pass
- - -# Some useful default Script types used by Evennia. - - -
[docs]class DoNothing(DefaultScript): - """ - A script that does nothing. Used as default fallback. - """ - -
[docs] def at_script_creation(self): - """ - Setup the script - """ - self.key = "sys_do_nothing" - self.desc = "This is an empty placeholder script."
- - -
[docs]class Store(DefaultScript): - """ - Simple storage script - """ - -
[docs] def at_script_creation(self): - """ - Setup the script - """ - self.key = "sys_storage" - self.desc = "This is a generic storage container."
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/scripts/taskhandler.html b/docs/0.9.5/_modules/evennia/scripts/taskhandler.html deleted file mode 100644 index 54903a111d..0000000000 --- a/docs/0.9.5/_modules/evennia/scripts/taskhandler.html +++ /dev/null @@ -1,694 +0,0 @@ - - - - - - - - evennia.scripts.taskhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.scripts.taskhandler

-"""
-Module containing the task handler for Evennia deferred tasks, persistent or not.
-"""
-
-from datetime import datetime, timedelta
-
-from twisted.internet import reactor
-from pickle import PickleError
-from twisted.internet.task import deferLater
-from twisted.internet.defer import CancelledError as DefCancelledError
-from evennia.server.models import ServerConfig
-from evennia.utils.logger import log_err
-from evennia.utils.dbserialize import dbserialize, dbunserialize
-
-TASK_HANDLER = None
-
-
-
[docs]def handle_error(*args, **kwargs): - """Handle errors within deferred objects.""" - for arg in args: - # suppress cancel errors - if arg.type == DefCancelledError: - continue - raise arg
- - -
[docs]class TaskHandlerTask: - """An object to represent a single TaskHandler task. - - Instance Attributes: - task_id (int): the global id for this task - deferred (deferred): a reference to this task's deferred - Property Attributes: - paused (bool): check if the deferred instance of a task has been paused. - called(self): A task attribute to check if the deferred instance of a task has been called. - - Methods: - pause(): Pause the callback of a task. - unpause(): Process all callbacks made since pause() was called. - do_task(): Execute the task (call its callback). - call(): Call the callback of this task. - remove(): Remove a task without executing it. - cancel(): Stop a task from automatically executing. - active(): Check if a task is active (has not been called yet). - exists(): Check if a task exists. - get_id(): Returns the global id for this task. For use with - - """ - -
[docs] def __init__(self, task_id): - self.task_id = task_id - self.deferred = TASK_HANDLER.get_deferred(task_id)
- -
[docs] def get_deferred(self): - """Return the instance of the deferred the task id is using. - - Returns: - bool or deferred: An instance of a deferred or False if there is no task with the id. - None is returned if there is no deferred affiliated with this id. - - """ - return TASK_HANDLER.get_deferred(self.task_id)
- -
[docs] def pause(self): - """Pause the callback of a task. - To resume use TaskHandlerTask.unpause - """ - d = self.deferred - if d: - d.pause()
- -
[docs] def unpause(self): - """Unpause a task, run the task if it has passed delay time.""" - d = self.deferred - if d: - d.unpause()
- - @property - def paused(self): - """A task attribute to check if the deferred instance of a task has been paused. - - This exists to mock usage of a twisted deferred object. - - Returns: - bool or None: True if the task was properly paused. None if the task does not have - a deferred instance. - - """ - d = self.deferred - if d: - return d.paused - else: - return None - -
[docs] def do_task(self): - """Execute the task (call its callback). - If calling before timedelay, cancel the deferred instance affliated to this task. - Remove the task from the dictionary of current tasks on a successful - callback. - - Returns: - bool or any: Set to `False` if the task does not exist in task - handler. Otherwise it will be the return of the task's callback. - - """ - return TASK_HANDLER.do_task(self.task_id)
- -
[docs] def call(self): - """Call the callback of a task. - Leave the task unaffected otherwise. - This does not use the task's deferred instance. - The only requirement is that the task exist in task handler. - - Returns: - bool or any: Set to `False` if the task does not exist in task - handler. Otherwise it will be the return of the task's callback. - - """ - return TASK_HANDLER.call_task(self.task_id)
- -
[docs] def remove(self): - """Remove a task without executing it. - Deletes the instance of the task's deferred. - - Args: - task_id (int): an existing task ID. - - Returns: - bool: True if the removal completed successfully. - - """ - return TASK_HANDLER.remove(self.task_id)
- -
[docs] def cancel(self): - """Stop a task from automatically executing. - This will not remove the task. - - Returns: - bool: True if the cancel completed successfully. - False if the cancel did not complete successfully. - - """ - return TASK_HANDLER.cancel(self.task_id)
- -
[docs] def active(self): - """Check if a task is active (has not been called yet). - - Returns: - bool: True if a task is active (has not been called yet). False if - it is not (has been called) or if the task does not exist. - - """ - return TASK_HANDLER.active(self.task_id)
- - @property - def called(self): - """ - A task attribute to check if the deferred instance of a task has been called. - - This exists to mock usage of a twisted deferred object. - It will not set to True if Task.call has been called. This only happens if - task's deferred instance calls the callback. - - Returns: - bool: True if the deferred instance of this task has called the callback. - False if the deferred instnace of this task has not called the callback. - - """ - d = self.deferred - if d: - return d.called - else: - return None - -
[docs] def exists(self): - """Check if a task exists. - Most task handler methods check for existence for you. - - Returns: - bool: True the task exists False if it does not. - - """ - return TASK_HANDLER.exists(self.task_id)
- -
[docs] def get_id(self): - """ Returns the global id for this task. For use with - `evennia.scripts.taskhandler.TASK_HANDLER`. - - Returns: - task_id (int): global task id for this task. - - """ - return self.task_id
- - -
[docs]class TaskHandler(object): - - """A light singleton wrapper allowing to access permanent tasks. - - When `utils.delay` is called, the task handler is used to create - the task. - - Task handler will automatically remove uncalled but canceled from task - handler. By default this will not occur until a canceled task - has been uncalled for 60 second after the time it should have been called. - To adjust this time use TASK_HANDLER.stale_timeout. If stale_timeout is 0 - stale tasks will not be automatically removed. - This is not done on a timer. I is done as new tasks are added or the load method is called. - - """ - -
[docs] def __init__(self): - self.tasks = {} - self.to_save = {} - self.clock = reactor - # number of seconds before an uncalled canceled task is removed from TaskHandler - self.stale_timeout = 60 - self._now = False # used in unit testing to manually set now time
- -
[docs] def load(self): - """Load from the ServerConfig. - - This should be automatically called when Evennia starts. - It populates `self.tasks` according to the ServerConfig. - - """ - to_save = False - value = ServerConfig.objects.conf("delayed_tasks", default={}) - if isinstance(value, str): - tasks = dbunserialize(value) - else: - tasks = value - - # At this point, `tasks` contains a dictionary of still-serialized tasks - for task_id, value in tasks.items(): - date, callback, args, kwargs = dbunserialize(value) - if isinstance(callback, tuple): - # `callback` can be an object and name for instance methods - obj, method = callback - if obj is None: - to_save = True - continue - - callback = getattr(obj, method) - self.tasks[task_id] = (date, callback, args, kwargs, True, None) - - if self.stale_timeout > 0: # cleanup stale tasks. - self.clean_stale_tasks() - if to_save: - self.save()
- -
[docs] def clean_stale_tasks(self): - """remove uncalled but canceled from task handler. - - By default this will not occur until a canceled task - has been uncalled for 60 second after the time it should have been called. - To adjust this time use TASK_HANDLER.stale_timeout. - - """ - clean_ids = [] - for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): - if not self.active(task_id): - stale_date = date + timedelta(seconds=self.stale_timeout) - # if a now time is provided use it (intended for unit testing) - now = self._now if self._now else datetime.now() - # the task was canceled more than stale_timeout seconds ago - if now > stale_date: - clean_ids.append(task_id) - for task_id in clean_ids: - self.remove(task_id) - return True
- -
[docs] def save(self): - """Save the tasks in ServerConfig.""" - - for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): - if task_id in self.to_save: - continue - if not persistent: - continue - - safe_callback = callback - if getattr(callback, "__self__", None): - # `callback` is an instance method - obj = callback.__self__ - name = callback.__name__ - safe_callback = (obj, name) - - # Check if callback can be pickled. args and kwargs have been checked - try: - dbserialize(safe_callback) - except (TypeError, AttributeError, PickleError) as err: - raise ValueError( - "the specified callback {callback} cannot be pickled. " - "It must be a top-level function in a module or an " - "instance method ({err}).".format(callback=callback, err=err) - ) - - self.to_save[task_id] = dbserialize((date, safe_callback, args, kwargs)) - ServerConfig.objects.conf("delayed_tasks", self.to_save)
- -
[docs] def add(self, timedelay, callback, *args, **kwargs): - """Add a new task. - - If the persistent kwarg is truthy: - The callback, args and values for kwarg will be serialized. Type - and attribute errors during the serialization will be logged, - but will not throw exceptions. - For persistent tasks do not use memory references in the callback - function or arguments. After a restart those memory references are no - longer accurate. - - Args: - timedelay (int or float): time in seconds before calling the callback. - callback (function or instance method): the callback itself - any (any): any additional positional arguments to send to the callback - *args: positional arguments to pass to callback. - **kwargs: keyword arguments to pass to callback. - - persistent (bool, optional): persist the task (stores it). - Persistent key and value is removed from kwargs it will - not be passed to callback. - - Returns: - TaskHandlerTask: An object to represent a task. - Reference evennia.scripts.taskhandler.TaskHandlerTask for complete details. - - """ - # set the completion time - # Only used on persistent tasks after a restart - now = datetime.now() - delta = timedelta(seconds=timedelay) - comp_time = now + delta - # get an open task id - used_ids = list(self.tasks.keys()) - task_id = 1 - while task_id in used_ids: - task_id += 1 - - # record the task to the tasks dictionary - persistent = kwargs.get("persistent", False) - if "persistent" in kwargs: - del kwargs["persistent"] - if persistent: - safe_args = [] - safe_kwargs = {} - - # Check that args and kwargs contain picklable information - for arg in args: - try: - dbserialize(arg) - except (TypeError, AttributeError, PickleError): - log_err( - "The positional argument {} cannot be " - "pickled and will not be present in the arguments " - "fed to the callback {}".format(arg, callback) - ) - else: - safe_args.append(arg) - - for key, value in kwargs.items(): - try: - dbserialize(value) - except (TypeError, AttributeError, PickleError): - log_err( - "The {} keyword argument {} cannot be " - "pickled and will not be present in the arguments " - "fed to the callback {}".format(key, value, callback) - ) - else: - safe_kwargs[key] = value - - self.tasks[task_id] = (comp_time, callback, safe_args, safe_kwargs, persistent, None) - self.save() - else: # this is a non-persitent task - self.tasks[task_id] = (comp_time, callback, args, kwargs, persistent, None) - - # defer the task - callback = self.do_task - args = [task_id] - kwargs = {} - d = deferLater(self.clock, timedelay, callback, *args, **kwargs) - d.addErrback(handle_error) - - # some tasks may complete before the deferred can be added - if task_id in self.tasks: - task = self.tasks.get(task_id) - task = list(task) - task[4] = persistent - task[5] = d - self.tasks[task_id] = task - else: # the task already completed - return False - if self.stale_timeout > 0: - self.clean_stale_tasks() - return TaskHandlerTask(task_id)
- -
[docs] def exists(self, task_id): - """Check if a task exists. - Most task handler methods check for existence for you. - - Args: - task_id (int): an existing task ID. - - Returns: - bool: True the task exists False if it does not. - - """ - if task_id in self.tasks: - return True - else: - return False
- -
[docs] def active(self, task_id): - """Check if a task is active (has not been called yet). - - Args: - task_id (int): an existing task ID. - - Returns: - bool: True if a task is active (has not been called yet). False if - it is not (has been called) or if the task does not exist. - - """ - if task_id in self.tasks: - # if the task has not been run, cancel it - deferred = self.get_deferred(task_id) - return not (deferred and deferred.called) - else: - return False
- -
[docs] def cancel(self, task_id): - """Stop a task from automatically executing. - This will not remove the task. - - Args: - task_id (int): an existing task ID. - - Returns: - bool: True if the cancel completed successfully. - False if the cancel did not complete successfully. - - """ - if task_id in self.tasks: - # if the task has not been run, cancel it - d = self.get_deferred(task_id) - if d: # it is remotely possible for a task to not have a deferred - if d.called: - return False - else: # the callback has not been called yet. - d.cancel() - return True - else: # this task has no deferred instance - return False - else: - return False
- -
[docs] def remove(self, task_id): - """Remove a task without executing it. - Deletes the instance of the task's deferred. - - Args: - task_id (int): an existing task ID. - - Returns: - bool: True if the removal completed successfully. - - """ - d = None - # delete the task from the tasks dictionary - if task_id in self.tasks: - # if the task has not been run, cancel it - self.cancel(task_id) - del self.tasks[task_id] # delete the task from the tasks dictionary - # remove the task from the persistent dictionary and ServerConfig - if task_id in self.to_save: - del self.to_save[task_id] - self.save() # remove from ServerConfig.objects - # delete the instance of the deferred - if d: - del d - return True
- -
[docs] def clear(self, save=True, cancel=True): - """clear all tasks. - By default tasks are canceled and removed from the database also. - - Args: - save=True (bool): Should changes to persistent tasks be saved to database. - cancel=True (bool): Cancel scheduled tasks before removing it from task handler. - - Returns: - True (bool): if the removal completed successfully. - - """ - if self.tasks: - for task_id in self.tasks.keys(): - if cancel: - self.cancel(task_id) - self.tasks = {} - if self.to_save: - self.to_save = {} - if save: - self.save() - return True
- -
[docs] def call_task(self, task_id): - """Call the callback of a task. - Leave the task unaffected otherwise. - This does not use the task's deferred instance. - The only requirement is that the task exist in task handler. - - Args: - task_id (int): an existing task ID. - - Returns: - bool or any: Set to `False` if the task does not exist in task - handler. Otherwise it will be the return of the task's callback. - - """ - if task_id in self.tasks: - date, callback, args, kwargs, persistent, d = self.tasks.get(task_id) - else: # the task does not exist - return False - return callback(*args, **kwargs)
- -
[docs] def do_task(self, task_id): - """Execute the task (call its callback). - If calling before timedelay cancel the deferred instance affliated to this task. - Remove the task from the dictionary of current tasks on a successful - callback. - - Args: - task_id (int): a valid task ID. - - Returns: - bool or any: Set to `False` if the task does not exist in task - handler. Otherwise it will be the return of the task's callback. - - """ - callback_return = False - if task_id in self.tasks: - date, callback, args, kwargs, persistent, d = self.tasks.get(task_id) - else: # the task does not exist - return False - if d: # it is remotely possible for a task to not have a deferred - if not d.called: # the task's deferred has not been called yet - d.cancel() # cancel the automated callback - else: # this task has no deferred, and should not be called - return False - callback_return = callback(*args, **kwargs) - self.remove(task_id) - return callback_return
- -
[docs] def get_deferred(self, task_id): - """ - Return the instance of the deferred the task id is using. - - Args: - task_id (int): a valid task ID. - - Returns: - bool or deferred: An instance of a deferred or False if there is no task with the id. - None is returned if there is no deferred affiliated with this id. - - """ - if task_id in self.tasks: - return self.tasks[task_id][5] - else: - return None
- -
[docs] def create_delays(self): - """Create the delayed tasks for the persistent tasks. - This method should be automatically called when Evennia starts. - - """ - now = datetime.now() - for task_id, (date, callback, args, kwargs, _, _) in self.tasks.items(): - self.tasks[task_id] = date, callback, args, kwargs, True, None - seconds = max(0, (date - now).total_seconds()) - d = deferLater(self.clock, seconds, self.do_task, task_id) - d.addErrback(handle_error) - # some tasks may complete before the deferred can be added - if self.tasks.get(task_id, False): - self.tasks[task_id] = date, callback, args, kwargs, True, d
- - -# Create the soft singleton -TASK_HANDLER = TaskHandler() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/scripts/tickerhandler.html b/docs/0.9.5/_modules/evennia/scripts/tickerhandler.html deleted file mode 100644 index da1c648f8c..0000000000 --- a/docs/0.9.5/_modules/evennia/scripts/tickerhandler.html +++ /dev/null @@ -1,749 +0,0 @@ - - - - - - - - evennia.scripts.tickerhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.scripts.tickerhandler

-"""
-TickerHandler
-
-This implements an efficient Ticker which uses a subscription
-model to 'tick' subscribed objects at regular intervals.
-
-The ticker mechanism is used by importing and accessing
-the instantiated TICKER_HANDLER instance in this module. This
-instance is run by the server; it will save its status across
-server reloads and be started automaticall on boot.
-
-Example:
-
-```python
-    from evennia.scripts.tickerhandler import TICKER_HANDLER
-
-    # call tick myobj.at_tick(*args, **kwargs) every 15 seconds
-    TICKER_HANDLER.add(15, myobj.at_tick, *args, **kwargs)
-```
-
-You supply the interval to tick and a callable to call regularly with
-any extra args/kwargs. The callable should either be a stand-alone
-function in a module *or* the method on a *typeclassed* entity (that
-is, on an object that can be safely and stably returned from the
-database).  Functions that are dynamically created or sits on
-in-memory objects cannot be used by the tickerhandler (there is no way
-to reference them safely across reboots and saves).
-
-The handler will transparently set
-up and add new timers behind the scenes to tick at given intervals,
-using a TickerPool - all callables with the same interval will share
-the interval ticker.
-
-To remove:
-
-```python
-    TICKER_HANDLER.remove(15, myobj.at_tick)
-```
-
-Both interval and callable must be given since a single object can be subscribed
-to many different tickers at the same time. You can also supply `idstring`
-as an identifying string if you ever want to tick the callable at the same interval
-but with different arguments (args/kwargs are not used for identifying the ticker). There
-is also `persistent=False` if you don't want to make a ticker that don't survive a reload.
-If either or both `idstring` or `persistent` has been changed from their defaults, they
-must be supplied to the `TICKER_HANDLER.remove` call to properly identify the ticker
-to remove.
-
-The TickerHandler's functionality can be overloaded by modifying the
-Ticker class and then changing TickerPool and TickerHandler to use the
-custom classes
-
-```python
-class MyTicker(Ticker):
-    # [doing custom stuff]
-
-class MyTickerPool(TickerPool):
-    ticker_class = MyTicker
-class MyTickerHandler(TickerHandler):
-    ticker_pool_class = MyTickerPool
-```
-
-If one wants to duplicate TICKER_HANDLER's auto-saving feature in
-a  custom handler one can make a custom `AT_STARTSTOP_MODULE` entry to
-call the handler's `save()` and `restore()` methods when the server reboots.
-
-"""
-import inspect
-
-from twisted.internet.defer import inlineCallbacks
-from django.core.exceptions import ObjectDoesNotExist
-from evennia.scripts.scripts import ExtendedLoopingCall
-from evennia.server.models import ServerConfig
-from evennia.utils.logger import log_trace, log_err
-from evennia.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj
-from evennia.utils import variable_from_module, inherits_from
-
-_GA = object.__getattribute__
-_SA = object.__setattr__
-
-
-_ERROR_ADD_TICKER = """TickerHandler: Tried to add an invalid ticker:
-{store_key}
-Ticker was not added."""
-
-_ERROR_ADD_TICKER_SUB_SECOND = """You are trying to add a ticker running faster
-than once per second. This is not supported and also probably not useful:
-Spamming messages to the user faster than once per second serves no purpose in
-a text-game, and if you want to update some property, consider doing so
-on-demand rather than using a ticker.
-"""
-
-
[docs]class Ticker(object): - """ - Represents a repeatedly running task that calls - hooks repeatedly. Overload `_callback` to change the - way it operates. - """ - - @inlineCallbacks - def _callback(self): - """ - This will be called repeatedly every `self.interval` seconds. - `self.subscriptions` contain tuples of (obj, args, kwargs) for - each subscribing object. - - If overloading, this callback is expected to handle all - subscriptions when it is triggered. It should not return - anything and should not traceback on poorly designed hooks. - The callback should ideally work under @inlineCallbacks so it - can yield appropriately. - - The _hook_key, which is passed down through the handler via - kwargs is used here to identify which hook method to call. - - """ - self._to_add = [] - self._to_remove = [] - self._is_ticking = True - for store_key, (args, kwargs) in self.subscriptions.items(): - callback = yield kwargs.pop("_callback", "at_tick") - obj = yield kwargs.pop("_obj", None) - try: - if callable(callback): - # call directly - yield callback(*args, **kwargs) - continue - # try object method - if not obj or not obj.pk: - # object was deleted between calls - self._to_remove.append(store_key) - continue - else: - yield _GA(obj, callback)(*args, **kwargs) - except ObjectDoesNotExist: - log_trace("Removing ticker.") - self._to_remove.append(store_key) - except Exception: - log_trace() - finally: - # make sure to re-store - kwargs["_callback"] = callback - kwargs["_obj"] = obj - # cleanup - we do this here to avoid changing the subscription dict while it loops - self._is_ticking = False - for store_key in self._to_remove: - self.remove(store_key) - for store_key, (args, kwargs) in self._to_add: - self.add(store_key, *args, **kwargs) - self._to_remove = [] - self._to_add = [] - -
[docs] def __init__(self, interval): - """ - Set up the ticker - - Args: - interval (int): The stepping interval. - - """ - self.interval = interval - self.subscriptions = {} - self._is_ticking = False - self._to_remove = [] - self._to_add = [] - # set up a twisted asynchronous repeat call - self.task = ExtendedLoopingCall(self._callback)
- -
[docs] def validate(self, start_delay=None): - """ - Start/stop the task depending on how many subscribers we have - using it. - - Args: - start_delay (int): Time to way before starting. - - """ - subs = self.subscriptions - if self.task.running: - if not subs: - self.task.stop() - elif subs: - self.task.start(self.interval, now=False, start_delay=start_delay)
- -
[docs] def add(self, store_key, *args, **kwargs): - """ - Sign up a subscriber to this ticker. - Args: - store_key (str): Unique storage hash for this ticker subscription. - args (any, optional): Arguments to call the hook method with. - - Keyword Args: - _start_delay (int): If set, this will be - used to delay the start of the trigger instead of - `interval`. - - """ - if self._is_ticking: - # protects the subscription dict from - # updating while it is looping - self._to_add.append((store_key, (args, kwargs))) - else: - start_delay = kwargs.pop("_start_delay", None) - self.subscriptions[store_key] = (args, kwargs) - self.validate(start_delay=start_delay)
- -
[docs] def remove(self, store_key): - """ - Unsubscribe object from this ticker - - Args: - store_key (str): Unique store key. - - """ - if self._is_ticking: - # this protects the subscription dict from - # updating while it is looping - self._to_remove.append(store_key) - else: - self.subscriptions.pop(store_key, False) - self.validate()
- -
[docs] def stop(self): - """ - Kill the Task, regardless of subscriptions. - - """ - self.subscriptions = {} - self.validate()
- - -
[docs]class TickerPool(object): - """ - This maintains a pool of - `evennia.scripts.scripts.ExtendedLoopingCall` tasks for calling - subscribed objects at given times. - - """ - - ticker_class = Ticker - -
[docs] def __init__(self): - """ - Initialize the pool. - - """ - self.tickers = {}
- -
[docs] def add(self, store_key, *args, **kwargs): - """ - Add new ticker subscriber. - - Args: - store_key (str): Unique storage hash. - args (any, optional): Arguments to send to the hook method. - - """ - _, _, _, interval, _, _ = store_key - if not interval: - log_err(_ERROR_ADD_TICKER.format(store_key=store_key)) - return - - if interval not in self.tickers: - self.tickers[interval] = self.ticker_class(interval) - self.tickers[interval].add(store_key, *args, **kwargs)
- -
[docs] def remove(self, store_key): - """ - Remove subscription from pool. - - Args: - store_key (str): Unique storage hash to remove - - """ - _, _, _, interval, _, _ = store_key - if interval in self.tickers: - self.tickers[interval].remove(store_key) - if not self.tickers[interval]: - del self.tickers[interval]
- -
[docs] def stop(self, interval=None): - """ - Stop all scripts in pool. This is done at server reload since - restoring the pool will automatically re-populate the pool. - - Args: - interval (int, optional): Only stop tickers with this - interval. - - """ - if interval and interval in self.tickers: - self.tickers[interval].stop() - else: - for ticker in self.tickers.values(): - ticker.stop()
- - -
[docs]class TickerHandler(object): - """ - The Tickerhandler maintains a pool of tasks for subscribing - objects to various tick rates. The pool maintains creation - instructions and and re-applies them at a server restart. - - """ - - ticker_pool_class = TickerPool - -
[docs] def __init__(self, save_name="ticker_storage"): - """ - Initialize handler - - save_name (str, optional): The name of the ServerConfig - instance to store the handler state persistently. - - """ - self.ticker_storage = {} - self.save_name = save_name - self.ticker_pool = self.ticker_pool_class()
- - def _get_callback(self, callback): - """ - Analyze callback and determine its consituents - - Args: - callback (function or method): This is either a stand-alone - function or class method on a typeclassed entitye (that is, - an entity that can be saved to the database). - Returns: - ret (tuple): This is a tuple of the form `(obj, path, callfunc)`, - where `obj` is the database object the callback is defined on - if it's a method (otherwise `None`) and vice-versa, `path` is - the python-path to the stand-alone function (`None` if a method). - The `callfunc` is either the name of the method to call or the - callable function object itself. - Raises: - TypeError: If the callback is of an unsupported type. - - """ - outobj, outpath, outcallfunc = None, None, None - if callable(callback): - if inspect.ismethod(callback): - outobj = callback.__self__ - outcallfunc = callback.__func__.__name__ - elif inspect.isfunction(callback): - outpath = "%s.%s" % (callback.__module__, callback.__name__) - outcallfunc = callback - else: - raise TypeError(f"{callback} is not a method or function.") - else: - raise TypeError(f"{callback} is not a callable function or method.") - - if outobj and not inherits_from(outobj, "evennia.typeclasses.models.TypedObject"): - raise TypeError( - f"{callback} is a method on a normal object - it must " - "be either a method on a typeclass, or a stand-alone function." - ) - - return outobj, outpath, outcallfunc - - def _store_key(self, obj, path, interval, callfunc, idstring="", persistent=True): - """ - Tries to create a store_key for the object. - - Args: - obj (Object, tuple or None): Subscribing object if any. If a tuple, this is - a packed_obj tuple from dbserialize. - path (str or None): Python-path to callable, if any. - interval (int): Ticker interval. Floats will be converted to - nearest lower integer value. - callfunc (callable or str): This is either the callable function or - the name of the method to call. Note that the callable is never - stored in the key; that is uniquely identified with the python-path. - idstring (str, optional): Additional separator between - different subscription types. - persistent (bool, optional): If this ticker should survive a system - shutdown or not. - - Returns: - store_key (tuple): A tuple `(packed_obj, methodname, outpath, interval, - idstring, persistent)` that uniquely identifies the - ticker. Here, `packed_obj` is the unique string representation of the - object or `None`. The `methodname` is the string name of the method on - `packed_obj` to call, or `None` if `packed_obj` is unset. `path` is - the Python-path to a non-method callable, or `None`. Finally, `interval` - `idstring` and `persistent` are integers, strings and bools respectively. - - """ - if interval < 1: - raise RuntimeError(_ERROR_ADD_TICKER_SUB_SECOND) - - interval = int(interval) - persistent = bool(persistent) - packed_obj = pack_dbobj(obj) - methodname = callfunc if callfunc and isinstance(callfunc, str) else None - outpath = path if path and isinstance(path, str) else None - return (packed_obj, methodname, outpath, interval, idstring, persistent) - -
[docs] def save(self): - """ - Save ticker_storage as a serialized string into a temporary - ServerConf field. Whereas saving is done on the fly, if called - by server when it shuts down, the current timer of each ticker - will be saved so it can start over from that point. - - """ - if self.ticker_storage: - # get the current times so the tickers can be restarted with a delay later - start_delays = dict( - (interval, ticker.task.next_call_time()) - for interval, ticker in self.ticker_pool.tickers.items() - ) - - # remove any subscriptions that lost its object in the interim - to_save = { - store_key: (args, kwargs) - for store_key, (args, kwargs) in self.ticker_storage.items() - if ( - ( - store_key[1] - and ("_obj" in kwargs and kwargs["_obj"].pk) - and hasattr(kwargs["_obj"], store_key[1]) - ) - or store_key[2] # a valid method with existing obj - ) - } # a path given - - # update the timers for the tickers - for store_key, (args, kwargs) in to_save.items(): - interval = store_key[1] - # this is a mutable, so it's updated in-place in ticker_storage - kwargs["_start_delay"] = start_delays.get(interval, None) - ServerConfig.objects.conf(key=self.save_name, value=dbserialize(to_save)) - else: - # make sure we have nothing lingering in the database - ServerConfig.objects.conf(key=self.save_name, delete=True)
- -
[docs] def restore(self, server_reload=True): - """ - Restore ticker_storage from database and re-initialize the - handler from storage. This is triggered by the server at - restart. - - Args: - server_reload (bool, optional): If this is False, it means - the server went through a cold reboot and all - non-persistent tickers must be killed. - - """ - # load stored command instructions and use them to re-initialize handler - restored_tickers = ServerConfig.objects.conf(key=self.save_name) - if restored_tickers: - # the dbunserialize will convert all serialized dbobjs to real objects - - restored_tickers = dbunserialize(restored_tickers) - self.ticker_storage = {} - for store_key, (args, kwargs) in restored_tickers.items(): - try: - # at this point obj is the actual object (or None) due to how - # the dbunserialize works - obj, callfunc, path, interval, idstring, persistent = store_key - if not persistent and not server_reload: - # this ticker will not be restarted - continue - if isinstance(callfunc, str) and not obj: - # methods must have an existing object - continue - # we must rebuild the store_key here since obj must not be - # stored as the object itself for the store_key to be hashable. - store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent) - - if obj and callfunc: - kwargs["_callback"] = callfunc - kwargs["_obj"] = obj - elif path: - modname, varname = path.rsplit(".", 1) - callback = variable_from_module(modname, varname) - kwargs["_callback"] = callback - kwargs["_obj"] = None - else: - # Neither object nor path - discard this ticker - log_err("Tickerhandler: Removing malformed ticker: %s" % str(store_key)) - continue - except Exception: - # this suggests a malformed save or missing objects - log_trace("Tickerhandler: Removing malformed ticker: %s" % str(store_key)) - continue - # if we get here we should create a new ticker - self.ticker_storage[store_key] = (args, kwargs) - self.ticker_pool.add(store_key, *args, **kwargs)
- -
[docs] def add(self, interval=60, callback=None, idstring="", persistent=True, *args, **kwargs): - """ - Add subscription to tickerhandler - - Args: - interval (int, optional): Interval in seconds between calling - `callable(*args, **kwargs)` - callable (callable function or method, optional): This - should either be a stand-alone function or a method on a - typeclassed entity (that is, one that can be saved to the - database). - idstring (str, optional): Identifier for separating - this ticker-subscription from others with the same - interval. Allows for managing multiple calls with - the same time interval and callback. - persistent (bool, optional): A ticker will always survive - a server reload. If this is unset, the ticker will be - deleted by a server shutdown. - args, kwargs (optional): These will be passed into the - callback every time it is called. This must be data possible - to pickle! - - Returns: - store_key (tuple): The immutable store-key for this ticker. This can - be stored and passed into `.remove(store_key=store_key)` later to - easily stop this ticker later. - - Notes: - The callback will be identified by type and stored either as - as combination of serialized database object + methodname or - as a python-path to the module + funcname. These strings will - be combined iwth `interval` and `idstring` to define a - unique storage key for saving. These must thus all be supplied - when wanting to modify/remove the ticker later. - - """ - obj, path, callfunc = self._get_callback(callback) - store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent) - kwargs["_obj"] = obj - kwargs["_callback"] = callfunc # either method-name or callable - self.ticker_storage[store_key] = (args, kwargs) - self.ticker_pool.add(store_key, *args, **kwargs) - self.save() - return store_key
- -
[docs] def remove(self, interval=60, callback=None, idstring="", persistent=True, store_key=None): - """ - Remove ticker subscription from handler. - - Args: - interval (int, optional): Interval of ticker to remove. - callback (callable function or method): Either a function or - the method of a typeclassed object. - idstring (str, optional): Identifier id of ticker to remove. - persistent (bool, optional): Whether this ticker is persistent or not. - store_key (str, optional): If given, all other kwargs are ignored and only - this is used to identify the ticker. - - Raises: - KeyError: If no matching ticker was found to remove. - - Notes: - The store-key is normally built from the interval/callback/idstring/persistent values; - but if the `store_key` is explicitly given, this is used instead. - - """ - if isinstance(callback, int): - raise RuntimeError( - "TICKER_HANDLER.remove has changed: " - "the interval is now the first argument, callback the second." - ) - if not store_key: - obj, path, callfunc = self._get_callback(callback) - store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent) - to_remove = self.ticker_storage.pop(store_key, None) - if to_remove: - self.ticker_pool.remove(store_key) - self.save() - else: - raise KeyError(f"No Ticker was found matching the store-key {store_key}.")
- -
[docs] def clear(self, interval=None): - """ - Stop/remove tickers from handler. - - Args: - interval (int): Only stop tickers with this interval. - - Notes: - This is the only supported way to kill tickers related to - non-db objects. - - """ - self.ticker_pool.stop(interval) - if interval: - self.ticker_storage = dict( - (store_key, store_key) - for store_key in self.ticker_storage - if store_key[1] != interval - ) - else: - self.ticker_storage = {} - self.save()
- -
[docs] def all(self, interval=None): - """ - Get all subscriptions. - - Args: - interval (int): Limit match to tickers with this interval. - - Returns: - tickers (list): If `interval` was given, this is a list of - tickers using that interval. - tickerpool_layout (dict): If `interval` was *not* given, - this is a dict {interval1: [ticker1, ticker2, ...], ...} - - """ - if interval is None: - # return dict of all, ordered by interval - return dict( - (interval, ticker.subscriptions) - for interval, ticker in self.ticker_pool.tickers.items() - ) - else: - # get individual interval - ticker = self.ticker_pool.tickers.get(interval, None) - if ticker: - return {interval: ticker.subscriptions} - return None
- -
[docs] def all_display(self): - """ - Get all tickers on an easily displayable form. - - Returns: - tickers (dict): A list of all storekeys - - """ - store_keys = [] - for ticker in self.ticker_pool.tickers.values(): - for ( - (objtup, callfunc, path, interval, idstring, persistent), - (args, kwargs), - ) in ticker.subscriptions.items(): - store_keys.append( - (kwargs.get("_obj", None), callfunc, path, interval, idstring, persistent) - ) - return store_keys
- - -# main tickerhandler -TICKER_HANDLER = TickerHandler() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/admin.html b/docs/0.9.5/_modules/evennia/server/admin.html deleted file mode 100644 index cd7e0218d5..0000000000 --- a/docs/0.9.5/_modules/evennia/server/admin.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - evennia.server.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.admin

-#
-# This sets up how models are displayed
-# in the web admin interface.
-#
-
-from django.contrib import admin
-from evennia.server.models import ServerConfig
-
-
-
[docs]class ServerConfigAdmin(admin.ModelAdmin): - """ - Custom admin for server configs - - """ - - list_display = ("db_key", "db_value") - list_display_links = ("db_key",) - ordering = ["db_key", "db_value"] - search_fields = ["db_key"] - save_as = True - save_on_top = True - list_select_related = True
- - -admin.site.register(ServerConfig, ServerConfigAdmin) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/amp_client.html b/docs/0.9.5/_modules/evennia/server/amp_client.html deleted file mode 100644 index d65470c781..0000000000 --- a/docs/0.9.5/_modules/evennia/server/amp_client.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - evennia.server.amp_client — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.amp_client

-"""
-The Evennia Server service acts as an AMP-client when talking to the
-Portal. This module sets up the Client-side communication.
-
-"""
-
-import os
-from evennia.server.portal import amp
-from twisted.internet import protocol
-from evennia.utils import logger
-
-
-
[docs]class AMPClientFactory(protocol.ReconnectingClientFactory): - """ - This factory creates an instance of an AMP client connection. This handles communication from - the be the Evennia 'Server' service to the 'Portal'. The client will try to auto-reconnect on a - connection error. - - """ - - # Initial reconnect delay in seconds. - initialDelay = 1 - factor = 1.5 - maxDelay = 1 - noisy = False - -
[docs] def __init__(self, server): - """ - Initializes the client factory. - - Args: - server (server): server instance. - - """ - self.server = server - self.protocol = AMPServerClientProtocol - self.maxDelay = 10 - # not really used unless connecting to multiple servers, but - # avoids having to check for its existence on the protocol - self.broadcasts = []
- -
[docs] def startedConnecting(self, connector): - """ - Called when starting to try to connect to the Portal AMP server. - - Args: - connector (Connector): Twisted Connector instance representing - this connection. - - """ - pass
- -
[docs] def buildProtocol(self, addr): - """ - Creates an AMPProtocol instance when connecting to the AMP server. - - Args: - addr (str): Connection address. Not used. - - """ - self.resetDelay() - self.server.amp_protocol = AMPServerClientProtocol() - self.server.amp_protocol.factory = self - return self.server.amp_protocol
- -
[docs] def clientConnectionLost(self, connector, reason): - """ - Called when the AMP connection to the MUD server is lost. - - Args: - connector (Connector): Twisted Connector instance representing - this connection. - reason (str): Eventual text describing why connection was lost. - - """ - logger.log_info("Server disconnected from the portal.") - protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
- -
[docs] def clientConnectionFailed(self, connector, reason): - """ - Called when an AMP connection attempt to the MUD server fails. - - Args: - connector (Connector): Twisted Connector instance representing - this connection. - reason (str): Eventual text describing why connection failed. - - """ - logger.log_msg("Attempting to reconnect to Portal ...") - protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
- - -
[docs]class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol): - """ - This protocol describes the Server service (acting as an AMP-client)'s communication with the - Portal (which acts as the AMP-server) - - """ - - # sending AMP data - -
[docs] def connectionMade(self): - """ - Called when a new connection is established. - - """ - # print("AMPClient new connection {}".format(self)) - info_dict = self.factory.server.get_info_dict() - super(AMPServerClientProtocol, self).connectionMade() - # first thing we do is to request the Portal to sync all sessions - # back with the Server side. We also need the startup mode (reload, reset, shutdown) - self.send_AdminServer2Portal( - amp.DUMMYSESSION, operation=amp.PSYNC, spid=os.getpid(), info_dict=info_dict - ) - # run the intial setup if needed - self.factory.server.run_initial_setup()
- -
[docs] def data_to_portal(self, command, sessid, **kwargs): - """ - Send data across the wire to the Portal - - Args: - command (AMP Command): A protocol send command. - sessid (int): A unique Session id. - kwargs (any): Any data to pickle into the command. - - Returns: - deferred (deferred or None): A deferred with an errback. - - Notes: - Data will be sent across the wire pickled as a tuple - (sessid, kwargs). - - """ - # print("server data_to_portal: {}, {}, {}".format(command, sessid, kwargs)) - return self.callRemote(command, packed_data=amp.dumps((sessid, kwargs))).addErrback( - self.errback, command.key - )
- -
[docs] def send_MsgServer2Portal(self, session, **kwargs): - """ - Access method - executed on the Server for sending data - to Portal. - - Args: - session (Session): Unique Session. - kwargs (any, optiona): Extra data. - - """ - return self.data_to_portal(amp.MsgServer2Portal, session.sessid, **kwargs)
- -
[docs] def send_AdminServer2Portal(self, session, operation="", **kwargs): - """ - Administrative access method called by the Server to send an - instruction to the Portal. - - Args: - session (Session): Session. - operation (char, optional): Identifier for the server - operation, as defined by the global variables in - `evennia/server/amp.py`. - kwargs (dict, optional): Data going into the adminstrative. - - """ - return self.data_to_portal( - amp.AdminServer2Portal, session.sessid, operation=operation, **kwargs - )
- - # receiving AMP data - -
[docs] @amp.MsgStatus.responder - def server_receive_status(self, question): - return {"status": "OK"}
- - @amp.MsgPortal2Server.responder - @amp.catch_traceback - def server_receive_msgportal2server(self, packed_data): - """ - Receives message arriving to server. This method is executed - on the Server. - - Args: - packed_data (str): Data to receive (a pickled tuple (sessid,kwargs)) - - """ - sessid, kwargs = self.data_in(packed_data) - session = self.factory.server.sessions.get(sessid, None) - if session: - self.factory.server.sessions.data_in(session, **kwargs) - return {} - - @amp.AdminPortal2Server.responder - @amp.catch_traceback - def server_receive_adminportal2server(self, packed_data): - """ - Receives admin data from the Portal (allows the portal to - perform admin operations on the server). This is executed on - the Server. - - Args: - packed_data (str): Incoming, pickled data. - - """ - sessid, kwargs = self.data_in(packed_data) - operation = kwargs.pop("operation", "") - server_sessionhandler = self.factory.server.sessions - - if operation == amp.PCONN: # portal_session_connect - # create a new session and sync it - server_sessionhandler.portal_connect(kwargs.get("sessiondata")) - - elif operation == amp.PCONNSYNC: # portal_session_sync - server_sessionhandler.portal_session_sync(kwargs.get("sessiondata")) - - elif operation == amp.PDISCONN: # portal_session_disconnect - # session closed from portal sid - session = server_sessionhandler.get(sessid) - if session: - server_sessionhandler.portal_disconnect(session) - - elif operation == amp.PDISCONNALL: # portal_disconnect_all - # portal orders all sessions to close - server_sessionhandler.portal_disconnect_all() - - elif operation == amp.PSYNC: # portal_session_sync - # force a resync of sessions from the portal side. This happens on - # first server-connect. - server_restart_mode = kwargs.get("server_restart_mode", "shutdown") - self.factory.server.run_init_hooks(server_restart_mode) - server_sessionhandler.portal_sessions_sync(kwargs.get("sessiondata")) - server_sessionhandler.portal_start_time = kwargs.get("portal_start_time") - - elif operation == amp.SRELOAD: # server reload - # shut down in reload mode - server_sessionhandler.all_sessions_portal_sync() - server_sessionhandler.server.shutdown(mode="reload") - - elif operation == amp.SRESET: - # shut down in reset mode - server_sessionhandler.all_sessions_portal_sync() - server_sessionhandler.server.shutdown(mode="reset") - - elif operation == amp.SSHUTD: # server shutdown - # shutdown in stop mode - server_sessionhandler.server.shutdown(mode="shutdown") - - else: - raise Exception("operation %(op)s not recognized." % {"op": operation}) - - return {}
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/connection_wizard.html b/docs/0.9.5/_modules/evennia/server/connection_wizard.html deleted file mode 100644 index 39459c435e..0000000000 --- a/docs/0.9.5/_modules/evennia/server/connection_wizard.html +++ /dev/null @@ -1,627 +0,0 @@ - - - - - - - - evennia.server.connection_wizard — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.connection_wizard

-"""
-Link Evennia to external resources (wizard plugin for evennia_launcher)
-
-"""
-import sys
-from os import path
-import pprint
-from django.conf import settings
-from evennia.utils.utils import list_to_string, mod_import
-
-
-
[docs]class ConnectionWizard(object): -
[docs] def __init__(self): - self.data = {} - self.prev_node = None
- -
[docs] def display(self, text): - "Show text" - print(text)
- -
[docs] def ask_continue(self): - "'Press return to continue'-prompt" - input(" (Press return to continue)")
- -
[docs] def ask_node(self, options, prompt="Enter choice: ", default=None): - """ - Retrieve options and jump to different menu nodes - - Args: - options (dict): Node options on the form {key: (desc, callback), } - prompt (str, optional): Question to ask - default (str, optional): Default value to use if user hits return. - - """ - - opt_txt = "\n".join(f" {key}: {desc}" for key, (desc, _, _) in options.items()) - self.display(opt_txt + "\n") - - while True: - resp = input(prompt).strip() - - if not resp: - if default: - resp = str(default) - - if resp.lower() in options: - # self.display(f" Selected '{resp}'.") - desc, callback, kwargs = options[resp.lower()] - callback(self, **kwargs) - elif resp.lower() in ("quit", "q"): - sys.exit() - elif resp: - # input, but nothing was recognized - self.display(" Choose one of: {}".format(list_to_string(list(options))))
- -
[docs] def ask_yesno(self, prompt, default="yes"): - """ - Ask a yes/no question inline. - - Keyword Args: - prompt (str): The prompt to ask. - default (str): "yes" or "no", used if pressing return. - Returns: - reply (str): Either 'yes' or 'no'. - - """ - prompt = prompt + (" [Y]/N? " if default == "yes" else " Y/[N]? ") - - while True: - resp = input(prompt).lstrip().lower() - if not resp: - resp = default.lower() - if resp in ("yes", "y"): - self.display(" Answered Yes.") - return "yes" - elif resp in ("no", "n"): - self.display(" Answered No.") - return "no" - elif resp.lower() in ("quit", "q"): - sys.exit()
- -
[docs] def ask_choice(self, prompt=" > ", options=None, default=None): - """ - Ask multiple-choice question, get response inline. - - Keyword Args: - prompt (str): Input prompt. - options (list): List of options. Will be indexable by sequence number 1... - default (int): The list index+1 of the default choice, if any - Returns: - reply (str): The answered reply. - - """ - opt_txt = "\n".join(f" {ind + 1}: {desc}" for ind, desc in enumerate(options)) - self.display(opt_txt + "\n") - - while True: - resp = input(prompt).strip() - - if not resp: - if default: - return options[int(default)] - if resp.lower() in ("quit", "q"): - sys.exit() - if resp.isdigit(): - resp = int(resp) - 1 - if 0 <= resp < len(options): - selection = options[resp] - self.display(f" Selected '{selection}'.") - return selection - self.display(" Select one of the given options.")
- -
[docs] def ask_input(self, prompt=" > ", default=None, validator=None): - """ - Get arbitrary input inline. - - Keyword Args: - prompt (str): The display prompt. - default (str): If empty input, use this. - validator (callable): If given, the input will be passed - into this callable. It should return True unless validation - fails (and is expected to echo why if so). - - Returns: - inp (str): The input given, or default. - - """ - while True: - resp = input(prompt).strip() - - if not resp and default: - resp = str(default) - - if resp.lower() in ("q", "quit"): - sys.exit() - - if resp.lower() == "none": - resp = "" - - if validator and not validator(resp): - continue - - ok = input("\n Leave blank? [Y]/N: ") - if ok.lower() in ("n", "no"): - continue - elif ok.lower() in ("q", "quit"): - sys.exit() - return resp - - if validator and not validator(resp): - continue - - self.display(resp) - ok = input("\n Is this correct? [Y]/N: ") - if ok.lower() in ("n", "no"): - continue - elif ok.lower() in ("q", "quit"): - sys.exit() - return resp
- - -
[docs]def node_start(wizard): - text = """ - This wizard helps to attach your Evennia server to external networks. It - will save to a file `server/conf/connection_settings.py` that will be - imported from the bottom of your game settings file. Once generated you can - also modify that file directly. - - Make sure you have at least started the game once before continuing! - - Use `quit` at any time to abort and throw away unsaved changes. - """ - options = { - "1": ( - "Add your game to the Evennia game index (also for closed-dev games)", - node_game_index_start, - {}, - ), - "2": ("MSSP setup (for mud-list crawlers)", node_mssp_start, {}), - # "3": ("Add Grapevine listing", - # node_grapevine_start, {}), - # "4": ("Add IRC link", - # "node_irc_start", {}), - # "5" ("Add RSS feed", - # "node_rss_start", {}), - "s": ("View and (optionally) Save created settings", node_view_and_apply_settings, {}), - "q": ("Quit", lambda *args: sys.exit(), {}), - } - - wizard.display(text) - wizard.ask_node(options)
- - -# Evennia game index - - -
[docs]def node_game_index_start(wizard, **kwargs): - text = """ - The Evennia game index (http://games.evennia.com) lists both active Evennia - games as well as games in various stages of development. - - You can put up your game in the index also if you are not (yet) open for - players. If so, put 'None' for the connection details - you are just telling - us that you are out there, making us excited about your upcoming game! - - Please check the listing online first to see that your exact game name is - not colliding with an existing game-name in the list (be nice!). - """ - - wizard.display(text) - if wizard.ask_yesno("Continue adding/editing an Index entry?") == "yes": - node_game_index_fields(wizard) - else: - node_start(wizard)
- - -
[docs]def node_game_index_fields(wizard, status=None): - - # reset the listing if needed - if not hasattr(wizard, "game_index_listing"): - wizard.game_index_listing = settings.GAME_INDEX_LISTING - - # game status - - status_default = wizard.game_index_listing["game_status"] - text = f""" - What is the status of your game? - - pre-alpha: a game in its very early stages, mostly unfinished or unstarted - - alpha: a working concept, probably lots of bugs and incomplete features - - beta: a working game, but expect bugs and changing features - - launched: a full, working game (that may still be expanded upon and improved later) - - Current value (return to keep): - {status_default} - """ - - options = ["pre-alpha", "alpha", "beta", "launched"] - - wizard.display(text) - wizard.game_index_listing["game_status"] = wizard.ask_choice("Select one: ", options) - - # game name - - name_default = settings.SERVERNAME - text = f""" - Your game's name should usually be the same as `settings.SERVERNAME`, but - you can set it to something else here if you want. - - Current value: - {name_default} - """ - - def name_validator(inp): - tmax = 80 - tlen = len(inp) - if tlen > tmax: - print(f"The name must be shorter than {tmax} characters (was {tlen}).") - wizard.ask_continue() - return False - return True - - wizard.display(text) - wizard.game_index_listing["game_name"] = wizard.ask_input( - default=name_default, validator=name_validator - ) - - # short desc - - sdesc_default = wizard.game_index_listing.get("short_description", None) - - text = f""" - Enter a short description of your game. Make it snappy and interesting! - This should be at most one or two sentences (255 characters) to display by - '{settings.SERVERNAME}' in the main game list. Line breaks will be ignored. - - Current value: - {sdesc_default} - """ - - def sdesc_validator(inp): - tmax = 255 - tlen = len(inp) - if tlen > tmax: - print(f"The short desc must be shorter than {tmax} characters (was {tlen}).") - wizard.ask_continue() - return False - return True - - wizard.display(text) - wizard.game_index_listing["short_description"] = wizard.ask_input( - default=sdesc_default, validator=sdesc_validator - ) - - # long desc - - long_default = wizard.game_index_listing.get("long_description", None) - - text = f""" - Enter a longer, full-length description. This will be shown when clicking - on your game's listing. You can use \\n to create line breaks and may use - Markdown formatting like *bold*, _italic_, [linkname](http://link) etc. - - Current value: - {long_default} - """ - - wizard.display(text) - wizard.game_index_listing["long_description"] = wizard.ask_input(default=long_default) - - # listing contact - - listing_default = wizard.game_index_listing.get("listing_contact", None) - text = f""" - Enter a listing email-contact. This will not be visible in the listing, but - allows us to get in touch with you should there be some listing issue (like - a name collision) or some bug with the listing (us actually using this is - likely to be somewhere between super-rarely and never). - - Current value: - {listing_default} - """ - - def contact_validator(inp): - if not inp or "@" not in inp: - print("This should be an email and cannot be blank.") - wizard.ask_continue() - return False - return True - - wizard.display(text) - wizard.game_index_listing["listing_contact"] = wizard.ask_input( - default=listing_default, validator=contact_validator - ) - - # telnet hostname - - hostname_default = wizard.game_index_listing.get("telnet_hostname", None) - text = f""" - Enter the hostname to which third-party telnet mud clients can connect to - your game. This would be the name of the server your game is hosted on, - like `coolgame.games.com`, or `mygreatgame.se`. - - Write 'None' if you are not offering public telnet connections at this time. - - Current value: - {hostname_default} - """ - - wizard.display(text) - wizard.game_index_listing["telnet_hostname"] = wizard.ask_input(default=hostname_default) - - # telnet port - - port_default = wizard.game_index_listing.get("telnet_port", None) - text = f""" - Enter the main telnet port. The Evennia default is 4000. You can change - this with the TELNET_PORTS server setting. - - Write 'None' if you are not offering public telnet connections at this time. - - Current value: - {port_default} - """ - - wizard.display(text) - wizard.game_index_listing["telnet_port"] = wizard.ask_input(default=port_default) - - # website - - website_default = wizard.game_index_listing.get("game_website", None) - text = f""" - Evennia is its own web server and runs your game's website. Enter the - URL of the website here, like http://yourwebsite.com, here. - - Write 'None' if you are not offering a publicly visible website at this time. - - Current value: - {website_default} - """ - - wizard.display(text) - wizard.game_index_listing["game_website"] = wizard.ask_input(default=website_default) - - # webclient - - webclient_default = wizard.game_index_listing.get("web_client_url", None) - text = f""" - Evennia offers its own native webclient. Normally it will be found from the - game homepage at something like http://yourwebsite.com/webclient. Enter - your specific URL here (when clicking this link you should launch into the - web client) - - Write 'None' if you don't want to list a publicly accessible webclient. - - Current value: - {webclient_default} - """ - - wizard.display(text) - wizard.game_index_listing["web_client_url"] = wizard.ask_input(default=webclient_default) - - if not ( - wizard.game_index_listing.get("web_client_url") - or (wizard.game_index_listing.get("telnet_host")) - ): - wizard.display( - "\nNote: You have not specified any connection options. This means " - "your game \nwill be marked as being in 'closed development' in " - "the index." - ) - - wizard.display("\nDon't forget to inspect and save your changes.") - - node_start(wizard)
- - -# MSSP - - -
[docs]def node_mssp_start(wizard): - - mssp_module = mod_import(settings.MSSP_META_MODULE or "server.conf.mssp") - try: - filename = mssp_module.__file__ - except AttributeError: - filename = "server/conf/mssp.py" - - text = f""" - MSSP (Mud Server Status Protocol) has a vast amount of options so it must - be modified outside this wizard by directly editing its config file here: - - '{filename}' - - MSSP allows traditional online MUD-listing sites/crawlers to continuously - monitor your game and list information about it. Some of this, like active - player-count, Evennia will automatically add for you, whereas most fields - you need to set manually. - - To use MSSP you should generally have a publicly open game that external - players can connect to. You also need to register at a MUD listing site to - tell them to crawl your game. - """ - - wizard.display(text) - wizard.ask_continue() - node_start(wizard)
- - -# Admin - - -def _save_changes(wizard): - """ - Perform the save - """ - - # add import statement to settings file - import_stanza = "from .connection_settings import *" - setting_module = mod_import("server.conf.settings") - with open(setting_module.__file__, "r+") as f: - txt = f.read() # moves pointer to end of file - if import_stanza not in txt: - # add to the end of the file - f.write( - "\n\n" - "try:\n" - " # Created by the `evennia connections` wizard\n" - f" {import_stanza}\n" - "except ImportError:\n" - " pass" - ) - - connect_settings_file = path.join(settings.GAME_DIR, "server", "conf", "connection_settings.py") - with open(connect_settings_file, "w") as f: - f.write( - "# This file is auto-generated by the `evennia connections` wizard.\n" - "# Don't edit manually, your changes will be overwritten.\n\n" - ) - - f.write(wizard.save_output) - wizard.display(f"saving to {connect_settings_file} ...") - - -
[docs]def node_view_and_apply_settings(wizard): - """ - Inspect and save the data gathered in the other nodes - - """ - pp = pprint.PrettyPrinter(indent=4) - saves = False - - # game index - game_index_save_text = "" - game_index_listing = ( - wizard.game_index_listing if hasattr(wizard, "game_index_listing") else None - ) - if not game_index_listing and settings.GAME_INDEX_ENABLED: - game_index_listing = settings.GAME_INDEX_LISTING - if game_index_listing: - game_index_save_text = ( - "GAME_INDEX_ENABLED = True\n" - "GAME_INDEX_LISTING = \\\n" + pp.pformat(game_index_listing) - ) - saves = True - else: - game_index_save_text = "# No Game Index settings found." - - # potentially add other wizards in the future - text = game_index_save_text - - wizard.display(f"Settings to save:\n\n{text}") - - if saves: - if wizard.ask_yesno("\nDo you want to save these settings?") == "yes": - wizard.save_output = text - _save_changes(wizard) - wizard.display("... saved!\nThe changes will apply after you reload your server.") - else: - wizard.display("... cancelled.") - wizard.ask_continue() - node_start(wizard)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/deprecations.html b/docs/0.9.5/_modules/evennia/server/deprecations.html deleted file mode 100644 index e4d3ad1b76..0000000000 --- a/docs/0.9.5/_modules/evennia/server/deprecations.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - evennia.server.deprecations — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.deprecations

-"""
-This module contains historical deprecations that the Evennia launcher
-checks for.
-
-These all print to the terminal.
-"""
-
-
-
[docs]def check_errors(settings): - """ - Check for deprecations that are critical errors and should stop - the launcher. - - Args: - settings (Settings): The Django settings file - - Raises: - DeprecationWarning if a critical deprecation is found. - - """ - deprstring = ( - "settings.%s should be renamed to %s. If defaults are used, " - "their path/classname must be updated " - "(see evennia/settings_default.py)." - ) - if hasattr(settings, "CMDSET_DEFAULT"): - raise DeprecationWarning(deprstring % ("CMDSET_DEFAULT", "CMDSET_CHARACTER")) - if hasattr(settings, "CMDSET_OOC"): - raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_ACCOUNT")) - if settings.WEBSERVER_ENABLED and not isinstance(settings.WEBSERVER_PORTS[0], tuple): - raise DeprecationWarning( - "settings.WEBSERVER_PORTS must be on the form " "[(proxyport, serverport), ...]" - ) - if hasattr(settings, "BASE_COMM_TYPECLASS"): - raise DeprecationWarning(deprstring % ("BASE_COMM_TYPECLASS", "BASE_CHANNEL_TYPECLASS")) - if hasattr(settings, "COMM_TYPECLASS_PATHS"): - raise DeprecationWarning(deprstring % ("COMM_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS")) - if hasattr(settings, "CHARACTER_DEFAULT_HOME"): - raise DeprecationWarning( - "settings.CHARACTER_DEFAULT_HOME should be renamed to " - "DEFAULT_HOME. See also settings.START_LOCATION " - "(see evennia/settings_default.py)." - ) - deprstring = ( - "settings.%s is now merged into settings.TYPECLASS_PATHS. " "Update your settings file." - ) - if hasattr(settings, "OBJECT_TYPECLASS_PATHS"): - raise DeprecationWarning(deprstring % "OBJECT_TYPECLASS_PATHS") - if hasattr(settings, "SCRIPT_TYPECLASS_PATHS"): - raise DeprecationWarning(deprstring % "SCRIPT_TYPECLASS_PATHS") - if hasattr(settings, "ACCOUNT_TYPECLASS_PATHS"): - raise DeprecationWarning(deprstring % "ACCOUNT_TYPECLASS_PATHS") - if hasattr(settings, "CHANNEL_TYPECLASS_PATHS"): - raise DeprecationWarning(deprstring % "CHANNEL_TYPECLASS_PATHS") - if hasattr(settings, "SEARCH_MULTIMATCH_SEPARATOR"): - raise DeprecationWarning( - "settings.SEARCH_MULTIMATCH_SEPARATOR was replaced by " - "SEARCH_MULTIMATCH_REGEX and SEARCH_MULTIMATCH_TEMPLATE. " - "Update your settings file (see evennia/settings_default.py " - "for more info)." - ) - - gametime_deprecation = ( - "The settings TIME_SEC_PER_MIN, TIME_MIN_PER_HOUR," - "TIME_HOUR_PER_DAY, TIME_DAY_PER_WEEK, \n" - "TIME_WEEK_PER_MONTH and TIME_MONTH_PER_YEAR " - "are no longer supported. Remove them from your " - "settings file to continue.\nIf you want to use " - "and manipulate these time units, the tools from utils.gametime " - "are now found in contrib/convert_gametime.py instead." - ) - if any( - hasattr(settings, value) - for value in ( - "TIME_SEC_PER_MIN", - "TIME_MIN_PER_HOUR", - "TIME_HOUR_PER_DAY", - "TIME_DAY_PER_WEEK", - "TIME_WEEK_PER_MONTH", - "TIME_MONTH_PER_YEAR", - ) - ): - raise DeprecationWarning(gametime_deprecation) - - game_directory_deprecation = ( - "The setting GAME_DIRECTORY_LISTING was removed. It must be " - "renamed to GAME_INDEX_LISTING instead." - ) - if hasattr(settings, "GAME_DIRECTORY_LISTING"): - raise DeprecationWarning(game_directory_deprecation) - - chan_connectinfo = settings.CHANNEL_CONNECTINFO - if chan_connectinfo is not None and not isinstance(chan_connectinfo, dict): - raise DeprecationWarning( - "settings.CHANNEL_CONNECTINFO has changed. It " - "must now be either None or a dict " - "specifying the properties of the channel to create." - ) - if hasattr(settings, "CYCLE_LOGFILES"): - raise DeprecationWarning( - "settings.CYCLE_LOGFILES is unused and should be removed. " - "Use PORTAL/SERVER_LOG_DAY_ROTATION and PORTAL/SERVER_LOG_MAX_SIZE " - "to control log cycling." - )
- - -
[docs]def check_warnings(settings): - """ - Check conditions and deprecations that should produce warnings but which - does not stop launch. - """ - if settings.DEBUG: - print(" [Devel: settings.DEBUG is True. Important to turn off in production.]") - if settings.IN_GAME_ERRORS: - print(" [Devel: settings.IN_GAME_ERRORS is True. Turn off in production.]") - if settings.ALLOWED_HOSTS == ["*"]: - print(" [Devel: settings.ALLOWED_HOSTS set to '*' (all). Limit in production.]") - for dbentry in settings.DATABASES.values(): - if "psycopg" in dbentry.get("ENGINE", ""): - print( - 'Deprecation: postgresql_psycopg2 backend is deprecated". ' - "Switch settings.DATABASES to use " - '"ENGINE": "django.db.backends.postgresql instead"' - )
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/evennia_launcher.html b/docs/0.9.5/_modules/evennia/server/evennia_launcher.html deleted file mode 100644 index 81ff3e873b..0000000000 --- a/docs/0.9.5/_modules/evennia/server/evennia_launcher.html +++ /dev/null @@ -1,2400 +0,0 @@ - - - - - - - - evennia.server.evennia_launcher — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.evennia_launcher

-#!/usr/bin/python
-"""
-Evennia launcher program
-
-This is the start point for running Evennia.
-
-Sets the appropriate environmental variables for managing an Evennia game. It will start and connect
-to the Portal, through which the Server is also controlled. This pprogram
-
-Run the script with the -h flag to see usage information.
-
-"""
-
-import os
-import sys
-import re
-import signal
-import shutil
-import importlib
-import pickle
-from distutils.version import LooseVersion
-from argparse import ArgumentParser
-import argparse
-from subprocess import Popen, check_output, call, CalledProcessError, STDOUT
-
-from twisted.protocols import amp
-from twisted.internet import reactor, endpoints
-import django
-from django.core.management import execute_from_command_line
-from django.db.utils import ProgrammingError
-
-# Signal processing
-SIG = signal.SIGINT
-CTRL_C_EVENT = 0  # Windows SIGINT-like signal
-
-# Set up the main python paths to Evennia
-EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-
-import evennia  # noqa
-
-EVENNIA_LIB = os.path.join(os.path.dirname(os.path.abspath(evennia.__file__)))
-EVENNIA_SERVER = os.path.join(EVENNIA_LIB, "server")
-EVENNIA_TEMPLATE = os.path.join(EVENNIA_LIB, "game_template")
-EVENNIA_PROFILING = os.path.join(EVENNIA_SERVER, "profiling")
-EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_PROFILING, "dummyrunner.py")
-
-TWISTED_BINARY = "twistd"
-
-# Game directory structure
-SETTINGFILE = "settings.py"
-SERVERDIR = "server"
-CONFDIR = os.path.join(SERVERDIR, "conf")
-SETTINGS_PATH = os.path.join(CONFDIR, SETTINGFILE)
-SETTINGS_DOTPATH = "server.conf.settings"
-CURRENT_DIR = os.getcwd()
-GAMEDIR = CURRENT_DIR
-
-# Operational setup
-
-SERVER_LOGFILE = None
-PORTAL_LOGFILE = None
-HTTP_LOGFILE = None
-
-SERVER_PIDFILE = None
-PORTAL_PIDFILE = None
-
-SERVER_PY_FILE = None
-PORTAL_PY_FILE = None
-
-SPROFILER_LOGFILE = None
-PPROFILER_LOGFILE = None
-
-TEST_MODE = False
-ENFORCED_SETTING = False
-
-REACTOR_RUN = False
-NO_REACTOR_STOP = False
-
-# communication constants
-
-AMP_PORT = None
-AMP_HOST = None
-AMP_INTERFACE = None
-AMP_CONNECTION = None
-
-SRELOAD = chr(14)  # server reloading (have portal start a new server)
-SSTART = chr(15)  # server start
-PSHUTD = chr(16)  # portal (+server) shutdown
-SSHUTD = chr(17)  # server-only shutdown
-PSTATUS = chr(18)  # ping server or portal status
-SRESET = chr(19)  # shutdown server in reset mode
-
-# requirements
-PYTHON_MIN = "3.7"
-TWISTED_MIN = "18.0.0"
-DJANGO_MIN = "3.2"
-DJANGO_LT = "3.3"
-
-try:
-    sys.path[1] = EVENNIA_ROOT
-except IndexError:
-    sys.path.append(EVENNIA_ROOT)
-
-# ------------------------------------------------------------
-#
-# Messages
-#
-# ------------------------------------------------------------
-
-CREATED_NEW_GAMEDIR = """
-    Welcome to Evennia!
-    Created a new Evennia game directory '{gamedir}'.
-
-    You can now optionally edit your new settings file
-    at {settings_path}. If you don't, the defaults
-    will work out of the box. When ready to continue, 'cd' to your
-    game directory and run:
-
-       evennia migrate
-
-    This initializes the database. To start the server for the first
-    time, run:
-
-       evennia start
-
-    Make sure to create a superuser when asked for it (the email is optional)
-    You should now be able to connect to your server on 'localhost', port 4000
-    using a telnet/mud client or http://localhost:4001 using your web browser.
-    If things don't work, check the log with `evennia --log`. Also make sure
-    ports are open.
-
-    (Finally, why not run `evennia connections` and make the world aware of
-    your new Evennia project!)
-    """
-
-ERROR_INPUT = """
-    Command
-      {args} {kwargs}
-    raised an error: '{traceback}'.
-"""
-
-ERROR_NO_ALT_GAMEDIR = """
-    The path '{gamedir}' could not be found.
-"""
-
-ERROR_NO_GAMEDIR = """
-    ERROR: No Evennia settings file was found. Evennia looks for the
-    file in your game directory as ./server/conf/settings.py.
-
-    You must run this command from somewhere inside a valid game
-    directory first created with
-
-        evennia --init mygamename
-
-    If you are in a game directory but is missing a settings.py file,
-    it may be because you have git-cloned an existing game directory.
-    The settings.py file is not cloned by git (it's in .gitignore)
-    since it can contain sensitive and/or server-specific information.
-    You can create a new, empty settings file with
-
-        evennia --initsettings
-
-    If cloning the settings file is not a problem you could manually
-    copy over the old settings file or remove its entry in .gitignore
-
-    """
-
-WARNING_MOVING_SUPERUSER = """
-    WARNING: Evennia expects an Account superuser with id=1. No such
-    Account was found. However, another superuser ('{other_key}',
-    id={other_id}) was found in the database. If you just created this
-    superuser and still see this text it is probably due to the
-    database being flushed recently - in this case the database's
-    internal auto-counter might just start from some value higher than
-    one.
-
-    We will fix this by assigning the id 1 to Account '{other_key}'.
-    Please confirm this is acceptable before continuing.
-    """
-
-WARNING_RUNSERVER = """
-    WARNING: There is no need to run the Django development
-    webserver to test out Evennia web features (the web client
-    will in fact not work since the Django test server knows
-    nothing about MUDs).  Instead, just start Evennia with the
-    webserver component active (this is the default).
-    """
-
-ERROR_SETTINGS = """
-    ERROR: There was an error importing Evennia's config file
-    {settingspath}.
-    There is usually one of three reasons for this:
-        1) You are not running this command from your game directory.
-           Change directory to your game directory and try again (or
-           create a new game directory using evennia --init <dirname>)
-        2) The settings file contains a syntax error. If you see a
-           traceback above, review it, resolve the problem and try again.
-        3) Django is not correctly installed. This usually shows as
-           errors mentioning 'DJANGO_SETTINGS_MODULE'. If you run a
-           virtual machine, it might be worth to restart it to see if
-           this resolves the issue.
-    """.format(
-    settingspath=SETTINGS_PATH
-)
-
-ERROR_INITSETTINGS = """
-    ERROR: 'evennia --initsettings' must be called from the root of
-    your game directory, since it tries to (re)create the new
-    settings.py file in a subfolder server/conf/.
-    """
-
-RECREATED_SETTINGS = """
-    (Re)created an empty settings file in server/conf/settings.py.
-
-    Note that if you were using an existing database, the password
-    salt of this new settings file will be different from the old one.
-    This means that any existing accounts may not be able to log in to
-    their accounts with their old passwords.
-    """
-
-ERROR_INITMISSING = """
-    ERROR: 'evennia --initmissing' must be called from the root of
-    your game directory, since it tries to create any missing files
-    in the server/ subfolder.
-    """
-
-RECREATED_MISSING = """
-    (Re)created any missing directories or files.  Evennia should
-    be ready to run now!
-    """
-
-ERROR_DATABASE = """
-    ERROR: Your database does not exist or is not set up correctly.
-    (error was '{traceback}')
-
-    If you think your database should work, make sure you are running your
-    commands from inside your game directory. If this error persists, run
-
-       evennia migrate
-
-    to initialize/update the database according to your settings.
-    """
-
-ERROR_WINDOWS_WIN32API = """
-    ERROR: Unable to import win32api, which Twisted requires to run.
-    You may download it from:
-
-    http://sourceforge.net/projects/pywin32/files/pywin32/
-
-    If you are running in a virtual environment, browse to the
-    location of the latest win32api exe file for your computer and
-    Python version and copy the url to it; then paste it into a call
-    to easy_install:
-
-        easy_install http://<url to win32api exe>
-    """
-
-INFO_WINDOWS_BATFILE = """
-    INFO: Since you are running Windows, a file 'twistd.bat' was
-    created for you. This is a simple batch file that tries to call
-    the twisted executable. Evennia determined this to be:
-
-       {twistd_path}
-
-    If you run into errors at startup you might need to edit
-    twistd.bat to point to the actual location of the Twisted
-    executable (usually called twistd.py) on your machine.
-
-    This procedure is only done once. Run `evennia` again when you
-    are ready to start the server.
-    """
-
-CMDLINE_HELP = """Starts, initializes, manages and operates the Evennia MU* server.
-Most standard django management commands are also accepted."""
-
-
-VERSION_INFO = """
-    Evennia {version}
-    OS: {os}
-    Python: {python}
-    Twisted: {twisted}
-    Django: {django}{about}
-    """
-
-ABOUT_INFO = """
-    Evennia MUD/MUX/MU* development system
-
-    Licence: BSD 3-Clause Licence
-    Web: http://www.evennia.com
-    Irc: #evennia on FreeNode
-    Forum: http://www.evennia.com/discussions
-    Maintainer (2006-10): Greg Taylor
-    Maintainer (2010-):   Griatch (griatch AT gmail DOT com)
-
-    Use -h for command line options.
-    """
-
-HELP_ENTRY = """
-    Evennia has two processes, the 'Server' and the 'Portal'.
-    External users connect to the Portal while the Server runs the
-    game/database. Restarting the Server will refresh code but not
-    disconnect users.
-
-    To start a new game, use 'evennia --init mygame'.
-    For more ways to operate and manage Evennia, see 'evennia -h'.
-
-    If you want to add unit tests to your game, see
-        https://github.com/evennia/evennia/wiki/Unit-Testing
-
-    Evennia's manual is found here:
-        https://github.com/evennia/evennia/wiki
-    """
-
-MENU = """
-    +----Evennia Launcher-------------------------------------------+
-    {gameinfo}
-    +--- Common operations -----------------------------------------+
-    |  1) Start                       (also restart stopped Server) |
-    |  2) Reload               (stop/start Server in 'reload' mode) |
-    |  3) Stop                         (shutdown Portal and Server) |
-    |  4) Reboot                            (shutdown then restart) |
-    +--- Other operations ------------------------------------------+
-    |  5) Reset              (stop/start Server in 'shutdown' mode) |
-    |  6) Stop Server only                                          |
-    |  7) Kill Server only            (send kill signal to process) |
-    |  8) Kill Portal + Server                                      |
-    +--- Information -----------------------------------------------+
-    |  9) Tail log files      (quickly see errors - Ctrl-C to exit) |
-    | 10) Status                                                    |
-    | 11) Port info                                                 |
-    +--- Testing ---------------------------------------------------+
-    | 12) Test gamedir             (run gamedir test suite, if any) |
-    | 13) Test Evennia                     (run Evennia test suite) |
-    +---------------------------------------------------------------+
-    |  h) Help               i) About info                q) Abort  |
-    +---------------------------------------------------------------+"""
-
-ERROR_AMP_UNCONFIGURED = """
-    Can't find server info for connecting. Either run this command from
-    the game dir (it will then use the game's settings file) or specify
-    the path to your game's settings file manually with the --settings
-    option.
-    """
-
-ERROR_LOGDIR_MISSING = """
-    ERROR: One or more log-file directory locations could not be
-    found:
-
-    {logfiles}
-
-    This is simple to fix: Just manually create the missing log
-    directory (or directories) and re-launch the server (the log files
-    will be created automatically).
-
-    (Explanation: Evennia creates the log directory automatically when
-    initializing a new game directory. This error usually happens if
-    you used git to clone a pre-created game directory - since log
-    files are in .gitignore they will not be cloned, which leads to
-    the log directory also not being created.)
-    """
-
-ERROR_PYTHON_VERSION = """
-    ERROR: Python {pversion} used. Evennia requires version
-    {python_min} or higher.
-    """
-
-ERROR_TWISTED_VERSION = """
-    ERROR: Twisted {tversion} found. Evennia requires
-    version {twisted_min} or higher.
-    """
-
-ERROR_NOTWISTED = """
-    ERROR: Twisted does not seem to be installed.
-    """
-
-ERROR_DJANGO_MIN = """
-    ERROR: Django {dversion} found. Evennia requires at least version {django_min} (but
-    no higher than {django_lt}).
-
-    If you are using a virtualenv, use the command `pip install --upgrade -e evennia` where
-    `evennia` is the folder to where you cloned the Evennia library. If not
-    in a virtualenv you can install django with for example `pip install --upgrade django`
-    or with `pip install django=={django_min}` to get a specific version.
-
-    It's also a good idea to run `evennia migrate` after this upgrade. Ignore
-    any warnings and don't run `makemigrate` even if told to.
-    """
-
-NOTE_DJANGO_NEW = """
-    NOTE: Django {dversion} found. This is newer than Evennia's
-    recommended version ({django_rec}). It might work, but is new
-    enough to not be fully tested yet. Report any issues.
-    """
-
-ERROR_NODJANGO = """
-    ERROR: Django does not seem to be installed.
-    """
-
-NOTE_KEYBOARDINTERRUPT = """
-    STOP: Caught keyboard interrupt while in interactive mode.
-    """
-
-NOTE_TEST_DEFAULT = """
-    TESTING: Using Evennia's default settings file (evennia.settings_default).
-    (use 'evennia test --settings settings.py .' to run only your custom game tests)
-    """
-
-NOTE_TEST_CUSTOM = """
-    TESTING: Using specified settings file '{settings_dotpath}'.
-
-    OBS: Evennia's full test suite may not pass if the settings are very
-    different from the default (use 'evennia test evennia' to run core tests)
-    """
-
-PROCESS_ERROR = """
-    {component} process error: {traceback}.
-    """
-
-PORTAL_INFO = """{servername} Portal {version}
-    external ports:
-        {telnet}
-        {telnet_ssl}
-        {ssh}
-        {webserver_proxy}
-        {webclient}
-    internal_ports (to Server):
-        {webserver_internal}
-        {amp}
-"""
-
-
-SERVER_INFO = """{servername} Server {version}
-    internal ports (to Portal):
-        {webserver}
-        {amp}
-    {irc_rss}
-    {info}
-    {errors}"""
-
-
-ARG_OPTIONS = """Actions on installed server. One of:
- start       - launch server+portal if not running
- reload      - restart server in 'reload' mode
- stop        - shutdown server+portal
- reboot      - shutdown server+portal, then start again
- reset       - restart server in 'shutdown' mode
- istart      - start server in foreground (until reload)
- ipstart     - start portal in foreground
- sstop       - stop only server
- kill        - send kill signal to portal+server (force)
- skill       - send kill signal only to server
- status      - show server and portal run state
- info        - show server and portal port info
- menu        - show a menu of options
- connections - show connection wizard
-Others, like migrate, test and shell is passed on to Django."""
-
-# ------------------------------------------------------------
-#
-# Private helper functions
-#
-# ------------------------------------------------------------
-
-
-def _is_windows():
-    return os.name == "nt"
-
-
-def _file_names_compact(filepath1, filepath2):
-    "Compact the output of filenames with same base dir"
-    dirname1 = os.path.dirname(filepath1)
-    dirname2 = os.path.dirname(filepath2)
-    if dirname1 == dirname2:
-        name2 = os.path.basename(filepath2)
-        return "{} and {}".format(filepath1, name2)
-    else:
-        return "{} and {}".format(filepath1, filepath2)
-
-
-def _print_info(portal_info_dict, server_info_dict):
-    """
-    Format info dicts from the Portal/Server for display
-
-    """
-    ind = " " * 8
-
-    def _prepare_dict(dct):
-        out = {}
-        for key, value in dct.items():
-            if isinstance(value, list):
-                value = "\n{}".format(ind).join(str(val) for val in value)
-            out[key] = value
-        return out
-
-    def _strip_empty_lines(string):
-        return "\n".join(line for line in string.split("\n") if line.strip())
-
-    pstr, sstr = "", ""
-    if portal_info_dict:
-        pdict = _prepare_dict(portal_info_dict)
-        pstr = _strip_empty_lines(PORTAL_INFO.format(**pdict))
-
-    if server_info_dict:
-        sdict = _prepare_dict(server_info_dict)
-        sstr = _strip_empty_lines(SERVER_INFO.format(**sdict))
-
-    info = pstr + ("\n\n" + sstr if sstr else "")
-    maxwidth = max(len(line) for line in info.split("\n"))
-    top_border = "-" * (maxwidth - 11) + " Evennia " + "---"
-    border = "-" * (maxwidth + 1)
-    print(top_border + "\n" + info + "\n" + border)
-
-
-def _parse_status(response):
-    "Unpack the status information"
-    return pickle.loads(response["status"])
-
-
-def _get_twistd_cmdline(pprofiler, sprofiler):
-    """
-    Compile the command line for starting a Twisted application using the 'twistd' executable.
-
-    """
-    portal_cmd = [TWISTED_BINARY, "--python={}".format(PORTAL_PY_FILE)]
-    server_cmd = [TWISTED_BINARY, "--python={}".format(SERVER_PY_FILE)]
-
-    if os.name != "nt":
-        # PID files only for UNIX
-        portal_cmd.append("--pidfile={}".format(PORTAL_PIDFILE))
-        server_cmd.append("--pidfile={}".format(SERVER_PIDFILE))
-
-    if pprofiler:
-        portal_cmd.extend(
-            ["--savestats", "--profiler=cprofile", "--profile={}".format(PPROFILER_LOGFILE)]
-        )
-    if sprofiler:
-        server_cmd.extend(
-            ["--savestats", "--profiler=cprofile", "--profile={}".format(SPROFILER_LOGFILE)]
-        )
-
-    return portal_cmd, server_cmd
-
-
-def _reactor_stop():
-    if not NO_REACTOR_STOP:
-        reactor.stop()
-
-
-# ------------------------------------------------------------
-#
-#  Protocol Evennia launcher - Portal/Server communication
-#
-# ------------------------------------------------------------
-
-
-
[docs]class MsgStatus(amp.Command): - """ - Ping between AMP services - - """ - - key = "MsgStatus" - arguments = [(b"status", amp.String())] - errors = {Exception: b"EXCEPTION"} - response = [(b"status", amp.String())]
- - -
[docs]class MsgLauncher2Portal(amp.Command): - """ - Message Launcher -> Portal - - """ - - key = "MsgLauncher2Portal" - arguments = [(b"operation", amp.String()), (b"arguments", amp.String())] - errors = {Exception: b"EXCEPTION"} - response = []
- - -
[docs]class AMPLauncherProtocol(amp.AMP): - """ - Defines callbacks to the launcher - - """ - -
[docs] def __init__(self): - self.on_status = []
- -
[docs] def wait_for_status(self, callback): - """ - Register a waiter for a status return. - - """ - self.on_status.append(callback)
- -
[docs] @MsgStatus.responder - def receive_status_from_portal(self, status): - """ - Get a status signal from portal - fire next queued - callback - - """ - try: - callback = self.on_status.pop() - except IndexError: - pass - else: - status = pickle.loads(status) - callback(status) - return {"status": pickle.dumps(b"")}
- - -
[docs]def send_instruction(operation, arguments, callback=None, errback=None): - """ - Send instruction and handle the response. - - """ - global AMP_CONNECTION, REACTOR_RUN - - if None in (AMP_HOST, AMP_PORT, AMP_INTERFACE): - print(ERROR_AMP_UNCONFIGURED) - sys.exit() - - def _callback(result): - if callback: - callback(result) - - def _errback(fail): - if errback: - errback(fail) - - def _on_connect(prot): - """ - This fires with the protocol when connection is established. We - immediately send off the instruction - - """ - global AMP_CONNECTION - AMP_CONNECTION = prot - _send() - - def _on_connect_fail(fail): - "This is called if portal is not reachable." - errback(fail) - - def _send(): - if operation == PSTATUS: - return AMP_CONNECTION.callRemote(MsgStatus, status=b"").addCallbacks( - _callback, _errback - ) - else: - return AMP_CONNECTION.callRemote( - MsgLauncher2Portal, - operation=bytes(operation, "utf-8"), - arguments=pickle.dumps(arguments, pickle.HIGHEST_PROTOCOL), - ).addCallbacks(_callback, _errback) - - if AMP_CONNECTION: - # already connected - send right away - return _send() - else: - # we must connect first, send once connected - point = endpoints.TCP4ClientEndpoint(reactor, AMP_HOST, AMP_PORT) - deferred = endpoints.connectProtocol(point, AMPLauncherProtocol()) - deferred.addCallbacks(_on_connect, _on_connect_fail) - REACTOR_RUN = True - return deferred
- - -
[docs]def query_status(callback=None): - """ - Send status ping to portal - - """ - wmap = {True: "RUNNING", False: "NOT RUNNING"} - - def _callback(response): - if callback: - callback(response) - else: - pstatus, sstatus, ppid, spid, pinfo, sinfo = _parse_status(response) - print( - "Portal: {}{}\nServer: {}{}".format( - wmap[pstatus], - " (pid {})".format(get_pid(PORTAL_PIDFILE, ppid)) if pstatus else "", - wmap[sstatus], - " (pid {})".format(get_pid(SERVER_PIDFILE, spid)) if sstatus else "", - ) - ) - _reactor_stop() - - def _errback(fail): - pstatus, sstatus = False, False - print("Portal: {}\nServer: {}".format(wmap[pstatus], wmap[sstatus])) - _reactor_stop() - - send_instruction(PSTATUS, None, _callback, _errback)
- - -
[docs]def wait_for_status_reply(callback): - """ - Wait for an explicit STATUS signal to be sent back from Evennia. - """ - if AMP_CONNECTION: - AMP_CONNECTION.wait_for_status(callback) - else: - print("No Evennia connection established.")
- - -
[docs]def wait_for_status( - portal_running=True, server_running=True, callback=None, errback=None, rate=0.5, retries=20 -): - """ - Repeat the status ping until the desired state combination is achieved. - - Args: - portal_running (bool or None): Desired portal run-state. If None, any state - is accepted. - server_running (bool or None): Desired server run-state. If None, any state - is accepted. The portal must be running. - callback (callable): Will be called with portal_state, server_state when - condition is fulfilled. - errback (callable): Will be called with portal_state, server_state if the - request is timed out. - rate (float): How often to retry. - retries (int): How many times to retry before timing out and calling `errback`. - """ - - def _callback(response): - prun, srun, _, _, _, _ = _parse_status(response) - if (portal_running is None or prun == portal_running) and ( - server_running is None or srun == server_running - ): - # the correct state was achieved - if callback: - callback(prun, srun) - else: - _reactor_stop() - else: - if retries <= 0: - if errback: - errback(prun, srun) - else: - print("Connection to Evennia timed out. Try again.") - _reactor_stop() - else: - reactor.callLater( - rate, - wait_for_status, - portal_running, - server_running, - callback, - errback, - rate, - retries - 1, - ) - - def _errback(fail): - """ - Portal not running - """ - if not portal_running: - # this is what we want - if callback: - callback(portal_running, server_running) - else: - _reactor_stop() - else: - if retries <= 0: - if errback: - errback(portal_running, server_running) - else: - print("Connection to Evennia timed out. Try again.") - _reactor_stop() - else: - reactor.callLater( - rate, - wait_for_status, - portal_running, - server_running, - callback, - errback, - rate, - retries - 1, - ) - - return send_instruction(PSTATUS, None, _callback, _errback)
- - -# ------------------------------------------------------------ -# -# Operational functions -# -# ------------------------------------------------------------ - - -
[docs]def collectstatic(): - "Run the collectstatic django command" - django.core.management.call_command("collectstatic", interactive=False, verbosity=0)
- - -
[docs]def start_evennia(pprofiler=False, sprofiler=False): - """ - This will start Evennia anew by launching the Evennia Portal (which in turn - will start the Server) - - """ - portal_cmd, server_cmd = _get_twistd_cmdline(pprofiler, sprofiler) - - def _fail(fail): - print(fail) - _reactor_stop() - - def _server_started(response): - print("... Server started.\nEvennia running.") - if response: - _, _, _, _, pinfo, sinfo = response - _print_info(pinfo, sinfo) - _reactor_stop() - - def _portal_started(*args): - print( - "... Portal started.\nServer starting {} ...".format( - "(under cProfile)" if sprofiler else "" - ) - ) - wait_for_status_reply(_server_started) - send_instruction(SSTART, server_cmd) - - def _portal_running(response): - prun, srun, ppid, spid, _, _ = _parse_status(response) - print("Portal is already running as process {pid}. Not restarted.".format(pid=ppid)) - if srun: - print("Server is already running as process {pid}. Not restarted.".format(pid=spid)) - _reactor_stop() - else: - print("Server starting {}...".format("(under cProfile)" if sprofiler else "")) - send_instruction(SSTART, server_cmd, _server_started, _fail) - - def _portal_not_running(fail): - print("Portal starting {}...".format("(under cProfile)" if pprofiler else "")) - try: - if _is_windows(): - # Windows requires special care - create_no_window = 0x08000000 - Popen(portal_cmd, env=getenv(), bufsize=-1, creationflags=create_no_window) - else: - Popen(portal_cmd, env=getenv(), bufsize=-1) - except Exception as e: - print(PROCESS_ERROR.format(component="Portal", traceback=e)) - _reactor_stop() - wait_for_status(True, None, _portal_started) - - collectstatic() - send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
- - -
[docs]def reload_evennia(sprofiler=False, reset=False): - """ - This will instruct the Portal to reboot the Server component. We - do this manually by telling the server to shutdown (in reload mode) - and wait for the portal to report back, at which point we start the - server again. This way we control the process exactly. - - """ - _, server_cmd = _get_twistd_cmdline(False, sprofiler) - - def _server_restarted(*args): - print("... Server re-started.") - _reactor_stop() - - def _server_reloaded(status): - print("... Server {}.".format("reset" if reset else "reloaded")) - _reactor_stop() - - def _server_stopped(status): - wait_for_status_reply(_server_reloaded) - send_instruction(SSTART, server_cmd) - - def _portal_running(response): - _, srun, _, _, _, _ = _parse_status(response) - if srun: - print("Server {}...".format("resetting" if reset else "reloading")) - wait_for_status_reply(_server_stopped) - send_instruction(SRESET if reset else SRELOAD, {}) - else: - print("Server down. Re-starting ...") - wait_for_status_reply(_server_restarted) - send_instruction(SSTART, server_cmd) - - def _portal_not_running(fail): - print("Evennia not running. Starting up ...") - start_evennia() - - collectstatic() - send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
- - -
[docs]def stop_evennia(): - """ - This instructs the Portal to stop the Server and then itself. - - """ - - def _portal_stopped(*args): - print("... Portal stopped.\nEvennia shut down.") - _reactor_stop() - - def _server_stopped(*args): - print("... Server stopped.\nStopping Portal ...") - send_instruction(PSHUTD, {}) - wait_for_status(False, None, _portal_stopped) - - def _portal_running(response): - prun, srun, ppid, spid, _, _ = _parse_status(response) - if srun: - print("Server stopping ...") - send_instruction(SSHUTD, {}) - wait_for_status_reply(_server_stopped) - else: - print("Server already stopped.\nStopping Portal ...") - send_instruction(PSHUTD, {}) - wait_for_status(False, None, _portal_stopped) - - def _portal_not_running(fail): - print("Evennia not running.") - _reactor_stop() - - send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
- - -
[docs]def reboot_evennia(pprofiler=False, sprofiler=False): - """ - This is essentially an evennia stop && evennia start except we make sure - the system has successfully shut down before starting it again. - - If evennia was not running, start it. - - """ - global AMP_CONNECTION - - def _portal_stopped(*args): - print("... Portal stopped. Evennia shut down. Rebooting ...") - global AMP_CONNECTION - AMP_CONNECTION = None - start_evennia(pprofiler, sprofiler) - - def _server_stopped(*args): - print("... Server stopped.\nStopping Portal ...") - send_instruction(PSHUTD, {}) - wait_for_status(False, None, _portal_stopped) - - def _portal_running(response): - prun, srun, ppid, spid, _, _ = _parse_status(response) - if srun: - print("Server stopping ...") - send_instruction(SSHUTD, {}) - wait_for_status_reply(_server_stopped) - else: - print("Server already stopped.\nStopping Portal ...") - send_instruction(PSHUTD, {}) - wait_for_status(False, None, _portal_stopped) - - def _portal_not_running(fail): - print("Evennia not running. Starting up ...") - start_evennia() - - collectstatic() - send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
- - -
[docs]def start_only_server(): - """ - Tell portal to start server (debug) - """ - portal_cmd, server_cmd = _get_twistd_cmdline(False, False) - print("launcher: Sending to portal: SSTART + {}".format(server_cmd)) - collectstatic() - send_instruction(SSTART, server_cmd)
- - -
[docs]def start_server_interactive(): - """ - Start the Server under control of the launcher process (foreground) - - """ - - def _iserver(): - _, server_twistd_cmd = _get_twistd_cmdline(False, False) - server_twistd_cmd.append("--nodaemon") - print("Starting Server in interactive mode (stop with Ctrl-C)...") - try: - Popen(server_twistd_cmd, env=getenv(), stderr=STDOUT).wait() - except KeyboardInterrupt: - print("... Stopped Server with Ctrl-C.") - else: - print("... Server stopped (leaving interactive mode).") - - collectstatic() - stop_server_only(when_stopped=_iserver, interactive=True)
- - -
[docs]def start_portal_interactive(): - """ - Start the Portal under control of the launcher process (foreground) - - Notes: - In a normal start, the launcher waits for the Portal to start, then - tells it to start the Server. Since we can't do this here, we instead - start the Server first and then starts the Portal - the Server will - auto-reconnect to the Portal. To allow the Server to be reloaded, this - relies on a fixed server server-cmdline stored as a fallback on the - portal application in evennia/server/portal/portal.py. - - """ - - def _iportal(fail): - portal_twistd_cmd, server_twistd_cmd = _get_twistd_cmdline(False, False) - portal_twistd_cmd.append("--nodaemon") - - # starting Server first - it will auto-connect once Portal comes up - if _is_windows(): - # Windows requires special care - create_no_window = 0x08000000 - Popen(server_twistd_cmd, env=getenv(), bufsize=-1, creationflags=create_no_window) - else: - Popen(server_twistd_cmd, env=getenv(), bufsize=-1) - - print("Starting Portal in interactive mode (stop with Ctrl-C)...") - try: - Popen(portal_twistd_cmd, env=getenv(), stderr=STDOUT).wait() - except KeyboardInterrupt: - print("... Stopped Portal with Ctrl-C.") - else: - print("... Portal stopped (leaving interactive mode).") - - def _portal_running(response): - print("Evennia must be shut down completely before running Portal in interactive mode.") - _reactor_stop() - - send_instruction(PSTATUS, None, _portal_running, _iportal)
- - -
[docs]def stop_server_only(when_stopped=None, interactive=False): - """ - Only stop the Server-component of Evennia (this is not useful except for debug) - - Args: - when_stopped (callable): This will be called with no arguments when Server has stopped (or - if it had already stopped when this is called). - interactive (bool, optional): Set if this is called as part of the interactive reload - mechanism. - - """ - - def _server_stopped(*args): - if when_stopped: - when_stopped() - else: - print("... Server stopped.") - _reactor_stop() - - def _portal_running(response): - _, srun, _, _, _, _ = _parse_status(response) - if srun: - print("Server stopping ...") - wait_for_status_reply(_server_stopped) - if interactive: - send_instruction(SRELOAD, {}) - else: - send_instruction(SSHUTD, {}) - else: - if when_stopped: - when_stopped() - else: - print("Server is not running.") - _reactor_stop() - - def _portal_not_running(fail): - print("Evennia is not running.") - if interactive: - print("Start Evennia normally first, then use `istart` to switch to interactive mode.") - _reactor_stop() - - send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
- - -
[docs]def query_info(): - """ - Display the info strings from the running Evennia - - """ - - def _got_status(status): - _, _, _, _, pinfo, sinfo = _parse_status(status) - _print_info(pinfo, sinfo) - _reactor_stop() - - def _portal_running(response): - query_status(_got_status) - - def _portal_not_running(fail): - print("Evennia is not running.") - - send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
- - -
[docs]def tail_log_files(filename1, filename2, start_lines1=20, start_lines2=20, rate=1): - """ - Tail two logfiles interactively, combining their output to stdout - - When first starting, this will display the tail of the log files. After - that it will poll the log files repeatedly and display changes. - - Args: - filename1 (str): Path to first log file. - filename2 (str): Path to second log file. - start_lines1 (int): How many lines to show from existing first log. - start_lines2 (int): How many lines to show from existing second log. - rate (int, optional): How often to poll the log file. - - """ - global REACTOR_RUN - - def _file_changed(filename, prev_size): - "Get size of file in bytes, get diff compared with previous size" - try: - new_size = os.path.getsize(filename) - except FileNotFoundError: - return False, 0 - return new_size != prev_size, new_size - - def _get_new_lines(filehandle, old_linecount): - "count lines, get the ones not counted before" - - def _block(filehandle, size=65536): - "File block generator for quick traversal" - while True: - dat = filehandle.read(size) - if not dat: - break - yield dat - - # count number of lines in file - new_linecount = sum(blck.count("\n") for blck in _block(filehandle)) - - if new_linecount < old_linecount: - # this happens if the file was cycled or manually deleted/edited. - print( - " ** Log file {filename} has cycled or been edited. " - "Restarting log. ".format(filename=filehandle.name) - ) - new_linecount = 0 - old_linecount = 0 - - lines_to_get = max(0, new_linecount - old_linecount) - - if not lines_to_get: - return [], old_linecount - - lines_found = [] - buffer_size = 4098 - block_count = -1 - - while len(lines_found) < lines_to_get: - try: - # scan backwards in file, starting from the end - filehandle.seek(block_count * buffer_size, os.SEEK_END) - except IOError: - # file too small for current seek, include entire file - filehandle.seek(0) - lines_found = filehandle.readlines() - break - lines_found = filehandle.readlines() - block_count -= 1 - - # only actually return the new lines - return lines_found[-lines_to_get:], new_linecount - - def _tail_file(filename, file_size, line_count, max_lines=None): - """This will cycle repeatedly, printing new lines""" - - # poll for changes - has_changed, file_size = _file_changed(filename, file_size) - - if has_changed: - try: - with open(filename, "r") as filehandle: - new_lines, line_count = _get_new_lines(filehandle, line_count) - except IOError: - # the log file might not exist yet. Wait a little, then try again ... - pass - else: - if max_lines == 0: - # don't show any lines from old file - new_lines = [] - elif max_lines: - # show some lines from first startup - new_lines = new_lines[-max_lines:] - - # print to stdout without line break (log has its own line feeds) - sys.stdout.write("".join(new_lines)) - sys.stdout.flush() - - # set up the next poll - reactor.callLater(rate, _tail_file, filename, file_size, line_count, max_lines=100) - - reactor.callLater(0, _tail_file, filename1, 0, 0, max_lines=start_lines1) - reactor.callLater(0, _tail_file, filename2, 0, 0, max_lines=start_lines2) - - REACTOR_RUN = True
- - -# ------------------------------------------------------------ -# -# Environment setup -# -# ------------------------------------------------------------ - - -
[docs]def evennia_version(): - """ - Get the Evennia version info from the main package. - - """ - version = "Unknown" - try: - version = evennia.__version__ - except ImportError: - # even if evennia is not found, we should not crash here. - pass - try: - rev = ( - check_output("git rev-parse --short HEAD", shell=True, cwd=EVENNIA_ROOT, stderr=STDOUT) - .strip() - .decode() - ) - version = "%s (rev %s)" % (version, rev) - except (IOError, CalledProcessError, OSError): - # move on if git is not answering - pass - return version
- - -EVENNIA_VERSION = evennia_version() - - -
[docs]def check_main_evennia_dependencies(): - """ - Checks and imports the Evennia dependencies. This must be done - already before the paths are set up. - - Returns: - not_error (bool): True if no dependency error was found. - - """ - error = False - - # Python - pversion = ".".join(str(num) for num in sys.version_info if isinstance(num, int)) - if LooseVersion(pversion) < LooseVersion(PYTHON_MIN): - print(ERROR_PYTHON_VERSION.format(pversion=pversion, python_min=PYTHON_MIN)) - error = True - # Twisted - try: - import twisted - - tversion = twisted.version.short() - if LooseVersion(tversion) < LooseVersion(TWISTED_MIN): - print(ERROR_TWISTED_VERSION.format(tversion=tversion, twisted_min=TWISTED_MIN)) - error = True - except ImportError: - print(ERROR_NOTWISTED) - error = True - # Django - try: - dversion = ".".join(str(num) for num in django.VERSION if isinstance(num, int)) - # only the main version (1.5, not 1.5.4.0) - dversion_main = ".".join(dversion.split(".")[:2]) - if LooseVersion(dversion) < LooseVersion(DJANGO_MIN): - print( - ERROR_DJANGO_MIN.format( - dversion=dversion_main, django_min=DJANGO_MIN, django_lt=DJANGO_LT - ) - ) - error = True - elif LooseVersion(DJANGO_LT) <= LooseVersion(dversion_main): - print(NOTE_DJANGO_NEW.format(dversion=dversion_main, django_rec=DJANGO_LT)) - except ImportError: - print(ERROR_NODJANGO) - error = True - if error: - sys.exit() - - # return True/False if error was reported or not - return not error
- - -
[docs]def set_gamedir(path): - """ - Set GAMEDIR based on path, by figuring out where the setting file - is inside the directory tree. This allows for running the launcher - from elsewhere than the top of the gamedir folder. - - """ - global GAMEDIR - - Ndepth = 10 - settings_path = os.path.join("server", "conf", "settings.py") - os.chdir(GAMEDIR) - for i in range(Ndepth): - gpath = os.getcwd() - if "server" in os.listdir(gpath): - if os.path.isfile(settings_path): - GAMEDIR = gpath - return - os.chdir(os.pardir) - print(ERROR_NO_GAMEDIR) - sys.exit()
- - -
[docs]def create_secret_key(): - """ - Randomly create the secret key for the settings file - - """ - import random - import string - - secret_key = list( - (string.ascii_letters + string.digits + string.punctuation) - .replace("\\", "") - .replace("'", '"') - .replace("{", "_") - .replace("}", "-") - ) - random.shuffle(secret_key) - secret_key = "".join(secret_key[:40]) - return secret_key
- - -
[docs]def create_settings_file(init=True, secret_settings=False): - """ - Uses the template settings file to build a working settings file. - - Args: - init (bool): This is part of the normal evennia --init - operation. If false, this function will copy a fresh - template file in (asking if it already exists). - secret_settings (bool, optional): If False, create settings.py, otherwise - create the secret_settings.py file. - - """ - if secret_settings: - settings_path = os.path.join(GAMEDIR, "server", "conf", "secret_settings.py") - setting_dict = {"secret_key": "'%s'" % create_secret_key()} - else: - settings_path = os.path.join(GAMEDIR, "server", "conf", "settings.py") - setting_dict = { - "settings_default": os.path.join(EVENNIA_LIB, "settings_default.py"), - "servername": '"%s"' % GAMEDIR.rsplit(os.path.sep, 1)[1], - "secret_key": "'%s'" % create_secret_key(), - } - - if not init: - # if not --init mode, settings file may already exist from before - if os.path.exists(settings_path): - inp = input("%s already exists. Do you want to reset it? y/[N]> " % settings_path) - if not inp.lower() == "y": - print("Aborted.") - sys.exit() - else: - print("Reset the settings file.") - - if secret_settings: - default_settings_path = os.path.join( - EVENNIA_TEMPLATE, "server", "conf", "secret_settings.py" - ) - else: - default_settings_path = os.path.join(EVENNIA_TEMPLATE, "server", "conf", "settings.py") - shutil.copy(default_settings_path, settings_path) - - with open(settings_path, "r") as f: - settings_string = f.read() - - settings_string = settings_string.format(**setting_dict) - - with open(settings_path, "w") as f: - f.write(settings_string)
- - -
[docs]def create_game_directory(dirname): - """ - Initialize a new game directory named dirname - at the current path. This means copying the - template directory from evennia's root. - - Args: - dirname (str): The directory name to create. - - """ - global GAMEDIR - GAMEDIR = os.path.abspath(os.path.join(CURRENT_DIR, dirname)) - if os.path.exists(GAMEDIR): - print("Cannot create new Evennia game dir: '%s' already exists." % dirname) - sys.exit() - # copy template directory - shutil.copytree(EVENNIA_TEMPLATE, GAMEDIR) - # rename gitignore to .gitignore - os.rename(os.path.join(GAMEDIR, "gitignore"), os.path.join(GAMEDIR, ".gitignore")) - - # pre-build settings file in the new GAMEDIR - create_settings_file() - create_settings_file(secret_settings=True)
- - -
[docs]def create_superuser(): - """ - Create the superuser account - - """ - print( - "\nCreate a superuser below. The superuser is Account #1, the 'owner' " - "account of the server. Email is optional and can be empty.\n" - ) - django.core.management.call_command("createsuperuser", interactive=True)
- - -
[docs]def check_database(always_return=False): - """ - Check so the database exists. - - Args: - always_return (bool, optional): If set, will always return True/False - also on critical errors. No output will be printed. - Returns: - exists (bool): `True` if the database exists, otherwise `False`. - - - """ - # Check so a database exists and is accessible - from django.db import connection - - tables = connection.introspection.get_table_list(connection.cursor()) - if not tables or not isinstance(tables[0], str): # django 1.8+ - tables = [tableinfo.name for tableinfo in tables] - if tables and "accounts_accountdb" in tables: - # database exists and seems set up. Initialize evennia. - evennia._init() - # Try to get Account#1 - from evennia.accounts.models import AccountDB - - try: - AccountDB.objects.get(id=1) - except (django.db.utils.OperationalError, ProgrammingError) as e: - if always_return: - return False - print(ERROR_DATABASE.format(traceback=e)) - sys.exit() - except AccountDB.DoesNotExist: - # no superuser yet. We need to create it. - - other_superuser = AccountDB.objects.filter(is_superuser=True) - if other_superuser: - # Another superuser was found, but not with id=1. This may - # happen if using flush (the auto-id starts at a higher - # value). Wwe copy this superuser into id=1. To do - # this we must deepcopy it, delete it then save the copy - # with the new id. This allows us to avoid the UNIQUE - # constraint on usernames. - other = other_superuser[0] - other_id = other.id - other_key = other.username - print(WARNING_MOVING_SUPERUSER.format(other_key=other_key, other_id=other_id)) - res = "" - while res.upper() != "Y": - # ask for permission - res = eval(input("Continue [Y]/N: ")) - if res.upper() == "N": - sys.exit() - elif not res: - break - # continue with the - from copy import deepcopy - - new = deepcopy(other) - other.delete() - new.id = 1 - new.save() - else: - create_superuser() - check_database(always_return=always_return) - return True
- - -
[docs]def getenv(): - """ - Get current environment and add PYTHONPATH. - - Returns: - env (dict): Environment global dict. - - """ - sep = ";" if _is_windows() else ":" - env = os.environ.copy() - env["PYTHONPATH"] = sep.join(sys.path) - return env
- - -
[docs]def get_pid(pidfile, default=None): - """ - Get the PID (Process ID) by trying to access an PID file. - - Args: - pidfile (str): The path of the pid file. - default (int, optional): What to return if file does not exist. - - Returns: - pid (str): The process id or `default`. - - """ - if os.path.exists(pidfile): - with open(pidfile, "r") as f: - pid = f.read() - return pid - return default
- - -
[docs]def del_pid(pidfile): - """ - The pidfile should normally be removed after a process has - finished, but when sending certain signals they remain, so we need - to clean them manually. - - Args: - pidfile (str): The path of the pid file. - - """ - if os.path.exists(pidfile): - os.remove(pidfile)
- - -
[docs]def kill(pidfile, component="Server", callback=None, errback=None, killsignal=SIG): - """ - Send a kill signal to a process based on PID. A customized - success/error message will be returned. If clean=True, the system - will attempt to manually remove the pid file. On Windows, no arguments - are useful since Windows has no ability to direct signals except to all - children of a console. - - Args: - pidfile (str): The path of the pidfile to get the PID from. This is ignored - on Windows. - component (str, optional): Usually one of 'Server' or 'Portal'. This is - ignored on Windows. - errback (callable, optional): Called if signal failed to send. This - is ignored on Windows. - callback (callable, optional): Called if kill signal was sent successfully. - This is ignored on Windows. - killsignal (int, optional): Signal identifier for signal to send. This is - ignored on Windows. - - """ - if _is_windows(): - # Windows signal sending is very limited. - from win32api import GenerateConsoleCtrlEvent, SetConsoleCtrlHandler - - try: - # Windows can only send a SIGINT-like signal to - # *every* process spawned off the same console, so we must - # avoid killing ourselves here. - SetConsoleCtrlHandler(None, True) - GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) - except KeyboardInterrupt: - # We must catch and ignore the interrupt sent. - pass - print("Sent kill signal to all spawned processes") - - else: - # Linux/Unix/Mac can send kill signal directly to specific PIDs. - pid = get_pid(pidfile) - if pid: - if _is_windows(): - os.remove(pidfile) - try: - os.kill(int(pid), killsignal) - except OSError: - print( - "{component} ({pid}) cannot be stopped. " - "The PID file '{pidfile}' seems stale. " - "Try removing it manually.".format( - component=component, pid=pid, pidfile=pidfile - ) - ) - return - if callback: - callback() - else: - print("Sent kill signal to {component}.".format(component=component)) - return - if errback: - errback() - else: - print( - "Could not send kill signal - {component} does " - "not appear to be running.".format(component=component) - )
- - -
[docs]def show_version_info(about=False): - """ - Display version info. - - Args: - about (bool): Include ABOUT info as well as version numbers. - - Returns: - version_info (str): A complete version info string. - - """ - import sys - import twisted - - return VERSION_INFO.format( - version=EVENNIA_VERSION, - about=ABOUT_INFO if about else "", - os=os.name, - python=sys.version.split()[0], - twisted=twisted.version.short(), - django=django.get_version(), - )
- - -
[docs]def error_check_python_modules(show_warnings=False): - """ - Import settings modules in settings. This will raise exceptions on - pure python-syntax issues which are hard to catch gracefully with - exceptions in the engine (since they are formatting errors in the - python source files themselves). Best they fail already here - before we get any further. - - Keyword Args: - show_warnings (bool): If non-fatal warning messages should be shown. - - """ - - from django.conf import settings - - def _imp(path, split=True): - "helper method" - mod, fromlist = path, "None" - if split: - mod, fromlist = path.rsplit(".", 1) - __import__(mod, fromlist=[fromlist]) - - # check the historical deprecations - from evennia.server import deprecations - - try: - deprecations.check_errors(settings) - except DeprecationWarning as err: - print(err) - sys.exit() - - if show_warnings: - deprecations.check_warnings(settings) - - # core modules - _imp(settings.COMMAND_PARSER) - _imp(settings.SEARCH_AT_RESULT) - _imp(settings.CONNECTION_SCREEN_MODULE) - # imp(settings.AT_INITIAL_SETUP_HOOK_MODULE, split=False) - for path in settings.LOCK_FUNC_MODULES: - _imp(path, split=False) - - from evennia.commands import cmdsethandler - - if not cmdsethandler.import_cmdset(settings.CMDSET_UNLOGGEDIN, None): - print("Warning: CMDSET_UNLOGGED failed to load!") - if not cmdsethandler.import_cmdset(settings.CMDSET_CHARACTER, None): - print("Warning: CMDSET_CHARACTER failed to load") - if not cmdsethandler.import_cmdset(settings.CMDSET_ACCOUNT, None): - print("Warning: CMDSET_ACCOUNT failed to load") - # typeclasses - _imp(settings.BASE_ACCOUNT_TYPECLASS) - _imp(settings.BASE_OBJECT_TYPECLASS) - _imp(settings.BASE_CHARACTER_TYPECLASS) - _imp(settings.BASE_ROOM_TYPECLASS) - _imp(settings.BASE_EXIT_TYPECLASS) - _imp(settings.BASE_SCRIPT_TYPECLASS)
- - -# ------------------------------------------------------------ -# -# Options -# -# ------------------------------------------------------------ - - -
[docs]def init_game_directory(path, check_db=True, need_gamedir=True): - """ - Try to analyze the given path to find settings.py - this defines - the game directory and also sets PYTHONPATH as well as the django - path. - - Args: - path (str): Path to new game directory, including its name. - check_db (bool, optional): Check if the databae exists. - need_gamedir (bool, optional): set to False if Evennia doesn't require to - be run in a valid game directory. - - """ - # set the GAMEDIR path - if need_gamedir: - set_gamedir(path) - - # Add gamedir to python path - sys.path.insert(0, GAMEDIR) - - if TEST_MODE or not need_gamedir: - if ENFORCED_SETTING: - print(NOTE_TEST_CUSTOM.format(settings_dotpath=SETTINGS_DOTPATH)) - os.environ["DJANGO_SETTINGS_MODULE"] = SETTINGS_DOTPATH - else: - print(NOTE_TEST_DEFAULT) - os.environ["DJANGO_SETTINGS_MODULE"] = "evennia.settings_default" - else: - os.environ["DJANGO_SETTINGS_MODULE"] = SETTINGS_DOTPATH - - # required since django1.7 - django.setup() - - # test existence of the settings module - try: - from django.conf import settings - except Exception as ex: - if not str(ex).startswith("No module named"): - import traceback - - print(traceback.format_exc().strip()) - print(ERROR_SETTINGS) - sys.exit() - - # this will both check the database and initialize the evennia dir. - if check_db: - check_database() - - # if we don't have to check the game directory, return right away - if not need_gamedir: - return - - # set up the Evennia executables and log file locations - global AMP_PORT, AMP_HOST, AMP_INTERFACE - global SERVER_PY_FILE, PORTAL_PY_FILE - global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE - global SERVER_PIDFILE, PORTAL_PIDFILE - global SPROFILER_LOGFILE, PPROFILER_LOGFILE - global EVENNIA_VERSION - - AMP_PORT = settings.AMP_PORT - AMP_HOST = settings.AMP_HOST - AMP_INTERFACE = settings.AMP_INTERFACE - - SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py") - PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "server", "portal", "portal.py") - - SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid") - PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid") - - SPROFILER_LOGFILE = os.path.join(GAMEDIR, SERVERDIR, "logs", "server.prof") - PPROFILER_LOGFILE = os.path.join(GAMEDIR, SERVERDIR, "logs", "portal.prof") - - SERVER_LOGFILE = settings.SERVER_LOG_FILE - PORTAL_LOGFILE = settings.PORTAL_LOG_FILE - HTTP_LOGFILE = settings.HTTP_LOG_FILE - - # verify existence of log file dir (this can be missing e.g. - # if the game dir itself was cloned since log files are in .gitignore) - logdirs = [ - logfile.rsplit(os.path.sep, 1) for logfile in (SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE) - ] - if not all(os.path.isdir(pathtup[0]) for pathtup in logdirs): - errstr = "\n ".join( - "%s (log file %s)" % (pathtup[0], pathtup[1]) - for pathtup in logdirs - if not os.path.isdir(pathtup[0]) - ) - print(ERROR_LOGDIR_MISSING.format(logfiles=errstr)) - sys.exit() - - if _is_windows(): - # We need to handle Windows twisted separately. We create a - # batchfile in game/server, linking to the actual binary - - global TWISTED_BINARY - # Windows requires us to use the absolute path for the bat file. - server_path = os.path.dirname(os.path.abspath(__file__)) - TWISTED_BINARY = os.path.join(server_path, "twistd.bat") - - # add path so system can find the batfile - sys.path.insert(1, os.path.join(GAMEDIR, SERVERDIR)) - - try: - importlib.import_module("win32api") - except ImportError: - print(ERROR_WINDOWS_WIN32API) - sys.exit() - - batpath = os.path.join(EVENNIA_SERVER, TWISTED_BINARY) - if not os.path.exists(batpath): - # Test for executable twisted batch file. This calls the - # twistd.py executable that is usually not found on the - # path in Windows. It's not enough to locate - # scripts.twistd, what we want is the executable script - # C:\PythonXX/Scripts/twistd.py. Alas we cannot hardcode - # this location since we don't know if user has Python in - # a non-standard location. So we try to figure it out. - twistd = importlib.import_module("twisted.scripts.twistd") - twistd_dir = os.path.dirname(twistd.__file__) - - # note that we hope the twistd package won't change here, since we - # try to get to the executable by relative path. - # Update: In 2016, it seems Twisted 16 has changed the name of - # of its executable from 'twistd.py' to 'twistd.exe'. - twistd_path = os.path.abspath( - os.path.join( - twistd_dir, os.pardir, os.pardir, os.pardir, os.pardir, "scripts", "twistd.exe" - ) - ) - - with open(batpath, "w") as bat_file: - # build a custom bat file for windows - bat_file.write('@"%s" %%*' % twistd_path) - - print(INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path))
- - -
[docs]def run_dummyrunner(number_of_dummies): - """ - Start an instance of the dummyrunner - - Args: - number_of_dummies (int): The number of dummy accounts to start. - - Notes: - The dummy accounts' behavior can be customized by adding a - `dummyrunner_settings.py` config file in the game's conf/ - directory. - - """ - number_of_dummies = str(int(number_of_dummies)) if number_of_dummies else 1 - cmdstr = [sys.executable, EVENNIA_DUMMYRUNNER, "-N", number_of_dummies] - config_file = os.path.join(SETTINGS_PATH, "dummyrunner_settings.py") - if os.path.exists(config_file): - cmdstr.extend(["--config", config_file]) - try: - call(cmdstr, env=getenv()) - except KeyboardInterrupt: - # this signals the dummyrunner to stop cleanly and should - # not lead to a traceback here. - pass
- - -
[docs]def run_connect_wizard(): - """ - Run the linking wizard, for adding new external connections. - - """ - from .connection_wizard import ConnectionWizard, node_start - - wizard = ConnectionWizard() - node_start(wizard)
- - -
[docs]def list_settings(keys): - """ - Display the server settings. We only display the Evennia specific - settings here. The result will be printed to the terminal. - - Args: - keys (str or list): Setting key or keys to inspect. - - """ - from importlib import import_module - from evennia.utils import evtable - - evsettings = import_module(SETTINGS_DOTPATH) - if len(keys) == 1 and keys[0].upper() == "ALL": - # show a list of all keys - # a specific key - table = evtable.EvTable() - confs = [key for key in sorted(evsettings.__dict__) if key.isupper()] - for i in range(0, len(confs), 4): - table.add_row(*confs[i : i + 4]) - else: - # a specific key - table = evtable.EvTable(width=131) - keys = [key.upper() for key in keys] - confs = dict((key, var) for key, var in evsettings.__dict__.items() if key in keys) - for key, val in confs.items(): - table.add_row(key, str(val)) - print(table)
- - -
[docs]def run_menu(): - """ - This launches an interactive menu. - - """ - while True: - # menu loop - gamedir = "/{}".format(os.path.basename(GAMEDIR)) - leninfo = len(gamedir) - line = "|" + " " * (61 - leninfo) + gamedir + " " * 2 + "|" - - print(MENU.format(gameinfo=line)) - inp = input(" option > ") - - # quitting and help - if inp.lower() == "q": - return - elif inp.lower() == "h": - print(HELP_ENTRY) - eval(input("press <return> to continue ...")) - continue - elif inp.lower() in ("v", "i", "a"): - print(show_version_info(about=True)) - eval(input("press <return> to continue ...")) - continue - - # options - try: - inp = int(inp) - except ValueError: - print("Not a valid option.") - continue - if inp == 1: - start_evennia(False, False) - elif inp == 2: - reload_evennia(False, False) - elif inp == 3: - stop_evennia() - elif inp == 4: - reboot_evennia(False, False) - elif inp == 5: - reload_evennia(False, True) - elif inp == 6: - stop_server_only() - elif inp == 7: - if _is_windows(): - print("This option is not supported on Windows.") - else: - kill(SERVER_PIDFILE, "Server") - elif inp == 8: - if _is_windows(): - print("This option is not supported on Windows.") - else: - kill(SERVER_PIDFILE, "Server") - kill(PORTAL_PIDFILE, "Portal") - elif inp == 9: - if not SERVER_LOGFILE: - init_game_directory(CURRENT_DIR, check_db=False) - tail_log_files(PORTAL_LOGFILE, SERVER_LOGFILE, 20, 20) - print( - " Tailing logfiles {} (Ctrl-C to exit) ...".format( - _file_names_compact(SERVER_LOGFILE, PORTAL_LOGFILE) - ) - ) - elif inp == 10: - query_status() - elif inp == 11: - query_info() - elif inp == 12: - print("Running 'evennia --settings settings.py test .' ...") - Popen( - [sys.executable, __file__, "--settings", "settings.py", "test", "."], env=getenv() - ).wait() - elif inp == 13: - print("Running 'evennia test evennia' ...") - Popen([sys.executable, __file__, "test", "evennia"], env=getenv()).wait() - else: - print("Not a valid option.") - continue - return
- - -
[docs]def main(): - """ - Run the evennia launcher main program. - - """ - # set up argument parser - - parser = ArgumentParser(description=CMDLINE_HELP, formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument( - "--gamedir", - nargs=1, - action="store", - dest="altgamedir", - metavar="<path>", - help="location of gamedir (default: current location)", - ) - parser.add_argument( - "--init", - action="store", - dest="init", - metavar="<gamename>", - help="creates a new gamedir 'name' at current location", - ) - parser.add_argument( - "--log", - "-l", - action="store_true", - dest="tail_log", - default=False, - help="tail the portal and server logfiles and print to stdout", - ) - parser.add_argument( - "--list", - nargs="+", - action="store", - dest="listsetting", - metavar="all|<key>", - help=("list settings, use 'all' to list all available keys"), - ) - parser.add_argument( - "--settings", - nargs=1, - action="store", - dest="altsettings", - default=None, - metavar="<path>", - help=( - "start evennia with alternative settings file from\n" - " gamedir/server/conf/. (default is settings.py)" - ), - ) - parser.add_argument( - "--initsettings", - action="store_true", - dest="initsettings", - default=False, - help="create a new, empty settings file as\n gamedir/server/conf/settings.py", - ) - parser.add_argument( - "--initmissing", - action="store_true", - dest="initmissing", - default=False, - help="checks for missing secret_settings or server logs\n directory, and adds them if needed", - ) - parser.add_argument( - "--profiler", - action="store_true", - dest="profiler", - default=False, - help="start given server component under the Python profiler", - ) - parser.add_argument( - "--dummyrunner", - nargs=1, - action="store", - dest="dummyrunner", - metavar="<N>", - help="test a server by connecting <N> dummy accounts to it", - ) - parser.add_argument( - "-v", - "--version", - action="store_true", - dest="show_version", - default=False, - help="show version info", - ) - - parser.add_argument("operation", nargs="?", default="noop", help=ARG_OPTIONS) - parser.epilog = ( - "Common Django-admin commands are shell, dbshell, test and migrate.\n" - "See the Django documentation for more management commands." - ) - - args, unknown_args = parser.parse_known_args() - - # handle arguments - option = args.operation - - # make sure we have everything - check_main_evennia_dependencies() - - if not args: - # show help pane - print(CMDLINE_HELP) - sys.exit() - - if args.altgamedir: - # use alternative gamedir path - global GAMEDIR - altgamedir = args.altgamedir[0] - if not os.path.isdir(altgamedir) and not args.init: - print(ERROR_NO_ALT_GAMEDIR.format(gamedir=altgamedir)) - sys.exit() - GAMEDIR = altgamedir - - if args.init: - # initialization of game directory - create_game_directory(args.init) - print( - CREATED_NEW_GAMEDIR.format( - gamedir=args.init, settings_path=os.path.join(args.init, SETTINGS_PATH) - ) - ) - sys.exit() - - if args.show_version: - # show the version info - print(show_version_info(option == "help")) - sys.exit() - - if args.altsettings: - # use alternative settings file - global SETTINGSFILE, SETTINGS_DOTPATH, ENFORCED_SETTING - sfile = args.altsettings[0] - SETTINGSFILE = sfile - ENFORCED_SETTING = True - SETTINGS_DOTPATH = "server.conf.%s" % sfile.rstrip(".py") - print("Using settings file '%s' (%s)." % (SETTINGSFILE, SETTINGS_DOTPATH)) - - if args.initsettings: - # create new settings file - try: - create_settings_file(init=False) - print(RECREATED_SETTINGS) - except IOError: - print(ERROR_INITSETTINGS) - sys.exit() - - if args.initmissing: - created = False - try: - log_path = os.path.join(SERVERDIR, "logs") - if not os.path.exists(log_path): - os.makedirs(log_path) - print(f" ... Created missing log dir {log_path}.") - created = True - - settings_path = os.path.join(CONFDIR, "secret_settings.py") - if not os.path.exists(settings_path): - create_settings_file(init=False, secret_settings=True) - print(f" ... Created missing secret_settings.py file as {settings_path}.") - created = True - - if created: - print(RECREATED_MISSING) - else: - print(" ... No missing resources to create/init. You are good to go.") - except IOError: - print(ERROR_INITMISSING) - sys.exit() - - if args.tail_log: - # set up for tailing the log files - global NO_REACTOR_STOP - NO_REACTOR_STOP = True - if not SERVER_LOGFILE: - init_game_directory(CURRENT_DIR, check_db=False) - - # adjust how many lines we show from existing logs - start_lines1, start_lines2 = 20, 20 - if option not in ("reload", "reset", "noop"): - start_lines1, start_lines2 = 0, 0 - - tail_log_files(PORTAL_LOGFILE, SERVER_LOGFILE, start_lines1, start_lines2) - print( - " Tailing logfiles {} (Ctrl-C to exit) ...".format( - _file_names_compact(SERVER_LOGFILE, PORTAL_LOGFILE) - ) - ) - if args.dummyrunner: - # launch the dummy runner - init_game_directory(CURRENT_DIR, check_db=True) - run_dummyrunner(args.dummyrunner[0]) - elif args.listsetting: - # display all current server settings - init_game_directory(CURRENT_DIR, check_db=False) - list_settings(args.listsetting) - elif option == "menu": - # launch menu for operation - init_game_directory(CURRENT_DIR, check_db=True) - run_menu() - elif option in ( - "status", - "info", - "start", - "istart", - "ipstart", - "reload", - "restart", - "reboot", - "reset", - "stop", - "sstop", - "kill", - "skill", - "sstart", - "connections", - ): - # operate the server directly - if not SERVER_LOGFILE: - init_game_directory(CURRENT_DIR, check_db=True) - if option == "status": - query_status() - elif option == "info": - query_info() - elif option == "start": - init_game_directory(CURRENT_DIR, check_db=True) - error_check_python_modules(show_warnings=args.tail_log) - start_evennia(args.profiler, args.profiler) - elif option == "istart": - init_game_directory(CURRENT_DIR, check_db=True) - error_check_python_modules(show_warnings=args.tail_log) - start_server_interactive() - elif option == "ipstart": - start_portal_interactive() - elif option in ("reload", "restart"): - reload_evennia(args.profiler) - elif option == "reboot": - reboot_evennia(args.profiler, args.profiler) - elif option == "reset": - reload_evennia(args.profiler, reset=True) - elif option == "stop": - stop_evennia() - elif option == "sstop": - stop_server_only() - elif option == "sstart": - start_only_server() - elif option == "kill": - if _is_windows(): - print("This option is not supported on Windows.") - else: - kill(SERVER_PIDFILE, "Server") - kill(PORTAL_PIDFILE, "Portal") - elif option == "skill": - if _is_windows(): - print("This option is not supported on Windows.") - else: - kill(SERVER_PIDFILE, "Server") - elif option == "connections": - run_connect_wizard() - - elif option != "noop": - # pass-through to django manager, but set things up first - check_db = False - need_gamedir = True - - # handle special django commands - if option in ("runserver", "testserver"): - # we don't want the django test-webserver - print(WARNING_RUNSERVER) - if option in ("makemessages", "compilemessages"): - # some commands don't require the presence of a game directory to work - need_gamedir = False - if option in ("shell", "check", "makemigrations", "createsuperuser"): - # some django commands requires the database to exist, - # or evennia._init to have run before they work right. - check_db = True - if option == "test": - global TEST_MODE - TEST_MODE = True - - init_game_directory(CURRENT_DIR, check_db=check_db, need_gamedir=need_gamedir) - - if option == "migrate": - # we need to bypass some checks here for the first db creation - if not check_database(always_return=True): - django.core.management.call_command(*([option] + unknown_args)) - sys.exit(0) - - # pass on to the core django manager - re-parse the entire input line - # but keep 'evennia' as the name instead of django-admin. This is - # an exit condition. - sys.argv[0] = re.sub(r"(-script\.pyw?|\.exe)?$", "", sys.argv[0]) - sys.exit(execute_from_command_line(sys.argv)) - - elif not args.tail_log: - # no input; print evennia info (don't pring if we're tailing log) - print(ABOUT_INFO) - - if REACTOR_RUN: - reactor.run()
- - -if __name__ == "__main__": - # start Evennia from the command line - main() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/game_index_client/client.html b/docs/0.9.5/_modules/evennia/server/game_index_client/client.html deleted file mode 100644 index 8f17143393..0000000000 --- a/docs/0.9.5/_modules/evennia/server/game_index_client/client.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - - evennia.server.game_index_client.client — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.game_index_client.client

-"""
-The client for sending data to the Evennia Game Index
-
-"""
-import urllib.request, urllib.parse, urllib.error
-import platform
-import warnings
-
-import django
-from django.conf import settings
-from twisted.internet import defer
-from twisted.internet import protocol
-from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks
-from twisted.web.client import Agent, _HTTP11ClientFactory, HTTPConnectionPool
-from twisted.web.http_headers import Headers
-from twisted.web.iweb import IBodyProducer
-from zope.interface import implementer
-
-from evennia.accounts.models import AccountDB
-from evennia.server.sessionhandler import SESSIONS
-from evennia.utils import get_evennia_version, logger
-
-_EGI_HOST = "http://evennia-game-index.appspot.com"
-_EGI_REPORT_PATH = "/api/v1/game/check_in"
-
-
-
[docs]class EvenniaGameIndexClient(object): - """ - This client class is used for gathering and sending game details to the - Evennia Game Index. Since EGI is in the early goings, this isn't - incredibly configurable as far as to what is being sent. - """ - -
[docs] def __init__(self, on_bad_request=None): - """ - :param on_bad_request: Optional callable to trigger when a bad request - was sent. This is almost always going to be due to bad config. - """ - self.report_host = _EGI_HOST - self.report_path = _EGI_REPORT_PATH - self.report_url = self.report_host + self.report_path - self.logged_first_connect = False - - self._on_bad_request = on_bad_request - # Oh, the humanity. Silence the factory start/stop messages. - self._conn_pool = HTTPConnectionPool(reactor) - self._conn_pool._factory = QuietHTTP11ClientFactory
- -
[docs] @inlineCallbacks - def send_game_details(self): - """ - This is where the magic happens. Send details about the game to the - Evennia Game Index. - """ - status_code, response_body = yield self._form_and_send_request() - if status_code == 200: - if not self.logged_first_connect: - logger.log_infomsg("Successfully sent game details to Evennia Game Index.") - self.logged_first_connect = True - return - # At this point, either EGD is having issues or the payload we sent - # is improperly formed (probably due to mis-configuration). - logger.log_errmsg( - "Failed to send game details to Evennia Game Index. HTTP " - "status code was %s. Message was: %s" % (status_code, response_body) - ) - - if status_code == 400 and self._on_bad_request: - # Improperly formed request. Defer to the callback as far as what - # to do. Probably not a great idea to continue attempting to send - # to EGD, though. - self._on_bad_request()
- - def _form_and_send_request(self): - """ - Build the request to send to the index. - - """ - agent = Agent(reactor, pool=self._conn_pool) - headers = { - b"User-Agent": [b"Evennia Game Index Client"], - b"Content-Type": [b"application/x-www-form-urlencoded"], - } - egi_config = settings.GAME_INDEX_LISTING - # We are using `or` statements below with dict.get() to avoid sending - # stringified 'None' values to the server. - try: - values = { - # Game listing stuff - "game_name": egi_config.get("game_name", settings.SERVERNAME), - "game_status": egi_config["game_status"], - "game_website": egi_config.get("game_website", ""), - "short_description": egi_config["short_description"], - "long_description": egi_config.get("long_description", ""), - "listing_contact": egi_config["listing_contact"], - # How to play - "telnet_hostname": egi_config.get("telnet_hostname", ""), - "telnet_port": egi_config.get("telnet_port", ""), - "web_client_url": egi_config.get("web_client_url", ""), - # Game stats - "connected_account_count": SESSIONS.account_count(), - "total_account_count": AccountDB.objects.num_total_accounts() or 0, - # System info - "evennia_version": get_evennia_version(), - "python_version": platform.python_version(), - "django_version": django.get_version(), - "server_platform": platform.platform(), - } - except KeyError as err: - raise KeyError(f"Error loading GAME_INDEX_LISTING: {err}") - - data = urllib.parse.urlencode(values) - - d = agent.request( - b"POST", - bytes(self.report_url, "utf-8"), - headers=Headers(headers), - bodyProducer=StringProducer(data), - ) - - d.addCallback(self.handle_egd_response) - return d - -
[docs] def handle_egd_response(self, response): - if 200 <= response.code < 300: - d = defer.succeed((response.code, "OK")) - else: - # Go through the horrifying process of getting the response body - # out of Twisted's plumbing. - d = defer.Deferred() - response.deliverBody(SimpleResponseReceiver(response.code, d)) - return d
- - -
[docs]class SimpleResponseReceiver(protocol.Protocol): - """ - Used for pulling the response body out of an HTTP response. - """ - -
[docs] def __init__(self, status_code, d): - self.status_code = status_code - self.buf = "" - self.d = d
- -
[docs] def dataReceived(self, data): - self.buf += data
- -
[docs] def connectionLost(self, reason=protocol.connectionDone): - self.d.callback((self.status_code, self.buf))
- - -
[docs]@implementer(IBodyProducer) -class StringProducer(object): - """ - Used for feeding a request body to the tx HTTP client. - """ - -
[docs] def __init__(self, body): - self.body = bytes(body, "utf-8") - self.length = len(body)
- -
[docs] def startProducing(self, consumer): - consumer.write(self.body) - return defer.succeed(None)
- -
[docs] def pauseProducing(self): - pass
- -
[docs] def stopProducing(self): - pass
- - -
[docs]class QuietHTTP11ClientFactory(_HTTP11ClientFactory): - """ - Silences the obnoxious factory start/stop messages in the default client. - """ - - noisy = False
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/game_index_client/service.html b/docs/0.9.5/_modules/evennia/server/game_index_client/service.html deleted file mode 100644 index 7103e7cf78..0000000000 --- a/docs/0.9.5/_modules/evennia/server/game_index_client/service.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - evennia.server.game_index_client.service — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.game_index_client.service

-"""
-Service for integrating the Evennia Game Index client into Evennia.
-
-"""
-from twisted.internet import reactor
-from twisted.internet.task import LoopingCall
-from twisted.application.service import Service
-
-from evennia.utils import logger
-from .client import EvenniaGameIndexClient
-
-# How many seconds to wait before triggering the first EGI check-in.
-_FIRST_UPDATE_DELAY = 10
-# How often to sync to the server
-_CLIENT_UPDATE_RATE = 60 * 30
-
-
-
[docs]class EvenniaGameIndexService(Service): - """ - Twisted Service that contains a LoopingCall for regularly sending game details - to the Evennia Game Index. - - """ - - # We didn't stick the Evennia prefix on here because it'd get marked as - # a core system service. - name = "GameIndexClient" - -
[docs] def __init__(self): - self.client = EvenniaGameIndexClient(on_bad_request=self._die_on_bad_request) - self.loop = LoopingCall(self.client.send_game_details)
- -
[docs] def startService(self): - super().startService() - # Check to make sure that the client is configured. - # Start the loop, but only after a short delay. This allows the - # portal and the server time to sync up as far as total player counts. - # Prevents always reporting a count of 0. - reactor.callLater(_FIRST_UPDATE_DELAY, self.loop.start, _CLIENT_UPDATE_RATE)
- -
[docs] def stopService(self): - if self.running == 0: - # reload errors if we've stopped this service. - return - super().stopService() - if self.loop.running: - self.loop.stop()
- - def _die_on_bad_request(self): - """ - If it becomes apparent that our configuration is generating improperly - formed messages to EGI, we don't want to keep sending bad messages. - Stop the service so we're not wasting resources. - """ - logger.log_infomsg( - "Shutting down Evennia Game Index client service due to " "invalid configuration." - ) - self.stopService()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/initial_setup.html b/docs/0.9.5/_modules/evennia/server/initial_setup.html deleted file mode 100644 index d2be1dcb47..0000000000 --- a/docs/0.9.5/_modules/evennia/server/initial_setup.html +++ /dev/null @@ -1,345 +0,0 @@ - - - - - - - - evennia.server.initial_setup — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.initial_setup

-"""
-This module handles initial database propagation, which is only run the first
-time the game starts. It will create some default channels, objects, and
-other things.
-
-Everything starts at handle_setup()
-"""
-
-
-import time
-from django.conf import settings
-from django.utils.translation import gettext as _
-from evennia.accounts.models import AccountDB
-from evennia.server.models import ServerConfig
-from evennia.utils import create, logger
-
-
-ERROR_NO_SUPERUSER = """
-    No superuser exists yet. The superuser is the 'owner' account on
-    the Evennia server. Create a new superuser using the command
-
-       evennia createsuperuser
-
-    Follow the prompts, then restart the server.
-    """
-
-
-LIMBO_DESC = _(
-    """
-Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need
-help, want to contribute, report issues or just join the community.
-As Account #1 you can create a demo/tutorial area with '|wbatchcommand tutorial_world.build|n'.
-    """
-)
-
-
-WARNING_POSTGRESQL_FIX = """
-    PostgreSQL-psycopg2 compatibility fix:
-    The in-game channels {chan1}, {chan2} and {chan3} were created,
-    but the superuser was not yet connected to them. Please use in
-    game commands to connect Account #1 to those channels when first
-    logging in.
-    """
-
-
-
[docs]def get_god_account(): - """ - Creates the god user and don't take no for an answer. - - """ - try: - god_account = AccountDB.objects.get(id=1) - except AccountDB.DoesNotExist: - raise AccountDB.DoesNotExist(ERROR_NO_SUPERUSER) - return god_account
- - -
[docs]def create_objects(): - """ - Creates the #1 account and Limbo room. - - """ - - logger.log_info("Initial setup: Creating objects (Account #1 and Limbo room) ...") - - # Set the initial User's account object's username on the #1 object. - # This object is pure django and only holds name, email and password. - god_account = get_god_account() - - # Create an Account 'user profile' object to hold eventual - # mud-specific settings for the AccountDB object. - account_typeclass = settings.BASE_ACCOUNT_TYPECLASS - - # run all creation hooks on god_account (we must do so manually - # since the manage.py command does not) - god_account.swap_typeclass(account_typeclass, clean_attributes=True) - god_account.basetype_setup() - god_account.at_account_creation() - god_account.locks.add( - "examine:perm(Developer);edit:false();delete:false();boot:false();msg:all()" - ) - # this is necessary for quelling to work correctly. - god_account.permissions.add("Developer") - - # Limbo is the default "nowhere" starting room - - # Create the in-game god-character for account #1 and set - # it to exist in Limbo. - character_typeclass = settings.BASE_CHARACTER_TYPECLASS - god_character = create.create_object(character_typeclass, key=god_account.username, nohome=True) - - god_character.id = 1 - god_character.save() - god_character.db.desc = _("This is User #1.") - god_character.locks.add( - "examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()" - ) - # we set this low so that quelling is more useful - god_character.permissions.add("Player") - - god_account.attributes.add("_first_login", True) - god_account.attributes.add("_last_puppet", god_character) - - try: - god_account.db._playable_characters.append(god_character) - except AttributeError: - god_account.db_playable_characters = [god_character] - - room_typeclass = settings.BASE_ROOM_TYPECLASS - limbo_obj = create.create_object(room_typeclass, _("Limbo"), nohome=True) - limbo_obj.id = 2 - limbo_obj.save() - limbo_obj.db.desc = LIMBO_DESC.strip() - limbo_obj.save() - - # Now that Limbo exists, try to set the user up in Limbo (unless - # the creation hooks already fixed this). - if not god_character.location: - god_character.location = limbo_obj - if not god_character.home: - god_character.home = limbo_obj
- - -
[docs]def create_channels(): - """ - Creates some sensible default channels. - - """ - logger.log_info("Initial setup: Creating default channels ...") - - goduser = get_god_account() - - channel_mudinfo = settings.CHANNEL_MUDINFO - if not channel_mudinfo: - raise RuntimeError("settings.CHANNEL_MUDINFO must be defined.") - channel = create.create_channel(**channel_mudinfo) - channel.connect(goduser) - - channel_connectinfo = settings.CHANNEL_CONNECTINFO - if channel_connectinfo: - channel = create.create_channel(**channel_connectinfo) - - for channeldict in settings.DEFAULT_CHANNELS: - channel = create.create_channel(**channeldict) - channel.connect(goduser)
- - -
[docs]def at_initial_setup(): - """ - Custom hook for users to overload some or all parts of the initial - setup. Called very last in the sequence. It tries to import and - srun a module settings.AT_INITIAL_SETUP_HOOK_MODULE and will fail - silently if this does not exist or fails to load. - - """ - modname = settings.AT_INITIAL_SETUP_HOOK_MODULE - if not modname: - return - try: - mod = __import__(modname, fromlist=[None]) - except (ImportError, ValueError): - return - logger.log_info("Initial setup: Running at_initial_setup() hook.") - if mod.__dict__.get("at_initial_setup", None): - mod.at_initial_setup()
- - -
[docs]def collectstatic(): - """ - Run collectstatic to make sure all web assets are loaded. - - """ - from django.core.management import call_command - - logger.log_info("Initial setup: Gathering static resources using 'collectstatic'") - call_command("collectstatic", "--noinput")
- - -
[docs]def reset_server(): - """ - We end the initialization by resetting the server. This makes sure - the first login is the same as all the following ones, - particularly it cleans all caches for the special objects. It - also checks so the warm-reset mechanism works as it should. - - """ - ServerConfig.objects.conf("server_epoch", time.time()) - from evennia.server.sessionhandler import SESSIONS - - logger.log_info("Initial setup complete. Restarting Server once.") - SESSIONS.portal_reset_server()
- - -
[docs]def handle_setup(last_step): - """ - Main logic for the module. It allows for restarting the - initialization at any point if one of the modules should crash. - - Args: - last_step (int): The last stored successful step, for starting - over on errors. If `< 0`, initialization has finished and no - steps need to be redone. - - """ - - if last_step < 0: - # this means we don't need to handle setup since - # it already ran sucessfully once. - return - # if None, set it to 0 - last_step = last_step or 0 - - # setting up the list of functions to run - setup_queue = [create_objects, create_channels, at_initial_setup, collectstatic, reset_server] - - # step through queue, from last completed function - for num, setup_func in enumerate(setup_queue[last_step:]): - # run the setup function. Note that if there is a - # traceback we let it stop the system so the config - # step is not saved. - - try: - setup_func() - except Exception: - if last_step + num == 1: - from evennia.objects.models import ObjectDB - - for obj in ObjectDB.objects.all(): - obj.delete() - elif last_step + num == 2: - from evennia.comms.models import ChannelDB - - ChannelDB.objects.all().delete() - raise - # save this step - ServerConfig.objects.conf("last_initial_setup_step", last_step + num + 1) - # We got through the entire list. Set last_step to -1 so we don't - # have to run this again. - ServerConfig.objects.conf("last_initial_setup_step", -1)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/inputfuncs.html b/docs/0.9.5/_modules/evennia/server/inputfuncs.html deleted file mode 100644 index 7fc35beaed..0000000000 --- a/docs/0.9.5/_modules/evennia/server/inputfuncs.html +++ /dev/null @@ -1,733 +0,0 @@ - - - - - - - - evennia.server.inputfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.inputfuncs

-"""
-Functions for processing input commands.
-
-All global functions in this module whose name does not start with "_"
-is considered an inputfunc. Each function must have the following
-callsign (where inputfunc name is always lower-case, no matter what the
-OOB input name looked like):
-
-    inputfunc(session, *args, **kwargs)
-
-Where "options" is always one of the kwargs, containing eventual
-protocol-options.
-There is one special function, the "default" function, which is called
-on a no-match. It has this callsign:
-
-    default(session, cmdname, *args, **kwargs)
-
-Evennia knows which modules to use for inputfuncs by
-settings.INPUT_FUNC_MODULES.
-
-"""
-
-import importlib
-from codecs import lookup as codecs_lookup
-from django.conf import settings
-from evennia.commands.cmdhandler import cmdhandler
-from evennia.accounts.models import AccountDB
-from evennia.utils.logger import log_err
-from evennia.utils.utils import to_str
-
-BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore
-
-
-# always let "idle" work since we use this in the webclient
-_IDLE_COMMAND = settings.IDLE_COMMAND
-_IDLE_COMMAND = (_IDLE_COMMAND,) if _IDLE_COMMAND == "idle" else (_IDLE_COMMAND, "idle")
-_GA = object.__getattribute__
-_SA = object.__setattr__
-
-
-def _NA(o):
-    return "N/A"
-
-
-_ERROR_INPUT = "Inputfunc {name}({session}): Wrong/unrecognized input: {inp}"
-
-
-# All global functions are inputfuncs available to process inputs
-
-
-
[docs]def text(session, *args, **kwargs): - """ - Main text input from the client. This will execute a command - string on the server. - - Args: - session (Session): The active Session to receive the input. - text (str): First arg is used as text-command input. Other - arguments are ignored. - - """ - # from evennia.server.profiling.timetrace import timetrace - # text = timetrace(text, "ServerSession.data_in") - - txt = args[0] if args else None - - # explicitly check for None since text can be an empty string, which is - # also valid - if txt is None: - return - # this is treated as a command input - # handle the 'idle' command - if txt.strip() in _IDLE_COMMAND: - session.update_session_counters(idle=True) - return - if session.account: - # nick replacement - puppet = session.puppet - if puppet: - txt = puppet.nicks.nickreplace( - txt, categories=("inputline", "channel"), include_account=True - ) - else: - txt = session.account.nicks.nickreplace( - txt, categories=("inputline", "channel"), include_account=False - ) - kwargs.pop("options", None) - cmdhandler(session, txt, callertype="session", session=session, **kwargs) - session.update_session_counters()
- - -
[docs]def bot_data_in(session, *args, **kwargs): - """ - Text input from the IRC and RSS bots. - This will trigger the execute_cmd method on the bots in-game counterpart. - - Args: - session (Session): The active Session to receive the input. - text (str): First arg is text input. Other arguments are ignored. - - """ - - txt = args[0] if args else None - - # Explicitly check for None since text can be an empty string, which is - # also valid - if txt is None: - return - # this is treated as a command input - # handle the 'idle' command - if txt.strip() in _IDLE_COMMAND: - session.update_session_counters(idle=True) - return - kwargs.pop("options", None) - # Trigger the execute_cmd method of the corresponding bot. - session.account.execute_cmd(session=session, txt=txt, **kwargs) - session.update_session_counters()
- - -
[docs]def echo(session, *args, **kwargs): - """ - Echo test function - """ - session.data_out(text="Echo returns: %s" % args)
- - -
[docs]def default(session, cmdname, *args, **kwargs): - """ - Default catch-function. This is like all other input functions except - it will get `cmdname` as the first argument. - - """ - err = ( - "Session {sessid}: Input command not recognized:\n" - " name: '{cmdname}'\n" - " args, kwargs: {args}, {kwargs}".format( - sessid=session.sessid, cmdname=cmdname, args=args, kwargs=kwargs - ) - ) - if session.protocol_flags.get("INPUTDEBUG", False): - session.msg(err) - log_err(err)
- - -_CLIENT_OPTIONS = ( - "ANSI", - "XTERM256", - "MXP", - "UTF-8", - "SCREENREADER", - "ENCODING", - "MCCP", - "SCREENHEIGHT", - "SCREENWIDTH", - "INPUTDEBUG", - "RAW", - "NOCOLOR", - "NOGOAHEAD", -) - - -
[docs]def client_options(session, *args, **kwargs): - """ - This allows the client an OOB way to inform us about its name and capabilities. - This will be integrated into the session settings - - Keyword Args: - get (bool): If this is true, return the settings as a dict - (ignore all other kwargs). - client (str): A client identifier, like "mushclient". - version (str): A client version - ansi (bool): Supports ansi colors - xterm256 (bool): Supports xterm256 colors or not - mxp (bool): Supports MXP or not - utf-8 (bool): Supports UTF-8 or not - screenreader (bool): Screen-reader mode on/off - mccp (bool): MCCP compression on/off - screenheight (int): Screen height in lines - screenwidth (int): Screen width in characters - inputdebug (bool): Debug input functions - nocolor (bool): Strip color - raw (bool): Turn off parsing - - """ - old_flags = session.protocol_flags - if not kwargs or kwargs.get("get", False): - # return current settings - options = dict((key, old_flags[key]) for key in old_flags if key.upper() in _CLIENT_OPTIONS) - session.msg(client_options=options) - return - - def validate_encoding(val): - # helper: change encoding - try: - codecs_lookup(val) - except LookupError: - raise RuntimeError("The encoding '|w%s|n' is invalid. " % val) - return val - - def validate_size(val): - return {0: int(val)} - - def validate_bool(val): - if isinstance(val, str): - return True if val.lower() in ("true", "on", "1") else False - return bool(val) - - flags = {} - for key, value in kwargs.items(): - key = key.lower() - if key == "client": - flags["CLIENTNAME"] = to_str(value) - elif key == "version": - if "CLIENTNAME" in flags: - flags["CLIENTNAME"] = "%s %s" % (flags["CLIENTNAME"], to_str(value)) - elif key == "ENCODING": - flags["ENCODING"] = validate_encoding(value) - elif key == "ansi": - flags["ANSI"] = validate_bool(value) - elif key == "xterm256": - flags["XTERM256"] = validate_bool(value) - elif key == "mxp": - flags["MXP"] = validate_bool(value) - elif key == "utf-8": - flags["UTF-8"] = validate_bool(value) - elif key == "screenreader": - flags["SCREENREADER"] = validate_bool(value) - elif key == "mccp": - flags["MCCP"] = validate_bool(value) - elif key == "screenheight": - flags["SCREENHEIGHT"] = validate_size(value) - elif key == "screenwidth": - flags["SCREENWIDTH"] = validate_size(value) - elif key == "inputdebug": - flags["INPUTDEBUG"] = validate_bool(value) - elif key == "nocolor": - flags["NOCOLOR"] = validate_bool(value) - elif key == "raw": - flags["RAW"] = validate_bool(value) - elif key == "nogoahead": - flags["NOGOAHEAD"] = validate_bool(value) - elif key in ( - "Char 1", - "Char.Skills 1", - "Char.Items 1", - "Room 1", - "IRE.Rift 1", - "IRE.Composer 1", - ): - # ignore mudlet's default send (aimed at IRE games) - pass - elif key not in ("options", "cmdid"): - err = _ERROR_INPUT.format(name="client_settings", session=session, inp=key) - session.msg(text=err) - - session.protocol_flags.update(flags) - # we must update the protocol flags on the portal session copy as well - session.sessionhandler.session_portal_partial_sync({session.sessid: {"protocol_flags": flags}})
- - -
[docs]def get_client_options(session, *args, **kwargs): - """ - Alias wrapper for getting options. - """ - client_options(session, get=True)
- - -
[docs]def get_inputfuncs(session, *args, **kwargs): - """ - Get the keys of all available inputfuncs. Note that we don't get - it from this module alone since multiple modules could be added. - So we get it from the sessionhandler. - """ - inputfuncsdict = dict( - (key, func.__doc__) for key, func in session.sessionhandler.get_inputfuncs().items() - ) - session.msg(get_inputfuncs=inputfuncsdict)
- - -
[docs]def login(session, *args, **kwargs): - """ - Peform a login. This only works if session is currently not logged - in. This will also automatically throttle too quick attempts. - - Keyword Args: - name (str): Account name - password (str): Plain-text password - - """ - if not session.logged_in and "name" in kwargs and "password" in kwargs: - from evennia.commands.default.unloggedin import create_normal_account - - account = create_normal_account(session, kwargs["name"], kwargs["password"]) - if account: - session.sessionhandler.login(session, account)
- - -_gettable = { - "name": lambda obj: obj.key, - "key": lambda obj: obj.key, - "location": lambda obj: obj.location.key if obj.location else "None", - "servername": lambda obj: settings.SERVERNAME, -} - - -
[docs]def get_value(session, *args, **kwargs): - """ - Return the value of a given attribute or db_property on the - session's current account or character. - - Keyword Args: - name (str): Name of info value to return. Only names - in the _gettable dictionary earlier in this module - are accepted. - - """ - name = kwargs.get("name", "") - obj = session.puppet or session.account - if name in _gettable: - session.msg(get_value={"name": name, "value": _gettable[name](obj)})
- - -def _testrepeat(**kwargs): - """ - This is a test function for using with the repeat - inputfunc. - - Keyword Args: - session (Session): Session to return to. - """ - import time - - kwargs["session"].msg(repeat="Repeat called: %s" % time.time()) - - -_repeatable = {"test1": _testrepeat, "test2": _testrepeat} # example only # " - - -
[docs]def repeat(session, *args, **kwargs): - """ - Call a named function repeatedly. Note that - this is meant as an example of limiting the number of - possible call functions. - - Keyword Args: - callback (str): The function to call. Only functions - from the _repeatable dictionary earlier in this - module are available. - interval (int): How often to call function (s). - Defaults to once every 60 seconds with a minimum - of 5 seconds. - stop (bool): Stop a previously assigned ticker with - the above settings. - - """ - from evennia.scripts.tickerhandler import TICKER_HANDLER - - name = kwargs.get("callback", "") - interval = max(5, int(kwargs.get("interval", 60))) - - if name in _repeatable: - if kwargs.get("stop", False): - TICKER_HANDLER.remove( - interval, _repeatable[name], idstring=session.sessid, persistent=False - ) - else: - TICKER_HANDLER.add( - interval, - _repeatable[name], - idstring=session.sessid, - persistent=False, - session=session, - ) - else: - session.msg("Allowed repeating functions are: %s" % (", ".join(_repeatable)))
- - -
[docs]def unrepeat(session, *args, **kwargs): - "Wrapper for OOB use" - kwargs["stop"] = True - repeat(session, *args, **kwargs)
- - -_monitorable = {"name": "db_key", "location": "db_location", "desc": "desc"} - - -def _on_monitor_change(**kwargs): - fieldname = kwargs["fieldname"] - obj = kwargs["obj"] - name = kwargs["name"] - session = kwargs["session"] - outputfunc_name = kwargs["outputfunc_name"] - - # the session may be None if the char quits and someone - # else then edits the object - - if session: - callsign = {outputfunc_name: {"name": name, "value": _GA(obj, fieldname)}} - session.msg(**callsign) - - -
[docs]def monitor(session, *args, **kwargs): - """ - Adds monitoring to a given property or Attribute. - - Keyword Args: - name (str): The name of the property or Attribute - to report. No db_* prefix is needed. Only names - in the _monitorable dict earlier in this module - are accepted. - stop (bool): Stop monitoring the above name. - outputfunc_name (str, optional): Change the name of - the outputfunc name. This is used e.g. by MSDP which - has its own specific output format. - - """ - from evennia.scripts.monitorhandler import MONITOR_HANDLER - - name = kwargs.get("name", None) - outputfunc_name = kwargs("outputfunc_name", "monitor") - if name and name in _monitorable and session.puppet: - field_name = _monitorable[name] - obj = session.puppet - if kwargs.get("stop", False): - MONITOR_HANDLER.remove(obj, field_name, idstring=session.sessid) - else: - # the handler will add fieldname and obj to the kwargs automatically - MONITOR_HANDLER.add( - obj, - field_name, - _on_monitor_change, - idstring=session.sessid, - persistent=False, - name=name, - session=session, - outputfunc_name=outputfunc_name, - )
- - -
[docs]def unmonitor(session, *args, **kwargs): - """ - Wrapper for turning off monitoring - """ - kwargs["stop"] = True - monitor(session, *args, **kwargs)
- - -
[docs]def monitored(session, *args, **kwargs): - """ - Report on what is being monitored - - """ - from evennia.scripts.monitorhandler import MONITOR_HANDLER - - obj = session.puppet - monitors = MONITOR_HANDLER.all(obj=obj) - session.msg(monitored=(monitors, {}))
- - -def _on_webclient_options_change(**kwargs): - """ - Called when the webclient options stored on the account changes. - Inform the interested clients of this change. - """ - session = kwargs["session"] - obj = kwargs["obj"] - fieldname = kwargs["fieldname"] - clientoptions = _GA(obj, fieldname) - - # the session may be None if the char quits and someone - # else then edits the object - if session: - session.msg(webclient_options=clientoptions) - - -
[docs]def webclient_options(session, *args, **kwargs): - """ - Handles retrieving and changing of options related to the webclient. - - If kwargs is empty (or contains just a "cmdid"), the saved options will be - sent back to the session. - A monitor handler will be created to inform the client of any future options - that changes. - - If kwargs is not empty, the key/values stored in there will be persisted - to the account object. - - Keyword Args: - <option name>: an option to save - - """ - account = session.account - - clientoptions = account.db._saved_webclient_options - if not clientoptions: - # No saved options for this account, copy and save the default. - account.db._saved_webclient_options = settings.WEBCLIENT_OPTIONS.copy() - # Get the _SaverDict created by the database. - clientoptions = account.db._saved_webclient_options - - # The webclient adds a cmdid to every kwargs, but we don't need it. - try: - del kwargs["cmdid"] - except KeyError: - pass - - if not kwargs: - # No kwargs: we are getting the stored options - # Convert clientoptions to regular dict for sending. - session.msg(webclient_options=dict(clientoptions)) - - # Create a monitor. If a monitor already exists then it will replace - # the previous one since it would use the same idstring - from evennia.scripts.monitorhandler import MONITOR_HANDLER - - MONITOR_HANDLER.add( - account, - "_saved_webclient_options", - _on_webclient_options_change, - idstring=session.sessid, - persistent=False, - session=session, - ) - else: - # kwargs provided: persist them to the account object. - clientoptions.update(kwargs)
- - -# OOB protocol-specific aliases and wrappers - -# GMCP aliases -hello = client_options -supports_set = client_options - - -# MSDP aliases (some of the the generic MSDP commands defined in the MSDP spec are prefixed -# by msdp_ at the protocol level) -# See https://tintin.sourceforge.io/protocols/msdp/ - - -
[docs]def msdp_list(session, *args, **kwargs): - """ - MSDP LIST command - - """ - from evennia.scripts.monitorhandler import MONITOR_HANDLER - - args_lower = [arg.lower() for arg in args] - if "commands" in args_lower: - inputfuncs = [ - key[5:] if key.startswith("msdp_") else key - for key in session.sessionhandler.get_inputfuncs().keys() - ] - session.msg(commands=(inputfuncs, {})) - if "lists" in args_lower: - session.msg( - lists=( - [ - "commands", - "lists", - "configurable_variables", - "reportable_variables", - "reported_variables", - "sendable_variables", - ], - {}, - ) - ) - if "configurable_variables" in args_lower: - session.msg(configurable_variables=(_CLIENT_OPTIONS, {})) - if "reportable_variables" in args_lower: - session.msg(reportable_variables=(_monitorable, {})) - if "reported_variables" in args_lower: - obj = session.puppet - monitor_infos = MONITOR_HANDLER.all(obj=obj) - fieldnames = [tup[1] for tup in monitor_infos] - session.msg(reported_variables=(fieldnames, {})) - if "sendable_variables" in args_lower: - session.msg(sendable_variables=(_monitorable, {}))
- - -
[docs]def msdp_report(session, *args, **kwargs): - """ - MSDP REPORT command - - """ - kwargs["outputfunc_name":"report"] - monitor(session, *args, **kwargs)
- - -
[docs]def msdp_unreport(session, *args, **kwargs): - """ - MSDP UNREPORT command - - """ - unmonitor(session, *args, **kwargs)
- - -
[docs]def msdp_send(session, *args, **kwargs): - """ - MSDP SEND command - """ - out = {} - for varname in args: - if varname.lower() in _monitorable: - out[varname] = _monitorable[varname.lower()] - session.msg(send=((), out))
- - -# client specific - - -def _not_implemented(session, *args, **kwargs): - """ - Dummy used to swallow missing-inputfunc errors for - common clients. - """ - pass - - -# GMCP External.Discord.Hello is sent by Mudlet as a greeting -# (see https://wiki.mudlet.org/w/Manual:Technical_Manual) -external_discord_hello = _not_implemented - - -# GMCP Client.Gui is sent by Mudlet for gui setup. -client_gui = _not_implemented -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/manager.html b/docs/0.9.5/_modules/evennia/server/manager.html deleted file mode 100644 index f516938168..0000000000 --- a/docs/0.9.5/_modules/evennia/server/manager.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - evennia.server.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.manager

-"""
-Custom manager for ServerConfig objects.
-"""
-from django.db import models
-
-
-
[docs]class ServerConfigManager(models.Manager): - """ - This ServerConfigManager implements methods for searching and - manipulating ServerConfigs directly from the database. - - These methods will all return database objects (or QuerySets) - directly. - - ServerConfigs are used to store certain persistent settings for - the server at run-time. - - """ - -
[docs] def conf(self, key=None, value=None, delete=False, default=None): - """ - Add, retrieve and manipulate config values. - - Args: - key (str, optional): Name of config. - value (str, optional): Data to store in this config value. - delete (bool, optional): If `True`, delete config with `key`. - default (str, optional): Use when retrieving a config value - by a key that does not exist. - Returns: - all (list): If `key` was not given - all stored config values. - value (str): If `key` was given, this is the stored value, or - `default` if no matching `key` was found. - - """ - if not key: - return self.all() - elif delete is True: - for conf in self.filter(db_key=key): - conf.delete() - elif value is not None: - conf = self.filter(db_key=key) - if conf: - conf = conf[0] - else: - conf = self.model(db_key=key) - conf.value = value # this will pickle - else: - conf = self.filter(db_key=key) - if not conf: - return default - return conf[0].value - return None
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/models.html b/docs/0.9.5/_modules/evennia/server/models.html deleted file mode 100644 index b8308ce7e8..0000000000 --- a/docs/0.9.5/_modules/evennia/server/models.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - - evennia.server.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.models

-"""
-
-Server Configuration flags
-
-This holds persistent server configuration flags.
-
-Config values should usually be set through the
-manager's conf() method.
-
-"""
-import pickle
-
-from django.db import models
-from evennia.utils.idmapper.models import WeakSharedMemoryModel
-from evennia.utils import logger, utils
-from evennia.utils.dbserialize import to_pickle, from_pickle
-from evennia.server.manager import ServerConfigManager
-from evennia.utils import picklefield
-
-
-# ------------------------------------------------------------
-#
-# ServerConfig
-#
-# ------------------------------------------------------------
-
-
-
[docs]class ServerConfig(WeakSharedMemoryModel): - """ - On-the fly storage of global settings. - - Properties defined on ServerConfig: - - - key: Main identifier - - value: Value stored in key. This is a pickled storage. - - """ - - # - # ServerConfig database model setup - # - # - # These database fields are all set using their corresponding properties, - # named same as the field, but without the db_* prefix. - - # main name of the database entry - db_key = models.CharField(max_length=64, unique=True) - # config value - # db_value = models.BinaryField(blank=True) - - db_value = picklefield.PickledObjectField( - "value", - null=True, - help_text="The data returned when the config value is accessed. Must be " - "written as a Python literal if editing through the admin " - "interface. Attribute values which are not Python literals " - "cannot be edited through the admin interface.", - ) - - objects = ServerConfigManager() - _is_deleted = False - - # Wrapper properties to easily set database fields. These are - # @property decorators that allows to access these fields using - # normal python operations (without having to remember to save() - # etc). So e.g. a property 'attr' has a get/set/del decorator - # defined that allows the user to do self.attr = value, - # value = self.attr and del self.attr respectively (where self - # is the object in question). - - # key property (wraps db_key) - # @property - def __key_get(self): - "Getter. Allows for value = self.key" - return self.db_key - - # @key.setter - def __key_set(self, value): - "Setter. Allows for self.key = value" - self.db_key = value - self.save() - - # @key.deleter - def __key_del(self): - "Deleter. Allows for del self.key. Deletes entry." - self.delete() - - key = property(__key_get, __key_set, __key_del) - - # value property (wraps db_value) - # @property - def __value_get(self): - "Getter. Allows for value = self.value" - return from_pickle(self.db_value, db_obj=self) - - # @value.setter - def __value_set(self, value): - "Setter. Allows for self.value = value" - if utils.has_parent("django.db.models.base.Model", value): - # we have to protect against storing db objects. - logger.log_err("ServerConfig cannot store db objects! (%s)" % value) - return - self.db_value = to_pickle(value) - self.save() - - # @value.deleter - def __value_del(self): - "Deleter. Allows for del self.value. Deletes entry." - self.delete() - - value = property(__value_get, __value_set, __value_del) - - class Meta(object): - "Define Django meta options" - verbose_name = "Server Config value" - verbose_name_plural = "Server Config values" - - # - # ServerConfig other methods - # - - def __repr__(self): - return "<{} {}>".format(self.__class__.__name__, self.key, self.value) - -
[docs] def store(self, key, value): - """ - Wrap the storage. - - Args: - key (str): The name of this store. - value (str): The data to store with this `key`. - - """ - self.key = key - self.value = value
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/amp.html b/docs/0.9.5/_modules/evennia/server/portal/amp.html deleted file mode 100644 index e5098b4b14..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/amp.html +++ /dev/null @@ -1,646 +0,0 @@ - - - - - - - - evennia.server.portal.amp — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.amp

-"""
-The AMP (Asynchronous Message Protocol)-communication commands and constants used by Evennia.
-
-This module acts as a central place for AMP-servers and -clients to get commands to use.
-
-"""
-
-from functools import wraps
-import time
-from twisted.protocols import amp
-from collections import defaultdict, namedtuple
-from io import BytesIO
-from itertools import count
-import zlib  # Used in Compressed class
-import pickle
-
-from twisted.internet.defer import DeferredList, Deferred
-from evennia.utils.utils import to_str, variable_from_module
-
-# delayed import
-_LOGGER = None
-
-# communication bits
-# (chr(9) and chr(10) are \t and \n, so skipping them)
-
-PCONN = chr(1)  # portal session connect
-PDISCONN = chr(2)  # portal session disconnect
-PSYNC = chr(3)  # portal session sync
-SLOGIN = chr(4)  # server session login
-SDISCONN = chr(5)  # server session disconnect
-SDISCONNALL = chr(6)  # server session disconnect all
-SSHUTD = chr(7)  # server shutdown
-SSYNC = chr(8)  # server session sync
-SCONN = chr(11)  # server creating new connection (for irc bots and etc)
-PCONNSYNC = chr(12)  # portal post-syncing a session
-PDISCONNALL = chr(13)  # portal session disconnect all
-SRELOAD = chr(14)  # server shutdown in reload mode
-SSTART = chr(15)  # server start (portal must already be running anyway)
-PSHUTD = chr(16)  # portal (+server) shutdown
-SSHUTD = chr(17)  # server shutdown
-PSTATUS = chr(18)  # ping server or portal status
-SRESET = chr(19)  # server shutdown in reset mode
-
-NUL = b"\x00"
-NULNUL = b"\x00\x00"
-
-AMP_MAXLEN = amp.MAX_VALUE_LENGTH  # max allowed data length in AMP protocol (cannot be changed)
-
-# amp internal
-ASK = b'_ask'
-ANSWER = b'_answer'
-ERROR = b'_error'
-ERROR_CODE = b'_error_code'
-ERROR_DESCRIPTION = b'_error_description'
-UNKNOWN_ERROR_CODE = b'UNKNOWN'
-
-# buffers
-_SENDBATCH = defaultdict(list)
-_MSGBUFFER = defaultdict(list)
-
-# resources
-
-DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0)
-
-
-_HTTP_WARNING = bytes(
-    """
-HTTP/1.1 200 OK
-Content-Type: text/html
-
-<html>
-  <body>
-    This is Evennia's internal AMP port. It handles communication
-    between Evennia's different processes.
-    <p>
-        <h3>This port should NOT be publicly visible.</h3>
-    </p>
-  </body>
-</html>""".strip(),
-    "utf-8",
-)
-
-
-# Helper functions for pickling.
-
-
-
[docs]def dumps(data): - return pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
- - -
[docs]def loads(data): - return pickle.loads(data)
- - -def _get_logger(): - "Delay import of logger until absolutely necessary" - global _LOGGER - if not _LOGGER: - from evennia.utils import logger as _LOGGER - return _LOGGER - - -@wraps -def catch_traceback(func): - "Helper decorator" - - def decorator(*args, **kwargs): - try: - func(*args, **kwargs) - except Exception as err: - _get_logger().log_trace() - raise # make sure the error is visible on the other side of the connection too - print(err) - - return decorator - - -# AMP Communication Command types - - -
[docs]class Compressed(amp.String): - """ - This is a custom AMP command Argument that both handles too-long - sends as well as uses zlib for compression across the wire. The - batch-grouping of too-long sends is borrowed from the "mediumbox" - recipy at twisted-hacks's ~glyph/+junk/amphacks/mediumbox. - - """ - -
[docs] def fromBox(self, name, strings, objects, proto): - """ - Converts from box string representation to python. We read back too-long batched data and - put it back together here. - - """ - - value = BytesIO() - value.write(self.fromStringProto(strings.get(name), proto)) - for counter in count(2): - # count from 2 upwards - chunk = strings.get(b"%s.%d" % (name, counter)) - if chunk is None: - break - value.write(self.fromStringProto(chunk, proto)) - objects[str(name, "utf-8")] = value.getvalue()
- -
[docs] def toBox(self, name, strings, objects, proto): - """ - Convert from python object to string box representation. - we break up too-long data snippets into multiple batches here. - - """ - - # print("toBox: name={}, strings={}, objects={}, proto{}".format(name, strings, objects, proto)) - - value = BytesIO(objects[str(name, "utf-8")]) - strings[name] = self.toStringProto(value.read(AMP_MAXLEN), proto) - - # print("toBox strings[name] = {}".format(strings[name])) - - for counter in count(2): - chunk = value.read(AMP_MAXLEN) - if not chunk: - break - strings[b"%s.%d" % (name, counter)] = self.toStringProto(chunk, proto)
- -
[docs] def toString(self, inObject): - """ - Convert to send as a bytestring on the wire, with compression. - - Note: In Py3 this is really a byte stream. - - """ - return zlib.compress(super(Compressed, self).toString(inObject), 9)
- -
[docs] def fromString(self, inString): - """ - Convert (decompress) from the string-representation on the wire to Python. - - """ - return super(Compressed, self).fromString(zlib.decompress(inString))
- - -
[docs]class MsgLauncher2Portal(amp.Command): - """ - Message Launcher -> Portal - - """ - - key = "MsgLauncher2Portal" - arguments = [(b"operation", amp.String()), (b"arguments", amp.String())] - errors = {Exception: b"EXCEPTION"} - response = []
- - -
[docs]class MsgPortal2Server(amp.Command): - """ - Message Portal -> Server - - """ - - key = b"MsgPortal2Server" - arguments = [(b"packed_data", Compressed())] - errors = {Exception: b"EXCEPTION"} - response = []
- - -
[docs]class MsgServer2Portal(amp.Command): - """ - Message Server -> Portal - - """ - - key = "MsgServer2Portal" - arguments = [(b"packed_data", Compressed())] - errors = {Exception: b"EXCEPTION"} - response = []
- - -
[docs]class AdminPortal2Server(amp.Command): - """ - Administration Portal -> Server - - Sent when the portal needs to perform admin operations on the - server, such as when a new session connects or resyncs - - """ - - key = "AdminPortal2Server" - arguments = [(b"packed_data", Compressed())] - errors = {Exception: b"EXCEPTION"} - response = []
- - -
[docs]class AdminServer2Portal(amp.Command): - """ - Administration Server -> Portal - - Sent when the server needs to perform admin operations on the - portal. - - """ - - key = "AdminServer2Portal" - arguments = [(b"packed_data", Compressed())] - errors = {Exception: b"EXCEPTION"} - response = []
- - -
[docs]class MsgStatus(amp.Command): - """ - Check Status between AMP services - - """ - - key = "MsgStatus" - arguments = [(b"status", amp.String())] - errors = {Exception: b"EXCEPTION"} - response = [(b"status", amp.String())]
- - -
[docs]class FunctionCall(amp.Command): - """ - Bidirectional Server <-> Portal - - Sent when either process needs to call an arbitrary function in - the other. This does not use the batch-send functionality. - - """ - - key = "FunctionCall" - arguments = [ - (b"module", amp.String()), - (b"function", amp.String()), - (b"args", amp.String()), - (b"kwargs", amp.String()), - ] - errors = {Exception: b"EXCEPTION"} - response = [(b"result", amp.String())]
- - -# ------------------------------------------------------------- -# Core AMP protocol for communication Server <-> Portal -# ------------------------------------------------------------- - - -
[docs]class AMPMultiConnectionProtocol(amp.AMP): - """ - AMP protocol that safely handle multiple connections to the same - server without dropping old ones - new clients will receive - all server returns (broadcast). Will also correctly handle - erroneous HTTP requests on the port and return a HTTP error response. - - """ - - # helper methods - -
[docs] def __init__(self, *args, **kwargs): - """ - Initialize protocol with some things that need to be in place - already before connecting both on portal and server. - - """ - self.send_batch_counter = 0 - self.send_reset_time = time.time() - self.send_mode = True - self.send_task = None - self.multibatches = 0 - # later twisted amp has its own __init__ - super(AMPMultiConnectionProtocol, self).__init__(*args, **kwargs)
- - def _commandReceived(self, box): - """ - This overrides the default Twisted AMP error handling which is not - passing enough of the traceback through to the other side. Instead we - add a specific log of the problem on the erroring side. - - """ - def formatAnswer(answerBox): - answerBox[ANSWER] = box[ASK] - return answerBox - - def formatError(error): - if error.check(amp.RemoteAmpError): - code = error.value.errorCode - desc = error.value.description - - # Evennia extra logging - desc += " (error logged on other side)" - _get_logger().log_err(f"AMP caught exception ({desc}):\n{error.value}") - - if isinstance(desc, str): - desc = desc.encode("utf-8", "replace") - if error.value.fatal: - errorBox = amp.QuitBox() - else: - errorBox = amp.AmpBox() - else: - errorBox = amp.QuitBox() - _get_logger().log_err(error) # server-side logging if unhandled error - code = UNKNOWN_ERROR_CODE - desc = b"Unknown Error" - errorBox[ERROR] = box[ASK] - errorBox[ERROR_DESCRIPTION] = desc - errorBox[ERROR_CODE] = code - return errorBox - deferred = self.dispatchCommand(box) - if ASK in box: - deferred.addCallbacks(formatAnswer, formatError) - deferred.addCallback(self._safeEmit) - deferred.addErrback(self.unhandledError) - -
[docs] def dataReceived(self, data): - """ - Handle non-AMP messages, such as HTTP communication. - """ - # print("dataReceived: {}".format(data)) - if data[:1] == NUL: - # an AMP communication - if data[-2:] != NULNUL: - # an incomplete AMP box means more batches are forthcoming. - self.multibatches += 1 - try: - super(AMPMultiConnectionProtocol, self).dataReceived(data) - except KeyError: - _get_logger().log_trace( - "Discarded incoming partial (packed) data (len {})".format(len(data)) - ) - elif self.multibatches: - # invalid AMP, but we have a pending multi-batch that is not yet complete - if data[-2:] == NULNUL: - # end of existing multibatch - self.multibatches = max(0, self.multibatches - 1) - try: - super(AMPMultiConnectionProtocol, self).dataReceived(data) - except KeyError: - _get_logger().log_trace( - "Discarded incoming multi-batch (packed) data (len {})".format(len(data)) - ) - else: - # not an AMP communication, return warning - self.transport.write(_HTTP_WARNING) - self.transport.loseConnection() - print("HTTP received (the AMP port should not receive http, only AMP!) %s" % data)
- -
[docs] def makeConnection(self, transport): - """ - Swallow connection log message here. Copied from original - in the amp protocol. - - """ - # copied from original, removing the log message - if not self._ampInitialized: - amp.AMP.__init__(self) - self._transportPeer = transport.getPeer() - self._transportHost = transport.getHost() - amp.BinaryBoxProtocol.makeConnection(self, transport)
- -
[docs] def connectionMade(self): - """ - This is called when an AMP connection is (re-)established. AMP calls it on both sides. - - """ - # print("connectionMade: {}".format(self)) - self.factory.broadcasts.append(self)
- -
[docs] def connectionLost(self, reason): - """ - We swallow connection errors here. The reason is that during a - normal reload/shutdown there will almost always be cases where - either the portal or server shuts down before a message has - returned its (empty) return, triggering a connectionLost error - that is irrelevant. If a true connection error happens, the - portal will continuously try to reconnect, showing the problem - that way. - """ - # print("ConnectionLost: {}: {}".format(self, reason)) - try: - self.factory.broadcasts.remove(self) - except ValueError: - pass
- - # Error handling - -
[docs] def errback(self, e, info): - """ - Error callback. - Handles errors to avoid dropping connections on server tracebacks. - - Args: - e (Failure): Deferred error instance. - info (str): Error string. - - """ - e.trap(Exception) - _get_logger().log_err( - "AMP Error from {info}: {trcbck} {err}".format( - info=info, trcbck=e.getTraceback(), err=e.getErrorMessage() - ) - )
- -
[docs] def data_in(self, packed_data): - """ - Process incoming packed data. - - Args: - packed_data (bytes): Pickled data. - Returns: - unpaced_data (any): Unpickled package - - """ - msg = loads(packed_data) - return msg
- -
[docs] def broadcast(self, command, sessid, **kwargs): - """ - Send data across the wire to all connections. - - Args: - command (AMP Command): A protocol send command. - sessid (int): A unique Session id. - - Returns: - deferred (deferred or None): A deferred with an errback. - - Notes: - Data will be sent across the wire pickled as a tuple - (sessid, kwargs). - - """ - deferreds = [] - # print("broadcast: {} {}: {}".format(command, sessid, kwargs)) - - for protcl in self.factory.broadcasts: - deferreds.append( - protcl.callRemote(command, **kwargs).addErrback(self.errback, command.key) - ) - - return DeferredList(deferreds)
- - # generic function send/recvs - -
[docs] def send_FunctionCall(self, modulepath, functionname, *args, **kwargs): - """ - Access method called by either process. This will call an arbitrary - function on the other process (On Portal if calling from Server and - vice versa). - - Inputs: - modulepath (str) - python path to module holding function to call - functionname (str) - name of function in given module - *args, **kwargs will be used as arguments/keyword args for the - remote function call - Returns: - A deferred that fires with the return value of the remote - function call - - """ - return ( - self.callRemote( - FunctionCall, - module=modulepath, - function=functionname, - args=dumps(args), - kwargs=dumps(kwargs), - ) - .addCallback(lambda r: loads(r["result"])) - .addErrback(self.errback, "FunctionCall") - )
- -
[docs] @FunctionCall.responder - @catch_traceback - def receive_functioncall(self, module, function, func_args, func_kwargs): - """ - This allows Portal- and Server-process to call an arbitrary - function in the other process. It is intended for use by - plugin modules. - - Args: - module (str or module): The module containing the - `function` to call. - function (str): The name of the function to call in - `module`. - func_args (str): Pickled args tuple for use in `function` call. - func_kwargs (str): Pickled kwargs dict for use in `function` call. - - """ - args = loads(func_args) - kwargs = loads(func_kwargs) - - # call the function (don't catch tracebacks here) - result = variable_from_module(module, function)(*args, **kwargs) - - if isinstance(result, Deferred): - # if result is a deferred, attach handler to properly - # wrap the return value - result.addCallback(lambda r: {"result": dumps(r)}) - return result - else: - return {"result": dumps(result)}
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/amp_server.html b/docs/0.9.5/_modules/evennia/server/portal/amp_server.html deleted file mode 100644 index e1bb8819bc..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/amp_server.html +++ /dev/null @@ -1,587 +0,0 @@ - - - - - - - - evennia.server.portal.amp_server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.amp_server

-"""
-The Evennia Portal service acts as an AMP-server, handling AMP
-communication to the AMP clients connecting to it (by default
-these are the Evennia Server and the evennia launcher).
-
-"""
-import os
-import sys
-from twisted.internet import protocol
-from evennia.server.portal import amp
-from django.conf import settings
-from subprocess import Popen, STDOUT
-from evennia.utils import logger
-
-
-def _is_windows():
-    return os.name == "nt"
-
-
-
[docs]def getenv(): - """ - Get current environment and add PYTHONPATH. - - Returns: - env (dict): Environment global dict. - - """ - sep = ";" if _is_windows() else ":" - env = os.environ.copy() - env["PYTHONPATH"] = sep.join(sys.path) - return env
- - -
[docs]class AMPServerFactory(protocol.ServerFactory): - - """ - This factory creates AMP Server connection. This acts as the 'Portal'-side communication to the - 'Server' process. - - """ - - noisy = False - -
[docs] def logPrefix(self): - "How this is named in logs" - return "AMP"
- -
[docs] def __init__(self, portal): - """ - Initialize the factory. This is called as the Portal service starts. - - Args: - portal (Portal): The Evennia Portal service instance. - protocol (Protocol): The protocol the factory creates - instances of. - - """ - self.portal = portal - self.protocol = AMPServerProtocol - self.broadcasts = [] - self.server_connection = None - self.launcher_connection = None - self.disconnect_callbacks = {} - self.server_connect_callbacks = []
- -
[docs] def buildProtocol(self, addr): - """ - Start a new connection, and store it on the service object. - - Args: - addr (str): Connection address. Not used. - - Returns: - protocol (Protocol): The created protocol. - - """ - self.portal.amp_protocol = AMPServerProtocol() - self.portal.amp_protocol.factory = self - return self.portal.amp_protocol
- - -
[docs]class AMPServerProtocol(amp.AMPMultiConnectionProtocol): - """ - Protocol subclass for the AMP-server run by the Portal. - - """ - -
[docs] def connectionLost(self, reason): - """ - Set up a simple callback mechanism to let the amp-server wait for a connection to close. - - """ - # wipe broadcast and data memory - super(AMPServerProtocol, self).connectionLost(reason) - if self.factory.server_connection == self: - self.factory.server_connection = None - self.factory.portal.server_info_dict = {} - if self.factory.launcher_connection == self: - self.factory.launcher_connection = None - - callback, args, kwargs = self.factory.disconnect_callbacks.pop(self, (None, None, None)) - if callback: - try: - callback(*args, **kwargs) - except Exception: - logger.log_trace()
- -
[docs] def get_status(self): - """ - Return status for the Evennia infrastructure. - - Returns: - status (tuple): The portal/server status and pids - (portal_live, server_live, portal_PID, server_PID). - - """ - server_connected = bool( - self.factory.server_connection and self.factory.server_connection.transport.connected - ) - portal_info_dict = self.factory.portal.get_info_dict() - server_info_dict = self.factory.portal.server_info_dict - server_pid = self.factory.portal.server_process_id - portal_pid = os.getpid() - return (True, server_connected, portal_pid, server_pid, portal_info_dict, server_info_dict)
- -
[docs] def data_to_server(self, command, sessid, **kwargs): - """ - Send data across the wire to the Server. - - Args: - command (AMP Command): A protocol send command. - sessid (int): A unique Session id. - kwargs (any): Data to send. This will be pickled. - - Returns: - deferred (deferred or None): A deferred with an errback. - - Notes: - Data will be sent across the wire pickled as a tuple - (sessid, kwargs). - - """ - # print("portal data_to_server: {}, {}, {}".format(command, sessid, kwargs)) - if self.factory.server_connection: - return self.factory.server_connection.callRemote( - command, packed_data=amp.dumps((sessid, kwargs)) - ).addErrback(self.errback, command.key) - else: - # if no server connection is available, broadcast - return self.broadcast(command, sessid, packed_data=amp.dumps((sessid, kwargs)))
- -
[docs] def start_server(self, server_twistd_cmd): - """ - (Re-)Launch the Evennia server. - - Args: - server_twisted_cmd (list): The server start instruction - to pass to POpen to start the server. - - """ - # start the Server - print("Portal starting server ... ") - process = None - with open(settings.SERVER_LOG_FILE, "a") as logfile: - # we link stdout to a file in order to catch - # eventual errors happening before the Server has - # opened its logger. - try: - if _is_windows(): - # Windows requires special care - create_no_window = 0x08000000 - process = Popen( - server_twistd_cmd, - env=getenv(), - bufsize=-1, - stdout=logfile, - stderr=STDOUT, - creationflags=create_no_window, - ) - - else: - process = Popen( - server_twistd_cmd, env=getenv(), bufsize=-1, stdout=logfile, stderr=STDOUT - ) - except Exception: - logger.log_trace() - - self.factory.portal.server_twistd_cmd = server_twistd_cmd - logfile.flush() - if process and not _is_windows(): - # avoid zombie-process on Unix/BSD - process.wait() - return
- -
[docs] def wait_for_disconnect(self, callback, *args, **kwargs): - """ - Add a callback for when this connection is lost. - - Args: - callback (callable): Will be called with *args, **kwargs - once this protocol is disconnected. - - """ - self.factory.disconnect_callbacks[self] = (callback, args, kwargs)
- -
[docs] def wait_for_server_connect(self, callback, *args, **kwargs): - """ - Add a callback for when the Server is sure to have connected. - - Args: - callback (callable): Will be called with *args, **kwargs - once the Server handshake with Portal is complete. - - """ - self.factory.server_connect_callbacks.append((callback, args, kwargs))
- -
[docs] def stop_server(self, mode="shutdown"): - """ - Shut down server in one or more modes. - - Args: - mode (str): One of 'shutdown', 'reload' or 'reset'. - - """ - if mode == "reload": - self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SRELOAD) - elif mode == "reset": - self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SRESET) - elif mode == "shutdown": - self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SSHUTD) - self.factory.portal.server_restart_mode = mode
- - # sending amp data - -
[docs] def send_Status2Launcher(self): - """ - Send a status stanza to the launcher. - - """ - # print("send status to launcher") - # print("self.get_status(): {}".format(self.get_status())) - if self.factory.launcher_connection: - self.factory.launcher_connection.callRemote( - amp.MsgStatus, status=amp.dumps(self.get_status()) - ).addErrback(self.errback, amp.MsgStatus.key)
- -
[docs] def send_MsgPortal2Server(self, session, **kwargs): - """ - Access method called by the Portal and executed on the Portal. - - Args: - session (session): Session - kwargs (any, optional): Optional data. - - Returns: - deferred (Deferred): Asynchronous return. - - """ - return self.data_to_server(amp.MsgPortal2Server, session.sessid, **kwargs)
- -
[docs] def send_AdminPortal2Server(self, session, operation="", **kwargs): - """ - Send Admin instructions from the Portal to the Server. - Executed on the Portal. - - Args: - session (Session): Session. - operation (char, optional): Identifier for the server operation, as defined by the - global variables in `evennia/server/amp.py`. - data (str or dict, optional): Data used in the administrative operation. - - """ - return self.data_to_server( - amp.AdminPortal2Server, session.sessid, operation=operation, **kwargs - )
- - # receive amp data - - @amp.MsgStatus.responder - @amp.catch_traceback - def portal_receive_status(self, status): - """ - Returns run-status for the server/portal. - - Args: - status (str): Not used. - Returns: - status (dict): The status is a tuple - (portal_running, server_running, portal_pid, server_pid). - - """ - # print('Received PSTATUS request') - return {"status": amp.dumps(self.get_status())} - - @amp.MsgLauncher2Portal.responder - @amp.catch_traceback - def portal_receive_launcher2portal(self, operation, arguments): - """ - Receives message arriving from evennia_launcher. - This method is executed on the Portal. - - Args: - operation (str): The action to perform. - arguments (str): Possible argument to the instruction, or the empty string. - - Returns: - result (dict): The result back to the launcher. - - Notes: - This is the entrypoint for controlling the entire Evennia system from the evennia - launcher. It can obviously only accessed when the Portal is already up and running. - - """ - # Since the launcher command uses amp.String() we need to convert from byte here. - operation = str(operation, "utf-8") - self.factory.launcher_connection = self - _, server_connected, _, _, _, _ = self.get_status() - - # logger.log_msg("Evennia Launcher->Portal operation %s:%s received" % (ord(operation), arguments)) - - # logger.log_msg("operation == amp.SSTART: {}: {}".format(operation == amp.SSTART, amp.loads(arguments))) - - if operation == amp.SSTART: # portal start #15 - # first, check if server is already running - if not server_connected: - self.wait_for_server_connect(self.send_Status2Launcher) - self.start_server(amp.loads(arguments)) - - elif operation == amp.SRELOAD: # reload server #14 - if server_connected: - # We let the launcher restart us once they get the signal - self.factory.server_connection.wait_for_disconnect(self.send_Status2Launcher) - self.stop_server(mode="reload") - else: - self.wait_for_server_connect(self.send_Status2Launcher) - self.start_server(amp.loads(arguments)) - - elif operation == amp.SRESET: # reload server #19 - if server_connected: - self.factory.server_connection.wait_for_disconnect(self.send_Status2Launcher) - self.stop_server(mode="reset") - else: - self.wait_for_server_connect(self.send_Status2Launcher) - self.start_server(amp.loads(arguments)) - - elif operation == amp.SSHUTD: # server-only shutdown #17 - if server_connected: - self.factory.server_connection.wait_for_disconnect(self.send_Status2Launcher) - self.stop_server(mode="shutdown") - - elif operation == amp.PSHUTD: # portal + server shutdown #16 - if server_connected: - self.factory.server_connection.wait_for_disconnect(self.factory.portal.shutdown) - else: - self.factory.portal.shutdown() - - else: - logger.log_err("Operation {} not recognized".format(operation)) - raise Exception("operation %(op)s not recognized." % {"op": operation}) - - return {} - - @amp.MsgServer2Portal.responder - @amp.catch_traceback - def portal_receive_server2portal(self, packed_data): - """ - Receives message arriving to Portal from Server. - This method is executed on the Portal. - - Args: - packed_data (str): Pickled data (sessid, kwargs) coming over the wire. - - """ - try: - sessid, kwargs = self.data_in(packed_data) - session = self.factory.portal.sessions.get(sessid, None) - if session: - self.factory.portal.sessions.data_out(session, **kwargs) - except Exception: - logger.log_trace("packed_data len {}".format(len(packed_data))) - return {} - - @amp.AdminServer2Portal.responder - @amp.catch_traceback - def portal_receive_adminserver2portal(self, packed_data): - """ - - Receives and handles admin operations sent to the Portal - This is executed on the Portal. - - Args: - packed_data (str): Data received, a pickled tuple (sessid, kwargs). - - """ - self.factory.server_connection = self - - sessid, kwargs = self.data_in(packed_data) - - # logger.log_msg("Evennia Server->Portal admin data %s:%s received" % (sessid, kwargs)) - - operation = kwargs.pop("operation") - portal_sessionhandler = self.factory.portal.sessions - - if operation == amp.SLOGIN: # server_session_login - # a session has authenticated; sync it. - session = portal_sessionhandler.get(sessid) - if session: - portal_sessionhandler.server_logged_in(session, kwargs.get("sessiondata")) - - elif operation == amp.SDISCONN: # server_session_disconnect - # the server is ordering to disconnect the session - session = portal_sessionhandler.get(sessid) - if session: - portal_sessionhandler.server_disconnect(session, reason=kwargs.get("reason")) - - elif operation == amp.SDISCONNALL: # server_session_disconnect_all - # server orders all sessions to disconnect - portal_sessionhandler.server_disconnect_all(reason=kwargs.get("reason")) - - elif operation == amp.SRELOAD: # server reload - self.factory.server_connection.wait_for_disconnect( - self.start_server, self.factory.portal.server_twistd_cmd - ) - self.stop_server(mode="reload") - - elif operation == amp.SRESET: # server reset - self.factory.server_connection.wait_for_disconnect( - self.start_server, self.factory.portal.server_twistd_cmd - ) - self.stop_server(mode="reset") - - elif operation == amp.SSHUTD: # server-only shutdown - self.stop_server(mode="shutdown") - - elif operation == amp.PSHUTD: # full server+server shutdown - self.factory.server_connection.wait_for_disconnect(self.factory.portal.shutdown) - self.stop_server(mode="shutdown") - - elif operation == amp.PSYNC: # portal sync - # Server has (re-)connected and wants the session data from portal - self.factory.portal.server_info_dict = kwargs.get("info_dict", {}) - self.factory.portal.server_process_id = kwargs.get("spid", None) - # this defaults to 'shutdown' or whatever value set in server_stop - server_restart_mode = self.factory.portal.server_restart_mode - - sessdata = self.factory.portal.sessions.get_all_sync_data() - self.send_AdminPortal2Server( - amp.DUMMYSESSION, - amp.PSYNC, - server_restart_mode=server_restart_mode, - sessiondata=sessdata, - portal_start_time=self.factory.portal.start_time, - ) - self.factory.portal.sessions.at_server_connection() - - if self.factory.server_connection: - # this is an indication the server has successfully connected, so - # we trigger any callbacks (usually to tell the launcher server is up) - for callback, args, kwargs in self.factory.server_connect_callbacks: - try: - callback(*args, **kwargs) - except Exception: - logger.log_trace() - self.factory.server_connect_callbacks = [] - - elif operation == amp.SSYNC: # server_session_sync - # server wants to save session data to the portal, - # maybe because it's about to shut down. - portal_sessionhandler.server_session_sync( - kwargs.get("sessiondata"), kwargs.get("clean", True) - ) - - # set a flag in case we are about to shut down soon - self.factory.server_restart_mode = True - - elif operation == amp.SCONN: # server_force_connection (for irc/etc) - portal_sessionhandler.server_connect(**kwargs) - - else: - raise Exception("operation %(op)s not recognized." % {"op": operation}) - return {}
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/grapevine.html b/docs/0.9.5/_modules/evennia/server/portal/grapevine.html deleted file mode 100644 index bb75788fc1..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/grapevine.html +++ /dev/null @@ -1,465 +0,0 @@ - - - - - - - - evennia.server.portal.grapevine — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.grapevine

-"""
-Grapevine network connection
-
-This is an implementation of the Grapevine Websocket protocol v 1.0.0 as
-outlined here: https://grapevine.haus/docs
-
-This will allow the linked game to transfer status as well as connects
-the grapevine client to in-game channels.
-
-"""
-
-import json
-from twisted.internet import protocol
-from django.conf import settings
-from evennia.server.session import Session
-from evennia.utils import get_evennia_version
-from evennia.utils.logger import log_info, log_err
-from autobahn.twisted.websocket import WebSocketClientProtocol, WebSocketClientFactory, connectWS
-
-# There is only one at this time
-GRAPEVINE_URI = "wss://grapevine.haus/socket"
-
-GRAPEVINE_CLIENT_ID = settings.GRAPEVINE_CLIENT_ID
-GRAPEVINE_CLIENT_SECRET = settings.GRAPEVINE_CLIENT_SECRET
-GRAPEVINE_CHANNELS = settings.GRAPEVINE_CHANNELS
-
-# defined error codes
-CLOSE_NORMAL = 1000
-GRAPEVINE_AUTH_ERROR = 4000
-GRAPEVINE_HEARTBEAT_FAILURE = 4001
-
-
-
[docs]class RestartingWebsocketServerFactory(WebSocketClientFactory, protocol.ReconnectingClientFactory): - """ - A variant of the websocket-factory that auto-reconnects. - - """ - - initialDelay = 1 - factor = 1.5 - maxDelay = 60 - -
[docs] def __init__(self, sessionhandler, *args, **kwargs): - - self.uid = kwargs.pop("uid") - self.channel = kwargs.pop("grapevine_channel") - self.sessionhandler = sessionhandler - - # self.noisy = False - self.port = None - self.bot = None - - WebSocketClientFactory.__init__(self, GRAPEVINE_URI, *args, **kwargs)
- -
[docs] def buildProtocol(self, addr): - """ - Build new instance of protocol - - Args: - addr (str): Not used, using factory/settings data - - """ - protocol = GrapevineClient() - protocol.factory = self - protocol.channel = self.channel - protocol.sessionhandler = self.sessionhandler - return protocol
- -
[docs] def startedConnecting(self, connector): - """ - Tracks reconnections for debugging. - - Args: - connector (Connector): Represents the connection. - - """ - log_info("(re)connecting to grapevine channel '%s'" % self.channel)
- -
[docs] def clientConnectionFailed(self, connector, reason): - """ - Called when Client failed to connect. - - Args: - connector (Connection): Represents the connection. - reason (str): The reason for the failure. - - """ - protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
- -
[docs] def clientConnectionLost(self, connector, reason): - """ - Called when Client loses connection. - - Args: - connector (Connection): Represents the connection. - reason (str): The reason for the failure. - - """ - if not (self.bot or (self.bot and self.bot.stopping)): - self.retry(connector)
- -
[docs] def reconnect(self): - """ - Force a reconnection of the bot protocol. This requires - de-registering the session and then reattaching a new one, - otherwise you end up with an ever growing number of bot - sessions. - - """ - self.bot.stopping = True - self.bot.transport.loseConnection() - self.sessionhandler.server_disconnect(self.bot) - self.start()
- -
[docs] def start(self): - "Connect protocol to remote server" - - try: - from twisted.internet import ssl - except ImportError: - log_err("To use Grapevine, The PyOpenSSL module must be installed.") - else: - context_factory = ssl.ClientContextFactory() if self.isSecure else None - connectWS(self, context_factory)
- # service.name = "websocket/grapevine" - # self.sessionhandler.portal.services.addService(service) - - -
[docs]class GrapevineClient(WebSocketClientProtocol, Session): - """ - Implements the grapevine client - """ - -
[docs] def __init__(self): - WebSocketClientProtocol.__init__(self) - Session.__init__(self) - self.restart_downtime = None
- -
[docs] def at_login(self): - pass
- -
[docs] def onOpen(self): - """ - Called when connection is established. - - """ - self.restart_downtime = None - self.restart_task = None - - self.stopping = False - self.factory.bot = self - - self.init_session("grapevine", GRAPEVINE_URI, self.factory.sessionhandler) - self.uid = int(self.factory.uid) - self.logged_in = True - self.sessionhandler.connect(self) - - self.send_authenticate()
- -
[docs] def onMessage(self, payload, isBinary): - """ - Callback fired when a complete WebSocket message was received. - - Args: - payload (bytes): The WebSocket message received. - isBinary (bool): Flag indicating whether payload is binary or - UTF-8 encoded text. - - """ - if not isBinary: - data = json.loads(str(payload, "utf-8")) - self.data_in(data=data) - self.retry_task = None
- -
[docs] def onClose(self, wasClean, code=None, reason=None): - """ - This is executed when the connection is lost for whatever - reason. it can also be called directly, from the disconnect - method. - - Args: - wasClean (bool): ``True`` if the WebSocket was closed cleanly. - code (int or None): Close status as sent by the WebSocket peer. - reason (str or None): Close reason as sent by the WebSocket peer. - - """ - self.disconnect(reason) - - if code == GRAPEVINE_HEARTBEAT_FAILURE: - log_err("Grapevine connection lost (Heartbeat error)") - elif code == GRAPEVINE_AUTH_ERROR: - log_err("Grapevine connection lost (Auth error)") - elif self.restart_downtime: - # server previously warned us about downtime and told us to be - # ready to reconnect. - log_info("Grapevine connection lost (Server restart).")
- - def _send_json(self, data): - """ - Send (json-) data to client. - - Args: - data (str): Text to send. - - """ - return self.sendMessage(json.dumps(data).encode("utf-8")) - -
[docs] def disconnect(self, reason=None): - """ - Generic hook for the engine to call in order to - disconnect this protocol. - - Args: - reason (str or None): Motivation for the disconnection. - - """ - self.sessionhandler.disconnect(self) - # autobahn-python: 1000 for a normal close, 3000-4999 for app. specific, - # in case anyone wants to expose this functionality later. - # - # sendClose() under autobahn/websocket/interfaces.py - self.sendClose(CLOSE_NORMAL, reason)
- - # send_* method are automatically callable through .msg(heartbeat={}) etc - -
[docs] def send_authenticate(self, *args, **kwargs): - """ - Send grapevine authentication. This should be send immediately upon connection. - - """ - data = { - "event": "authenticate", - "payload": { - "client_id": GRAPEVINE_CLIENT_ID, - "client_secret": GRAPEVINE_CLIENT_SECRET, - "supports": ["channels"], - "channels": GRAPEVINE_CHANNELS, - "version": "1.0.0", - "user_agent": get_evennia_version("pretty"), - }, - } - # override on-the-fly - data.update(kwargs) - - self._send_json(data)
- -
[docs] def send_heartbeat(self, *args, **kwargs): - """ - Send heartbeat to remote grapevine server. - - """ - # pass along all connected players - data = {"event": "heartbeat", "payload": {}} - sessions = self.sessionhandler.get_sessions(include_unloggedin=False) - data["payload"]["players"] = [ - sess.account.key for sess in sessions if hasattr(sess, "account") - ] - - self._send_json(data)
- -
[docs] def send_subscribe(self, channelname, *args, **kwargs): - """ - Subscribe to new grapevine channel - - Use with session.msg(subscribe="channelname") - """ - data = {"event": "channels/subscribe", "payload": {"channel": channelname}} - self._send_json(data)
- -
[docs] def send_unsubscribe(self, channelname, *args, **kwargs): - """ - Un-subscribe to a grapevine channel - - Use with session.msg(unsubscribe="channelname") - """ - data = {"event": "channels/unsubscribe", "payload": {"channel": channelname}} - self._send_json(data)
- -
[docs] def send_channel(self, text, channel, sender, *args, **kwargs): - """ - Send text type Evennia -> grapevine - - This is the channels/send message type - - Use with session.msg(channel=(message, channel, sender)) - - """ - - data = { - "event": "channels/send", - "payload": {"message": text, "channel": channel, "name": sender}, - } - self._send_json(data)
- -
[docs] def send_default(self, *args, **kwargs): - """ - Ignore other outputfuncs - - """ - pass
- -
[docs] def data_in(self, data, **kwargs): - """ - Send data grapevine -> Evennia - - Keyword Args: - data (dict): Converted json data. - - """ - event = data["event"] - if event == "authenticate": - # server replies to our auth handshake - if data["status"] != "success": - log_err("Grapevine authentication failed.") - self.disconnect() - else: - log_info("Connected and authenticated to Grapevine network.") - elif event == "heartbeat": - # server sends heartbeat - we have to send one back - self.send_heartbeat() - elif event == "restart": - # set the expected downtime - self.restart_downtime = data["payload"]["downtime"] - elif event == "channels/subscribe": - # subscription verification - if data.get("status", "success") == "failure": - err = data.get("error", "N/A") - self.sessionhandler.data_in( - bot_data_in=((f"Grapevine error: {err}"), {"event": event}) - ) - elif event == "channels/unsubscribe": - # unsubscribe-verification - pass - elif event == "channels/broadcast": - # incoming broadcast from network - payload = data["payload"] - - print("channels/broadcast:", payload["channel"], self.channel) - if str(payload["channel"]) != self.channel: - # only echo from channels this particular bot actually listens to - return - else: - # correct channel - self.sessionhandler.data_in( - self, - bot_data_in=( - str(payload["message"]), - { - "event": event, - "grapevine_channel": str(payload["channel"]), - "sender": str(payload["name"]), - "game": str(payload["game"]), - }, - ), - ) - elif event == "channels/send": - pass - else: - self.sessionhandler.data_in(self, bot_data_in=("", kwargs))
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/irc.html b/docs/0.9.5/_modules/evennia/server/portal/irc.html deleted file mode 100644 index b791f10402..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/irc.html +++ /dev/null @@ -1,584 +0,0 @@ - - - - - - - - evennia.server.portal.irc — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.irc

-"""
-This connects to an IRC network/channel and launches an 'bot' onto it.
-The bot then pipes what is being said between the IRC channel and one or
-more Evennia channels.
-"""
-
-import re
-from twisted.application import internet
-from twisted.words.protocols import irc
-from twisted.internet import protocol, reactor
-from evennia.server.session import Session
-from evennia.utils import logger, utils, ansi
-
-
-# IRC colors
-
-IRC_BOLD = "\002"
-IRC_COLOR = "\003"
-IRC_RESET = "\017"
-IRC_ITALIC = "\026"
-IRC_INVERT = "\x16"
-IRC_NORMAL = "99"
-IRC_UNDERLINE = "37"
-
-IRC_WHITE = "0"
-IRC_BLACK = "1"
-IRC_DBLUE = "2"
-IRC_DGREEN = "3"
-IRC_RED = "4"
-IRC_DRED = "5"
-IRC_DMAGENTA = "6"
-IRC_DYELLOW = "7"
-IRC_YELLOW = "8"
-IRC_GREEN = "9"
-IRC_DCYAN = "10"
-IRC_CYAN = "11"
-IRC_BLUE = "12"
-IRC_MAGENTA = "13"
-IRC_DGREY = "14"
-IRC_GREY = "15"
-
-# obsolete test:
-
-# test evennia->irc:
-# |rred |ggreen |yyellow |bblue |mmagenta |ccyan |wwhite |xdgrey
-# |Rdred |Gdgreen |Ydyellow |Bdblue |Mdmagenta |Cdcyan |Wlgrey |Xblack
-# |[rredbg |[ggreenbg |[yyellowbg |[bbluebg |[mmagentabg |[ccyanbg |[wlgreybg |[xblackbg
-
-# test irc->evennia
-# Use Ctrl+C <num> to produce mIRC colors in e.g. irssi
-
-IRC_COLOR_MAP = dict(
-    (
-        (r"|n", IRC_COLOR + IRC_NORMAL),  # normal mode
-        (r"|H", IRC_RESET),  # un-highlight
-        (r"|/", "\n"),  # line break
-        (r"|t", "    "),  # tab
-        (r"|-", "    "),  # fixed tab
-        (r"|_", " "),  # space
-        (r"|*", IRC_INVERT),  # invert
-        (r"|^", ""),  # blinking text
-        (r"|h", IRC_BOLD),  # highlight, use bold instead
-        (r"|r", IRC_COLOR + IRC_RED),
-        (r"|g", IRC_COLOR + IRC_GREEN),
-        (r"|y", IRC_COLOR + IRC_YELLOW),
-        (r"|b", IRC_COLOR + IRC_BLUE),
-        (r"|m", IRC_COLOR + IRC_MAGENTA),
-        (r"|c", IRC_COLOR + IRC_CYAN),
-        (r"|w", IRC_COLOR + IRC_WHITE),  # pure white
-        (r"|x", IRC_COLOR + IRC_DGREY),  # dark grey
-        (r"|R", IRC_COLOR + IRC_DRED),
-        (r"|G", IRC_COLOR + IRC_DGREEN),
-        (r"|Y", IRC_COLOR + IRC_DYELLOW),
-        (r"|B", IRC_COLOR + IRC_DBLUE),
-        (r"|M", IRC_COLOR + IRC_DMAGENTA),
-        (r"|C", IRC_COLOR + IRC_DCYAN),
-        (r"|W", IRC_COLOR + IRC_GREY),  # light grey
-        (r"|X", IRC_COLOR + IRC_BLACK),  # pure black
-        (r"|[r", IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
-        (r"|[g", IRC_COLOR + IRC_NORMAL + "," + IRC_DGREEN),
-        (r"|[y", IRC_COLOR + IRC_NORMAL + "," + IRC_DYELLOW),
-        (r"|[b", IRC_COLOR + IRC_NORMAL + "," + IRC_DBLUE),
-        (r"|[m", IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
-        (r"|[c", IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
-        (r"|[w", IRC_COLOR + IRC_NORMAL + "," + IRC_GREY),  # light grey background
-        (r"|[x", IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK),  # pure black background
-    )
-)
-# ansi->irc
-RE_ANSI_COLOR = re.compile(r"|".join([re.escape(key) for key in IRC_COLOR_MAP.keys()]), re.DOTALL)
-RE_MXP = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
-RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
-# irc->ansi
-_CLR_LIST = [
-    re.escape(val) for val in sorted(IRC_COLOR_MAP.values(), key=len, reverse=True) if val.strip()
-]
-_CLR_LIST = _CLR_LIST[-2:] + _CLR_LIST[:-2]
-RE_IRC_COLOR = re.compile(r"|".join(_CLR_LIST), re.DOTALL)
-ANSI_COLOR_MAP = dict((tup[1], tup[0]) for tup in IRC_COLOR_MAP.items() if tup[1].strip())
-
-
-
[docs]def parse_ansi_to_irc(string): - """ - Parse |-type syntax and replace with IRC color markers - - Args: - string (str): String to parse for ANSI colors. - - Returns: - parsed_string (str): String with replaced ANSI colors. - - """ - - def _sub_to_irc(ansi_match): - return IRC_COLOR_MAP.get(ansi_match.group(), "") - - in_string = utils.to_str(string) - parsed_string = [] - parts = RE_ANSI_ESCAPES.split(in_string) + [" "] - for part, sep in zip(parts[::2], parts[1::2]): - pstring = RE_ANSI_COLOR.sub(_sub_to_irc, part) - parsed_string.append("%s%s" % (pstring, sep[0].strip())) - # strip mxp - parsed_string = RE_MXP.sub(r"\2", "".join(parsed_string)) - return parsed_string
- - -
[docs]def parse_irc_to_ansi(string): - """ - Parse IRC mIRC color syntax and replace with Evennia ANSI color markers - - Args: - string (str): String to parse for IRC colors. - - Returns: - parsed_string (str): String with replaced IRC colors. - - """ - - def _sub_to_ansi(irc_match): - return ANSI_COLOR_MAP.get(irc_match.group(), "") - - in_string = utils.to_str(string) - pstring = RE_IRC_COLOR.sub(_sub_to_ansi, in_string) - return pstring
- - -# IRC bot - - -
[docs]class IRCBot(irc.IRCClient, Session): - """ - An IRC bot that tracks activity in a channel as well - as sends text to it when prompted - - """ - - lineRate = 1 - - # assigned by factory at creation - - nickname = None - logger = None - factory = None - channel = None - sourceURL = "http://code.evennia.com" - -
[docs] def signedOn(self): - """ - This is called when we successfully connect to the network. We - make sure to now register with the game as a full session. - - """ - self.join(self.channel) - self.stopping = False - self.factory.bot = self - address = "%s@%s" % (self.channel, self.network) - self.init_session("ircbot", address, self.factory.sessionhandler) - # we link back to our bot and log in - self.uid = int(self.factory.uid) - self.logged_in = True - self.factory.sessionhandler.connect(self) - logger.log_info( - "IRC bot '%s' connected to %s at %s:%s." - % (self.nickname, self.channel, self.network, self.port) - )
- -
[docs] def disconnect(self, reason=""): - """ - Called by sessionhandler to disconnect this protocol. - - Args: - reason (str): Motivation for the disconnect. - - """ - self.sessionhandler.disconnect(self) - self.stopping = True - self.transport.loseConnection()
- -
[docs] def at_login(self): - pass
- -
[docs] def privmsg(self, user, channel, msg): - """ - Called when the connected channel receives a message. - - Args: - user (str): User name sending the message. - channel (str): Channel name seeing the message. - msg (str): The message arriving from channel. - - """ - if channel == self.nickname: - # private message - user = user.split("!", 1)[0] - self.data_in(text=msg, type="privmsg", user=user, channel=channel) - elif not msg.startswith("***"): - # channel message - user = user.split("!", 1)[0] - user = ansi.raw(user) - self.data_in(text=msg, type="msg", user=user, channel=channel)
- -
[docs] def action(self, user, channel, msg): - """ - Called when an action is detected in channel. - - Args: - user (str): User name sending the message. - channel (str): Channel name seeing the message. - msg (str): The message arriving from channel. - - """ - if not msg.startswith("**"): - user = user.split("!", 1)[0] - self.data_in(text=msg, type="action", user=user, channel=channel)
- -
[docs] def get_nicklist(self): - """ - Retrieve name list from the channel. The return - is handled by the catch methods below. - - """ - if not self.nicklist: - self.sendLine("NAMES %s" % self.channel)
- -
[docs] def irc_RPL_NAMREPLY(self, prefix, params): - """"Handles IRC NAME request returns (nicklist)""" - channel = params[2].lower() - if channel != self.channel.lower(): - return - self.nicklist += params[3].split(" ")
- -
[docs] def irc_RPL_ENDOFNAMES(self, prefix, params): - """Called when the nicklist has finished being returned.""" - channel = params[1].lower() - if channel != self.channel.lower(): - return - self.data_in( - text="", type="nicklist", user="server", channel=channel, nicklist=self.nicklist - ) - self.nicklist = []
- -
[docs] def pong(self, user, time): - """ - Called with the return timing from a PING. - - Args: - user (str): Name of user - time (float): Ping time in secs. - - """ - self.data_in(text="", type="ping", user="server", channel=self.channel, timing=time)
- -
[docs] def data_in(self, text=None, **kwargs): - """ - Data IRC -> Server. - - Keyword Args: - text (str): Ingoing text. - kwargs (any): Other data from protocol. - - """ - self.sessionhandler.data_in(self, bot_data_in=[parse_irc_to_ansi(text), kwargs])
- -
[docs] def send_channel(self, *args, **kwargs): - """ - Send channel text to IRC channel (visible to all). Note that - we don't handle the "text" send (it's rerouted to send_default - which does nothing) - this is because the IRC bot is a normal - session and would otherwise report anything that happens to it - to the IRC channel (such as it seeing server reload messages). - - Args: - text (str): Outgoing text - - """ - text = args[0] if args else "" - if text: - text = parse_ansi_to_irc(text) - self.say(self.channel, text)
- -
[docs] def send_privmsg(self, *args, **kwargs): - """ - Send message only to specific user. - - Args: - text (str): Outgoing text. - - Keyword Args: - user (str): the nick to send - privately to. - - """ - text = args[0] if args else "" - user = kwargs.get("user", None) - if text and user: - text = parse_ansi_to_irc(text) - self.msg(user, text)
- -
[docs] def send_request_nicklist(self, *args, **kwargs): - """ - Send a request for the channel nicklist. The return (handled - by `self.irc_RPL_ENDOFNAMES`) will be sent back as a message - with type `nicklist'. - """ - self.get_nicklist()
- -
[docs] def send_ping(self, *args, **kwargs): - """ - Send a ping. The return (handled by `self.pong`) will be sent - back as a message of type 'ping'. - """ - self.ping(self.nickname)
- -
[docs] def send_reconnect(self, *args, **kwargs): - """ - The server instructs us to rebuild the connection by force, - probably because the client silently lost connection. - """ - self.factory.reconnect()
- -
[docs] def send_default(self, *args, **kwargs): - """ - Ignore other types of sends. - - """ - pass
- - -
[docs]class IRCBotFactory(protocol.ReconnectingClientFactory): - """ - Creates instances of IRCBot, connecting with a staggered - increase in delay - - """ - - # scaling reconnect time - initialDelay = 1 - factor = 1.5 - maxDelay = 60 - -
[docs] def __init__( - self, - sessionhandler, - uid=None, - botname=None, - channel=None, - network=None, - port=None, - ssl=None, - ): - """ - Storing some important protocol properties. - - Args: - sessionhandler (SessionHandler): Reference to the main Sessionhandler. - - Keyword Args: - uid (int): Bot user id. - botname (str): Bot name (seen in IRC channel). - channel (str): IRC channel to connect to. - network (str): Network address to connect to. - port (str): Port of the network. - ssl (bool): Indicates SSL connection. - - """ - self.sessionhandler = sessionhandler - self.uid = uid - self.nickname = str(botname) - self.channel = str(channel) - self.network = str(network) - self.port = port - self.ssl = ssl - self.bot = None - self.nicklists = {}
- -
[docs] def buildProtocol(self, addr): - """ - Build the protocol and assign it some properties. - - Args: - addr (str): Not used; using factory data. - - """ - protocol = IRCBot() - protocol.factory = self - protocol.nickname = self.nickname - protocol.channel = self.channel - protocol.network = self.network - protocol.port = self.port - protocol.ssl = self.ssl - protocol.nicklist = [] - return protocol
- -
[docs] def startedConnecting(self, connector): - """ - Tracks reconnections for debugging. - - Args: - connector (Connector): Represents the connection. - - """ - logger.log_info("(re)connecting to %s" % self.channel)
- -
[docs] def clientConnectionFailed(self, connector, reason): - """ - Called when Client failed to connect. - - Args: - connector (Connection): Represents the connection. - reason (str): The reason for the failure. - - """ - self.retry(connector)
- -
[docs] def clientConnectionLost(self, connector, reason): - """ - Called when Client loses connection. - - Args: - connector (Connection): Represents the connection. - reason (str): The reason for the failure. - - """ - if not (self.bot or (self.bot and self.bot.stopping)): - self.retry(connector)
- -
[docs] def reconnect(self): - """ - Force a reconnection of the bot protocol. This requires - de-registering the session and then reattaching a new one, - otherwise you end up with an ever growing number of bot - sessions. - - """ - self.bot.stopping = True - self.bot.transport.loseConnection() - self.sessionhandler.server_disconnect(self.bot) - self.start()
- -
[docs] def start(self): - """ - Connect session to sessionhandler. - - """ - if self.port: - if self.ssl: - try: - from twisted.internet import ssl - - service = reactor.connectSSL( - self.network, int(self.port), self, ssl.ClientContextFactory() - ) - except ImportError: - logger.log_err("To use SSL, the PyOpenSSL module must be installed.") - else: - service = internet.TCPClient(self.network, int(self.port), self) - self.sessionhandler.portal.services.addService(service)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/mccp.html b/docs/0.9.5/_modules/evennia/server/portal/mccp.html deleted file mode 100644 index 758824773e..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/mccp.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - evennia.server.portal.mccp — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.mccp

-"""
-
-MCCP - Mud Client Compression Protocol
-
-This implements the MCCP v2 telnet protocol as per
-http://tintin.sourceforge.net/mccp/. MCCP allows for the server to
-compress data when sending to supporting clients, reducing bandwidth
-by 70-90%.. The compression is done using Python's builtin zlib
-library. If the client doesn't support MCCP, server sends uncompressed
-as normal.  Note: On modern hardware you are not likely to notice the
-effect of MCCP unless you have extremely heavy traffic or sits on a
-terribly slow connection.
-
-This protocol is implemented by the telnet protocol importing
-mccp_compress and calling it from its write methods.
-"""
-import zlib
-
-# negotiations for v1 and v2 of the protocol
-MCCP = bytes([86])  # b"\x56"
-FLUSH = zlib.Z_SYNC_FLUSH
-
-
-
[docs]def mccp_compress(protocol, data): - """ - Handles zlib compression, if applicable. - - Args: - data (str): Incoming data to compress. - - Returns: - stream (binary): Zlib-compressed data. - - """ - if hasattr(protocol, "zlib"): - return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH) - return data
- - -
[docs]class Mccp(object): - """ - Implements the MCCP protocol. Add this to a - variable on the telnet protocol to set it up. - - """ - -
[docs] def __init__(self, protocol): - """ - initialize MCCP by storing protocol on - ourselves and calling the client to see if - it supports MCCP. Sets callbacks to - start zlib compression in that case. - - Args: - protocol (Protocol): The active protocol instance. - - """ - - self.protocol = protocol - self.protocol.protocol_flags["MCCP"] = False - # ask if client will mccp, connect callbacks to handle answer - self.protocol.will(MCCP).addCallbacks(self.do_mccp, self.no_mccp)
- -
[docs] def no_mccp(self, option): - """ - Called if client doesn't support mccp or chooses to turn it off. - - Args: - option (Option): Option dict (not used). - - """ - if hasattr(self.protocol, "zlib"): - del self.protocol.zlib - self.protocol.protocol_flags["MCCP"] = False - self.protocol.handshake_done()
- -
[docs] def do_mccp(self, option): - """ - The client supports MCCP. Set things up by - creating a zlib compression stream. - - Args: - option (Option): Option dict (not used). - - """ - self.protocol.protocol_flags["MCCP"] = True - self.protocol.requestNegotiation(MCCP, b"") - self.protocol.zlib = zlib.compressobj(9) - self.protocol.handshake_done()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/mssp.html b/docs/0.9.5/_modules/evennia/server/portal/mssp.html deleted file mode 100644 index 6550a6f3b5..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/mssp.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - evennia.server.portal.mssp — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.mssp

-"""
-
-MSSP - Mud Server Status Protocol
-
-This implements the MSSP telnet protocol as per
-http://tintin.sourceforge.net/mssp/.  MSSP allows web portals and
-listings to have their crawlers find the mud and automatically
-extract relevant information about it, such as genre, how many
-active players and so on.
-
-
-"""
-from django.conf import settings
-from evennia.utils import utils
-
-MSSP = bytes([70])  # b"\x46"
-MSSP_VAR = bytes([1])  # b"\x01"
-MSSP_VAL = bytes([2])  # b"\x02"
-
-# try to get the customized mssp info, if it exists.
-MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
-
-
-
[docs]class Mssp(object): - """ - Implements the MSSP protocol. Add this to a variable on the telnet - protocol to set it up. - - """ - -
[docs] def __init__(self, protocol): - """ - initialize MSSP by storing protocol on ourselves and calling - the client to see if it supports MSSP. - - Args: - protocol (Protocol): The active protocol instance. - - """ - self.protocol = protocol - self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp)
- -
[docs] def get_player_count(self): - """ - Get number of logged-in players. - - Returns: - count (int): The number of players in the MUD. - - """ - return str(self.protocol.sessionhandler.count_loggedin())
- -
[docs] def get_uptime(self): - """ - Get how long the portal has been online (reloads are not counted). - - Returns: - uptime (int): Number of seconds of uptime. - - """ - return str(self.protocol.sessionhandler.uptime)
- -
[docs] def no_mssp(self, option): - """ - Called when mssp is not requested. This is the normal - operation. - - Args: - option (Option): Not used. - - """ - self.protocol.handshake_done()
- -
[docs] def do_mssp(self, option): - """ - Negotiate all the information. - - Args: - option (Option): Not used. - - """ - - self.mssp_table = { - # Required fields - "NAME": settings.SERVERNAME, - "PLAYERS": self.get_player_count, - "UPTIME": self.get_uptime, - "PORT": list( - str(port) for port in reversed(settings.TELNET_PORTS) - ), # most important port should be last in list - # Evennia auto-filled - "CRAWL DELAY": "-1", - "CODEBASE": utils.get_evennia_version(mode="pretty"), - "FAMILY": "Custom", - "ANSI": "1", - "GMCP": "1" if settings.TELNET_OOB_ENABLED else "0", - "ATCP": "0", - "MCCP": "1", - "MCP": "0", - "MSDP": "1" if settings.TELNET_OOB_ENABLED else "0", - "MSP": "0", - "MXP": "1", - "PUEBLO": "0", - "SSL": "1" if settings.SSL_ENABLED else "0", - "UTF-8": "1", - "ZMP": "0", - "VT100": "1", - "XTERM 256 COLORS": "1", - } - - # update the static table with the custom one - if MSSPTable_CUSTOM: - self.mssp_table.update(MSSPTable_CUSTOM) - - varlist = b"" - for variable, value in self.mssp_table.items(): - if callable(value): - value = value() - if utils.is_iter(value): - for partval in value: - varlist += ( - MSSP_VAR - + bytes(str(variable), "utf-8") - + MSSP_VAL - + bytes(str(partval), "utf-8") - ) - else: - varlist += ( - MSSP_VAR + bytes(str(variable), "utf-8") + MSSP_VAL + bytes(str(value), "utf-8") - ) - - # send to crawler by subnegotiation - self.protocol.requestNegotiation(MSSP, varlist) - self.protocol.handshake_done()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/mxp.html b/docs/0.9.5/_modules/evennia/server/portal/mxp.html deleted file mode 100644 index 4416f9137e..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/mxp.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - evennia.server.portal.mxp — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.mxp

-"""
-MXP - Mud eXtension Protocol.
-
-Partial implementation of the MXP protocol.
-The MXP protocol allows more advanced formatting options for telnet clients
-that supports it (mudlet, zmud, mushclient are a few)
-
-This only implements the SEND tag.
-
-More information can be found on the following links:
-http://www.zuggsoft.com/zmud/mxp.htm
-http://www.mushclient.com/mushclient/mxp.htm
-http://www.gammon.com.au/mushclient/addingservermxp.htm
-
-"""
-import re
-
-LINKS_SUB = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
-
-# MXP Telnet option
-MXP = bytes([91])  # b"\x5b"
-
-MXP_TEMPSECURE = "\x1B[4z"
-MXP_SEND = MXP_TEMPSECURE + '<SEND HREF="\\1">' + "\\2" + MXP_TEMPSECURE + "</SEND>"
-
-
-
[docs]def mxp_parse(text): - """ - Replaces links to the correct format for MXP. - - Args: - text (str): The text to parse. - - Returns: - parsed (str): The parsed text. - - """ - text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;") - - text = LINKS_SUB.sub(MXP_SEND, text) - return text
- - -
[docs]class Mxp(object): - """ - Implements the MXP protocol. - - """ - -
[docs] def __init__(self, protocol): - """ - Initializes the protocol by checking if the client supports it. - - Args: - protocol (Protocol): The active protocol instance. - - """ - self.protocol = protocol - self.protocol.protocol_flags["MXP"] = False - self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp)
- -
[docs] def no_mxp(self, option): - """ - Called when the Client reports to not support MXP. - - Args: - option (Option): Not used. - - """ - self.protocol.protocol_flags["MXP"] = False - self.protocol.handshake_done()
- -
[docs] def do_mxp(self, option): - """ - Called when the Client reports to support MXP. - - Args: - option (Option): Not used. - - """ - self.protocol.protocol_flags["MXP"] = True - self.protocol.requestNegotiation(MXP, b"") - self.protocol.handshake_done()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/naws.html b/docs/0.9.5/_modules/evennia/server/portal/naws.html deleted file mode 100644 index b08b6624c2..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/naws.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - evennia.server.portal.naws — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.naws

-"""
-
-NAWS - Negotiate About Window Size
-
-This implements the NAWS telnet option as per
-https://www.ietf.org/rfc/rfc1073.txt
-
-NAWS allows telnet clients to report their current window size to the
-client and update it when the size changes
-
-"""
-from codecs import encode as codecs_encode
-from django.conf import settings
-
-NAWS = bytes([31])  # b"\x1f"
-IS = bytes([0])  # b"\x00"
-# default taken from telnet specification
-DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
-
-# try to get the customized mssp info, if it exists.
-
-
-
[docs]class Naws(object): - """ - Implements the NAWS protocol. Add this to a variable on the telnet - protocol to set it up. - - """ - -
[docs] def __init__(self, protocol): - """ - initialize NAWS by storing protocol on ourselves and calling - the client to see if it supports NAWS. - - Args: - protocol (Protocol): The active protocol instance. - - """ - self.naws_step = 0 - self.protocol = protocol - self.protocol.protocol_flags["SCREENWIDTH"] = { - 0: DEFAULT_WIDTH - } # windowID (0 is root):width - self.protocol.protocol_flags["SCREENHEIGHT"] = {0: DEFAULT_HEIGHT} # windowID:width - self.protocol.negotiationMap[NAWS] = self.negotiate_sizes - self.protocol.do(NAWS).addCallbacks(self.do_naws, self.no_naws)
- -
[docs] def no_naws(self, option): - """ - Called when client is not reporting NAWS. This is the normal - operation. - - Args: - option (Option): Not used. - - """ - self.protocol.handshake_done()
- -
[docs] def do_naws(self, option): - """ - Client wants to negotiate all the NAWS information. - - Args: - option (Option): Not used. - - """ - self.protocol.handshake_done()
- -
[docs] def negotiate_sizes(self, options): - """ - Step through the NAWS handshake. - - Args: - option (list): The incoming NAWS options. - - """ - if len(options) == 4: - # NAWS is negotiated with 16bit words - width = options[0] + options[1] - self.protocol.protocol_flags["SCREENWIDTH"][0] = int(codecs_encode(width, "hex"), 16) - height = options[2] + options[3] - self.protocol.protocol_flags["SCREENHEIGHT"][0] = int(codecs_encode(height, "hex"), 16)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/portal.html b/docs/0.9.5/_modules/evennia/server/portal/portal.html deleted file mode 100644 index 1c7ed587a4..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/portal.html +++ /dev/null @@ -1,541 +0,0 @@ - - - - - - - - evennia.server.portal.portal — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.portal

-"""
-This module implements the main Evennia server process, the core of
-the game engine.
-
-This module should be started with the 'twistd' executable since it
-sets up all the networking features.  (this is done automatically
-by game/evennia.py).
-
-"""
-import sys
-import os
-import time
-
-from os.path import dirname, abspath
-from twisted.application import internet, service
-from twisted.internet.task import LoopingCall
-from twisted.internet import protocol, reactor
-from twisted.python.log import ILogObserver
-
-import django
-
-django.setup()
-from django.conf import settings
-from django.db import connection
-
-import evennia
-
-evennia._init()
-
-from evennia.utils.utils import get_evennia_version, mod_import, make_iter
-from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS
-from evennia.utils import logger
-from evennia.server.webserver import EvenniaReverseProxyResource
-
-
-# we don't need a connection to the database so close it right away
-try:
-    connection.close()
-except Exception:
-    pass
-
-PORTAL_SERVICES_PLUGIN_MODULES = [
-    mod_import(module) for module in make_iter(settings.PORTAL_SERVICES_PLUGIN_MODULES)
-]
-LOCKDOWN_MODE = settings.LOCKDOWN_MODE
-
-# -------------------------------------------------------------
-# Evennia Portal settings
-# -------------------------------------------------------------
-
-VERSION = get_evennia_version()
-
-SERVERNAME = settings.SERVERNAME
-
-PORTAL_RESTART = os.path.join(settings.GAME_DIR, "server", "portal.restart")
-
-TELNET_PORTS = settings.TELNET_PORTS
-SSL_PORTS = settings.SSL_PORTS
-SSH_PORTS = settings.SSH_PORTS
-WEBSERVER_PORTS = settings.WEBSERVER_PORTS
-WEBSOCKET_CLIENT_PORT = settings.WEBSOCKET_CLIENT_PORT
-
-TELNET_INTERFACES = ["127.0.0.1"] if LOCKDOWN_MODE else settings.TELNET_INTERFACES
-SSL_INTERFACES = ["127.0.0.1"] if LOCKDOWN_MODE else settings.SSL_INTERFACES
-SSH_INTERFACES = ["127.0.0.1"] if LOCKDOWN_MODE else settings.SSH_INTERFACES
-WEBSERVER_INTERFACES = ["127.0.0.1"] if LOCKDOWN_MODE else settings.WEBSERVER_INTERFACES
-WEBSOCKET_CLIENT_INTERFACE = "127.0.0.1" if LOCKDOWN_MODE else settings.WEBSOCKET_CLIENT_INTERFACE
-WEBSOCKET_CLIENT_URL = settings.WEBSOCKET_CLIENT_URL
-
-TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS and TELNET_INTERFACES
-SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
-SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
-WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
-WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
-WEBSOCKET_CLIENT_ENABLED = (
-    settings.WEBSOCKET_CLIENT_ENABLED and WEBSOCKET_CLIENT_PORT and WEBSOCKET_CLIENT_INTERFACE
-)
-
-AMP_HOST = settings.AMP_HOST
-AMP_PORT = settings.AMP_PORT
-AMP_INTERFACE = settings.AMP_INTERFACE
-AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE
-
-INFO_DICT = {
-    "servername": SERVERNAME,
-    "version": VERSION,
-    "errors": "",
-    "info": "",
-    "lockdown_mode": "",
-    "amp": "",
-    "telnet": [],
-    "telnet_ssl": [],
-    "ssh": [],
-    "webclient": [],
-    "webserver_proxy": [],
-    "webserver_internal": [],
-}
-
-try:
-    WEB_PLUGINS_MODULE = mod_import(settings.WEB_PLUGINS_MODULE)
-except ImportError:
-    WEB_PLUGINS_MODULE = None
-    INFO_DICT["errors"] = (
-        "WARNING: settings.WEB_PLUGINS_MODULE not found - "
-        "copy 'evennia/game_template/server/conf/web_plugins.py to "
-        "mygame/server/conf."
-    )
-
-
-_MAINTENANCE_COUNT = 0
-
-
-def _portal_maintenance():
-    """
-    Repeated maintenance tasks for the portal.
-
-    """
-    global _MAINTENANCE_COUNT
-
-    _MAINTENANCE_COUNT += 1
-
-    if _MAINTENANCE_COUNT % (60 * 7) == 0:
-        # drop database connection every 7 hrs to avoid default timeouts on MySQL
-        # (see https://github.com/evennia/evennia/issues/1376)
-        connection.close()
-
-
-# -------------------------------------------------------------
-# Portal Service object
-# -------------------------------------------------------------
-
-
-
[docs]class Portal(object): - - """ - The main Portal server handler. This object sets up the database - and tracks and interlinks all the twisted network services that - make up Portal. - - """ - -
[docs] def __init__(self, application): - """ - Setup the server. - - Args: - application (Application): An instantiated Twisted application - - """ - sys.path.append(".") - - # create a store of services - self.services = service.MultiService() - self.services.setServiceParent(application) - self.amp_protocol = None # set by amp factory - self.sessions = PORTAL_SESSIONS - self.sessions.portal = self - self.process_id = os.getpid() - - self.server_process_id = None - self.server_restart_mode = "shutdown" - self.server_info_dict = {} - - self.start_time = time.time() - - self.maintenance_task = LoopingCall(_portal_maintenance) - self.maintenance_task.start(60, now=True) # call every minute - - # in non-interactive portal mode, this gets overwritten by - # cmdline sent by the evennia launcher - self.server_twistd_cmd = self._get_backup_server_twistd_cmd() - - # set a callback if the server is killed abruptly, - # by Ctrl-C, reboot etc. - reactor.addSystemEventTrigger( - "before", "shutdown", self.shutdown, _reactor_stopping=True, _stop_server=True - )
- - def _get_backup_server_twistd_cmd(self): - """ - For interactive Portal mode there is no way to get the server cmdline from the launcher, so - we need to guess it here (it's very likely to not change) - - Returns: - server_twistd_cmd (list): An instruction for starting the server, to pass to Popen. - """ - server_twistd_cmd = [ - "twistd", - "--python={}".format(os.path.join(dirname(dirname(abspath(__file__))), "server.py")), - ] - if os.name != "nt": - gamedir = os.getcwd() - server_twistd_cmd.append( - "--pidfile={}".format(os.path.join(gamedir, "server", "server.pid")) - ) - return server_twistd_cmd - -
[docs] def get_info_dict(self): - "Return the Portal info, for display." - return INFO_DICT
- -
[docs] def shutdown(self, _reactor_stopping=False, _stop_server=False): - """ - Shuts down the server from inside it. - - Args: - _reactor_stopping (bool, optional): This is set if server - is already in the process of shutting down; in this case - we don't need to stop it again. - _stop_server (bool, optional): Only used in portal-interactive mode; - makes sure to stop the Server cleanly. - - Note that restarting (regardless of the setting) will not work - if the Portal is currently running in daemon mode. In that - case it always needs to be restarted manually. - - """ - if _reactor_stopping and hasattr(self, "shutdown_complete"): - # we get here due to us calling reactor.stop below. No need - # to do the shutdown procedure again. - return - - self.sessions.disconnect_all() - if _stop_server: - self.amp_protocol.stop_server(mode="shutdown") - if not _reactor_stopping: - # shutting down the reactor will trigger another signal. We set - # a flag to avoid loops. - self.shutdown_complete = True - reactor.callLater(0, reactor.stop)
- - -# ------------------------------------------------------------- -# -# Start the Portal proxy server and add all active services -# -# ------------------------------------------------------------- - - -# twistd requires us to define the variable 'application' so it knows -# what to execute from. -application = service.Application("Portal") - -# custom logging - -if "--nodaemon" not in sys.argv: - logfile = logger.WeeklyLogFile( - os.path.basename(settings.PORTAL_LOG_FILE), - os.path.dirname(settings.PORTAL_LOG_FILE), - day_rotation=settings.PORTAL_LOG_DAY_ROTATION, - max_size=settings.PORTAL_LOG_MAX_SIZE, - ) - application.setComponent(ILogObserver, logger.PortalLogObserver(logfile).emit) - -# The main Portal server program. This sets up the database -# and is where we store all the other services. -PORTAL = Portal(application) - -if LOCKDOWN_MODE: - - INFO_DICT["lockdown_mode"] = " LOCKDOWN_MODE active: Only local connections." - -if AMP_ENABLED: - - # The AMP protocol handles the communication between - # the portal and the mud server. Only reason to ever deactivate - # it would be during testing and debugging. - - from evennia.server.portal import amp_server - - INFO_DICT["amp"] = "amp: %s" % AMP_PORT - - factory = amp_server.AMPServerFactory(PORTAL) - amp_service = internet.TCPServer(AMP_PORT, factory, interface=AMP_INTERFACE) - amp_service.setName("PortalAMPServer") - PORTAL.services.addService(amp_service) - - -# We group all the various services under the same twisted app. -# These will gradually be started as they are initialized below. - -if TELNET_ENABLED: - - # Start telnet game connections - - from evennia.server.portal import telnet - - for interface in TELNET_INTERFACES: - ifacestr = "" - if interface not in ("0.0.0.0", "::") or len(TELNET_INTERFACES) > 1: - ifacestr = "-%s" % interface - for port in TELNET_PORTS: - pstring = "%s:%s" % (ifacestr, port) - factory = telnet.TelnetServerFactory() - factory.noisy = False - factory.protocol = telnet.TelnetProtocol - factory.sessionhandler = PORTAL_SESSIONS - telnet_service = internet.TCPServer(port, factory, interface=interface) - telnet_service.setName("EvenniaTelnet%s" % pstring) - PORTAL.services.addService(telnet_service) - - INFO_DICT["telnet"].append("telnet%s: %s" % (ifacestr, port)) - - -if SSL_ENABLED: - - # Start Telnet+SSL game connection (requires PyOpenSSL). - - from evennia.server.portal import telnet_ssl - - for interface in SSL_INTERFACES: - ifacestr = "" - if interface not in ("0.0.0.0", "::") or len(SSL_INTERFACES) > 1: - ifacestr = "-%s" % interface - for port in SSL_PORTS: - pstring = "%s:%s" % (ifacestr, port) - factory = protocol.ServerFactory() - factory.noisy = False - factory.sessionhandler = PORTAL_SESSIONS - factory.protocol = telnet_ssl.SSLProtocol - - ssl_context = telnet_ssl.getSSLContext() - if ssl_context: - ssl_service = internet.SSLServer( - port, factory, telnet_ssl.getSSLContext(), interface=interface - ) - ssl_service.setName("EvenniaSSL%s" % pstring) - PORTAL.services.addService(ssl_service) - - INFO_DICT["telnet_ssl"].append("telnet+ssl%s: %s" % (ifacestr, port)) - else: - INFO_DICT["telnet_ssl"].append( - "telnet+ssl%s: %s (deactivated - keys/cert unset)" % (ifacestr, port) - ) - - -if SSH_ENABLED: - - # Start SSH game connections. Will create a keypair in - # evennia/game if necessary. - - from evennia.server.portal import ssh - - for interface in SSH_INTERFACES: - ifacestr = "" - if interface not in ("0.0.0.0", "::") or len(SSH_INTERFACES) > 1: - ifacestr = "-%s" % interface - for port in SSH_PORTS: - pstring = "%s:%s" % (ifacestr, port) - factory = ssh.makeFactory( - { - "protocolFactory": ssh.SshProtocol, - "protocolArgs": (), - "sessions": PORTAL_SESSIONS, - } - ) - factory.noisy = False - ssh_service = internet.TCPServer(port, factory, interface=interface) - ssh_service.setName("EvenniaSSH%s" % pstring) - PORTAL.services.addService(ssh_service) - - INFO_DICT["ssh"].append("ssh%s: %s" % (ifacestr, port)) - - -if WEBSERVER_ENABLED: - from evennia.server.webserver import Website - - # Start a reverse proxy to relay data to the Server-side webserver - - websocket_started = False - for interface in WEBSERVER_INTERFACES: - ifacestr = "" - if interface not in ("0.0.0.0", "::") or len(WEBSERVER_INTERFACES) > 1: - ifacestr = "-%s" % interface - for proxyport, serverport in WEBSERVER_PORTS: - web_root = EvenniaReverseProxyResource("127.0.0.1", serverport, "") - webclientstr = "" - if WEBCLIENT_ENABLED: - # create ajax client processes at /webclientdata - from evennia.server.portal import webclient_ajax - - ajax_webclient = webclient_ajax.AjaxWebClient() - ajax_webclient.sessionhandler = PORTAL_SESSIONS - web_root.putChild(b"webclientdata", ajax_webclient) - webclientstr = "webclient (ajax only)" - - if WEBSOCKET_CLIENT_ENABLED and not websocket_started: - # start websocket client port for the webclient - # we only support one websocket client - from evennia.server.portal import webclient - from autobahn.twisted.websocket import WebSocketServerFactory - - w_interface = WEBSOCKET_CLIENT_INTERFACE - w_ifacestr = "" - if w_interface not in ("0.0.0.0", "::") or len(WEBSERVER_INTERFACES) > 1: - w_ifacestr = "-%s" % w_interface - port = WEBSOCKET_CLIENT_PORT - -
[docs] class Websocket(WebSocketServerFactory): - "Only here for better naming in logs" - pass
- - factory = Websocket() - factory.noisy = False - factory.protocol = webclient.WebSocketClient - factory.sessionhandler = PORTAL_SESSIONS - websocket_service = internet.TCPServer(port, factory, interface=w_interface) - websocket_service.setName("EvenniaWebSocket%s:%s" % (w_ifacestr, port)) - PORTAL.services.addService(websocket_service) - websocket_started = True - webclientstr = "webclient-websocket%s: %s" % (w_ifacestr, port) - INFO_DICT["webclient"].append(webclientstr) - - if WEB_PLUGINS_MODULE: - try: - web_root = WEB_PLUGINS_MODULE.at_webproxy_root_creation(web_root) - except Exception as e: # Legacy user has not added an at_webproxy_root_creation function in existing web plugins file - INFO_DICT["errors"] = ( - "WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() not found - " - "copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf." - ) - web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE) - web_root.is_portal = True - proxy_service = internet.TCPServer(proxyport, web_root, interface=interface) - proxy_service.setName("EvenniaWebProxy%s:%s" % (ifacestr, proxyport)) - PORTAL.services.addService(proxy_service) - INFO_DICT["webserver_proxy"].append("webserver-proxy%s: %s" % (ifacestr, proxyport)) - INFO_DICT["webserver_internal"].append("webserver: %s" % serverport) - - -for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES: - # external plugin services to start - if plugin_module: - plugin_module.start_plugin_services(PORTAL) - -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/portalsessionhandler.html b/docs/0.9.5/_modules/evennia/server/portal/portalsessionhandler.html deleted file mode 100644 index 38640c6fc2..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/portalsessionhandler.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - evennia.server.portal.portalsessionhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.portalsessionhandler

-"""
-Sessionhandler for portal sessions
-"""
-
-
-import time
-from collections import deque, namedtuple
-from twisted.internet import reactor
-from django.conf import settings
-from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC, PDISCONNALL
-from evennia.utils.logger import log_trace
-
-# module import
-_MOD_IMPORT = None
-
-# global throttles
-_MAX_CONNECTION_RATE = float(settings.MAX_CONNECTION_RATE)
-# per-session throttles
-_MAX_COMMAND_RATE = float(settings.MAX_COMMAND_RATE)
-_MAX_CHAR_LIMIT = int(settings.MAX_CHAR_LIMIT)
-
-_MIN_TIME_BETWEEN_CONNECTS = 1.0 / float(_MAX_CONNECTION_RATE)
-_MIN_TIME_BETWEEN_COMMANDS = 1.0 / float(_MAX_COMMAND_RATE)
-
-_ERROR_COMMAND_OVERFLOW = settings.COMMAND_RATE_WARNING
-_ERROR_MAX_CHAR = settings.MAX_CHAR_LIMIT_WARNING
-
-_CONNECTION_QUEUE = deque()
-
-DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0)
-
-# -------------------------------------------------------------
-# Portal-SessionHandler class
-# -------------------------------------------------------------
-
-
-
[docs]class PortalSessionHandler(SessionHandler): - """ - This object holds the sessions connected to the portal at any time. - It is synced with the server's equivalent SessionHandler over the AMP - connection. - - Sessions register with the handler using the connect() method. This - will assign a new unique sessionid to the session and send that sessid - to the server using the AMP connection. - - """ - -
[docs] def __init__(self, *args, **kwargs): - """ - Init the handler - - """ - super().__init__(*args, **kwargs) - self.portal = None - self.latest_sessid = 0 - self.uptime = time.time() - self.connection_time = 0 - - self.connection_last = self.uptime - self.connection_task = None
- -
[docs] def at_server_connection(self): - """ - Called when the Portal establishes connection with the Server. - At this point, the AMP connection is already established. - - """ - self.connection_time = time.time()
- -
[docs] def connect(self, session): - """ - Called by protocol at first connect. This adds a not-yet - authenticated session using an ever-increasing counter for - sessid. - - Args: - session (PortalSession): The Session connecting. - - Notes: - We implement a throttling mechanism here to limit the speed at - which new connections are accepted - this is both a stop - against DoS attacks as well as helps using the Dummyrunner - tester with a large number of connector dummies. - - """ - global _CONNECTION_QUEUE - - if session: - # assign if we are first-connectors - if not session.sessid: - # if the session already has a sessid (e.g. being inherited in the - # case of a webclient auto-reconnect), keep it - self.latest_sessid += 1 - session.sessid = self.latest_sessid - session.server_connected = False - _CONNECTION_QUEUE.appendleft(session) - if len(_CONNECTION_QUEUE) > 1: - session.data_out( - text=[ - [ - "%s DoS protection is active. You are queued to connect in %g seconds ..." - % ( - settings.SERVERNAME, - len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS, - ) - ], - {}, - ] - ) - now = time.time() - if ( - now - self.connection_last < _MIN_TIME_BETWEEN_CONNECTS - ) or not self.portal.amp_protocol: - if not session or not self.connection_task: - self.connection_task = reactor.callLater( - _MIN_TIME_BETWEEN_CONNECTS, self.connect, None - ) - self.connection_last = now - return - elif not session: - if _CONNECTION_QUEUE: - # keep launching tasks until queue is empty - self.connection_task = reactor.callLater( - _MIN_TIME_BETWEEN_CONNECTS, self.connect, None - ) - else: - self.connection_task = None - self.connection_last = now - - if _CONNECTION_QUEUE: - # sync with server-side - session = _CONNECTION_QUEUE.pop() - sessdata = session.get_sync_data() - - self[session.sessid] = session - session.server_connected = True - self.portal.amp_protocol.send_AdminPortal2Server( - session, operation=PCONN, sessiondata=sessdata - )
- -
[docs] def sync(self, session): - """ - Called by the protocol of an already connected session. This - can be used to sync the session info in a delayed manner, such - as when negotiation and handshakes are delayed. - - Args: - session (PortalSession): Session to sync. - - """ - if session.sessid and session.server_connected: - # only use if session already has sessid and has already connected - # once to the server - if so we must re-sync woth the server, otherwise - # we skip this step. - sessdata = session.get_sync_data() - if self.portal.amp_protocol: - # we only send sessdata that should not have changed - # at the server level at this point - sessdata = dict( - (key, val) - for key, val in sessdata.items() - if key - in ( - "protocol_key", - "address", - "sessid", - "csessid", - "conn_time", - "protocol_flags", - "server_data", - ) - ) - self.portal.amp_protocol.send_AdminPortal2Server( - session, operation=PCONNSYNC, sessiondata=sessdata - )
- -
[docs] def disconnect(self, session): - """ - Called from portal when the connection is closed from the - portal side. - - Args: - session (PortalSession): Session to disconnect. - delete (bool, optional): Delete the session from - the handler. Only time to not do this is when - this is called from a loop, such as from - self.disconnect_all(). - - """ - global _CONNECTION_QUEUE - if session in _CONNECTION_QUEUE: - # connection was already dropped before we had time - # to forward this to the Server, so now we just remove it. - _CONNECTION_QUEUE.remove(session) - return - - if session.sessid in self and not hasattr(self, "_disconnect_all"): - # if this was called directly from the protocol, the - # connection is already dead and we just need to cleanup - del self[session.sessid] - - # Tell the Server to disconnect its version of the Session as well. - self.portal.amp_protocol.send_AdminPortal2Server(session, operation=PDISCONN)
- -
[docs] def disconnect_all(self): - """ - Disconnect all sessions, informing the Server. - """ - - def _callback(result, sessionhandler): - # we set a watchdog to stop self.disconnect from deleting - # sessions while we are looping over them. - sessionhandler._disconnect_all = True - for session in sessionhandler.values(): - session.disconnect() - del sessionhandler._disconnect_all - - # inform Server; wait until finished sending before we continue - # removing all the sessions. - self.portal.amp_protocol.send_AdminPortal2Server( - DUMMYSESSION, operation=PDISCONNALL - ).addCallback(_callback, self)
- -
[docs] def server_connect(self, protocol_path="", config=dict()): - """ - Called by server to force the initialization of a new protocol - instance. Server wants this instance to get a unique sessid and to be - connected back as normal. This is used to initiate irc/rss etc - connections. - - Args: - protocol_path (str): Full python path to the class factory - for the protocol used, eg - 'evennia.server.portal.irc.IRCClientFactory' - config (dict): Dictionary of configuration options, fed as - `**kwarg` to protocol class `__init__` method. - - Raises: - RuntimeError: If The correct factory class is not found. - - Notes: - The called protocol class must have a method start() - that calls the portalsession.connect() as a normal protocol. - - """ - global _MOD_IMPORT - if not _MOD_IMPORT: - from evennia.utils.utils import variable_from_module as _MOD_IMPORT - path, clsname = protocol_path.rsplit(".", 1) - cls = _MOD_IMPORT(path, clsname) - if not cls: - raise RuntimeError("ServerConnect: protocol factory '%s' not found." % protocol_path) - protocol = cls(self, **config) - protocol.start()
- -
[docs] def server_disconnect(self, session, reason=""): - """ - Called by server to force a disconnect by sessid. - - Args: - session (portalsession): Session to disconnect. - reason (str, optional): Motivation for disconnect. - - """ - if session: - session.disconnect(reason) - if session.sessid in self: - # in case sess.disconnect doesn't delete it - del self[session.sessid] - del session
- -
[docs] def server_disconnect_all(self, reason=""): - """ - Called by server when forcing a clean disconnect for everyone. - - Args: - reason (str, optional): Motivation for disconnect. - - """ - for session in list(self.values()): - session.disconnect(reason) - del session - self.clear()
- -
[docs] def server_logged_in(self, session, data): - """ - The server tells us that the session has been authenticated. - Update it. Called by the Server. - - Args: - session (Session): Session logging in. - data (dict): The session sync data. - - """ - session.load_sync_data(data) - session.at_login()
- -
[docs] def server_session_sync(self, serversessions, clean=True): - """ - Server wants to save data to the portal, maybe because it's - about to shut down. We don't overwrite any sessions here, just - update them in-place. - - Args: - serversessions (dict): This is a dictionary - - `{sessid:{property:value},...}` describing - the properties to sync on all sessions. - clean (bool): If True, remove any Portal sessions that are - not included in serversessions. - """ - to_save = [sessid for sessid in serversessions if sessid in self] - # save protocols - for sessid in to_save: - self[sessid].load_sync_data(serversessions[sessid]) - if clean: - # disconnect out-of-sync missing protocols - to_delete = [sessid for sessid in self if sessid not in to_save] - for sessid in to_delete: - self.server_disconnect(sessid)
- -
[docs] def count_loggedin(self, include_unloggedin=False): - """ - Count loggedin connections, alternatively count all connections. - - Args: - include_unloggedin (bool): Also count sessions that have - not yet authenticated. - - Returns: - count (int): Number of sessions. - - """ - return len(self.get_sessions(include_unloggedin=include_unloggedin))
- -
[docs] def sessions_from_csessid(self, csessid): - """ - Given a session id, retrieve the session (this is primarily - intended to be called by web clients) - - Args: - csessid (int): Session id. - - Returns: - session (list): The matching session, if found. - - """ - return [ - sess - for sess in self.get_sessions(include_unloggedin=True) - if hasattr(sess, "csessid") and sess.csessid and sess.csessid == csessid - ]
- -
[docs] def announce_all(self, message): - """ - Send message to all connected sessions. - - Args: - message (str): Message to relay. - - Notes: - This will create an on-the fly text-type - send command. - - """ - for session in self.values(): - self.data_out(session, text=[[message], {}])
- -
[docs] def data_in(self, session, **kwargs): - """ - Called by portal sessions for relaying data coming - in from the protocol to the server. - - Args: - session (PortalSession): Session receiving data. - - Keyword Args: - kwargs (any): Other data from protocol. - - Notes: - Data is serialized before passed on. - - """ - try: - text = kwargs["text"] - if (_MAX_CHAR_LIMIT > 0) and len(text) > _MAX_CHAR_LIMIT: - if session: - self.data_out(session, text=[[_ERROR_MAX_CHAR], {}]) - return - except Exception: - # if there is a problem to send, we continue - pass - if session: - now = time.time() - - try: - command_counter_reset = session.command_counter_reset - except AttributeError: - command_counter_reset = session.command_counter_reset = now - session.command_counter = 0 - - # global command-rate limit - if max(0, now - command_counter_reset) > 1.0: - # more than a second since resetting the counter. Refresh. - session.command_counter_reset = now - session.command_counter = 0 - - session.command_counter += 1 - - if session.command_counter * _MIN_TIME_BETWEEN_COMMANDS > 1.0: - self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW], {}]) - return - - if not self.portal.amp_protocol: - # this can happen if someone connects before AMP connection - # was established (usually on first start) - reactor.callLater(1.0, self.data_in, session, **kwargs) - return - - # scrub data - kwargs = self.clean_senddata(session, kwargs) - - # relay data to Server - session.cmd_last = now - self.portal.amp_protocol.send_MsgPortal2Server(session, **kwargs)
- -
[docs] def data_out(self, session, **kwargs): - """ - Called by server for having the portal relay messages and data - to the correct session protocol. - - Args: - session (Session): Session sending data. - - Keyword Args: - kwargs (any): Each key is a command instruction to the - protocol on the form key = [[args],{kwargs}]. This will - call a method send_<key> on the protocol. If no such - method exixts, it sends the data to a method send_default. - - """ - # from evennia.server.profiling.timetrace import timetrace # DEBUG - # text = timetrace(text, "portalsessionhandler.data_out") # DEBUG - - # distribute outgoing data to the correct session methods. - if session: - for cmdname, (cmdargs, cmdkwargs) in kwargs.items(): - funcname = "send_%s" % cmdname.strip().lower() - if hasattr(session, funcname): - # better to use hassattr here over try..except - # - avoids hiding AttributeErrors in the call. - try: - getattr(session, funcname)(*cmdargs, **cmdkwargs) - except Exception: - log_trace() - else: - try: - # note that send_default always takes cmdname - # as arg too. - session.send_default(cmdname, *cmdargs, **cmdkwargs) - except Exception: - log_trace()
- - -PORTAL_SESSIONS = PortalSessionHandler() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/rss.html b/docs/0.9.5/_modules/evennia/server/portal/rss.html deleted file mode 100644 index d7ae9fe9f5..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/rss.html +++ /dev/null @@ -1,269 +0,0 @@ - - - - - - - - evennia.server.portal.rss — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.rss

-"""
-RSS parser for Evennia
-
-This connects an RSS feed to an in-game Evennia channel, sending messages
-to the channel whenever the feed updates.
-
-"""
-from twisted.internet import task, threads
-from django.conf import settings
-from evennia.server.session import Session
-from evennia.utils import logger
-
-RSS_ENABLED = settings.RSS_ENABLED
-# RETAG = re.compile(r'<[^>]*?>')
-
-if RSS_ENABLED:
-    try:
-        import feedparser
-    except ImportError:
-        raise ImportError(
-            "RSS requires python-feedparser to be installed. Install or set RSS_ENABLED=False."
-        )
-
-
-
[docs]class RSSReader(Session): - """ - A simple RSS reader using the feedparser module. - - """ - -
[docs] def __init__(self, factory, url, rate): - """ - Initialize the reader. - - Args: - factory (RSSFactory): The protocol factory. - url (str): The RSS url. - rate (int): The seconds between RSS lookups. - - """ - self.url = url - self.rate = rate - self.factory = factory - self.old_entries = {}
- -
[docs] def get_new(self): - """ - Returns list of new items. - - """ - feed = feedparser.parse(self.url) - new_entries = [] - for entry in feed["entries"]: - idval = entry["id"] + entry.get("updated", "") - if idval not in self.old_entries: - self.old_entries[idval] = entry - new_entries.append(entry) - return new_entries
- -
[docs] def disconnect(self, reason=None): - """ - Disconnect from feed. - - Args: - reason (str, optional): Motivation for the disconnect. - - """ - if self.factory.task and self.factory.task.running: - self.factory.task.stop() - self.sessionhandler.disconnect(self)
- - def _callback(self, new_entries, init): - """ - Called when RSS returns. - - Args: - new_entries (list): List of new RSS entries since last. - init (bool): If this is a startup operation (at which - point all entries are considered new). - - """ - if not init: - # for initialization we just ignore old entries - for entry in reversed(new_entries): - self.data_in(entry) - -
[docs] def data_in(self, text=None, **kwargs): - """ - Data RSS -> Evennia. - - Keyword Args: - text (str): Incoming text - kwargs (any): Options from protocol. - - """ - self.sessionhandler.data_in(self, bot_data_in=text, **kwargs)
- - def _errback(self, fail): - "Report error" - logger.log_err("RSS feed error: %s" % fail.value) - -
[docs] def update(self, init=False): - """ - Request the latest version of feed. - - Args: - init (bool, optional): If this is an initialization call - or not (during init, all entries are conidered new). - - Notes: - This call is done in a separate thread to avoid blocking - on slow connections. - - """ - return ( - threads.deferToThread(self.get_new) - .addCallback(self._callback, init) - .addErrback(self._errback) - )
- - -
[docs]class RSSBotFactory(object): - """ - Initializes new bots. - """ - -
[docs] def __init__(self, sessionhandler, uid=None, url=None, rate=None): - """ - Initialize the bot. - - Args: - sessionhandler (PortalSessionHandler): The main sessionhandler object. - uid (int): User id for the bot. - url (str): The RSS URL. - rate (int): How often for the RSS to request the latest RSS entries. - - """ - self.sessionhandler = sessionhandler - self.url = url - self.rate = rate - self.uid = uid - self.bot = RSSReader(self, url, rate) - self.task = None
- -
[docs] def start(self): - """ - Called by portalsessionhandler. Starts the bot. - """ - - def errback(fail): - logger.log_err(fail.value) - - # set up session and connect it to sessionhandler - self.bot.init_session("rssbot", self.url, self.sessionhandler) - self.bot.uid = self.uid - self.bot.logged_in = True - self.sessionhandler.connect(self.bot) - - # start repeater task - self.bot.update(init=True) - self.task = task.LoopingCall(self.bot.update) - if self.rate: - self.task.start(self.rate, now=False).addErrback(errback)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/ssh.html b/docs/0.9.5/_modules/evennia/server/portal/ssh.html deleted file mode 100644 index ae8c2d09b7..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/ssh.html +++ /dev/null @@ -1,632 +0,0 @@ - - - - - - - - evennia.server.portal.ssh — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.ssh

-"""
-This module implements the ssh (Secure SHell) protocol for encrypted
-connections.
-
-This depends on a generic session module that implements the actual
-login procedure of the game, tracks sessions etc.
-
-Using standard ssh client,
-
-"""
-
-import os
-import re
-
-from twisted.cred.checkers import credentials
-from twisted.cred.portal import Portal
-from twisted.conch.interfaces import IConchUser
-
-_SSH_IMPORT_ERROR = """
-ERROR: Missing crypto library for SSH. Install it with
-
-       pip install cryptography pyasn1 bcrypt
-
-(On older Twisted versions you may have to do 'pip install pycrypto pyasn1' instead).
-
-If you get a compilation error you must install a C compiler and the
-SSL dev headers (On Debian-derived systems this is the gcc and libssl-dev
-packages).
-"""
-
-try:
-    from twisted.conch.ssh.keys import Key
-except ImportError:
-    raise ImportError(_SSH_IMPORT_ERROR)
-
-from twisted.conch.ssh.userauth import SSHUserAuthServer
-from twisted.conch.ssh import common
-from twisted.conch.insults import insults
-from twisted.conch.manhole_ssh import TerminalRealm, _Glue, ConchFactory
-from twisted.conch.manhole import Manhole, recvline
-from twisted.internet import defer, protocol
-from twisted.conch import interfaces as iconch
-from twisted.python import components
-from django.conf import settings
-
-from evennia.server import session
-from evennia.accounts.models import AccountDB
-from evennia.utils import ansi
-from evennia.utils.utils import to_str
-
-_RE_N = re.compile(r"\|n$")
-_RE_SCREENREADER_REGEX = re.compile(
-    r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE
-)
-_GAME_DIR = settings.GAME_DIR
-_PRIVATE_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssh-private.key")
-_PUBLIC_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssh-public.key")
-_KEY_LENGTH = 2048
-
-CTRL_C = "\x03"
-CTRL_D = "\x04"
-CTRL_BACKSLASH = "\x1c"
-CTRL_L = "\x0c"
-
-_NO_AUTOGEN = """
-Evennia could not generate SSH private- and public keys ({{err}})
-Using conch default keys instead.
-
-If this error persists, create the keys manually (using the tools for your OS)
-and put them here:
-    {}
-    {}
-""".format(
-    _PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
-)
-
-
-# not used atm
-
[docs]class SSHServerFactory(protocol.ServerFactory): - "This is only to name this better in logs" - noisy = False - -
[docs] def logPrefix(self): - return "SSH"
- - -
[docs]class SshProtocol(Manhole, session.Session): - """ - Each account connecting over ssh gets this protocol assigned to - them. All communication between game and account goes through - here. - - """ - - noisy = False - -
[docs] def __init__(self, starttuple): - """ - For setting up the account. If account is not None then we'll - login automatically. - - Args: - starttuple (tuple): A (account, factory) tuple. - - """ - self.protocol_key = "ssh" - self.authenticated_account = starttuple[0] - # obs must not be called self.factory, that gets overwritten! - self.cfactory = starttuple[1]
- -
[docs] def terminalSize(self, width, height): - """ - Initialize the terminal and connect to the new session. - - Args: - width (int): Width of terminal. - height (int): Height of terminal. - - """ - # Clear the previous input line, redraw it at the new - # cursor position - self.terminal.eraseDisplay() - self.terminal.cursorHome() - self.width = width - self.height = height - - # initialize the session - client_address = self.getClientAddress() - client_address = client_address.host if client_address else None - self.init_session("ssh", client_address, self.cfactory.sessionhandler) - - # since we might have authenticated already, we might set this here. - if self.authenticated_account: - self.logged_in = True - self.uid = self.authenticated_account.id - self.sessionhandler.connect(self)
- -
[docs] def connectionMade(self): - """ - This is called when the connection is first established. - - """ - recvline.HistoricRecvLine.connectionMade(self) - self.keyHandlers[CTRL_C] = self.handle_INT - self.keyHandlers[CTRL_D] = self.handle_EOF - self.keyHandlers[CTRL_L] = self.handle_FF - self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
- - # initalize - -
[docs] def handle_INT(self): - """ - Handle ^C as an interrupt keystroke by resetting the current - input variables to their initial state. - - """ - self.lineBuffer = [] - self.lineBufferIndex = 0 - - self.terminal.nextLine() - self.terminal.write("KeyboardInterrupt") - self.terminal.nextLine()
- -
[docs] def handle_EOF(self): - """ - Handles EOF generally used to exit. - - """ - if self.lineBuffer: - self.terminal.write("\a") - else: - self.handle_QUIT()
- -
[docs] def handle_FF(self): - """ - Handle a 'form feed' byte - generally used to request a screen - refresh/redraw. - - """ - self.terminal.eraseDisplay() - self.terminal.cursorHome()
- -
[docs] def handle_QUIT(self): - """ - Quit, end, and lose the connection. - - """ - self.terminal.loseConnection()
- -
[docs] def connectionLost(self, reason=None): - """ - This is executed when the connection is lost for whatever - reason. It can also be called directly, from the disconnect - method. - - Args: - reason (str): Motivation for loosing connection. - - """ - insults.TerminalProtocol.connectionLost(self, reason) - self.sessionhandler.disconnect(self) - self.terminal.loseConnection()
- -
[docs] def getClientAddress(self): - """ - Get client address. - - Returns: - address_and_port (tuple): The client's address and port in - a tuple. For example `('127.0.0.1', 41917)`. - - """ - return self.terminal.transport.getPeer()
- -
[docs] def lineReceived(self, string): - """ - Communication User -> Evennia. Any line return indicates a - command for the purpose of the MUD. So we take the user input - and pass it on to the game engine. - - Args: - string (str): Input text. - - """ - self.sessionhandler.data_in(self, text=string)
- -
[docs] def sendLine(self, string): - """ - Communication Evennia -> User. Any string sent should - already have been properly formatted and processed before - reaching this point. - - Args: - string (str): Output text. - - """ - for line in string.split("\n"): - # the telnet-specific method for sending - self.terminal.write(line) - self.terminal.nextLine()
- - # session-general method hooks - -
[docs] def at_login(self): - """ - Called when this session gets authenticated by the server. - """ - pass
- -
[docs] def disconnect(self, reason="Connection closed. Goodbye for now."): - """ - Disconnect from server. - - Args: - reason (str): Motivation for disconnect. - - """ - if reason: - self.data_out(text=((reason,), {})) - self.connectionLost(reason)
- -
[docs] def data_out(self, **kwargs): - """ - Data Evennia -> User - - Keyword Args: - kwargs (any): Options to the protocol. - - """ - self.sessionhandler.data_out(self, **kwargs)
- -
[docs] def send_text(self, *args, **kwargs): - """ - Send text data. This is an in-band telnet operation. - - Args: - text (str): The first argument is always the text string to send. No other arguments - are considered. - Keyword Args: - options (dict): Send-option flags: - - - mxp: Enforce MXP link support. - - ansi: Enforce no ANSI colors. - - xterm256: Enforce xterm256 colors, regardless of TTYPE setting. - - nocolor: Strip all colors. - - raw: Pass string through without any ansi processing - (i.e. include Evennia ansi markers but do not - convert them into ansi tokens) - - echo: Turn on/off line echo on the client. Turn - off line echo for client, for example for password. - Note that it must be actively turned back on again! - - """ - # print "telnet.send_text", args,kwargs # DEBUG - text = args[0] if args else "" - if text is None: - return - text = to_str(text) - - # handle arguments - options = kwargs.get("options", {}) - flags = self.protocol_flags - xterm256 = options.get("xterm256", flags.get("XTERM256", True)) - useansi = options.get("ansi", flags.get("ANSI", True)) - raw = options.get("raw", flags.get("RAW", False)) - nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi)) - # echo = options.get("echo", None) # DEBUG - screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) - - if screenreader: - # screenreader mode cleans up output - text = ansi.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) - text = _RE_SCREENREADER_REGEX.sub("", text) - - if raw: - # no processing - self.sendLine(text) - return - else: - # we need to make sure to kill the color at the end in order - # to match the webclient output. - linetosend = ansi.parse_ansi( - _RE_N.sub("", text) + ("||n" if text.endswith("|") else "|n"), - strip_ansi=nocolor, - xterm256=xterm256, - mxp=False, - ) - self.sendLine(linetosend)
- -
[docs] def send_prompt(self, *args, **kwargs): - self.send_text(*args, **kwargs)
- -
[docs] def send_default(self, *args, **kwargs): - pass
- - -
[docs]class ExtraInfoAuthServer(SSHUserAuthServer): - - noisy = False - -
[docs] def auth_password(self, packet): - """ - Password authentication. - - Used mostly for setting up the transport so we can query - username and password later. - - Args: - packet (Packet): Auth packet. - - """ - password = common.getNS(packet[1:])[0] - c = credentials.UsernamePassword(self.user, password) - c.transport = self.transport - return self.portal.login(c, None, IConchUser).addErrback(self._ebPassword)
- - -
[docs]class AccountDBPasswordChecker(object): - """ - Checks the django db for the correct credentials for - username/password otherwise it returns the account or None which is - useful for the Realm. - - """ - - noisy = False - credentialInterfaces = (credentials.IUsernamePassword,) - -
[docs] def __init__(self, factory): - """ - Initialize the factory. - - Args: - factory (SSHFactory): Checker factory. - - """ - self.factory = factory - super().__init__()
- -
[docs] def requestAvatarId(self, c): - """ - Generic credentials. - - """ - up = credentials.IUsernamePassword(c, None) - username = up.username - password = up.password - account = AccountDB.objects.get_account_from_name(username) - res = (None, self.factory) - if account and account.check_password(password): - res = (account, self.factory) - return defer.succeed(res)
- - -
[docs]class PassAvatarIdTerminalRealm(TerminalRealm): - """ - Returns an avatar that passes the avatarId through to the - protocol. This is probably not the best way to do it. - - """ - - noisy = False - - def _getAvatar(self, avatarId): - comp = components.Componentized() - user = self.userFactory(comp, avatarId) - sess = self.sessionFactory(comp) - - sess.transportFactory = self.transportFactory - sess.chainedProtocolFactory = lambda: self.chainedProtocolFactory(avatarId) - - comp.setComponent(iconch.IConchUser, user) - comp.setComponent(iconch.ISession, sess) - - return user
- - -
[docs]class TerminalSessionTransport_getPeer(object): - """ - Taken from twisted's TerminalSessionTransport which doesn't - provide getPeer to the transport. This one does. - - """ - - noisy = False - -
[docs] def __init__(self, proto, chainedProtocol, avatar, width, height): - self.proto = proto - self.avatar = avatar - self.chainedProtocol = chainedProtocol - - session = self.proto.session - - self.proto.makeConnection( - _Glue( - write=self.chainedProtocol.dataReceived, - loseConnection=lambda: avatar.conn.sendClose(session), - name="SSH Proto Transport", - ) - ) - - def loseConnection(): - self.proto.loseConnection() - - def getPeer(): - return session.conn.transport.transport.getPeer() - - self.chainedProtocol.makeConnection( - _Glue( - getPeer=getPeer, - write=self.proto.write, - loseConnection=loseConnection, - name="Chained Proto Transport", - ) - ) - - self.chainedProtocol.terminalProtocol.terminalSize(width, height)
- - -
[docs]def getKeyPair(pubkeyfile, privkeyfile): - """ - This function looks for RSA keypair files in the current directory. If they - do not exist, the keypair is created. - """ - - if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)): - # No keypair exists. Generate a new RSA keypair - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric import rsa - - rsa_key = Key( - rsa.generate_private_key( - public_exponent=65537, key_size=_KEY_LENGTH, backend=default_backend() - ) - ) - public_key_string = rsa_key.public().toString(type="OPENSSH").decode() - private_key_string = rsa_key.toString(type="OPENSSH").decode() - - # save keys for the future. - with open(privkeyfile, "wt") as pfile: - pfile.write(private_key_string) - print("Created SSH private key in '{}'".format(_PRIVATE_KEY_FILE)) - with open(pubkeyfile, "wt") as pfile: - pfile.write(public_key_string) - print("Created SSH public key in '{}'".format(_PUBLIC_KEY_FILE)) - else: - with open(pubkeyfile) as pfile: - public_key_string = pfile.read() - with open(privkeyfile) as pfile: - private_key_string = pfile.read() - - return Key.fromString(public_key_string), Key.fromString(private_key_string)
- - -
[docs]def makeFactory(configdict): - """ - Creates the ssh server factory. - """ - - def chainProtocolFactory(username=None): - return insults.ServerProtocol( - configdict["protocolFactory"], - *configdict.get("protocolConfigdict", (username,)), - **configdict.get("protocolKwArgs", {}), - ) - - rlm = PassAvatarIdTerminalRealm() - rlm.transportFactory = TerminalSessionTransport_getPeer - rlm.chainedProtocolFactory = chainProtocolFactory - factory = ConchFactory(Portal(rlm)) - factory.sessionhandler = configdict["sessions"] - - try: - # create/get RSA keypair - publicKey, privateKey = getKeyPair(_PUBLIC_KEY_FILE, _PRIVATE_KEY_FILE) - factory.publicKeys = {b"ssh-rsa": publicKey} - factory.privateKeys = {b"ssh-rsa": privateKey} - except Exception as err: - print(_NO_AUTOGEN.format(err=err)) - - factory.services = factory.services.copy() - factory.services["ssh-userauth"] = ExtraInfoAuthServer - - factory.portal.registerChecker(AccountDBPasswordChecker(factory)) - - return factory
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/ssl.html b/docs/0.9.5/_modules/evennia/server/portal/ssl.html deleted file mode 100644 index 22055d6c08..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/ssl.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - - - evennia.server.portal.ssl — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.ssl

-"""
-This is a simple context factory for auto-creating
-SSL keys and certificates.
-
-"""
-import os
-import sys
-
-try:
-    import OpenSSL
-    from twisted.internet import ssl as twisted_ssl
-except ImportError as error:
-    errstr = """
-    {err}
-    SSL requires the PyOpenSSL library:
-        pip install pyopenssl
-    """
-    raise ImportError(errstr.format(err=error))
-
-from django.conf import settings
-from evennia.server.portal.telnet import TelnetProtocol
-
-_GAME_DIR = settings.GAME_DIR
-
-# messages
-
-NO_AUTOGEN = """
-
-{err}
-Evennia could not auto-generate the SSL private key. If this error
-persists, create {keyfile} yourself using third-party tools.
-"""
-
-NO_AUTOCERT = """
-
-{err}
-Evennia's SSL context factory could not automatically, create an SSL
-certificate {certfile}.
-
-A private key {keyfile} was already created. Please create {certfile}
-manually using the commands valid  for your operating system, for
-example (linux, using the openssl program):
-    {exestring}
-"""
-
-
-
[docs]class SSLProtocol(TelnetProtocol): - """ - Communication is the same as telnet, except data transfer - is done with encryption. - """ - -
[docs] def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.protocol_name = "ssl"
- - -
[docs]def verify_SSL_key_and_cert(keyfile, certfile): - """ - This function looks for RSA key and certificate in the current - directory. If files ssl.key and ssl.cert does not exist, they - are created. - """ - - if not (os.path.exists(keyfile) and os.path.exists(certfile)): - # key/cert does not exist. Create. - import subprocess - from Crypto.PublicKey import RSA - from twisted.conch.ssh.keys import Key - - print(" Creating SSL key and certificate ... ", end=" ") - - try: - # create the RSA key and store it. - KEY_LENGTH = 1024 - rsaKey = Key(RSA.generate(KEY_LENGTH)) - keyString = rsaKey.toString(type="OPENSSH") - file(keyfile, "w+b").write(keyString) - except Exception as err: - print(NO_AUTOGEN.format(err=err, keyfile=keyfile)) - sys.exit(5) - - # try to create the certificate - CERT_EXPIRE = 365 * 20 # twenty years validity - # default: - # openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300 - exestring = "openssl req -new -x509 -key %s -out %s -days %s" % ( - keyfile, - certfile, - CERT_EXPIRE, - ) - try: - subprocess.call(exestring) - except OSError as err: - raise OSError( - NO_AUTOCERT.format(err=err, certfile=certfile, keyfile=keyfile, exestring=exestring) - ) - print("done.")
- - -
[docs]def getSSLContext(): - """ - This is called by the portal when creating the SSL context - server-side. - - Returns: - ssl_context (tuple): A key and certificate that is either - existing previously or or created on the fly. - - """ - keyfile = os.path.join(_GAME_DIR, "server", "ssl.key") - certfile = os.path.join(_GAME_DIR, "server", "ssl.cert") - - verify_SSL_key_and_cert(keyfile, certfile) - return twisted_ssl.DefaultOpenSSLContextFactory(keyfile, certfile)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/suppress_ga.html b/docs/0.9.5/_modules/evennia/server/portal/suppress_ga.html deleted file mode 100644 index 41bd106ecc..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/suppress_ga.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - evennia.server.portal.suppress_ga — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.suppress_ga

-"""
-
-SUPPRESS-GO-AHEAD
-
-This supports suppressing or activating Evennia
-the GO-AHEAD telnet operation after every server reply.
-If the client sends no explicit DONT SUPRESS GO-AHEAD,
-Evennia will default to supressing it since many clients
-will fail to use it and has no knowledge of this standard.
-
-It is set as the NOGOAHEAD protocol_flag option.
-
-http://www.faqs.org/rfcs/rfc858.html
-
-"""
-SUPPRESS_GA = bytes([3])  # b"\x03"
-
-# default taken from telnet specification
-
-# try to get the customized mssp info, if it exists.
-
-
-
[docs]class SuppressGA(object): - """ - Implements the SUPRESS-GO-AHEAD protocol. Add this to a variable on the telnet - protocol to set it up. - - """ - -
[docs] def __init__(self, protocol): - """ - Initialize suppression of GO-AHEADs. - - Args: - protocol (Protocol): The active protocol instance. - - """ - self.protocol = protocol - - self.protocol.protocol_flags["NOGOAHEAD"] = True - self.protocol.protocol_flags[ - "NOPROMPTGOAHEAD" - ] = True # Used to send a GA after a prompt line only, set in TTYPE (per client) - # tell the client that we prefer to suppress GA ... - self.protocol.will(SUPPRESS_GA).addCallbacks(self.will_suppress_ga, self.wont_suppress_ga)
- -
[docs] def wont_suppress_ga(self, option): - """ - Called when client requests to not suppress GA. - - Args: - option (Option): Not used. - - """ - self.protocol.protocol_flags["NOGOAHEAD"] = False - self.protocol.handshake_done()
- -
[docs] def will_suppress_ga(self, option): - """ - Client will suppress GA - - Args: - option (Option): Not used. - - """ - self.protocol.protocol_flags["NOGOAHEAD"] = True - self.protocol.handshake_done()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/telnet.html b/docs/0.9.5/_modules/evennia/server/portal/telnet.html deleted file mode 100644 index 78d47d3bfb..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/telnet.html +++ /dev/null @@ -1,597 +0,0 @@ - - - - - - - - evennia.server.portal.telnet — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.telnet

-"""
-This module implements the telnet protocol.
-
-This depends on a generic session module that implements
-the actual login procedure of the game, tracks
-sessions etc.
-
-"""
-
-import re
-from twisted.internet import protocol
-from twisted.internet.task import LoopingCall
-from twisted.conch.telnet import Telnet, StatefulTelnetProtocol
-from twisted.conch.telnet import (
-    IAC,
-    NOP,
-    LINEMODE,
-    GA,
-    WILL,
-    WONT,
-    ECHO,
-    NULL,
-    MODE,
-    LINEMODE_EDIT,
-    LINEMODE_TRAPSIG,
-)
-from django.conf import settings
-from evennia.server.session import Session
-from evennia.server.portal import ttype, mssp, telnet_oob, naws, suppress_ga
-from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP
-from evennia.server.portal.mxp import Mxp, mxp_parse
-from evennia.utils import ansi
-from evennia.utils.utils import to_bytes
-
-_RE_N = re.compile(r"\|n$")
-_RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE)
-_RE_LINEBREAK = re.compile(br"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE)
-_RE_SCREENREADER_REGEX = re.compile(
-    r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE
-)
-_IDLE_COMMAND = str.encode(settings.IDLE_COMMAND + "\n")
-
-# identify HTTP indata
-_HTTP_REGEX = re.compile(
-    b"(GET|HEAD|POST|PUT|DELETE|TRACE|OPTIONS|CONNECT|PATCH) (.*? HTTP/[0-9]\.[0-9])", re.I
-)
-
-_HTTP_WARNING = bytes(
-    """
-    This is Evennia's Telnet port and cannot be used for regular HTTP traffic.
-    Use a telnet client to connect here and point your browser to the server's
-    dedicated web port instead.
-
-    """.strip(),
-    "utf-8",
-)
-
-
-
[docs]class TelnetServerFactory(protocol.ServerFactory): - "This is only to name this better in logs" - noisy = False - -
[docs] def logPrefix(self): - return "Telnet"
- - -
[docs]class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): - """ - Each player connecting over telnet (ie using most traditional mud - clients) gets a telnet protocol instance assigned to them. All - communication between game and player goes through here. - """ - -
[docs] def __init__(self, *args, **kwargs): - self.protocol_key = "telnet" - super().__init__(*args, **kwargs)
- -
[docs] def dataReceived(self, data): - """ - Unused by default, but a good place to put debug printouts - of incoming data. - """ - # print(f"telnet dataReceived: {data}") - try: - super().dataReceived(data) - except ValueError as err: - from evennia.utils import logger - logger.log_err(f"Malformed telnet input: {err}")
- -
[docs] def connectionMade(self): - """ - This is called when the connection is first established. - - """ - # important in order to work normally with standard telnet - self.do(LINEMODE).addErrback(self._wont_linemode) - # initialize the session - self.line_buffer = b"" - client_address = self.transport.client - client_address = client_address[0] if client_address else None - # this number is counted down for every handshake that completes. - # when it reaches 0 the portal/server syncs their data - self.handshakes = 8 # suppress-go-ahead, naws, ttype, mccp, mssp, msdp, gmcp, mxp - - self.init_session(self.protocol_key, client_address, self.factory.sessionhandler) - self.protocol_flags["ENCODING"] = settings.ENCODINGS[0] if settings.ENCODINGS else "utf-8" - # add this new connection to sessionhandler so - # the Server becomes aware of it. - self.sessionhandler.connect(self) - # change encoding to ENCODINGS[0] which reflects Telnet default encoding - - # suppress go-ahead - self.sga = suppress_ga.SuppressGA(self) - # negotiate client size - self.naws = naws.Naws(self) - # negotiate ttype (client info) - # Obs: mudlet ttype does not seem to work if we start mccp before ttype. /Griatch - self.ttype = ttype.Ttype(self) - # negotiate mccp (data compression) - turn this off for wireshark analysis - self.mccp = Mccp(self) - # negotiate mssp (crawler communication) - self.mssp = mssp.Mssp(self) - # oob communication (MSDP, GMCP) - two handshake calls! - self.oob = telnet_oob.TelnetOOB(self) - # mxp support - self.mxp = Mxp(self) - - from evennia.utils.utils import delay - - # timeout the handshakes in case the client doesn't reply at all - self._handshake_delay = delay(2, callback=self.handshake_done, timeout=True) - - # TCP/IP keepalive watches for dead links - self.transport.setTcpKeepAlive(1) - # The TCP/IP keepalive is not enough for some networks; - # we have to complement it with a NOP keep-alive. - self.protocol_flags["NOPKEEPALIVE"] = True - self.nop_keep_alive = None - self.toggle_nop_keepalive()
- - def _wont_linemode(self, *args): - """ - Client refuses do(linemode). This is common for MUD-specific - clients, but we must ask for the sake of raw telnet. We ignore - this error. - """ - pass - - def _send_nop_keepalive(self): - """Send NOP keepalive unless flag is set""" - if self.protocol_flags.get("NOPKEEPALIVE"): - self._write(IAC + NOP) - -
[docs] def toggle_nop_keepalive(self): - """ - Allow to toggle the NOP keepalive for those sad clients that - can't even handle a NOP instruction. This is turned off by the - protocol_flag NOPKEEPALIVE (settable e.g. by the default - `@option` command). - """ - if self.nop_keep_alive and self.nop_keep_alive.running: - self.nop_keep_alive.stop() - else: - self.nop_keep_alive = LoopingCall(self._send_nop_keepalive) - self.nop_keep_alive.start(30, now=False)
- -
[docs] def handshake_done(self, timeout=False): - """ - This is called by all telnet extensions once they are finished. - When all have reported, a sync with the server is performed. - The system will force-call this sync after a small time to handle - clients that don't reply to handshakes at all. - """ - if timeout: - if self.handshakes > 0: - self.handshakes = 0 - self.sessionhandler.sync(self) - else: - self.handshakes -= 1 - if self.handshakes <= 0: - # do the sync - self.sessionhandler.sync(self)
- -
[docs] def at_login(self): - """ - Called when this session gets authenticated by the server. - """ - pass
- -
[docs] def enableRemote(self, option): - """ - This sets up the remote-activated options we allow for this protocol. - - Args: - option (char): The telnet option to enable. - - Returns: - enable (bool): If this option should be enabled. - - """ - if option == LINEMODE: - # make sure to activate line mode with local editing for all clients - self.requestNegotiation( - LINEMODE, MODE + bytes(chr(ord(LINEMODE_EDIT) + ord(LINEMODE_TRAPSIG)), "ascii") - ) - return True - else: - return ( - option == ttype.TTYPE - or option == naws.NAWS - or option == MCCP - or option == mssp.MSSP - or option == suppress_ga.SUPPRESS_GA - )
- -
[docs] def disableRemote(self, option): - return ( - option == LINEMODE - or option == ttype.TTYPE - or option == naws.NAWS - or option == MCCP - or option == mssp.MSSP - or option == suppress_ga.SUPPRESS_GA - )
- -
[docs] def enableLocal(self, option): - """ - Call to allow the activation of options for this protocol - - Args: - option (char): The telnet option to enable locally. - - Returns: - enable (bool): If this option should be enabled. - - """ - return ( - option == LINEMODE - or option == MCCP - or option == ECHO - or option == suppress_ga.SUPPRESS_GA - )
- -
[docs] def disableLocal(self, option): - """ - Disable a given option locally. - - Args: - option (char): The telnet option to disable locally. - - """ - if option == LINEMODE: - return True - if option == ECHO: - return True - if option == MCCP: - self.mccp.no_mccp(option) - return True - else: - try: - return super().disableLocal(option) - except Exception: - from evennia.utils import logger - - logger.log_trace()
- -
[docs] def connectionLost(self, reason): - """ - this is executed when the connection is lost for whatever - reason. it can also be called directly, from the disconnect - method - - Args: - reason (str): Motivation for losing connection. - - """ - self.sessionhandler.disconnect(self) - self.transport.loseConnection()
- -
[docs] def applicationDataReceived(self, data): - """ - Telnet method called when non-telnet-command data is coming in - over the telnet connection. We pass it on to the game engine - directly. - - Args: - data (str): Incoming data. - - """ - if not data: - data = [data] - elif data.strip() == NULL: - # this is an ancient type of keepalive used by some - # legacy clients. There should never be a reason to send a - # lone NULL character so this seems to be a safe thing to - # support for backwards compatibility. It also stops the - # NULL from continuously popping up as an unknown command. - data = [_IDLE_COMMAND] - else: - data = _RE_LINEBREAK.split(data) - - if len(data) > 2 and _HTTP_REGEX.match(data[0]): - # guard against HTTP request on the Telnet port; we - # block and kill the connection. - self.transport.write(_HTTP_WARNING) - self.transport.loseConnection() - return - - if self.line_buffer and len(data) > 1: - # buffer exists, it is terminated by the first line feed - data[0] = self.line_buffer + data[0] - self.line_buffer = b"" - # if the last data split is empty, it means all splits have - # line breaks, if not, it is unterminated and must be - # buffered. - self.line_buffer += data.pop() - # send all data chunks - for dat in data: - self.data_in(text=dat + b"\n")
- - def _write(self, data): - """hook overloading the one used in plain telnet""" - data = data.replace(b"\n", b"\r\n").replace(b"\r\r\n", b"\r\n") - super()._write(mccp_compress(self, data)) - -
[docs] def sendLine(self, line): - """ - Hook overloading the one used by linereceiver. - - Args: - line (str): Line to send. - - """ - line = to_bytes(line, self) - # escape IAC in line mode, and correctly add \r\n (the TELNET end-of-line) - line = line.replace(IAC, IAC + IAC) - line = line.replace(b"\n", b"\r\n") - if not line.endswith(b"\r\n") and self.protocol_flags.get("FORCEDENDLINE", True): - line += b"\r\n" - if not self.protocol_flags.get("NOGOAHEAD", True): - line += IAC + GA - return self.transport.write(mccp_compress(self, line))
- - # Session hooks - -
[docs] def disconnect(self, reason=""): - """ - generic hook for the engine to call in order to - disconnect this protocol. - - Args: - reason (str, optional): Reason for disconnecting. - - """ - self.data_out(text=((reason,), {})) - self.connectionLost(reason)
- -
[docs] def data_in(self, **kwargs): - """ - Data User -> Evennia - - Keyword Args: - kwargs (any): Options from the protocol. - - """ - # from evennia.server.profiling.timetrace import timetrace # DEBUG - # text = timetrace(text, "telnet.data_in") # DEBUG - - self.sessionhandler.data_in(self, **kwargs)
- -
[docs] def data_out(self, **kwargs): - """ - Data Evennia -> User - - Keyword Args: - kwargs (any): Options to the protocol - """ - self.sessionhandler.data_out(self, **kwargs)
- - # send_* methods - -
[docs] def send_text(self, *args, **kwargs): - """ - Send text data. This is an in-band telnet operation. - - Args: - text (str): The first argument is always the text string to send. No other arguments - are considered. - Keyword Args: - options (dict): Send-option flags: - - - mxp: Enforce MXP link support. - - ansi: Enforce no ANSI colors. - - xterm256: Enforce xterm256 colors, regardless of TTYPE. - - noxterm256: Enforce no xterm256 color support, regardless of TTYPE. - - nocolor: Strip all Color, regardless of ansi/xterm256 setting. - - raw: Pass string through without any ansi processing - (i.e. include Evennia ansi markers but do not - convert them into ansi tokens) - - echo: Turn on/off line echo on the client. Turn - off line echo for client, for example for password. - Note that it must be actively turned back on again! - - """ - text = args[0] if args else "" - if text is None: - return - - # handle arguments - options = kwargs.get("options", {}) - flags = self.protocol_flags - xterm256 = options.get( - "xterm256", flags.get("XTERM256", False) if flags.get("TTYPE", False) else True - ) - useansi = options.get( - "ansi", flags.get("ANSI", False) if flags.get("TTYPE", False) else True - ) - raw = options.get("raw", flags.get("RAW", False)) - nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi)) - echo = options.get("echo", None) - mxp = options.get("mxp", flags.get("MXP", False)) - screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) - - if screenreader: - # screenreader mode cleans up output - text = ansi.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) - text = _RE_SCREENREADER_REGEX.sub("", text) - - if options.get("send_prompt"): - # send a prompt instead. - prompt = text - if not raw: - # processing - prompt = ansi.parse_ansi( - _RE_N.sub("", prompt) + ("||n" if prompt.endswith("|") else "|n"), - strip_ansi=nocolor, - xterm256=xterm256, - ) - if mxp: - prompt = mxp_parse(prompt) - prompt = to_bytes(prompt, self) - prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n") - if not self.protocol_flags.get("NOPROMPTGOAHEAD", - self.protocol_flags.get("NOGOAHEAD", True)): - prompt += IAC + GA - self.transport.write(mccp_compress(self, prompt)) - else: - if echo is not None: - # turn on/off echo. Note that this is a bit turned around since we use - # echo as if we are "turning off the client's echo" when telnet really - # handles it the other way around. - if echo: - # by telling the client that WE WON'T echo, the client knows - # that IT should echo. This is the expected behavior from - # our perspective. - self.transport.write(mccp_compress(self, IAC + WONT + ECHO)) - else: - # by telling the client that WE WILL echo, the client can - # safely turn OFF its OWN echo. - self.transport.write(mccp_compress(self, IAC + WILL + ECHO)) - if raw: - # no processing - self.sendLine(text) - return - else: - # we need to make sure to kill the color at the end in order - # to match the webclient output. - linetosend = ansi.parse_ansi( - _RE_N.sub("", text) + ("||n" if text.endswith("|") else "|n"), - strip_ansi=nocolor, - xterm256=xterm256, - mxp=mxp, - ) - if mxp: - linetosend = mxp_parse(linetosend) - self.sendLine(linetosend)
- -
[docs] def send_prompt(self, *args, **kwargs): - """ - Send a prompt - a text without a line end. See send_text for argument options. - - """ - kwargs["options"].update({"send_prompt": True}) - self.send_text(*args, **kwargs)
- -
[docs] def send_default(self, cmdname, *args, **kwargs): - """ - Send other oob data - """ - if not cmdname == "options": - self.oob.data_out(cmdname, *args, **kwargs)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/telnet_oob.html b/docs/0.9.5/_modules/evennia/server/portal/telnet_oob.html deleted file mode 100644 index 0a25f42472..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/telnet_oob.html +++ /dev/null @@ -1,551 +0,0 @@ - - - - - - - - evennia.server.portal.telnet_oob — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.telnet_oob

-"""
-
-Telnet OOB (Out of band communication)
-
-OOB protocols allow for asynchronous communication between Evennia and
-compliant telnet clients. The "text" type of send command will always
-be sent "in-band", appearing in the client's main text output. OOB
-commands, by contrast, can have many forms and it is up to the client
-how and if they are handled.  Examples of OOB instructions could be to
-instruct the client to play sounds or to update a graphical health
-bar.
-
-> Note that in Evennia's Web client, all send commands are "OOB commands",
-(including the "text" one), there is no equivalence to MSDP/GMCP for the
-webclient since it doesn't need it.
-
-This implements the following telnet OOB communication protocols:
-
-- MSDP (Mud Server Data Protocol), as per
-  http://tintin.sourceforge.net/msdp/
-- GMCP (Generic Mud Communication Protocol) as per
-  http://www.ironrealms.com/rapture/manual/files/FeatGMCP-txt.html#Generic_MUD_Communication_Protocol%28GMCP%29
-
-Following the lead of KaVir's protocol snippet, we first check if client
-supports MSDP and if not, we fallback to GMCP with a MSDP header where
-applicable.
-
-----
-
-"""
-import re
-import json
-from evennia.utils.utils import is_iter
-
-# General Telnet
-from twisted.conch.telnet import IAC, SB, SE
-
-# MSDP-relevant telnet cmd/opt-codes
-MSDP = bytes([69])
-MSDP_VAR = bytes([1])
-MSDP_VAL = bytes([2])
-MSDP_TABLE_OPEN = bytes([3])
-MSDP_TABLE_CLOSE = bytes([4])
-
-MSDP_ARRAY_OPEN = bytes([5])
-MSDP_ARRAY_CLOSE = bytes([6])
-
-# GMCP
-GMCP = bytes([201])
-
-
-# pre-compiled regexes
-# returns 2-tuple
-msdp_regex_table = re.compile(
-    br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)
-)
-# returns 2-tuple
-msdp_regex_array = re.compile(
-    br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE)
-)
-msdp_regex_var = re.compile(br"%s" % MSDP_VAR)
-msdp_regex_val = re.compile(br"%s" % MSDP_VAL)
-
-EVENNIA_TO_GMCP = {
-    "client_options": "Core.Supports.Get",
-    "get_inputfuncs": "Core.Commands.Get",
-    "get_value": "Char.Value.Get",
-    "repeat": "Char.Repeat.Update",
-    "monitor": "Char.Monitor.Update",
-}
-
-
-# MSDP/GMCP communication handler
-
-
-
[docs]class TelnetOOB(object): - """ - Implements the MSDP and GMCP protocols. - """ - -
[docs] def __init__(self, protocol): - """ - Initiates by storing the protocol on itself and trying to - determine if the client supports MSDP. - - Args: - protocol (Protocol): The active protocol. - - """ - self.protocol = protocol - self.protocol.protocol_flags["OOB"] = False - self.MSDP = False - self.GMCP = False - # ask for the available protocols and assign decoders - # (note that handshake_done() will be called twice!) - self.protocol.negotiationMap[MSDP] = self.decode_msdp - self.protocol.negotiationMap[GMCP] = self.decode_gmcp - self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp) - self.protocol.will(GMCP).addCallbacks(self.do_gmcp, self.no_gmcp) - self.oob_reported = {}
- -
[docs] def no_msdp(self, option): - """ - Client reports No msdp supported or wanted. - - Args: - option (Option): Not used. - - """ - # no msdp, check GMCP - self.protocol.handshake_done()
- -
[docs] def do_msdp(self, option): - """ - Client reports that it supports msdp. - - Args: - option (Option): Not used. - - """ - self.MSDP = True - self.protocol.protocol_flags["OOB"] = True - self.protocol.handshake_done()
- -
[docs] def no_gmcp(self, option): - """ - If this is reached, it means neither MSDP nor GMCP is - supported. - - Args: - option (Option): Not used. - - """ - self.protocol.handshake_done()
- -
[docs] def do_gmcp(self, option): - """ - Called when client confirms that it can do MSDP or GMCP. - - Args: - option (Option): Not used. - - """ - self.GMCP = True - self.protocol.protocol_flags["OOB"] = True - self.protocol.handshake_done()
- - # encoders - -
[docs] def encode_msdp(self, cmdname, *args, **kwargs): - """ - Encode into a valid MSDP command. - - Args: - cmdname (str): Name of send instruction. - args, kwargs (any): Arguments to OOB command. - - Notes: - The output of this encoding will be - MSDP structures on these forms: - :: - - [cmdname, [], {}] -> VAR cmdname VAL "" - [cmdname, [arg], {}] -> VAR cmdname VAL arg - [cmdname, [args],{}] -> VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE - [cmdname, [], {kwargs}] -> VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE - [cmdname, [args], {kwargs}] -> VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE - VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE - - Further nesting is not supported, so if an array argument consists - of an array (for example), that array will be json-converted to a - string. - - """ - msdp_cmdname = "{msdp_var}{msdp_cmdname}{msdp_val}".format( - msdp_var=MSDP_VAR.decode(), msdp_cmdname=cmdname, msdp_val=MSDP_VAL.decode() - ) - - if not (args or kwargs): - return msdp_cmdname.encode() - - # print("encode_msdp in:", cmdname, args, kwargs) # DEBUG - - msdp_args = "" - if args: - msdp_args = msdp_cmdname - if len(args) == 1: - msdp_args += args[0] - else: - msdp_args += ( - "{msdp_array_open}" - "{msdp_args}" - "{msdp_array_close}".format( - msdp_array_open=MSDP_ARRAY_OPEN.decode(), - msdp_array_close=MSDP_ARRAY_CLOSE.decode(), - msdp_args="".join("%s%s" % (MSDP_VAL.decode(), val) for val in args), - ) - ) - - msdp_kwargs = "" - if kwargs: - msdp_kwargs = msdp_cmdname - msdp_kwargs += ( - "{msdp_table_open}" - "{msdp_kwargs}" - "{msdp_table_close}".format( - msdp_table_open=MSDP_TABLE_OPEN.decode(), - msdp_table_close=MSDP_TABLE_CLOSE.decode(), - msdp_kwargs="".join( - "%s%s%s%s" % (MSDP_VAR.decode(), key, MSDP_VAL.decode(), val) - for key, val in kwargs.items() - ), - ) - ) - - msdp_string = msdp_args + msdp_kwargs - - # print("msdp_string:", msdp_string) # DEBUG - return msdp_string.encode()
- -
[docs] def encode_gmcp(self, cmdname, *args, **kwargs): - """ - Encode into GMCP messages. - - Args: - cmdname (str): GMCP OOB command name. - args, kwargs (any): Arguments to OOB command. - - Notes: - GMCP messages will be outgoing on the following - form (the non-JSON cmdname at the start is what - IRE games use, supposedly, and what clients appear - to have adopted). A cmdname without Package will end - up in the Core package, while Core package names will - be stripped on the Evennia side. - :: - - [cmd.name, [], {}] -> Cmd.Name - [cmd.name, [arg], {}] -> Cmd.Name arg - [cmd.name, [args],{}] -> Cmd.Name [args] - [cmd.name, [], {kwargs}] -> Cmd.Name {kwargs} - [cmdname, [args, {kwargs}] -> Core.Cmdname [[args],{kwargs}] - - Notes: - There are also a few default mappings between evennia outputcmds and - GMCP: - :: - - client_options -> Core.Supports.Get - get_inputfuncs -> Core.Commands.Get - get_value -> Char.Value.Get - repeat -> Char.Repeat.Update - monitor -> Char.Monitor.Update - - """ - - if cmdname in EVENNIA_TO_GMCP: - gmcp_cmdname = EVENNIA_TO_GMCP[cmdname] - elif "_" in cmdname: - gmcp_cmdname = ".".join(word.capitalize() for word in cmdname.split("_")) - else: - gmcp_cmdname = "Core.%s" % cmdname.capitalize() - - if not (args or kwargs): - gmcp_string = gmcp_cmdname - elif args: - if len(args) == 1: - args = args[0] - if kwargs: - gmcp_string = "%s %s" % (gmcp_cmdname, json.dumps([args, kwargs])) - else: - gmcp_string = "%s %s" % (gmcp_cmdname, json.dumps(args)) - else: # only kwargs - gmcp_string = "%s %s" % (gmcp_cmdname, json.dumps(kwargs)) - - # print("gmcp string", gmcp_string) # DEBUG - return gmcp_string.encode()
- -
[docs] def decode_msdp(self, data): - """ - Decodes incoming MSDP data. - - Args: - data (str or list): MSDP data. - - Notes: - Clients should always send MSDP data on - one of the following forms: - :: - - cmdname '' -> [cmdname, [], {}] - cmdname val -> [cmdname, [val], {}] - cmdname array -> [cmdname, [array], {}] - cmdname table -> [cmdname, [], {table}] - cmdname array cmdname table -> [cmdname, [array], {table}] - - Observe that all MSDP_VARS are used to identify cmdnames, - so if there are multiple arrays with the same cmdname - given, they will be merged into one argument array, same - for tables. Different MSDP_VARS (outside tables) will be - identified as separate cmdnames. - - """ - if isinstance(data, list): - data = b"".join(data) - - # print("decode_msdp in:", data) # DEBUG - - tables = {} - arrays = {} - variables = {} - - # decode tables - for key, table in msdp_regex_table.findall(data): - key = key.decode() - tables[key] = {} if key not in tables else tables[key] - for varval in msdp_regex_var.split(table)[1:]: - var, val = msdp_regex_val.split(varval, 1) - var, val = var.decode(), val.decode() - if var: - tables[key][var] = val - - # decode arrays from all that was not a table - data_no_tables = msdp_regex_table.sub(b"", data) - for key, array in msdp_regex_array.findall(data_no_tables): - key = key.decode() - arrays[key] = [] if key not in arrays else arrays[key] - parts = msdp_regex_val.split(array) - parts = [part.decode() for part in parts] - if len(parts) == 2: - arrays[key].append(parts[1]) - elif len(parts) > 1: - arrays[key].extend(parts[1:]) - - # decode remainders from all that were not tables or arrays - data_no_tables_or_arrays = msdp_regex_array.sub(b"", data_no_tables) - for varval in msdp_regex_var.split(data_no_tables_or_arrays): - # get remaining varvals after cleaning away tables/arrays. If mathcing - # an existing key in arrays, it will be added as an argument to that command, - # otherwise it will be treated as a command without argument. - parts = msdp_regex_val.split(varval) - parts = [part.decode() for part in parts] - if len(parts) == 2: - variables[parts[0]] = parts[1] - elif len(parts) > 1: - variables[parts[0]] = parts[1:] - - cmds = {} - # merge matching table/array/variables together - for key, table in tables.items(): - args, kwargs = [], table - if key in arrays: - args.extend(arrays.pop(key)) - if key in variables: - args.append(variables.pop(key)) - cmds[key] = [args, kwargs] - - for key, arr in arrays.items(): - args, kwargs = arr, {} - if key in variables: - args.append(variables.pop(key)) - cmds[key] = [args, kwargs] - - for key, var in variables.items(): - cmds[key] = [[var], {}] - - # remap the 'generic msdp commands' to avoid colliding with builtins etc - # by prepending "msdp_" - lower_case = {key.lower(): key for key in cmds} - for remap in ("list", "report", "reset", "send", "unreport"): - if remap in lower_case: - cmds["msdp_{}".format(remap)] = cmds.pop(lower_case[remap]) - - # print("msdp data in:", cmds) # DEBUG - self.protocol.data_in(**cmds)
- -
[docs] def decode_gmcp(self, data): - """ - Decodes incoming GMCP data on the form 'varname <structure>'. - - Args: - data (str or list): GMCP data. - - Notes: - Clients send data on the form "Module.Submodule.Cmdname <structure>". - We assume the structure is valid JSON. - - The following is parsed into Evennia's formal structure: - :: - - Core.Name -> [name, [], {}] - Core.Name string -> [name, [string], {}] - Core.Name [arg, arg,...] -> [name, [args], {}] - Core.Name {key:arg, key:arg, ...} -> [name, [], {kwargs}] - Core.Name [[args], {kwargs}] -> [name, [args], {kwargs}] - - """ - if isinstance(data, list): - data = b"".join(data) - - # print("decode_gmcp in:", data) # DEBUG - if data: - try: - cmdname, structure = data.split(None, 1) - except ValueError: - cmdname, structure = data, b"" - cmdname = cmdname.replace(b".", b"_") - try: - structure = json.loads(structure) - except ValueError: - # maybe the structure is not json-serialized at all - pass - args, kwargs = [], {} - if is_iter(structure): - if isinstance(structure, dict): - kwargs = {key: value for key, value in structure.items() if key} - else: - args = list(structure) - else: - args = (structure,) - if cmdname.lower().startswith(b"core_"): - # if Core.cmdname, then use cmdname - cmdname = cmdname[5:] - self.protocol.data_in(**{cmdname.lower().decode(): [args, kwargs]})
- - # access methods - -
[docs] def data_out(self, cmdname, *args, **kwargs): - """ - Return a MSDP- or GMCP-valid subnegotiation across the protocol. - - Args: - cmdname (str): OOB-command name. - args, kwargs (any): Arguments to OOB command. - - """ - kwargs.pop("options", None) - - if self.MSDP: - encoded_oob = self.encode_msdp(cmdname, *args, **kwargs) - self.protocol._write(IAC + SB + MSDP + encoded_oob + IAC + SE) - - if self.GMCP: - encoded_oob = self.encode_gmcp(cmdname, *args, **kwargs) - self.protocol._write(IAC + SB + GMCP + encoded_oob + IAC + SE)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/telnet_ssl.html b/docs/0.9.5/_modules/evennia/server/portal/telnet_ssl.html deleted file mode 100644 index 2c7c326af8..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/telnet_ssl.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - - - - evennia.server.portal.telnet_ssl — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.telnet_ssl

-"""
-This allows for running the telnet communication over an encrypted SSL tunnel. To use it, requires a
-client supporting Telnet SSL.
-
-The protocol will try to automatically create the private key and certificate on the server side
-when starting and will warn if this was not possible. These will appear as files ssl.key and
-ssl.cert in mygame/server/.
-
-"""
-import os
-
-try:
-    from OpenSSL import crypto
-    from twisted.internet import ssl as twisted_ssl
-except ImportError as error:
-    errstr = """
-    {err}
-    Telnet-SSL requires the PyOpenSSL library and dependencies:
-
-        pip install pyopenssl pycrypto enum pyasn1 service_identity
-
-    Stop and start Evennia again. If no certificate can be generated, you'll
-    get a suggestion for a (linux) command to generate this locally.
-
-    """
-    raise ImportError(errstr.format(err=error))
-
-from django.conf import settings
-from evennia.server.portal.telnet import TelnetProtocol
-
-_GAME_DIR = settings.GAME_DIR
-
-_PRIVATE_KEY_LENGTH = 2048
-_PRIVATE_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssl.key")
-_PUBLIC_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssl-public.key")
-_CERTIFICATE_FILE = os.path.join(_GAME_DIR, "server", "ssl.cert")
-_CERTIFICATE_EXPIRE = 365 * 24 * 60 * 60 * 20  # 20 years
-_CERTIFICATE_ISSUER = {
-    "C": "EV",
-    "ST": "Evennia",
-    "L": "Evennia",
-    "O": "Evennia Security",
-    "OU": "Evennia Department",
-    "CN": "evennia",
-}
-
-# messages
-
-NO_AUTOGEN = """
-Evennia could not auto-generate the SSL private- and public keys ({{err}}).
-If this error persists, create them manually (using the tools for your OS). The files
-should be placed and named like this:
-    {}
-    {}
-""".format(
-    _PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
-)
-
-NO_AUTOCERT = """
-Evennia's could not auto-generate the SSL certificate ({{err}}).
-The private key already exists here:
-    {}
-If this error persists, create the certificate manually (using the private key and
-the tools for your OS). The file should be placed and named like this:
-    {}
-""".format(
-    _PRIVATE_KEY_FILE, _CERTIFICATE_FILE
-)
-
-
-
[docs]class SSLProtocol(TelnetProtocol): - """ - Communication is the same as telnet, except data transfer - is done with encryption set up by the portal at start time. - """ - -
[docs] def __init__(self, *args, **kwargs): - super(SSLProtocol, self).__init__(*args, **kwargs) - self.protocol_key = "telnet/ssl"
- - -
[docs]def verify_or_create_SSL_key_and_cert(keyfile, certfile): - """ - Verify or create new key/certificate files. - - Args: - keyfile (str): Path to ssl.key file. - certfile (str): Parth to ssl.cert file. - - Notes: - If files don't already exist, they are created. - - """ - - if not (os.path.exists(keyfile) and os.path.exists(certfile)): - # key/cert does not exist. Create. - try: - # generate the keypair - keypair = crypto.PKey() - keypair.generate_key(crypto.TYPE_RSA, _PRIVATE_KEY_LENGTH) - - with open(_PRIVATE_KEY_FILE, "wt") as pfile: - pfile.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, keypair).decode("utf-8")) - print("Created SSL private key in '{}'.".format(_PRIVATE_KEY_FILE)) - - with open(_PUBLIC_KEY_FILE, "wt") as pfile: - pfile.write(crypto.dump_publickey(crypto.FILETYPE_PEM, keypair).decode("utf-8")) - print("Created SSL public key in '{}'.".format(_PUBLIC_KEY_FILE)) - - except Exception as err: - print(NO_AUTOGEN.format(err=err)) - return False - - else: - - try: - # create certificate - cert = crypto.X509() - subj = cert.get_subject() - for key, value in _CERTIFICATE_ISSUER.items(): - setattr(subj, key, value) - cert.set_issuer(subj) - - cert.set_serial_number(1000) - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(_CERTIFICATE_EXPIRE) - cert.set_pubkey(keypair) - cert.sign(keypair, "sha1") - - with open(_CERTIFICATE_FILE, "wt") as cfile: - cfile.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode("utf-8")) - print("Created SSL certificate in '{}'.".format(_CERTIFICATE_FILE)) - - except Exception as err: - print(NO_AUTOCERT.format(err=err)) - return False - - return True
- - -
[docs]def getSSLContext(): - """ - This is called by the portal when creating the SSL context - server-side. - - Returns: - ssl_context (tuple): A key and certificate that is either - existing previously or created on the fly. - - """ - - if verify_or_create_SSL_key_and_cert(_PRIVATE_KEY_FILE, _CERTIFICATE_FILE): - return twisted_ssl.DefaultOpenSSLContextFactory(_PRIVATE_KEY_FILE, _CERTIFICATE_FILE) - else: - return None
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/tests.html b/docs/0.9.5/_modules/evennia/server/portal/tests.html deleted file mode 100644 index 45187e8487..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/tests.html +++ /dev/null @@ -1,427 +0,0 @@ - - - - - - - - evennia.server.portal.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.tests

-try:
-    from django.utils.unittest import TestCase
-except ImportError:
-    from django.test import TestCase
-
-try:
-    from django.utils import unittest
-except ImportError:
-    import unittest
-
-import sys
-import string
-import mock
-import pickle
-import json
-
-from mock import Mock, MagicMock
-from evennia.server.portal import irc
-from evennia.utils.test_resources import EvenniaTest
-
-from twisted.conch.telnet import IAC, WILL, DONT, SB, SE, NAWS, DO
-from twisted.test import proto_helpers
-from twisted.trial.unittest import TestCase as TwistedTestCase
-from twisted.internet.base import DelayedCall
-
-from .telnet import TelnetServerFactory, TelnetProtocol
-from .portal import PORTAL_SESSIONS
-from .suppress_ga import SUPPRESS_GA
-from .naws import DEFAULT_HEIGHT, DEFAULT_WIDTH
-from .ttype import TTYPE, IS
-from .mccp import MCCP
-from .mssp import MSSP
-from .mxp import MXP
-from .telnet_oob import MSDP, MSDP_VAL, MSDP_VAR
-
-from .amp import AMPMultiConnectionProtocol, MsgServer2Portal, MsgPortal2Server, AMP_MAXLEN
-from .amp_server import AMPServerFactory
-
-from autobahn.twisted.websocket import WebSocketServerFactory
-from .webclient import WebSocketClient
-
-
-
[docs]class TestAMPServer(TwistedTestCase): - """ - Test AMP communication - """ - -
[docs] def setUp(self): - super(TestAMPServer, self).setUp() - portal = Mock() - factory = AMPServerFactory(portal) - self.proto = factory.buildProtocol(("localhost", 0)) - self.transport = MagicMock() # proto_helpers.StringTransport() - self.transport.client = ["localhost"] - self.transport.write = MagicMock()
- -
[docs] def test_amp_out(self): - self.proto.makeConnection(self.transport) - - self.proto.data_to_server(MsgServer2Portal, 1, test=2) - - if pickle.HIGHEST_PROTOCOL == 5: - # Python 3.8+ - byte_out = ( - b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0b" - b"packed_data\x00 x\xdak`\x9d*\xc8\x00\x01\xde\x8c\xb5SzXJR" - b"\x8bK\xa6x3\x15\xb7M\xd1\x03\x00VU\x07u\x00\x00" - ) - elif pickle.HIGHEST_PROTOCOL == 4: - # Python 3.7 - byte_out = ( - b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0b" - b"packed_data\x00 x\xdak`\x99*\xc8\x00\x01\xde\x8c\xb5SzXJR" - b"\x8bK\xa6x3\x15\xb7M\xd1\x03\x00V:\x07t\x00\x00" - ) - self.transport.write.assert_called_with(byte_out) - with mock.patch("evennia.server.portal.amp.amp.AMP.dataReceived") as mocked_amprecv: - self.proto.dataReceived(byte_out) - mocked_amprecv.assert_called_with(byte_out)
- -
[docs] def test_amp_in(self): - self.proto.makeConnection(self.transport) - - self.proto.data_to_server(MsgPortal2Server, 1, test=2) - if pickle.HIGHEST_PROTOCOL == 5: - # Python 3.8+ - byte_out = ( - b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgPortal2Server\x00\x0b" - b"packed_data\x00 x\xdak`\x9d*\xc8\x00\x01\xde\x8c\xb5SzXJR" - b"\x8bK\xa6x3\x15\xb7M\xd1\x03\x00VU\x07u\x00\x00" - ) - elif pickle.HIGHEST_PROTOCOL == 4: - # Python 3.7 - byte_out = ( - b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgPortal2Server\x00\x0b" - b"packed_data\x00 x\xdak`\x99*\xc8\x00\x01\xde\x8c\xb5SzXJR" - b"\x8bK\xa6x3\x15\xb7M\xd1\x03\x00V:\x07t\x00\x00" - ) - self.transport.write.assert_called_with(byte_out) - with mock.patch("evennia.server.portal.amp.amp.AMP.dataReceived") as mocked_amprecv: - self.proto.dataReceived(byte_out) - mocked_amprecv.assert_called_with(byte_out)
- -
[docs] def test_large_msg(self): - """ - Send message larger than AMP_MAXLEN - should be split into several - """ - self.proto.makeConnection(self.transport) - outstr = "test" * AMP_MAXLEN - self.proto.data_to_server(MsgServer2Portal, 1, test=outstr) - - if pickle.HIGHEST_PROTOCOL == 5: - # Python 3.8+ - self.transport.write.assert_called_with( - b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0bpacked_data" - b"\x00wx\xda\xed\xc6\xc1\t\x80 \x00@Q#=5Z\x0b\xb8\x80\x13\xe85h\x80\x8e\xbam`Dc\xf4><\xf8g" - b"\x1a[\xf8\xda\x97\xa3_\xb1\x95\xdaz\xbe\xe7\x1a\xde\x03\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x1f\x1eP\x1d\x02\r\x00\rpacked_data.2" - b"\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08\xc0\xa0\xb4&\xf0\xfdg\x10a\xa3" - b"\xd9RUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU\xf5\xfb\x03m\xe0\x06" - b"\x1d\x00\rpacked_data.3\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08\xc0\xa0\xb4&\xf0\xfdg\x10a" - b"\xa3fSUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU\xf5\xfb\x03n\x1c" - b"\x06\x1e\x00\rpacked_data.4\x00Zx\xda\xed\xc3\x01\t\x00\x00\x0c\x03\xa0\xb4O\xb0\xf5gA" - b"\xae`\xda\x8b\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - b"\xaa\xaa\xaa\xdf\x0fnI\x06,\x00\rpacked_data.5\x00\x18x\xdaK-.)I\xc5\x8e\xa7\xb22@\xc0" - b"\x94\xe2\xb6)z\x00Z\x1e\x0e\xb6\x00\x00" - ) - elif pickle.HIGHEST_PROTOCOL == 4: - # Python 3.7 - self.transport.write.assert_called_with( - b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0bpacked_data" - b"\x00wx\xda\xed\xc6\xc1\t\x80 \x00@Q#o\x8e\xd6\x02-\xe0\x04z\r\x1a\xa0\xa3m+$\xd2" - b"\x18\xbe\x0f\x0f\xfe\x1d\xdf\x14\xfe\x8e\xedjO\xac\xb9\xd4v\xf6o\x0f\xf3\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00X\xc3\x00P\x10\x02\x0c\x00\rpacked_data.2\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08" - b"\xc0\xa0\xb4&\xf0\xfdg\x10a\xa3\xd9RUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" - b"\xf5\xfb\x03m\xe0\x06\x1d\x00\rpacked_data.3\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08" - b"\xc0\xa0\xb4&\xf0\xfdg\x10a\xa3fSUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" - b"\xf5\xfb\x03n\x1c\x06\x1e\x00\rpacked_data.4\x00Zx\xda\xed\xc3\x01\t\x00\x00\x0c" - b"\x03\xa0\xb4O\xb0\xf5gA\xae`\xda\x8b\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xdf\x0fnI\x06,\x00\rpacked_data.5" - b"\x00\x18x\xdaK-.)I\xc5\x8e\xa7\xb22@\xc0\x94\xe2\xb6)z\x00Z\x1e\x0e\xb6\x00\x00" - )
- - -
[docs]class TestIRC(TestCase): -
[docs] def test_plain_ansi(self): - """ - Test that printable characters do not get mangled. - """ - irc_ansi = irc.parse_ansi_to_irc(string.printable) - ansi_irc = irc.parse_irc_to_ansi(string.printable) - self.assertEqual(irc_ansi, string.printable) - self.assertEqual(ansi_irc, string.printable)
- -
[docs] def test_bold(self): - s_irc = "\x02thisisatest" - s_eve = r"|hthisisatest" - self.assertEqual(irc.parse_ansi_to_irc(s_eve), s_irc) - self.assertEqual(s_eve, irc.parse_irc_to_ansi(s_irc))
- -
[docs] def test_italic(self): - s_irc = "\x02thisisatest" - s_eve = r"|hthisisatest" - self.assertEqual(irc.parse_ansi_to_irc(s_eve), s_irc)
- -
[docs] def test_colors(self): - color_map = ( - ("\0030", r"|w"), - ("\0031", r"|X"), - ("\0032", r"|B"), - ("\0033", r"|G"), - ("\0034", r"|r"), - ("\0035", r"|R"), - ("\0036", r"|M"), - ("\0037", r"|Y"), - ("\0038", r"|y"), - ("\0039", r"|g"), - ("\00310", r"|C"), - ("\00311", r"|c"), - ("\00312", r"|b"), - ("\00313", r"|m"), - ("\00314", r"|x"), - ("\00315", r"|W"), - ("\00399,5", r"|[r"), - ("\00399,3", r"|[g"), - ("\00399,7", r"|[y"), - ("\00399,2", r"|[b"), - ("\00399,6", r"|[m"), - ("\00399,10", r"|[c"), - ("\00399,15", r"|[w"), - ("\00399,1", r"|[x"), - ) - - for m in color_map: - self.assertEqual(irc.parse_irc_to_ansi(m[0]), m[1]) - self.assertEqual(m[0], irc.parse_ansi_to_irc(m[1]))
- -
[docs] def test_identity(self): - """ - Test that the composition of the function and - its inverse gives the correct string. - """ - - s = r"|wthis|Xis|gis|Ma|C|complex|*string" - - self.assertEqual(irc.parse_irc_to_ansi(irc.parse_ansi_to_irc(s)), s)
- - -
[docs]class TestTelnet(TwistedTestCase): -
[docs] def setUp(self): - super(TestTelnet, self).setUp() - factory = TelnetServerFactory() - factory.protocol = TelnetProtocol - factory.sessionhandler = PORTAL_SESSIONS - factory.sessionhandler.portal = Mock() - self.proto = factory.buildProtocol(("localhost", 0)) - self.transport = proto_helpers.StringTransport() - self.addCleanup(factory.sessionhandler.disconnect_all)
- -
[docs] @mock.patch("evennia.server.portal.portalsessionhandler.reactor", new=MagicMock()) - def test_mudlet_ttype(self): - self.transport.client = ["localhost"] - self.transport.setTcpKeepAlive = Mock() - d = self.proto.makeConnection(self.transport) - # test suppress_ga - self.assertTrue(self.proto.protocol_flags["NOGOAHEAD"]) - self.proto.dataReceived(IAC + DONT + SUPPRESS_GA) - self.assertFalse(self.proto.protocol_flags["NOGOAHEAD"]) - self.assertEqual(self.proto.handshakes, 7) - # test naws - self.assertEqual(self.proto.protocol_flags["SCREENWIDTH"], {0: DEFAULT_WIDTH}) - self.assertEqual(self.proto.protocol_flags["SCREENHEIGHT"], {0: DEFAULT_HEIGHT}) - self.proto.dataReceived(IAC + WILL + NAWS) - self.proto.dataReceived(b"".join([IAC, SB, NAWS, b"", b"x", b"", b"d", IAC, SE])) - self.assertEqual(self.proto.protocol_flags["SCREENWIDTH"][0], 78) - self.assertEqual(self.proto.protocol_flags["SCREENHEIGHT"][0], 45) - self.assertEqual(self.proto.handshakes, 6) - # test ttype - self.assertFalse(self.proto.protocol_flags["TTYPE"]) - self.assertTrue(self.proto.protocol_flags["ANSI"]) - self.proto.dataReceived(IAC + WILL + TTYPE) - self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MUDLET", IAC, SE])) - self.assertTrue(self.proto.protocol_flags["XTERM256"]) - self.assertEqual(self.proto.protocol_flags["CLIENTNAME"], "MUDLET") - self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"]) - self.assertTrue(self.proto.protocol_flags["NOGOAHEAD"]) - self.assertFalse(self.proto.protocol_flags["NOPROMPTGOAHEAD"]) - self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"XTERM", IAC, SE])) - self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MTTS 137", IAC, SE])) - self.assertEqual(self.proto.handshakes, 5) - # test mccp - self.proto.dataReceived(IAC + DONT + MCCP) - self.assertFalse(self.proto.protocol_flags["MCCP"]) - self.assertEqual(self.proto.handshakes, 4) - # test mssp - self.proto.dataReceived(IAC + DONT + MSSP) - self.assertEqual(self.proto.handshakes, 3) - # test oob - self.proto.dataReceived(IAC + DO + MSDP) - self.proto.dataReceived( - b"".join([IAC, SB, MSDP, MSDP_VAR, b"LIST", MSDP_VAL, b"COMMANDS", IAC, SE]) - ) - self.assertTrue(self.proto.protocol_flags["OOB"]) - self.assertEqual(self.proto.handshakes, 2) - # test mxp - self.proto.dataReceived(IAC + DONT + MXP) - self.assertFalse(self.proto.protocol_flags["MXP"]) - self.assertEqual(self.proto.handshakes, 1) - # clean up to prevent Unclean reactor - self.proto.nop_keep_alive.stop() - self.proto._handshake_delay.cancel() - return d
- - -
[docs]class TestWebSocket(EvenniaTest): -
[docs] def setUp(self): - super().setUp() - self.proto = WebSocketClient() - self.proto.factory = WebSocketServerFactory() - self.proto.factory.sessionhandler = PORTAL_SESSIONS - self.proto.sessionhandler = PORTAL_SESSIONS - self.proto.sessionhandler.portal = Mock() - self.proto.transport = proto_helpers.StringTransport() - # self.proto.transport = proto_helpers.FakeDatagramTransport() - self.proto.transport.client = ["localhost"] - self.proto.transport.setTcpKeepAlive = Mock() - self.proto.state = MagicMock() - self.addCleanup(self.proto.factory.sessionhandler.disconnect_all) - DelayedCall.debug = True
- -
[docs] def tearDown(self): - super().tearDown()
- -
[docs] @mock.patch("evennia.server.portal.portalsessionhandler.reactor", new=MagicMock()) - def test_data_in(self): - self.proto.sessionhandler.data_in = MagicMock() - self.proto.onOpen() - msg = json.dumps(["logged_in", (), {}]).encode() - self.proto.onMessage(msg, isBinary=False) - self.proto.sessionhandler.data_in.assert_called_with(self.proto, logged_in=[[], {}]) - sendStr = "You can get anything you want at Alice's Restaurant." - msg = json.dumps(["text", (sendStr,), {}]).encode() - self.proto.onMessage(msg, isBinary=False) - self.proto.sessionhandler.data_in.assert_called_with(self.proto, text=[[sendStr], {}])
- -
[docs] @mock.patch("evennia.server.portal.portalsessionhandler.reactor", new=MagicMock()) - def test_data_out(self): - self.proto.onOpen() - self.proto.sendLine = MagicMock() - msg = json.dumps(["logged_in", (), {}]) - self.proto.sessionhandler.data_out(self.proto, text=[["Excepting Alice"], {}]) - self.proto.sendLine.assert_called_with(json.dumps(["text", ["Excepting Alice"], {}]))
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/ttype.html b/docs/0.9.5/_modules/evennia/server/portal/ttype.html deleted file mode 100644 index af1ba69e68..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/ttype.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - - - - evennia.server.portal.ttype — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.ttype

-"""
-TTYPE (MTTS) - Mud Terminal Type Standard
-
-This module implements the TTYPE telnet protocol as per
-http://tintin.sourceforge.net/mtts/. It allows the server to ask the
-client about its capabilities. If the client also supports TTYPE, it
-will return with information such as its name, if it supports colour
-etc. If the client does not support TTYPE, this will be ignored.
-
-All data will be stored on the protocol's protocol_flags dictionary,
-under the 'TTYPE' key.
-"""
-# telnet option codes
-TTYPE = bytes([24])  # b"\x18"
-IS = bytes([0])  # b"\x00"
-SEND = bytes([1])  # b"\x01"
-
-# terminal capabilities and their codes
-MTTS = [
-    (128, "PROXY"),
-    (64, "SCREENREADER"),
-    (32, "OSC_COLOR_PALETTE"),
-    (16, "MOUSE_TRACKING"),
-    (8, "XTERM256"),
-    (4, "UTF-8"),
-    (2, "VT100"),
-    (1, "ANSI"),
-]
-
-
-
[docs]class Ttype(object): - """ - Handles ttype negotiations. Called and initiated by the - telnet protocol. - - """ - -
[docs] def __init__(self, protocol): - """ - Initialize ttype by storing protocol on ourselves and calling - the client to see if it supporst ttype. - - Args: - protocol (Protocol): The protocol instance. - - Notes: - The `self.ttype_step` indicates how far in the data - retrieval we've gotten. - - """ - self.ttype_step = 0 - self.protocol = protocol - # we set FORCEDENDLINE for clients not supporting ttype - self.protocol.protocol_flags["FORCEDENDLINE"] = True - self.protocol.protocol_flags["TTYPE"] = False - # is it a safe bet to assume ANSI is always supported? - self.protocol.protocol_flags["ANSI"] = True - # setup protocol to handle ttype initialization and negotiation - self.protocol.negotiationMap[TTYPE] = self.will_ttype - # ask if client will ttype, connect callback if it does. - self.protocol.do(TTYPE).addCallbacks(self.will_ttype, self.wont_ttype)
- -
[docs] def wont_ttype(self, option): - """ - Callback if ttype is not supported by client. - - Args: - option (Option): Not used. - - """ - self.protocol.protocol_flags["TTYPE"] = False - self.protocol.handshake_done()
- -
[docs] def will_ttype(self, option): - """ - Handles negotiation of the ttype protocol once the client has - confirmed that it will respond with the ttype protocol. - - Args: - option (Option): Not used. - - Notes: - The negotiation proceeds in several steps, each returning a - certain piece of information about the client. All data is - stored on protocol.protocol_flags under the TTYPE key. - - """ - options = self.protocol.protocol_flags - - if options and options.get("TTYPE", False) or self.ttype_step > 3: - return - - try: - option = b"".join(option).lstrip(IS).decode() - except TypeError: - # option is not on a suitable form for joining - pass - - if self.ttype_step == 0: - # just start the request chain - self.protocol.requestNegotiation(TTYPE, SEND) - - elif self.ttype_step == 1: - # this is supposed to be the name of the client/terminal. - # For clients not supporting the extended TTYPE - # definition, subsequent calls will just repeat-return this. - try: - clientname = option.upper() - except AttributeError: - # malformed option (not a string) - clientname = "UNKNOWN" - - # use name to identify support for xterm256. Many of these - # only support after a certain version, but all support - # it since at least 4 years. We assume recent client here for now. - xterm256 = False - if clientname.startswith("MUDLET"): - # supports xterm256 stably since 1.1 (2010?) - xterm256 = clientname.split("MUDLET", 1)[1].strip() >= "1.1" - # Mudlet likes GA's on a prompt line for the prompt trigger to - # match, if it's not wanting NOGOAHEAD. - if not self.protocol.protocol_flags["NOGOAHEAD"]: - self.protocol.protocol_flags["NOGOAHEAD"] = True - self.protocol.protocol_flags["NOPROMPTGOAHEAD"] = False - - if ( - clientname.startswith("XTERM") - or clientname.endswith("-256COLOR") - or clientname - in ( - "ATLANTIS", # > 0.9.9.0 (aug 2009) - "CMUD", # > 3.04 (mar 2009) - "KILDCLIENT", # > 2.2.0 (sep 2005) - "MUDLET", # > beta 15 (sep 2009) - "MUSHCLIENT", # > 4.02 (apr 2007) - "PUTTY", # > 0.58 (apr 2005) - "BEIP", # > 2.00.206 (late 2009) (BeipMu) - "POTATO", # > 2.00 (maybe earlier) - "TINYFUGUE", # > 4.x (maybe earlier) - ) - ): - xterm256 = True - - # all clients supporting TTYPE at all seem to support ANSI - self.protocol.protocol_flags["ANSI"] = True - self.protocol.protocol_flags["XTERM256"] = xterm256 - self.protocol.protocol_flags["CLIENTNAME"] = clientname - self.protocol.requestNegotiation(TTYPE, SEND) - - elif self.ttype_step == 2: - # this is a term capabilities flag - term = option - tupper = term.upper() - # identify xterm256 based on flag - xterm256 = ( - tupper.endswith("-256COLOR") - or tupper.endswith("XTERM") # Apple Terminal, old Tintin - and not tupper.endswith("-COLOR") # old Tintin, Putty - ) - if xterm256: - self.protocol.protocol_flags["ANSI"] = True - self.protocol.protocol_flags["XTERM256"] = xterm256 - self.protocol.protocol_flags["TERM"] = term - # request next information - self.protocol.requestNegotiation(TTYPE, SEND) - - elif self.ttype_step == 3: - # the MTTS bitstring identifying term capabilities - if option.startswith("MTTS"): - option = option[4:].strip() - if option.isdigit(): - # a number - determine the actual capabilities - option = int(option) - support = dict( - (capability, True) for bitval, capability in MTTS if option & bitval > 0 - ) - self.protocol.protocol_flags.update(support) - else: - # some clients send erroneous MTTS as a string. Add directly. - self.protocol.protocol_flags[option.upper()] = True - - self.protocol.protocol_flags["TTYPE"] = True - # we must sync ttype once it'd done - self.protocol.handshake_done() - self.ttype_step += 1
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/webclient.html b/docs/0.9.5/_modules/evennia/server/portal/webclient.html deleted file mode 100644 index 7f4602e337..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/webclient.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - evennia.server.portal.webclient — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.webclient

-"""
-Webclient based on websockets.
-
-This implements a webclient with WebSockets (http://en.wikipedia.org/wiki/WebSocket)
-by use of the autobahn-python package's implementation (https://github.com/crossbario/autobahn-python).
-It is used together with evennia/web/media/javascript/evennia_websocket_webclient.js.
-
-All data coming into the webclient is in the form of valid JSON on the form
-
-`["inputfunc_name", [args], {kwarg}]`
-
-which represents an "inputfunc" to be called on the Evennia side with *args, **kwargs.
-The most common inputfunc is "text", which takes just the text input
-from the command line and interprets it as an Evennia Command: `["text", ["look"], {}]`
-
-"""
-import re
-import json
-import html
-from twisted.internet.protocol import Protocol
-from django.conf import settings
-from evennia.server.session import Session
-from evennia.utils.utils import to_str, mod_import
-from evennia.utils.ansi import parse_ansi
-from evennia.utils.text2html import parse_html
-from autobahn.twisted.websocket import WebSocketServerProtocol
-from autobahn.exception import Disconnected
-
-_RE_SCREENREADER_REGEX = re.compile(
-    r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE
-)
-_CLIENT_SESSIONS = mod_import(settings.SESSION_ENGINE).SessionStore
-_UPSTREAM_IPS = settings.UPSTREAM_IPS
-
-# Status Code 1000: Normal Closure
-#   called when the connection was closed through JavaScript
-CLOSE_NORMAL = WebSocketServerProtocol.CLOSE_STATUS_CODE_NORMAL
-
-# Status Code 1001: Going Away
-#   called when the browser is navigating away from the page
-GOING_AWAY = WebSocketServerProtocol.CLOSE_STATUS_CODE_GOING_AWAY
-
-STATE_CLOSING = WebSocketServerProtocol.STATE_CLOSING
-
-
-
[docs]class WebSocketClient(WebSocketServerProtocol, Session): - """ - Implements the server-side of the Websocket connection. - """ - - # nonce value, used to prevent the webclient from erasing the - # webclient_authenticated_uid value of csession on disconnect - nonce = None - -
[docs] def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.protocol_key = "webclient/websocket"
- -
[docs] def get_client_session(self): - """ - Get the Client browser session (used for auto-login based on browser session) - - Returns: - csession (ClientSession): This is a django-specific internal representation - of the browser session. - - """ - try: - self.csessid = self.http_request_uri.split("?", 1)[1] - except IndexError: - # this may happen for custom webclients not caring for the - # browser session. - self.csessid = None - return None - except AttributeError: - from evennia.utils import logger - - self.csessid = None - logger.log_trace(str(self)) - return None - if self.csessid: - return _CLIENT_SESSIONS(session_key=self.csessid)
- -
[docs] def onOpen(self): - """ - This is called when the WebSocket connection is fully established. - - """ - client_address = self.transport.client - client_address = client_address[0] if client_address else None - - if client_address in _UPSTREAM_IPS and "x-forwarded-for" in self.http_headers: - addresses = [x.strip() for x in self.http_headers["x-forwarded-for"].split(",")] - addresses.reverse() - - for addr in addresses: - if addr not in _UPSTREAM_IPS: - client_address = addr - break - - self.init_session("websocket", client_address, self.factory.sessionhandler) - - csession = self.get_client_session() # this sets self.csessid - csessid = self.csessid - uid = csession and csession.get("webclient_authenticated_uid", None) - nonce = csession and csession.get("webclient_authenticated_nonce", 0) - if uid: - # the client session is already logged in. - self.uid = uid - self.nonce = nonce - self.logged_in = True - - for old_session in self.sessionhandler.sessions_from_csessid(csessid): - if ( - hasattr(old_session, "websocket_close_code") - and old_session.websocket_close_code != CLOSE_NORMAL - ): - # if we have old sessions with the same csession, they are remnants - self.sessid = old_session.sessid - self.sessionhandler.disconnect(old_session) - - self.protocol_flags["CLIENTNAME"] = "Evennia Webclient (websocket)" - self.protocol_flags["UTF-8"] = True - self.protocol_flags["OOB"] = True - - # watch for dead links - self.transport.setTcpKeepAlive(1) - # actually do the connection - self.sessionhandler.connect(self)
- -
[docs] def disconnect(self, reason=None): - """ - Generic hook for the engine to call in order to - disconnect this protocol. - - Args: - reason (str or None): Motivation for the disconnection. - - """ - csession = self.get_client_session() - - if csession: - # if the nonce is different, webclient_authenticated_uid has been - # set *before* this disconnect (disconnect called after a new client - # connects, which occurs in some 'fast' browsers like Google Chrome - # and Mobile Safari) - if csession.get("webclient_authenticated_nonce", None) == self.nonce: - csession["webclient_authenticated_uid"] = None - csession["webclient_authenticated_nonce"] = 0 - csession.save() - self.logged_in = False - - self.sessionhandler.disconnect(self) - # autobahn-python: - # 1000 for a normal close, 1001 if the browser window is closed, - # 3000-4999 for app. specific, - # in case anyone wants to expose this functionality later. - # - # sendClose() under autobahn/websocket/interfaces.py - ret = self.sendClose(CLOSE_NORMAL, reason)
- -
[docs] def onClose(self, wasClean, code=None, reason=None): - """ - This is executed when the connection is lost for whatever - reason. it can also be called directly, from the disconnect - method. - - Args: - wasClean (bool): ``True`` if the WebSocket was closed cleanly. - code (int or None): Close status as sent by the WebSocket peer. - reason (str or None): Close reason as sent by the WebSocket peer. - - """ - if code == CLOSE_NORMAL or code == GOING_AWAY: - self.disconnect(reason) - else: - self.websocket_close_code = code
- -
[docs] def onMessage(self, payload, isBinary): - """ - Callback fired when a complete WebSocket message was received. - - Args: - payload (bytes): The WebSocket message received. - isBinary (bool): Flag indicating whether payload is binary or - UTF-8 encoded text. - - """ - cmdarray = json.loads(str(payload, "utf-8")) - if cmdarray: - self.data_in(**{cmdarray[0]: [cmdarray[1], cmdarray[2]]})
- -
[docs] def sendLine(self, line): - """ - Send data to client. - - Args: - line (str): Text to send. - - """ - try: - return self.sendMessage(line.encode()) - except Disconnected: - # this can happen on an unclean close of certain browsers. - # it means this link is actually already closed. - self.disconnect(reason="Browser already closed.")
- -
[docs] def at_login(self): - csession = self.get_client_session() - if csession: - csession["webclient_authenticated_uid"] = self.uid - csession.save()
- -
[docs] def data_in(self, **kwargs): - """ - Data User > Evennia. - - Args: - text (str): Incoming text. - kwargs (any): Options from protocol. - - Notes: - At initilization, the client will send the special - 'csessid' command to identify its browser session hash - with the Evennia side. - - The websocket client will also pass 'websocket_close' command - to report that the client has been closed and that the - session should be disconnected. - - Both those commands are parsed and extracted already at - this point. - - """ - if "websocket_close" in kwargs: - self.disconnect() - return - - self.sessionhandler.data_in(self, **kwargs)
- -
[docs] def send_text(self, *args, **kwargs): - """ - Send text data. This will pre-process the text for - color-replacement, conversion to html etc. - - Args: - text (str): Text to send. - - Keyword Args: - options (dict): Options-dict with the following keys understood: - - raw (bool): No parsing at all (leave ansi-to-html markers unparsed). - - nocolor (bool): Clean out all color. - - screenreader (bool): Use Screenreader mode. - - send_prompt (bool): Send a prompt with parsed html - - """ - if args: - args = list(args) - text = args[0] - if text is None: - return - else: - return - # just to be sure - text = to_str(text) - - flags = self.protocol_flags - - options = kwargs.pop("options", {}) - raw = options.get("raw", flags.get("RAW", False)) - client_raw = options.get("client_raw", False) - nocolor = options.get("nocolor", flags.get("NOCOLOR", False)) - screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) - prompt = options.get("send_prompt", False) - - if screenreader: - # screenreader mode cleans up output - text = parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) - text = _RE_SCREENREADER_REGEX.sub("", text) - cmd = "prompt" if prompt else "text" - if raw: - if client_raw: - args[0] = text - else: - args[0] = html.escape(text) # escape html! - else: - args[0] = parse_html(text, strip_ansi=nocolor) - - # send to client on required form [cmdname, args, kwargs] - self.sendLine(json.dumps([cmd, args, kwargs]))
- -
[docs] def send_prompt(self, *args, **kwargs): - kwargs["options"].update({"send_prompt": True}) - self.send_text(*args, **kwargs)
- -
[docs] def send_default(self, cmdname, *args, **kwargs): - """ - Data Evennia -> User. - - Args: - cmdname (str): The first argument will always be the oob cmd name. - *args (any): Remaining args will be arguments for `cmd`. - - Keyword Args: - options (dict): These are ignored for oob commands. Use command - arguments (which can hold dicts) to send instructions to the - client instead. - - """ - if not cmdname == "options": - self.sendLine(json.dumps([cmdname, args, kwargs]))
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/portal/webclient_ajax.html b/docs/0.9.5/_modules/evennia/server/portal/webclient_ajax.html deleted file mode 100644 index 665a14d717..0000000000 --- a/docs/0.9.5/_modules/evennia/server/portal/webclient_ajax.html +++ /dev/null @@ -1,573 +0,0 @@ - - - - - - - - evennia.server.portal.webclient_ajax — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.portal.webclient_ajax

-"""
-AJAX/COMET fallback webclient
-
-The AJAX/COMET web client consists of two components running on
-twisted and django. They are both a part of the Evennia website url
-tree (so the testing website might be located on
-http://localhost:4001/, whereas the webclient can be found on
-http://localhost:4001/webclient.)
-
-/webclient - this url is handled through django's template
-             system and serves the html page for the client
-             itself along with its javascript chat program.
-/webclientdata - this url is called by the ajax chat using
-                 POST requests (long-polling when necessary)
-                 The WebClient resource in this module will
-                 handle these requests and act as a gateway
-                 to sessions connected over the webclient.
-"""
-import json
-import re
-import time
-import html
-
-from twisted.web import server, resource
-from twisted.internet.task import LoopingCall
-from django.utils.functional import Promise
-from django.conf import settings
-from evennia.utils.ansi import parse_ansi
-from evennia.utils import utils
-from evennia.utils.utils import to_bytes, to_str
-from evennia.utils.text2html import parse_html
-from evennia.server import session
-
-_CLIENT_SESSIONS = utils.mod_import(settings.SESSION_ENGINE).SessionStore
-_RE_SCREENREADER_REGEX = re.compile(
-    r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE
-)
-_SERVERNAME = settings.SERVERNAME
-_KEEPALIVE = 30  # how often to check keepalive
-
-
-# defining a simple json encoder for returning
-# django data to the client. Might need to
-# extend this if one wants to send more
-# complex database objects too.
-
-
-
[docs]class LazyEncoder(json.JSONEncoder): -
[docs] def default(self, obj): - if isinstance(obj, Promise): - return str(obj) - return super().default(obj)
- - -
[docs]def jsonify(obj): - return to_bytes(json.dumps(obj, ensure_ascii=False, cls=LazyEncoder))
- - -# -# AjaxWebClient resource - this is called by the ajax client -# using POST requests to /webclientdata. -# - - -
[docs]class AjaxWebClient(resource.Resource): - """ - An ajax/comet long-polling transport - - """ - - isLeaf = True - allowedMethods = ("POST",) - -
[docs] def __init__(self): - self.requests = {} - self.databuffer = {} - - self.last_alive = {} - self.keep_alive = None
- - def _responseFailed(self, failure, csessid, request): - "callback if a request is lost/timed out" - try: - del self.requests[csessid] - except KeyError: - # nothing left to delete - pass - - def _keepalive(self): - """ - Callback for checking the connection is still alive. - """ - now = time.time() - to_remove = [] - keep_alives = ( - (csessid, remove) - for csessid, (t, remove) in self.last_alive.items() - if now - t > _KEEPALIVE - ) - for csessid, remove in keep_alives: - if remove: - # keepalive timeout. Line is dead. - to_remove.append(csessid) - else: - # normal timeout - send keepalive - self.last_alive[csessid] = (now, True) - self.lineSend(csessid, ["ajax_keepalive", [], {}]) - # remove timed-out sessions - for csessid in to_remove: - sessions = self.sessionhandler.sessions_from_csessid(csessid) - for sess in sessions: - sess.disconnect() - self.last_alive.pop(csessid, None) - if not self.last_alive: - # no more ajax clients. Stop the keepalive - self.keep_alive.stop() - self.keep_alive = None - -
[docs] def get_client_sessid(self, request): - """ - Helper to get the client session id out of the request. - - Args: - request (Request): Incoming request object. - Returns: - csessid (int): The client-session id. - - """ - return html.escape(request.args[b"csessid"][0].decode("utf-8"))
- -
[docs] def at_login(self): - """ - Called when this session gets authenticated by the server. - """ - pass
- -
[docs] def lineSend(self, csessid, data): - """ - This adds the data to the buffer and/or sends it to the client - as soon as possible. - - Args: - csessid (int): Session id. - data (list): A send structure [cmdname, [args], {kwargs}]. - - """ - request = self.requests.get(csessid) - if request: - # we have a request waiting. Return immediately. - request.write(jsonify(data)) - request.finish() - del self.requests[csessid] - else: - # no waiting request. Store data in buffer - dataentries = self.databuffer.get(csessid, []) - dataentries.append(jsonify(data)) - self.databuffer[csessid] = dataentries
- -
[docs] def client_disconnect(self, csessid): - """ - Disconnect session with given csessid. - - Args: - csessid (int): Session id. - - """ - if csessid in self.requests: - self.requests[csessid].finish() - del self.requests[csessid] - if csessid in self.databuffer: - del self.databuffer[csessid]
- -
[docs] def mode_init(self, request): - """ - This is called by render_POST when the client requests an init - mode operation (at startup) - - Args: - request (Request): Incoming request. - - """ - csessid = self.get_client_sessid(request) - - remote_addr = request.getClientIP() - - if remote_addr in settings.UPSTREAM_IPS and request.getHeader("x-forwarded-for"): - addresses = [x.strip() for x in request.getHeader("x-forwarded-for").split(",")] - addresses.reverse() - - for addr in addresses: - if addr not in settings.UPSTREAM_IPS: - remote_addr = addr - break - - host_string = "%s (%s:%s)" % ( - _SERVERNAME, - request.getRequestHostname(), - request.getHost().port, - ) - - sess = AjaxWebClientSession() - sess.client = self - sess.init_session("ajax/comet", remote_addr, self.sessionhandler) - - sess.csessid = csessid - csession = _CLIENT_SESSIONS(session_key=sess.csessid) - uid = csession and csession.get("webclient_authenticated_uid", False) - if uid: - # the client session is already logged in - sess.uid = uid - sess.logged_in = True - - # watch for dead links - self.last_alive[csessid] = (time.time(), False) - if not self.keep_alive: - # the keepalive is not running; start it. - self.keep_alive = LoopingCall(self._keepalive) - self.keep_alive.start(_KEEPALIVE, now=False) - - # actually do the connection - sess.sessionhandler.connect(sess) - - return jsonify({"msg": host_string, "csessid": csessid})
- -
[docs] def mode_keepalive(self, request): - - """ - This is called by render_POST when the - client is replying to the keepalive. - """ - csessid = self.get_client_sessid(request) - self.last_alive[csessid] = (time.time(), False) - return b'""'
- -
[docs] def mode_input(self, request): - """ - This is called by render_POST when the client - is sending data to the server. - - Args: - request (Request): Incoming request. - - """ - csessid = self.get_client_sessid(request) - self.last_alive[csessid] = (time.time(), False) - cmdarray = json.loads(request.args.get(b"data")[0]) - for sess in self.sessionhandler.sessions_from_csessid(csessid): - sess.data_in(**{cmdarray[0]: [cmdarray[1], cmdarray[2]]}) - return b'""'
- -
[docs] def mode_receive(self, request): - """ - This is called by render_POST when the client is telling us - that it is ready to receive data as soon as it is available. - This is the basis of a long-polling (comet) mechanism: the - server will wait to reply until data is available. - - Args: - request (Request): Incoming request. - - """ - csessid = html.escape(request.args[b"csessid"][0].decode("utf-8")) - self.last_alive[csessid] = (time.time(), False) - - dataentries = self.databuffer.get(csessid) - if dataentries: - # we have data that could not be sent earlier (because client was not - # ready to receive it). Return this buffered data immediately - return dataentries.pop(0) - else: - # we have no data to send. End the old request and start - # a new long-polling one - request.notifyFinish().addErrback(self._responseFailed, csessid, request) - if csessid in self.requests: - self.requests[csessid].finish() # Clear any stale request. - self.requests[csessid] = request - return server.NOT_DONE_YET
- -
[docs] def mode_close(self, request): - """ - This is called by render_POST when the client is signalling - that it is about to be closed. - - Args: - request (Request): Incoming request. - - """ - csessid = self.get_client_sessid(request) - try: - sess = self.sessionhandler.sessions_from_csessid(csessid)[0] - sess.sessionhandler.disconnect(sess) - except IndexError: - self.client_disconnect(csessid) - return b'""'
- -
[docs] def render_POST(self, request): - """ - This function is what Twisted calls with POST requests coming - in from the ajax client. The requests should be tagged with - different modes depending on what needs to be done, such as - initializing or sending/receving data through the request. It - uses a long-polling mechanism to avoid sending data unless - there is actual data available. - - Args: - request (Request): Incoming request. - - """ - dmode = request.args.get(b"mode", [b"None"])[0].decode("utf-8") - - if dmode == "init": - # startup. Setup the server. - return self.mode_init(request) - elif dmode == "input": - # input from the client to the server - return self.mode_input(request) - elif dmode == "receive": - # the client is waiting to receive data. - return self.mode_receive(request) - elif dmode == "close": - # the client is closing - return self.mode_close(request) - elif dmode == "keepalive": - # A reply to our keepalive request - all is well - return self.mode_keepalive(request) - else: - # This should not happen if client sends valid data. - return b'""'
- - -# -# A session type handling communication over the -# web client interface. -# - - -
[docs]class AjaxWebClientSession(session.Session): - """ - This represents a session running in an AjaxWebclient. - """ - -
[docs] def __init__(self, *args, **kwargs): - self.protocol_key = "webclient/ajax" - super().__init__(*args, **kwargs)
- -
[docs] def get_client_session(self): - """ - Get the Client browser session (used for auto-login based on browser session) - - Returns: - csession (ClientSession): This is a django-specific internal representation - of the browser session. - - """ - if self.csessid: - return _CLIENT_SESSIONS(session_key=self.csessid)
- -
[docs] def disconnect(self, reason="Server disconnected."): - """ - Disconnect from server. - - Args: - reason (str): Motivation for the disconnect. - """ - csession = self.get_client_session() - - if csession: - csession["webclient_authenticated_uid"] = None - csession.save() - self.logged_in = False - self.client.lineSend(self.csessid, ["connection_close", [reason], {}]) - self.client.client_disconnect(self.csessid) - self.sessionhandler.disconnect(self)
- -
[docs] def at_login(self): - csession = self.get_client_session() - if csession: - csession["webclient_authenticated_uid"] = self.uid - csession.save()
- -
[docs] def data_in(self, **kwargs): - """ - Data User -> Evennia - - Keyword Args: - kwargs (any): Incoming data. - - """ - self.sessionhandler.data_in(self, **kwargs)
- -
[docs] def data_out(self, **kwargs): - """ - Data Evennia -> User - - Keyword Args: - kwargs (any): Options to the protocol - """ - self.sessionhandler.data_out(self, **kwargs)
- -
[docs] def send_text(self, *args, **kwargs): - """ - Send text data. This will pre-process the text for - color-replacement, conversion to html etc. - - Args: - text (str): Text to send. - - Keyword Args: - options (dict): Options-dict with the following keys understood: - - raw (bool): No parsing at all (leave ansi-to-html markers unparsed). - - nocolor (bool): Remove all color. - - screenreader (bool): Use Screenreader mode. - - send_prompt (bool): Send a prompt with parsed html - - """ - if args: - args = list(args) - text = args[0] - if text is None: - return - else: - return - - flags = self.protocol_flags - text = utils.to_str(text) - - options = kwargs.pop("options", {}) - raw = options.get("raw", flags.get("RAW", False)) - xterm256 = options.get("xterm256", flags.get("XTERM256", True)) - useansi = options.get("ansi", flags.get("ANSI", True)) - nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi)) - screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) - prompt = options.get("send_prompt", False) - - if screenreader: - # screenreader mode cleans up output - text = parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) - text = _RE_SCREENREADER_REGEX.sub("", text) - cmd = "prompt" if prompt else "text" - if raw: - args[0] = text - else: - args[0] = parse_html(text, strip_ansi=nocolor) - - # send to client on required form [cmdname, args, kwargs] - self.client.lineSend(self.csessid, [cmd, args, kwargs])
- -
[docs] def send_prompt(self, *args, **kwargs): - kwargs["options"].update({"send_prompt": True}) - self.send_text(*args, **kwargs)
- -
[docs] def send_default(self, cmdname, *args, **kwargs): - """ - Data Evennia -> User. - - Args: - cmdname (str): The first argument will always be the oob cmd name. - *args (any): Remaining args will be arguments for `cmd`. - - Keyword Args: - options (dict): These are ignored for oob commands. Use command - arguments (which can hold dicts) to send instructions to the - client instead. - - """ - if not cmdname == "options": - self.client.lineSend(self.csessid, [cmdname, args, kwargs])
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner.html b/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner.html deleted file mode 100644 index 0583762c3d..0000000000 --- a/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner.html +++ /dev/null @@ -1,540 +0,0 @@ - - - - - - - - evennia.server.profiling.dummyrunner — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.profiling.dummyrunner

-"""
-Dummy client runner
-
-This module implements a stand-alone launcher for stress-testing
-an Evennia game. It will launch any number of fake clients. These
-clients will log into the server and start doing random operations.
-Customizing and weighing these operations differently depends on
-which type of game is tested. The module contains a testing module
-for plain Evennia.
-
-Please note that you shouldn't run this on a production server!
-Launch the program without any arguments or options to see a
-full step-by-step setup help.
-
-Basically (for testing default Evennia):
-
- - Use an empty/testing database.
- - set PERMISSION_ACCOUNT_DEFAULT = "Builder"
- - start server, eventually with profiling active
- - launch this client runner
-
-If you want to customize the runner's client actions
-(because you changed the cmdset or needs to better
-match your use cases or add more actions), you can
-change which actions by adding a path to
-
-   DUMMYRUNNER_ACTIONS_MODULE = <path.to.your.module>
-
-in your settings. See utils.dummyrunner_actions.py
-for instructions on how to define this module.
-
-"""
-
-
-import sys
-import time
-import random
-from argparse import ArgumentParser
-from twisted.conch import telnet
-from twisted.internet import reactor, protocol
-from twisted.internet.task import LoopingCall
-
-from django.conf import settings
-from evennia.utils import mod_import, time_format
-
-# Load the dummyrunner settings module
-
-DUMMYRUNNER_SETTINGS = mod_import(settings.DUMMYRUNNER_SETTINGS_MODULE)
-if not DUMMYRUNNER_SETTINGS:
-    raise IOError(
-        "Error: Dummyrunner could not find settings file at %s"
-        % settings.DUMMYRUNNER_SETTINGS_MODULE
-    )
-
-DATESTRING = "%Y%m%d%H%M%S"
-
-# Settings
-
-# number of clients to launch if no input is given on command line
-NCLIENTS = 1
-# time between each 'tick', in seconds, if not set on command
-# line. All launched clients will be called upon to possibly do an
-# action with this frequency.
-TIMESTEP = DUMMYRUNNER_SETTINGS.TIMESTEP
-# chance of a client performing an action, per timestep. This helps to
-# spread out usage randomly, like it would be in reality.
-CHANCE_OF_ACTION = DUMMYRUNNER_SETTINGS.CHANCE_OF_ACTION
-# spread out the login action separately, having many accounts create accounts
-# and connect simultaneously is generally unlikely.
-CHANCE_OF_LOGIN = DUMMYRUNNER_SETTINGS.CHANCE_OF_LOGIN
-# Port to use, if not specified on command line
-TELNET_PORT = DUMMYRUNNER_SETTINGS.TELNET_PORT or settings.TELNET_PORTS[0]
-#
-NLOGGED_IN = 0
-
-
-# Messages
-
-
-INFO_STARTING = """
-    Dummyrunner starting using {N} dummy account(s). If you don't see
-    any connection messages, make sure that the Evennia server is
-    running.
-
-    Use Ctrl-C to stop/disconnect clients.
-    """
-
-ERROR_NO_MIXIN = """
-    Error: Evennia is not set up for dummyrunner. Before starting the
-    server, make sure to include the following at *the end* of your
-    settings file (remove when not using dummyrunner!):
-
-        from evennia.server.profiling.settings_mixin import *
-
-    This will change the settings in the following way:
-        - change PERMISSION_ACCOUNT_DEFAULT to 'Developer' to allow clients
-          to test all commands
-        - change PASSWORD_HASHERS to use a faster (but less safe) algorithm
-          when creating large numbers of accounts at the same time
-
-    If you don't want to use the custom settings of the mixin for some
-    reason, you can change their values manually after the import, or
-    add DUMMYRUNNER_MIXIN=True to your settings file to avoid this
-    error completely.
-
-    Warning: Don't run dummyrunner on a production database! It will
-    create a lot of spammy objects and accounts!
-    """
-
-
-ERROR_FEW_ACTIONS = """
-    Dummyrunner settings error: The ACTIONS tuple is too short: it must
-    contain at least login- and logout functions.
-    """
-
-
-HELPTEXT = """
-DO NOT RUN THIS ON A PRODUCTION SERVER! USE A CLEAN/TESTING DATABASE!
-
-This stand-alone program launches dummy telnet clients against a
-running Evennia server. The idea is to mimic real accounts logging in
-and repeatedly doing resource-heavy commands so as to stress test the
-game. It uses the default command set to log in and issue commands, so
-if that was customized, some of the functionality will not be tested
-(it will not fail, the commands will just not be recognized).  The
-running clients will create new objects and rooms all over the place
-as part of their running, so using a clean/testing database is
-strongly recommended.
-
-Setup:
-  1) setup a fresh/clean database (if using sqlite, just safe-copy
-     away your real evennia.db3 file and create a new one with
-     `evennia migrate`)
-  2) in server/conf/settings.py, add
-
-        PERMISSION_ACCOUNT_DEFAULT="Builder"
-
-     This is so that the dummy accounts can test building operations.
-     You can also customize the dummyrunner by modifying a setting
-     file specified by DUMMYRUNNER_SETTINGS_MODULE
-
-  3) Start Evennia like normal, optionally with profiling (--profile)
-  4) Run this dummy runner via the evennia launcher:
-
-        evennia --dummyrunner <nr_of_clients>
-
-  5) Log on and determine if game remains responsive despite the
-     heavier load. Note that if you activated profiling, there is a
-     considerate additional overhead from the profiler too so you
-     should usually not consider game responsivity when using the
-     profiler at the same time.
-  6) If you use profiling, let the game run long enough to gather
-     data, then stop the server cleanly using evennia stop or @shutdown.
-     @shutdown. The profile appears as
-     server/logs/server.prof/portal.prof (see Python's manual on
-     cProfiler).
-
-Notes:
-
-The dummyrunner tends to create a lot of accounts all at once, which is
-a very heavy operation. This is not a realistic use-case - what you want
-to test is performance during run. A large
-number of clients here may lock up the client until all have been
-created. It may be better to connect multiple dummyrunners instead of
-starting one single one with a lot of accounts. Exactly what this number
-is depends on your computer power. So start with 10-20 clients and increase
-until you see the initial login slows things too much.
-
-"""
-
-# ------------------------------------------------------------
-# Helper functions
-# ------------------------------------------------------------
-
-
-ICOUNT = 0
-
-
-
[docs]def idcounter(): - """ - Makes unique ids. - - Returns: - count (int): A globally unique counter. - - """ - global ICOUNT - ICOUNT += 1 - return str(ICOUNT)
- - -GCOUNT = 0 - - -
[docs]def gidcounter(): - """ - Makes globally unique ids. - - Returns: - count (int); A globally unique counter. - - """ - global GCOUNT - GCOUNT += 1 - return "%s-%s" % (time.strftime(DATESTRING), GCOUNT)
- - -
[docs]def makeiter(obj): - """ - Makes everything iterable. - - Args: - obj (any): Object to turn iterable. - - Returns: - iterable (iterable): An iterable object. - """ - return obj if hasattr(obj, "__iter__") else [obj]
- - -# ------------------------------------------------------------ -# Client classes -# ------------------------------------------------------------ - - -
[docs]class DummyClient(telnet.StatefulTelnetProtocol): - """ - Handles connection to a running Evennia server, - mimicking a real account by sending commands on - a timer. - - """ - -
[docs] def connectionMade(self): - """ - Called when connection is first established. - - """ - # public properties - self.cid = idcounter() - self.key = "Dummy-%s" % self.cid - self.gid = "%s-%s" % (time.strftime(DATESTRING), self.cid) - self.istep = 0 - self.exits = [] # exit names created - self.objs = [] # obj names created - - self._connected = False - self._loggedin = False - self._logging_out = False - self._report = "" - self._cmdlist = [] # already stepping in a cmd definition - self._login = self.factory.actions[0] - self._logout = self.factory.actions[1] - self._actions = self.factory.actions[2:] - - reactor.addSystemEventTrigger("before", "shutdown", self.logout)
- -
[docs] def dataReceived(self, data): - """ - Called when data comes in over the protocol. We wait to start - stepping until the server actually responds - - Args: - data (str): Incoming data. - - """ - if not self._connected and not data.startswith(chr(255)): - # wait until we actually get text back (not just telnet - # negotiation) - self._connected = True - # start client tick - d = LoopingCall(self.step) - # dissipate exact step by up to +/- 0.5 second - timestep = TIMESTEP + (-0.5 + (random.random() * 1.0)) - d.start(timestep, now=True).addErrback(self.error)
- -
[docs] def connectionLost(self, reason): - """ - Called when loosing the connection. - - Args: - reason (str): Reason for loosing connection. - - """ - if not self._logging_out: - print("client %s(%s) lost connection (%s)" % (self.key, self.cid, reason))
- -
[docs] def error(self, err): - """ - Error callback. - - Args: - err (Failure): Error instance. - """ - print(err)
- -
[docs] def counter(self): - """ - Produces a unique id, also between clients. - - Returns: - counter (int): A unique counter. - - """ - return gidcounter()
- -
[docs] def logout(self): - """ - Causes the client to log out of the server. Triggered by ctrl-c signal. - - """ - self._logging_out = True - cmd = self._logout(self) - print("client %s(%s) logout (%s actions)" % (self.key, self.cid, self.istep)) - self.sendLine(cmd)
- -
[docs] def step(self): - """ - Perform a step. This is called repeatedly by the runner and - causes the client to issue commands to the server. This holds - all "intelligence" of the dummy client. - - """ - global NLOGGED_IN - - rand = random.random() - - if not self._cmdlist: - # no commands ready. Load some. - - if not self._loggedin: - if rand < CHANCE_OF_LOGIN: - # get the login commands - self._cmdlist = list(makeiter(self._login(self))) - NLOGGED_IN += 1 # this is for book-keeping - print("connecting client %s (%i/%i)..." % (self.key, NLOGGED_IN, NCLIENTS)) - self._loggedin = True - else: - # no login yet, so cmdlist not yet set - return - else: - # we always pick a cumulatively random function - crand = random.random() - cfunc = [func for (cprob, func) in self._actions if cprob >= crand][0] - self._cmdlist = list(makeiter(cfunc(self))) - - # at this point we always have a list of commands - if rand < CHANCE_OF_ACTION: - # send to the game - self.sendLine(str(self._cmdlist.pop(0))) - self.istep += 1
- - -
[docs]class DummyFactory(protocol.ClientFactory): - protocol = DummyClient - -
[docs] def __init__(self, actions): - "Setup the factory base (shared by all clients)" - self.actions = actions
- - -# ------------------------------------------------------------ -# Access method: -# Starts clients and connects them to a running server. -# ------------------------------------------------------------ - - -
[docs]def start_all_dummy_clients(nclients): - """ - Initialize all clients, connect them and start to step them - - Args: - nclients (int): Number of dummy clients to connect. - - """ - global NCLIENTS - NCLIENTS = int(nclients) - actions = DUMMYRUNNER_SETTINGS.ACTIONS - - if len(actions) < 2: - print(ERROR_FEW_ACTIONS) - return - - # make sure the probabilities add up to 1 - pratio = 1.0 / sum(tup[0] for tup in actions[2:]) - flogin, flogout, probs, cfuncs = ( - actions[0], - actions[1], - [tup[0] * pratio for tup in actions[2:]], - [tup[1] for tup in actions[2:]], - ) - # create cumulative probabilies for the random actions - cprobs = [sum(v for i, v in enumerate(probs) if i <= k) for k in range(len(probs))] - # rebuild a new, optimized action structure - actions = (flogin, flogout) + tuple(zip(cprobs, cfuncs)) - - # setting up all clients (they are automatically started) - factory = DummyFactory(actions) - for i in range(NCLIENTS): - reactor.connectTCP("localhost", TELNET_PORT, factory) - # start reactor - reactor.run()
- - -# ------------------------------------------------------------ -# Command line interface -# ------------------------------------------------------------ - - -if __name__ == "__main__": - - try: - settings.DUMMYRUNNER_MIXIN - except AttributeError: - print(ERROR_NO_MIXIN) - sys.exit() - - # parsing command line with default vals - parser = ArgumentParser(description=HELPTEXT) - parser.add_argument( - "-N", nargs=1, default=1, dest="nclients", help="Number of clients to start" - ) - - args = parser.parse_args() - - print(INFO_STARTING.format(N=args.nclients[0])) - - # run the dummyrunner - t0 = time.time() - start_all_dummy_clients(nclients=args.nclients[0]) - ttot = time.time() - t0 - - # output runtime - print("... dummy client runner stopped after %s." % time_format(ttot, style=3)) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner_settings.html b/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner_settings.html deleted file mode 100644 index 9c249649cf..0000000000 --- a/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner_settings.html +++ /dev/null @@ -1,398 +0,0 @@ - - - - - - - - evennia.server.profiling.dummyrunner_settings — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.profiling.dummyrunner_settings

-"""
-Settings and actions for the dummyrunner
-
-This module defines dummyrunner settings and sets up
-the actions available to dummy accounts.
-
-The settings are global variables:
-
-TIMESTEP - time in seconds between each 'tick'
-CHANCE_OF_ACTION - chance 0-1 of action happening
-CHANCE_OF_LOGIN - chance 0-1 of login happening
-TELNET_PORT - port to use, defaults to settings.TELNET_PORT
-ACTIONS - see below
-
-ACTIONS is a tuple
-
-```
-(login_func, logout_func, (0.3, func1), (0.1, func2) ... )
-```
-
-where the first entry is the function to call on first connect, with a
-chance of occurring given by CHANCE_OF_LOGIN. This function is usually
-responsible for logging in the account. The second entry is always
-called when the dummyrunner disconnects from the server and should
-thus issue a logout command.  The other entries are tuples (chance,
-func). They are picked randomly, their commonality based on the
-cumulative chance given (the chance is normalized between all options
-so if will still work also if the given chances don't add up to 1).
-Since each function can return a list of game-command strings, each
-function may result in multiple operations.
-
-An action-function is called with a "client" argument which is a
-reference to the dummy client currently performing the action. It
-returns a string or a list of command strings to execute.  Use the
-client object for optionally saving data between actions.
-
-The client object has the following relevant properties and methods:
-
-- key - an optional client key. This is only used for dummyrunner output.
-        Default is "Dummy-<cid>"
-- cid - client id
-- gid - globally unique id, hashed with time stamp
-- istep - the current step
-- exits - an empty list. Can be used to store exit names
-- objs - an empty list. Can be used to store object names
-- counter() - returns a unique increasing id, hashed with time stamp
-              to make it unique also between dummyrunner instances.
-
-The return should either be a single command string or a tuple of
-command strings. This list of commands will always be executed every
-TIMESTEP with a chance given by CHANCE_OF_ACTION by in the order given
-(no randomness) and allows for setting up a more complex chain of
-commands (such as creating an account and logging in).
-
----
-
-"""
-# Dummy runner settings
-
-# Time between each dummyrunner "tick", in seconds. Each dummy
-# will be called with this frequency.
-TIMESTEP = 2
-
-# Chance of a dummy actually performing an action on a given tick.
-# This spreads out usage randomly, like it would be in reality.
-CHANCE_OF_ACTION = 0.5
-
-# Chance of a currently unlogged-in dummy performing its login
-# action every tick. This emulates not all accounts logging in
-# at exactly the same time.
-CHANCE_OF_LOGIN = 1.0
-
-# Which telnet port to connect to. If set to None, uses the first
-# default telnet port of the running server.
-TELNET_PORT = None
-
-
-# Setup actions tuple
-
-# some convenient templates
-
-DUMMY_NAME = "Dummy-%s"
-DUMMY_PWD = "password-%s"
-START_ROOM = "testing_room_start_%s"
-ROOM_TEMPLATE = "testing_room_%s"
-EXIT_TEMPLATE = "exit_%s"
-OBJ_TEMPLATE = "testing_obj_%s"
-TOBJ_TEMPLATE = "testing_button_%s"
-TOBJ_TYPECLASS = "contrib.tutorial_examples.red_button.RedButton"
-
-
-# action function definitions (pick and choose from
-# these to build a client "usage profile"
-
-# login/logout
-
-
-
[docs]def c_login(client): - "logins to the game" - # we always use a new client name - cname = DUMMY_NAME % client.gid - cpwd = DUMMY_PWD % client.gid - - # set up for digging a first room (to move to and keep the - # login room clean) - roomname = ROOM_TEMPLATE % client.counter() - exitname1 = EXIT_TEMPLATE % client.counter() - exitname2 = EXIT_TEMPLATE % client.counter() - client.exits.extend([exitname1, exitname2]) - - cmds = ( - "create %s %s" % (cname, cpwd), - "connect %s %s" % (cname, cpwd), - "@dig %s" % START_ROOM % client.gid, - "@teleport %s" % START_ROOM % client.gid, - "@dig %s = %s, %s" % (roomname, exitname1, exitname2), - ) - return cmds
- - -
[docs]def c_login_nodig(client): - "logins, don't dig its own room" - cname = DUMMY_NAME % client.gid - cpwd = DUMMY_PWD % client.gid - - cmds = ("create %s %s" % (cname, cpwd), "connect %s %s" % (cname, cpwd)) - return cmds
- - -
[docs]def c_logout(client): - "logouts of the game" - return "@quit"
- - -# random commands - - -
[docs]def c_looks(client): - "looks at various objects" - cmds = ["look %s" % obj for obj in client.objs] - if not cmds: - cmds = ["look %s" % exi for exi in client.exits] - if not cmds: - cmds = "look" - return cmds
- - -
[docs]def c_examines(client): - "examines various objects" - cmds = ["examine %s" % obj for obj in client.objs] - if not cmds: - cmds = ["examine %s" % exi for exi in client.exits] - if not cmds: - cmds = "examine me" - return cmds
- - -
[docs]def c_idles(client): - "idles" - cmds = ("idle", "idle") - return cmds
- - -
[docs]def c_help(client): - "reads help files" - cmds = ("help", "help @teleport", "help look", "help @tunnel", "help @dig") - return cmds
- - -
[docs]def c_digs(client): - "digs a new room, storing exit names on client" - roomname = ROOM_TEMPLATE % client.counter() - exitname1 = EXIT_TEMPLATE % client.counter() - exitname2 = EXIT_TEMPLATE % client.counter() - client.exits.extend([exitname1, exitname2]) - return "@dig/tel %s = %s, %s" % (roomname, exitname1, exitname2)
- - -
[docs]def c_creates_obj(client): - "creates normal objects, storing their name on client" - objname = OBJ_TEMPLATE % client.counter() - client.objs.append(objname) - cmds = ( - "@create %s" % objname, - '@desc %s = "this is a test object' % objname, - "@set %s/testattr = this is a test attribute value." % objname, - "@set %s/testattr2 = this is a second test attribute." % objname, - ) - return cmds
- - -
[docs]def c_creates_button(client): - "creates example button, storing name on client" - objname = TOBJ_TEMPLATE % client.counter() - client.objs.append(objname) - cmds = ("@create %s:%s" % (objname, TOBJ_TYPECLASS), "@desc %s = test red button!" % objname) - return cmds
- - -
[docs]def c_socialize(client): - "socializechats on channel" - cmds = ( - "ooc Hello!", - "ooc Testing ...", - "ooc Testing ... times 2", - "say Yo!", - "emote stands looking around.", - ) - return cmds
- - -
[docs]def c_moves(client): - "moves to a previously created room, using the stored exits" - cmds = client.exits # try all exits - finally one will work - return "look" if not cmds else cmds
- - -
[docs]def c_moves_n(client): - "move through north exit if available" - return "north"
- - -
[docs]def c_moves_s(client): - "move through south exit if available" - return "south"
- - -# Action tuple (required) -# -# This is a tuple of client action functions. The first element is the -# function the client should use to log into the game and move to -# STARTROOM . The second element is the logout command, for cleanly -# exiting the mud. The following elements are 2-tuples of (probability, -# action_function). The probablities should normally sum up to 1, -# otherwise the system will normalize them. -# - - -# "normal builder" definitionj -# ACTIONS = ( c_login, -# c_logout, -# (0.5, c_looks), -# (0.08, c_examines), -# (0.1, c_help), -# (0.01, c_digs), -# (0.01, c_creates_obj), -# (0.3, c_moves)) -# "heavy" builder definition -# ACTIONS = ( c_login, -# c_logout, -# (0.2, c_looks), -# (0.1, c_examines), -# (0.2, c_help), -# (0.1, c_digs), -# (0.1, c_creates_obj), -# #(0.01, c_creates_button), -# (0.2, c_moves)) -# "passive account" definition -# ACTIONS = ( c_login, -# c_logout, -# (0.7, c_looks), -# #(0.1, c_examines), -# (0.3, c_help)) -# #(0.1, c_digs), -# #(0.1, c_creates_obj), -# #(0.1, c_creates_button), -# #(0.4, c_moves)) -# "inactive account" definition -# ACTIONS = (c_login_nodig, -# c_logout, -# (1.0, c_idles)) -# "normal account" definition -ACTIONS = (c_login, c_logout, (0.01, c_digs), (0.39, c_looks), (0.2, c_help), (0.4, c_moves)) -# walking tester. This requires a pre-made -# "loop" of multiple rooms that ties back -# to limbo (using @tunnel and @open) -# ACTIONS = (c_login_nodig, -# c_logout, -# (1.0, c_moves_n)) -# "socializing heavy builder" definition -# ACTIONS = (c_login, -# c_logout, -# (0.1, c_socialize), -# (0.1, c_looks), -# (0.2, c_help), -# (0.1, c_creates_obj), -# (0.2, c_digs), -# (0.3, c_moves)) -# "heavy digger memory tester" definition -# ACTIONS = (c_login, -# c_logout, -# (1.0, c_digs)) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/profiling/memplot.html b/docs/0.9.5/_modules/evennia/server/profiling/memplot.html deleted file mode 100644 index fa22a42594..0000000000 --- a/docs/0.9.5/_modules/evennia/server/profiling/memplot.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - evennia.server.profiling.memplot — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.profiling.memplot

-"""
-Script that saves memory and idmapper data over time.
-
-Data will be saved to game/logs/memoryusage.log. Note that
-the script will append to this file if it already exists.
-
-Call this module directly to plot the log (requires matplotlib and numpy).
-"""
-
-import os
-import sys
-import time
-
-# TODO!
-# sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
-# os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
-import evennia
-from evennia.utils.idmapper import models as _idmapper
-
-LOGFILE = "logs/memoryusage.log"
-INTERVAL = 30  # log every 30 seconds
-
-
-
[docs]class Memplot(evennia.DefaultScript): - """ - Describes a memory plotting action. - - """ - -
[docs] def at_script_creation(self): - "Called at script creation" - self.key = "memplot" - self.desc = "Save server memory stats to file" - self.start_delay = False - self.persistent = True - self.interval = INTERVAL - self.db.starttime = time.time()
- -
[docs] def at_repeat(self): - "Regularly save memory statistics." - pid = os.getpid() - rmem = ( - float(os.popen("ps -p %d -o %s | tail -1" % (pid, "rss")).read()) / 1000.0 - ) # resident memory - vmem = ( - float(os.popen("ps -p %d -o %s | tail -1" % (pid, "vsz")).read()) / 1000.0 - ) # virtual memory - total_num, cachedict = _idmapper.cache_size() - t0 = (time.time() - self.db.starttime) / 60.0 # save in minutes - - with open(LOGFILE, "a") as f: - f.write("%s, %s, %s, %s\n" % (t0, rmem, vmem, int(total_num)))
- - -if __name__ == "__main__": - - # plot output from the file - - from matplotlib import pyplot as pp - import numpy - - data = numpy.genfromtxt("../../../game/" + LOGFILE, delimiter=",") - secs = data[:, 0] - rmem = data[:, 1] - vmem = data[:, 2] - nobj = data[:, 3] - - # calculate derivative of obj creation - # oderiv = (0.5*(nobj[2:] - nobj[:-2]) / (secs[2:] - secs[:-2])).copy() - # oderiv = (0.5*(rmem[2:] - rmem[:-2]) / (secs[2:] - secs[:-2])).copy() - - fig = pp.figure() - ax1 = fig.add_subplot(111) - ax1.set_title("1000 bots (normal accounts with light building)") - ax1.set_xlabel("Time (mins)") - ax1.set_ylabel("Memory usage (MB)") - ax1.plot(secs, rmem, "r", label="RMEM", lw=2) - ax1.plot(secs, vmem, "b", label="VMEM", lw=2) - ax1.legend(loc="upper left") - - ax2 = ax1.twinx() - ax2.plot(secs, nobj, "g--", label="objs in cache", lw=2) - # ax2.plot(secs[:-2], oderiv/60.0, "g--", label="Objs/second", lw=2) - # ax2.plot(secs[:-2], oderiv, "g--", label="Objs/second", lw=2) - ax2.set_ylabel("Number of objects") - ax2.legend(loc="lower right") - ax2.annotate("First 500 bots\nconnecting", xy=(10, 4000)) - ax2.annotate("Next 500 bots\nconnecting", xy=(350, 10000)) - # ax2.annotate("@reload", xy=(185,600)) - - # # plot mem vs cachesize - # nobj, rmem, vmem = nobj[:262].copy(), rmem[:262].copy(), vmem[:262].copy() - # - # fig = pp.figure() - # ax1 = fig.add_subplot(111) - # ax1.set_title("Memory usage per cache size") - # ax1.set_xlabel("Cache size (number of objects)") - # ax1.set_ylabel("Memory usage (MB)") - # ax1.plot(nobj, rmem, "r", label="RMEM", lw=2) - # ax1.plot(nobj, vmem, "b", label="VMEM", lw=2) - # - - # empirical estimate of memory usage: rmem = 35.0 + 0.0157 * Ncache - # Ncache = int((rmem - 35.0) / 0.0157) (rmem in MB) - # - # rderiv_aver = 0.0157 - # fig = pp.figure() - # ax1 = fig.add_subplot(111) - # ax1.set_title("Relation between memory and cache size") - # ax1.set_xlabel("Memory usage (MB)") - # ax1.set_ylabel("Idmapper Cache Size (number of objects)") - # rmem = numpy.linspace(35, 2000, 2000) - # nobjs = numpy.array([int((mem - 35.0) / 0.0157) for mem in rmem]) - # ax1.plot(rmem, nobjs, "r", lw=2) - - pp.show() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/profiling/test_queries.html b/docs/0.9.5/_modules/evennia/server/profiling/test_queries.html deleted file mode 100644 index 09603849e0..0000000000 --- a/docs/0.9.5/_modules/evennia/server/profiling/test_queries.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - evennia.server.profiling.test_queries — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.profiling.test_queries

-"""
-This is a little routine for viewing the sql queries that are executed by a given
-query as well as count them for optimization testing.
-
-"""
-
-import sys
-import os
-
-# sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
-# os.environ["DJANGO_SETTINGS_MODULE"] = "game.settings"
-from django.db import connection
-
-
-
[docs]def count_queries(exec_string, setup_string): - """ - Display queries done by exec_string. Use setup_string - to setup the environment to test. - """ - - exec(setup_string) - - num_queries_old = len(connection.queries) - exec(exec_string) - nqueries = len(connection.queries) - num_queries_old - - for query in connection.queries[-nqueries if nqueries else 1 :]: - print(query["time"], query["sql"]) - print("Number of queries: %s" % nqueries)
- - -if __name__ == "__main__": - - # setup tests here - - setup_string = """ -from evennia.objects.models import ObjectDB -g = ObjectDB.objects.get(db_key="Griatch") -""" - exec_string = """ -g.tags.all() -""" - count_queries(exec_string, setup_string) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/profiling/tests.html b/docs/0.9.5/_modules/evennia/server/profiling/tests.html deleted file mode 100644 index 65612a1311..0000000000 --- a/docs/0.9.5/_modules/evennia/server/profiling/tests.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - - - evennia.server.profiling.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.profiling.tests

-from django.test import TestCase
-from mock import Mock, patch, mock_open
-from .dummyrunner_settings import (
-    c_creates_button,
-    c_creates_obj,
-    c_digs,
-    c_examines,
-    c_help,
-    c_idles,
-    c_login,
-    c_login_nodig,
-    c_logout,
-    c_looks,
-    c_moves,
-    c_moves_n,
-    c_moves_s,
-    c_socialize,
-)
-
-try:
-    import memplot
-except ImportError:
-    memplot = Mock()
-
-
-
[docs]class TestDummyrunnerSettings(TestCase): -
[docs] def setUp(self): - self.client = Mock() - self.client.cid = 1 - self.client.counter = Mock(return_value=1) - self.client.gid = "20171025161153-1" - self.client.name = "Dummy-%s" % self.client.gid - self.client.password = "password-%s" % self.client.gid - self.client.start_room = "testing_room_start_%s" % self.client.gid - self.client.objs = [] - self.client.exits = []
- -
[docs] def clear_client_lists(self): - self.client.objs = [] - self.client.exits = []
- -
[docs] def test_c_login(self): - self.assertEqual( - c_login(self.client), - ( - "create %s %s" % (self.client.name, self.client.password), - "connect %s %s" % (self.client.name, self.client.password), - "@dig %s" % self.client.start_room, - "@teleport %s" % self.client.start_room, - "@dig testing_room_1 = exit_1, exit_1", - ), - )
- -
[docs] def test_c_login_no_dig(self): - self.assertEqual( - c_login_nodig(self.client), - ( - "create %s %s" % (self.client.name, self.client.password), - "connect %s %s" % (self.client.name, self.client.password), - ), - )
- -
[docs] def test_c_logout(self): - self.assertEqual(c_logout(self.client), "@quit")
- -
[docs] def perception_method_tests(self, func, verb, alone_suffix=""): - self.assertEqual(func(self.client), "%s%s" % (verb, alone_suffix)) - self.client.exits = ["exit1", "exit2"] - self.assertEqual(func(self.client), ["%s exit1" % verb, "%s exit2" % verb]) - self.client.objs = ["foo", "bar"] - self.assertEqual(func(self.client), ["%s foo" % verb, "%s bar" % verb]) - self.clear_client_lists()
- -
[docs] def test_c_looks(self): - self.perception_method_tests(c_looks, "look")
- -
[docs] def test_c_examines(self): - self.perception_method_tests(c_examines, "examine", " me")
- -
[docs] def test_idles(self): - self.assertEqual(c_idles(self.client), ("idle", "idle"))
- -
[docs] def test_c_help(self): - self.assertEqual( - c_help(self.client), - ("help", "help @teleport", "help look", "help @tunnel", "help @dig"), - )
- -
[docs] def test_c_digs(self): - self.assertEqual(c_digs(self.client), ("@dig/tel testing_room_1 = exit_1, exit_1")) - self.assertEqual(self.client.exits, ["exit_1", "exit_1"]) - self.clear_client_lists()
- -
[docs] def test_c_creates_obj(self): - objname = "testing_obj_1" - self.assertEqual( - c_creates_obj(self.client), - ( - "@create %s" % objname, - '@desc %s = "this is a test object' % objname, - "@set %s/testattr = this is a test attribute value." % objname, - "@set %s/testattr2 = this is a second test attribute." % objname, - ), - ) - self.assertEqual(self.client.objs, [objname]) - self.clear_client_lists()
- -
[docs] def test_c_creates_button(self): - objname = "testing_button_1" - typeclass_name = "contrib.tutorial_examples.red_button.RedButton" - self.assertEqual( - c_creates_button(self.client), - ("@create %s:%s" % (objname, typeclass_name), "@desc %s = test red button!" % objname), - ) - self.assertEqual(self.client.objs, [objname]) - self.clear_client_lists()
- -
[docs] def test_c_socialize(self): - self.assertEqual( - c_socialize(self.client), - ( - "ooc Hello!", - "ooc Testing ...", - "ooc Testing ... times 2", - "say Yo!", - "emote stands looking around.", - ), - )
- -
[docs] def test_c_moves(self): - self.assertEqual(c_moves(self.client), "look") - self.client.exits = ["south", "north"] - self.assertEqual(c_moves(self.client), ["south", "north"]) - self.clear_client_lists()
- -
[docs] def test_c_move_n(self): - self.assertEqual(c_moves_n(self.client), "north")
- -
[docs] def test_c_move_s(self): - self.assertEqual(c_moves_s(self.client), "south")
- - -
[docs]class TestMemPlot(TestCase): -
[docs] @patch.object(memplot, "_idmapper") - @patch.object(memplot, "os") - @patch.object(memplot, "open", new_callable=mock_open, create=True) - @patch.object(memplot, "time") - @patch("evennia.utils.idmapper.models.SharedMemoryModel.flush_from_cache", new=Mock()) - def test_memplot(self, mock_time, mocked_open, mocked_os, mocked_idmapper): - if isinstance(memplot, Mock): - return - from evennia.utils.create import create_script - - mocked_idmapper.cache_size.return_value = (9, 5000) - mock_time.time = Mock(return_value=6000.0) - script = create_script(memplot.Memplot) - script.db.starttime = 0.0 - mocked_os.popen.read.return_value = 5000.0 - script.at_repeat() - handle = mocked_open() - handle.write.assert_called_with("100.0, 0.001, 0.001, 9\n") - script.stop()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/profiling/timetrace.html b/docs/0.9.5/_modules/evennia/server/profiling/timetrace.html deleted file mode 100644 index 343da14001..0000000000 --- a/docs/0.9.5/_modules/evennia/server/profiling/timetrace.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - evennia.server.profiling.timetrace — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.profiling.timetrace

-"""
-Trace a message through the messaging system
-"""
-
-import time
-
-
-
[docs]def timetrace(message, idstring, tracemessage="TEST_MESSAGE", final=False): - """ - Trace a message with time stamps. - - Args: - message (str): The actual message coming through - idstring (str): An identifier string specifying where this trace is happening. - tracemessage (str): The start of the message to tag. - This message will get attached time stamp. - final (bool): This is the final leg in the path - include total time in message - - """ - if message.startswith(tracemessage): - # the message is on the form TEST_MESSAGE tlast t0 - # where t0 is the initial starting time and last is the time - # saved at the last stop. - try: - prefix, tlast, t0 = message.split(None, 2) - tlast, t0 = float(tlast), float(t0) - except (IndexError, ValueError): - t0 = time.time() - tlast = t0 - t1 = t0 - else: - t1 = time.time() - # print to log (important!) - print("** timetrace (%s): dT=%fs, total=%fs." % (idstring, t1 - tlast, t1 - t0)) - - if final: - message = " **** %s (total %f) **** " % (tracemessage, t1 - t0) - else: - message = "%s %f %f" % (tracemessage, t1, t0) - return message
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/server.html b/docs/0.9.5/_modules/evennia/server/server.html deleted file mode 100644 index 0004b4f788..0000000000 --- a/docs/0.9.5/_modules/evennia/server/server.html +++ /dev/null @@ -1,848 +0,0 @@ - - - - - - - - evennia.server.server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.server

-"""
-This module implements the main Evennia server process, the core of
-the game engine.
-
-This module should be started with the 'twistd' executable since it
-sets up all the networking features.  (this is done automatically
-by evennia/server/server_runner.py).
-
-"""
-import time
-import sys
-import os
-
-from twisted.web import static
-from twisted.application import internet, service
-from twisted.internet import reactor, defer
-from twisted.internet.task import LoopingCall
-from twisted.python.log import ILogObserver
-
-import django
-
-django.setup()
-
-import evennia
-
-evennia._init()
-
-from django.db import connection
-from django.db.utils import OperationalError
-from django.conf import settings
-
-from evennia.accounts.models import AccountDB
-from evennia.scripts.models import ScriptDB
-from evennia.server.models import ServerConfig
-from evennia.server import initial_setup
-
-from evennia.utils.utils import get_evennia_version, mod_import, make_iter
-from evennia.utils import logger
-from evennia.comms import channelhandler
-from evennia.server.sessionhandler import SESSIONS
-
-from django.utils.translation import gettext as _
-
-_SA = object.__setattr__
-
-# a file with a flag telling the server to restart after shutdown or not.
-SERVER_RESTART = os.path.join(settings.GAME_DIR, "server", "server.restart")
-
-# module containing hook methods called during start_stop
-SERVER_STARTSTOP_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE)
-
-# modules containing plugin services
-SERVER_SERVICES_PLUGIN_MODULES = make_iter(settings.SERVER_SERVICES_PLUGIN_MODULES)
-
-
-# ------------------------------------------------------------
-# Evennia Server settings
-# ------------------------------------------------------------
-
-SERVERNAME = settings.SERVERNAME
-VERSION = get_evennia_version()
-
-AMP_ENABLED = True
-AMP_HOST = settings.AMP_HOST
-AMP_PORT = settings.AMP_PORT
-AMP_INTERFACE = settings.AMP_INTERFACE
-
-WEBSERVER_PORTS = settings.WEBSERVER_PORTS
-WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
-
-GUEST_ENABLED = settings.GUEST_ENABLED
-
-# server-channel mappings
-WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
-IRC_ENABLED = settings.IRC_ENABLED
-RSS_ENABLED = settings.RSS_ENABLED
-GRAPEVINE_ENABLED = settings.GRAPEVINE_ENABLED
-WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
-GAME_INDEX_ENABLED = settings.GAME_INDEX_ENABLED
-
-INFO_DICT = {
-    "servername": SERVERNAME,
-    "version": VERSION,
-    "amp": "",
-    "errors": "",
-    "info": "",
-    "webserver": "",
-    "irc_rss": "",
-}
-
-try:
-    WEB_PLUGINS_MODULE = mod_import(settings.WEB_PLUGINS_MODULE)
-except ImportError:
-    WEB_PLUGINS_MODULE = None
-    INFO_DICT["errors"] = (
-        "WARNING: settings.WEB_PLUGINS_MODULE not found - "
-        "copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf."
-    )
-
-
-# Maintenance function - this is called repeatedly by the server
-
-_MAINTENANCE_COUNT = 0
-_FLUSH_CACHE = None
-_IDMAPPER_CACHE_MAXSIZE = settings.IDMAPPER_CACHE_MAXSIZE
-_GAMETIME_MODULE = None
-
-_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
-_LAST_SERVER_TIME_SNAPSHOT = 0
-
-
-def _server_maintenance():
-    """
-    This maintenance function handles repeated checks and updates that
-    the server needs to do. It is called every minute.
-    """
-    global EVENNIA, _MAINTENANCE_COUNT, _FLUSH_CACHE, _GAMETIME_MODULE
-    global _LAST_SERVER_TIME_SNAPSHOT
-
-    if not _FLUSH_CACHE:
-        from evennia.utils.idmapper.models import conditional_flush as _FLUSH_CACHE
-    if not _GAMETIME_MODULE:
-        from evennia.utils import gametime as _GAMETIME_MODULE
-
-    _MAINTENANCE_COUNT += 1
-
-    now = time.time()
-    if _MAINTENANCE_COUNT == 1:
-        # first call after a reload
-        _GAMETIME_MODULE.SERVER_START_TIME = now
-        _GAMETIME_MODULE.SERVER_RUNTIME = ServerConfig.objects.conf("runtime", default=0.0)
-        _LAST_SERVER_TIME_SNAPSHOT = now
-    else:
-        # adjust the runtime not with 60s but with the actual elapsed time
-        # in case this may varies slightly from 60s.
-        _GAMETIME_MODULE.SERVER_RUNTIME += now - _LAST_SERVER_TIME_SNAPSHOT
-    _LAST_SERVER_TIME_SNAPSHOT = now
-
-    # update game time and save it across reloads
-    _GAMETIME_MODULE.SERVER_RUNTIME_LAST_UPDATED = now
-    ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.SERVER_RUNTIME)
-
-    if _MAINTENANCE_COUNT % 5 == 0:
-        # check cache size every 5 minutes
-        _FLUSH_CACHE(_IDMAPPER_CACHE_MAXSIZE)
-    if _MAINTENANCE_COUNT % 60 == 0:
-        # validate scripts every hour
-        evennia.ScriptDB.objects.validate()
-    if _MAINTENANCE_COUNT % 61 == 0:
-        # validate channels off-sync with scripts
-        evennia.CHANNEL_HANDLER.update()
-    if _MAINTENANCE_COUNT % (60 * 7) == 0:
-        # drop database connection every 7 hrs to avoid default timeouts on MySQL
-        # (see https://github.com/evennia/evennia/issues/1376)
-        connection.close()
-
-    # handle idle timeouts
-    if _IDLE_TIMEOUT > 0:
-        reason = _("idle timeout exceeded")
-        to_disconnect = []
-        for session in (
-            sess for sess in SESSIONS.values() if (now - sess.cmd_last) > _IDLE_TIMEOUT
-        ):
-            if not session.account or not session.account.access(
-                session.account, "noidletimeout", default=False
-            ):
-                to_disconnect.append(session)
-
-        for session in to_disconnect:
-            SESSIONS.disconnect(session, reason=reason)
-
-
-# ------------------------------------------------------------
-# Evennia Main Server object
-# ------------------------------------------------------------
-
-
-
[docs]class Evennia(object): - - """ - The main Evennia server handler. This object sets up the database and - tracks and interlinks all the twisted network services that make up - evennia. - """ - -
[docs] def __init__(self, application): - """ - Setup the server. - - application - an instantiated Twisted application - - """ - sys.path.insert(1, ".") - - # create a store of services - self.services = service.MultiService() - self.services.setServiceParent(application) - self.amp_protocol = None # set by amp factory - self.sessions = SESSIONS - self.sessions.server = self - self.process_id = os.getpid() - - # Database-specific startup optimizations. - self.sqlite3_prep() - - self.start_time = time.time() - - # initialize channelhandler - try: - channelhandler.CHANNELHANDLER.update() - except OperationalError: - print("channelhandler couldn't update - db not set up") - - # wrap the SIGINT handler to make sure we empty the threadpool - # even when we reload and we have long-running requests in queue. - # this is necessary over using Twisted's signal handler. - # (see https://github.com/evennia/evennia/issues/1128) - def _wrap_sigint_handler(*args): - from twisted.internet.defer import Deferred - - if hasattr(self, "web_root"): - d = self.web_root.empty_threadpool() - d.addCallback(lambda _: self.shutdown("reload", _reactor_stopping=True)) - else: - d = Deferred(lambda _: self.shutdown("reload", _reactor_stopping=True)) - d.addCallback(lambda _: reactor.stop()) - reactor.callLater(1, d.callback, None) - - reactor.sigInt = _wrap_sigint_handler
- - # Server startup methods - -
[docs] def sqlite3_prep(self): - """ - Optimize some SQLite stuff at startup since we - can't save it to the database. - """ - if ( - ".".join(str(i) for i in django.VERSION) < "1.2" - and settings.DATABASES.get("default", {}).get("ENGINE") == "sqlite3" - ) or ( - hasattr(settings, "DATABASES") - and settings.DATABASES.get("default", {}).get("ENGINE", None) - == "django.db.backends.sqlite3" - ): - cursor = connection.cursor() - cursor.execute("PRAGMA cache_size=10000") - cursor.execute("PRAGMA synchronous=OFF") - cursor.execute("PRAGMA count_changes=OFF") - cursor.execute("PRAGMA temp_store=2")
- -
[docs] def update_defaults(self): - """ - We make sure to store the most important object defaults here, so - we can catch if they change and update them on-objects automatically. - This allows for changing default cmdset locations and default - typeclasses in the settings file and have them auto-update all - already existing objects. - """ - global INFO_DICT - - # setting names - settings_names = ( - "CMDSET_CHARACTER", - "CMDSET_ACCOUNT", - "BASE_ACCOUNT_TYPECLASS", - "BASE_OBJECT_TYPECLASS", - "BASE_CHARACTER_TYPECLASS", - "BASE_ROOM_TYPECLASS", - "BASE_EXIT_TYPECLASS", - "BASE_SCRIPT_TYPECLASS", - "BASE_CHANNEL_TYPECLASS", - ) - # get previous and current settings so they can be compared - settings_compare = list( - zip( - [ServerConfig.objects.conf(name) for name in settings_names], - [settings.__getattr__(name) for name in settings_names], - ) - ) - mismatches = [ - i for i, tup in enumerate(settings_compare) if tup[0] and tup[1] and tup[0] != tup[1] - ] - if len( - mismatches - ): # can't use any() since mismatches may be [0] which reads as False for any() - # we have a changed default. Import relevant objects and - # run the update - from evennia.objects.models import ObjectDB - from evennia.comms.models import ChannelDB - - # from evennia.accounts.models import AccountDB - for i, prev, curr in ( - (i, tup[0], tup[1]) for i, tup in enumerate(settings_compare) if i in mismatches - ): - # update the database - INFO_DICT["info"] = ( - " %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..." - % (settings_names[i], prev, curr) - ) - if i == 0: - ObjectDB.objects.filter(db_cmdset_storage__exact=prev).update( - db_cmdset_storage=curr - ) - if i == 1: - AccountDB.objects.filter(db_cmdset_storage__exact=prev).update( - db_cmdset_storage=curr - ) - if i == 2: - AccountDB.objects.filter(db_typeclass_path__exact=prev).update( - db_typeclass_path=curr - ) - if i in (3, 4, 5, 6): - ObjectDB.objects.filter(db_typeclass_path__exact=prev).update( - db_typeclass_path=curr - ) - if i == 7: - ScriptDB.objects.filter(db_typeclass_path__exact=prev).update( - db_typeclass_path=curr - ) - if i == 8: - ChannelDB.objects.filter(db_typeclass_path__exact=prev).update( - db_typeclass_path=curr - ) - # store the new default and clean caches - ServerConfig.objects.conf(settings_names[i], curr) - ObjectDB.flush_instance_cache() - AccountDB.flush_instance_cache() - ScriptDB.flush_instance_cache() - ChannelDB.flush_instance_cache() - # if this is the first start we might not have a "previous" - # setup saved. Store it now. - [ - ServerConfig.objects.conf(settings_names[i], tup[1]) - for i, tup in enumerate(settings_compare) - if not tup[0] - ]
- -
[docs] def run_initial_setup(self): - """ - This is triggered by the amp protocol when the connection - to the portal has been established. - This attempts to run the initial_setup script of the server. - It returns if this is not the first time the server starts. - Once finished the last_initial_setup_step is set to -1. - """ - global INFO_DICT - last_initial_setup_step = ServerConfig.objects.conf("last_initial_setup_step") - if not last_initial_setup_step: - # None is only returned if the config does not exist, - # i.e. this is an empty DB that needs populating. - INFO_DICT["info"] = " Server started for the first time. Setting defaults." - initial_setup.handle_setup(0) - elif int(last_initial_setup_step) >= 0: - # a positive value means the setup crashed on one of its - # modules and setup will resume from this step, retrying - # the last failed module. When all are finished, the step - # is set to -1 to show it does not need to be run again. - INFO_DICT["info"] = " Resuming initial setup from step {last}.".format( - last=last_initial_setup_step - ) - initial_setup.handle_setup(int(last_initial_setup_step))
- -
[docs] def run_init_hooks(self, mode): - """ - Called by the amp client once receiving sync back from Portal - - Args: - mode (str): One of shutdown, reload or reset - - """ - from evennia.objects.models import ObjectDB - - # start server time and maintenance task - self.maintenance_task = LoopingCall(_server_maintenance) - self.maintenance_task.start(60, now=True) # call every minute - - # update eventual changed defaults - self.update_defaults() - - [o.at_init() for o in ObjectDB.get_all_cached_instances()] - [p.at_init() for p in AccountDB.get_all_cached_instances()] - - # call correct server hook based on start file value - if mode == "reload": - logger.log_msg("Server successfully reloaded.") - self.at_server_reload_start() - elif mode == "reset": - # only run hook, don't purge sessions - self.at_server_cold_start() - logger.log_msg("Evennia Server successfully restarted in 'reset' mode.") - elif mode == "shutdown": - self.at_server_cold_start() - # clear eventual lingering session storages - ObjectDB.objects.clear_all_sessids() - logger.log_msg("Evennia Server successfully started.") - - # always call this regardless of start type - self.at_server_start()
- -
[docs] @defer.inlineCallbacks - def shutdown(self, mode="reload", _reactor_stopping=False): - """ - Shuts down the server from inside it. - - Keyword Args: - mode (str): Sets the server restart mode: - - 'reload': server restarts, no "persistent" scripts - are stopped, at_reload hooks called. - - 'reset' - server restarts, non-persistent scripts stopped, - at_shutdown hooks called but sessions will not - be disconnected. - -'shutdown' - like reset, but server will not auto-restart. - _reactor_stopping: This is set if server is stopped by a kill - command OR this method was already called - once - in both cases the reactor is dead/stopping already. - - """ - if _reactor_stopping and hasattr(self, "shutdown_complete"): - # this means we have already passed through this method - # once; we don't need to run the shutdown procedure again. - defer.returnValue(None) - - from evennia.objects.models import ObjectDB - from evennia.server.models import ServerConfig - from evennia.utils import gametime as _GAMETIME_MODULE - - if mode == "reload": - # call restart hooks - ServerConfig.objects.conf("server_restart_mode", "reload") - yield [o.at_server_reload() for o in ObjectDB.get_all_cached_instances()] - yield [p.at_server_reload() for p in AccountDB.get_all_cached_instances()] - yield [ - (s.pause(manual_pause=False), s.at_server_reload()) - for s in ScriptDB.get_all_cached_instances() - if s.id and (s.is_active or s.attributes.has("_manual_pause")) - ] - yield self.sessions.all_sessions_portal_sync() - self.at_server_reload_stop() - # only save monitor state on reload, not on shutdown/reset - from evennia.scripts.monitorhandler import MONITOR_HANDLER - - MONITOR_HANDLER.save() - else: - if mode == "reset": - # like shutdown but don't unset the is_connected flag and don't disconnect sessions - yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()] - yield [p.at_server_shutdown() for p in AccountDB.get_all_cached_instances()] - if self.amp_protocol: - yield self.sessions.all_sessions_portal_sync() - else: # shutdown - yield [_SA(p, "is_connected", False) for p in AccountDB.get_all_cached_instances()] - yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()] - yield [ - (p.unpuppet_all(), p.at_server_shutdown()) - for p in AccountDB.get_all_cached_instances() - ] - yield ObjectDB.objects.clear_all_sessids() - yield [ - ( - s.pause(manual_pause=s.attributes.get("_manual_pause", False)), - s.at_server_shutdown(), - ) - for s in ScriptDB.get_all_cached_instances() - ] - ServerConfig.objects.conf("server_restart_mode", "reset") - self.at_server_cold_stop() - - # tickerhandler state should always be saved. - from evennia.scripts.tickerhandler import TICKER_HANDLER - - TICKER_HANDLER.save() - - # always called, also for a reload - self.at_server_stop() - - if hasattr(self, "web_root"): # not set very first start - yield self.web_root.empty_threadpool() - - if not _reactor_stopping: - # kill the server - self.shutdown_complete = True - reactor.callLater(1, reactor.stop) - - # we make sure the proper gametime is saved as late as possible - ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime())
- -
[docs] def get_info_dict(self): - "Return the server info, for display." - return INFO_DICT
- - # server start/stop hooks - -
[docs] def at_server_start(self): - """ - This is called every time the server starts up, regardless of - how it was shut down. - """ - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_start()
- -
[docs] def at_server_stop(self): - """ - This is called just before a server is shut down, regardless - of it is fore a reload, reset or shutdown. - """ - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_stop()
- -
[docs] def at_server_reload_start(self): - """ - This is called only when server starts back up after a reload. - """ - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_reload_start()
- -
[docs] def at_post_portal_sync(self, mode): - """ - This is called just after the portal has finished syncing back data to the server - after reconnecting. - - Args: - mode (str): One of reload, reset or shutdown. - - """ - - from evennia.scripts.monitorhandler import MONITOR_HANDLER - - MONITOR_HANDLER.restore(mode == "reload") - - from evennia.scripts.tickerhandler import TICKER_HANDLER - - TICKER_HANDLER.restore(mode == "reload") - - # after sync is complete we force-validate all scripts - # (this also starts any that didn't yet start) - ScriptDB.objects.validate(init_mode=mode) - - # start the task handler - from evennia.scripts.taskhandler import TASK_HANDLER - - TASK_HANDLER.load() - TASK_HANDLER.create_delays() - - # check so default channels exist - from evennia.comms.models import ChannelDB - from evennia.accounts.models import AccountDB - from evennia.utils.create import create_channel - - god_account = AccountDB.objects.get(id=1) - # mudinfo - mudinfo_chan = settings.CHANNEL_MUDINFO - if not mudinfo_chan: - raise RuntimeError("settings.CHANNEL_MUDINFO must be defined.") - if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]): - channel = create_channel(**mudinfo_chan) - channel.connect(god_account) - # connectinfo - connectinfo_chan = settings.CHANNEL_MUDINFO - if connectinfo_chan: - if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]): - channel = create_channel(**connectinfo_chan) - # default channels - for chan_info in settings.DEFAULT_CHANNELS: - if not ChannelDB.objects.filter(db_key=chan_info["key"]): - channel = create_channel(**chan_info) - channel.connect(god_account) - - # delete the temporary setting - ServerConfig.objects.conf("server_restart_mode", delete=True)
- -
[docs] def at_server_reload_stop(self): - """ - This is called only time the server stops before a reload. - """ - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_reload_stop()
- -
[docs] def at_server_cold_start(self): - """ - This is called only when the server starts "cold", i.e. after a - shutdown or a reset. - """ - # We need to do this just in case the server was killed in a way where - # the normal cleanup operations did not have time to run. - from evennia.objects.models import ObjectDB - - ObjectDB.objects.clear_all_sessids() - - # Remove non-persistent scripts - from evennia.scripts.models import ScriptDB - - for script in ScriptDB.objects.filter(db_persistent=False): - script.stop() - - if GUEST_ENABLED: - for guest in AccountDB.objects.all().filter( - db_typeclass_path=settings.BASE_GUEST_TYPECLASS - ): - for character in guest.db._playable_characters: - if character: - character.delete() - guest.delete() - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_cold_start()
- -
[docs] def at_server_cold_stop(self): - """ - This is called only when the server goes down due to a shutdown or reset. - """ - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_cold_stop()
- - -# ------------------------------------------------------------ -# -# Start the Evennia game server and add all active services -# -# ------------------------------------------------------------ - - -# Tell the system the server is starting up; some things are not available yet -try: - ServerConfig.objects.conf("server_starting_mode", True) -except OperationalError: - print("Server server_starting_mode couldn't be set - database not set up.") - - -# twistd requires us to define the variable 'application' so it knows -# what to execute from. -application = service.Application("Evennia") - -if "--nodaemon" not in sys.argv: - # custom logging, but only if we are not running in interactive mode - logfile = logger.WeeklyLogFile( - os.path.basename(settings.SERVER_LOG_FILE), - os.path.dirname(settings.SERVER_LOG_FILE), - day_rotation=settings.SERVER_LOG_DAY_ROTATION, - max_size=settings.SERVER_LOG_MAX_SIZE, - ) - application.setComponent(ILogObserver, logger.ServerLogObserver(logfile).emit) - -# The main evennia server program. This sets up the database -# and is where we store all the other services. -EVENNIA = Evennia(application) - -if AMP_ENABLED: - - # The AMP protocol handles the communication between - # the portal and the mud server. Only reason to ever deactivate - # it would be during testing and debugging. - - ifacestr = "" - if AMP_INTERFACE != "127.0.0.1": - ifacestr = "-%s" % AMP_INTERFACE - - INFO_DICT["amp"] = "amp %s: %s" % (ifacestr, AMP_PORT) - - from evennia.server import amp_client - - factory = amp_client.AMPClientFactory(EVENNIA) - amp_service = internet.TCPClient(AMP_HOST, AMP_PORT, factory) - amp_service.setName("ServerAMPClient") - EVENNIA.services.addService(amp_service) - -if WEBSERVER_ENABLED: - - # Start a django-compatible webserver. - - from evennia.server.webserver import ( - DjangoWebRoot, - WSGIWebServer, - Website, - LockableThreadPool, - PrivateStaticRoot, - ) - - # start a thread pool and define the root url (/) as a wsgi resource - # recognized by Django - threads = LockableThreadPool( - minthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[0]), - maxthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[1]), - ) - - web_root = DjangoWebRoot(threads) - # point our media resources to url /media - web_root.putChild(b"media", PrivateStaticRoot(settings.MEDIA_ROOT)) - # point our static resources to url /static - web_root.putChild(b"static", PrivateStaticRoot(settings.STATIC_ROOT)) - EVENNIA.web_root = web_root - - if WEB_PLUGINS_MODULE: - # custom overloads - web_root = WEB_PLUGINS_MODULE.at_webserver_root_creation(web_root) - - web_site = Website(web_root, logPath=settings.HTTP_LOG_FILE) - web_site.is_portal = False - - INFO_DICT["webserver"] = "" - for proxyport, serverport in WEBSERVER_PORTS: - # create the webserver (we only need the port for this) - webserver = WSGIWebServer(threads, serverport, web_site, interface="127.0.0.1") - webserver.setName("EvenniaWebServer%s" % serverport) - EVENNIA.services.addService(webserver) - - INFO_DICT["webserver"] += "webserver: %s" % serverport - -ENABLED = [] -if IRC_ENABLED: - # IRC channel connections - ENABLED.append("irc") - -if RSS_ENABLED: - # RSS feed channel connections - ENABLED.append("rss") - -if GRAPEVINE_ENABLED: - # Grapevine channel connections - ENABLED.append("grapevine") - -if GAME_INDEX_ENABLED: - from evennia.server.game_index_client.service import EvenniaGameIndexService - - egi_service = EvenniaGameIndexService() - EVENNIA.services.addService(egi_service) - -if ENABLED: - INFO_DICT["irc_rss"] = ", ".join(ENABLED) + " enabled." - -for plugin_module in SERVER_SERVICES_PLUGIN_MODULES: - # external plugin protocols - load here - plugin_module = mod_import(plugin_module) - if plugin_module: - plugin_module.start_plugin_services(EVENNIA) - else: - print(f"Could not load plugin module {plugin_module}") - -# clear server startup mode -try: - ServerConfig.objects.conf("server_starting_mode", delete=True) -except OperationalError: - print("Server server_starting_mode couldn't unset - db not set up.") -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/serversession.html b/docs/0.9.5/_modules/evennia/server/serversession.html deleted file mode 100644 index 540c0d9415..0000000000 --- a/docs/0.9.5/_modules/evennia/server/serversession.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - evennia.server.serversession — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.serversession

-"""
-This defines a the Server's generic session object. This object represents
-a connection to the outside world but don't know any details about how the
-connection actually happens (so it's the same for telnet, web, ssh etc).
-
-It is stored on the Server side (as opposed to protocol-specific sessions which
-are stored on the Portal side)
-"""
-import weakref
-import time
-from django.utils import timezone
-from django.conf import settings
-from evennia.comms.models import ChannelDB
-from evennia.utils import logger
-from evennia.utils.utils import make_iter, lazy_property
-from evennia.commands.cmdsethandler import CmdSetHandler
-from evennia.server.session import Session
-from evennia.scripts.monitorhandler import MONITOR_HANDLER
-
-_GA = object.__getattribute__
-_SA = object.__setattr__
-_ObjectDB = None
-_ANSI = None
-
-# i18n
-from django.utils.translation import gettext as _
-
-# Handlers for Session.db/ndb operation
-
-
-
[docs]class NDbHolder(object): - """Holder for allowing property access of attributes""" - -
[docs] def __init__(self, obj, name, manager_name="attributes"): - _SA(self, name, _GA(obj, manager_name)) - _SA(self, "name", name)
- - def __getattribute__(self, attrname): - if attrname == "all": - # we allow to overload our default .all - attr = _GA(self, _GA(self, "name")).get("all") - return attr if attr else _GA(self, "all") - return _GA(self, _GA(self, "name")).get(attrname) - - def __setattr__(self, attrname, value): - _GA(self, _GA(self, "name")).add(attrname, value) - - def __delattr__(self, attrname): - _GA(self, _GA(self, "name")).remove(attrname) - -
[docs] def get_all(self): - return _GA(self, _GA(self, "name")).all()
- - all = property(get_all)
- - -
[docs]class NAttributeHandler(object): - """ - NAttributeHandler version without recache protection. - This stand-alone handler manages non-database saving. - It is similar to `AttributeHandler` and is used - by the `.ndb` handler in the same way as `.db` does - for the `AttributeHandler`. - """ - -
[docs] def __init__(self, obj): - """ - Initialized on the object - """ - self._store = {} - self.obj = weakref.proxy(obj)
- -
[docs] def has(self, key): - """ - Check if object has this attribute or not. - - Args: - key (str): The Nattribute key to check. - - Returns: - has_nattribute (bool): If Nattribute is set or not. - - """ - return key in self._store
- -
[docs] def get(self, key, default=None): - """ - Get the named key value. - - Args: - key (str): The Nattribute key to get. - - Returns: - the value of the Nattribute. - - """ - return self._store.get(key, default)
- -
[docs] def add(self, key, value): - """ - Add new key and value. - - Args: - key (str): The name of Nattribute to add. - value (any): The value to store. - - """ - self._store[key] = value
- -
[docs] def remove(self, key): - """ - Remove Nattribute from storage. - - Args: - key (str): The name of the Nattribute to remove. - - """ - if key in self._store: - del self._store[key]
- -
[docs] def clear(self): - """ - Remove all NAttributes from handler. - - """ - self._store = {}
- -
[docs] def all(self, return_tuples=False): - """ - List the contents of the handler. - - Args: - return_tuples (bool, optional): Defines if the Nattributes - are returns as a list of keys or as a list of `(key, value)`. - - Returns: - nattributes (list): A list of keys `[key, key, ...]` or a - list of tuples `[(key, value), ...]` depending on the - setting of `return_tuples`. - - """ - if return_tuples: - return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] - return [key for key in self._store if not key.startswith("_")]
- - -# ------------------------------------------------------------- -# Server Session -# ------------------------------------------------------------- - - -
[docs]class ServerSession(Session): - """ - This class represents an account's session and is a template for - individual protocols to communicate with Evennia. - - Each account gets a session assigned to them whenever they connect - to the game server. All communication between game and account goes - through their session. - - """ - -
[docs] def __init__(self): - """Initiate to avoid AttributeErrors down the line""" - self.puppet = None - self.account = None - self.cmdset_storage_string = "" - self.cmdset = CmdSetHandler(self, True)
- - def __cmdset_storage_get(self): - return [path.strip() for path in self.cmdset_storage_string.split(",")] - - def __cmdset_storage_set(self, value): - self.cmdset_storage_string = ",".join(str(val).strip() for val in make_iter(value)) - - cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set) - -
[docs] def at_sync(self): - """ - This is called whenever a session has been resynced with the - portal. At this point all relevant attributes have already - been set and self.account been assigned (if applicable). - - Since this is often called after a server restart we need to - set up the session as it was. - - """ - global _ObjectDB - if not _ObjectDB: - from evennia.objects.models import ObjectDB as _ObjectDB - - super(ServerSession, self).at_sync() - if not self.logged_in: - # assign the unloggedin-command set. - self.cmdset_storage = settings.CMDSET_UNLOGGEDIN - - self.cmdset.update(init_mode=True) - - if self.puid: - # reconnect puppet (puid is only set if we are coming - # back from a server reload). This does all the steps - # done in the default @ic command but without any - # hooks, echoes or access checks. - obj = _ObjectDB.objects.get(id=self.puid) - obj.sessions.add(self) - obj.account = self.account - self.puid = obj.id - self.puppet = obj - # obj.scripts.validate() - obj.locks.cache_lock_bypass(obj)
- -
[docs] def at_login(self, account): - """ - Hook called by sessionhandler when the session becomes authenticated. - - Args: - account (Account): The account associated with the session. - - """ - self.account = account - self.uid = self.account.id - self.uname = self.account.username - self.logged_in = True - self.conn_time = time.time() - self.puid = None - self.puppet = None - self.cmdset_storage = settings.CMDSET_SESSION - - # Update account's last login time. - self.account.last_login = timezone.now() - self.account.save() - - # add the session-level cmdset - self.cmdset = CmdSetHandler(self, True)
- -
[docs] def at_disconnect(self, reason=None): - """ - Hook called by sessionhandler when disconnecting this session. - - """ - if self.logged_in: - account = self.account - if self.puppet: - account.unpuppet_object(self) - uaccount = account - uaccount.last_login = timezone.now() - uaccount.save() - # calling account hook - account.at_disconnect(reason) - self.logged_in = False - if not self.sessionhandler.sessions_from_account(account): - # no more sessions connected to this account - account.is_connected = False - # this may be used to e.g. delete account after disconnection etc - account.at_post_disconnect() - # remove any webclient settings monitors associated with this - # session - MONITOR_HANDLER.remove(account, "_saved_webclient_options", self.sessid)
- -
[docs] def get_account(self): - """ - Get the account associated with this session - - Returns: - account (Account): The associated Account. - - """ - return self.logged_in and self.account
- -
[docs] def get_puppet(self): - """ - Get the in-game character associated with this session. - - Returns: - puppet (Object): The puppeted object, if any. - - """ - return self.logged_in and self.puppet
- - get_character = get_puppet - -
[docs] def get_puppet_or_account(self): - """ - Get puppet or account. - - Returns: - controller (Object or Account): The puppet if one exists, - otherwise return the account. - - """ - if self.logged_in: - return self.puppet if self.puppet else self.account - return None
- -
[docs] def log(self, message, channel=True): - """ - Emits session info to the appropriate outputs and info channels. - - Args: - message (str): The message to log. - channel (bool, optional): Log to the CHANNEL_CONNECTINFO channel - in addition to the server log. - - """ - cchan = channel and settings.CHANNEL_CONNECTINFO - if cchan: - try: - cchan = ChannelDB.objects.get_channel(cchan["key"]) - cchan.msg("[%s]: %s" % (cchan.key, message)) - except Exception: - logger.log_trace() - logger.log_info(message)
- -
[docs] def get_client_size(self): - """ - Return eventual eventual width and height reported by the - client. Note that this currently only deals with a single - client window (windowID==0) as in a traditional telnet session. - - """ - flags = self.protocol_flags - width = flags.get("SCREENWIDTH", {}).get(0, settings.CLIENT_DEFAULT_WIDTH) - height = flags.get("SCREENHEIGHT", {}).get(0, settings.CLIENT_DEFAULT_HEIGHT) - return width, height
- -
[docs] def update_session_counters(self, idle=False): - """ - Hit this when the user enters a command in order to update - idle timers and command counters. - - """ - # Idle time used for timeout calcs. - self.cmd_last = time.time() - - # Store the timestamp of the user's last command. - if not idle: - # Increment the user's command counter. - self.cmd_total += 1 - # Account-visible idle time, not used in idle timeout calcs. - self.cmd_last_visible = self.cmd_last
- -
[docs] def update_flags(self, **kwargs): - """ - Update the protocol_flags and sync them with Portal. - - Keyword Args: - any: A key:value pair to set in the - protocol_flags dictionary. - - Notes: - Since protocols can vary, no checking is done - as to the existene of the flag or not. The input - data should have been validated before this call. - - """ - if kwargs: - self.protocol_flags.update(kwargs) - self.sessionhandler.session_portal_sync(self)
- -
[docs] def data_out(self, **kwargs): - """ - Sending data from Evennia->Client - - Keyword Args: - text (str or tuple) - any (str or tuple): Send-commands identified - by their keys. Or "options", carrying options - for the protocol(s). - - """ - self.sessionhandler.data_out(self, **kwargs)
- -
[docs] def data_in(self, **kwargs): - """ - Receiving data from the client, sending it off to - the respective inputfuncs. - - Keyword Args: - any: Incoming data from protocol on - the form `{"commandname": ((args), {kwargs}),...}` - Notes: - This method is here in order to give the user - a single place to catch and possibly process all incoming data from - the client. It should usually always end by sending - this data off to `self.sessionhandler.call_inputfuncs(self, **kwargs)`. - - """ - self.sessionhandler.call_inputfuncs(self, **kwargs)
- -
[docs] def msg(self, text=None, **kwargs): - """ - Wrapper to mimic msg() functionality of Objects and Accounts. - - Args: - text (str): String input. - kwargs (str or tuple): Send-commands identified - by their keys. Or "options", carrying options - for the protocol(s). - - """ - # this can happen if this is triggered e.g. a command.msg - # that auto-adds the session, we'd get a kwarg collision. - kwargs.pop("session", None) - kwargs.pop("from_obj", None) - if text is not None: - self.data_out(text=text, **kwargs) - else: - self.data_out(**kwargs)
- -
[docs] def execute_cmd(self, raw_string, session=None, **kwargs): - """ - Do something as this object. This method is normally never - called directly, instead incoming command instructions are - sent to the appropriate inputfunc already at the sessionhandler - level. This method allows Python code to inject commands into - this stream, and will lead to the text inputfunc be called. - - Args: - raw_string (string): Raw command input - session (Session): This is here to make API consistent with - Account/Object.execute_cmd. If given, data is passed to - that Session, otherwise use self. - Keyword Args: - Other keyword arguments will be added to the found command - object instace as variables before it executes. This is - unused by default Evennia but may be used to set flags and - change operating paramaters for commands at run-time. - - """ - # inject instruction into input stream - kwargs["text"] = ((raw_string,), {}) - self.sessionhandler.data_in(session or self, **kwargs)
- - def __eq__(self, other): - """Handle session comparisons""" - try: - return self.address == other.address - except AttributeError: - return False - - def __hash__(self): - """ - Python 3 requires that any class which implements __eq__ must also - implement __hash__ and that the corresponding hashes for equivalent - instances are themselves equivalent. - - """ - return hash(self.address) - - def __ne__(self, other): - try: - return self.address != other.address - except AttributeError: - return True - - def __str__(self): - """ - String representation of the user session class. We use - this a lot in the server logs. - - """ - symbol = "" - if self.logged_in and hasattr(self, "account") and self.account: - symbol = "(#%s)" % self.account.id - try: - if hasattr(self.address, "__iter__"): - address = ":".join([str(part) for part in self.address]) - else: - address = self.address - except Exception: - address = self.address - return "%s%s@%s" % (self.uname, symbol, address) - - def __repr__(self): - return "%s" % str(self) - - # Dummy API hooks for use during non-loggedin operation - -
[docs] def at_cmdset_get(self, **kwargs): - """ - A dummy hook all objects with cmdsets need to have - """ - - pass
- - # Mock db/ndb properties for allowing easy storage on the session - # (note that no databse is involved at all here. session.db.attr = - # value just saves a normal property in memory, just like ndb). - -
[docs] @lazy_property - def nattributes(self): - return NAttributeHandler(self)
- -
[docs] @lazy_property - def attributes(self): - return self.nattributes
- - # @property -
[docs] def ndb_get(self): - """ - A non-persistent store (ndb: NonDataBase). Everything stored - to this is guaranteed to be cleared when a server is shutdown. - Syntax is same as for the _get_db_holder() method and - property, e.g. obj.ndb.attr = value etc. - - """ - try: - return self._ndb_holder - except AttributeError: - self._ndb_holder = NDbHolder(self, "nattrhandler", manager_name="nattributes") - return self._ndb_holder
- - # @ndb.setter -
[docs] def ndb_set(self, value): - """ - Stop accidentally replacing the db object - - Args: - value (any): A value to store in the ndb. - - """ - string = "Cannot assign directly to ndb object! " - string += "Use ndb.attr=value instead." - raise Exception(string)
- - # @ndb.deleter -
[docs] def ndb_del(self): - """Stop accidental deletion.""" - raise Exception("Cannot delete the ndb object!")
- - ndb = property(ndb_get, ndb_set, ndb_del) - db = property(ndb_get, ndb_set, ndb_del) - - # Mock access method for the session (there is no lock info - # at this stage, so we just present a uniform API) -
[docs] def access(self, *args, **kwargs): - """Dummy method to mimic the logged-in API.""" - return True
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/session.html b/docs/0.9.5/_modules/evennia/server/session.html deleted file mode 100644 index e79ca2eb35..0000000000 --- a/docs/0.9.5/_modules/evennia/server/session.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - - - evennia.server.session — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.session

-"""
-This module defines a generic session class. All connection instances
-(both on Portal and Server side) should inherit from this class.
-
-"""
-from django.conf import settings
-
-import time
-
-# ------------------------------------------------------------
-# Server Session
-# ------------------------------------------------------------
-
-
-
[docs]class Session(object): - """ - This class represents a player's session and is a template for - both portal- and server-side sessions. - - Each connection will see two session instances created: - - 1. A Portal session. This is customized for the respective connection - protocols that Evennia supports, like Telnet, SSH etc. The Portal - session must call init_session() as part of its initialization. The - respective hook methods should be connected to the methods unique - for the respective protocol so that there is a unified interface - to Evennia. - 2. A Server session. This is the same for all connected accounts, - regardless of how they connect. - - The Portal and Server have their own respective sessionhandlers. These - are synced whenever new connections happen or the Server restarts etc, - which means much of the same information must be stored in both places - e.g. the portal can re-sync with the server when the server reboots. - - """ - - # names of attributes that should be affected by syncing. - _attrs_to_sync = ( - "protocol_key", - "address", - "suid", - "sessid", - "uid", - "csessid", - "uname", - "logged_in", - "puid", - "conn_time", - "cmd_last", - "cmd_last_visible", - "cmd_total", - "protocol_flags", - "server_data", - "cmdset_storage_string", - ) - -
[docs] def init_session(self, protocol_key, address, sessionhandler): - """ - Initialize the Session. This should be called by the protocol when - a new session is established. - - Args: - protocol_key (str): By default, one of 'telnet', 'telnet/ssl', 'ssh', - 'webclient/websocket' or 'webclient/ajax'. - address (str): Client address. - sessionhandler (SessionHandler): Reference to the - main sessionhandler instance. - - """ - # This is currently 'telnet', 'ssh', 'ssl' or 'web' - self.protocol_key = protocol_key - # Protocol address tied to this session - self.address = address - - # suid is used by some protocols, it's a hex key. - self.suid = None - - # unique id for this session - self.sessid = 0 # no sessid yet - # client session id, if given by the client - self.csessid = None - # database id for the user connected to this session - self.uid = None - # user name, for easier tracking of sessions - self.uname = None - # if user has authenticated already or not - self.logged_in = False - - # database id of puppeted object (if any) - self.puid = None - - # session time statistics - self.conn_time = time.time() - self.cmd_last_visible = self.conn_time - self.cmd_last = self.conn_time - self.cmd_total = 0 - - self.protocol_flags = { - "ENCODING": "utf-8", - "SCREENREADER": False, - "INPUTDEBUG": False, - "RAW": False, - "NOCOLOR": False, - } - self.server_data = {} - - # map of input data to session methods - self.datamap = {} - - # a back-reference to the relevant sessionhandler this - # session is stored in. - self.sessionhandler = sessionhandler
- -
[docs] def get_sync_data(self): - """ - Get all data relevant to sync the session. - - Args: - syncdata (dict): All syncdata values, based on - the keys given by self._attrs_to_sync. - - """ - return dict( - (key, value) for key, value in self.__dict__.items() if key in self._attrs_to_sync - )
- -
[docs] def load_sync_data(self, sessdata): - """ - Takes a session dictionary, as created by get_sync_data, and - loads it into the correct properties of the session. - - Args: - sessdata (dict): Session data dictionary. - - """ - for propname, value in sessdata.items(): - if ( - propname == "protocol_flags" - and isinstance(value, dict) - and hasattr(self, "protocol_flags") - and isinstance(self.protocol_flags, dict) - ): - # special handling to allow partial update of protocol flags - self.protocol_flags.update(value) - else: - setattr(self, propname, value)
- -
[docs] def at_sync(self): - """ - Called after a session has been fully synced (including - secondary operations such as setting self.account based - on uid etc). - - """ - if self.account: - self.protocol_flags.update( - self.account.attributes.get("_saved_protocol_flags", None) or {} - )
- - # access hooks - -
[docs] def disconnect(self, reason=None): - """ - generic hook called from the outside to disconnect this session - should be connected to the protocols actual disconnect mechanism. - - Args: - reason (str): Eventual text motivating the disconnect. - - """ - pass
- -
[docs] def data_out(self, **kwargs): - """ - Generic hook for sending data out through the protocol. Server - protocols can use this right away. Portal sessions - should overload this to format/handle the outgoing data as needed. - - Keyword Args: - kwargs (any): Other data to the protocol. - - """ - pass
- -
[docs] def data_in(self, **kwargs): - """ - Hook for protocols to send incoming data to the engine. - - Keyword Args: - kwargs (any): Other data from the protocol. - - """ - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/sessionhandler.html b/docs/0.9.5/_modules/evennia/server/sessionhandler.html deleted file mode 100644 index 0bb2678151..0000000000 --- a/docs/0.9.5/_modules/evennia/server/sessionhandler.html +++ /dev/null @@ -1,969 +0,0 @@ - - - - - - - - evennia.server.sessionhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.sessionhandler

-"""
-This module defines handlers for storing sessions when handles
-sessions of users connecting to the server.
-
-There are two similar but separate stores of sessions:
-
-  - ServerSessionHandler - this stores generic game sessions
-         for the game. These sessions has no knowledge about
-         how they are connected to the world.
-  - PortalSessionHandler - this stores sessions created by
-         twisted protocols. These are dumb connectors that
-         handle network communication but holds no game info.
-
-"""
-import time
-
-from django.conf import settings
-from evennia.commands.cmdhandler import CMD_LOGINSTART
-from evennia.utils.logger import log_trace
-from evennia.utils.utils import (
-    variable_from_module,
-    is_iter,
-    make_iter,
-    delay,
-    callables_from_module,
-)
-from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT
-from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT
-from evennia.utils.inlinefuncs import parse_inlinefunc
-from codecs import decode as codecs_decode
-
-_INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED
-
-# delayed imports
-_AccountDB = None
-_ServerSession = None
-_ServerConfig = None
-_ScriptDB = None
-_OOB_HANDLER = None
-
-_ERR_BAD_UTF8 = "Your client sent an incorrect UTF-8 sequence."
-
-
-
[docs]class DummySession(object): - sessid = 0
- - -DUMMYSESSION = DummySession() - -# AMP signals -PCONN = chr(1) # portal session connect -PDISCONN = chr(2) # portal session disconnect -PSYNC = chr(3) # portal session sync -SLOGIN = chr(4) # server session login -SDISCONN = chr(5) # server session disconnect -SDISCONNALL = chr(6) # server session disconnect all -SSHUTD = chr(7) # server shutdown -SSYNC = chr(8) # server session sync -SCONN = chr(11) # server portal connection (for bots) -PCONNSYNC = chr(12) # portal post-syncing session -PDISCONNALL = chr(13) # portal session discnnect all -SRELOAD = chr(14) # server reloading (have portal start a new server) -SSTART = chr(15) # server start (portal must already be running anyway) -PSHUTD = chr(16) # portal (+server) shutdown -SSHUTD = chr(17) # server shutdown -PSTATUS = chr(18) # ping server or portal status -SRESET = chr(19) # server shutdown in reset mode - -# i18n -from django.utils.translation import gettext as _ - -_SERVERNAME = settings.SERVERNAME -_MULTISESSION_MODE = settings.MULTISESSION_MODE -_IDLE_TIMEOUT = settings.IDLE_TIMEOUT -_DELAY_CMD_LOGINSTART = settings.DELAY_CMD_LOGINSTART -_MAX_SERVER_COMMANDS_PER_SECOND = 100.0 -_MAX_SESSION_COMMANDS_PER_SECOND = 5.0 -_MODEL_MAP = None - -# input handlers - -_INPUT_FUNCS = {} -for modname in make_iter(settings.INPUT_FUNC_MODULES): - _INPUT_FUNCS.update(callables_from_module(modname)) - - -
[docs]def delayed_import(): - """ - Helper method for delayed import of all needed entities. - - """ - global _ServerSession, _AccountDB, _ServerConfig, _ScriptDB - if not _ServerSession: - # we allow optional arbitrary serversession class for overloading - modulename, classname = settings.SERVER_SESSION_CLASS.rsplit(".", 1) - _ServerSession = variable_from_module(modulename, classname) - if not _AccountDB: - from evennia.accounts.models import AccountDB as _AccountDB - if not _ServerConfig: - from evennia.server.models import ServerConfig as _ServerConfig - if not _ScriptDB: - from evennia.scripts.models import ScriptDB as _ScriptDB - # including once to avoid warnings in Python syntax checkers - assert _ServerSession - assert _AccountDB - assert _ServerConfig - assert _ScriptDB
- - -# ----------------------------------------------------------- -# SessionHandler base class -# ------------------------------------------------------------ - - -
[docs]class SessionHandler(dict): - """ - This handler holds a stack of sessions. - - """ - - def __getitem__(self, key): - "Clean out None-sessions automatically." - if None in self: - del self[None] - return super().__getitem__(key) - -
[docs] def get(self, key, default=None): - "Clean out None-sessions automatically." - if None in self: - del self[None] - return super().get(key, default)
- - def __setitem__(self, key, value): - "Don't assign None sessions" - if key is not None: - super().__setitem__(key, value) - - def __contains__(self, key): - "None-keys are not accepted." - return False if key is None else super().__contains__(key) - -
[docs] def get_sessions(self, include_unloggedin=False): - """ - Returns the connected session objects. - - Args: - include_unloggedin (bool, optional): Also list Sessions - that have not yet authenticated. - - Returns: - sessions (list): A list of `Session` objects. - - """ - if include_unloggedin: - return list(self.values()) - else: - return [session for session in self.values() if session.logged_in]
- -
[docs] def get_all_sync_data(self): - """ - Create a dictionary of sessdata dicts representing all - sessions in store. - - Returns: - syncdata (dict): A dict of sync data. - - """ - return dict((sessid, sess.get_sync_data()) for sessid, sess in self.items())
- -
[docs] def clean_senddata(self, session, kwargs): - """ - Clean up data for sending across the AMP wire. Also apply INLINEFUNCS. - - Args: - session (Session): The relevant session instance. - kwargs (dict) Each keyword represents a - send-instruction, with the keyword itself being the name - of the instruction (like "text"). Suitable values for each - keyword are: - :: - - arg -> [[arg], {}] - [args] -> [[args], {}] - {kwargs} -> [[], {kwargs}] - [args, {kwargs}] -> [[arg], {kwargs}] - [[args], {kwargs}] -> [[args], {kwargs}] - - Returns: - kwargs (dict): A cleaned dictionary of cmdname:[[args],{kwargs}] pairs, - where the keys, args and kwargs have all been converted to - send-safe entities (strings or numbers), and inlinefuncs have been - applied. - - """ - options = kwargs.pop("options", None) or {} - raw = options.get("raw", False) - strip_inlinefunc = options.get("strip_inlinefunc", False) - - def _utf8(data): - if isinstance(data, bytes): - try: - data = codecs_decode(data, session.protocol_flags["ENCODING"]) - except LookupError: - # wrong encoding set on the session. Set it to a safe one - session.protocol_flags["ENCODING"] = "utf-8" - data = codecs_decode(data, "utf-8") - except UnicodeDecodeError: - # incorrect unicode sequence - session.sendLine(_ERR_BAD_UTF8) - data = "" - - return data - - def _validate(data): - "Helper function to convert data to AMP-safe (picketable) values" - if isinstance(data, dict): - newdict = {} - for key, part in data.items(): - newdict[key] = _validate(part) - return newdict - elif is_iter(data): - return [_validate(part) for part in data] - elif isinstance(data, (str, bytes)): - data = _utf8(data) - - if _INLINEFUNC_ENABLED and not raw and isinstance(self, ServerSessionHandler): - # only parse inlinefuncs on the outgoing path (sessionhandler->) - data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) - - return str(data) - elif ( - hasattr(data, "id") - and hasattr(data, "db_date_created") - and hasattr(data, "__dbclass__") - ): - # convert database-object to their string representation. - return _validate(str(data)) - else: - return data - - rkwargs = {} - for key, data in kwargs.items(): - key = _validate(key) - if not data: - if key == "text": - # we don't allow sending text = None, this must mean - # that the text command is not to be used. - continue - rkwargs[key] = [[], {}] - elif isinstance(data, dict): - rkwargs[key] = [[], _validate(data)] - elif is_iter(data): - if isinstance(data[-1], dict): - if len(data) == 2: - if is_iter(data[0]): - rkwargs[key] = [_validate(data[0]), _validate(data[1])] - else: - rkwargs[key] = [[_validate(data[0])], _validate(data[1])] - else: - rkwargs[key] = [_validate(data[:-1]), _validate(data[-1])] - else: - rkwargs[key] = [_validate(data), {}] - else: - rkwargs[key] = [[_validate(data)], {}] - rkwargs[key][1]["options"] = options - return rkwargs
- - -# ------------------------------------------------------------ -# Server-SessionHandler class -# ------------------------------------------------------------ - - -
[docs]class ServerSessionHandler(SessionHandler): - """ - This object holds the stack of sessions active in the game at - any time. - - A session register with the handler in two steps, first by - registering itself with the connect() method. This indicates an - non-authenticated session. Whenever the session is authenticated - the session together with the related account is sent to the login() - method. - - """ - - # AMP communication methods - -
[docs] def __init__(self, *args, **kwargs): - """ - Init the handler. - - """ - super().__init__(*args, **kwargs) - self.server = None # set at server initialization - self.server_data = {"servername": _SERVERNAME} - # will be set on psync - self.portal_start_time = 0.0
- - def _run_cmd_login(self, session): - """ - Launch the CMD_LOGINSTART command. This is wrapped - for delays. - - """ - if not session.logged_in: - self.data_in(session, text=[[CMD_LOGINSTART], {}]) - -
[docs] def portal_connect(self, portalsessiondata): - """ - Called by Portal when a new session has connected. - Creates a new, unlogged-in game session. - - Args: - portalsessiondata (dict): a dictionary of all property:value - keys defining the session and which is marked to be - synced. - - """ - delayed_import() - global _ServerSession, _AccountDB, _ScriptDB - - sess = _ServerSession() - sess.sessionhandler = self - sess.load_sync_data(portalsessiondata) - sess.at_sync() - # validate all scripts - _ScriptDB.objects.validate() - self[sess.sessid] = sess - - if sess.logged_in and sess.uid: - # Session is already logged in. This can happen in the - # case of auto-authenticating protocols like SSH or - # webclient's session sharing - account = _AccountDB.objects.get_account_from_uid(sess.uid) - if account: - # this will set account.is_connected too - self.login(sess, account, force=True) - return - else: - sess.logged_in = False - sess.uid = None - - # show the first login command, may delay slightly to allow - # the handshakes to finish. - delay(_DELAY_CMD_LOGINSTART, self._run_cmd_login, sess)
- -
[docs] def portal_session_sync(self, portalsessiondata): - """ - Called by Portal when it wants to update a single session (e.g. - because of all negotiation protocols have finally replied) - - Args: - portalsessiondata (dict): a dictionary of all property:value - keys defining the session and which is marked to be - synced. - - """ - sessid = portalsessiondata.get("sessid") - session = self.get(sessid) - if session: - # since some of the session properties may have had - # a chance to change already before the portal gets here - # the portal doesn't send all sessiondata but only - # ones which should only be changed from portal (like - # protocol_flags etc) - session.load_sync_data(portalsessiondata)
- -
[docs] def portal_sessions_sync(self, portalsessionsdata): - """ - Syncing all session ids of the portal with the ones of the - server. This is instantiated by the portal when reconnecting. - - Args: - portalsessionsdata (dict): A dictionary - `{sessid: {property:value},...}` defining each session and - the properties in it which should be synced. - - """ - delayed_import() - global _ServerSession, _AccountDB, _ServerConfig, _ScriptDB - - for sess in list(self.values()): - # we delete the old session to make sure to catch eventual - # lingering references. - del sess - - for sessid, sessdict in portalsessionsdata.items(): - sess = _ServerSession() - sess.sessionhandler = self - sess.load_sync_data(sessdict) - if sess.uid: - sess.account = _AccountDB.objects.get_account_from_uid(sess.uid) - self[sessid] = sess - sess.at_sync() - - mode = "reload" - - # tell the server hook we synced - self.server.at_post_portal_sync(mode) - # announce the reconnection - self.announce_all(_(" ... Server restarted."))
- -
[docs] def portal_disconnect(self, session): - """ - Called from Portal when Portal session closed from the portal - side. There is no message to report in this case. - - Args: - session (Session): The Session to disconnect - - """ - # disconnect us without calling Portal since - # Portal already knows. - self.disconnect(session, reason="", sync_portal=False)
- -
[docs] def portal_disconnect_all(self): - """ - Called from Portal when Portal is closing down. All - Sessions should die. The Portal should not be informed. - - """ - # set a watchdog to avoid self.disconnect from deleting - # the session while we are looping over them - self._disconnect_all = True - for session in self.values(): - session.disconnect() - del self._disconnect_all
- - # server-side access methods - -
[docs] def start_bot_session(self, protocol_path, configdict): - """ - This method allows the server-side to force the Portal to - create a new bot session. - - Args: - protocol_path (str): The full python path to the bot's - class. - configdict (dict): This dict will be used to configure - the bot (this depends on the bot protocol). - - Examples: - start_bot_session("evennia.server.portal.irc.IRCClient", - {"uid":1, "botname":"evbot", "channel":"#evennia", - "network:"irc.freenode.net", "port": 6667}) - - Notes: - The new session will use the supplied account-bot uid to - initiate an already logged-in connection. The Portal will - treat this as a normal connection and henceforth so will - the Server. - - """ - self.server.amp_protocol.send_AdminServer2Portal( - DUMMYSESSION, operation=SCONN, protocol_path=protocol_path, config=configdict - )
- -
[docs] def portal_restart_server(self): - """ - Called by server when reloading. We tell the portal to start a new server instance. - - """ - self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=SRELOAD)
- -
[docs] def portal_reset_server(self): - """ - Called by server when reloading. We tell the portal to start a new server instance. - - """ - self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=SRESET)
- -
[docs] def portal_shutdown(self): - """ - Called by server when it's time to shut down (the portal will shut us down and then shut - itself down) - - """ - self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=PSHUTD)
- -
[docs] def login(self, session, account, force=False, testmode=False): - """ - Log in the previously unloggedin session and the account we by - now should know is connected to it. After this point we assume - the session to be logged in one way or another. - - Args: - session (Session): The Session to authenticate. - account (Account): The Account identified as associated with this Session. - force (bool): Login also if the session thinks it's already logged in - (this can happen for auto-authenticating protocols) - testmode (bool, optional): This is used by unittesting for - faking login without any AMP being actually active. - - """ - if session.logged_in and not force: - # don't log in a session that is already logged in. - return - - account.is_connected = True - - # sets up and assigns all properties on the session - session.at_login(account) - - # account init - account.at_init() - - # Check if this is the first time the *account* logs in - if account.db.FIRST_LOGIN: - account.at_first_login() - del account.db.FIRST_LOGIN - - account.at_pre_login() - - if _MULTISESSION_MODE == 0: - # disconnect all previous sessions. - self.disconnect_duplicate_sessions(session) - - nsess = len(self.sessions_from_account(account)) - string = "Logged in: {account} {address} ({nsessions} session(s) total)" - string = string.format(account=account, address=session.address, nsessions=nsess) - session.log(string) - session.logged_in = True - # sync the portal to the session - if not testmode: - self.server.amp_protocol.send_AdminServer2Portal( - session, operation=SLOGIN, sessiondata={"logged_in": True, "uid": session.uid} - ) - account.at_post_login(session=session) - if nsess < 2: - SIGNAL_ACCOUNT_POST_FIRST_LOGIN.send(sender=account, session=session) - SIGNAL_ACCOUNT_POST_LOGIN.send(sender=account, session=session)
- -
[docs] def disconnect(self, session, reason="", sync_portal=True): - """ - Called from server side to remove session and inform portal - of this fact. - - Args: - session (Session): The Session to disconnect. - reason (str, optional): A motivation for the disconnect. - sync_portal (bool, optional): Sync the disconnect to - Portal side. This should be done unless this was - called by self.portal_disconnect(). - - """ - session = self.get(session.sessid) - if not session: - return - - if hasattr(session, "account") and session.account: - # only log accounts logging off - nsess = len(self.sessions_from_account(session.account)) - 1 - sreason = " ({})".format(reason) if reason else "" - string = "Logged out: {account} {address} ({nsessions} sessions(s) remaining){reason}" - string = string.format( - reason=sreason, account=session.account, address=session.address, nsessions=nsess - ) - session.log(string) - - if nsess == 0: - SIGNAL_ACCOUNT_POST_LAST_LOGOUT.send(sender=session.account, session=session) - - session.at_disconnect(reason) - SIGNAL_ACCOUNT_POST_LOGOUT.send(sender=session.account, session=session) - sessid = session.sessid - if sessid in self and not hasattr(self, "_disconnect_all"): - del self[sessid] - if sync_portal: - # inform portal that session should be closed. - self.server.amp_protocol.send_AdminServer2Portal( - session, operation=SDISCONN, reason=reason - )
- -
[docs] def all_sessions_portal_sync(self): - """ - This is called by the server when it reboots. It syncs all session data - to the portal. Returns a deferred! - - """ - sessdata = self.get_all_sync_data() - return self.server.amp_protocol.send_AdminServer2Portal( - DUMMYSESSION, operation=SSYNC, sessiondata=sessdata - )
- -
[docs] def session_portal_sync(self, session): - """ - This is called by the server when it wants to sync a single session - with the Portal for whatever reason. Returns a deferred! - - """ - sessdata = {session.sessid: session.get_sync_data()} - return self.server.amp_protocol.send_AdminServer2Portal( - DUMMYSESSION, operation=SSYNC, sessiondata=sessdata, clean=False - )
- -
[docs] def session_portal_partial_sync(self, session_data): - """ - Call to make a partial update of the session, such as only a particular property. - - Args: - session_data (dict): Store `{sessid: {property:value}, ...}` defining one or - more sessions in detail. - - """ - return self.server.amp_protocol.send_AdminServer2Portal( - DUMMYSESSION, operation=SSYNC, sessiondata=session_data, clean=False - )
- -
[docs] def disconnect_all_sessions(self, reason="You have been disconnected."): - """ - Cleanly disconnect all of the connected sessions. - - Args: - reason (str, optional): The reason for the disconnection. - - """ - - for session in self: - del session - # tell portal to disconnect all sessions - self.server.amp_protocol.send_AdminServer2Portal( - DUMMYSESSION, operation=SDISCONNALL, reason=reason - )
- -
[docs] def disconnect_duplicate_sessions( - self, curr_session, reason=_("Logged in from elsewhere. Disconnecting.") - ): - """ - Disconnects any existing sessions with the same user. - - args: - curr_session (Session): Disconnect all Sessions matching this one. - reason (str, optional): A motivation for disconnecting. - - """ - uid = curr_session.uid - # we can't compare sessions directly since this will compare addresses and - # mean connecting from the same host would not catch duplicates - sid = id(curr_session) - doublet_sessions = [ - sess for sess in self.values() if sess.logged_in and sess.uid == uid and id(sess) != sid - ] - - for session in doublet_sessions: - self.disconnect(session, reason)
- -
[docs] def validate_sessions(self): - """ - Check all currently connected sessions (logged in and not) and - see if any are dead or idle. - - """ - tcurr = time.time() - reason = _("Idle timeout exceeded, disconnecting.") - for session in ( - session - for session in self.values() - if session.logged_in - and _IDLE_TIMEOUT > 0 - and (tcurr - session.cmd_last) > _IDLE_TIMEOUT - ): - self.disconnect(session, reason=reason)
- -
[docs] def account_count(self): - """ - Get the number of connected accounts (not sessions since a - account may have more than one session depending on settings). - Only logged-in accounts are counted here. - - Returns: - naccount (int): Number of connected accounts - - """ - return len(set(session.uid for session in self.values() if session.logged_in))
- -
[docs] def all_connected_accounts(self): - """ - Get a unique list of connected and logged-in Accounts. - - Returns: - accounts (list): All conected Accounts (which may be fewer than the - amount of Sessions due to multi-playing). - - """ - return list( - set( - session.account - for session in self.values() - if session.logged_in and session.account - ) - )
- -
[docs] def session_from_sessid(self, sessid): - """ - Get session based on sessid, or None if not found - - Args: - sessid (int or list): Session id(s). - - Return: - sessions (Session or list): Session(s) found. This - is a list if input was a list. - - """ - if is_iter(sessid): - return [self.get(sid) for sid in sessid if sid in self] - return self.get(sessid)
- -
[docs] def session_from_account(self, account, sessid): - """ - Given an account and a session id, return the actual session - object. - - Args: - account (Account): The Account to get the Session from. - sessid (int or list): Session id(s). - - Returns: - sessions (Session or list): Session(s) found. - - """ - sessions = [ - self[sid] - for sid in make_iter(sessid) - if sid in self and self[sid].logged_in and account.uid == self[sid].uid - ] - return sessions[0] if len(sessions) == 1 else sessions
- -
[docs] def sessions_from_account(self, account): - """ - Given an account, return all matching sessions. - - Args: - account (Account): Account to get sessions from. - - Returns: - sessions (list): All Sessions associated with this account. - - """ - uid = account.uid - return [session for session in self.values() if session.logged_in and session.uid == uid]
- -
[docs] def sessions_from_puppet(self, puppet): - """ - Given a puppeted object, return all controlling sessions. - - Args: - puppet (Object): Object puppeted - - Returns. - sessions (Session or list): Can be more than one of Object is controlled by - more than one Session (MULTISESSION_MODE > 1). - - """ - sessions = puppet.sessid.get() - return sessions[0] if len(sessions) == 1 else sessions
- - sessions_from_character = sessions_from_puppet - -
[docs] def sessions_from_csessid(self, csessid): - """ - Given a client identification hash (for session types that offer them) - return all sessions with a matching hash. - - Args: - csessid (str): The session hash. - Returns: - sessions (list): The sessions with matching .csessid, if any. - - """ - if csessid: - return [] - return [ - session for session in self.values() if session.csessid and session.csessid == csessid - ]
- -
[docs] def announce_all(self, message): - """ - Send message to all connected sessions - - Args: - message (str): Message to send. - - """ - for session in self.values(): - self.data_out(session, text=message)
- -
[docs] def data_out(self, session, **kwargs): - """ - Sending data Server -> Portal - - Args: - session (Session): Session to relay to. - text (str, optional): text data to return - - Notes: - The outdata will be scrubbed for sending across - the wire here. - """ - # clean output for sending - kwargs = self.clean_senddata(session, kwargs) - - # send across AMP - self.server.amp_protocol.send_MsgServer2Portal(session, **kwargs)
- -
[docs] def get_inputfuncs(self): - """ - Get all registered inputfuncs (access function) - - Returns: - inputfuncs (dict): A dict of {key:inputfunc,...} - """ - return _INPUT_FUNCS
- -
[docs] def data_in(self, session, **kwargs): - """ - We let the data take a "detour" to session.data_in - so the user can override and see it all in one place. - That method is responsible to in turn always call - this class' `sessionhandler.call_inputfunc` with the - (possibly processed) data. - - """ - if session: - session.data_in(**kwargs)
- -
[docs] def call_inputfuncs(self, session, **kwargs): - """ - Split incoming data into its inputfunc counterparts. - This should be called by the serversession.data_in - as `sessionhandler.call_inputfunc(self, **kwargs)`. - - We also intercept OOB communication here. - - Args: - sessions (Session): Session. - - Keyword Args: - kwargs (any): Incoming data from protocol on - the form `{"commandname": ((args), {kwargs}),...}` - - """ - - # distribute incoming data to the correct receiving methods. - if session: - input_debug = session.protocol_flags.get("INPUTDEBUG", False) - for cmdname, (cmdargs, cmdkwargs) in kwargs.items(): - cname = cmdname.strip().lower() - try: - cmdkwargs.pop("options", None) - if cname in _INPUT_FUNCS: - _INPUT_FUNCS[cname](session, *cmdargs, **cmdkwargs) - else: - _INPUT_FUNCS["default"](session, cname, *cmdargs, **cmdkwargs) - except Exception as err: - if input_debug: - session.msg(err) - log_trace()
- - -SESSION_HANDLER = ServerSessionHandler() -SESSIONS = SESSION_HANDLER # legacy -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/throttle.html b/docs/0.9.5/_modules/evennia/server/throttle.html deleted file mode 100644 index 2cb990c5bf..0000000000 --- a/docs/0.9.5/_modules/evennia/server/throttle.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - - evennia.server.throttle — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.throttle

-from collections import defaultdict, deque
-from evennia.utils import logger
-import time
-
-
-
[docs]class Throttle(object): - """ - Keeps a running count of failed actions per IP address. - - Available methods indicate whether or not the number of failures exceeds a - particular threshold. - - This version of the throttle is usable by both the terminal server as well - as the web server, imposes limits on memory consumption by using deques - with length limits instead of open-ended lists, and removes sparse keys when - no recent failures have been recorded. - """ - - error_msg = "Too many failed attempts; you must wait a few minutes before trying again." - -
[docs] def __init__(self, **kwargs): - """ - Allows setting of throttle parameters. - - Keyword Args: - limit (int): Max number of failures before imposing limiter - timeout (int): number of timeout seconds after - max number of tries has been reached. - cache_size (int): Max number of attempts to record per IP within a - rolling window; this is NOT the same as the limit after which - the throttle is imposed! - """ - self.storage = defaultdict(deque) - self.cache_size = self.limit = kwargs.get("limit", 5) - self.timeout = kwargs.get("timeout", 5 * 60)
- -
[docs] def get(self, ip=None): - """ - Convenience function that returns the storage table, or part of. - - Args: - ip (str, optional): IP address of requestor - - Returns: - storage (dict): When no IP is provided, returns a dict of all - current IPs being tracked and the timestamps of their recent - failures. - timestamps (deque): When an IP is provided, returns a deque of - timestamps of recent failures only for that IP. - - """ - if ip: - return self.storage.get(ip, deque(maxlen=self.cache_size)) - else: - return self.storage
- -
[docs] def update(self, ip, failmsg="Exceeded threshold."): - """ - Store the time of the latest failure. - - Args: - ip (str): IP address of requestor - failmsg (str, optional): Message to display in logs upon activation - of throttle. - - Returns: - None - - """ - # Get current status - previously_throttled = self.check(ip) - - # Enforce length limits - if not self.storage[ip].maxlen: - self.storage[ip] = deque(maxlen=self.cache_size) - - self.storage[ip].append(time.time()) - - # See if this update caused a change in status - currently_throttled = self.check(ip) - - # If this makes it engage, log a single activation event - if not previously_throttled and currently_throttled: - logger.log_sec( - "Throttle Activated: %s (IP: %s, %i hits in %i seconds.)" - % (failmsg, ip, self.limit, self.timeout) - )
- -
[docs] def check(self, ip): - """ - This will check the session's address against the - storage dictionary to check they haven't spammed too many - fails recently. - - Args: - ip (str): IP address of requestor - - Returns: - throttled (bool): True if throttling is active, - False otherwise. - - """ - now = time.time() - ip = str(ip) - - # checking mode - latest_fails = self.storage[ip] - if latest_fails and len(latest_fails) >= self.limit: - # too many fails recently - if now - latest_fails[-1] < self.timeout: - # too soon - timeout in play - return True - else: - # timeout has passed. clear faillist - del self.storage[ip] - return False - else: - return False
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/validators.html b/docs/0.9.5/_modules/evennia/server/validators.html deleted file mode 100644 index fb1e28c16c..0000000000 --- a/docs/0.9.5/_modules/evennia/server/validators.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - evennia.server.validators — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.validators

-from django.conf import settings
-from django.core.exceptions import ValidationError
-from django.utils.translation import gettext as _
-from evennia.accounts.models import AccountDB
-import re
-
-
-
[docs]class EvenniaUsernameAvailabilityValidator: - """ - Checks to make sure a given username is not taken or otherwise reserved. - """ - - def __call__(self, username): - """ - Validates a username to make sure it is not in use or reserved. - - Args: - username (str): Username to validate - - Returns: - None (None): None if password successfully validated, - raises ValidationError otherwise. - - """ - - # Check guest list - if settings.GUEST_LIST and username.lower() in ( - guest.lower() for guest in settings.GUEST_LIST - ): - raise ValidationError( - _("Sorry, that username is reserved."), code="evennia_username_reserved" - ) - - # Check database - exists = AccountDB.objects.filter(username__iexact=username).exists() - if exists: - raise ValidationError( - _("Sorry, that username is already taken."), code="evennia_username_taken" - )
- - -
[docs]class EvenniaPasswordValidator: -
[docs] def __init__( - self, - regex=r"^[\w. @+\-',]+$", - policy="Password should contain a mix of letters, " - "spaces, digits and @/./+/-/_/'/, only.", - ): - """ - Constructs a standard Django password validator. - - Args: - regex (str): Regex pattern of valid characters to allow. - policy (str): Brief explanation of what the defined regex permits. - - """ - self.regex = regex - self.policy = policy
- -
[docs] def validate(self, password, user=None): - """ - Validates a password string to make sure it meets predefined Evennia - acceptable character policy. - - Args: - password (str): Password to validate - user (None): Unused argument but required by Django - - Returns: - None (None): None if password successfully validated, - raises ValidationError otherwise. - - """ - # Check complexity - if not re.findall(self.regex, password): - raise ValidationError(_(self.policy), code="evennia_password_policy")
- -
[docs] def get_help_text(self): - """ - Returns a user-facing explanation of the password policy defined - by this validator. - - Returns: - text (str): Explanation of password policy. - - """ - return _( - "%s From a terminal client, you can also use a phrase of multiple words if " - "you enclose the password in double quotes." % self.policy - )
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/server/webserver.html b/docs/0.9.5/_modules/evennia/server/webserver.html deleted file mode 100644 index 1b80cffac7..0000000000 --- a/docs/0.9.5/_modules/evennia/server/webserver.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - - - evennia.server.webserver — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.server.webserver

-"""
-This implements resources for Twisted webservers using the WSGI
-interface of Django. This alleviates the need of running e.g. an
-Apache server to serve Evennia's web presence (although you could do
-that too if desired).
-
-The actual servers are started inside server.py as part of the Evennia
-application.
-
-(Lots of thanks to http://github.com/clemesha/twisted-wsgi-django for
-a great example/aid on how to do this.)
-
-
-"""
-import urllib.parse
-from urllib.parse import quote as urlquote
-from twisted.web import resource, http, server, static
-from twisted.internet import reactor
-from twisted.application import internet
-from twisted.web.proxy import ReverseProxyResource
-from twisted.web.server import NOT_DONE_YET
-from twisted.python import threadpool
-from twisted.internet import defer
-
-from twisted.web.wsgi import WSGIResource
-from django.conf import settings
-from django.core.wsgi import get_wsgi_application
-
-
-from evennia.utils import logger
-
-_UPSTREAM_IPS = settings.UPSTREAM_IPS
-_DEBUG = settings.DEBUG
-
-
-
[docs]class LockableThreadPool(threadpool.ThreadPool): - """ - Threadpool that can be locked from accepting new requests. - """ - -
[docs] def __init__(self, *args, **kwargs): - self._accept_new = True - threadpool.ThreadPool.__init__(self, *args, **kwargs)
- -
[docs] def lock(self): - self._accept_new = False
- -
[docs] def callInThread(self, func, *args, **kwargs): - """ - called in the main reactor thread. Makes sure the pool - is not locked before continuing. - """ - if self._accept_new: - threadpool.ThreadPool.callInThread(self, func, *args, **kwargs)
- - -# -# X-Forwarded-For Handler -# - - -
[docs]class HTTPChannelWithXForwardedFor(http.HTTPChannel): - """ - HTTP xforward class - - """ - -
[docs] def allHeadersReceived(self): - """ - Check to see if this is a reverse proxied connection. - - """ - if self.requests: - CLIENT = 0 - http.HTTPChannel.allHeadersReceived(self) - req = self.requests[-1] - client_ip, port = self.transport.client - proxy_chain = req.getHeader("X-FORWARDED-FOR") - if proxy_chain and client_ip in _UPSTREAM_IPS: - forwarded = proxy_chain.split(", ", 1)[CLIENT] - self.transport.client = (forwarded, port)
- - -# Monkey-patch Twisted to handle X-Forwarded-For. - -http.HTTPFactory.protocol = HTTPChannelWithXForwardedFor - - -
[docs]class EvenniaReverseProxyResource(ReverseProxyResource): -
[docs] def getChild(self, path, request): - """ - Create and return a proxy resource with the same proxy configuration - as this one, except that its path also contains the segment given by - path at the end. - - Args: - path (str): Url path. - request (Request object): Incoming request. - - Return: - resource (EvenniaReverseProxyResource): A proxy resource. - - """ - request.notifyFinish().addErrback( - lambda f: 0 - # lambda f: logger.log_trace("%s\nCaught errback in webserver.py" % f) - ) - return EvenniaReverseProxyResource( - self.host, self.port, self.path + "/" + urlquote(path, safe=""), self.reactor - )
- -
[docs] def render(self, request): - """ - Render a request by forwarding it to the proxied server. - - Args: - request (Request): Incoming request. - - Returns: - not_done (char): Indicator to note request not yet finished. - - """ - # RFC 2616 tells us that we can omit the port if it's the default port, - # but we have to provide it otherwise - request.content.seek(0, 0) - qs = urllib.parse.urlparse(request.uri)[4] - if qs: - rest = self.path + "?" + qs.decode() - else: - rest = self.path - rest = rest.encode() - clientFactory = self.proxyClientFactoryClass( - request.method, - rest, - request.clientproto, - request.getAllHeaders(), - request.content.read(), - request, - ) - clientFactory.noisy = False - self.reactor.connectTCP(self.host, self.port, clientFactory) - # don't trigger traceback if connection is lost before request finish. - request.notifyFinish().addErrback(lambda f: 0) - # request.notifyFinish().addErrback( - # lambda f:logger.log_trace("Caught errback in webserver.py: %s" % f) - return NOT_DONE_YET
- - -# -# Website server resource -# - - -
[docs]class DjangoWebRoot(resource.Resource): - """ - This creates a web root (/) that Django - understands by tweaking the way - child instances are recognized. - """ - -
[docs] def __init__(self, pool): - """ - Setup the django+twisted resource. - - Args: - pool (ThreadPool): The twisted threadpool. - - """ - self.pool = pool - self._echo_log = True - self._pending_requests = {} - super().__init__() - self.wsgi_resource = WSGIResource(reactor, pool, get_wsgi_application())
- -
[docs] def empty_threadpool(self): - """ - Converts our _pending_requests list of deferreds into a DeferredList - - Returns: - deflist (DeferredList): Contains all deferreds of pending requests. - - """ - self.pool.lock() - if self._pending_requests and self._echo_log: - self._echo_log = False # just to avoid multiple echoes - msg = "Webserver waiting for %i requests ... " - logger.log_info(msg % len(self._pending_requests)) - return defer.DeferredList(self._pending_requests, consumeErrors=True)
- - def _decrement_requests(self, *args, **kwargs): - self._pending_requests.pop(kwargs.get("deferred", None), None) - -
[docs] def getChild(self, path, request): - """ - To make things work we nudge the url tree to make this the - root. - - Args: - path (str): Url path. - request (Request object): Incoming request. - - Notes: - We make sure to save the request queue so - that we can safely kill the threadpool - on a server reload. - - """ - path0 = request.prepath.pop(0) - request.postpath.insert(0, path0) - - request.notifyFinish().addErrback( - lambda f: 0 - # lambda f: logger.log_trace("%s\nCaught errback in webserver.py:" % f) - ) - - deferred = request.notifyFinish() - self._pending_requests[deferred] = deferred - deferred.addBoth(self._decrement_requests, deferred=deferred) - - return self.wsgi_resource
- - -# -# Site with deactivateable logging -# - - -
[docs]class Website(server.Site): - """ - This class will only log http requests if settings.DEBUG is True. - """ - - noisy = False - -
[docs] def logPrefix(self): - "How to be named in logs" - if hasattr(self, "is_portal") and self.is_portal: - return "Webserver-proxy" - return "Webserver"
- -
[docs] def log(self, request): - """Conditional logging""" - if _DEBUG: - server.Site.log(self, request)
- - -# -# Threaded Webserver -# - - -
[docs]class WSGIWebServer(internet.TCPServer): - """ - This is a WSGI webserver. It makes sure to start - the threadpool after the service itself started, - so as to register correctly with the twisted daemon. - - call with WSGIWebServer(threadpool, port, wsgi_resource) - - """ - -
[docs] def __init__(self, pool, *args, **kwargs): - """ - This just stores the threadpool. - - Args: - pool (ThreadPool): The twisted threadpool. - args, kwargs (any): Passed on to the TCPServer. - - """ - self.pool = pool - super().__init__(*args, **kwargs)
- -
[docs] def startService(self): - """ - Start the pool after the service starts. - - """ - super().startService() - self.pool.start()
- -
[docs] def stopService(self): - """ - Safely stop the pool after the service stops. - - """ - super().stopService() - self.pool.stop()
- - -
[docs]class PrivateStaticRoot(static.File): - """ - This overrides the default static file resource so as to not make the - directory listings public (that is, if you go to /media or /static you - won't see an index of all static/media files on the server). - - """ - -
[docs] def directoryListing(self): - return resource.ForbiddenResource()
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/typeclasses/admin.html b/docs/0.9.5/_modules/evennia/typeclasses/admin.html deleted file mode 100644 index 07ff40d2e0..0000000000 --- a/docs/0.9.5/_modules/evennia/typeclasses/admin.html +++ /dev/null @@ -1,450 +0,0 @@ - - - - - - - - evennia.typeclasses.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.typeclasses.admin

-import traceback
-from datetime import datetime
-from django.contrib import admin
-from evennia.typeclasses.models import Tag
-from django import forms
-from evennia.utils.picklefield import PickledFormField
-from evennia.utils.dbserialize import from_pickle, _SaverSet
-
-
-
[docs]class TagAdmin(admin.ModelAdmin): - """ - A django Admin wrapper for Tags. - """ - - search_fields = ("db_key", "db_category", "db_tagtype") - list_display = ("db_key", "db_category", "db_tagtype", "db_data") - fields = ("db_key", "db_category", "db_tagtype", "db_data") - list_filter = ("db_tagtype",)
- - -
[docs]class TagForm(forms.ModelForm): - """ - This form overrides the base behavior of the ModelForm that would be used for a - Tag-through-model. Since the through-models only have access to the foreignkeys of the Tag and - the Object that they're attached to, we need to spoof the behavior of it being a form that would - correspond to its tag, or the creation of a tag. Instead of being saved, we'll call to the - Object's handler, which will handle the creation, change, or deletion of a tag for us, as well - as updating the handler's cache so that all changes are instantly updated in-game. - """ - - tag_key = forms.CharField( - label="Tag Name", required=True, help_text="This is the main key identifier" - ) - tag_category = forms.CharField( - label="Category", - help_text="Used for grouping tags. Unset (default) gives a category of None", - required=False, - ) - tag_type = forms.CharField( - label="Type", - help_text='Internal use. Either unset, "alias" or "permission"', - required=False, - ) - tag_data = forms.CharField( - label="Data", - help_text="Usually unused. Intended for eventual info about the tag itself", - required=False, - ) - -
[docs] class Meta: - fields = ("tag_key", "tag_category", "tag_data", "tag_type")
- -
[docs] def __init__(self, *args, **kwargs): - """ - If we have a tag, then we'll prepopulate our instance with the fields we'd expect it - to have based on the tag. tag_key, tag_category, tag_type, and tag_data all refer to - the corresponding tag fields. The initial data of the form fields will similarly be - populated. - """ - super().__init__(*args, **kwargs) - tagkey = None - tagcategory = None - tagtype = None - tagdata = None - if hasattr(self.instance, "tag"): - tagkey = self.instance.tag.db_key - tagcategory = self.instance.tag.db_category - tagtype = self.instance.tag.db_tagtype - tagdata = self.instance.tag.db_data - self.fields["tag_key"].initial = tagkey - self.fields["tag_category"].initial = tagcategory - self.fields["tag_type"].initial = tagtype - self.fields["tag_data"].initial = tagdata - self.instance.tag_key = tagkey - self.instance.tag_category = tagcategory - self.instance.tag_type = tagtype - self.instance.tag_data = tagdata
- -
[docs] def save(self, commit=True): - """ - One thing we want to do here is the or None checks, because forms are saved with an empty - string rather than null from forms, usually, and the Handlers may handle empty strings - differently than None objects. So for consistency with how things are handled in game, - we'll try to make sure that empty form fields will be None, rather than ''. - """ - # we are spoofing a tag for the Handler that will be called - # instance = super().save(commit=False) - instance = self.instance - instance.tag_key = self.cleaned_data["tag_key"] - instance.tag_category = self.cleaned_data["tag_category"] or None - instance.tag_type = self.cleaned_data["tag_type"] or None - instance.tag_data = self.cleaned_data["tag_data"] or None - return instance
- - -
[docs]class TagFormSet(forms.BaseInlineFormSet): - """ - The Formset handles all the inline forms that are grouped together on the change page of the - corresponding object. All the tags will appear here, and we'll save them by overriding the - formset's save method. The forms will similarly spoof their save methods to return an instance - which hasn't been saved to the database, but have the relevant fields filled out based on the - contents of the cleaned form. We'll then use that to call to the handler of the corresponding - Object, where the handler is an AliasHandler, PermissionsHandler, or TagHandler, based on the - type of tag. - """ - -
[docs] def save(self, commit=True): - def get_handler(finished_object): - related = getattr(finished_object, self.related_field) - try: - tagtype = finished_object.tag_type - except AttributeError: - tagtype = finished_object.tag.db_tagtype - if tagtype == "alias": - handler_name = "aliases" - elif tagtype == "permission": - handler_name = "permissions" - else: - handler_name = "tags" - return getattr(related, handler_name) - - instances = super().save(commit=False) - # self.deleted_objects is a list created when super of save is called, we'll remove those - for obj in self.deleted_objects: - handler = get_handler(obj) - handler.remove(obj.tag_key, category=obj.tag_category) - for instance in instances: - handler = get_handler(instance) - handler.add(instance.tag_key, category=instance.tag_category, data=instance.tag_data)
- - -
[docs]class TagInline(admin.TabularInline): - """ - A handler for inline Tags. This class should be subclassed in the admin of your models, - and the 'model' and 'related_field' class attributes must be set. model should be the - through model (ObjectDB_db_tag', for example), while related field should be the name - of the field on that through model which points to the model being used: 'objectdb', - 'msg', 'accountdb', etc. - """ - - # Set this to the through model of your desired M2M when subclassing. - model = None - form = TagForm - formset = TagFormSet - related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing - # raw_id_fields = ('tag',) - # readonly_fields = ('tag',) - extra = 0 - -
[docs] def get_formset(self, request, obj=None, **kwargs): - """ - get_formset has to return a class, but we need to make the class that we return - know about the related_field that we'll use. Returning the class itself rather than - a proxy isn't threadsafe, since it'd be the base class and would change if multiple - people used the admin at the same time - """ - formset = super().get_formset(request, obj, **kwargs) - - class ProxyFormset(formset): - pass - - ProxyFormset.related_field = self.related_field - return ProxyFormset
- - -
[docs]class AttributeForm(forms.ModelForm): - """ - This form overrides the base behavior of the ModelForm that would be used for a Attribute-through-model. - Since the through-models only have access to the foreignkeys of the Attribute and the Object that they're - attached to, we need to spoof the behavior of it being a form that would correspond to its Attribute, - or the creation of an Attribute. Instead of being saved, we'll call to the Object's handler, which will handle - the creation, change, or deletion of an Attribute for us, as well as updating the handler's cache so that all - changes are instantly updated in-game. - """ - - attr_key = forms.CharField( - label="Attribute Name", required=False, initial="Enter Attribute Name Here" - ) - attr_category = forms.CharField( - label="Category", help_text="type of attribute, for sorting", required=False, max_length=128 - ) - attr_value = PickledFormField(label="Value", help_text="Value to pickle/save", required=False) - attr_type = forms.CharField( - label="Type", - help_text='Internal use. Either unset (normal Attribute) or "nick"', - required=False, - max_length=16, - ) - attr_lockstring = forms.CharField( - label="Locks", - required=False, - help_text="Lock string on the form locktype:lockdef;lockfunc:lockdef;...", - widget=forms.Textarea(attrs={"rows": 1, "cols": 8}), - ) - -
[docs] class Meta: - fields = ("attr_key", "attr_value", "attr_category", "attr_lockstring", "attr_type")
- -
[docs] def __init__(self, *args, **kwargs): - """ - If we have an Attribute, then we'll prepopulate our instance with the fields we'd expect it - to have based on the Attribute. attr_key, attr_category, attr_value, attr_type, - and attr_lockstring all refer to the corresponding Attribute fields. The initial data of the form fields will - similarly be populated. - - """ - super().__init__(*args, **kwargs) - attr_key = None - attr_category = None - attr_value = None - attr_type = None - attr_lockstring = None - if hasattr(self.instance, "attribute"): - attr_key = self.instance.attribute.db_key - attr_category = self.instance.attribute.db_category - attr_value = self.instance.attribute.db_value - attr_type = self.instance.attribute.db_attrtype - attr_lockstring = self.instance.attribute.db_lock_storage - self.fields["attr_key"].initial = attr_key - self.fields["attr_category"].initial = attr_category - self.fields["attr_type"].initial = attr_type - self.fields["attr_value"].initial = attr_value - self.fields["attr_lockstring"].initial = attr_lockstring - self.instance.attr_key = attr_key - self.instance.attr_category = attr_category - self.instance.attr_value = attr_value - - # prevent from being transformed to str - if isinstance(attr_value, (set, _SaverSet)): - self.fields["attr_value"].disabled = True - - self.instance.deserialized_value = from_pickle(attr_value) - self.instance.attr_type = attr_type - self.instance.attr_lockstring = attr_lockstring
- -
[docs] def save(self, commit=True): - """ - One thing we want to do here is the or None checks, because forms are saved with an empty - string rather than null from forms, usually, and the Handlers may handle empty strings - differently than None objects. So for consistency with how things are handled in game, - we'll try to make sure that empty form fields will be None, rather than ''. - """ - # we are spoofing an Attribute for the Handler that will be called - instance = self.instance - instance.attr_key = self.cleaned_data["attr_key"] or "no_name_entered_for_attribute" - instance.attr_category = self.cleaned_data["attr_category"] or None - instance.attr_value = self.cleaned_data["attr_value"] - # convert the serialized string value into an object, if necessary, for AttributeHandler - instance.attr_value = from_pickle(instance.attr_value) - instance.attr_type = self.cleaned_data["attr_type"] or None - instance.attr_lockstring = self.cleaned_data["attr_lockstring"] - return instance
- -
[docs] def clean_attr_value(self): - """ - Prevent certain data-types from being cleaned due to literal_eval - failing on them. Otherwise they will be turned into str. - - """ - data = self.cleaned_data["attr_value"] - initial = self.instance.attr_value - if isinstance(initial, (set, _SaverSet, datetime)): - return initial - return data
- - -
[docs]class AttributeFormSet(forms.BaseInlineFormSet): - """ - Attribute version of TagFormSet, as above. - """ - -
[docs] def save(self, commit=True): - def get_handler(finished_object): - related = getattr(finished_object, self.related_field) - try: - attrtype = finished_object.attr_type - except AttributeError: - attrtype = finished_object.attribute.db_attrtype - if attrtype == "nick": - handler_name = "nicks" - else: - handler_name = "attributes" - return getattr(related, handler_name) - - instances = super().save(commit=False) - for obj in self.deleted_objects: - # self.deleted_objects is a list created when super of save is called, we'll remove those - handler = get_handler(obj) - handler.remove(obj.attr_key, category=obj.attr_category) - - for instance in instances: - handler = get_handler(instance) - - value = instance.attr_value - - try: - handler.add( - instance.attr_key, - value, - category=instance.attr_category, - strattr=False, - lockstring=instance.attr_lockstring, - ) - except (TypeError, ValueError): - # catch errors in nick templates and continue - traceback.print_exc() - continue
- - -
[docs]class AttributeInline(admin.TabularInline): - """ - A handler for inline Attributes. This class should be subclassed in the admin of your models, - and the 'model' and 'related_field' class attributes must be set. model should be the - through model (ObjectDB_db_tag', for example), while related field should be the name - of the field on that through model which points to the model being used: 'objectdb', - 'msg', 'accountdb', etc. - """ - - # Set this to the through model of your desired M2M when subclassing. - model = None - form = AttributeForm - formset = AttributeFormSet - related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing - # raw_id_fields = ('attribute',) - # readonly_fields = ('attribute',) - extra = 0 - -
[docs] def get_formset(self, request, obj=None, **kwargs): - """ - get_formset has to return a class, but we need to make the class that we return - know about the related_field that we'll use. Returning the class itself rather than - a proxy isn't threadsafe, since it'd be the base class and would change if multiple - people used the admin at the same time - """ - formset = super().get_formset(request, obj, **kwargs) - - class ProxyFormset(formset): - pass - - ProxyFormset.related_field = self.related_field - return ProxyFormset
- - -admin.site.register(Tag, TagAdmin) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/typeclasses/attributes.html b/docs/0.9.5/_modules/evennia/typeclasses/attributes.html deleted file mode 100644 index 9532fc027f..0000000000 --- a/docs/0.9.5/_modules/evennia/typeclasses/attributes.html +++ /dev/null @@ -1,1237 +0,0 @@ - - - - - - - - evennia.typeclasses.attributes — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.typeclasses.attributes

-"""
-Attributes are arbitrary data stored on objects. Attributes supports
-both pure-string values and pickled arbitrary data.
-
-Attributes are also used to implement Nicks. This module also contains
-the Attribute- and NickHandlers as well as the `NAttributeHandler`,
-which is a non-db version of Attributes.
-
-
-"""
-import re
-import fnmatch
-import weakref
-
-from django.db import models
-from django.conf import settings
-from django.utils.encoding import smart_str
-
-from evennia.locks.lockhandler import LockHandler
-from evennia.utils.idmapper.models import SharedMemoryModel
-from evennia.utils.dbserialize import to_pickle, from_pickle
-from evennia.utils.picklefield import PickledObjectField
-from evennia.utils.utils import lazy_property, to_str, make_iter, is_iter
-
-_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
-
-# -------------------------------------------------------------
-#
-#   Attributes
-#
-# -------------------------------------------------------------
-
-
-
[docs]class Attribute(SharedMemoryModel): - """ - Attributes are things that are specific to different types of objects. For - example, a drink container needs to store its fill level, whereas an exit - needs to store its open/closed/locked/unlocked state. These are done via - attributes, rather than making different classes for each object type and - storing them directly. The added benefit is that we can add/remove - attributes on the fly as we like. - - The Attribute class defines the following properties: - - key (str): Primary identifier. - - lock_storage (str): Perm strings. - - model (str): A string defining the model this is connected to. This - is a natural_key, like "objects.objectdb" - - date_created (datetime): When the attribute was created. - - value (any): The data stored in the attribute, in pickled form - using wrappers to be able to store/retrieve models. - - strvalue (str): String-only data. This data is not pickled and - is thus faster to search for in the database. - - category (str): Optional character string for grouping the - Attribute. - - """ - - # - # Attribute Database Model setup - # - # These database fields are all set using their corresponding properties, - # named same as the field, but without the db_* prefix. - db_key = models.CharField("key", max_length=255, db_index=True) - db_value = PickledObjectField( - "value", - null=True, - help_text="The data returned when the attribute is accessed. Must be " - "written as a Python literal if editing through the admin " - "interface. Attribute values which are not Python literals " - "cannot be edited through the admin interface.", - ) - db_strvalue = models.TextField( - "strvalue", null=True, blank=True, help_text="String-specific storage for quick look-up" - ) - db_category = models.CharField( - "category", - max_length=128, - db_index=True, - blank=True, - null=True, - help_text="Optional categorization of attribute.", - ) - # Lock storage - db_lock_storage = models.TextField( - "locks", blank=True, help_text="Lockstrings for this object are stored here." - ) - db_model = models.CharField( - "model", - max_length=32, - db_index=True, - blank=True, - null=True, - help_text="Which model of object this attribute is attached to (A " - "natural key like 'objects.objectdb'). You should not change " - "this value unless you know what you are doing.", - ) - # subclass of Attribute (None or nick) - db_attrtype = models.CharField( - "attrtype", - max_length=16, - db_index=True, - blank=True, - null=True, - help_text="Subclass of Attribute (None or nick)", - ) - # time stamp - db_date_created = models.DateTimeField("date_created", editable=False, auto_now_add=True) - - # Database manager - # objects = managers.AttributeManager() - -
[docs] @lazy_property - def locks(self): - return LockHandler(self)
- - class Meta(object): - "Define Django meta options" - verbose_name = "Evennia Attribute" - - # read-only wrappers - key = property(lambda self: self.db_key) - strvalue = property(lambda self: self.db_strvalue) - category = property(lambda self: self.db_category) - model = property(lambda self: self.db_model) - attrtype = property(lambda self: self.db_attrtype) - date_created = property(lambda self: self.db_date_created) - - def __lock_storage_get(self): - return self.db_lock_storage - - def __lock_storage_set(self, value): - self.db_lock_storage = value - self.save(update_fields=["db_lock_storage"]) - - def __lock_storage_del(self): - self.db_lock_storage = "" - self.save(update_fields=["db_lock_storage"]) - - lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del) - - # Wrapper properties to easily set database fields. These are - # @property decorators that allows to access these fields using - # normal python operations (without having to remember to save() - # etc). So e.g. a property 'attr' has a get/set/del decorator - # defined that allows the user to do self.attr = value, - # value = self.attr and del self.attr respectively (where self - # is the object in question). - - # value property (wraps db_value) - # @property - def __value_get(self): - """ - Getter. Allows for `value = self.value`. - We cannot cache here since it makes certain cases (such - as storing a dbobj which is then deleted elsewhere) out-of-sync. - The overhead of unpickling seems hard to avoid. - """ - return from_pickle(self.db_value, db_obj=self) - - # @value.setter - def __value_set(self, new_value): - """ - Setter. Allows for self.value = value. We cannot cache here, - see self.__value_get. - """ - self.db_value = to_pickle(new_value) - # print("value_set, self.db_value:", repr(self.db_value)) # DEBUG - self.save(update_fields=["db_value"]) - - # @value.deleter - def __value_del(self): - """Deleter. Allows for del attr.value. This removes the entire attribute.""" - self.delete() - - value = property(__value_get, __value_set, __value_del) - - # - # - # Attribute methods - # - # - - def __str__(self): - return smart_str("%s[category=%s](#%s)" % (self.db_key, self.db_category, self.id)) - - def __repr__(self): - return "%s[category=%s](#%s)" % (self.db_key, self.db_category, self.id) - -
[docs] def access(self, accessing_obj, access_type="attrread", default=False, **kwargs): - """ - Determines if another object has permission to access. - - Args: - accessing_obj (object): Entity trying to access this one. - access_type (str, optional): Type of access sought, see - the lock documentation. - default (bool, optional): What result to return if no lock - of access_type was found. The default, `False`, means a lockdown - policy, only allowing explicit access. - kwargs (any, optional): Not used; here to make the API consistent with - other access calls. - - Returns: - result (bool): If the lock was passed or not. - - """ - result = self.locks.check(accessing_obj, access_type=access_type, default=default) - return result
- - -# -# Handlers making use of the Attribute model -# - - -
[docs]class AttributeHandler(object): - """ - Handler for adding Attributes to the object. - """ - - _m2m_fieldname = "db_attributes" - _attrcreate = "attrcreate" - _attredit = "attredit" - _attrread = "attrread" - _attrtype = None - -
[docs] def __init__(self, obj): - """Initialize handler.""" - self.obj = obj - self._objid = obj.id - self._model = to_str(obj.__dbclass__.__name__.lower()) - self._cache = {} - # store category names fully cached - self._catcache = {} - # full cache was run on all attributes - self._cache_complete = False
- - def _query_all(self): - "Fetch all Attributes on this object" - query = { - "%s__id" % self._model: self._objid, - "attribute__db_model__iexact": self._model, - "attribute__db_attrtype": self._attrtype, - } - return [ - conn.attribute - for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query) - ] - - def _fullcache(self): - """Cache all attributes of this object""" - if not _TYPECLASS_AGGRESSIVE_CACHE: - return - attrs = self._query_all() - self._cache = dict( - ( - "%s-%s" - % ( - to_str(attr.db_key).lower(), - attr.db_category.lower() if attr.db_category is not None else None, - ), - attr, - ) - for attr in attrs - ) - self._cache_complete = True - - def _getcache(self, key=None, category=None): - """ - Retrieve from cache or database (always caches) - - Args: - key (str, optional): Attribute key to query for - category (str, optional): Attribiute category - - Returns: - args (list): Returns a list of zero or more matches - found from cache or database. - Notes: - When given a category only, a search for all objects - of that cateogory is done and the category *name* is - stored. This tells the system on subsequent calls that the - list of cached attributes of this category is up-to-date - and that the cache can be queried for category matches - without missing any. - The TYPECLASS_AGGRESSIVE_CACHE=False setting will turn off - caching, causing each attribute access to trigger a - database lookup. - - """ - key = key.strip().lower() if key else None - category = category.strip().lower() if category is not None else None - if key: - cachekey = "%s-%s" % (key, category) - cachefound = False - try: - attr = _TYPECLASS_AGGRESSIVE_CACHE and self._cache[cachekey] - cachefound = True - except KeyError: - attr = None - - if attr and (not hasattr(attr, "pk") and attr.pk is None): - # clear out Attributes deleted from elsewhere. We must search this anew. - attr = None - cachefound = False - del self._cache[cachekey] - if cachefound and _TYPECLASS_AGGRESSIVE_CACHE: - if attr: - return [attr] # return cached entity - else: - return [] # no such attribute: return an empty list - else: - query = { - "%s__id" % self._model: self._objid, - "attribute__db_model__iexact": self._model, - "attribute__db_attrtype": self._attrtype, - "attribute__db_key__iexact": key.lower(), - "attribute__db_category__iexact": category.lower() if category else None, - } - if not self.obj.pk: - return [] - conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query) - if conn: - attr = conn[0].attribute - if _TYPECLASS_AGGRESSIVE_CACHE: - self._cache[cachekey] = attr - return [attr] if attr.pk else [] - else: - # There is no such attribute. We will explicitly save that - # in our cache to avoid firing another query if we try to - # retrieve that (non-existent) attribute again. - if _TYPECLASS_AGGRESSIVE_CACHE: - self._cache[cachekey] = None - return [] - else: - # only category given (even if it's None) - we can't - # assume the cache to be complete unless we have queried - # for this category before - catkey = "-%s" % category - if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache: - return [attr for key, attr in self._cache.items() if key.endswith(catkey) and attr] - else: - # we have to query to make this category up-date in the cache - query = { - "%s__id" % self._model: self._objid, - "attribute__db_model__iexact": self._model, - "attribute__db_attrtype": self._attrtype, - "attribute__db_category__iexact": category.lower() if category else None, - } - attrs = [ - conn.attribute - for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter( - **query - ) - ] - if _TYPECLASS_AGGRESSIVE_CACHE: - for attr in attrs: - if attr.pk: - cachekey = "%s-%s" % (attr.db_key, category) - self._cache[cachekey] = attr - # mark category cache as up-to-date - self._catcache[catkey] = True - return attrs - - def _setcache(self, key, category, attr_obj): - """ - Update cache. - - Args: - key (str): A cleaned key string - category (str or None): A cleaned category name - attr_obj (Attribute): The newly saved attribute - - """ - if not _TYPECLASS_AGGRESSIVE_CACHE: - return - if not key: # don't allow an empty key in cache - return - cachekey = "%s-%s" % (key, category) - catkey = "-%s" % category - self._cache[cachekey] = attr_obj - # mark that the category cache is no longer up-to-date - self._catcache.pop(catkey, None) - self._cache_complete = False - - def _delcache(self, key, category): - """ - Remove attribute from cache - - Args: - key (str): A cleaned key string - category (str or None): A cleaned category name - - """ - catkey = "-%s" % category - if key: - cachekey = "%s-%s" % (key, category) - self._cache.pop(cachekey, None) - else: - self._cache = { - key: attrobj - for key, attrobj in list(self._cache.items()) - if not key.endswith(catkey) - } - # mark that the category cache is no longer up-to-date - self._catcache.pop(catkey, None) - self._cache_complete = False - -
[docs] def reset_cache(self): - """ - Reset cache from the outside. - """ - self._cache_complete = False - self._cache = {} - self._catcache = {}
- -
[docs] def has(self, key=None, category=None): - """ - Checks if the given Attribute (or list of Attributes) exists on - the object. - - Args: - key (str or iterable): The Attribute key or keys to check for. - If `None`, search by category. - category (str or None): Limit the check to Attributes with this - category (note, that `None` is the default category). - - Returns: - has_attribute (bool or list): If the Attribute exists on - this object or not. If `key` was given as an iterable then - the return is a list of booleans. - - """ - ret = [] - category = category.strip().lower() if category is not None else None - for keystr in make_iter(key): - keystr = key.strip().lower() - ret.extend(bool(attr) for attr in self._getcache(keystr, category)) - return ret[0] if len(ret) == 1 else ret
- -
[docs] def get( - self, - key=None, - default=None, - category=None, - return_obj=False, - strattr=False, - raise_exception=False, - accessing_obj=None, - default_access=True, - return_list=False, - ): - """ - Get the Attribute. - - Args: - key (str or list, optional): the attribute identifier or - multiple attributes to get. if a list of keys, the - method will return a list. - category (str, optional): the category within which to - retrieve attribute(s). - default (any, optional): The value to return if an - Attribute was not defined. If set, it will be returned in - a one-item list. - return_obj (bool, optional): If set, the return is not the value of the - Attribute but the Attribute object itself. - strattr (bool, optional): Return the `strvalue` field of - the Attribute rather than the usual `value`, this is a - string-only value for quick database searches. - raise_exception (bool, optional): When an Attribute is not - found, the return from this is usually `default`. If this - is set, an exception is raised instead. - accessing_obj (object, optional): If set, an `attrread` - permission lock will be checked before returning each - looked-after Attribute. - default_access (bool, optional): If no `attrread` lock is set on - object, this determines if the lock should then be passed or not. - return_list (bool, optional): - - Returns: - result (any or list): One or more matches for keys and/or - categories. Each match will be the value of the found Attribute(s) - unless `return_obj` is True, at which point it will be the - attribute object itself or None. If `return_list` is True, this - will always be a list, regardless of the number of elements. - - Raises: - AttributeError: If `raise_exception` is set and no matching Attribute - was found matching `key`. - - """ - - ret = [] - for keystr in make_iter(key): - # it's okay to send a None key - attr_objs = self._getcache(keystr, category) - if attr_objs: - ret.extend(attr_objs) - elif raise_exception: - raise AttributeError - elif return_obj: - ret.append(None) - - if accessing_obj: - # check 'attrread' locks - ret = [ - attr - for attr in ret - if attr.access(accessing_obj, self._attrread, default=default_access) - ] - if strattr: - ret = ret if return_obj else [attr.strvalue for attr in ret if attr] - else: - ret = ret if return_obj else [attr.value for attr in ret if attr] - - if return_list: - return ret if ret else [default] if default is not None else [] - return ret[0] if ret and len(ret) == 1 else ret or default
- -
[docs] def add( - self, - key, - value, - category=None, - lockstring="", - strattr=False, - accessing_obj=None, - default_access=True, - ): - """ - Add attribute to object, with optional `lockstring`. - - Args: - key (str): An Attribute name to add. - value (any or str): The value of the Attribute. If - `strattr` keyword is set, this *must* be a string. - category (str, optional): The category for the Attribute. - The default `None` is the normal category used. - lockstring (str, optional): A lock string limiting access - to the attribute. - strattr (bool, optional): Make this a string-only Attribute. - This is only ever useful for optimization purposes. - accessing_obj (object, optional): An entity to check for - the `attrcreate` access-type. If not passing, this method - will be exited. - default_access (bool, optional): What access to grant if - `accessing_obj` is given but no lock of the type - `attrcreate` is defined on the Attribute in question. - - """ - if accessing_obj and not self.obj.access( - accessing_obj, self._attrcreate, default=default_access - ): - # check create access - return - - if not key: - return - - category = category.strip().lower() if category is not None else None - - keystr = key.strip().lower() - attr_obj = self._getcache(key, category) - - if attr_obj: - # update an existing attribute object - attr_obj = attr_obj[0] - if strattr: - # store as a simple string (will not notify OOB handlers) - attr_obj.db_strvalue = value - attr_obj.save(update_fields=["db_strvalue"]) - else: - # store normally (this will also notify OOB handlers) - attr_obj.value = value - else: - # create a new Attribute (no OOB handlers can be notified) - kwargs = { - "db_key": keystr, - "db_category": category, - "db_model": self._model, - "db_attrtype": self._attrtype, - "db_value": None if strattr else to_pickle(value), - "db_strvalue": value if strattr else None, - } - new_attr = Attribute(**kwargs) - new_attr.save() - getattr(self.obj, self._m2m_fieldname).add(new_attr) - # update cache - self._setcache(keystr, category, new_attr)
- -
[docs] def batch_add(self, *args, **kwargs): - """ - Batch-version of `add()`. This is more efficient than - repeat-calling add when having many Attributes to add. - - Args: - *args (tuple): Each argument should be a tuples (can be of varying - length) representing the Attribute to add to this object. - Supported tuples are - - - `(key, value)` - - `(key, value, category)` - - `(key, value, category, lockstring)` - - `(key, value, category, lockstring, default_access)` - - Keyword Args: - strattr (bool): If `True`, value must be a string. This - will save the value without pickling which is less - flexible but faster to search (not often used except - internally). - - Raises: - RuntimeError: If trying to pass a non-iterable as argument. - - Notes: - The indata tuple order matters, so if you want a lockstring - but no category, set the category to `None`. This method - does not have the ability to check editing permissions like - normal .add does, and is mainly used internally. It does not - use the normal self.add but apply the Attributes directly - to the database. - - """ - new_attrobjs = [] - strattr = kwargs.get("strattr", False) - for tup in args: - if not is_iter(tup) or len(tup) < 2: - raise RuntimeError("batch_add requires iterables as arguments (got %r)." % tup) - ntup = len(tup) - keystr = str(tup[0]).strip().lower() - new_value = tup[1] - category = str(tup[2]).strip().lower() if ntup > 2 and tup[2] is not None else None - lockstring = tup[3] if ntup > 3 else "" - - attr_objs = self._getcache(keystr, category) - - if attr_objs: - attr_obj = attr_objs[0] - # update an existing attribute object - attr_obj.db_category = category - attr_obj.db_lock_storage = lockstring or "" - attr_obj.save(update_fields=["db_category", "db_lock_storage"]) - if strattr: - # store as a simple string (will not notify OOB handlers) - attr_obj.db_strvalue = new_value - attr_obj.save(update_fields=["db_strvalue"]) - else: - # store normally (this will also notify OOB handlers) - attr_obj.value = new_value - else: - # create a new Attribute (no OOB handlers can be notified) - kwargs = { - "db_key": keystr, - "db_category": category, - "db_model": self._model, - "db_attrtype": self._attrtype, - "db_value": None if strattr else to_pickle(new_value), - "db_strvalue": new_value if strattr else None, - "db_lock_storage": lockstring or "", - } - new_attr = Attribute(**kwargs) - new_attr.save() - new_attrobjs.append(new_attr) - self._setcache(keystr, category, new_attr) - if new_attrobjs: - # Add new objects to m2m field all at once - getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs)
- -
[docs] def remove( - self, - key=None, - raise_exception=False, - category=None, - accessing_obj=None, - default_access=True, - ): - """ - Remove attribute or a list of attributes from object. - - Args: - key (str or list, optional): An Attribute key to remove or a list of keys. If - multiple keys, they must all be of the same `category`. If None and - category is not given, remove all Attributes. - raise_exception (bool, optional): If set, not finding the - Attribute to delete will raise an exception instead of - just quietly failing. - category (str, optional): The category within which to - remove the Attribute. - accessing_obj (object, optional): An object to check - against the `attredit` lock. If not given, the check will - be skipped. - default_access (bool, optional): The fallback access to - grant if `accessing_obj` is given but there is no - `attredit` lock set on the Attribute in question. - - Raises: - AttributeError: If `raise_exception` is set and no matching Attribute - was found matching `key`. - - Notes: - If neither key nor category is given, this acts as clear(). - - """ - - if key is None: - self.clear( - category=category, accessing_obj=accessing_obj, default_access=default_access - ) - return - - category = category.strip().lower() if category is not None else None - - for keystr in make_iter(key): - keystr = keystr.lower() - - attr_objs = self._getcache(keystr, category) - for attr_obj in attr_objs: - if not ( - accessing_obj - and not attr_obj.access(accessing_obj, self._attredit, default=default_access) - ): - try: - attr_obj.delete() - except AssertionError: - print("Assertionerror for attr.delete()") - # this happens if the attr was already deleted - pass - finally: - self._delcache(keystr, category) - if not attr_objs and raise_exception: - raise AttributeError
- -
[docs] def clear(self, category=None, accessing_obj=None, default_access=True): - """ - Remove all Attributes on this object. - - Args: - category (str, optional): If given, clear only Attributes - of this category. - accessing_obj (object, optional): If given, check the - `attredit` lock on each Attribute before continuing. - default_access (bool, optional): Use this permission as - fallback if `access_obj` is given but there is no lock of - type `attredit` on the Attribute in question. - - """ - category = category.strip().lower() if category is not None else None - - if not self._cache_complete: - self._fullcache() - - if category is not None: - attrs = [attr for attr in self._cache.values() if attr.category == category] - else: - attrs = self._cache.values() - - if accessing_obj: - [ - attr.delete() - for attr in attrs - if attr and attr.access(accessing_obj, self._attredit, default=default_access) - ] - else: - [attr.delete() for attr in attrs if attr and attr.pk] - self._cache = {} - self._catcache = {} - self._cache_complete = False
- -
[docs] def all(self, accessing_obj=None, default_access=True): - """ - Return all Attribute objects on this object, regardless of category. - - Args: - accessing_obj (object, optional): Check the `attrread` - lock on each attribute before returning them. If not - given, this check is skipped. - default_access (bool, optional): Use this permission as a - fallback if `accessing_obj` is given but one or more - Attributes has no lock of type `attrread` defined on them. - - Returns: - Attributes (list): All the Attribute objects (note: Not - their values!) in the handler. - - """ - if _TYPECLASS_AGGRESSIVE_CACHE: - if not self._cache_complete: - self._fullcache() - attrs = sorted([attr for attr in self._cache.values() if attr], key=lambda o: o.id) - else: - attrs = sorted([attr for attr in self._query_all() if attr], key=lambda o: o.id) - - if accessing_obj: - return [ - attr - for attr in attrs - if attr.access(accessing_obj, self._attredit, default=default_access) - ] - else: - return attrs
- - -# Nick templating -# - -""" -This supports the use of replacement templates in nicks: - -This happens in two steps: - -1) The user supplies a template that is converted to a regex according - to the unix-like templating language. -2) This regex is tested against nicks depending on which nick replacement - strategy is considered (most commonly inputline). -3) If there is a template match and there are templating markers, - these are replaced with the arguments actually given. - -@desc $1 $2 $3 - -This will be converted to the following regex: - -\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+) - -Supported template markers (through fnmatch) - * matches anything (non-greedy) -> .*? - ? matches any single character -> - [seq] matches any entry in sequence - [!seq] matches entries not in sequence -Custom arg markers - $N argument position (1-99) - -""" -_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)") -_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)") -_RE_NICK_SPACE = re.compile(r"\\ ") - - -
[docs]class NickTemplateInvalid(ValueError): - pass
- - -
[docs]def initialize_nick_templates(in_template, out_template): - """ - Initialize the nick templates for matching and remapping a string. - - Args: - in_template (str): The template to be used for nick recognition. - out_template (str): The template to be used to replace the string - matched by the in_template. - - Returns: - (regex, str): Regex to match against strings and a template - Template with markers `{arg1}`, `{arg2}`, etc for - replacement using the standard `.format` method. - - Raises: - attributes.NickTemplateInvalid: If the in/out template does not have a matching - number of $args. - - """ - - # create the regex for in_template - regex_string = fnmatch.translate(in_template) - # we must account for a possible line break coming over the wire - - # NOTE-PYTHON3: fnmatch.translate format changed since Python2 - regex_string = regex_string[:-2] + r"(?:[\n\r]*?)\Z" - - # validate the templates - regex_args = [match.group(2) for match in _RE_NICK_ARG.finditer(regex_string)] - temp_args = [match.group(2) for match in _RE_NICK_TEMPLATE_ARG.finditer(out_template)] - if set(regex_args) != set(temp_args): - # We don't have the same $-tags in input/output. - raise NickTemplateInvalid - - regex_string = _RE_NICK_SPACE.sub(r"\\s+", regex_string) - regex_string = _RE_NICK_ARG.sub(lambda m: "(?P<arg%s>.+?)" % m.group(2), regex_string) - template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template) - - return regex_string, template_string
- - -
[docs]def parse_nick_template(string, template_regex, outtemplate): - """ - Parse a text using a template and map it to another template - - Args: - string (str): The input string to processj - template_regex (regex): A template regex created with - initialize_nick_template. - outtemplate (str): The template to which to map the matches - produced by the template_regex. This should have $1, $2, - etc to match the regex. - - """ - match = template_regex.match(string) - if match: - return True, outtemplate.format(**match.groupdict()) - return False, string
- - -
[docs]class NickHandler(AttributeHandler): - """ - Handles the addition and removal of Nicks. Nicks are special - versions of Attributes with an `_attrtype` hardcoded to `nick`. - They also always use the `strvalue` fields for their data. - - """ - - _attrtype = "nick" - -
[docs] def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._regex_cache = {}
- -
[docs] def has(self, key, category="inputline"): - """ - Args: - key (str or iterable): The Nick key or keys to check for. - category (str): Limit the check to Nicks with this - category (note, that `None` is the default category). - - Returns: - has_nick (bool or list): If the Nick exists on this object - or not. If `key` was given as an iterable then the return - is a list of booleans. - - """ - return super().has(key, category=category)
- -
[docs] def get(self, key=None, category="inputline", return_tuple=False, **kwargs): - """ - Get the replacement value matching the given key and category - - Args: - key (str or list, optional): the attribute identifier or - multiple attributes to get. if a list of keys, the - method will return a list. - category (str, optional): the category within which to - retrieve the nick. The "inputline" means replacing data - sent by the user. - return_tuple (bool, optional): return the full nick tuple rather - than just the replacement. For non-template nicks this is just - a string. - kwargs (any, optional): These are passed on to `AttributeHandler.get`. - - """ - if return_tuple or "return_obj" in kwargs: - return super().get(key=key, category=category, **kwargs) - else: - retval = super().get(key=key, category=category, **kwargs) - if retval: - return ( - retval[3] - if isinstance(retval, tuple) - else [tup[3] for tup in make_iter(retval)] - ) - return None
- -
[docs] def add(self, key, replacement, category="inputline", **kwargs): - """ - Add a new nick. - - Args: - key (str): A key (or template) for the nick to match for. - replacement (str): The string (or template) to replace `key` with (the "nickname"). - category (str, optional): the category within which to - retrieve the nick. The "inputline" means replacing data - sent by the user. - kwargs (any, optional): These are passed on to `AttributeHandler.get`. - - """ - if category == "channel": - nick_regex, nick_template = initialize_nick_templates(key + " $1", replacement + " $1") - else: - nick_regex, nick_template = initialize_nick_templates(key, replacement) - super().add(key, (nick_regex, nick_template, key, replacement), category=category, **kwargs)
- -
[docs] def remove(self, key, category="inputline", **kwargs): - """ - Remove Nick with matching category. - - Args: - key (str): A key for the nick to match for. - category (str, optional): the category within which to - removethe nick. The "inputline" means replacing data - sent by the user. - kwargs (any, optional): These are passed on to `AttributeHandler.get`. - - """ - super().remove(key, category=category, **kwargs)
- -
[docs] def nickreplace(self, raw_string, categories=("inputline", "channel"), include_account=True): - """ - Apply nick replacement of entries in raw_string with nick replacement. - - Args: - raw_string (str): The string in which to perform nick - replacement. - categories (tuple, optional): Replacement categories in - which to perform the replacement, such as "inputline", - "channel" etc. - include_account (bool, optional): Also include replacement - with nicks stored on the Account level. - kwargs (any, optional): Not used. - - Returns: - string (str): A string with matching keys replaced with - their nick equivalents. - - """ - nicks = {} - for category in make_iter(categories): - nicks.update( - { - nick.key: nick - for nick in make_iter(self.get(category=category, return_obj=True)) - if nick and nick.key - } - ) - if include_account and self.obj.has_account: - for category in make_iter(categories): - nicks.update( - { - nick.key: nick - for nick in make_iter( - self.obj.account.nicks.get(category=category, return_obj=True) - ) - if nick and nick.key - } - ) - for key, nick in nicks.items(): - nick_regex, template, _, _ = nick.value - regex = self._regex_cache.get(nick_regex) - if not regex: - regex = re.compile(nick_regex, re.I + re.DOTALL + re.U) - self._regex_cache[nick_regex] = regex - - is_match, raw_string = parse_nick_template(raw_string.strip(), regex, template) - if is_match: - break - return raw_string
- - -
[docs]class NAttributeHandler(object): - """ - This stand-alone handler manages non-database saving. - It is similar to `AttributeHandler` and is used - by the `.ndb` handler in the same way as `.db` does - for the `AttributeHandler`. - """ - -
[docs] def __init__(self, obj): - """ - Initialized on the object - """ - self._store = {} - self.obj = weakref.proxy(obj)
- -
[docs] def has(self, key): - """ - Check if object has this attribute or not. - - Args: - key (str): The Nattribute key to check. - - Returns: - has_nattribute (bool): If Nattribute is set or not. - - """ - return key in self._store
- -
[docs] def get(self, key): - """ - Get the named key value. - - Args: - key (str): The Nattribute key to get. - - Returns: - the value of the Nattribute. - - """ - return self._store.get(key, None)
- -
[docs] def add(self, key, value): - """ - Add new key and value. - - Args: - key (str): The name of Nattribute to add. - value (any): The value to store. - - """ - self._store[key] = value
- -
[docs] def remove(self, key): - """ - Remove Nattribute from storage. - - Args: - key (str): The name of the Nattribute to remove. - - """ - if key in self._store: - del self._store[key]
- -
[docs] def clear(self): - """ - Remove all NAttributes from handler. - - """ - self._store = {}
- -
[docs] def all(self, return_tuples=False): - """ - List the contents of the handler. - - Args: - return_tuples (bool, optional): Defines if the Nattributes - are returns as a list of keys or as a list of `(key, value)`. - - Returns: - nattributes (list): A list of keys `[key, key, ...]` or a - list of tuples `[(key, value), ...]` depending on the - setting of `return_tuples`. - - """ - if return_tuples: - return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] - return [key for key in self._store if not str(key).startswith("_")]
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/typeclasses/managers.html b/docs/0.9.5/_modules/evennia/typeclasses/managers.html deleted file mode 100644 index 3a9f8e3656..0000000000 --- a/docs/0.9.5/_modules/evennia/typeclasses/managers.html +++ /dev/null @@ -1,956 +0,0 @@ - - - - - - - - evennia.typeclasses.managers — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.typeclasses.managers

-"""
-This implements the common managers that are used by the
-abstract models in dbobjects.py (and which are thus shared by
-all Attributes and TypedObjects).
-
-"""
-import shlex
-from django.db.models import F, Q, Count, ExpressionWrapper, FloatField
-from django.db.models.functions import Cast
-from evennia.utils import idmapper
-from evennia.utils.utils import make_iter, variable_from_module
-from evennia.typeclasses.attributes import Attribute
-from evennia.typeclasses.tags import Tag
-
-__all__ = ("TypedObjectManager",)
-_GA = object.__getattribute__
-_Tag = None
-
-
-# Managers
-
-
-
[docs]class TypedObjectManager(idmapper.manager.SharedMemoryManager): - """ - Common ObjectManager for all dbobjects. - - """ - - # common methods for all typed managers. These are used - # in other methods. Returns querysets. - - # Attribute manager methods -
[docs] def get_attribute( - self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None, **kwargs - ): - """ - Return Attribute objects by key, by category, by value, by - `strvalue`, by object (it is stored on) or with a combination of - those criteria. - - Args: - key (str, optional): The attribute's key to search for. - category (str, optional): The category of the attribute(s) - to search for. - value (str, optional): The attribute value to search for. - Note that this is not a very efficient operation since it - will query for a pickled entity. Mutually exclusive to - `strvalue`. - strvalue (str, optional): The str-value to search for. - Most Attributes will not have strvalue set. This is - mutually exclusive to the `value` keyword and will take - precedence if given. - obj (Object, optional): On which object the Attribute to - search for is. - attrtype (str, optional): An attribute-type to search for. - By default this is either `None` (normal Attributes) or - `"nick"`. - kwargs (any): Currently unused. Reserved for future use. - - Returns: - attributes (list): The matching Attributes. - - """ - dbmodel = self.model.__dbclass__.__name__.lower() - query = [("attribute__db_attrtype", attrtype), ("attribute__db_model", dbmodel)] - if obj: - query.append(("%s__id" % self.model.__dbclass__.__name__.lower(), obj.id)) - if key: - query.append(("attribute__db_key", key)) - if category: - query.append(("attribute__db_category", category)) - if strvalue: - query.append(("attribute__db_strvalue", strvalue)) - if value: - # no reason to make strvalue/value mutually exclusive at this level - query.append(("attribute__db_value", value)) - return Attribute.objects.filter( - pk__in=self.model.db_attributes.through.objects.filter(**dict(query)).values_list( - "attribute_id", flat=True - ) - )
- -
[docs] def get_nick(self, key=None, category=None, value=None, strvalue=None, obj=None): - """ - Get a nick, in parallel to `get_attribute`. - - Args: - key (str, optional): The nicks's key to search for - category (str, optional): The category of the nicks(s) to search for. - value (str, optional): The attribute value to search for. Note that this - is not a very efficient operation since it will query for a pickled - entity. Mutually exclusive to `strvalue`. - strvalue (str, optional): The str-value to search for. Most Attributes - will not have strvalue set. This is mutually exclusive to the `value` - keyword and will take precedence if given. - obj (Object, optional): On which object the Attribute to search for is. - - Returns: - nicks (list): The matching Nicks. - - """ - return self.get_attribute( - key=key, category=category, value=value, strvalue=strvalue, obj=obj - )
- -
[docs] def get_by_attribute( - self, key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs - ): - """ - Return objects having attributes with the given key, category, - value, strvalue or combination of those criteria. - - Args: - key (str, optional): The attribute's key to search for - category (str, optional): The category of the attribute - to search for. - value (str, optional): The attribute value to search for. - Note that this is not a very efficient operation since it - will query for a pickled entity. Mutually exclusive to - `strvalue`. - strvalue (str, optional): The str-value to search for. - Most Attributes will not have strvalue set. This is - mutually exclusive to the `value` keyword and will take - precedence if given. - attrype (str, optional): An attribute-type to search for. - By default this is either `None` (normal Attributes) or - `"nick"`. - kwargs (any): Currently unused. Reserved for future use. - - Returns: - obj (list): Objects having the matching Attributes. - - """ - dbmodel = self.model.__dbclass__.__name__.lower() - query = [ - ("db_attributes__db_attrtype", attrtype), - ("db_attributes__db_model", dbmodel), - ] - if key: - query.append(("db_attributes__db_key", key)) - if category: - query.append(("db_attributes__db_category", category)) - if strvalue: - query.append(("db_attributes__db_strvalue", strvalue)) - elif value: - # strvalue and value are mutually exclusive - query.append(("db_attributes__db_value", value)) - return self.filter(**dict(query))
- -
[docs] def get_by_nick(self, key=None, nick=None, category="inputline"): - """ - Get object based on its key or nick. - - Args: - key (str, optional): The attribute's key to search for - nick (str, optional): The nickname to search for - category (str, optional): The category of the nick - to search for. - - Returns: - obj (list): Objects having the matching Nicks. - - """ - return self.get_by_attribute(key=key, category=category, strvalue=nick, attrtype="nick")
- - # Tag manager methods - -
[docs] def get_tag(self, key=None, category=None, obj=None, tagtype=None, global_search=False): - """ - Return Tag objects by key, by category, by object (it is - stored on) or with a combination of those criteria. - - Args: - key (str, optional): The Tag's key to search for - category (str, optional): The Tag of the attribute(s) - to search for. - obj (Object, optional): On which object the Tag to - search for is. - tagtype (str, optional): One of None (normal tags), - "alias" or "permission" - global_search (bool, optional): Include all possible tags, - not just tags on this object - - Returns: - tag (list): The matching Tags. - - """ - global _Tag - if not _Tag: - from evennia.typeclasses.models import Tag as _Tag - dbmodel = self.model.__dbclass__.__name__.lower() - if global_search: - # search all tags using the Tag model - query = [("db_tagtype", tagtype), ("db_model", dbmodel)] - if obj: - query.append(("id", obj.id)) - if key: - query.append(("db_key", key)) - if category: - query.append(("db_category", category)) - return _Tag.objects.filter(**dict(query)) - else: - # search only among tags stored on on this model - query = [("tag__db_tagtype", tagtype), ("tag__db_model", dbmodel)] - if obj: - query.append(("%s__id" % self.model.__name__.lower(), obj.id)) - if key: - query.append(("tag__db_key", key)) - if category: - query.append(("tag__db_category", category)) - return Tag.objects.filter( - pk__in=self.model.db_tags.through.objects.filter(**dict(query)).values_list( - "tag_id", flat=True - ) - )
- -
[docs] def get_permission(self, key=None, category=None, obj=None): - """ - Get a permission from the database. - - Args: - key (str, optional): The permission's identifier. - category (str, optional): The permission's category. - obj (object, optional): The object on which this Tag is set. - - Returns: - permission (list): Permission objects. - - """ - return self.get_tag(key=key, category=category, obj=obj, tagtype="permission")
- -
[docs] def get_alias(self, key=None, category=None, obj=None): - """ - Get an alias from the database. - - Args: - key (str, optional): The permission's identifier. - category (str, optional): The permission's category. - obj (object, optional): The object on which this Tag is set. - - Returns: - alias (list): Alias objects. - - """ - return self.get_tag(key=key, category=category, obj=obj, tagtype="alias")
- -
[docs] def get_by_tag(self, key=None, category=None, tagtype=None, **kwargs): - """ - Return objects having tags with a given key or category or combination of the two. - Also accepts multiple tags/category/tagtype - - Args: - key (str or list, optional): Tag key or list of keys. Not case sensitive. - category (str or list, optional): Tag category. Not case sensitive. - If `key` is a list, a single category can either apply to all - keys in that list or this must be a list matching the `key` - list element by element. If no `key` is given, all objects with - tags of this category are returned. - tagtype (str, optional): 'type' of Tag, by default - this is either `None` (a normal Tag), `alias` or - `permission`. This always apply to all queried tags. - - Keyword Args: - match (str): "all" (default) or "any"; determines whether the - target object must be tagged with ALL of the provided - tags/categories or ANY single one. ANY will perform a weighted - sort, so objects with more tag matches will outrank those with - fewer tag matches. - - Returns: - objects (list): Objects with matching tag. - - Raises: - IndexError: If `key` and `category` are both lists and `category` is shorter - than `key`. - - """ - if not (key or category): - return [] - - global _Tag - if not _Tag: - from evennia.typeclasses.models import Tag as _Tag - - anymatch = "any" == kwargs.get("match", "all").lower().strip() - - keys = make_iter(key) if key else [] - categories = make_iter(category) if category else [] - n_keys = len(keys) - n_categories = len(categories) - unique_categories = sorted(set(categories)) - n_unique_categories = len(unique_categories) - - dbmodel = self.model.__dbclass__.__name__.lower() - query = ( - self.filter(db_tags__db_tagtype__iexact=tagtype, db_tags__db_model__iexact=dbmodel) - .distinct() - .order_by("id") - ) - - if n_keys > 0: - # keys and/or categories given - if n_categories == 0: - categories = [None for _ in range(n_keys)] - elif n_categories == 1 and n_keys > 1: - cat = categories[0] - categories = [cat for _ in range(n_keys)] - elif 1 < n_categories < n_keys: - raise IndexError( - "get_by_tag needs a single category or a list of categories " - "the same length as the list of tags." - ) - clauses = Q() - for ikey, key in enumerate(keys): - # ANY mode; must match any one of the given tags/categories - clauses |= Q(db_key__iexact=key, db_category__iexact=categories[ikey]) - else: - # only one or more categories given - clauses = Q() - # ANY mode; must match any one of them - for category in unique_categories: - clauses |= Q(db_category__iexact=category) - - tags = _Tag.objects.filter(clauses) - query = query.filter(db_tags__in=tags).annotate( - matches=Count("db_tags__pk", filter=Q(db_tags__in=tags), distinct=True) - ) - - if anymatch: - # ANY: Match any single tag, ordered by weight - query = query.order_by("-matches") - else: - # Default ALL: Match all of the tags and optionally more - n_req_tags = n_keys if n_keys > 0 else n_unique_categories - query = query.filter(matches__gte=n_req_tags) - - return query
- -
[docs] def get_by_permission(self, key=None, category=None): - """ - Return objects having permissions with a given key or category or - combination of the two. - - Args: - key (str, optional): Permissions key. Not case sensitive. - category (str, optional): Permission category. Not case sensitive. - Returns: - objects (list): Objects with matching permission. - """ - return self.get_by_tag(key=key, category=category, tagtype="permission")
- -
[docs] def get_by_alias(self, key=None, category=None): - """ - Return objects having aliases with a given key or category or - combination of the two. - - Args: - key (str, optional): Alias key. Not case sensitive. - category (str, optional): Alias category. Not case sensitive. - Returns: - objects (list): Objects with matching alias. - """ - return self.get_by_tag(key=key, category=category, tagtype="alias")
- -
[docs] def create_tag(self, key=None, category=None, data=None, tagtype=None): - """ - Create a new Tag of the base type associated with this - object. This makes sure to create case-insensitive tags. - If the exact same tag configuration (key+category+tagtype+dbmodel) - exists on the model, a new tag will not be created, but an old - one returned. - - - Args: - key (str, optional): Tag key. Not case sensitive. - category (str, optional): Tag category. Not case sensitive. - data (str, optional): Extra information about the tag. - tagtype (str or None, optional): 'type' of Tag, by default - this is either `None` (a normal Tag), `alias` or - `permission`. - Notes: - The `data` field is not part of the uniqueness of the tag: - Setting `data` on an existing tag will overwrite the old - data field. It is intended only as a way to carry - information about the tag (like a help text), not to carry - any information about the tagged objects themselves. - - """ - data = str(data) if data is not None else None - # try to get old tag - - dbmodel = self.model.__dbclass__.__name__.lower() - tag = self.get_tag(key=key, category=category, tagtype=tagtype, global_search=True) - if tag and data is not None: - # get tag from list returned by get_tag - tag = tag[0] - # overload data on tag - tag.db_data = data - tag.save() - elif not tag: - # create a new tag - global _Tag - if not _Tag: - from evennia.typeclasses.models import Tag as _Tag - tag = _Tag.objects.create( - db_key=key.strip().lower() if key is not None else None, - db_category=category.strip().lower() if category and key is not None else None, - db_data=data, - db_model=dbmodel, - db_tagtype=tagtype.strip().lower() if tagtype is not None else None, - ) - tag.save() - return make_iter(tag)[0]
- -
[docs] def dbref(self, dbref, reqhash=True): - """ - Determing if input is a valid dbref. - - Args: - dbref (str or int): A possible dbref. - reqhash (bool, optional): If the "#" is required for this - to be considered a valid hash. - - Returns: - dbref (int or None): The integer part of the dbref. - - Notes: - Valid forms of dbref (database reference number) are - either a string '#N' or an integer N. - - """ - if reqhash and not (isinstance(dbref, str) and dbref.startswith("#")): - return None - if isinstance(dbref, str): - dbref = dbref.lstrip("#") - try: - if int(dbref) < 0: - return None - except Exception: - return None - return dbref
- -
[docs] def get_id(self, dbref): - """ - Find object with given dbref. - - Args: - dbref (str or int): The id to search for. - - Returns: - object (TypedObject): The matched object. - - """ - dbref = self.dbref(dbref, reqhash=False) - try: - return self.get(id=dbref) - except self.model.DoesNotExist: - pass - return None
- - - -
[docs] def get_dbref_range(self, min_dbref=None, max_dbref=None): - """ - Get objects within a certain range of dbrefs. - - Args: - min_dbref (int): Start of dbref range. - max_dbref (int): End of dbref range (inclusive) - - Returns: - objects (list): TypedObjects with dbrefs within - the given dbref ranges. - - """ - retval = super().all() - if min_dbref is not None: - retval = retval.filter(id__gte=self.dbref(min_dbref, reqhash=False)) - if max_dbref is not None: - retval = retval.filter(id__lte=self.dbref(max_dbref, reqhash=False)) - return retval
- -
[docs] def get_typeclass_totals(self, *args, **kwargs) -> object: - """ - Returns a queryset of typeclass composition statistics. - - Returns: - qs (Queryset): A queryset of dicts containing the typeclass (name), - the count of objects with that typeclass and a float representing - the percentage of objects associated with the typeclass. - - """ - return ( - self.values("db_typeclass_path") - .distinct() - .annotate( - # Get count of how many objects for each typeclass exist - count=Count("db_typeclass_path") - ) - .annotate( - # Rename db_typeclass_path field to something more human - typeclass=F("db_typeclass_path"), - # Calculate this class' percentage of total composition - percent=ExpressionWrapper( - ((F("count") / float(self.count())) * 100.0), output_field=FloatField(), - ), - ) - .values("typeclass", "count", "percent") - )
- -
[docs] def object_totals(self): - """ - Get info about database statistics. - - Returns: - census (dict): A dictionary `{typeclass_path: number, ...}` with - all the typeclasses active in-game as well as the number - of such objects defined (i.e. the number of database - object having that typeclass set on themselves). - - """ - stats = self.get_typeclass_totals().order_by("typeclass") - return {x.get("typeclass"): x.get("count") for x in stats}
- -
- - -class TypeclassManager(TypedObjectManager): - """ - Manager for the typeclasses. The main purpose of this manager is - to limit database queries to the given typeclass despite all - typeclasses technically being defined in the same core database - model. - - """ - - # object-manager methods - def smart_search(self, query): - """ - Search by supplying a string with optional extra search criteria to aid the query. - - Args: - query (str): A search criteria that accepts extra search criteria on the - - following forms: [key|alias|#dbref...] [tag==<tagstr>[:category]...] [attr==<key>:<value>:category...] - " != " != " - Returns: - matches (queryset): A queryset result matching all queries exactly. If wanting to use spaces or - ==, != in tags or attributes, enclose them in quotes. - - Note: - The flexibility of this method is limited by the input line format. Tag/attribute - matching only works for matching primitives. For even more complex queries, such as - 'in' operations or object field matching, use the full django query language. - - """ - # shlex splits by spaces unless escaped by quotes - querysplit = shlex.split(query) - queries, plustags, plusattrs, negtags, negattrs = [], [], [], [], [] - for ipart, part in enumerate(querysplit): - key, rest = part, "" - if ":" in part: - key, rest = part.split(":", 1) - # tags are on the form tag or tag:category - if key.startswith("tag=="): - plustags.append((key[5:], rest)) - continue - elif key.startswith("tag!="): - negtags.append((key[5:], rest)) - continue - # attrs are on the form attr:value or attr:value:category - elif rest: - value, category = rest, "" - if ":" in rest: - value, category = rest.split(":", 1) - if key.startswith("attr=="): - plusattrs.append((key[7:], value, category)) - continue - elif key.startswith("attr!="): - negattrs.append((key[7:], value, category)) - continue - # if we get here, we are entering a key search criterion which - # we assume is one word. - queries.append(part) - # build query from components - query = " ".join(queries) - # TODO - - def get(self, *args, **kwargs): - """ - Overload the standard get. This will limit itself to only - return the current typeclass. - - Args: - args (any): These are passed on as arguments to the default - django get method. - Keyword Args: - kwargs (any): These are passed on as normal arguments - to the default django get method - Returns: - object (object): The object found. - - Raises: - ObjectNotFound: The exact name of this exception depends - on the model base used. - - """ - kwargs.update({"db_typeclass_path": self.model.path}) - return super().get(**kwargs) - - def filter(self, *args, **kwargs): - """ - Overload of the standard filter function. This filter will - limit itself to only the current typeclass. - - Args: - args (any): These are passed on as arguments to the default - django filter method. - Keyword Args: - kwargs (any): These are passed on as normal arguments - to the default django filter method. - Returns: - objects (queryset): The objects found. - - """ - kwargs.update({"db_typeclass_path": self.model.path}) - return super().filter(*args, **kwargs) - - def all(self): - """ - Overload method to return all matches, filtering for typeclass. - - Returns: - objects (queryset): The objects found. - - """ - return super().all().filter(db_typeclass_path=self.model.path) - - def first(self): - """ - Overload method to return first match, filtering for typeclass. - - Returns: - object (object): The object found. - - Raises: - ObjectNotFound: The exact name of this exception depends - on the model base used. - - """ - return super().filter(db_typeclass_path=self.model.path).first() - - def last(self): - """ - Overload method to return last match, filtering for typeclass. - - Returns: - object (object): The object found. - - Raises: - ObjectNotFound: The exact name of this exception depends - on the model base used. - - """ - return super().filter(db_typeclass_path=self.model.path).last() - - def count(self): - """ - Overload method to return number of matches, filtering for typeclass. - - Returns: - integer : Number of objects found. - - """ - return super().filter(db_typeclass_path=self.model.path).count() - - def annotate(self, *args, **kwargs): - """ - Overload annotate method to filter on typeclass before annotating. - Args: - *args (any): Positional arguments passed along to queryset annotate method. - **kwargs (any): Keyword arguments passed along to queryset annotate method. - - Returns: - Annotated queryset. - """ - return ( - super(TypeclassManager, self) - .filter(db_typeclass_path=self.model.path) - .annotate(*args, **kwargs) - ) - - def values(self, *args, **kwargs): - """ - Overload values method to filter on typeclass first. - Args: - *args (any): Positional arguments passed along to values method. - **kwargs (any): Keyword arguments passed along to values method. - - Returns: - Queryset of values dictionaries, just filtered by typeclass first. - """ - return ( - super(TypeclassManager, self) - .filter(db_typeclass_path=self.model.path) - .values(*args, **kwargs) - ) - - def values_list(self, *args, **kwargs): - """ - Overload values method to filter on typeclass first. - Args: - *args (any): Positional arguments passed along to values_list method. - **kwargs (any): Keyword arguments passed along to values_list method. - - Returns: - Queryset of value_list tuples, just filtered by typeclass first. - """ - return ( - super(TypeclassManager, self) - .filter(db_typeclass_path=self.model.path) - .values_list(*args, **kwargs) - ) - - def _get_subclasses(self, cls): - """ - Recursively get all subclasses to a class. - - Args: - cls (classoject): A class to get subclasses from. - """ - all_subclasses = cls.__subclasses__() - for subclass in all_subclasses: - all_subclasses.extend(self._get_subclasses(subclass)) - return all_subclasses - - def get_family(self, **kwargs): - """ - Variation of get that not only returns the current typeclass - but also all subclasses of that typeclass. - - Keyword Args: - kwargs (any): These are passed on as normal arguments - to the default django get method. - Returns: - objects (list): The objects found. - - Raises: - ObjectNotFound: The exact name of this exception depends - on the model base used. - - """ - paths = [self.model.path] + [ - "%s.%s" % (cls.__module__, cls.__name__) for cls in self._get_subclasses(self.model) - ] - kwargs.update({"db_typeclass_path__in": paths}) - return super().get(**kwargs) - - def filter_family(self, *args, **kwargs): - """ - Variation of filter that allows results both from typeclass - and from subclasses of typeclass - - Args: - args (any): These are passed on as arguments to the default - django filter method. - Keyword Args: - kwargs (any): These are passed on as normal arguments - to the default django filter method. - Returns: - objects (list): The objects found. - - """ - # query, including all subclasses - paths = [self.model.path] + [ - "%s.%s" % (cls.__module__, cls.__name__) for cls in self._get_subclasses(self.model) - ] - kwargs.update({"db_typeclass_path__in": paths}) - return super().filter(*args, **kwargs) - - def all_family(self): - """ - Return all matches, allowing matches from all subclasses of - the typeclass. - - Returns: - objects (list): The objects found. - - """ - paths = [self.model.path] + [ - "%s.%s" % (cls.__module__, cls.__name__) for cls in self._get_subclasses(self.model) - ] - return super().all().filter(db_typeclass_path__in=paths) -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/typeclasses/models.html b/docs/0.9.5/_modules/evennia/typeclasses/models.html deleted file mode 100644 index 7a093ede10..0000000000 --- a/docs/0.9.5/_modules/evennia/typeclasses/models.html +++ /dev/null @@ -1,1185 +0,0 @@ - - - - - - - - evennia.typeclasses.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.typeclasses.models

-"""
-This is the *abstract* django models for many of the database objects
-in Evennia. A django abstract (obs, not the same as a Python metaclass!) is
-a model which is not actually created in the database, but which only exists
-for other models to inherit from, to avoid code duplication. Any model can
-import and inherit from these classes.
-
-Attributes are database objects stored on other objects. The implementing
-class needs to supply a ForeignKey field attr_object pointing to the kind
-of object being mapped. Attributes storing iterables actually store special
-types of iterables named PackedList/PackedDict respectively. These make
-sure to save changes to them to database - this is criticial in order to
-allow for obj.db.mylist[2] = data. Also, all dbobjects are saved as
-dbrefs but are also aggressively cached.
-
-TypedObjects are objects 'decorated' with a typeclass - that is, the typeclass
-(which is a normal Python class implementing some special tricks with its
-get/set attribute methods, allows for the creation of all sorts of different
-objects all with the same database object underneath. Usually attributes are
-used to permanently store things not hard-coded as field on the database object.
-The admin should usually not have to deal directly  with the database object
-layer.
-
-This module also contains the Managers for the respective models; inherit from
-these to create custom managers.
-
-----
-
-"""
-from django.db.models import signals
-
-from django.db.models.base import ModelBase
-from django.db import models
-from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import ObjectDoesNotExist
-from django.conf import settings
-from django.urls import reverse
-from django.utils.encoding import smart_str
-from django.utils.text import slugify
-
-from evennia.typeclasses.attributes import Attribute, AttributeHandler, NAttributeHandler
-from evennia.typeclasses.tags import Tag, TagHandler, AliasHandler, PermissionHandler
-
-from evennia.utils.idmapper.models import SharedMemoryModel, SharedMemoryModelBase
-from evennia.server.signals import SIGNAL_TYPED_OBJECT_POST_RENAME
-
-from evennia.typeclasses import managers
-from evennia.locks.lockhandler import LockHandler
-from evennia.utils.utils import is_iter, inherits_from, lazy_property, class_from_module
-from evennia.utils.logger import log_trace
-
-__all__ = ("TypedObject",)
-
-TICKER_HANDLER = None
-
-_PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
-_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
-_GA = object.__getattribute__
-_SA = object.__setattr__
-
-
-# signal receivers. Connected in __new__
-
-
-def call_at_first_save(sender, instance, created, **kwargs):
-    """
-    Receives a signal just after the object is saved.
-    """
-    if created:
-        instance.at_first_save()
-
-
-def remove_attributes_on_delete(sender, instance, **kwargs):
-    """
-    Wipe object's Attributes when it's deleted
-    """
-    instance.db_attributes.all().delete()
-
-
-# ------------------------------------------------------------
-#
-# Typed Objects
-#
-# ------------------------------------------------------------
-
-
-#
-# Meta class for typeclasses
-#
-
-
-class TypeclassBase(SharedMemoryModelBase):
-    """
-    Metaclass which should be set for the root of model proxies
-    that don't define any new fields, like Object, Script etc. This
-    is the basis for the typeclassing system.
-    """
-
-    def __new__(cls, name, bases, attrs):
-        """
-        We must define our Typeclasses as proxies. We also store the
-        path directly on the class, this is required by managers.
-        """
-
-        # storage of stats
-        attrs["typename"] = name
-        attrs["path"] = "%s.%s" % (attrs["__module__"], name)
-
-        def _get_dbmodel(bases):
-            """Recursively get the dbmodel"""
-            if not hasattr(bases, "__iter__"):
-                bases = [bases]
-            for base in bases:
-                try:
-                    if base._meta.proxy or base._meta.abstract:
-                        for kls in base._meta.parents:
-                            return _get_dbmodel(kls)
-                except AttributeError:
-                    # this happens if trying to parse a non-typeclass mixin parent,
-                    # without a _meta
-                    continue
-                else:
-                    return base
-                return None
-
-        dbmodel = _get_dbmodel(bases)
-
-        if not dbmodel:
-            raise TypeError(f"{name} does not appear to inherit from a database model.")
-
-        # typeclass proxy setup
-        # first check explicit __applabel__ on the typeclass, then figure
-        # it out from the dbmodel
-        if "__applabel__" not in attrs:
-            # find the app-label in one of the bases, usually the dbmodel
-            attrs["__applabel__"] = dbmodel._meta.app_label
-
-        if "Meta" not in attrs:
-
-            class Meta:
-                proxy = True
-                app_label = attrs.get("__applabel__", "typeclasses")
-
-            attrs["Meta"] = Meta
-        attrs["Meta"].proxy = True
-
-        new_class = ModelBase.__new__(cls, name, bases, attrs)
-
-        # django doesn't support inheriting proxy models so we hack support for
-        # it here by injecting `proxy_for_model` to the actual dbmodel.
-        # Unfortunately we cannot also set the correct model_name, because this
-        # would block multiple-inheritance of typeclasses (Django doesn't allow
-        # multiple bases of the same model).
-        if dbmodel:
-            new_class._meta.proxy_for_model = dbmodel
-            # Maybe Django will eventually handle this in the future:
-            # new_class._meta.model_name = dbmodel._meta.model_name
-
-        # attach signals
-        signals.post_save.connect(call_at_first_save, sender=new_class)
-        signals.pre_delete.connect(remove_attributes_on_delete, sender=new_class)
-        return new_class
-
-
-class DbHolder(object):
-    """
-    Holder for allowing property access of attributes.
-
-    """
-
-    def __init__(self, obj, name, manager_name="attributes"):
-        _SA(self, name, _GA(obj, manager_name))
-        _SA(self, "name", name)
-
-    def __getattribute__(self, attrname):
-        if attrname == "all":
-            # we allow to overload our default .all
-            attr = _GA(self, _GA(self, "name")).get("all")
-            return attr if attr else _GA(self, "all")
-        return _GA(self, _GA(self, "name")).get(attrname)
-
-    def __setattr__(self, attrname, value):
-        _GA(self, _GA(self, "name")).add(attrname, value)
-
-    def __delattr__(self, attrname):
-        _GA(self, _GA(self, "name")).remove(attrname)
-
-    def get_all(self):
-        return _GA(self, _GA(self, "name")).all()
-
-    all = property(get_all)
-
-
-#
-# Main TypedObject abstraction
-#
-
-
-
[docs]class TypedObject(SharedMemoryModel): - """ - Abstract Django model. - - This is the basis for a typed object. It also contains all the - mechanics for managing connected attributes. - - The TypedObject has the following properties: - key - main name - name - alias for key - typeclass_path - the path to the decorating typeclass - typeclass - auto-linked typeclass - date_created - time stamp of object creation - permissions - perm strings - dbref - #id of object - db - persistent attribute storage - ndb - non-persistent attribute storage - - """ - - # - # TypedObject Database Model setup - # - # - # These databse fields are all accessed and set using their corresponding - # properties, named same as the field, but without the db_* prefix - # (no separate save() call is needed) - - # Main identifier of the object, for searching. Is accessed with self.key - # or self.name - db_key = models.CharField("key", max_length=255, db_index=True) - # This is the python path to the type class this object is tied to. The - # typeclass is what defines what kind of Object this is) - db_typeclass_path = models.CharField( - "typeclass", - max_length=255, - null=True, - help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", - db_index=True, - ) - # Creation date. This is not changed once the object is created. - db_date_created = models.DateTimeField("creation date", editable=False, auto_now_add=True) - # Lock storage - db_lock_storage = models.TextField( - "locks", - blank=True, - help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", - ) - # many2many relationships - db_attributes = models.ManyToManyField( - Attribute, - help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).", - ) - db_tags = models.ManyToManyField( - Tag, - help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.", - ) - - # Database manager - objects = managers.TypedObjectManager() - - # quick on-object typeclass cache for speed - _cached_typeclass = None - - # typeclass mechanism - -
[docs] def set_class_from_typeclass(self, typeclass_path=None): - if typeclass_path: - try: - self.__class__ = class_from_module( - typeclass_path, defaultpaths=settings.TYPECLASS_PATHS - ) - except Exception: - log_trace() - try: - self.__class__ = class_from_module(self.__settingsclasspath__) - except Exception: - log_trace() - try: - self.__class__ = class_from_module(self.__defaultclasspath__) - except Exception: - log_trace() - self.__class__ = self._meta.concrete_model or self.__class__ - finally: - self.db_typeclass_path = typeclass_path - elif self.db_typeclass_path: - try: - self.__class__ = class_from_module(self.db_typeclass_path) - except Exception: - log_trace() - try: - self.__class__ = class_from_module(self.__defaultclasspath__) - except Exception: - log_trace() - self.__dbclass__ = self._meta.concrete_model or self.__class__ - else: - self.db_typeclass_path = "%s.%s" % (self.__module__, self.__class__.__name__) - # important to put this at the end since _meta is based on the set __class__ - try: - self.__dbclass__ = self._meta.concrete_model or self.__class__ - except AttributeError: - err_class = repr(self.__class__) - self.__class__ = class_from_module("evennia.objects.objects.DefaultObject") - self.__dbclass__ = class_from_module("evennia.objects.models.ObjectDB") - self.db_typeclass_path = "evennia.objects.objects.DefaultObject" - log_trace( - "Critical: Class %s of %s is not a valid typeclass!\nTemporarily falling back to %s." - % (err_class, self, self.__class__) - )
- -
[docs] def __init__(self, *args, **kwargs): - """ - The `__init__` method of typeclasses is the core operational - code of the typeclass system, where it dynamically re-applies - a class based on the db_typeclass_path database field rather - than use the one in the model. - - Args: - *args: Passed through to parent. - **kwargs: Passed through to parent. - - Notes: - The loading mechanism will attempt the following steps: - - 1. Attempt to load typeclass given on command line - 2. Attempt to load typeclass stored in db_typeclass_path - 3. Attempt to load `__settingsclasspath__`, which is by the - default classes defined to be the respective user-set - base typeclass settings, like `BASE_OBJECT_TYPECLASS`. - 4. Attempt to load `__defaultclasspath__`, which is the - base classes in the library, like DefaultObject etc. - 5. If everything else fails, use the database model. - - Normal operation is to load successfully at either step 1 - or 2 depending on how the class was called. Tracebacks - will be logged for every step the loader must take beyond - 2. - - """ - typeclass_path = kwargs.pop("typeclass", None) - super().__init__(*args, **kwargs) - self.set_class_from_typeclass(typeclass_path=typeclass_path)
- - # initialize all handlers in a lazy fashion -
[docs] @lazy_property - def attributes(self): - return AttributeHandler(self)
- -
[docs] @lazy_property - def locks(self): - return LockHandler(self)
- -
[docs] @lazy_property - def tags(self): - return TagHandler(self)
- -
[docs] @lazy_property - def aliases(self): - return AliasHandler(self)
- -
[docs] @lazy_property - def permissions(self): - return PermissionHandler(self)
- -
[docs] @lazy_property - def nattributes(self): - return NAttributeHandler(self)
- -
[docs] class Meta(object): - """ - Django setup info. - """ - - abstract = True - verbose_name = "Evennia Database Object" - ordering = ["-db_date_created", "id", "db_typeclass_path", "db_key"]
- - # wrapper - # Wrapper properties to easily set database fields. These are - # @property decorators that allows to access these fields using - # normal python operations (without having to remember to save() - # etc). So e.g. a property 'attr' has a get/set/del decorator - # defined that allows the user to do self.attr = value, - # value = self.attr and del self.attr respectively (where self - # is the object in question). - - # name property (alias to self.key) - def __name_get(self): - return self.key - - def __name_set(self, value): - self.key = value - - def __name_del(self): - raise Exception("Cannot delete name") - - name = property(__name_get, __name_set, __name_del) - - # key property (overrides's the idmapper's db_key for the at_rename hook) - @property - def key(self): - return self.db_key - - @key.setter - def key(self, value): - oldname = str(self.db_key) - self.db_key = value - self.save(update_fields=["db_key"]) - self.at_rename(oldname, value) - SIGNAL_TYPED_OBJECT_POST_RENAME.send(sender=self, old_key=oldname, new_key=value) - - # - # - # TypedObject main class methods and properties - # - # - - def __eq__(self, other): - try: - return self.__dbclass__ == other.__dbclass__ and self.dbid == other.dbid - except AttributeError: - return False - - def __hash__(self): - # this is required to maintain hashing - return super().__hash__() - - def __str__(self): - return smart_str("%s" % self.db_key) - - def __repr__(self): - return "%s" % self.db_key - - # @property - def __dbid_get(self): - """ - Caches and returns the unique id of the object. - Use this instead of self.id, which is not cached. - """ - return self.id - - def __dbid_set(self, value): - raise Exception("dbid cannot be set!") - - def __dbid_del(self): - raise Exception("dbid cannot be deleted!") - - dbid = property(__dbid_get, __dbid_set, __dbid_del) - - # @property - def __dbref_get(self): - """ - Returns the object's dbref on the form #NN. - """ - return "#%s" % self.id - - def __dbref_set(self): - raise Exception("dbref cannot be set!") - - def __dbref_del(self): - raise Exception("dbref cannot be deleted!") - - dbref = property(__dbref_get, __dbref_set, __dbref_del) - -
[docs] def at_idmapper_flush(self): - """ - This is called when the idmapper cache is flushed and - allows customized actions when this happens. - - Returns: - do_flush (bool): If True, flush this object as normal. If - False, don't flush and expect this object to handle - the flushing on its own. - - Notes: - The default implementation relies on being able to clear - Django's Foreignkey cache on objects not affected by the - flush (notably objects with an NAttribute stored). We rely - on this cache being stored on the format "_<fieldname>_cache". - If Django were to change this name internally, we need to - update here (unlikely, but marking just in case). - - """ - if self.nattributes.all(): - # we can't flush this object if we have non-persistent - # attributes stored - those would get lost! Nevertheless - # we try to flush as many references as we can. - self.attributes.reset_cache() - self.tags.reset_cache() - # flush caches for all related fields - for field in self._meta.fields: - name = "_%s_cache" % field.name - if field.is_relation and name in self.__dict__: - # a foreignkey - remove its cache - del self.__dict__[name] - return False - # a normal flush - return True
- - # - # Object manipulation methods - # - -
[docs] def is_typeclass(self, typeclass, exact=False): - """ - Returns true if this object has this type OR has a typeclass - which is an subclass of the given typeclass. This operates on - the actually loaded typeclass (this is important since a - failing typeclass may instead have its default currently - loaded) typeclass - can be a class object or the python path - to such an object to match against. - - Args: - typeclass (str or class): A class or the full python path - to the class to check. - exact (bool, optional): Returns true only if the object's - type is exactly this typeclass, ignoring parents. - - Returns: - is_typeclass (bool): If this typeclass matches the given - typeclass. - - """ - if isinstance(typeclass, str): - typeclass = [typeclass] + [ - "%s.%s" % (prefix, typeclass) for prefix in settings.TYPECLASS_PATHS - ] - else: - typeclass = [typeclass.path] - - selfpath = self.path - if exact: - # check only exact match - return selfpath in typeclass - else: - # check parent chain - return any( - hasattr(cls, "path") and cls.path in typeclass for cls in self.__class__.mro() - )
- -
[docs] def swap_typeclass( - self, - new_typeclass, - clean_attributes=False, - run_start_hooks="all", - no_default=True, - clean_cmdsets=False, - ): - """ - This performs an in-situ swap of the typeclass. This means - that in-game, this object will suddenly be something else. - Account will not be affected. To 'move' an account to a different - object entirely (while retaining this object's type), use - self.account.swap_object(). - - Note that this might be an error prone operation if the - old/new typeclass was heavily customized - your code - might expect one and not the other, so be careful to - bug test your code if using this feature! Often its easiest - to create a new object and just swap the account over to - that one instead. - - Args: - new_typeclass (str or classobj): Type to switch to. - clean_attributes (bool or list, optional): Will delete all - attributes stored on this object (but not any of the - database fields such as name or location). You can't get - attributes back, but this is often the safest bet to make - sure nothing in the new typeclass clashes with the old - one. If you supply a list, only those named attributes - will be cleared. - run_start_hooks (str or None, optional): This is either None, - to not run any hooks, "all" to run all hooks defined by - at_first_start, or a string giving the name of the hook - to run (for example 'at_object_creation'). This will - always be called without arguments. - no_default (bool, optiona): If set, the swapper will not - allow for swapping to a default typeclass in case the - given one fails for some reason. Instead the old one will - be preserved. - clean_cmdsets (bool, optional): Delete all cmdsets on the object. - - """ - - if not callable(new_typeclass): - # this is an actual class object - build the path - new_typeclass = class_from_module(new_typeclass, defaultpaths=settings.TYPECLASS_PATHS) - - # if we get to this point, the class is ok. - - if inherits_from(self, "evennia.scripts.models.ScriptDB"): - if self.interval > 0: - raise RuntimeError( - "Cannot use swap_typeclass on time-dependent " - "Script '%s'.\nStop and start a new Script of the " - "right type instead." % self.key - ) - - self.typeclass_path = new_typeclass.path - self.__class__ = new_typeclass - - if clean_attributes: - # Clean out old attributes - if is_iter(clean_attributes): - for attr in clean_attributes: - self.attributes.remove(attr) - for nattr in clean_attributes: - if hasattr(self.ndb, nattr): - self.nattributes.remove(nattr) - else: - self.attributes.clear() - self.nattributes.clear() - if clean_cmdsets: - # purge all cmdsets - self.cmdset.clear() - self.cmdset.remove_default() - - if run_start_hooks == "all": - # fake this call to mimic the first save - self.at_first_save() - elif run_start_hooks: - # a custom hook-name to call. - getattr(self, run_start_hooks)()
- - # - # Lock / permission methods - # - -
[docs] def access( - self, accessing_obj, access_type="read", default=False, no_superuser_bypass=False, **kwargs - ): - """ - Determines if another object has permission to access this one. - - Args: - accessing_obj (str): Object trying to access this one. - access_type (str, optional): Type of access sought. - default (bool, optional): What to return if no lock of - access_type was found - no_superuser_bypass (bool, optional): Turn off the - superuser lock bypass (be careful with this one). - - Keyword Args: - kwargs (any): Ignored, but is there to make the api - consistent with the object-typeclass method access, which - use it to feed to its hook methods. - - """ - return self.locks.check( - accessing_obj, - access_type=access_type, - default=default, - no_superuser_bypass=no_superuser_bypass, - )
- -
[docs] def check_permstring(self, permstring): - """ - This explicitly checks if we hold particular permission - without involving any locks. - - Args: - permstring (str): The permission string to check against. - - Returns: - result (bool): If the permstring is passed or not. - - """ - if hasattr(self, "account"): - if ( - self.account - and self.account.is_superuser - and not self.account.attributes.get("_quell") - ): - return True - else: - if self.is_superuser and not self.attributes.get("_quell"): - return True - - if not permstring: - return False - perm = permstring.lower() - perms = [p.lower() for p in self.permissions.all()] - if perm in perms: - # simplest case - we have a direct match - return True - if perm in _PERMISSION_HIERARCHY: - # check if we have a higher hierarchy position - ppos = _PERMISSION_HIERARCHY.index(perm) - return any( - True - for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) - if hperm in perms and hpos > ppos - ) - # we ignore pluralization (english only) - if perm.endswith("s"): - return self.check_permstring(perm[:-1]) - - return False
- - # - # Deletion methods - # - - def _deleted(self, *args, **kwargs): - """ - Scrambling method for already deleted objects - """ - raise ObjectDoesNotExist("This object was already deleted!") - -
[docs] def delete(self): - """ - Cleaning up handlers on the typeclass level - - """ - global TICKER_HANDLER - self.permissions.clear() - self.attributes.clear() - self.aliases.clear() - if hasattr(self, "nicks"): - self.nicks.clear() - # scrambling properties - self.delete = self._deleted - super().delete()
- - # - # Attribute storage - # - - # @property db - def __db_get(self): - """ - Attribute handler wrapper. Allows for the syntax - :: - - obj.db.attrname = value - and - value = obj.db.attrname - and - del obj.db.attrname - and - all_attr = obj.db.all() - - (unless there is an attribute named 'all', in which case that will be - returned instead). - - """ - try: - return self._db_holder - except AttributeError: - self._db_holder = DbHolder(self, "attributes") - return self._db_holder - - # @db.setter - def __db_set(self, value): - """Stop accidentally replacing the db object""" - string = "Cannot assign directly to db object! " - string += "Use db.attr=value instead." - raise Exception(string) - - # @db.deleter - def __db_del(self): - """Stop accidental deletion.""" - raise Exception("Cannot delete the db object!") - - db = property(__db_get, __db_set, __db_del) - - # - # Non-persistent (ndb) storage - # - - # @property ndb - def __ndb_get(self): - """ - A non-attr_obj store (NonDataBase). Everything stored to this is - guaranteed to be cleared when a server is shutdown. Syntax is same as - for the `.db` property, e.g. - :: - - obj.ndb.attrname = value - and - value = obj.ndb.attrname - and - del obj.ndb.attrname - and - all_attr = obj.ndb.all() - - What makes this preferable over just assigning properties directly on - the object is that Evennia can track caching for these properties and - for example avoid wiping objects with set `.ndb` data on cache flushes. - - """ - try: - return self._ndb_holder - except AttributeError: - self._ndb_holder = DbHolder(self, "nattrhandler", manager_name="nattributes") - return self._ndb_holder - - # @db.setter - def __ndb_set(self, value): - "Stop accidentally replacing the ndb object" - string = "Cannot assign directly to ndb object! " - string += "Use ndb.attr=value instead." - raise Exception(string) - - # @db.deleter - def __ndb_del(self): - "Stop accidental deletion." - raise Exception("Cannot delete the ndb object!") - - ndb = property(__ndb_get, __ndb_set, __ndb_del) - -
[docs] def get_display_name(self, looker, **kwargs): - """ - Displays the name of the object in a viewer-aware manner. - - Args: - looker (TypedObject, optional): The object or account that is looking - at/getting inforamtion for this object. If not given, some - 'safe' minimum level should be returned. - - Returns: - name (str): A string containing the name of the object, - including the DBREF if this user is privileged to control - said object. - - Notes: - This function could be extended to change how object names - appear to users in character, but be wary. This function - does not change an object's keys or aliases when - searching, and is expected to produce something useful for - builders. - - """ - if self.access(looker, access_type="controls"): - return "{}(#{})".format(self.name, self.id) - return self.name
- -
[docs] def get_extra_info(self, looker, **kwargs): - """ - Used when an object is in a list of ambiguous objects as an - additional information tag. - - For instance, if you had potions which could have varying - levels of liquid left in them, you might want to display how - many drinks are left in each when selecting which to drop, but - not in your normal inventory listing. - - Args: - looker (TypedObject): The object or account that is looking - at/getting information for this object. - - Returns: - info (str): A string with disambiguating information, - conventionally with a leading space. - - """ - - if self.location == looker: - return " (carried)" - return ""
- -
[docs] def at_rename(self, oldname, newname): - """ - This Hook is called by @name on a successful rename. - - Args: - oldname (str): The instance's original name. - newname (str): The new name for the instance. - - """ - pass
- - # - # Web/Django methods - # - -
[docs] def web_get_admin_url(self): - """ - Returns the URI path for the Django Admin page for this object. - - ex. Account#1 = '/admin/accounts/accountdb/1/change/' - - Returns: - path (str): URI path to Django Admin page for object. - - """ - content_type = ContentType.objects.get_for_model(self.__class__) - return reverse( - "admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,) - )
- -
[docs] @classmethod - def web_get_create_url(cls): - """ - - Returns the URI path for a View that allows users to create new - instances of this object. - - Returns: - path (str): URI path to object creation page, if defined. - - Examples: - :: - - Chargen = '/characters/create/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-create' would be referenced by this method. - :: - - url(r'characters/create/', ChargenView.as_view(), name='character-create') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - Notes: - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can create new objects is the - developer's responsibility. - - """ - try: - return reverse("%s-create" % slugify(cls._meta.verbose_name)) - except: - return "#"
- -
[docs] def web_get_detail_url(self): - """ - Returns the URI path for a View that allows users to view details for - this object. - - Returns: - path (str): URI path to object detail page, if defined. - - Examples: - :: - - Oscar (Character) = '/characters/oscar/1/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-detail' would be referenced by this method. - :: - - CharDetailView.as_view(), name='character-detail') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - Notes: - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can view this object is the - developer's responsibility. - - """ - try: - return reverse( - "%s-detail" % slugify(self._meta.verbose_name), - kwargs={"pk": self.pk, "slug": slugify(self.name)}, - ) - except: - return "#"
- -
[docs] def web_get_puppet_url(self): - """ - Returns the URI path for a View that allows users to puppet a specific - object. - - Returns: - path (str): URI path to object puppet page, if defined. - - Examples: - :: - - Oscar (Character) = '/characters/oscar/1/puppet/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-puppet' would be referenced by this method. - :: - - url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/puppet/$', - CharPuppetView.as_view(), name='character-puppet') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - Notes: - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can view this object is the developer's - responsibility. - - - """ - try: - return reverse( - "%s-puppet" % slugify(self._meta.verbose_name), - kwargs={"pk": self.pk, "slug": slugify(self.name)}, - ) - except: - return "#"
- -
[docs] def web_get_update_url(self): - """ - Returns the URI path for a View that allows users to update this - object. - - Returns: - path (str): URI path to object update page, if defined. - - Examples: - :: - - Oscar (Character) = '/characters/oscar/1/change/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-update' would be referenced by this method. - :: - - url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$', - CharUpdateView.as_view(), name='character-update') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - Notes: - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can modify objects is the developer's - responsibility. - - """ - try: - return reverse( - "%s-update" % slugify(self._meta.verbose_name), - kwargs={"pk": self.pk, "slug": slugify(self.name)}, - ) - except: - return "#"
- -
[docs] def web_get_delete_url(self): - """ - Returns the URI path for a View that allows users to delete this object. - - Returns: - path (str): URI path to object deletion page, if defined. - - Examples: - :: - - Oscar (Character) = '/characters/oscar/1/delete/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-detail' would be referenced by this method. - :: - - url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$', - CharDeleteView.as_view(), name='character-delete') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - Notes: - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can delete this object is the developer's - responsibility. - - - """ - try: - return reverse( - "%s-delete" % slugify(self._meta.verbose_name), - kwargs={"pk": self.pk, "slug": slugify(self.name)}, - ) - except: - return "#"
- - # Used by Django Sites/Admin - get_absolute_url = web_get_detail_url
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/typeclasses/tags.html b/docs/0.9.5/_modules/evennia/typeclasses/tags.html deleted file mode 100644 index 9fb859b9e7..0000000000 --- a/docs/0.9.5/_modules/evennia/typeclasses/tags.html +++ /dev/null @@ -1,602 +0,0 @@ - - - - - - - - evennia.typeclasses.tags — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.typeclasses.tags

-"""
-Tags are entities that are attached to objects in the same way as
-Attributes. But contrary to Attributes, which are unique to an
-individual object, a single Tag can be attached to any number of
-objects at the same time.
-
-Tags are used for tagging, obviously, but the data structure is also
-used for storing Aliases and Permissions. This module contains the
-respective handlers.
-
-"""
-from collections import defaultdict
-
-from django.conf import settings
-from django.db import models
-from evennia.utils.utils import to_str, make_iter
-
-
-_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
-
-# ------------------------------------------------------------
-#
-# Tags
-#
-# ------------------------------------------------------------
-
-
-
[docs]class Tag(models.Model): - """ - Tags are quick markers for objects in-game. An typeobject can have - any number of tags, stored via its db_tags property. Tagging - similar objects will make it easier to quickly locate the group - later (such as when implementing zones). The main advantage of - tagging as opposed to using tags is speed; a tag is very - limited in what data it can hold, and the tag key+category is - indexed for efficient lookup in the database. Tags are shared - between objects - a new tag is only created if the key+category - combination did not previously exist, making them unsuitable for - storing object-related data (for this a regular Attribute should be - used). - - The 'db_data' field is intended as a documentation field for the - tag itself, such as to document what this tag+category stands for - and display that in a web interface or similar. - - The main default use for Tags is to implement Aliases for objects. - this uses the 'aliases' tag category, which is also checked by the - default search functions of Evennia to allow quick searches by alias. - - """ - - db_key = models.CharField( - "key", max_length=255, null=True, help_text="tag identifier", db_index=True - ) - db_category = models.CharField( - "category", max_length=64, null=True, help_text="tag category", db_index=True - ) - db_data = models.TextField( - "data", - null=True, - blank=True, - help_text="optional data field with extra information. This is not searched for.", - ) - # this is "objectdb" etc. Required behind the scenes - db_model = models.CharField( - "model", max_length=32, null=True, help_text="database model to Tag", db_index=True - ) - # this is None, alias or permission - db_tagtype = models.CharField( - "tagtype", - max_length=16, - null=True, - blank=True, - help_text="overall type of Tag", - db_index=True, - ) - - class Meta(object): - "Define Django meta options" - verbose_name = "Tag" - unique_together = (("db_key", "db_category", "db_tagtype", "db_model"),) - index_together = (("db_key", "db_category", "db_tagtype", "db_model"),) - - def __lt__(self, other): - return str(self) < str(other) - - def __str__(self): - return str( - "<Tag: %s%s>" - % (self.db_key, "(category:%s)" % self.db_category if self.db_category else "") - )
- - -# -# Handlers making use of the Tags model -# - - -
[docs]class TagHandler(object): - """ - Generic tag-handler. Accessed via TypedObject.tags. - - """ - - _m2m_fieldname = "db_tags" - _tagtype = None - -
[docs] def __init__(self, obj): - """ - Tags are stored internally in the TypedObject.db_tags m2m - field with an tag.db_model based on the obj the taghandler is - stored on and with a tagtype given by self.handlertype - - Args: - obj (object): The object on which the handler is set. - - """ - self.obj = obj - self._objid = obj.id - self._model = obj.__dbclass__.__name__.lower() - self._cache = {} - # store category names fully cached - self._catcache = {} - # full cache was run on all tags - self._cache_complete = False
- - def _query_all(self): - "Get all tags for this objects" - query = { - "%s__id" % self._model: self._objid, - "tag__db_model": self._model, - "tag__db_tagtype": self._tagtype, - } - return [ - conn.tag - for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query) - ] - - def _fullcache(self): - "Cache all tags of this object" - if not _TYPECLASS_AGGRESSIVE_CACHE: - return - tags = self._query_all() - self._cache = dict( - ( - "%s-%s" - % ( - to_str(tag.db_key).lower(), - tag.db_category.lower() if tag.db_category else None, - ), - tag, - ) - for tag in tags - ) - self._cache_complete = True - - def _getcache(self, key=None, category=None): - """ - Retrieve from cache or database (always caches) - - Args: - key (str, optional): Tag key to query for - category (str, optional): Tag category - - Returns: - args (list): Returns a list of zero or more matches - found from cache or database. - Notes: - When given a category only, a search for all objects - of that category is done and a the category *name* is is - stored. This tells the system on subsequent calls that the - list of cached tags of this category is up-to-date - and that the cache can be queried for category matches - without missing any. - The TYPECLASS_AGGRESSIVE_CACHE=False setting will turn off - caching, causing each tag access to trigger a - database lookup. - - """ - key = key.strip().lower() if key else None - category = category.strip().lower() if category else None - if key: - cachekey = "%s-%s" % (key, category) - tag = _TYPECLASS_AGGRESSIVE_CACHE and self._cache.get(cachekey, None) - if tag and (not hasattr(tag, "pk") and tag.pk is None): - # clear out Tags deleted from elsewhere. We must search this anew. - tag = None - del self._cache[cachekey] - if tag: - return [tag] # return cached entity - else: - query = { - "%s__id" % self._model: self._objid, - "tag__db_model": self._model, - "tag__db_tagtype": self._tagtype, - "tag__db_key__iexact": key.lower(), - "tag__db_category__iexact": category.lower() if category else None, - } - conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query) - if conn: - tag = conn[0].tag - if _TYPECLASS_AGGRESSIVE_CACHE: - self._cache[cachekey] = tag - return [tag] - else: - # only category given (even if it's None) - we can't - # assume the cache to be complete unless we have queried - # for this category before - catkey = "-%s" % category - if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache: - return [tag for key, tag in self._cache.items() if key.endswith(catkey)] - else: - # we have to query to make this category up-date in the cache - query = { - "%s__id" % self._model: self._objid, - "tag__db_model": self._model, - "tag__db_tagtype": self._tagtype, - "tag__db_category__iexact": category.lower() if category else None, - } - tags = [ - conn.tag - for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter( - **query - ) - ] - if _TYPECLASS_AGGRESSIVE_CACHE: - for tag in tags: - cachekey = "%s-%s" % (tag.db_key, category) - self._cache[cachekey] = tag - # mark category cache as up-to-date - self._catcache[catkey] = True - return tags - return [] - - def _setcache(self, key, category, tag_obj): - """ - Update cache. - - Args: - key (str): A cleaned key string - category (str or None): A cleaned category name - tag_obj (tag): The newly saved tag - - """ - if not _TYPECLASS_AGGRESSIVE_CACHE: - return - if not key: # don't allow an empty key in cache - return - key, category = (key.strip().lower(), category.strip().lower() if category else category) - cachekey = "%s-%s" % (key, category) - catkey = "-%s" % category - self._cache[cachekey] = tag_obj - # mark that the category cache is no longer up-to-date - self._catcache.pop(catkey, None) - self._cache_complete = False - - def _delcache(self, key, category): - """ - Remove tag from cache - - Args: - key (str): A cleaned key string - category (str or None): A cleaned category name - - """ - key, category = (key.strip().lower(), category.strip().lower() if category else category) - catkey = "-%s" % category - if key: - cachekey = "%s-%s" % (key, category) - self._cache.pop(cachekey, None) - else: - [self._cache.pop(key, None) for key in self._cache if key.endswith(catkey)] - # mark that the category cache is no longer up-to-date - self._catcache.pop(catkey, None) - self._cache_complete = False - -
[docs] def reset_cache(self): - """ - Reset the cache from the outside. - """ - self._cache_complete = False - self._cache = {} - self._catcache = {}
- -
[docs] def add(self, tag=None, category=None, data=None): - """ - Add a new tag to the handler. - - Args: - tag (str or list): The name of the tag to add. If a list, - add several Tags. - category (str, optional): Category of Tag. `None` is the default category. - data (str, optional): Info text about the tag(s) added. - This can not be used to store object-unique info but only - eventual info about the tag itself. - - Notes: - If the tag + category combination matches an already - existing Tag object, this will be re-used and no new Tag - will be created. - - """ - if not tag: - return - if not self._cache_complete: - self._fullcache() - for tagstr in make_iter(tag): - if not tagstr: - continue - tagstr = str(tagstr).strip().lower() - category = str(category).strip().lower() if category else category - data = str(data) if data is not None else None - # this will only create tag if no matches existed beforehand (it - # will overload data on an existing tag since that is not - # considered part of making the tag unique) - tagobj = self.obj.__class__.objects.create_tag( - key=tagstr, category=category, data=data, tagtype=self._tagtype - ) - getattr(self.obj, self._m2m_fieldname).add(tagobj) - self._setcache(tagstr, category, tagobj)
- -
[docs] def get(self, key=None, default=None, category=None, return_tagobj=False, return_list=False): - """ - Get the tag for the given key, category or combination of the two. - - Args: - key (str or list, optional): The tag or tags to retrieve. - default (any, optional): The value to return in case of no match. - category (str, optional): The Tag category to limit the - request to. Note that `None` is the valid, default - category. If no `key` is given, all tags of this category will be - returned. - return_tagobj (bool, optional): Return the Tag object itself - instead of a string representation of the Tag. - return_list (bool, optional): Always return a list, regardless - of number of matches. - - Returns: - tags (list): The matches, either string - representations of the tags or the Tag objects themselves - depending on `return_tagobj`. If 'default' is set, this - will be a list with the default value as its only element. - - """ - ret = [] - for keystr in make_iter(key): - # note - the _getcache call removes case sensitivity for us - ret.extend( - [ - tag if return_tagobj else to_str(tag.db_key) - for tag in self._getcache(keystr, category) - ] - ) - if return_list: - return ret if ret else [default] if default is not None else [] - return ret[0] if len(ret) == 1 else (ret if ret else default)
- -
[docs] def remove(self, key=None, category=None): - """ - Remove a tag from the handler based ond key and/or category. - - Args: - key (str or list, optional): The tag or tags to retrieve. - category (str, optional): The Tag category to limit the - request to. Note that `None` is the valid, default - category - Notes: - If neither key nor category is specified, this acts - as .clear(). - - """ - if not key: - # only category - self.clear(category=category) - return - - for key in make_iter(key): - if not (key or key.strip()): # we don't allow empty tags - continue - tagstr = key.strip().lower() - category = category.strip().lower() if category else category - - # This does not delete the tag object itself. Maybe it should do - # that when no objects reference the tag anymore (but how to check)? - # For now, tags are never deleted, only their connection to objects. - tagobj = getattr(self.obj, self._m2m_fieldname).filter( - db_key=tagstr, db_category=category, db_model=self._model, db_tagtype=self._tagtype - ) - if tagobj: - getattr(self.obj, self._m2m_fieldname).remove(tagobj[0]) - self._delcache(key, category)
- -
[docs] def clear(self, category=None): - """ - Remove all tags from the handler. - - Args: - category (str, optional): The Tag category to limit the - request to. Note that `None` is the valid, default - category. - - """ - if not self._cache_complete: - self._fullcache() - query = { - "%s__id" % self._model: self._objid, - "tag__db_model": self._model, - "tag__db_tagtype": self._tagtype, - } - if category: - query["tag__db_category"] = category.strip().lower() - getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query).delete() - self._cache = {} - self._catcache = {} - self._cache_complete = False
- -
[docs] def all(self, return_key_and_category=False, return_objs=False): - """ - Get all tags in this handler, regardless of category. - - Args: - return_key_and_category (bool, optional): Return a list of - tuples `[(key, category), ...]`. - return_objs (bool, optional): Return tag objects. - - Returns: - tags (list): A list of tag keys `[tagkey, tagkey, ...]` or - a list of tuples `[(key, category), ...]` if - `return_key_and_category` is set. - - """ - if _TYPECLASS_AGGRESSIVE_CACHE: - if not self._cache_complete: - self._fullcache() - tags = sorted(self._cache.values()) - else: - tags = sorted(self._query_all()) - - if return_key_and_category: - # return tuple (key, category) - return [(to_str(tag.db_key), tag.db_category) for tag in tags] - elif return_objs: - return tags - else: - return [to_str(tag.db_key) for tag in tags]
- -
[docs] def batch_add(self, *args): - """ - Batch-add tags from a list of tuples. - - Args: - *args (tuple or str): Each argument should be a `tagstr` keys or tuple `(keystr, category)` or - `(keystr, category, data)`. It's possible to mix input types. - - Notes: - This will generate a mimimal number of self.add calls, - based on the number of categories involved (including - `None`) (data is not unique and may be overwritten by the content - of a latter tuple with the same category). - - """ - keys = defaultdict(list) - data = {} - for tup in args: - tup = make_iter(tup) - nlen = len(tup) - if nlen == 1: # just a key - keys[None].append(tup[0]) - elif nlen == 2: - keys[tup[1]].append(tup[0]) - else: - keys[tup[1]].append(tup[0]) - data[tup[1]] = tup[2] # overwrite previous - for category, key in keys.items(): - self.add(tag=key, category=category, data=data.get(category, None))
- - def __str__(self): - return ",".join(self.all())
- - -
[docs]class AliasHandler(TagHandler): - """ - A handler for the Alias Tag type. - - """ - - _tagtype = "alias"
- - -
[docs]class PermissionHandler(TagHandler): - """ - A handler for the Permission Tag type. - - """ - - _tagtype = "permission"
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/ansi.html b/docs/0.9.5/_modules/evennia/utils/ansi.html deleted file mode 100644 index 39cdd1eefd..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/ansi.html +++ /dev/null @@ -1,1524 +0,0 @@ - - - - - - - - evennia.utils.ansi — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.ansi

-"""
-ANSI - Gives colour to text.
-
-Use the codes defined in ANSIPARSER in your text to apply colour to text
-according to the ANSI standard.
-
-Examples:
-
-```python
-"This is |rRed text|n and this is normal again."
-```
-
-Mostly you should not need to call `parse_ansi()` explicitly; it is run by
-Evennia just before returning data to/from the user. Depreciated example forms
-are available by extending the ansi mapping.
-
-"""
-import functools
-
-import re
-from collections import OrderedDict
-
-from django.conf import settings
-
-from evennia.utils import utils
-from evennia.utils import logger
-
-from evennia.utils.utils import to_str
-
-
-# ANSI definitions
-
-ANSI_BEEP = "\07"
-ANSI_ESCAPE = "\033"
-ANSI_NORMAL = "\033[0m"
-
-ANSI_UNDERLINE = "\033[4m"
-ANSI_HILITE = "\033[1m"
-ANSI_UNHILITE = "\033[22m"
-ANSI_BLINK = "\033[5m"
-ANSI_INVERSE = "\033[7m"
-ANSI_INV_HILITE = "\033[1;7m"
-ANSI_INV_BLINK = "\033[7;5m"
-ANSI_BLINK_HILITE = "\033[1;5m"
-ANSI_INV_BLINK_HILITE = "\033[1;5;7m"
-
-# Foreground colors
-ANSI_BLACK = "\033[30m"
-ANSI_RED = "\033[31m"
-ANSI_GREEN = "\033[32m"
-ANSI_YELLOW = "\033[33m"
-ANSI_BLUE = "\033[34m"
-ANSI_MAGENTA = "\033[35m"
-ANSI_CYAN = "\033[36m"
-ANSI_WHITE = "\033[37m"
-
-# Background colors
-ANSI_BACK_BLACK = "\033[40m"
-ANSI_BACK_RED = "\033[41m"
-ANSI_BACK_GREEN = "\033[42m"
-ANSI_BACK_YELLOW = "\033[43m"
-ANSI_BACK_BLUE = "\033[44m"
-ANSI_BACK_MAGENTA = "\033[45m"
-ANSI_BACK_CYAN = "\033[46m"
-ANSI_BACK_WHITE = "\033[47m"
-
-# Formatting Characters
-ANSI_RETURN = "\r\n"
-ANSI_TAB = "\t"
-ANSI_SPACE = " "
-
-# Escapes
-ANSI_ESCAPES = ("{{", "\\\\", "\|\|")
-
-_PARSE_CACHE = OrderedDict()
-_PARSE_CACHE_SIZE = 10000
-
-_COLOR_NO_DEFAULT = settings.COLOR_NO_DEFAULT
-
-
-
[docs]class ANSIParser(object): - """ - A class that parses ANSI markup to ANSI command sequences. - - We also allow to escape colour codes by prepending with an extra `|`. - - """ - - # Mapping using {r {n etc - - ansi_map = [ - # alternative |-format - (r"|n", ANSI_NORMAL), # reset - (r"|/", ANSI_RETURN), # line break - (r"|-", ANSI_TAB), # tab - (r"|>", ANSI_SPACE * 4), # indent (4 spaces) - (r"|_", ANSI_SPACE), # space - (r"|*", ANSI_INVERSE), # invert - (r"|^", ANSI_BLINK), # blinking text (very annoying and not supported by all clients) - (r"|u", ANSI_UNDERLINE), # underline - (r"|r", ANSI_HILITE + ANSI_RED), - (r"|g", ANSI_HILITE + ANSI_GREEN), - (r"|y", ANSI_HILITE + ANSI_YELLOW), - (r"|b", ANSI_HILITE + ANSI_BLUE), - (r"|m", ANSI_HILITE + ANSI_MAGENTA), - (r"|c", ANSI_HILITE + ANSI_CYAN), - (r"|w", ANSI_HILITE + ANSI_WHITE), # pure white - (r"|x", ANSI_HILITE + ANSI_BLACK), # dark grey - (r"|R", ANSI_UNHILITE + ANSI_RED), - (r"|G", ANSI_UNHILITE + ANSI_GREEN), - (r"|Y", ANSI_UNHILITE + ANSI_YELLOW), - (r"|B", ANSI_UNHILITE + ANSI_BLUE), - (r"|M", ANSI_UNHILITE + ANSI_MAGENTA), - (r"|C", ANSI_UNHILITE + ANSI_CYAN), - (r"|W", ANSI_UNHILITE + ANSI_WHITE), # light grey - (r"|X", ANSI_UNHILITE + ANSI_BLACK), # pure black - # hilight-able colors - (r"|h", ANSI_HILITE), - (r"|H", ANSI_UNHILITE), - (r"|!R", ANSI_RED), - (r"|!G", ANSI_GREEN), - (r"|!Y", ANSI_YELLOW), - (r"|!B", ANSI_BLUE), - (r"|!M", ANSI_MAGENTA), - (r"|!C", ANSI_CYAN), - (r"|!W", ANSI_WHITE), # light grey - (r"|!X", ANSI_BLACK), # pure black - # normal ANSI backgrounds - (r"|[R", ANSI_BACK_RED), - (r"|[G", ANSI_BACK_GREEN), - (r"|[Y", ANSI_BACK_YELLOW), - (r"|[B", ANSI_BACK_BLUE), - (r"|[M", ANSI_BACK_MAGENTA), - (r"|[C", ANSI_BACK_CYAN), - (r"|[W", ANSI_BACK_WHITE), # light grey background - (r"|[X", ANSI_BACK_BLACK), # pure black background - ] - - ansi_xterm256_bright_bg_map = [ - # "bright" ANSI backgrounds using xterm256 since ANSI - # standard does not support it (will - # fallback to dark ANSI background colors if xterm256 - # is not supported by client) - # |-style variations - (r"|[r", r"|[500"), - (r"|[g", r"|[050"), - (r"|[y", r"|[550"), - (r"|[b", r"|[005"), - (r"|[m", r"|[505"), - (r"|[c", r"|[055"), - (r"|[w", r"|[555"), # white background - (r"|[x", r"|[222"), - ] # dark grey background - - # xterm256. These are replaced directly by - # the sub_xterm256 method - - if settings.COLOR_NO_DEFAULT: - ansi_map = settings.COLOR_ANSI_EXTRA_MAP - xterm256_fg = settings.COLOR_XTERM256_EXTRA_FG - xterm256_bg = settings.COLOR_XTERM256_EXTRA_BG - xterm256_gfg = settings.COLOR_XTERM256_EXTRA_GFG - xterm256_gbg = settings.COLOR_XTERM256_EXTRA_GBG - ansi_xterm256_bright_bg_map = settings.COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP - else: - xterm256_fg = [r"\|([0-5])([0-5])([0-5])"] # |123 - foreground colour - xterm256_bg = [r"\|\[([0-5])([0-5])([0-5])"] # |[123 - background colour - xterm256_gfg = [r"\|=([a-z])"] # |=a - greyscale foreground - xterm256_gbg = [r"\|\[=([a-z])"] # |[=a - greyscale background - ansi_map += settings.COLOR_ANSI_EXTRA_MAP - xterm256_fg += settings.COLOR_XTERM256_EXTRA_FG - xterm256_bg += settings.COLOR_XTERM256_EXTRA_BG - xterm256_gfg += settings.COLOR_XTERM256_EXTRA_GFG - xterm256_gbg += settings.COLOR_XTERM256_EXTRA_GBG - ansi_xterm256_bright_bg_map += settings.COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP - - mxp_re = r"\|lc(.*?)\|lt(.*?)\|le" - - # prepare regex matching - brightbg_sub = re.compile( - r"|".join([r"(?<!\|)%s" % re.escape(tup[0]) for tup in ansi_xterm256_bright_bg_map]), - re.DOTALL, - ) - xterm256_fg_sub = re.compile(r"|".join(xterm256_fg), re.DOTALL) - xterm256_bg_sub = re.compile(r"|".join(xterm256_bg), re.DOTALL) - xterm256_gfg_sub = re.compile(r"|".join(xterm256_gfg), re.DOTALL) - xterm256_gbg_sub = re.compile(r"|".join(xterm256_gbg), re.DOTALL) - - # xterm256_sub = re.compile(r"|".join([tup[0] for tup in xterm256_map]), re.DOTALL) - ansi_sub = re.compile(r"|".join([re.escape(tup[0]) for tup in ansi_map]), re.DOTALL) - mxp_sub = re.compile(mxp_re, re.DOTALL) - - # used by regex replacer to correctly map ansi sequences - ansi_map_dict = dict(ansi_map) - ansi_xterm256_bright_bg_map_dict = dict(ansi_xterm256_bright_bg_map) - - # prepare matching ansi codes overall - ansi_re = r"\033\[[0-9;]+m" - ansi_regex = re.compile(ansi_re) - - # escapes - these double-chars will be replaced with a single - # instance of each - ansi_escapes = re.compile(r"(%s)" % "|".join(ANSI_ESCAPES), re.DOTALL) - -
[docs] def sub_ansi(self, ansimatch): - """ - Replacer used by `re.sub` to replace ANSI - markers with correct ANSI sequences - - Args: - ansimatch (re.matchobject): The match. - - Returns: - processed (str): The processed match string. - - """ - return self.ansi_map_dict.get(ansimatch.group(), "")
- -
[docs] def sub_brightbg(self, ansimatch): - """ - Replacer used by `re.sub` to replace ANSI - bright background markers with Xterm256 replacement - - Args: - ansimatch (re.matchobject): The match. - - Returns: - processed (str): The processed match string. - - """ - return self.ansi_xterm256_bright_bg_map_dict.get(ansimatch.group(), "")
- -
[docs] def sub_xterm256(self, rgbmatch, use_xterm256=False, color_type="fg"): - """ - This is a replacer method called by `re.sub` with the matched - tag. It must return the correct ansi sequence. - - It checks `self.do_xterm256` to determine if conversion - to standard ANSI should be done or not. - - Args: - rgbmatch (re.matchobject): The match. - use_xterm256 (bool, optional): Don't convert 256-colors to 16. - color_type (str): One of 'fg', 'bg', 'gfg', 'gbg'. - - Returns: - processed (str): The processed match string. - - """ - if not rgbmatch: - return "" - - # get tag, stripping the initial marker - # rgbtag = rgbmatch.group()[1:] - - background = color_type in ("bg", "gbg") - grayscale = color_type in ("gfg", "gbg") - - if not grayscale: - # 6x6x6 color-cube (xterm indexes 16-231) - try: - red, green, blue = [int(val) for val in rgbmatch.groups() if val is not None] - except (IndexError, ValueError): - logger.log_trace() - return rgbmatch.group(0) - else: - # grayscale values (xterm indexes 0, 232-255, 15) for full spectrum - try: - letter = [val for val in rgbmatch.groups() if val is not None][0] - except IndexError: - logger.log_trace() - return rgbmatch.group(0) - - if letter == "a": - colval = 16 # pure black @ index 16 (first color cube entry) - elif letter == "z": - colval = 231 # pure white @ index 231 (last color cube entry) - else: - # letter in range [b..y] (exactly 24 values!) - colval = 134 + ord(letter) - - # ansi fallback logic expects r,g,b values in [0..5] range - gray = (ord(letter) - 97) / 5.0 - red, green, blue = gray, gray, gray - - if use_xterm256: - - if not grayscale: - colval = 16 + (red * 36) + (green * 6) + blue - - return "\033[%s8;5;%sm" % (3 + int(background), colval) - # replaced since some clients (like Potato) does not accept codes with leading zeroes, see issue #1024. - # return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10) - - else: - # xterm256 not supported, convert the rgb value to ansi instead - if red == green == blue and red < 3: - if background: - return ANSI_BACK_BLACK - elif red >= 1: - return ANSI_HILITE + ANSI_BLACK - else: - return ANSI_NORMAL + ANSI_BLACK - elif red == green == blue: - if background: - return ANSI_BACK_WHITE - elif red >= 4: - return ANSI_HILITE + ANSI_WHITE - else: - return ANSI_NORMAL + ANSI_WHITE - elif red > green and red > blue: - if background: - return ANSI_BACK_RED - elif red >= 3: - return ANSI_HILITE + ANSI_RED - else: - return ANSI_NORMAL + ANSI_RED - elif red == green and red > blue: - if background: - return ANSI_BACK_YELLOW - elif red >= 3: - return ANSI_HILITE + ANSI_YELLOW - else: - return ANSI_NORMAL + ANSI_YELLOW - elif red == blue and red > green: - if background: - return ANSI_BACK_MAGENTA - elif red >= 3: - return ANSI_HILITE + ANSI_MAGENTA - else: - return ANSI_NORMAL + ANSI_MAGENTA - elif green > blue: - if background: - return ANSI_BACK_GREEN - elif green >= 3: - return ANSI_HILITE + ANSI_GREEN - else: - return ANSI_NORMAL + ANSI_GREEN - elif green == blue: - if background: - return ANSI_BACK_CYAN - elif green >= 3: - return ANSI_HILITE + ANSI_CYAN - else: - return ANSI_NORMAL + ANSI_CYAN - else: # mostly blue - if background: - return ANSI_BACK_BLUE - elif blue >= 3: - return ANSI_HILITE + ANSI_BLUE - else: - return ANSI_NORMAL + ANSI_BLUE
- -
[docs] def strip_raw_codes(self, string): - """ - Strips raw ANSI codes from a string. - - Args: - string (str): The string to strip. - - Returns: - string (str): The processed string. - - """ - return self.ansi_regex.sub("", string)
- -
[docs] def strip_mxp(self, string): - """ - Strips all MXP codes from a string. - - Args: - string (str): The string to strip. - - Returns: - string (str): The processed string. - - """ - return self.mxp_sub.sub(r"\2", string)
- -
[docs] def parse_ansi(self, string, strip_ansi=False, xterm256=False, mxp=False): - """ - Parses a string, subbing color codes according to the stored - mapping. - - Args: - string (str): The string to parse. - strip_ansi (boolean, optional): Strip all found ansi markup. - xterm256 (boolean, optional): If actually using xterm256 or if - these values should be converted to 16-color ANSI. - mxp (boolean, optional): Parse MXP commands in string. - - Returns: - string (str): The parsed string. - - """ - if hasattr(string, "_raw_string"): - if strip_ansi: - return string.clean() - else: - return string.raw() - - if not string: - return "" - - # check cached parsings - global _PARSE_CACHE - cachekey = "%s-%s-%s-%s" % (string, strip_ansi, xterm256, mxp) - if cachekey in _PARSE_CACHE: - return _PARSE_CACHE[cachekey] - - # pre-convert bright colors to xterm256 color tags - string = self.brightbg_sub.sub(self.sub_brightbg, string) - - def do_xterm256_fg(part): - return self.sub_xterm256(part, xterm256, "fg") - - def do_xterm256_bg(part): - return self.sub_xterm256(part, xterm256, "bg") - - def do_xterm256_gfg(part): - return self.sub_xterm256(part, xterm256, "gfg") - - def do_xterm256_gbg(part): - return self.sub_xterm256(part, xterm256, "gbg") - - in_string = utils.to_str(string) - - # do string replacement - parsed_string = [] - parts = self.ansi_escapes.split(in_string) + [" "] - for part, sep in zip(parts[::2], parts[1::2]): - pstring = self.xterm256_fg_sub.sub(do_xterm256_fg, part) - pstring = self.xterm256_bg_sub.sub(do_xterm256_bg, pstring) - pstring = self.xterm256_gfg_sub.sub(do_xterm256_gfg, pstring) - pstring = self.xterm256_gbg_sub.sub(do_xterm256_gbg, pstring) - pstring = self.ansi_sub.sub(self.sub_ansi, pstring) - parsed_string.append("%s%s" % (pstring, sep[0].strip())) - parsed_string = "".join(parsed_string) - - if not mxp: - parsed_string = self.strip_mxp(parsed_string) - - if strip_ansi: - # remove all ansi codes (including those manually - # inserted in string) - return self.strip_raw_codes(parsed_string) - - # cache and crop old cache - _PARSE_CACHE[cachekey] = parsed_string - if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE: - _PARSE_CACHE.popitem(last=False) - - return parsed_string
- - -ANSI_PARSER = ANSIParser() - - -# -# Access function -# - - -
[docs]def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER, xterm256=False, mxp=False): - """ - Parses a string, subbing color codes as needed. - - Args: - string (str): The string to parse. - strip_ansi (bool, optional): Strip all ANSI sequences. - parser (ansi.AnsiParser, optional): A parser instance to use. - xterm256 (bool, optional): Support xterm256 or not. - mxp (bool, optional): Support MXP markup or not. - - Returns: - string (str): The parsed string. - - """ - return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256, mxp=mxp)
- - -
[docs]def strip_ansi(string, parser=ANSI_PARSER): - """ - Strip all ansi from the string. This handles the Evennia-specific - markup. - - Args: - string (str): The string to strip. - parser (ansi.AnsiParser, optional): The parser to use. - - Returns: - string (str): The stripped string. - - """ - return parser.parse_ansi(string, strip_ansi=True)
- - -
[docs]def strip_raw_ansi(string, parser=ANSI_PARSER): - """ - Remove raw ansi codes from string. This assumes pure - ANSI-bytecodes in the string. - - Args: - string (str): The string to parse. - parser (bool, optional): The parser to use. - - Returns: - string (str): the stripped string. - - """ - return parser.strip_raw_codes(string)
- - -
[docs]def raw(string): - """ - Escapes a string into a form which won't be colorized by the ansi - parser. - - Returns: - string (str): The raw, escaped string. - - """ - return string.replace("{", "{{").replace("|", "||")
- - -# ------------------------------------------------------------ -# -# ANSIString - ANSI-aware string class -# -# ------------------------------------------------------------ - - -def _spacing_preflight(func): - """ - This wrapper function is used to do some preflight checks on - functions used for padding ANSIStrings. - - """ - - @functools.wraps(func) - def wrapped(self, width=78, fillchar=None): - if fillchar is None: - fillchar = " " - if (len(fillchar) != 1) or (not isinstance(fillchar, str)): - raise TypeError("must be char, not %s" % type(fillchar)) - if not isinstance(width, int): - raise TypeError("integer argument expected, got %s" % type(width)) - _difference = width - len(self) - if _difference <= 0: - return self - return func(self, width, fillchar, _difference) - - return wrapped - - -def _query_super(func_name): - """ - Have the string class handle this with the cleaned string instead - of ANSIString. - - """ - - def wrapped(self, *args, **kwargs): - return getattr(self.clean(), func_name)(*args, **kwargs) - - return wrapped - - -def _on_raw(func_name): - """ - Like query_super, but makes the operation run on the raw string. - - """ - - def wrapped(self, *args, **kwargs): - args = list(args) - try: - string = args.pop(0) - if hasattr(string, "_raw_string"): - args.insert(0, string.raw()) - else: - args.insert(0, string) - except IndexError: - # just skip out if there are no more strings - pass - result = getattr(self._raw_string, func_name)(*args, **kwargs) - if isinstance(result, str): - return ANSIString(result, decoded=True) - return result - - return wrapped - - -def _transform(func_name): - """ - Some string functions, like those manipulating capital letters, - return a string the same length as the original. This function - allows us to do the same, replacing all the non-coded characters - with the resulting string. - - """ - - def wrapped(self, *args, **kwargs): - replacement_string = _query_super(func_name)(self, *args, **kwargs) - to_string = [] - char_counter = 0 - for index in range(0, len(self._raw_string)): - if index in self._code_indexes: - to_string.append(self._raw_string[index]) - elif index in self._char_indexes: - to_string.append(replacement_string[char_counter]) - char_counter += 1 - return ANSIString( - "".join(to_string), - decoded=True, - code_indexes=self._code_indexes, - char_indexes=self._char_indexes, - clean_string=replacement_string, - ) - - return wrapped - - -
[docs]class ANSIMeta(type): - """ - Many functions on ANSIString are just light wrappers around the string - base class. We apply them here, as part of the classes construction. - - """ - -
[docs] def __init__(cls, *args, **kwargs): - for func_name in [ - "count", - "startswith", - "endswith", - "find", - "index", - "isalnum", - "isalpha", - "isdigit", - "islower", - "isspace", - "istitle", - "isupper", - "rfind", - "rindex", - "__len__", - ]: - setattr(cls, func_name, _query_super(func_name)) - for func_name in ["__mod__", "expandtabs", "decode", "replace", "format", "encode"]: - setattr(cls, func_name, _on_raw(func_name)) - for func_name in ["capitalize", "translate", "lower", "upper", "swapcase"]: - setattr(cls, func_name, _transform(func_name)) - super().__init__(*args, **kwargs)
- - -
[docs]class ANSIString(str, metaclass=ANSIMeta): - """ - Unicode-like object that is aware of ANSI codes. - - This class can be used nearly identically to strings, in that it will - report string length, handle slices, etc, much like a string object - would. The methods should be used identically as string methods are. - - There is at least one exception to this (and there may be more, though - they have not come up yet). When using ''.join() or u''.join() on an - ANSIString, color information will get lost. You must use - ANSIString('').join() to preserve color information. - - This implementation isn't perfectly clean, as it doesn't really have an - understanding of what the codes mean in order to eliminate - redundant characters-- though cleaning up the strings might end up being - inefficient and slow without some C code when dealing with larger values. - Such enhancements could be made as an enhancement to ANSI_PARSER - if needed, however. - - If one is going to use ANSIString, one should generally avoid converting - away from it until one is about to send information on the wire. This is - because escape sequences in the string may otherwise already be decoded, - and taken literally the second time around. - - """ - - # A compiled Regex for the format mini-language: https://docs.python.org/3/library/string.html#formatspec - re_format = re.compile( - r"(?i)(?P<just>(?P<fill>.)?(?P<align>\<|\>|\=|\^))?(?P<sign>\+|\-| )?(?P<alt>\#)?" - r"(?P<zero>0)?(?P<width>\d+)?(?P<grouping>\_|\,)?(?:\.(?P<precision>\d+))?" - r"(?P<type>b|c|d|e|E|f|F|g|G|n|o|s|x|X|%)?" - ) - - def __new__(cls, *args, **kwargs): - """ - When creating a new ANSIString, you may use a custom parser that has - the same attributes as the standard one, and you may declare the - string to be handled as already decoded. It is important not to double - decode strings, as escapes can only be respected once. - - Internally, ANSIString can also passes itself precached code/character - indexes and clean strings to avoid doing extra work when combining - ANSIStrings. - - """ - string = args[0] - if not isinstance(string, str): - string = to_str(string) - parser = kwargs.get("parser", ANSI_PARSER) - decoded = kwargs.get("decoded", False) or hasattr(string, "_raw_string") - code_indexes = kwargs.pop("code_indexes", None) - char_indexes = kwargs.pop("char_indexes", None) - clean_string = kwargs.pop("clean_string", None) - # All True, or All False, not just one. - checks = [x is None for x in [code_indexes, char_indexes, clean_string]] - if not len(set(checks)) == 1: - raise ValueError( - "You must specify code_indexes, char_indexes, " - "and clean_string together, or not at all." - ) - if not all(checks): - decoded = True - if not decoded: - # Completely new ANSI String - clean_string = parser.parse_ansi(string, strip_ansi=True, mxp=True) - string = parser.parse_ansi(string, xterm256=True, mxp=True) - elif clean_string is not None: - # We have an explicit clean string. - pass - elif hasattr(string, "_clean_string"): - # It's already an ANSIString - clean_string = string._clean_string - code_indexes = string._code_indexes - char_indexes = string._char_indexes - string = string._raw_string - else: - # It's a string that has been pre-ansi decoded. - clean_string = parser.strip_raw_codes(string) - - if not isinstance(string, str): - string = string.decode("utf-8") - - ansi_string = super().__new__(ANSIString, to_str(clean_string)) - ansi_string._raw_string = string - ansi_string._clean_string = clean_string - ansi_string._code_indexes = code_indexes - ansi_string._char_indexes = char_indexes - return ansi_string - - def __str__(self): - return self._raw_string - - def __format__(self, format_spec): - """ - This magic method covers ANSIString's behavior within a str.format() or f-string. - - Current features supported: fill, align, width. - - Args: - format_spec (str): The format specification passed by f-string or str.format(). This is a string such as - "0<30" which would mean "left justify to 30, filling with zeros". The full specification can be found - at https://docs.python.org/3/library/string.html#formatspec - - Returns: - ansi_str (str): The formatted ANSIString's .raw() form, for display. - """ - # This calls the compiled regex stored on ANSIString's class to analyze the format spec. - # It returns a dictionary. - format_data = self.re_format.match(format_spec).groupdict() - clean = self.clean() - base_output = ANSIString(self.raw()) - align = format_data.get("align", "<") - fill = format_data.get("fill", " ") - - # Need to coerce width into an integer. We can be certain that it's numeric thanks to regex. - width = format_data.get("width", None) - if width is None: - width = len(clean) - else: - width = int(width) - - if align == "<": - base_output = self.ljust(width, fill) - elif align == ">": - base_output = self.rjust(width, fill) - elif align == "^": - base_output = self.center(width, fill) - elif align == "=": - pass - - # Return the raw string with ANSI markup, ready to be displayed. - return base_output.raw() - - def __repr__(self): - """ - Let's make the repr the command that would actually be used to - construct this object, for convenience and reference. - - """ - return "ANSIString(%s, decoded=True)" % repr(self._raw_string) - -
[docs] def __init__(self, *_, **kwargs): - """ - When the ANSIString is first initialized, a few internal variables - have to be set. - - The first is the parser. It is possible to replace Evennia's standard - ANSI parser with one of your own syntax if you wish, so long as it - implements the same interface. - - The second is the _raw_string. This is the original "dumb" string - with ansi escapes that ANSIString represents. - - The third thing to set is the _clean_string. This is a string that is - devoid of all ANSI Escapes. - - Finally, _code_indexes and _char_indexes are defined. These are lookup - tables for which characters in the raw string are related to ANSI - escapes, and which are for the readable text. - - """ - self.parser = kwargs.pop("parser", ANSI_PARSER) - super().__init__() - if self._code_indexes is None: - self._code_indexes, self._char_indexes = self._get_indexes()
- - @staticmethod - def _shifter(iterable, offset): - """ - Takes a list of integers, and produces a new one incrementing all - by a number. - - """ - if not offset: - return iterable - return [i + offset for i in iterable] - - @classmethod - def _adder(cls, first, second): - """ - Joins two ANSIStrings, preserving calculated info. - - """ - - raw_string = first._raw_string + second._raw_string - clean_string = first._clean_string + second._clean_string - code_indexes = first._code_indexes[:] - char_indexes = first._char_indexes[:] - code_indexes.extend(cls._shifter(second._code_indexes, len(first._raw_string))) - char_indexes.extend(cls._shifter(second._char_indexes, len(first._raw_string))) - return ANSIString( - raw_string, - code_indexes=code_indexes, - char_indexes=char_indexes, - clean_string=clean_string, - ) - - def __add__(self, other): - """ - We have to be careful when adding two strings not to reprocess things - that don't need to be reprocessed, lest we end up with escapes being - interpreted literally. - - """ - if not isinstance(other, str): - return NotImplemented - if not isinstance(other, ANSIString): - other = ANSIString(other) - return self._adder(self, other) - - def __radd__(self, other): - """ - Likewise, if we're on the other end. - - """ - if not isinstance(other, str): - return NotImplemented - if not isinstance(other, ANSIString): - other = ANSIString(other) - return self._adder(other, self) - - def __getslice__(self, i, j): - """ - This function is deprecated, so we just make it call the proper - function. - - """ - return self.__getitem__(slice(i, j)) - - def _slice(self, slc): - """ - This function takes a slice() object. - - Slices have to be handled specially. Not only are they able to specify - a start and end with [x:y], but many forget that they can also specify - an interval with [x:y:z]. As a result, not only do we have to track - the ANSI Escapes that have played before the start of the slice, we - must also replay any in these intervals, should they exist. - - Thankfully, slicing the _char_indexes table gives us the actual - indexes that need slicing in the raw string. We can check between - those indexes to figure out what escape characters need to be - replayed. - - """ - char_indexes = self._char_indexes - slice_indexes = char_indexes[slc] - # If it's the end of the string, we need to append final color codes. - if not slice_indexes: - # if we find no characters it may be because we are just outside - # of the interval, using an open-ended slice. We must replay all - # of the escape characters until/after this point. - if char_indexes: - if slc.start is None and slc.stop is None: - # a [:] slice of only escape characters - return ANSIString(self._raw_string[slc]) - if slc.start is None: - # this is a [:x] slice - return ANSIString(self._raw_string[:char_indexes[0]]) - if slc.stop is None: - # a [x:] slice - return ANSIString(self._raw_string[char_indexes[-1] + 1:]) - return ANSIString("") - try: - string = self[slc.start or 0]._raw_string - except IndexError: - return ANSIString("") - last_mark = slice_indexes[0] - # Check between the slice intervals for escape sequences. - i = None - for i in slice_indexes[1:]: - for index in range(last_mark, i): - if index in self._code_indexes: - string += self._raw_string[index] - last_mark = i - try: - string += self._raw_string[i] - except IndexError: - # raw_string not long enough - pass - if i is not None: - append_tail = self._get_interleving(char_indexes.index(i) + 1) - else: - append_tail = "" - return ANSIString(string + append_tail, decoded=True) - - def __getitem__(self, item): - """ - Gateway for slices and getting specific indexes in the ANSIString. If - this is a regexable ANSIString, it will get the data from the raw - string instead, bypassing ANSIString's intelligent escape skipping, - for reasons explained in the __new__ method's docstring. - - """ - if isinstance(item, slice): - # Slices must be handled specially. - return self._slice(item) - try: - self._char_indexes[item] - except IndexError: - raise IndexError("ANSIString Index out of range") - # Get character codes after the index as well. - if self._char_indexes[-1] == self._char_indexes[item]: - append_tail = self._get_interleving(item + 1) - else: - append_tail = "" - item = self._char_indexes[item] - - clean = self._raw_string[item] - result = "" - # Get the character they're after, and replay all escape sequences - # previous to it. - for index in range(0, item + 1): - if index in self._code_indexes: - result += self._raw_string[index] - return ANSIString(result + clean + append_tail, decoded=True) - -
[docs] def clean(self): - """ - Return a string object *without* the ANSI escapes. - - Returns: - clean_string (str): A unicode object with no ANSI escapes. - - """ - return self._clean_string
- -
[docs] def raw(self): - """ - Return a string object with the ANSI escapes. - - Returns: - raw (str): A unicode object *with* the raw ANSI escape sequences. - - """ - return self._raw_string
- -
[docs] def partition(self, sep, reverse=False): - """ - Splits once into three sections (with the separator being the middle section) - - We use the same techniques we used in split() to make sure each are - colored. - - Args: - sep (str): The separator to split the string on. - reverse (boolean): Whether to split the string on the last - occurrence of the separator rather than the first. - - Returns: - ANSIString: The part of the string before the separator - ANSIString: The separator itself - ANSIString: The part of the string after the separator. - - """ - if hasattr(sep, "_clean_string"): - sep = sep.clean() - if reverse: - parent_result = self._clean_string.rpartition(sep) - else: - parent_result = self._clean_string.partition(sep) - current_index = 0 - result = tuple() - for section in parent_result: - result += (self[current_index : current_index + len(section)],) - current_index += len(section) - return result
- - def _get_indexes(self): - """ - Two tables need to be made, one which contains the indexes of all - readable characters, and one which contains the indexes of all ANSI - escapes. It's important to remember that ANSI escapes require more - that one character at a time, though no readable character needs more - than one character, since the string base class abstracts that away - from us. However, several readable characters can be placed in a row. - - We must use regexes here to figure out where all the escape sequences - are hiding in the string. Then we use the ranges of their starts and - ends to create a final, comprehensive list of all indexes which are - dedicated to code, and all dedicated to text. - - It's possible that only one of these tables is actually needed, the - other assumed to be what isn't in the first. - - """ - - code_indexes = [] - for match in self.parser.ansi_regex.finditer(self._raw_string): - code_indexes.extend(list(range(match.start(), match.end()))) - if not code_indexes: - # Plain string, no ANSI codes. - return code_indexes, list(range(0, len(self._raw_string))) - # all indexes not occupied by ansi codes are normal characters - char_indexes = [i for i in range(len(self._raw_string)) if i not in code_indexes] - return code_indexes, char_indexes - - def _get_interleving(self, index): - """ - Get the code characters from the given slice end to the next - character. - - """ - try: - index = self._char_indexes[index - 1] - except IndexError: - return "" - s = "" - while True: - index += 1 - if index in self._char_indexes: - break - elif index in self._code_indexes: - s += self._raw_string[index] - else: - break - return s - - def __mul__(self, other): - """ - Multiplication method. Implemented for performance reasons. - - """ - if not isinstance(other, int): - return NotImplemented - raw_string = self._raw_string * other - clean_string = self._clean_string * other - code_indexes = self._code_indexes[:] - char_indexes = self._char_indexes[:] - for i in range(other): - code_indexes.extend(self._shifter(self._code_indexes, i * len(self._raw_string))) - char_indexes.extend(self._shifter(self._char_indexes, i * len(self._raw_string))) - return ANSIString( - raw_string, - code_indexes=code_indexes, - char_indexes=char_indexes, - clean_string=clean_string, - ) - - def __rmul__(self, other): - return self.__mul__(other) - -
[docs] def split(self, by=None, maxsplit=-1): - """ - Splits a string based on a separator. - - Stolen from PyPy's pure Python string implementation, tweaked for - ANSIString. - - PyPy is distributed under the MIT licence. - http://opensource.org/licenses/MIT - - Args: - by (str): A string to search for which will be used to split - the string. For instance, ',' for 'Hello,world' would - result in ['Hello', 'world'] - maxsplit (int): The maximum number of times to split the string. - For example, a maxsplit of 2 with a by of ',' on the string - 'Hello,world,test,string' would result in - ['Hello', 'world', 'test,string'] - Returns: - result (list of ANSIStrings): A list of ANSIStrings derived from - this string. - - """ - drop_spaces = by is None - if drop_spaces: - by = " " - - bylen = len(by) - if bylen == 0: - raise ValueError("empty separator") - - res = [] - start = 0 - while maxsplit != 0: - next = self._clean_string.find(by, start) - if next < 0: - break - # Get character codes after the index as well. - res.append(self[start:next]) - start = next + bylen - maxsplit -= 1 # NB. if it's already < 0, it stays < 0 - - res.append(self[start : len(self)]) - if drop_spaces: - return [part for part in res if part != ""] - return res
- -
[docs] def rsplit(self, by=None, maxsplit=-1): - """ - Like split, but starts from the end of the string rather than the - beginning. - - Stolen from PyPy's pure Python string implementation, tweaked for - ANSIString. - - PyPy is distributed under the MIT licence. - http://opensource.org/licenses/MIT - - Args: - by (str): A string to search for which will be used to split - the string. For instance, ',' for 'Hello,world' would - result in ['Hello', 'world'] - maxsplit (int): The maximum number of times to split the string. - For example, a maxsplit of 2 with a by of ',' on the string - 'Hello,world,test,string' would result in - ['Hello,world', 'test', 'string'] - Returns: - result (list of ANSIStrings): A list of ANSIStrings derived from - this string. - - """ - res = [] - end = len(self) - drop_spaces = by is None - if drop_spaces: - by = " " - bylen = len(by) - if bylen == 0: - raise ValueError("empty separator") - - while maxsplit != 0: - next = self._clean_string.rfind(by, 0, end) - if next < 0: - break - # Get character codes after the index as well. - res.append(self[next + bylen : end]) - end = next - maxsplit -= 1 # NB. if it's already < 0, it stays < 0 - - res.append(self[:end]) - res.reverse() - if drop_spaces: - return [part for part in res if part != ""] - return res
- -
[docs] def strip(self, chars=None): - """ - Strip from both ends, taking ANSI markers into account. - - Args: - chars (str, optional): A string containing individual characters - to strip off of both ends of the string. By default, any blank - spaces are trimmed. - Returns: - result (ANSIString): A new ANSIString with the ends trimmed of the - relevant characters. - - """ - clean = self._clean_string - raw = self._raw_string - - # count continuous sequence of chars from left and right - nlen = len(clean) - nlstripped = nlen - len(clean.lstrip(chars)) - nrstripped = nlen - len(clean.rstrip(chars)) - - # within the stripped regions, only retain parts of the raw - # string *not* matching the clean string (these are ansi/mxp tags) - lstripped = "" - ic, ir1 = 0, 0 - while nlstripped: - if ic >= nlstripped: - break - elif raw[ir1] != clean[ic]: - lstripped += raw[ir1] - else: - ic += 1 - ir1 += 1 - rstripped = "" - ic, ir2 = nlen - 1, len(raw) - 1 - while nrstripped: - if nlen - ic > nrstripped: - break - elif raw[ir2] != clean[ic]: - rstripped += raw[ir2] - else: - ic -= 1 - ir2 -= 1 - rstripped = rstripped[::-1] - return ANSIString(lstripped + raw[ir1 : ir2 + 1] + rstripped)
- -
[docs] def lstrip(self, chars=None): - """ - Strip from the left, taking ANSI markers into account. - - Args: - chars (str, optional): A string containing individual characters - to strip off of the left end of the string. By default, any - blank spaces are trimmed. - Returns: - result (ANSIString): A new ANSIString with the left end trimmed of - the relevant characters. - - """ - clean = self._clean_string - raw = self._raw_string - - # count continuous sequence of chars from left and right - nlen = len(clean) - nlstripped = nlen - len(clean.lstrip(chars)) - # within the stripped regions, only retain parts of the raw - # string *not* matching the clean string (these are ansi/mxp tags) - lstripped = "" - ic, ir1 = 0, 0 - while nlstripped: - if ic >= nlstripped: - break - elif raw[ir1] != clean[ic]: - lstripped += raw[ir1] - else: - ic += 1 - ir1 += 1 - return ANSIString(lstripped + raw[ir1:])
- -
[docs] def rstrip(self, chars=None): - """ - Strip from the right, taking ANSI markers into account. - - Args: - chars (str, optional): A string containing individual characters - to strip off of the right end of the string. By default, any - blank spaces are trimmed. - Returns: - result (ANSIString): A new ANSIString with the right end trimmed of - the relevant characters. - - """ - clean = self._clean_string - raw = self._raw_string - nlen = len(clean) - nrstripped = nlen - len(clean.rstrip(chars)) - rstripped = "" - ic, ir2 = nlen - 1, len(raw) - 1 - while nrstripped: - if nlen - ic > nrstripped: - break - elif raw[ir2] != clean[ic]: - rstripped += raw[ir2] - else: - ic -= 1 - ir2 -= 1 - rstripped = rstripped[::-1] - return ANSIString(raw[: ir2 + 1] + rstripped)
- -
[docs] def join(self, iterable): - """ - Joins together strings in an iterable, using this string between each - one. - - NOTE: This should always be used for joining strings when ANSIStrings - are involved. Otherwise color information will be discarded by python, - due to details in the C implementation of strings. - - Args: - iterable (list of strings): A list of strings to join together - - Returns: - ANSIString: A single string with all of the iterable's - contents concatenated, with this string between each. - - Examples: - :: - - >>> ANSIString(', ').join(['up', 'right', 'left', 'down']) - ANSIString('up, right, left, down') - - """ - result = ANSIString("") - last_item = None - for item in iterable: - if last_item is not None: - result += self._raw_string - if not isinstance(item, ANSIString): - item = ANSIString(item) - result += item - last_item = item - return result
- - def _filler(self, char, amount): - """ - Generate a line of characters in a more efficient way than just adding - ANSIStrings. - - """ - if not isinstance(char, ANSIString): - line = char * amount - return ANSIString( - char * amount, - code_indexes=[], - char_indexes=list(range(0, len(line))), - clean_string=char, - ) - try: - start = char._code_indexes[0] - except IndexError: - start = None - end = char._char_indexes[0] - prefix = char._raw_string[start:end] - postfix = char._raw_string[end + 1 :] - line = char._clean_string * amount - code_indexes = [i for i in range(0, len(prefix))] - length = len(prefix) + len(line) - code_indexes.extend([i for i in range(length, length + len(postfix))]) - char_indexes = self._shifter(list(range(0, len(line))), len(prefix)) - raw_string = prefix + line + postfix - return ANSIString( - raw_string, clean_string=line, char_indexes=char_indexes, code_indexes=code_indexes - ) - - # The following methods should not be called with the '_difference' argument explicitly. This is - # data provided by the wrapper _spacing_preflight. -
[docs] @_spacing_preflight - def center(self, width, fillchar, _difference): - """ - Center some text with some spaces padding both sides. - - Args: - width (int): The target width of the output string. - fillchar (str): A single character string to pad the output string - with. - Returns: - result (ANSIString): A string padded on both ends with fillchar. - - """ - remainder = _difference % 2 - _difference //= 2 - spacing = self._filler(fillchar, _difference) - result = spacing + self + spacing + self._filler(fillchar, remainder) - return result
- -
[docs] @_spacing_preflight - def ljust(self, width, fillchar, _difference): - """ - Left justify some text. - - Args: - width (int): The target width of the output string. - fillchar (str): A single character string to pad the output string - with. - Returns: - result (ANSIString): A string padded on the right with fillchar. - - """ - return self + self._filler(fillchar, _difference)
- -
[docs] @_spacing_preflight - def rjust(self, width, fillchar, _difference): - """ - Right justify some text. - - Args: - width (int): The target width of the output string. - fillchar (str): A single character string to pad the output string - with. - Returns: - result (ANSIString): A string padded on the left with fillchar. - - """ - return self._filler(fillchar, _difference) + self
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/batchprocessors.html b/docs/0.9.5/_modules/evennia/utils/batchprocessors.html deleted file mode 100644 index fba11286dc..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/batchprocessors.html +++ /dev/null @@ -1,533 +0,0 @@ - - - - - - - - evennia.utils.batchprocessors — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.batchprocessors

-"""
-This module contains the core methods for the Batch-command- and
-Batch-code-processors respectively. In short, these are two different
-ways to build a game world using a normal text-editor without having
-to do so 'on the fly' in-game. They also serve as an automatic backup
-so you can quickly recreate a world also after a server reset. The
-functions in this module is meant to form the backbone of a system
-called and accessed through game commands.
-
-The Batch-command processor is the simplest. It simply runs a list of
-in-game commands in sequence by reading them from a text file. The
-advantage of this is that the builder only need to remember the normal
-in-game commands. They are also executing with full permission checks
-etc, making it relatively safe for builders to use. The drawback is
-that in-game there is really a builder-character walking around
-building things, and it can be important to create rooms and objects
-in the right order, so the character can move between them. Also
-objects that affects players (such as mobs, dark rooms etc) will
-affect the building character too, requiring extra care to turn
-off/on.
-
-The Batch-code processor is a more advanced system that accepts full
-Python code, executing in chunks. The advantage of this is much more
-power; practically anything imaginable can be coded and handled using
-the batch-code processor. There is no in-game character that moves and
-that can be affected by what is being built - the database is
-populated on the fly. The drawback is safety and entry threshold - the
-code is executed as would any server code, without mud-specific
-permission-checks, and you have full access to modifying objects
-etc. You also need to know Python and Evennia's API. Hence it's
-recommended that the batch-code processor is limited only to
-superusers or highly trusted staff.
-
-Batch-Command processor file syntax
------------------------------------
-
-The batch-command processor accepts 'batchcommand files' e.g
-`batch.ev`, containing a sequence of valid Evennia commands in a
-simple format. The engine runs each command in sequence, as if they
-had been run at the game prompt.
-
-Each Evennia command must be delimited by a line comment to mark its
-end. This way entire game worlds can be created and planned offline; it is
-especially useful in order to create long room descriptions where a
-real offline text editor is often much better than any online text
-editor or prompt.
-
-There is only one batchcommand-specific entry to use in a batch-command
-files (all others are just like in-game commands):
-
-- `#INSERT path.batchcmdfile` - this as the first entry on a line will
-  import and run a batch.ev file in this position, as if it was
-  written in this file.
-
-
-Example of batch.ev file:
-::
-
-    # batch file
-    # all lines starting with # are comments; they also indicate
-    # that a command definition is over.
-
-    @create box
-
-    # this comment ends the @create command.
-
-    @set box/desc = A large box.
-
-    Inside are some scattered piles of clothing.
-
-
-    It seems the bottom of the box is a bit loose.
-
-    # Again, this comment indicates the @set command is over. Note how
-    # the description could be freely added. Excess whitespace on a line
-    # is ignored.  An empty line in the command definition is parsed as a \n
-    # (so two empty lines becomes a new paragraph).
-
-    @teleport #221
-
-    # (Assuming #221 is a warehouse or something.)
-    # (remember, this comment ends the @teleport command! Don'f forget it)
-
-    # Example of importing another file at this point.
-    #INSERT examples.batch
-
-    @drop box
-
-    # Done, the box is in the warehouse! (this last comment is not necessary to
-    # close the @drop command since it's the end of the file)
-
-
-An example batch file is `contrib/examples/batch_example.ev`.
-
-
-Batch-Code processor file syntax
---------------------------------
-
-The Batch-code processor accepts full python modules (e.g. `batch.py`)
-that looks identical to normal Python files. The difference from
-importing and running any Python module is that the batch-code module
-is loaded as a file and executed directly, so changes to the file will
-apply immediately without a server @reload.
-
-Optionally, one can add some special commented tokens to split the
-execution of the code for the benefit of the batchprocessor's
-interactive- and debug-modes. This allows to conveniently step through
-the code and re-run sections of it easily during development.
-
-Code blocks are marked by commented tokens alone on a line:
-
-- `#HEADER` - This denotes code that should be pasted at the top of all
-  other code. Multiple HEADER statements - regardless of where
-  it exists in the file - is the same as one big block.
-  Observe that changes to variables made in one block is not
-  preserved between blocks!
-- `#CODE` - This designates a code block that will be executed like a
-  stand-alone piece of code together with any HEADER(s)
-  defined. It is mainly used as a way to mark stop points for
-  the interactive mode of the batchprocessor. If no CODE block
-  is defined in the module, the entire module (including HEADERS)
-  is assumed to be a CODE block.
-- `#INSERT path.filename` - This imports another batch_code.py file and
-  runs it in the given position. The inserted file will retain
-  its own HEADERs which will not be mixed with the headers of
-  this file.
-
-Importing works as normal. The following variables are automatically
-made available in the script namespace.
-
-- `caller` -  The object executing the batchscript
-- `DEBUG` - This is a boolean marking if the batchprocessor is running
-  in debug mode. It can be checked to e.g. delete created objects
-  when running a CODE block multiple times during testing.
-  (avoids creating a slew of same-named db objects)
-
-Example batch.py file:
-::
-
-    #HEADER
-
-    from django.conf import settings
-    from evennia.utils import create
-    from types import basetypes
-
-    GOLD = 10
-
-    #CODE
-
-    obj = create.create_object(basetypes.Object)
-    obj2 = create.create_object(basetypes.Object)
-    obj.location = caller.location
-    obj.db.gold = GOLD
-    caller.msg("The object was created!")
-
-    if DEBUG:
-        obj.delete()
-        obj2.delete()
-
-    #INSERT another_batch_file
-
-    #CODE
-
-    script = create.create_script()
-
-----
-
-"""
-import re
-import codecs
-import traceback
-import sys
-from django.conf import settings
-from evennia.utils import utils
-
-_ENCODINGS = settings.ENCODINGS
-_RE_INSERT = re.compile(r"^\#INSERT (.*)$", re.MULTILINE)
-_RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
-_RE_CMD_SPLIT = re.compile(r"^\#.*?$", re.MULTILINE)
-_RE_CODE_OR_HEADER = re.compile(
-    r"((?:\A|^)#CODE|(?:/A|^)#HEADER|\A)(.*?)$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)",
-    re.MULTILINE + re.DOTALL,
-)
-
-
-# -------------------------------------------------------------
-# Helper function
-# -------------------------------------------------------------
-
-
-
[docs]def read_batchfile(pythonpath, file_ending=".py"): - """ - This reads the contents of a batch-file. Filename is considered - to be a python path to a batch file relative the directory - specified in `settings.py`. - - file_ending specify which batchfile ending should be assumed (.ev - or .py). The ending should not be included in the python path. - - Args: - pythonpath (str): A dot-python path to a file. - file_ending (str): The file ending of this file (.ev or .py) - - Returns: - str: The text content of the batch file. - - Raises: - IOError: If problems reading file. - - """ - - # find all possible absolute paths - abspaths = utils.pypath_to_realpath(pythonpath, file_ending, settings.BASE_BATCHPROCESS_PATHS) - if not abspaths: - raise IOError("Absolute batchcmd paths could not be found.") - text = None - decoderr = [] - for abspath in abspaths: - # try different paths, until we get a match - # we read the file directly into string. - for file_encoding in _ENCODINGS: - # try different encodings, in order - try: - with codecs.open(abspath, "r", encoding=file_encoding) as fobj: - text = fobj.read() - except (ValueError, UnicodeDecodeError) as e: - # this means an encoding error; try another encoding - decoderr.append(str(e)) - continue - break - if not text and decoderr: - raise UnicodeDecodeError("\n".join(decoderr), bytearray(), 0, 0, "") - - return text
- - -# ------------------------------------------------------------- -# -# Batch-command processor -# -# ------------------------------------------------------------- - - -
[docs]class BatchCommandProcessor(object): - """ - This class implements a batch-command processor. - - """ - -
[docs] def parse_file(self, pythonpath): - """ - This parses the lines of a batchfile according to the following - rules: - - 1. `#` at the beginning of a line marks the end of the command before - it. It is also a comment and any number of # can exist on - subsequent lines (but not inside comments). - 2. `#INSERT` at the beginning of a line imports another - batch-cmd file file and pastes it into the batch file as if - it was written there. - 3. Commands are placed alone at the beginning of a line and their - arguments are considered to be everything following (on any - number of lines) until the next comment line beginning with #. - 4. Newlines are ignored in command definitions - 5. A completely empty line in a command line definition is condered - a newline (so two empty lines is a paragraph). - 6. Excess spaces and indents inside arguments are stripped. - - """ - - text = "".join(read_batchfile(pythonpath, file_ending=".ev")) - - def replace_insert(match): - """Map replace entries""" - try: - path = match.group(1) - return "\n#\n".join(self.parse_file(path)) - except IOError as err: - raise IOError("#INSERT {} failed.".format(path)) - - text = _RE_INSERT.sub(replace_insert, text) - commands = _RE_CMD_SPLIT.split(text) - commands = [c.strip("\r\n") for c in commands] - commands = [c for c in commands if c] - - return commands
- - -# ------------------------------------------------------------- -# -# Batch-code processor -# -# ------------------------------------------------------------- - - -
[docs]def tb_filename(tb): - """Helper to get filename from traceback""" - return tb.tb_frame.f_code.co_filename
- - -
[docs]def tb_iter(tb): - """Traceback iterator.""" - while tb is not None: - yield tb - tb = tb.tb_next
- - -
[docs]class BatchCodeProcessor(object): - """ - This implements a batch-code processor - - """ - -
[docs] def parse_file(self, pythonpath): - """ - This parses the lines of a batchfile according to the following - rules: - - Args: - pythonpath (str): The dot-python path to the file. - - Returns: - codeblocks (list): A list of all #CODE blocks, each with - prepended #HEADER data. If no #CODE blocks were found, - this will be a list of one element. - - Notes: - 1. Code before a #CODE/HEADER block are considered part of - the first code/header block or is the ONLY block if no - #CODE/HEADER blocks are defined. - 2. Lines starting with #HEADER starts a header block (ends other blocks) - 3. Lines starting with #CODE begins a code block (ends other blocks) - 4. Lines starting with #INSERT are on form #INSERT filename. Code from - this file are processed with their headers *separately* before - being inserted at the point of the #INSERT. - 5. Code after the last block is considered part of the last header/code - block - - """ - - text = "".join(read_batchfile(pythonpath, file_ending=".py")) - - def replace_insert(match): - """Run parse_file on the import before sub:ing it into this file""" - path = match.group(1) - try: - return "# batchcode insert (%s):" % path + "\n".join(self.parse_file(path)) - except IOError as err: - raise IOError("#INSERT {} failed.".format(path)) - - # process and then insert code from all #INSERTS - text = _RE_INSERT.sub(replace_insert, text) - - headers = [] - codes = [] - for imatch, match in enumerate(list(_RE_CODE_OR_HEADER.finditer(text))): - mtype = match.group(1).strip() - # we need to handle things differently at the start of the file - if mtype: - istart, iend = match.span(3) - else: - istart, iend = match.start(2), match.end(3) - code = text[istart:iend] - if mtype == "#HEADER": - headers.append(code) - else: # either #CODE or matching from start of file - codes.append(code) - - # join all headers together to one - header = "# batchcode header:\n%s\n\n" % "\n\n".join(headers) if headers else "" - # add header to each code block - codes = ["%s# batchcode code:\n%s" % (header, code) for code in codes] - return codes
- -
[docs] def code_exec(self, code, extra_environ=None, debug=False): - """ - Execute a single code block, including imports and appending - global vars. - - Args: - code (str): Code to run. - extra_environ (dict): Environment variables to run with code. - debug (bool, optional): Set the DEBUG variable in the execution - namespace. - - Returns: - err (str or None): An error code or None (ok). - - """ - # define the execution environment - environdict = {"settings_module": settings, "DEBUG": debug} - for key, value in extra_environ.items(): - environdict[key] = value - - # initializing the django settings at the top of code - code = ( - "# batchcode evennia initialization: \n" - "try: settings_module.configure()\n" - "except RuntimeError: pass\n" - "finally: del settings_module\n\n%s" % code - ) - - # execute the block - try: - exec(code, environdict) - except Exception: - etype, value, tb = sys.exc_info() - - fname = tb_filename(tb) - for tb in tb_iter(tb): - if fname != tb_filename(tb): - break - lineno = tb.tb_lineno - 1 - err = "" - for iline, line in enumerate(code.split("\n")): - if iline == lineno: - err += "\n|w%02i|n: %s" % (iline + 1, line) - elif lineno - 5 < iline < lineno + 5: - err += "\n%02i: %s" % (iline + 1, line) - - err += "\n".join(traceback.format_exception(etype, value, tb)) - return err - return None
- - -BATCHCMD = BatchCommandProcessor() -BATCHCODE = BatchCodeProcessor() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/containers.html b/docs/0.9.5/_modules/evennia/utils/containers.html deleted file mode 100644 index b6b7be0c6d..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/containers.html +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - - evennia.utils.containers — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.containers

-"""
-Containers
-
-Containers are storage classes usually initialized from a setting. They
-represent Singletons and acts as a convenient place to find resources (
-available as properties on the singleton)
-
-evennia.GLOBAL_SCRIPTS
-evennia.OPTION_CLASSES
-
-"""
-
-
-from django.conf import settings
-from evennia.utils.utils import class_from_module, callables_from_module
-from evennia.utils import logger
-
-
-SCRIPTDB = None
-
-
-
[docs]class Container(object): - """ - Base container class. A container is simply a storage object whose - properties can be acquired as a property on it. This is generally - considered a read-only affair. - - The container is initialized by a list of modules containing callables. - - """ - - storage_modules = [] - -
[docs] def __init__(self): - """ - Read data from module. - - """ - self.loaded_data = None
- -
[docs] def load_data(self): - """ - Delayed import to avoid eventual circular imports from inside - the storage modules. - - """ - if self.loaded_data is None: - self.loaded_data = {} - for module in self.storage_modules: - self.loaded_data.update(callables_from_module(module))
- - def __getattr__(self, key): - return self.get(key) - -
[docs] def get(self, key, default=None): - """ - Retrive data by key (in case of not knowing it beforehand). - - Args: - key (str): The name of the script. - default (any, optional): Value to return if key is not found. - - Returns: - any (any): The data loaded on this container. - - """ - self.load_data() - return self.loaded_data.get(key, default)
- -
[docs] def all(self): - """ - Get all stored data - - Returns: - scripts (list): All global script objects stored on the container. - - """ - self.load_data() - return list(self.loaded_data.values())
- - -
[docs]class OptionContainer(Container): - """ - Loads and stores the final list of OPTION CLASSES. - - Can access these as properties or dictionary-contents. - """ - - storage_modules = settings.OPTION_CLASS_MODULES
- - -
[docs]class GlobalScriptContainer(Container): - """ - Simple Handler object loaded by the Evennia API to contain and manage a - game's Global Scripts. This will list global Scripts created on their own - but will also auto-(re)create scripts defined in `settings.GLOBAL_SCRIPTS`. - - Example: - import evennia - evennia.GLOBAL_SCRIPTS.scriptname - - Note: - This does not use much of the BaseContainer since it's not loading - callables from settings but a custom dict of tuples. - - """ - -
[docs] def __init__(self): - """ - Note: We must delay loading of typeclasses since this module may get - initialized before Scripts are actually initialized. - - """ - self.typeclass_storage = None - self.loaded_data = { - key: {} if data is None else data for key, data in settings.GLOBAL_SCRIPTS.items() - }
- - def _get_scripts(self, key=None, default=None): - global SCRIPTDB - if not SCRIPTDB: - from evennia.scripts.models import ScriptDB as SCRIPTDB - if key: - try: - return SCRIPTDB.objects.get(db_key__exact=key, db_obj__isnull=True) - except SCRIPTDB.DoesNotExist: - return default - else: - return SCRIPTDB.objects.filter(db_obj__isnull=True) - - def _load_script(self, key): - self.load_data() - - typeclass = self.typeclass_storage[key] - found = typeclass.objects.filter(db_key=key).first() - interval = self.loaded_data[key].get("interval", None) - start_delay = self.loaded_data[key].get("start_delay", None) - repeats = self.loaded_data[key].get("repeats", 0) - desc = self.loaded_data[key].get("desc", "") - - if not found: - logger.log_info(f"GLOBAL_SCRIPTS: (Re)creating {key} ({typeclass}).") - new_script, errors = typeclass.create( - key=key, - persistent=True, - interval=interval, - start_delay=start_delay, - repeats=repeats, - desc=desc, - ) - if errors: - logger.log_err("\n".join(errors)) - return None - - new_script.start() - return new_script - - if ( - (found.interval != interval) - or (found.start_delay != start_delay) - or (found.repeats != repeats) - ): - found.restart(interval=interval, start_delay=start_delay, repeats=repeats) - if found.desc != desc: - found.desc = desc - return found - -
[docs] def start(self): - """ - Called last in evennia.__init__ to initialize the container late - (after script typeclasses have finished loading). - - We include all global scripts in the handler and - make sure to auto-load time-based scripts. - - """ - # populate self.typeclass_storage - self.load_data() - - # start registered scripts - for key in self.loaded_data: - self._load_script(key)
- -
[docs] def load_data(self): - """ - This delayed import avoids trying to load Scripts before they are - initialized. - - """ - if self.typeclass_storage is None: - self.typeclass_storage = {} - for key, data in self.loaded_data.items(): - try: - typeclass = data.get("typeclass", settings.BASE_SCRIPT_TYPECLASS) - self.typeclass_storage[key] = class_from_module(typeclass) - except ImportError as err: - logger.log_err( - f"GlobalScriptContainer could not start global script {key}: {err}" - )
- -
[docs] def get(self, key, default=None): - """ - Retrive data by key (in case of not knowing it beforehand). Any - scripts that are in settings.GLOBAL_SCRIPTS that are not found - will be recreated on-demand. - - Args: - key (str): The name of the script. - default (any, optional): Value to return if key is not found - at all on this container (i.e it cannot be loaded at all). - - Returns: - any (any): The data loaded on this container. - """ - res = self._get_scripts(key) - if not res: - if key in self.loaded_data: - # recreate if we have the info - return self._load_script(key) or default - return default - return res
- -
[docs] def all(self): - """ - Get all global scripts. Note that this will not auto-start - scripts defined in settings. - - Returns: - scripts (list): All global script objects stored on the container. - - """ - self.typeclass_storage = None - self.load_data() - for key in self.loaded_data: - self._load_script(key) - return self._get_scripts(None)
- - -# Create all singletons - -GLOBAL_SCRIPTS = GlobalScriptContainer() -OPTION_CLASSES = OptionContainer() -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/create.html b/docs/0.9.5/_modules/evennia/utils/create.html deleted file mode 100644 index 6809d10665..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/create.html +++ /dev/null @@ -1,700 +0,0 @@ - - - - - - - - evennia.utils.create — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.create

-"""
-This module gathers all the essential database-creation
-functions for the game engine's various object types.
-
-Only objects created 'stand-alone' are in here, e.g. object Attributes
-are always created directly through their respective objects.
-
-Each creation_* function also has an alias named for the entity being
-created, such as create_object() and object().  This is for
-consistency with the utils.search module and allows you to do the
-shorter "create.object()".
-
-The respective object managers hold more methods for manipulating and
-searching objects already existing in the database.
-
-Models covered:
- Objects
- Scripts
- Help
- Message
- Channel
- Accounts
-"""
-from django.conf import settings
-from django.db import IntegrityError
-from django.utils import timezone
-from evennia.utils import logger
-from evennia.server import signals
-from evennia.utils.utils import make_iter, class_from_module, dbid_to_obj
-
-# delayed imports
-_User = None
-_ObjectDB = None
-_Object = None
-_Script = None
-_ScriptDB = None
-_HelpEntry = None
-_Msg = None
-_Account = None
-_AccountDB = None
-_to_object = None
-_ChannelDB = None
-_channelhandler = None
-
-
-# limit symbol import from API
-__all__ = (
-    "create_object",
-    "create_script",
-    "create_help_entry",
-    "create_message",
-    "create_channel",
-    "create_account",
-)
-
-_GA = object.__getattribute__
-
-#
-# Game Object creation
-
-
-
[docs]def create_object( - typeclass=None, - key=None, - location=None, - home=None, - permissions=None, - locks=None, - aliases=None, - tags=None, - destination=None, - report_to=None, - nohome=False, - attributes=None, - nattributes=None, -): - """ - - Create a new in-game object. - - Keyword Args: - typeclass (class or str): Class or python path to a typeclass. - key (str): Name of the new object. If not set, a name of - #dbref will be set. - home (Object or str): Obj or #dbref to use as the object's - home location. - permissions (list): A list of permission strings or tuples (permstring, category). - locks (str): one or more lockstrings, separated by semicolons. - aliases (list): A list of alternative keys or tuples (aliasstring, category). - tags (list): List of tag keys or tuples (tagkey, category) or (tagkey, category, data). - destination (Object or str): Obj or #dbref to use as an Exit's - target. - report_to (Object): The object to return error messages to. - nohome (bool): This allows the creation of objects without a - default home location; only used when creating the default - location itself or during unittests. - attributes (list): Tuples on the form (key, value) or (key, value, category), - (key, value, lockstring) or (key, value, lockstring, default_access). - to set as Attributes on the new object. - nattributes (list): Non-persistent tuples on the form (key, value). Note that - adding this rarely makes sense since this data will not survive a reload. - - Returns: - object (Object): A newly created object of the given typeclass. - - Raises: - ObjectDB.DoesNotExist: If trying to create an Object with - `location` or `home` that can't be found. - - """ - global _ObjectDB - if not _ObjectDB: - from evennia.objects.models import ObjectDB as _ObjectDB - - typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS - - # convenience converters to avoid common usage mistake - permissions = make_iter(permissions) if permissions is not None else None - locks = make_iter(locks) if locks is not None else None - aliases = make_iter(aliases) if aliases is not None else None - tags = make_iter(tags) if tags is not None else None - attributes = make_iter(attributes) if attributes is not None else None - - if isinstance(typeclass, str): - # a path is given. Load the actual typeclass - typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) - - # Setup input for the create command. We use ObjectDB as baseclass here - # to give us maximum freedom (the typeclasses will load - # correctly when each object is recovered). - - location = dbid_to_obj(location, _ObjectDB) - destination = dbid_to_obj(destination, _ObjectDB) - home = dbid_to_obj(home, _ObjectDB) - if not home: - try: - home = dbid_to_obj(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None - except _ObjectDB.DoesNotExist: - raise _ObjectDB.DoesNotExist( - "settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." - % settings.DEFAULT_HOME - ) - - # create new instance - new_object = typeclass( - db_key=key, - db_location=location, - db_destination=destination, - db_home=home, - db_typeclass_path=typeclass.path, - ) - # store the call signature for the signal - new_object._createdict = dict( - key=key, - location=location, - destination=destination, - home=home, - typeclass=typeclass.path, - permissions=permissions, - locks=locks, - aliases=aliases, - tags=tags, - report_to=report_to, - nohome=nohome, - attributes=attributes, - nattributes=nattributes, - ) - # this will trigger the save signal which in turn calls the - # at_first_save hook on the typeclass, where the _createdict can be - # used. - new_object.save() - - signals.SIGNAL_OBJECT_POST_CREATE.send(sender=new_object) - - return new_object
- - -# alias for create_object -object = create_object - - -# -# Script creation - - -
[docs]def create_script( - typeclass=None, - key=None, - obj=None, - account=None, - locks=None, - interval=None, - start_delay=None, - repeats=None, - persistent=None, - autostart=True, - report_to=None, - desc=None, - tags=None, - attributes=None, -): - """ - Create a new script. All scripts are a combination of a database - object that communicates with the database, and an typeclass that - 'decorates' the database object into being different types of - scripts. It's behaviour is similar to the game objects except - scripts has a time component and are more limited in scope. - - Keyword Args: - typeclass (class or str): Class or python path to a typeclass. - key (str): Name of the new object. If not set, a name of - #dbref will be set. - obj (Object): The entity on which this Script sits. If this - is `None`, we are creating a "global" script. - account (Account): The account on which this Script sits. It is - exclusiv to `obj`. - locks (str): one or more lockstrings, separated by semicolons. - interval (int): The triggering interval for this Script, in - seconds. If unset, the Script will not have a timing - component. - start_delay (bool): If `True`, will wait `interval` seconds - before triggering the first time. - repeats (int): The number of times to trigger before stopping. - If unset, will repeat indefinitely. - persistent (bool): If this Script survives a server shutdown - or not (all Scripts will survive a reload). - autostart (bool): If this Script will start immediately when - created or if the `start` method must be called explicitly. - report_to (Object): The object to return error messages to. - desc (str): Optional description of script - tags (list): List of tags or tuples (tag, category). - attributes (list): List of tuples `(key, value)`, `(key, value, category)`, - `(key, value, category, lockstring)` or - `(key, value, category, lockstring, default_access)`. - - Returns: - script (obj): An instance of the script created - - See evennia.scripts.manager for methods to manipulate existing - scripts in the database. - - """ - global _ScriptDB - if not _ScriptDB: - from evennia.scripts.models import ScriptDB as _ScriptDB - - typeclass = typeclass if typeclass else settings.BASE_SCRIPT_TYPECLASS - - if isinstance(typeclass, str): - # a path is given. Load the actual typeclass - typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) - - # validate input - kwarg = {} - if key: - kwarg["db_key"] = key - if account: - kwarg["db_account"] = dbid_to_obj(account, _AccountDB) - if obj: - kwarg["db_obj"] = dbid_to_obj(obj, _ObjectDB) - if interval: - kwarg["db_interval"] = max(0, interval) - if start_delay: - kwarg["db_start_delay"] = start_delay - if repeats: - kwarg["db_repeats"] = max(0, repeats) - if persistent: - kwarg["db_persistent"] = persistent - if desc: - kwarg["db_desc"] = desc - tags = make_iter(tags) if tags is not None else None - attributes = make_iter(attributes) if attributes is not None else None - - # create new instance - new_script = typeclass(**kwarg) - - # store the call signature for the signal - new_script._createdict = dict( - key=key, - obj=obj, - account=account, - locks=locks, - interval=interval, - start_delay=start_delay, - repeats=repeats, - persistent=persistent, - autostart=autostart, - report_to=report_to, - desc=desc, - tags=tags, - attributes=attributes, - ) - # this will trigger the save signal which in turn calls the - # at_first_save hook on the typeclass, where the _createdict - # can be used. - new_script.save() - - if not new_script.id: - # this happens in the case of having a repeating script with `repeats=1` and - # `start_delay=False` - the script will run once and immediately stop before save is over. - return None - - signals.SIGNAL_SCRIPT_POST_CREATE.send(sender=new_script) - - return new_script
- - -# alias -script = create_script - - -# -# Help entry creation -# - - -
[docs]def create_help_entry(key, entrytext, category="General", locks=None, aliases=None, tags=None): - """ - Create a static help entry in the help database. Note that Command - help entries are dynamic and directly taken from the __doc__ - entries of the command. The database-stored help entries are - intended for more general help on the game, more extensive info, - in-game setting information and so on. - - Args: - key (str): The name of the help entry. - entrytext (str): The body of te help entry - category (str, optional): The help category of the entry. - locks (str, optional): A lockstring to restrict access. - aliases (list of str, optional): List of alternative (likely shorter) keynames. - tags (lst, optional): List of tags or tuples `(tag, category)`. - - Returns: - help (HelpEntry): A newly created help entry. - - """ - global _HelpEntry - if not _HelpEntry: - from evennia.help.models import HelpEntry as _HelpEntry - - try: - new_help = _HelpEntry() - new_help.key = key - new_help.entrytext = entrytext - new_help.help_category = category - if locks: - new_help.locks.add(locks) - if aliases: - new_help.aliases.add(make_iter(aliases)) - if tags: - new_help.tags.batch_add(*tags) - new_help.save() - return new_help - except IntegrityError: - string = "Could not add help entry: key '%s' already exists." % key - logger.log_err(string) - return None - except Exception: - logger.log_trace() - return None - - signals.SIGNAL_HELPENTRY_POST_CREATE.send(sender=new_help)
- - -# alias -help_entry = create_help_entry - - -# -# Comm system methods - - -
[docs]def create_message( - senderobj, message, channels=None, receivers=None, locks=None, tags=None, header=None -): - """ - Create a new communication Msg. Msgs represent a unit of - database-persistent communication between entites. - - Args: - senderobj (Object or Account): The entity sending the Msg. - message (str): Text with the message. Eventual headers, titles - etc should all be included in this text string. Formatting - will be retained. - channels (Channel, key or list): A channel or a list of channels to - send to. The channels may be actual channel objects or their - unique key strings. - receivers (Object, Account, str or list): An Account/Object to send - to, or a list of them. May be Account objects or accountnames. - locks (str): Lock definition string. - tags (list): A list of tags or tuples `(tag, category)`. - header (str): Mime-type or other optional information for the message - - Notes: - The Comm system is created very open-ended, so it's fully possible - to let a message both go to several channels and to several - receivers at the same time, it's up to the command definitions to - limit this as desired. - - """ - global _Msg - if not _Msg: - from evennia.comms.models import Msg as _Msg - if not message: - # we don't allow empty messages. - return None - new_message = _Msg(db_message=message) - new_message.save() - for sender in make_iter(senderobj): - new_message.senders = sender - new_message.header = header - for channel in make_iter(channels): - new_message.channels = channel - for receiver in make_iter(receivers): - new_message.receivers = receiver - if locks: - new_message.locks.add(locks) - if tags: - new_message.tags.batch_add(*tags) - - new_message.save() - return new_message
- - -message = create_message -create_msg = create_message - - -
[docs]def create_channel( - key, aliases=None, desc=None, locks=None, keep_log=True, typeclass=None, tags=None -): - """ - Create A communication Channel. A Channel serves as a central hub - for distributing Msgs to groups of people without specifying the - receivers explicitly. Instead accounts may 'connect' to the channel - and follow the flow of messages. By default the channel allows - access to all old messages, but this can be turned off with the - keep_log switch. - - Args: - key (str): This must be unique. - - Keyword Args: - aliases (list of str): List of alternative (likely shorter) keynames. - desc (str): A description of the channel, for use in listings. - locks (str): Lockstring. - keep_log (bool): Log channel throughput. - typeclass (str or class): The typeclass of the Channel (not - often used). - tags (list): A list of tags or tuples `(tag, category)`. - - Returns: - channel (Channel): A newly created channel. - - """ - typeclass = typeclass if typeclass else settings.BASE_CHANNEL_TYPECLASS - - if isinstance(typeclass, str): - # a path is given. Load the actual typeclass - typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) - - # create new instance - new_channel = typeclass(db_key=key) - - # store call signature for the signal - new_channel._createdict = dict( - key=key, aliases=aliases, desc=desc, locks=locks, keep_log=keep_log, tags=tags - ) - - # this will trigger the save signal which in turn calls the - # at_first_save hook on the typeclass, where the _createdict can be - # used. - new_channel.save() - - signals.SIGNAL_CHANNEL_POST_CREATE.send(sender=new_channel) - - return new_channel
- - -channel = create_channel - - -# -# Account creation methods -# - - -
[docs]def create_account( - key, - email, - password, - typeclass=None, - is_superuser=False, - locks=None, - permissions=None, - tags=None, - attributes=None, - report_to=None, -): - """ - This creates a new account. - - Args: - key (str): The account's name. This should be unique. - email (str or None): Email on valid addr@addr.domain form. If - the empty string, will be set to None. - password (str): Password in cleartext. - - Keyword Args: - typeclass (str): The typeclass to use for the account. - is_superuser (bool): Wether or not this account is to be a superuser - locks (str): Lockstring. - permission (list): List of permission strings. - tags (list): List of Tags on form `(key, category[, data])` - attributes (list): List of Attributes on form - `(key, value [, category, [,lockstring [, default_pass]]])` - report_to (Object): An object with a msg() method to report - errors to. If not given, errors will be logged. - - Returns: - Account: The newly created Account. - Raises: - ValueError: If `key` already exists in database. - - - Notes: - Usually only the server admin should need to be superuser, all - other access levels can be handled with more fine-grained - permissions or groups. A superuser bypasses all lock checking - operations and is thus not suitable for play-testing the game. - - """ - global _AccountDB - if not _AccountDB: - from evennia.accounts.models import AccountDB as _AccountDB - - typeclass = typeclass if typeclass else settings.BASE_ACCOUNT_TYPECLASS - locks = make_iter(locks) if locks is not None else None - permissions = make_iter(permissions) if permissions is not None else None - tags = make_iter(tags) if tags is not None else None - attributes = make_iter(attributes) if attributes is not None else None - - if isinstance(typeclass, str): - # a path is given. Load the actual typeclass. - typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) - - # setup input for the create command. We use AccountDB as baseclass - # here to give us maximum freedom (the typeclasses will load - # correctly when each object is recovered). - - if not email: - email = None - if _AccountDB.objects.filter(username__iexact=key): - raise ValueError("An Account with the name '%s' already exists." % key) - - # this handles a given dbref-relocate to an account. - report_to = dbid_to_obj(report_to, _AccountDB) - - # create the correct account entity, using the setup from - # base django auth. - now = timezone.now() - email = typeclass.objects.normalize_email(email) - new_account = typeclass( - username=key, - email=email, - is_staff=is_superuser, - is_superuser=is_superuser, - last_login=now, - date_joined=now, - ) - if password is not None: - # the password may be None for 'fake' accounts, like bots - valid, error = new_account.validate_password(password, new_account) - if not valid: - raise error - - new_account.set_password(password) - - new_account._createdict = dict( - locks=locks, permissions=permissions, report_to=report_to, tags=tags, attributes=attributes - ) - # saving will trigger the signal that calls the - # at_first_save hook on the typeclass, where the _createdict - # can be used. - new_account.save() - - # note that we don't send a signal here, that is sent from the Account.create helper method - # instead. - - return new_account
- - -# alias -account = create_account -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/dbserialize.html b/docs/0.9.5/_modules/evennia/utils/dbserialize.html deleted file mode 100644 index d6435da014..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/dbserialize.html +++ /dev/null @@ -1,866 +0,0 @@ - - - - - - - - evennia.utils.dbserialize — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.dbserialize

-"""
-This module handles serialization of arbitrary python structural data,
-intended primarily to be stored in the database. It also supports
-storing Django model instances (which plain pickle cannot do).
-
-This serialization is used internally by the server, notably for
-storing data in Attributes and for piping data to process pools.
-
-The purpose of dbserialize is to handle all forms of data. For
-well-structured non-arbitrary exchange, such as communicating with a
-rich web client, a simpler JSON serialization makes more sense.
-
-This module also implements the `SaverList`, `SaverDict` and `SaverSet`
-classes. These are iterables that track their position in a nested
-structure and makes sure to send updates up to their root. This is
-used by Attributes - without it, one would not be able to update mutables
-in-situ, e.g `obj.db.mynestedlist[3][5] = 3` would never be saved and
-be out of sync with the database.
-
-"""
-from functools import update_wrapper
-from collections import defaultdict, MutableSequence, MutableSet, MutableMapping
-from collections import OrderedDict, deque
-
-try:
-    from pickle import dumps, loads
-except ImportError:
-    from pickle import dumps, loads
-from django.core.exceptions import ObjectDoesNotExist
-from django.contrib.contenttypes.models import ContentType
-from django.utils.safestring import SafeString
-from evennia.utils.utils import uses_database, is_iter, to_str, to_bytes
-from evennia.utils import logger
-
-__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle", "dbserialize", "dbunserialize")
-
-PICKLE_PROTOCOL = 2
-
-
-# message to send if editing an already deleted Attribute in a savermutable
-_ERROR_DELETED_ATTR = (
-    "{cls_name} {obj} has had its root Attribute deleted. "
-    "It must be cast to a {non_saver_name} before it can be modified further."
-)
-
-
-def _get_mysql_db_version():
-    """
-    This is a helper method for specifically getting the version
-    string of a MySQL database.
-
-    Returns:
-        mysql_version (str): The currently used mysql database
-            version.
-
-    """
-    from django.db import connection
-
-    conn = connection.cursor()
-    conn.execute("SELECT VERSION()")
-    version = conn.fetchone()
-    return version and str(version[0]) or ""
-
-
-# initialization and helpers
-
-
-_GA = object.__getattribute__
-_SA = object.__setattr__
-_FROM_MODEL_MAP = None
-_TO_MODEL_MAP = None
-_IGNORE_DATETIME_MODELS = None
-_SESSION_HANDLER = None
-
-
-def _IS_PACKED_DBOBJ(o):
-    return isinstance(o, tuple) and len(o) == 4 and o[0] == "__packed_dbobj__"
-
-
-def _IS_PACKED_SESSION(o):
-    return isinstance(o, tuple) and len(o) == 3 and o[0] == "__packed_session__"
-
-
-if uses_database("mysql") and _get_mysql_db_version() < "5.6.4":
-    # mysql <5.6.4 don't support millisecond precision
-    _DATESTRING = "%Y:%m:%d-%H:%M:%S:000000"
-else:
-    _DATESTRING = "%Y:%m:%d-%H:%M:%S:%f"
-
-
-def _TO_DATESTRING(obj):
-    """
-    Creates datestring hash.
-
-    Args:
-        obj (Object): Database object.
-
-    Returns:
-        datestring (str): A datestring hash.
-
-    """
-    try:
-        return _GA(obj, "db_date_created").strftime(_DATESTRING)
-    except AttributeError:
-        # this can happen if object is not yet saved - no datestring is then set
-        try:
-            obj.save()
-        except AttributeError:
-            # we have received a None object, for example due to an erroneous save.
-            return None
-        return _GA(obj, "db_date_created").strftime(_DATESTRING)
-
-
-def _init_globals():
-    """Lazy importing to avoid circular import issues"""
-    global _FROM_MODEL_MAP, _TO_MODEL_MAP, _SESSION_HANDLER, _IGNORE_DATETIME_MODELS
-    if not _FROM_MODEL_MAP:
-        _FROM_MODEL_MAP = defaultdict(str)
-        _FROM_MODEL_MAP.update(dict((c.model, c.natural_key()) for c in ContentType.objects.all()))
-    if not _TO_MODEL_MAP:
-        from django.conf import settings
-
-        _TO_MODEL_MAP = defaultdict(str)
-        _TO_MODEL_MAP.update(
-            dict((c.natural_key(), c.model_class()) for c in ContentType.objects.all())
-        )
-        _IGNORE_DATETIME_MODELS = []
-        for src_key, dst_key in settings.ATTRIBUTE_STORED_MODEL_RENAME:
-            _TO_MODEL_MAP[src_key] = _TO_MODEL_MAP.get(dst_key, None)
-            _IGNORE_DATETIME_MODELS.append(src_key)
-    if not _SESSION_HANDLER:
-        from evennia.server.sessionhandler import SESSION_HANDLER as _SESSION_HANDLER
-
-
-#
-# SaverList, SaverDict, SaverSet - Attribute-specific helper classes and functions
-#
-
-
-def _save(method):
-    """method decorator that saves data to Attribute"""
-
-    def save_wrapper(self, *args, **kwargs):
-        self.__doc__ = method.__doc__
-        ret = method(self, *args, **kwargs)
-        self._save_tree()
-        return ret
-
-    return update_wrapper(save_wrapper, method)
-
-
-class _SaverMutable(object):
-    """
-    Parent class for properly handling  of nested mutables in
-    an Attribute. If not used something like
-     obj.db.mylist[1][2] = "test" (allocation to a nested list)
-    will not save the updated value to the database.
-    """
-
-    def __init__(self, *args, **kwargs):
-        """store all properties for tracking the tree"""
-        self._parent = kwargs.pop("_parent", None)
-        self._db_obj = kwargs.pop("_db_obj", None)
-        self._data = None
-
-    def __bool__(self):
-        """Make sure to evaluate as False if empty"""
-        return bool(self._data)
-
-    def _save_tree(self):
-        """recursively traverse back up the tree, save when we reach the root"""
-        if self._parent:
-            self._parent._save_tree()
-        elif self._db_obj:
-            if not self._db_obj.pk:
-                cls_name = self.__class__.__name__
-                try:
-                    non_saver_name = cls_name.split("_Saver", 1)[1].lower()
-                except IndexError:
-                    non_saver_name = cls_name
-                raise ValueError(
-                    _ERROR_DELETED_ATTR.format(
-                        cls_name=cls_name, obj=self, non_saver_name=non_saver_name
-                    )
-                )
-            self._db_obj.value = self
-        else:
-            logger.log_err("_SaverMutable %s has no root Attribute to save to." % self)
-
-    def _convert_mutables(self, data):
-        """converts mutables to Saver* variants and assigns ._parent property"""
-
-        def process_tree(item, parent):
-            """recursively populate the tree, storing parents"""
-            dtype = type(item)
-            if dtype in (str, int, float, bool, tuple):
-                return item
-            elif dtype == list:
-                dat = _SaverList(_parent=parent)
-                dat._data.extend(process_tree(val, dat) for val in item)
-                return dat
-            elif dtype == dict:
-                dat = _SaverDict(_parent=parent)
-                dat._data.update((key, process_tree(val, dat)) for key, val in item.items())
-                return dat
-            elif dtype == set:
-                dat = _SaverSet(_parent=parent)
-                dat._data.update(process_tree(val, dat) for val in item)
-                return dat
-            return item
-
-        return process_tree(data, self)
-
-    def __repr__(self):
-        return self._data.__repr__()
-
-    def __len__(self):
-        return self._data.__len__()
-
-    def __iter__(self):
-        return self._data.__iter__()
-
-    def __getitem__(self, key):
-        return self._data.__getitem__(key)
-
-    def __eq__(self, other):
-        return self._data == other
-
-    def __ne__(self, other):
-        return self._data != other
-
-    def __lt__(self, other):
-        return self._data < other
-
-    def __gt__(self, other):
-        return self._data > other
-
-    @_save
-    def __setitem__(self, key, value):
-        self._data.__setitem__(key, self._convert_mutables(value))
-
-    @_save
-    def __delitem__(self, key):
-        self._data.__delitem__(key)
-
-
-class _SaverList(_SaverMutable, MutableSequence):
-    """
-    A list that saves itself to an Attribute when updated.
-    """
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self._data = list()
-
-    @_save
-    def __iadd__(self, otherlist):
-        self._data = self._data.__add__(otherlist)
-        return self._data
-
-    def __add__(self, otherlist):
-        return list(self._data) + otherlist
-
-    @_save
-    def insert(self, index, value):
-        self._data.insert(index, self._convert_mutables(value))
-
-    def __eq__(self, other):
-        try:
-            return list(self._data) == list(other)
-        except TypeError:
-            return False
-
-    def __ne__(self, other):
-        try:
-            return list(self._data) != list(other)
-        except TypeError:
-            return True
-
-    def index(self, value, *args):
-        return self._data.index(value, *args)
-
-    @_save
-    def sort(self, *, key=None, reverse=False):
-        self._data.sort(key=key, reverse=reverse)
-
-    def copy(self):
-        return self._data.copy()
-
-
-class _SaverDict(_SaverMutable, MutableMapping):
-    """
-    A dict that stores changes to an Attribute when updated
-    """
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self._data = dict()
-
-    def has_key(self, key):
-        return key in self._data
-
-    @_save
-    def update(self, *args, **kwargs):
-        self._data.update(*args, **kwargs)
-
-
-class _SaverSet(_SaverMutable, MutableSet):
-    """
-    A set that saves to an Attribute when updated
-    """
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self._data = set()
-
-    def __contains__(self, value):
-        return self._data.__contains__(value)
-
-    @_save
-    def add(self, value):
-        self._data.add(self._convert_mutables(value))
-
-    @_save
-    def discard(self, value):
-        self._data.discard(value)
-
-
-class _SaverOrderedDict(_SaverMutable, MutableMapping):
-    """
-    An ordereddict that can be saved and operated on.
-    """
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self._data = OrderedDict()
-
-    def has_key(self, key):
-        return key in self._data
-
-
-class _SaverDeque(_SaverMutable):
-    """
-    A deque that can be saved and operated on.
-    """
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self._data = deque()
-
-    @_save
-    def append(self, *args, **kwargs):
-        self._data.append(*args, **kwargs)
-
-    @_save
-    def appendleft(self, *args, **kwargs):
-        self._data.appendleft(*args, **kwargs)
-
-    @_save
-    def clear(self):
-        self._data.clear()
-
-    @_save
-    def extendleft(self, *args, **kwargs):
-        self._data.extendleft(*args, **kwargs)
-
-    # maxlen property
-    def _getmaxlen(self):
-        return self._data.maxlen
-
-    def _setmaxlen(self, value):
-        self._data.maxlen = value
-
-    def _delmaxlen(self):
-        del self._data.maxlen
-
-    maxlen = property(_getmaxlen, _setmaxlen, _delmaxlen)
-
-    @_save
-    def pop(self, *args, **kwargs):
-        return self._data.pop(*args, **kwargs)
-
-    @_save
-    def popleft(self, *args, **kwargs):
-        return self._data.popleft(*args, **kwargs)
-
-    @_save
-    def reverse(self):
-        self._data.reverse()
-
-    @_save
-    def rotate(self, *args):
-        self._data.rotate(*args)
-        
-    @_save
-    def remove(self, *args):
-        self._data.remove(*args)
-
-
-_DESERIALIZE_MAPPING = {
-    _SaverList.__name__: list,
-    _SaverDict.__name__: dict,
-    _SaverSet.__name__: set,
-    _SaverOrderedDict.__name__: OrderedDict,
-    _SaverDeque.__name__: deque,
-}
-
-
-def deserialize(obj):
-    """
-    Make sure to *fully* decouple a structure from the database, by turning all _Saver*-mutables
-    inside it back into their normal Python forms.
-
-    """
-
-    def _iter(obj):
-        typ = type(obj)
-        tname = typ.__name__
-        if tname in ("_SaverDict", "dict"):
-            return {_iter(key): _iter(val) for key, val in obj.items()}
-        elif tname in _DESERIALIZE_MAPPING:
-            return _DESERIALIZE_MAPPING[tname](_iter(val) for val in obj)
-        elif is_iter(obj):
-            return typ(_iter(val) for val in obj)
-        return obj
-
-    return _iter(obj)
-
-
-#
-# serialization helpers
-
-
-def pack_dbobj(item):
-    """
-    Check and convert django database objects to an internal representation.
-
-    Args:
-        item (any): A database entity to pack
-
-    Returns:
-        packed (any or tuple): Either returns the original input item
-            or the packing tuple `("__packed_dbobj__", key, creation_time, id)`.
-
-    """
-    _init_globals()
-    obj = item
-    natural_key = _FROM_MODEL_MAP[
-        hasattr(obj, "id")
-        and hasattr(obj, "db_date_created")
-        and hasattr(obj, "__dbclass__")
-        and obj.__dbclass__.__name__.lower()
-    ]
-    # build the internal representation as a tuple
-    #  ("__packed_dbobj__", key, creation_time, id)
-    return (
-        natural_key
-        and ("__packed_dbobj__", natural_key, _TO_DATESTRING(obj), _GA(obj, "id"))
-        or item
-    )
-
-
-def unpack_dbobj(item):
-    """
-    Check and convert internal representations back to Django database
-    models.
-
-    Args:
-        item (packed_dbobj): The fact that item is a packed dbobj
-            should be checked before this call.
-
-    Returns:
-        unpacked (any): Either the original input or converts the
-            internal store back to a database representation (its
-            typeclass is returned if applicable).
-
-    """
-    _init_globals()
-    try:
-        obj = item[3] and _TO_MODEL_MAP[item[1]].objects.get(id=item[3])
-    except ObjectDoesNotExist:
-        return None
-    except TypeError:
-        if hasattr(item, "pk"):
-            # this happens if item is already an obj
-            return item
-        return None
-    if item[1] in _IGNORE_DATETIME_MODELS:
-        # if we are replacing models we ignore the datatime
-        return obj
-    else:
-        # even if we got back a match, check the sanity of the date (some
-        # databases may 're-use' the id)
-        return _TO_DATESTRING(obj) == item[2] and obj or None
-
-
-def pack_session(item):
-    """
-    Handle the safe serializion of Sessions objects (these contain
-    hidden references to database objects (accounts, puppets) so they
-    can't be safely serialized).
-
-    Args:
-        item (Session)): This item must have all properties of a session
-            before entering this call.
-
-    Returns:
-        packed (tuple or None): A session-packed tuple on the form
-            `(__packed_session__, sessid, conn_time)`. If this sessid
-            does not match a session in the Session handler, None is returned.
-
-    """
-    _init_globals()
-    session = _SESSION_HANDLER.get(item.sessid)
-    if session and session.conn_time == item.conn_time:
-        # we require connection times to be identical for the Session
-        # to be accepted as actually being a session (sessids gets
-        # reused all the time).
-        return (
-            item.conn_time
-            and item.sessid
-            and ("__packed_session__", _GA(item, "sessid"), _GA(item, "conn_time"))
-        )
-    return None
-
-
-def unpack_session(item):
-    """
-    Check and convert internal representations back to Sessions.
-
-    Args:
-        item (packed_session): The fact that item is a packed session
-            should be checked before this call.
-
-    Returns:
-        unpacked (any): Either the original input or converts the
-            internal store back to a Session. If Session no longer
-            exists, None will be returned.
-    """
-    _init_globals()
-    session = _SESSION_HANDLER.get(item[1])
-    if session and session.conn_time == item[2]:
-        # we require connection times to be identical for the Session
-        # to be accepted as the same as the one stored (sessids gets
-        # reused all the time).
-        return session
-    return None
-
-
-#
-# Access methods
-
-
-
[docs]def to_pickle(data): - """ - This prepares data on arbitrary form to be pickled. It handles any - nested structure and returns data on a form that is safe to pickle - (including having converted any database models to their internal - representation). We also convert any Saver*-type objects back to - their normal representations, they are not pickle-safe. - - Args: - data (any): Data to pickle. - - Returns: - data (any): Pickled data. - - """ - - def process_item(item): - """Recursive processor and identification of data""" - dtype = type(item) - if dtype in (str, int, float, bool, bytes, SafeString): - return item - elif dtype == tuple: - return tuple(process_item(val) for val in item) - elif dtype in (list, _SaverList): - return [process_item(val) for val in item] - elif dtype in (dict, _SaverDict): - return dict((process_item(key), process_item(val)) for key, val in item.items()) - elif dtype in (set, _SaverSet): - return set(process_item(val) for val in item) - elif dtype in (OrderedDict, _SaverOrderedDict): - return OrderedDict((process_item(key), process_item(val)) for key, val in item.items()) - elif dtype in (deque, _SaverDeque): - return deque(process_item(val) for val in item) - - elif hasattr(item, "__iter__"): - # we try to conserve the iterable class, if not convert to list - try: - return item.__class__([process_item(val) for val in item]) - except (AttributeError, TypeError): - return [process_item(val) for val in item] - elif hasattr(item, "sessid") and hasattr(item, "conn_time"): - return pack_session(item) - try: - return pack_dbobj(item) - except TypeError: - return item - except Exception: - logger.log_err(f"The object {item} of type {type(item)} could not be stored.") - raise - - return process_item(data)
- - -# @transaction.autocommit -
[docs]def from_pickle(data, db_obj=None): - """ - This should be fed a just de-pickled data object. It will be converted back - to a form that may contain database objects again. Note that if a database - object was removed (or changed in-place) in the database, None will be - returned. - - Args: - data (any): Pickled data to unpickle. - db_obj (Atribute, any): This is the model instance (normally - an Attribute) that _Saver*-type iterables (_SaverList etc) - will save to when they update. It must have a 'value' property - that saves assigned data to the database. Skip if not - serializing onto a given object. If db_obj is given, this - function will convert lists, dicts and sets to their - `_SaverList`, `_SaverDict` and `_SaverSet` counterparts. - - Returns: - data (any): Unpickled data. - - """ - - def process_item(item): - """Recursive processor and identification of data""" - dtype = type(item) - if dtype in (str, int, float, bool, bytes, SafeString): - return item - elif _IS_PACKED_DBOBJ(item): - # this must be checked before tuple - return unpack_dbobj(item) - elif _IS_PACKED_SESSION(item): - return unpack_session(item) - elif dtype == tuple: - return tuple(process_item(val) for val in item) - elif dtype == dict: - return dict((process_item(key), process_item(val)) for key, val in item.items()) - elif dtype == set: - return set(process_item(val) for val in item) - elif dtype == OrderedDict: - return OrderedDict((process_item(key), process_item(val)) for key, val in item.items()) - elif dtype == deque: - return deque(process_item(val) for val in item) - elif hasattr(item, "__iter__"): - try: - # we try to conserve the iterable class if - # it accepts an iterator - return item.__class__(process_item(val) for val in item) - except (AttributeError, TypeError): - return [process_item(val) for val in item] - return item - - def process_tree(item, parent): - """Recursive processor, building a parent-tree from iterable data""" - dtype = type(item) - if dtype in (str, int, float, bool, bytes, SafeString): - return item - elif _IS_PACKED_DBOBJ(item): - # this must be checked before tuple - return unpack_dbobj(item) - elif dtype == tuple: - return tuple(process_tree(val, item) for val in item) - elif dtype == list: - dat = _SaverList(_parent=parent) - dat._data.extend(process_tree(val, dat) for val in item) - return dat - elif dtype == dict: - dat = _SaverDict(_parent=parent) - dat._data.update( - (process_item(key), process_tree(val, dat)) for key, val in item.items() - ) - return dat - elif dtype == set: - dat = _SaverSet(_parent=parent) - dat._data.update(set(process_tree(val, dat) for val in item)) - return dat - elif dtype == OrderedDict: - dat = _SaverOrderedDict(_parent=parent) - dat._data.update( - (process_item(key), process_tree(val, dat)) for key, val in item.items() - ) - return dat - elif dtype == deque: - dat = _SaverDeque(_parent=parent) - dat._data.extend(process_item(val) for val in item) - return dat - elif hasattr(item, "__iter__"): - try: - # we try to conserve the iterable class if it - # accepts an iterator - return item.__class__(process_tree(val, parent) for val in item) - except (AttributeError, TypeError): - dat = _SaverList(_parent=parent) - dat._data.extend(process_tree(val, dat) for val in item) - return dat - return item - - if db_obj: - # convert lists, dicts and sets to their Saved* counterparts. It - # is only relevant if the "root" is an iterable of the right type. - dtype = type(data) - if dtype == list: - dat = _SaverList(_db_obj=db_obj) - dat._data.extend(process_tree(val, dat) for val in data) - return dat - elif dtype == dict: - dat = _SaverDict(_db_obj=db_obj) - dat._data.update( - (process_item(key), process_tree(val, dat)) for key, val in data.items() - ) - return dat - elif dtype == set: - dat = _SaverSet(_db_obj=db_obj) - dat._data.update(process_tree(val, dat) for val in data) - return dat - elif dtype == OrderedDict: - dat = _SaverOrderedDict(_db_obj=db_obj) - dat._data.update( - (process_item(key), process_tree(val, dat)) for key, val in data.items() - ) - return dat - elif dtype == deque: - dat = _SaverDeque(_db_obj=db_obj) - dat._data.extend(process_item(val) for val in data) - return dat - return process_item(data)
- - -
[docs]def do_pickle(data): - """Perform pickle to string""" - try: - return dumps(data, protocol=PICKLE_PROTOCOL) - except Exception: - logger.log_err(f"Could not pickle data for storage: {data}") - raise
- - -
[docs]def do_unpickle(data): - """Retrieve pickle from pickled string""" - try: - return loads(to_bytes(data)) - except Exception: - logger.log_err(f"Could not unpickle data from storage: {data}") - raise
- - -
[docs]def dbserialize(data): - """Serialize to pickled form in one step""" - return do_pickle(to_pickle(data))
- - -
[docs]def dbunserialize(data, db_obj=None): - """Un-serialize in one step. See from_pickle for help db_obj.""" - return from_pickle(do_unpickle(data), db_obj=db_obj)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/eveditor.html b/docs/0.9.5/_modules/evennia/utils/eveditor.html deleted file mode 100644 index e935c699e3..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/eveditor.html +++ /dev/null @@ -1,1225 +0,0 @@ - - - - - - - - evennia.utils.eveditor — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.eveditor

-"""
-EvEditor (Evennia Line Editor)
-
-This implements an advanced line editor for editing longer texts
-in-game. The editor mimics the command mechanisms of the "VI" editor
-(a famous line-by-line editor) as far as reasonable.
-
-Features of the editor:
-
- - undo/redo.
- - edit/replace on any line of the buffer.
- - search&replace text anywhere in buffer.
- - formatting of buffer, or selection, to certain width + indentations.
- - allow to echo the input or not, depending on your client.
-
-To use the editor, just import EvEditor from this module
-and initialize it:
-::
-
-    from evennia.utils.eveditor import EvEditor
-    EvEditor(caller, loadfunc=None, savefunc=None, quitfunc=None, key="", persistent=True)
-
-- `caller` is the user of the editor, the one to see all feedback.
-- `loadfunc(caller)` is called when the editor is first launched; the
-  return from this function is loaded as the starting buffer in the
-  editor.
-- `safefunc(caller, buffer)` is called with the current buffer when
-  saving in the editor. The function should return True/False depending
-  on if the saving was successful or not.
-- `quitfunc(caller)` is called when the editor exits. If this is given,
-  no automatic quit messages will be given.
-- `key` is an optional identifier for the editing session, to be
-  displayed in the editor.
-- `persistent` means the editor state will be saved to the database making it
-  survive a server reload. Note that using this mode, the load- save-
-  and quit-funcs must all be possible to pickle - notable unusable
-  callables are class methods and functions defined inside other
-  functions. With persistent=False, no such restriction exists.
-- `code` set to True activates features on the EvEditor to enter Python code.
-
-In addition, the EvEditor can be used to enter Python source code,
-and offers basic handling of indentation.
-
-----
-
-"""
-import re
-
-from django.conf import settings
-from evennia import Command, CmdSet
-from evennia.utils import is_iter, fill, dedent, logger, justify, to_str, utils
-from evennia.utils.ansi import raw
-from evennia.commands import cmdhandler
-
-# we use cmdhandler instead of evennia.syscmdkeys to
-# avoid some cases of loading before evennia init'd
-_CMD_NOMATCH = cmdhandler.CMD_NOMATCH
-_CMD_NOINPUT = cmdhandler.CMD_NOINPUT
-
-_RE_GROUP = re.compile(r"\".*?\"|\'.*?\'|\S*")
-_COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
-# use NAWS in the future?
-_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-
-# -------------------------------------------------------------
-#
-# texts
-#
-# -------------------------------------------------------------
-
-_HELP_TEXT = """
- <txt>  - any non-command is appended to the end of the buffer.
- :  <l> - view buffer or only line(s) <l>
- :: <l> - raw-view buffer or only line(s) <l>
- :::    - escape - enter ':' as the only character on the line.
- :h     - this help.
-
- :w     - save the buffer (don't quit)
- :wq    - save buffer and quit
- :q     - quit (will be asked to save if buffer was changed)
- :q!    - quit without saving, no questions asked
-
- :u     - (undo) step backwards in undo history
- :uu    - (redo) step forward in undo history
- :UU    - reset all changes back to initial state
-
- :dd <l>     - delete last line or line(s) <l>
- :dw <l> <w> - delete word or regex <w> in entire buffer or on line <l>
- :DD         - clear entire buffer
-
- :y  <l>        - yank (copy) line(s) <l> to the copy buffer
- :x  <l>        - cut line(s) <l> and store it in the copy buffer
- :p  <l>        - put (paste) previously copied line(s) directly after <l>
- :i  <l> <txt>  - insert new text <txt> at line <l>. Old line will move down
- :r  <l> <txt>  - replace line <l> with text <txt>
- :I  <l> <txt>  - insert text at the beginning of line <l>
- :A  <l> <txt>  - append text after the end of line <l>
-
- :s <l> <w> <txt> - search/replace word or regex <w> in buffer or on line <l>
-
- :j <l> <w> - justify buffer or line <l>. <w> is f, c, l or r. Default f (full)
- :f <l>     - flood-fill entire buffer or line <l>: Equivalent to :j left
- :fi <l>    - indent entire buffer or line <l>
- :fd <l>    - de-indent entire buffer or line <l>
-
- :echo - turn echoing of the input on/off (helpful for some clients)
-"""
-
-_HELP_LEGEND = """
-    Legend:
-    <l>   - line number, like '5' or range, like '3:7'.
-    <w>   - a single word, or multiple words with quotes around them.
-    <txt> - longer string, usually not needing quotes.
-"""
-
-_HELP_CODE = """
- :!    - Execute code buffer without saving
- :<    - Decrease the level of automatic indentation for the next lines
- :>    - Increase the level of automatic indentation for the next lines
- :=    - Switch automatic indentation on/off
-""".lstrip(
-    "\n"
-)
-
-_ERROR_LOADFUNC = """
-{error}
-
-|rBuffer load function error. Could not load initial data.|n
-"""
-
-_ERROR_SAVEFUNC = """
-{error}
-
-|rSave function returned an error. Buffer not saved.|n
-"""
-
-_ERROR_NO_SAVEFUNC = "|rNo save function defined. Buffer cannot be saved.|n"
-
-_MSG_SAVE_NO_CHANGE = "No changes need saving"
-_DEFAULT_NO_QUITFUNC = "Exited editor."
-
-_ERROR_QUITFUNC = """
-{error}
-
-|rQuit function gave an error. Skipping.|n
-"""
-
-_ERROR_PERSISTENT_SAVING = """
-{error}
-
-|rThe editor state could not be saved for persistent mode. Switching
-to non-persistent mode (which means the editor session won't survive
-an eventual server reload - so save often!)|n
-"""
-
-_TRACE_PERSISTENT_SAVING = (
-    "EvEditor persistent-mode error. Commonly, this is because one or "
-    "more of the EvEditor callbacks could not be pickled, for example "
-    "because it's a class method or is defined inside another function."
-)
-
-
-_MSG_NO_UNDO = "Nothing to undo."
-_MSG_NO_REDO = "Nothing to redo."
-_MSG_UNDO = "Undid one step."
-_MSG_REDO = "Redid one step."
-
-# -------------------------------------------------------------
-#
-# Handle yes/no quit question
-#
-# -------------------------------------------------------------
-
-
-
[docs]class CmdSaveYesNo(_COMMAND_DEFAULT_CLASS): - """ - Save the editor state on quit. This catches - nomatches (defaults to Yes), and avoid saves only if - command was given specifically as "no" or "n". - """ - - key = _CMD_NOMATCH - aliases = _CMD_NOINPUT - locks = "cmd:all()" - help_cateogory = "LineEditor" - -
[docs] def func(self): - """Implement the yes/no choice.""" - # this is only called from inside the lineeditor - # so caller.ndb._lineditor must be set. - - self.caller.cmdset.remove(SaveYesNoCmdSet) - if self.raw_string.strip().lower() in ("no", "n"): - # answered no - self.caller.msg(self.caller.ndb._eveditor.quit()) - else: - # answered yes (default) - self.caller.ndb._eveditor.save_buffer() - self.caller.ndb._eveditor.quit()
- - -
[docs]class SaveYesNoCmdSet(CmdSet): - """Stores the yesno question""" - - key = "quitsave_yesno" - priority = 150 # override other cmdsets. - mergetype = "Replace" - -
[docs] def at_cmdset_creation(self): - """at cmdset creation""" - self.add(CmdSaveYesNo())
- - -# ------------------------------------------------------------- -# -# Editor commands -# -# ------------------------------------------------------------- - - -
[docs]class CmdEditorBase(_COMMAND_DEFAULT_CLASS): - """ - Base parent for editor commands - """ - - locks = "cmd:all()" - help_entry = "LineEditor" - - editor = None - -
[docs] def parse(self): - """ - Handles pre-parsing - - Usage: - :cmd [li] [w] [txt] - - Where all arguments are optional. - - - li - line number (int), starting from 1. This could also - be a range given as <l>:<l>. - - w - word(s) (string), could be encased in quotes. - - txt - extra text (string), could be encased in quotes. - - """ - - editor = self.caller.ndb._eveditor - if not editor: - # this will completely replace the editor - _load_editor(self.caller) - editor = self.caller.ndb._eveditor - self.editor = editor - - linebuffer = self.editor.get_buffer().split("\n") - - nlines = len(linebuffer) - - # The regular expression will split the line by whitespaces, - # stripping extra whitespaces, except if the text is - # surrounded by single- or double quotes, in which case they - # will be kept together and extra whitespace preserved. You - # can input quotes on the line by alternating single and - # double quotes. - arglist = [part for part in _RE_GROUP.findall(self.args) if part] - temp = [] - for arg in arglist: - # we want to clean the quotes, but only one type, - # in case we are nesting. - if arg.startswith('"'): - arg.strip('"') - elif arg.startswith("'"): - arg.strip("'") - temp.append(arg) - arglist = temp - - # A dumb split, without grouping quotes - words = self.args.split() - - # current line number - cline = nlines - 1 - - # the first argument could also be a range of line numbers, on the - # form <lstart>:<lend>. Either of the ends could be missing, to - # mean start/end of buffer respectively. - - lstart, lend = cline, cline + 1 - linerange = False - if arglist and arglist[0].count(":") == 1: - part1, part2 = arglist[0].split(":") - if part1 and part1.isdigit(): - lstart = min(max(0, int(part1)) - 1, nlines) - linerange = True - if part2 and part2.isdigit(): - lend = min(lstart + 1, int(part2)) + 1 - linerange = True - elif arglist and arglist[0].isdigit(): - lstart = min(max(0, int(arglist[0]) - 1), nlines) - lend = lstart + 1 - linerange = True - if linerange: - arglist = arglist[1:] - - # nicer output formatting of the line range. - lstr = ( - "line %i" % (lstart + 1) - if not linerange or lstart + 1 == lend - else "lines %i-%i" % (lstart + 1, lend) - ) - - # arg1 and arg2 is whatever arguments. Line numbers or -ranges are - # never included here. - args = " ".join(arglist) - arg1, arg2 = "", "" - if len(arglist) > 1: - arg1, arg2 = arglist[0], " ".join(arglist[1:]) - else: - arg1 = " ".join(arglist) - - # store for use in func() - - self.linebuffer = linebuffer - self.nlines = nlines - self.arglist = arglist - self.cline = cline - self.lstart = lstart - self.lend = lend - self.linerange = linerange - self.lstr = lstr - self.words = words - self.args = args - self.arg1 = arg1 - self.arg2 = arg2
- - -def _load_editor(caller): - """ - Load persistent editor from storage. - """ - saved_options = caller.attributes.get("_eveditor_saved") - saved_buffer, saved_undo = caller.attributes.get("_eveditor_buffer_temp", (None, None)) - unsaved = caller.attributes.get("_eveditor_unsaved", False) - indent = caller.attributes.get("_eveditor_indent", 0) - if saved_options: - eveditor = EvEditor(caller, **saved_options[0]) - if saved_buffer: - # we have to re-save the buffer data so we can handle subsequent restarts - caller.attributes.add("_eveditor_buffer_temp", (saved_buffer, saved_undo)) - setattr(eveditor, "_buffer", saved_buffer) - setattr(eveditor, "_undo_buffer", saved_undo) - setattr(eveditor, "_undo_pos", len(saved_undo) - 1) - setattr(eveditor, "_unsaved", unsaved) - setattr(eveditor, "_indent", indent) - for key, value in saved_options[1].items(): - setattr(eveditor, key, value) - else: - # something went wrong. Cleanup. - caller.cmdset.remove(EvEditorCmdSet) - - -
[docs]class CmdLineInput(CmdEditorBase): - """ - No command match - Inputs line of text into buffer. - """ - - key = _CMD_NOMATCH - aliases = _CMD_NOINPUT - -
[docs] def func(self): - """ - Adds the line without any formatting changes. - - If the editor handles code, it might add automatic - indentation. - """ - caller = self.caller - editor = caller.ndb._eveditor - buf = editor.get_buffer() - - # add a line of text to buffer - line = self.raw_string.strip("\r\n") - if editor._codefunc and editor._indent >= 0: - # if automatic indentation is active, add spaces - line = editor.deduce_indent(line, buf) - buf = line if not buf else buf + "\n%s" % line - self.editor.update_buffer(buf) - if self.editor._echo_mode: - # need to do it here or we will be off one line - cline = len(self.editor.get_buffer().split("\n")) - if editor._codefunc: - # display the current level of identation - indent = editor._indent - if indent < 0: - indent = "off" - - self.caller.msg("|b%02i|||n (|g%s|n) %s" % (cline, indent, raw(line))) - else: - self.caller.msg("|b%02i|||n %s" % (cline, raw(self.args)))
- - -
[docs]class CmdEditorGroup(CmdEditorBase): - """ - Commands for the editor - """ - - key = ":editor_command_group" - aliases = [ - ":", - "::", - ":::", - ":h", - ":w", - ":wq", - ":q", - ":q!", - ":u", - ":uu", - ":UU", - ":dd", - ":dw", - ":DD", - ":y", - ":x", - ":p", - ":i", - ":j", - ":r", - ":I", - ":A", - ":s", - ":S", - ":f", - ":fi", - ":fd", - ":echo", - ":!", - ":<", - ":>", - ":=", - ] - arg_regex = r"\s.*?|$" - -
[docs] def func(self): - """ - This command handles all the in-editor :-style commands. Since - each command is small and very limited, this makes for a more - efficient presentation. - """ - caller = self.caller - editor = caller.ndb._eveditor - - linebuffer = self.linebuffer - lstart, lend = self.lstart, self.lend - cmd = self.cmdstring - echo_mode = self.editor._echo_mode - - if cmd == ":": - # Echo buffer - if self.linerange: - buf = linebuffer[lstart:lend] - editor.display_buffer(buf=buf, offset=lstart) - else: - editor.display_buffer() - elif cmd == "::": - # Echo buffer without the line numbers and syntax parsing - if self.linerange: - buf = linebuffer[lstart:lend] - editor.display_buffer(buf=buf, offset=lstart, linenums=False, options={"raw": True}) - else: - editor.display_buffer(linenums=False, options={"raw": True}) - elif cmd == ":::": - # Insert single colon alone on a line - editor.update_buffer([":"] if lstart == 0 else linebuffer + [":"]) - if echo_mode: - caller.msg("Single ':' added to buffer.") - elif cmd == ":h": - # help entry - editor.display_help() - elif cmd == ":w": - # save without quitting - editor.save_buffer() - elif cmd == ":wq": - # save and quit - editor.save_buffer() - editor.quit() - elif cmd == ":q": - # quit. If not saved, will ask - if self.editor._unsaved: - caller.cmdset.add(SaveYesNoCmdSet) - caller.msg("Save before quitting? |lcyes|lt[Y]|le/|lcno|ltN|le") - else: - editor.quit() - elif cmd == ":q!": - # force quit, not checking saving - editor.quit() - elif cmd == ":u": - # undo - editor.update_undo(-1) - elif cmd == ":uu": - # redo - editor.update_undo(1) - elif cmd == ":UU": - # reset buffer - editor.update_buffer(editor._pristine_buffer) - caller.msg("Reverted all changes to the buffer back to original state.") - elif cmd == ":dd": - # :dd <l> - delete line <l> - buf = linebuffer[:lstart] + linebuffer[lend:] - editor.update_buffer(buf) - caller.msg("Deleted %s." % self.lstr) - elif cmd == ":dw": - # :dw <w> - delete word in entire buffer - # :dw <l> <w> delete word only on line(s) <l> - if not self.arg1: - caller.msg("You must give a search word to delete.") - else: - if not self.linerange: - lstart = 0 - lend = self.cline + 1 - caller.msg("Removed %s for lines %i-%i." % (self.arg1, lstart + 1, lend + 1)) - else: - caller.msg("Removed %s for %s." % (self.arg1, self.lstr)) - sarea = "\n".join(linebuffer[lstart:lend]) - sarea = re.sub(r"%s" % self.arg1.strip("'").strip('"'), "", sarea, re.MULTILINE) - buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:] - editor.update_buffer(buf) - elif cmd == ":DD": - # clear buffer - editor.update_buffer("") - - # Reset indentation level to 0 - if editor._codefunc: - if editor._indent >= 0: - editor._indent = 0 - if editor._persistent: - caller.attributes.add("_eveditor_indent", 0) - caller.msg("Cleared %i lines from buffer." % self.nlines) - elif cmd == ":y": - # :y <l> - yank line(s) to copy buffer - cbuf = linebuffer[lstart:lend] - editor._copy_buffer = cbuf - caller.msg("%s, %s yanked." % (self.lstr.capitalize(), cbuf)) - elif cmd == ":x": - # :x <l> - cut line to copy buffer - cbuf = linebuffer[lstart:lend] - editor._copy_buffer = cbuf - buf = linebuffer[:lstart] + linebuffer[lend:] - editor.update_buffer(buf) - caller.msg("%s, %s cut." % (self.lstr.capitalize(), cbuf)) - elif cmd == ":p": - # :p <l> paste line(s) from copy buffer - if not editor._copy_buffer: - caller.msg("Copy buffer is empty.") - else: - buf = linebuffer[:lstart] + editor._copy_buffer + linebuffer[lstart:] - editor.update_buffer(buf) - caller.msg("Pasted buffer %s to %s." % (editor._copy_buffer, self.lstr)) - elif cmd == ":i": - # :i <l> <txt> - insert new line - new_lines = self.args.split("\n") - if not new_lines: - caller.msg("You need to enter a new line and where to insert it.") - else: - buf = linebuffer[:lstart] + new_lines + linebuffer[lstart:] - editor.update_buffer(buf) - caller.msg("Inserted %i new line(s) at %s." % (len(new_lines), self.lstr)) - elif cmd == ":r": - # :r <l> <txt> - replace lines - new_lines = self.args.split("\n") - if not new_lines: - caller.msg("You need to enter a replacement string.") - else: - buf = linebuffer[:lstart] + new_lines + linebuffer[lend:] - editor.update_buffer(buf) - caller.msg("Replaced %i line(s) at %s." % (len(new_lines), self.lstr)) - elif cmd == ":I": - # :I <l> <txt> - insert text at beginning of line(s) <l> - if not self.raw_string and not editor._codefunc: - caller.msg("You need to enter text to insert.") - else: - buf = ( - linebuffer[:lstart] - + ["%s%s" % (self.args, line) for line in linebuffer[lstart:lend]] - + linebuffer[lend:] - ) - editor.update_buffer(buf) - caller.msg("Inserted text at beginning of %s." % self.lstr) - elif cmd == ":A": - # :A <l> <txt> - append text after end of line(s) - if not self.args: - caller.msg("You need to enter text to append.") - else: - buf = ( - linebuffer[:lstart] - + ["%s%s" % (line, self.args) for line in linebuffer[lstart:lend]] - + linebuffer[lend:] - ) - editor.update_buffer(buf) - caller.msg("Appended text to end of %s." % self.lstr) - elif cmd == ":s": - # :s <li> <w> <txt> - search and replace words - # in entire buffer or on certain lines - if not self.arg1 or not self.arg2: - caller.msg("You must give a search word and something to replace it with.") - else: - if not self.linerange: - lstart = 0 - lend = self.cline + 1 - caller.msg( - "Search-replaced %s -> %s for lines %i-%i." - % (self.arg1, self.arg2, lstart + 1, lend) - ) - else: - caller.msg( - "Search-replaced %s -> %s for %s." % (self.arg1, self.arg2, self.lstr) - ) - sarea = "\n".join(linebuffer[lstart:lend]) - - regex = r"%s|^%s(?=\s)|(?<=\s)%s(?=\s)|^%s$|(?<=\s)%s$" - regarg = self.arg1.strip("'").strip('"') - if " " in regarg: - regarg = regarg.replace(" ", " +") - sarea = re.sub( - regex % (regarg, regarg, regarg, regarg, regarg), - self.arg2.strip("'").strip('"'), - sarea, - re.MULTILINE, - ) - buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:] - editor.update_buffer(buf) - elif cmd == ":f": - # :f <l> flood-fill buffer or <l> lines of buffer. - width = _DEFAULT_WIDTH - if not self.linerange: - lstart = 0 - lend = self.cline + 1 - caller.msg("Flood filled lines %i-%i." % (lstart + 1, lend)) - else: - caller.msg("Flood filled %s." % self.lstr) - fbuf = "\n".join(linebuffer[lstart:lend]) - fbuf = fill(fbuf, width=width) - buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:] - editor.update_buffer(buf) - elif cmd == ":j": - # :f <l> <w> justify buffer of <l> with <w> as align (one of - # f(ull), c(enter), r(ight) or l(left). Default is full. - align_map = { - "full": "f", - "f": "f", - "center": "c", - "c": "c", - "right": "r", - "r": "r", - "left": "l", - "l": "l", - } - align_name = {"f": "Full", "c": "Center", "l": "Left", "r": "Right"} - width = _DEFAULT_WIDTH - if self.arg1 and self.arg1.lower() not in align_map: - self.caller.msg( - "Valid justifications are [f]ull (default), [c]enter, [r]right or [l]eft" - ) - return - align = align_map[self.arg1.lower()] if self.arg1 else "f" - if not self.linerange: - lstart = 0 - lend = self.cline + 1 - self.caller.msg("%s-justified lines %i-%i." % (align_name[align], lstart + 1, lend)) - else: - self.caller.msg("%s-justified %s." % (align_name[align], self.lstr)) - jbuf = "\n".join(linebuffer[lstart:lend]) - jbuf = justify(jbuf, width=width, align=align) - buf = linebuffer[:lstart] + jbuf.split("\n") + linebuffer[lend:] - editor.update_buffer(buf) - elif cmd == ":fi": - # :fi <l> indent buffer or lines <l> of buffer. - indent = " " * 4 - if not self.linerange: - lstart = 0 - lend = self.cline + 1 - caller.msg("Indented lines %i-%i." % (lstart + 1, lend)) - else: - caller.msg("Indented %s." % self.lstr) - fbuf = [indent + line for line in linebuffer[lstart:lend]] - buf = linebuffer[:lstart] + fbuf + linebuffer[lend:] - editor.update_buffer(buf) - elif cmd == ":fd": - # :fi <l> indent buffer or lines <l> of buffer. - if not self.linerange: - lstart = 0 - lend = self.cline + 1 - caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1, lend)) - else: - caller.msg("Removed left margin (dedented) %s." % self.lstr) - fbuf = "\n".join(linebuffer[lstart:lend]) - fbuf = dedent(fbuf) - buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:] - editor.update_buffer(buf) - elif cmd == ":echo": - # set echoing on/off - editor._echo_mode = not editor._echo_mode - caller.msg("Echo mode set to %s" % editor._echo_mode) - elif cmd == ":!": - if editor._codefunc: - editor._codefunc(caller, editor._buffer) - else: - caller.msg("This command is only available in code editor mode.") - elif cmd == ":<": - # :< - if editor._codefunc: - editor.decrease_indent() - indent = editor._indent - if indent >= 0: - caller.msg("Decreased indentation: new indentation is {}.".format(indent)) - else: - caller.msg("|rManual indentation is OFF.|n Use := to turn it on.") - else: - caller.msg("This command is only available in code editor mode.") - elif cmd == ":>": - # :> - if editor._codefunc: - editor.increase_indent() - indent = editor._indent - if indent >= 0: - caller.msg("Increased indentation: new indentation is {}.".format(indent)) - else: - caller.msg("|rManual indentation is OFF.|n Use := to turn it on.") - else: - caller.msg("This command is only available in code editor mode.") - elif cmd == ":=": - # := - if editor._codefunc: - editor.swap_autoindent() - indent = editor._indent - if indent >= 0: - caller.msg("Auto-indentation turned on.") - else: - caller.msg("Auto-indentation turned off.") - else: - caller.msg("This command is only available in code editor mode.")
- - -
[docs]class EvEditorCmdSet(CmdSet): - """CmdSet for the editor commands""" - - key = "editorcmdset" - mergetype = "Replace" - -
[docs] def at_cmdset_creation(self): - self.add(CmdLineInput()) - self.add(CmdEditorGroup())
- - -# ------------------------------------------------------------- -# -# Main Editor object -# -# ------------------------------------------------------------- - - -
[docs]class EvEditor(object): - """ - This defines a line editor object. It creates all relevant commands - and tracks the current state of the buffer. It also cleans up after - itself. - - """ - -
[docs] def __init__( - self, - caller, - loadfunc=None, - savefunc=None, - quitfunc=None, - key="", - persistent=False, - codefunc=False, - ): - """ - Launches a full in-game line editor, mimicking the functionality of VIM. - - Args: - caller (Object): Who is using the editor. - loadfunc (callable, optional): This will be called as - `loadfunc(caller)` when the editor is first started. Its - return will be used as the editor's starting buffer. - savefunc (callable, optional): This will be called as - `savefunc(caller, buffer)` when the save-command is given and - is used to actually determine where/how result is saved. - It should return `True` if save was successful and also - handle any feedback to the user. - quitfunc (callable, optional): This will optionally be - called as `quitfunc(caller)` when the editor is - exited. If defined, it should handle all wanted feedback - to the user. - quitfunc_args (tuple, optional): Optional tuple of arguments to - supply to `quitfunc`. - key (str, optional): An optional key for naming this - session and make it unique from other editing sessions. - persistent (bool, optional): Make the editor survive a reboot. Note - that if this is set, all callables must be possible to pickle - codefunc (bool, optional): If given, will run the editor in code mode. - This will be called as `codefunc(caller, buf)`. - - Notes: - In persistent mode, all the input callables (savefunc etc) - must be possible to be *pickled*, this excludes e.g. - callables that are class methods or functions defined - dynamically or as part of another function. In - non-persistent mode no such restrictions exist. - - - - """ - self._key = key - self._caller = caller - self._caller.ndb._eveditor = self - self._buffer = "" - self._unsaved = False - self._persistent = persistent - self._indent = 0 - - if loadfunc: - self._loadfunc = loadfunc - else: - self._loadfunc = lambda caller: self._buffer - self.load_buffer() - if savefunc: - self._savefunc = savefunc - else: - self._savefunc = lambda caller, buffer: caller.msg(_ERROR_NO_SAVEFUNC) - if quitfunc: - self._quitfunc = quitfunc - else: - self._quitfunc = lambda caller: caller.msg(_DEFAULT_NO_QUITFUNC) - self._codefunc = codefunc - - # store the original version - self._pristine_buffer = self._buffer - self._sep = "-" - - # undo operation buffer - self._undo_buffer = [self._buffer] - self._undo_pos = 0 - self._undo_max = 20 - - # copy buffer - self._copy_buffer = [] - - if persistent: - # save in tuple {kwargs, other options} - try: - caller.attributes.add( - "_eveditor_saved", - ( - dict( - loadfunc=loadfunc, - savefunc=savefunc, - quitfunc=quitfunc, - codefunc=codefunc, - key=key, - persistent=persistent, - ), - dict(_pristine_buffer=self._pristine_buffer, _sep=self._sep), - ), - ) - caller.attributes.add("_eveditor_buffer_temp", (self._buffer, self._undo_buffer)) - caller.attributes.add("_eveditor_unsaved", False) - caller.attributes.add("_eveditor_indent", 0) - except Exception as err: - caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err)) - logger.log_trace(_TRACE_PERSISTENT_SAVING) - persistent = False - - # Create the commands we need - caller.cmdset.add(EvEditorCmdSet, permanent=persistent) - - # echo inserted text back to caller - self._echo_mode = True - - # show the buffer ui - self.display_buffer()
- -
[docs] def load_buffer(self): - """ - Load the buffer using the load function hook. - """ - try: - self._buffer = self._loadfunc(self._caller) - if not isinstance(self._buffer, str): - self._buffer = to_str(self._buffer) - self._caller.msg("|rNote: input buffer was converted to a string.|n") - except Exception as e: - from evennia.utils import logger - - logger.log_trace() - self._caller.msg(_ERROR_LOADFUNC.format(error=e))
- -
[docs] def get_buffer(self): - """ - Return: - buffer (str): The current buffer. - - """ - return self._buffer
- -
[docs] def update_buffer(self, buf): - """ - This should be called when the buffer has been changed - somehow. It will handle unsaved flag and undo updating. - - Args: - buf (str): The text to update the buffer with. - - """ - if is_iter(buf): - buf = "\n".join(buf) - - if buf != self._buffer: - self._buffer = buf - self.update_undo() - self._unsaved = True - if self._persistent: - self._caller.attributes.add( - "_eveditor_buffer_temp", (self._buffer, self._undo_buffer) - ) - self._caller.attributes.add("_eveditor_unsaved", True) - self._caller.attributes.add("_eveditor_indent", self._indent)
- -
[docs] def quit(self): - """ - Cleanly exit the editor. - - """ - try: - self._quitfunc(self._caller) - except Exception as e: - self._caller.msg(_ERROR_QUITFUNC.format(error=e)) - self._caller.nattributes.remove("_eveditor") - self._caller.attributes.remove("_eveditor_buffer_temp") - self._caller.attributes.remove("_eveditor_saved") - self._caller.attributes.remove("_eveditor_unsaved") - self._caller.attributes.remove("_eveditor_indent") - self._caller.cmdset.remove(EvEditorCmdSet)
- -
[docs] def save_buffer(self): - """ - Saves the content of the buffer. - - """ - if self._unsaved or self._codefunc: - # always save code - this allows us to tie execution to - # saving if we want. - try: - if self._savefunc(self._caller, self._buffer): - # Save codes should return a true value to indicate - # save worked. The saving function is responsible for - # any status messages. - self._unsaved = False - except Exception as e: - self._caller.msg(_ERROR_SAVEFUNC.format(error=e)) - else: - self._caller.msg(_MSG_SAVE_NO_CHANGE)
- -
[docs] def update_undo(self, step=None): - """ - This updates the undo position. - - Args: - step (int, optional): The amount of steps - to progress the undo position to. This - may be a negative value for undo and - a positive value for redo. - - """ - if step and step < 0: - # undo - if self._undo_pos <= 0: - self._caller.msg(_MSG_NO_UNDO) - else: - self._undo_pos = max(0, self._undo_pos + step) - self._buffer = self._undo_buffer[self._undo_pos] - self._caller.msg(_MSG_UNDO) - elif step and step > 0: - # redo - if self._undo_pos >= len(self._undo_buffer) - 1 or self._undo_pos + 1 >= self._undo_max: - self._caller.msg(_MSG_NO_REDO) - else: - self._undo_pos = min( - self._undo_pos + step, min(len(self._undo_buffer), self._undo_max) - 1 - ) - self._buffer = self._undo_buffer[self._undo_pos] - self._caller.msg(_MSG_REDO) - if not self._undo_buffer or ( - self._undo_buffer and self._buffer != self._undo_buffer[self._undo_pos] - ): - # save undo state - self._undo_buffer = self._undo_buffer[: self._undo_pos + 1] + [self._buffer] - self._undo_pos = len(self._undo_buffer) - 1
- -
[docs] def display_buffer(self, buf=None, offset=0, linenums=True, options={"raw": False}): - """ - This displays the line editor buffer, or selected parts of it. - - Args: - buf (str, optional): The buffer or part of buffer to display. - offset (int, optional): If `buf` is set and is not the full buffer, - `offset` should define the actual starting line number, to - get the linenum display right. - linenums (bool, optional): Show line numbers in buffer. - options: raw (bool, optional): Tell protocol to not parse - formatting information. - - """ - if buf is None: - buf = self._buffer - if is_iter(buf): - buf = "\n".join(buf) - - lines = buf.split("\n") - nlines = len(lines) - nwords = len(buf.split()) - nchars = len(buf) - - sep = self._sep - header = ( - "|n" - + sep * 10 - + "Line Editor [%s]" % self._key - + sep * (_DEFAULT_WIDTH - 24 - len(self._key)) - ) - footer = ( - "|n" - + sep * 10 - + "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) - + sep * 12 - + "(:h for help)" - + sep * (_DEFAULT_WIDTH - 54) - ) - if linenums: - main = "\n".join( - "|b%02i|||n %s" % (iline + 1 + offset, raw(line)) - for iline, line in enumerate(lines) - ) - else: - main = "\n".join([raw(line) for line in lines]) - string = "%s\n%s\n%s" % (header, main, footer) - self._caller.msg(string, options=options)
- -
[docs] def display_help(self): - """ - Shows the help entry for the editor. - - """ - string = self._sep * _DEFAULT_WIDTH + _HELP_TEXT - if self._codefunc: - string += _HELP_CODE - string += _HELP_LEGEND + self._sep * _DEFAULT_WIDTH - self._caller.msg(string)
- -
[docs] def deduce_indent(self, line, buffer): - """ - Try to deduce the level of indentation of the given line. - - """ - keywords = { - "elif ": ["if "], - "else:": ["if ", "try"], - "except": ["try:"], - "finally:": ["try:"], - } - opening_tags = ("if ", "try:", "for ", "while ") - - # If the line begins by one of the given keywords - indent = self._indent - if any(line.startswith(kw) for kw in keywords.keys()): - # Get the keyword and matching begin tags - keyword = [kw for kw in keywords if line.startswith(kw)][0] - begin_tags = keywords[keyword] - for oline in reversed(buffer.splitlines()): - if any(oline.lstrip(" ").startswith(tag) for tag in begin_tags): - # This line begins with a begin tag, takes the identation - indent = (len(oline) - len(oline.lstrip(" "))) / 4 - break - - self._indent = indent + 1 - if self._persistent: - self._caller.attributes.add("_eveditor_indent", self._indent) - elif any(line.startswith(kw) for kw in opening_tags): - self._indent = indent + 1 - if self._persistent: - self._caller.attributes.add("_eveditor_indent", self._indent) - - line = " " * 4 * indent + line - return line
- -
[docs] def decrease_indent(self): - """Decrease automatic indentation by 1 level.""" - if self._codefunc and self._indent > 0: - self._indent -= 1 - if self._persistent: - self._caller.attributes.add("_eveditor_indent", self._indent)
- -
[docs] def increase_indent(self): - """Increase automatic indentation by 1 level.""" - if self._codefunc and self._indent >= 0: - self._indent += 1 - if self._persistent: - self._caller.attributes.add("_eveditor_indent", self._indent)
- -
[docs] def swap_autoindent(self): - """Swap automatic indentation on or off.""" - if self._codefunc: - if self._indent >= 0: - self._indent = -1 - else: - self._indent = 0 - - if self._persistent: - self._caller.attributes.add("_eveditor_indent", self._indent)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/evform.html b/docs/0.9.5/_modules/evennia/utils/evform.html deleted file mode 100644 index bdeb8c9219..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/evform.html +++ /dev/null @@ -1,571 +0,0 @@ - - - - - - - - evennia.utils.evform — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.evform

-# coding=utf-8
-"""
-EvForm - a way to create advanced ASCII forms
-
-This is intended for creating advanced ASCII game forms, such as a
-large pretty character sheet or info document.
-
-The system works on the basis of a readin template that is given in a
-separate Python file imported into the handler. This file contains
-some optional settings and a string mapping out the form. The template
-has markers in it to denounce fields to fill. The markers map the
-absolute size of the field and will be filled with an `evtable.EvCell`
-object when displaying the form.
-
-Example of input file `testform.py`:
-::
-
-    FORMCHAR = "x"
-    TABLECHAR = "c"
-
-    FORM = '''
-    .------------------------------------------------.
-    |                                                |
-    |  Name: xxxxx1xxxxx    Player: xxxxxxx2xxxxxxx  |
-    |        xxxxxxxxxxx                             |
-    |                                                |
-     >----------------------------------------------<
-    |                                                |
-    | Desc:  xxxxxxxxxxx    STR: x4x    DEX: x5x     |
-    |        xxxxx3xxxxx    INT: x6x    STA: x7x     |
-    |        xxxxxxxxxxx    LUC: x8x    MAG: x9x     |
-    |                                                |
-     >----------------------------------------------<
-    |          |                                     |
-    | cccccccc | ccccccccccccccccccccccccccccccccccc |
-    | cccccccc | ccccccccccccccccccccccccccccccccccc |
-    | cccAcccc | ccccccccccccccccccccccccccccccccccc |
-    | cccccccc | ccccccccccccccccccccccccccccccccccc |
-    | cccccccc | cccccccccccccccccBccccccccccccccccc |
-    |          |                                     |
-    -------------------------------------------------
-
-The first line of the `FORM` string is ignored. The forms and table
-markers must mark out complete, unbroken rectangles, each containing
-one embedded single-character identifier (so the smallest element
-possible is a 3-character wide form). The identifier can be any
-character except for the `FORM_CHAR` and `TABLE_CHAR` and some of the
-common ASCII-art elements, like space, `_` `|` `*` etc (see
-`INVALID_FORMCHARS` in this module). Form Rectangles can have any size,
-but must be separated from each other by at least one other
-character's width.
-
-
-Use as follows:
-::
-
-    from evennia import EvForm, EvTable
-
-    # create a new form from the template
-    form = EvForm("path/to/testform.py")
-
-    (MudForm can also take a dictionary holding
-     the required keys FORMCHAR, TABLECHAR and FORM)
-
-    # add data to each tagged form cell
-    form.map(cells={1: "Tom the Bouncer",
-                    2: "Griatch",
-                    3: "A sturdy fellow",
-                    4: 12,
-                    5: 10,
-                    6:  5,
-                    7: 18,
-                    8: 10,
-                    9:  3})
-    # create the EvTables
-    tableA = EvTable("HP","MV","MP",
-                               table=[["**"], ["*****"], ["***"]],
-                               border="incols")
-    tableB = EvTable("Skill", "Value", "Exp",
-                               table=[["Shooting", "Herbalism", "Smithing"],
-                                      [12,14,9],["550/1200", "990/1400", "205/900"]],
-                               border="incols")
-    # add the tables to the proper ids in the form
-    form.map(tables={"A": tableA,
-                     "B": tableB})
-
-    print(form)
-
-
-This produces the following result:
-::
-
-    .------------------------------------------------.
-    |                                                |
-    |  Name: Tom the        Player: Griatch          |
-    |        Bouncer                                 |
-    |                                                |
-     >----------------------------------------------<
-    |                                                |
-    | Desc:  A sturdy       STR: 12     DEX: 10      |
-    |        fellow         INT: 5      STA: 18      |
-    |                       LUC: 10     MAG: 3       |
-    |                                                |
-     >----------------------------------------------<
-    |          |                                     |
-    | HP|MV|MP | Skill      |Value      |Exp         |
-    | ~~+~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~ |
-    | **|**|** | Shooting   |12         |550/1200    |
-    |   |**|*  | Herbalism  |14         |990/1400    |
-    |   |* |   | Smithing   |9          |205/900     |
-    |          |                                     |
-     ------------------------------------------------
-
-
-The marked forms have been replaced with EvCells of text and with
-EvTables. The form can be updated by simply re-applying `form.map()`
-with the updated data.
-
-When working with the template ASCII file, you can use `form.reload()`
-to re-read the template and re-apply all existing mappings.
-
-Each component is restrained to the width and height specified by the
-template, so it will resize to fit (or crop text if the area is too
-small for it). If you try to fit a table into an area it cannot fit
-into (when including its borders and at least one line of text), the
-form will raise an error.
-
-----
-
-"""
-
-import re
-import copy
-from evennia.utils.evtable import EvCell, EvTable
-from evennia.utils.utils import all_from_module, to_str, is_iter
-from evennia.utils.ansi import ANSIString
-
-# non-valid form-identifying characters (which can thus be
-# used as separators between forms without being detected
-# as an identifier). These should be listed in regex form.
-
-INVALID_FORMCHARS = r"\s\/\|\\\*\_\-\#\<\>\~\^\:\;\.\,"
-# if there is an ansi-escape (||) we have to replace this with ||| to make sure
-# to properly escape down the line
-_ANSI_ESCAPE = re.compile(r"\|\|")
-
-
-def _to_rect(lines):
-    """
-    Forces all lines to be as long as the longest
-
-    Args:
-        lines (list): list of `ANSIString`s
-
-    Returns:
-        (list): list of `ANSIString`s of
-        same length as the longest input line
-
-    """
-    maxl = max(len(line) for line in lines)
-    return [line + " " * (maxl - len(line)) for line in lines]
-
-
-def _to_ansi(obj, regexable=False):
-    "convert to ANSIString"
-    if isinstance(obj, ANSIString):
-        return obj
-    elif isinstance(obj, str):
-        # since ansi will be parsed twice (here and in the normal ansi send), we have to
-        # escape the |-structure twice. TODO: This is tied to the default color-tag syntax
-        # which is not ideal for those wanting to replace/extend it ...
-        obj = _ANSI_ESCAPE.sub(r"||||", obj)
-    if isinstance(obj, dict):
-        return dict((key, _to_ansi(value, regexable=regexable)) for key, value in obj.items())
-    elif is_iter(obj):
-        return [_to_ansi(o) for o in obj]
-    else:
-        return ANSIString(obj, regexable=regexable)
-
-
-
[docs]class EvForm: - """ - This object is instantiated with a text file and parses - it for rectangular form fields. It can then be fed a - mapping so as to populate the fields with fixed-width - EvCell or Tables. - - """ - -
[docs] def __init__(self, filename=None, cells=None, tables=None, form=None, **kwargs): - """ - Initiate the form. - - Keyword Args: - filename (str): Path to template file. - cells (dict): A dictionary mapping of `{id:text}`. - tables (dict): A dictionary mapping of `{id:EvTable}`. - form (dict): A dictionary of - `{"FORMCHAR":char, "TABLECHAR":char, "FORM":templatestring}`. - if this is given, filename is not read. - Notes: - Other kwargs are fed as options to the EvCells and EvTables - (see `evtable.EvCell` and `evtable.EvTable` for more info). - - """ - self.filename = filename - self.input_form_dict = form - - self.cells_mapping = ( - dict((to_str(key), value) for key, value in cells.items()) if cells else {} - ) - self.tables_mapping = ( - dict((to_str(key), value) for key, value in tables.items()) if tables else {} - ) - - self.cellchar = "x" - self.tablechar = "c" - - self.raw_form = [] - self.form = [] - - # clean kwargs (these cannot be overridden) - kwargs.pop("enforce_size", None) - kwargs.pop("width", None) - kwargs.pop("height", None) - # table/cell options - self.options = kwargs - - self.reload()
- - def _parse_rectangles(self, cellchar, tablechar, form, **kwargs): - """ - Parse a form for rectangular formfields identified by formchar - enclosing an identifier. - - """ - - # update options given at creation with new input - this - # allows e.g. self.map() to add custom settings for individual - # cells/tables - custom_options = copy.copy(self.options) - custom_options.update(kwargs) - - nform = len(form) - - mapping = {} - cell_coords = {} - table_coords = {} - - # Locate the identifier tags and the horizontal end coords for all forms - re_cellchar = re.compile( - r"%s+([^%s%s]+)%s+" % (cellchar, INVALID_FORMCHARS, cellchar, cellchar) - ) - re_tablechar = re.compile( - r"%s+([^%s%s|+])%s+" % (tablechar, INVALID_FORMCHARS, tablechar, tablechar) - ) - for iy, line in enumerate(_to_ansi(form, regexable=True)): - # find cells - ix0 = 0 - while True: - match = re_cellchar.search(line, ix0) - if match: - # get the width of the rectangle directly from the match - cell_coords[match.group(1)] = [iy, match.start(), match.end()] - ix0 = match.end() - else: - break - # find tables - ix0 = 0 - while True: - match = re_tablechar.search(line, ix0) - if match: - # get the width of the rectangle directly from the match - table_coords[match.group(1)] = [iy, match.start(), match.end()] - ix0 = match.end() - else: - break - - # get rectangles and assign EvCells - for key, (iy, leftix, rightix) in cell_coords.items(): - # scan up to find top of rectangle - dy_up = 0 - if iy > 0: - for i in range(1, iy): - if all(form[iy - i][ix] == cellchar for ix in range(leftix, rightix)): - dy_up += 1 - else: - break - # find bottom edge of rectangle - dy_down = 0 - if iy < nform - 1: - for i in range(1, nform - iy - 1): - if all(form[iy + i][ix] == cellchar for ix in range(leftix, rightix)): - dy_down += 1 - else: - break - - # we have our rectangle. Calculate size of EvCell. - iyup = iy - dy_up - iydown = iy + dy_down - width = rightix - leftix - height = abs(iyup - iydown) + 1 - - # we have all the coordinates we need. Create EvCell. - data = self.cells_mapping.get(key, "") - # if key == "1": - - options = { - "pad_left": 0, - "pad_right": 0, - "pad_top": 0, - "pad_bottom": 0, - "align": "l", - "valign": "t", - "enforce_size": True, - } - options.update(custom_options) - # if key=="4": - - mapping[key] = ( - iyup, - leftix, - width, - height, - EvCell(data, width=width, height=height, **options), - ) - - # get rectangles and assign Tables - for key, (iy, leftix, rightix) in table_coords.items(): - - # scan up to find top of rectangle - dy_up = 0 - if iy > 0: - for i in range(1, iy): - if all(form[iy - i][ix] == tablechar for ix in range(leftix, rightix)): - dy_up += 1 - else: - break - # find bottom edge of rectangle - dy_down = 0 - if iy < nform - 1: - for i in range(1, nform - iy - 1): - if all(form[iy + i][ix] == tablechar for ix in range(leftix, rightix)): - dy_down += 1 - else: - break - - # we have our rectangle. Calculate size of Table. - iyup = iy - dy_up - iydown = iy + dy_down - width = rightix - leftix - height = abs(iyup - iydown) + 1 - - # we have all the coordinates we need. Create Table. - table = self.tables_mapping.get(key, None) - - options = { - "pad_left": 0, - "pad_right": 0, - "pad_top": 0, - "pad_bottom": 0, - "align": "l", - "valign": "t", - "enforce_size": True, - } - options.update(custom_options) - - if table: - table.reformat(width=width, height=height, **options) - else: - table = EvTable(width=width, height=height, **options) - mapping[key] = (iyup, leftix, width, height, table) - - return mapping - - def _populate_form(self, raw_form, mapping): - """ - Insert cell contents into form at given locations - - """ - form = copy.copy(raw_form) - for key, (iy0, ix0, width, height, cell_or_table) in mapping.items(): - # rect is a list of <height> lines, each <width> wide - rect = cell_or_table.get() - for il, rectline in enumerate(rect): - formline = form[iy0 + il] - # insert new content, replacing old - form[iy0 + il] = formline[:ix0] + rectline + formline[ix0 + width :] - return form - -
[docs] def map(self, cells=None, tables=None, **kwargs): - """ - Add mapping for form. - - Args: - cells (dict): A dictionary of {identifier:celltext} - tables (dict): A dictionary of {identifier:table} - - Notes: - kwargs will be forwarded to tables/cells. See - `evtable.EvCell` and `evtable.EvTable` for info. - - """ - # clean kwargs (these cannot be overridden) - kwargs.pop("enforce_size", None) - kwargs.pop("width", None) - kwargs.pop("height", None) - - new_cells = dict((to_str(key), value) for key, value in cells.items()) if cells else {} - new_tables = dict((to_str(key), value) for key, value in tables.items()) if tables else {} - - self.cells_mapping.update(new_cells) - self.tables_mapping.update(new_tables) - self.reload()
- -
[docs] def reload(self, filename=None, form=None, **kwargs): - """ - Creates the form from a stored file name. - - Args: - filename (str): The file to read from. - form (dict): A mapping for the form. - - Notes: - Kwargs are passed through to Cel creation. - - """ - # clean kwargs (these cannot be overridden) - kwargs.pop("enforce_size", None) - kwargs.pop("width", None) - kwargs.pop("height", None) - - if form or self.input_form_dict: - datadict = form if form else self.input_form_dict - self.input_form_dict = datadict - elif filename or self.filename: - filename = filename if filename else self.filename - datadict = all_from_module(filename) - self.filename = filename - else: - datadict = {} - - cellchar = to_str(datadict.get("FORMCHAR", "x")) - self.cellchar = to_str(cellchar[0] if len(cellchar) > 1 else cellchar) - tablechar = datadict.get("TABLECHAR", "c") - self.tablechar = tablechar[0] if len(tablechar) > 1 else tablechar - - # split into a list of list of lines. Form can be indexed with form[iy][ix] - raw_form = _to_ansi(datadict.get("FORM", "").split("\n")) - self.raw_form = _to_rect(raw_form) - - # strip first line - self.raw_form = self.raw_form[1:] if self.raw_form else self.raw_form - - self.options.update(kwargs) - - # parse and replace - self.mapping = self._parse_rectangles( - self.cellchar, self.tablechar, self.raw_form, **kwargs - ) - self.form = self._populate_form(self.raw_form, self.mapping)
- - def __str__(self): - "Prints the form" - return str(ANSIString("\n").join([line for line in self.form]))
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/evmenu.html b/docs/0.9.5/_modules/evennia/utils/evmenu.html deleted file mode 100644 index 973e85e5f6..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/evmenu.html +++ /dev/null @@ -1,2049 +0,0 @@ - - - - - - - - evennia.utils.evmenu — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.evmenu

-"""
-The EvMenu is a full in-game menu system for Evennia.
-
-To start the menu, just import the EvMenu class from this module.
-
-Example usage:
-::
-
-    from evennia.utils.evmenu import EvMenu
-
-    EvMenu(caller, menu_module_path,
-         startnode="node1",
-         cmdset_mergetype="Replace", cmdset_priority=1,
-         auto_quit=True, cmd_on_exit="look", persistent=True)
-
-Where `caller` is the Object to use the menu on - it will get a new
-cmdset while using the Menu. The `menu_module_path` is the python path
-to a python module containing function definitions. By adjusting the
-keyword options of the Menu() initialization call you can start the
-menu at different places in the menu definition file, adjust if the
-menu command should overload the normal commands or not, etc.
-
-The `persistent` keyword will make the menu survive a server reboot.
-It is `False` by default. Note that if using persistent mode, every
-node and callback in the menu must be possible to be *pickled*, this
-excludes e.g. callables that are class methods or functions defined
-dynamically or as part of another function. In non-persistent mode
-no such restrictions exist.
-
-The menu is defined in a module (this can be the same module as the
-command definition too) with function definitions:
-::
-
-    def node1(caller):
-        # (this is the start node if called like above)
-        # code
-        return text, options
-
-    def node_with_other_name(caller, input_string):
-        # code
-        return text, options
-
-    def another_node(caller, input_string, **kwargs):
-        # code
-        return text, options
-
-Where `caller` is the object using the menu and input_string is the
-command entered by the user on the *previous* node (the command
-entered to get to this node). The node function code will only be
-executed once per node-visit and the system will accept nodes with
-both one or two arguments interchangeably. It also accepts nodes
-that takes `**kwargs`.
-
-The menu tree itself is available on the caller as
-`caller.ndb._evmenu`. This makes it a convenient place to store
-temporary state variables between nodes, since this NAttribute is
-deleted when the menu is exited.
-
-The return values must be given in the above order, but each can be
-returned as None as well. If the options are returned as None, the
-menu is immediately exited and the default "look" command is called.
-
-- `text` (str, tuple or None): Text shown at this node. If a tuple, the
-  second element in the tuple is a help text to display at this
-  node when the user enters the menu help command there.
-- `options` (tuple, dict or None): If `None`, this exits the menu.
-  If a single dict, this is a single-option node. If a tuple,
-  it should be a tuple of option dictionaries. Option dicts have
-  the following keys:
-
-  - `key` (str or tuple, optional): What to enter to choose this option.
-      If a tuple, it must be a tuple of strings, where the first string is the
-      key which will be shown to the user and the others are aliases.
-      If unset, the options' number will be used. The special key `_default`
-      marks this option as the default fallback when no other option matches
-      the user input. There can only be one `_default` option per node. It
-      will not be displayed in the list.
-  - `desc` (str, optional): This describes what choosing the option will do.
-  - `goto` (str, tuple or callable): If string, should be the name of node to go to
-      when this option is selected. If a callable, it has the signature
-      `callable(caller[,raw_input][,**kwargs])`. If a tuple, the first element
-      is the callable and the second is a dict with the kwargs to pass to
-      the callable. Those kwargs will also be passed into the next node if possible.
-      Such a callable should return either a str or a (str, dict), where the
-      string is the name of the next node to go to and the dict is the new,
-      (possibly modified) kwarg to pass into the next node. If the callable returns
-      None or the empty string, the current node will be revisited.
-  - `exec` (str, callable or tuple, optional): This takes the same input as `goto` above
-      and runs before it. If given a node name, the node will be executed but will not
-      be considered the next node. If node/callback returns str or (str, dict), these will
-      replace the `goto` step (`goto` callbacks will not fire), with the string being the
-      next node name and the optional dict acting as the kwargs-input for the next node.
-      If an exec callable returns `None`, the current node is re-run.
-
-If key is not given, the option will automatically be identified by
-its number 1..N.
-
-Example:
-::
-
-    # in menu_module.py
-
-    def node1(caller):
-        text = ("This is a node text",
-                "This is help text for this node")
-        options = ({"key": "testing",
-                    "desc": "Select this to go to node 2",
-                    "goto": ("node2", {"foo": "bar"}),
-                    "exec": "callback1"},
-                   {"desc": "Go to node 3.",
-                    "goto": "node3"})
-        return text, options
-
-    def callback1(caller):
-        # this is called when choosing the "testing" option in node1
-        # (before going to node2). If it returned a string, say 'node3',
-        # then the next node would be node3 instead of node2 as specified
-        # by the normal 'goto' option key above.
-        caller.msg("Callback called!")
-
-    def node2(caller, **kwargs):
-        text = '''
-            This is node 2. It only allows you to go back
-            to the original node1. This extra indent will
-            be stripped. We don't include a help text but
-            here are the variables passed to us: {}
-            '''.format(kwargs)
-        options = {"goto": "node1"}
-        return text, options
-
-    def node3(caller):
-        text = "This ends the menu since there are no options."
-        return text, None
-
-When starting this menu with  `Menu(caller, "path.to.menu_module")`,
-the first node will look something like this:
-::
-
-    This is a node text
-    ______________________________________
-
-    testing: Select this to go to node 2
-    2: Go to node 3
-
-Where you can both enter "testing" and "1" to select the first option.
-If the client supports MXP, they may also mouse-click on "testing" to
-do the same. When making this selection, a function "callback1" in the
-same Using `help` will show the help text, otherwise a list of
-available commands while in menu mode.
-
-The menu tree is exited either by using the in-menu quit command or by
-reaching a node without any options.
-
-
-For a menu demo, import CmdTestMenu from this module and add it to
-your default cmdset. Run it with this module, like `testmenu
-evennia.utils.evmenu`.
-
-
-## Menu generation from template string
-
-In evmenu.py is a helper function `parse_menu_template` that parses a
-template-string and outputs a menu-tree dictionary suitable to pass into
-EvMenu:
-::
-
-    menutree = evmenu.parse_menu_template(caller, menu_template, goto_callables)
-    EvMenu(caller, menutree)
-
-For maximum flexibility you can inject normally-created nodes in the menu tree
-before passing it to EvMenu. If that's not needed, you can also create a menu
-in one step with:
-::
-
-    evmenu.template2menu(caller, menu_template, goto_callables)
-
-The `goto_callables` is a mapping `{"funcname": callable, ...}`, where each
-callable must be a module-global function on the form
-`funcname(caller, raw_string, **kwargs)` (like any goto-callable). The
-`menu_template` is a multi-line string on the following form:
-::
-
-    ## node start
-
-    This is the text of the start node.
-    The text area can have multiple lines, line breaks etc.
-
-    Each option below is one of these forms
-        key: desc -> gotostr_or_func
-        key: gotostr_or_func
-        >: gotostr_or_func
-        > glob/regex: gotostr_or_func
-
-    ## options
-
-        # comments are only allowed from beginning of line.
-        # Indenting is not necessary, but good for readability
-
-        1: Option number 1 -> node1
-        2: Option number 2 -> node2
-        next: This steps next -> go_back()
-        # the -> can be ignored if there is no desc
-        back: go_back(from_node=start)
-        abort: abort
-
-    ## node node1
-
-    Text for Node1. Enter a message!
-    <return> to go back.
-
-    ## options
-
-        # Starting the option-line with >
-        # allows to perform different actions depending on
-        # what is inserted.
-
-        # this catches everything starting with foo
-        > foo*: handle_foo_message()
-
-        # regex are also allowed (this catches number inputs)
-        > [0-9]+?: handle_numbers()
-
-        # this catches the empty return
-        >: start
-
-        # this catches everything else
-        > *: handle_message(from_node=node1)
-
-    ## node node2
-
-    Text for Node2. Just go back.
-
-    ## options
-
-        >: start
-
-    # node abort
-
-    This exits the menu since there is no `## options` section.
-
-Each menu node is defined by a `# node <name>` containing the text of the node,
-followed by `## options` Also `## NODE` and `## OPTIONS` work. No python code
-logics is allowed in the template, this code is not evaluated but parsed. More
-advanced dynamic usage requires a full node-function (which can be added to the
-generated dict, as said).
-
-Adding `(..)` to a goto treats it as a callable and it must then be included in
-the `goto_callable` mapping. Only named keywords (or no args at all) are
-allowed, these will be added to the `**kwargs` going into the callable. Quoting
-strings is only needed if wanting to pass strippable spaces, otherwise the
-key:values will be converted to strings/numbers with literal_eval before passed
-into the callable.
-
-The "> " option takes a glob or regex to perform different actions depending on user
-input. Make sure to sort these in increasing order of generality since they
-will be tested in sequence.
-
-----
-
-"""
-
-import re
-import inspect
-
-from ast import literal_eval
-from fnmatch import fnmatch
-
-from inspect import isfunction, getargspec
-from django.conf import settings
-from evennia import Command, CmdSet
-from evennia.utils import logger
-from evennia.utils.evtable import EvTable
-from evennia.utils.ansi import strip_ansi
-from evennia.utils.utils import mod_import, make_iter, pad, to_str, m_len, is_iter, dedent, crop
-from evennia.commands import cmdhandler
-
-# i18n
-from django.utils.translation import gettext as _
-
-# read from protocol NAWS later?
-_MAX_TEXT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-
-# we use cmdhandler instead of evennia.syscmdkeys to
-# avoid some cases of loading before evennia init'd
-_CMD_NOMATCH = cmdhandler.CMD_NOMATCH
-_CMD_NOINPUT = cmdhandler.CMD_NOINPUT
-
-# Return messages
-
-
-_ERR_NOT_IMPLEMENTED = _(
-    "Menu node '{nodename}' is either not implemented or caused an error. "
-    "Make another choice or try 'q' to abort."
-)
-_ERR_GENERAL = _("Error in menu node '{nodename}'.")
-_ERR_NO_OPTION_DESC = _("No description.")
-_HELP_FULL = _("Commands: <menu option>, help, quit")
-_HELP_NO_QUIT = _("Commands: <menu option>, help")
-_HELP_NO_OPTIONS = _("Commands: help, quit")
-_HELP_NO_OPTIONS_NO_QUIT = _("Commands: help")
-_HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.")
-
-_ERROR_PERSISTENT_SAVING = """
-{error}
-
-|rThe menu state could not be saved for persistent mode. Switching
-to non-persistent mode (which means the menu session won't survive
-an eventual server reload).|n
-"""
-
-_TRACE_PERSISTENT_SAVING = (
-    "EvMenu persistent-mode error. Commonly, this is because one or "
-    "more of the EvEditor callbacks could not be pickled, for example "
-    "because it's a class method or is defined inside another function."
-)
-
-
-
[docs]class EvMenuError(RuntimeError): - """ - Error raised by menu when facing internal errors. - - """ - - pass
- - -
[docs]class EvMenuGotoAbortMessage(RuntimeError): - """ - This can be raised by a goto-callable to abort the goto flow. The message - stored with the executable will be sent to the caller who will remain on - the current node. This can be used to pass single-line returns without - re-running the entire node with text and options. - - Example: - raise EvMenuGotoMessage("That makes no sense.") - - """
- - -# ------------------------------------------------------------- -# -# Menu command and command set -# -# ------------------------------------------------------------- - - -
[docs]class CmdEvMenuNode(Command): - """ - Menu options. - """ - - key = _CMD_NOINPUT - aliases = [_CMD_NOMATCH] - locks = "cmd:all()" - help_category = "Menu" - auto_help_display_key = "<menu commands>" - -
[docs] def get_help(self): - return "Menu commands are explained within the menu."
- -
[docs] def func(self): - """ - Implement all menu commands. - """ - - def _restore(caller): - # check if there is a saved menu available. - # this will re-start a completely new evmenu call. - saved_options = caller.attributes.get("_menutree_saved") - if saved_options: - startnode_tuple = caller.attributes.get("_menutree_saved_startnode") - try: - startnode, startnode_input = startnode_tuple - except ValueError: # old form of startnode store - startnode, startnode_input = startnode_tuple, "" - if startnode: - saved_options[2]["startnode"] = startnode - saved_options[2]["startnode_input"] = startnode_input - MenuClass = saved_options[0] - # this will create a completely new menu call - MenuClass(caller, *saved_options[1], **saved_options[2]) - return True - return None - - caller = self.caller - # we store Session on the menu since this can be hard to - # get in multisession environments if caller is an Account. - menu = caller.ndb._evmenu - if not menu: - if _restore(caller): - return - orig_caller = caller - caller = caller.account if hasattr(caller, "account") else None - menu = caller.ndb._evmenu if caller else None - if not menu: - if caller and _restore(caller): - return - caller = self.session - menu = caller.ndb._evmenu - if not menu: - # can't restore from a session - err = "Menu object not found as %s.ndb._evmenu!" % orig_caller - orig_caller.msg( - err - ) # don't give the session as a kwarg here, direct to original - raise EvMenuError(err) - # we must do this after the caller with the menu has been correctly identified since it - # can be either Account, Object or Session (in the latter case this info will be superfluous). - caller.ndb._evmenu._session = self.session - # we have a menu, use it. - menu.parse_input(self.raw_string)
- - -
[docs]class EvMenuCmdSet(CmdSet): - """ - The Menu cmdset replaces the current cmdset. - - """ - - key = "menu_cmdset" - priority = 1 - mergetype = "Replace" - no_objs = True - no_exits = True - no_channels = False - -
[docs] def at_cmdset_creation(self): - """ - Called when creating the set. - """ - self.add(CmdEvMenuNode())
- - -# ------------------------------------------------------------ -# -# Menu main class -# -# ------------------------------------------------------------- - - -
[docs]class EvMenu: - """ - This object represents an operational menu. It is initialized from - a menufile.py instruction. - - """ - - # convenient helpers for easy overloading - node_border_char = "_" - -
[docs] def __init__( - self, - caller, - menudata, - startnode="start", - cmdset_mergetype="Replace", - cmdset_priority=1, - auto_quit=True, - auto_look=True, - auto_help=True, - cmd_on_exit="look", - persistent=False, - startnode_input="", - session=None, - debug=False, - **kwargs, - ): - """ - Initialize the menu tree and start the caller onto the first node. - - Args: - caller (Object, Account or Session): The user of the menu. - menudata (str, module or dict): The full or relative path to the module - holding the menu tree data. All global functions in this module - whose name doesn't start with '_ ' will be parsed as menu nodes. - Also the module itself is accepted as input. Finally, a dictionary - menu tree can be given directly. This must then be a mapping - `{"nodekey":callable,...}` where `callable` must be called as - and return the data expected of a menu node. This allows for - dynamic menu creation. - startnode (str, optional): The starting node name in the menufile. - cmdset_mergetype (str, optional): 'Replace' (default) means the menu - commands will be exclusive - no other normal commands will - be usable while the user is in the menu. 'Union' means the - menu commands will be integrated with the existing commands - (it will merge with `merge_priority`), if so, make sure that - the menu's command names don't collide with existing commands - in an unexpected way. Also the CMD_NOMATCH and CMD_NOINPUT will - be overloaded by the menu cmdset. Other cmdser mergetypes - has little purpose for the menu. - cmdset_priority (int, optional): The merge priority for the - menu command set. The default (1) is usually enough for most - types of menus. - auto_quit (bool, optional): Allow user to use "q", "quit" or - "exit" to leave the menu at any point. Recommended during - development! - auto_look (bool, optional): Automatically make "looK" or "l" to - re-show the last node. Turning this off means you have to handle - re-showing nodes yourself, but may be useful if you need to - use "l" for some other purpose. - auto_help (bool, optional): Automatically make "help" or "h" show - the current help entry for the node. If turned off, eventual - help must be handled manually, but it may be useful if you - need 'h' for some other purpose, for example. - cmd_on_exit (callable, str or None, optional): When exiting the menu - (either by reaching a node with no options or by using the - in-built quit command (activated with `allow_quit`), this - callback function or command string will be executed. - The callback function takes two parameters, the caller then the - EvMenu object. This is called after cleanup is complete. - Set to None to not call any command. - persistent (bool, optional): Make the Menu persistent (i.e. it will - survive a reload. This will make the Menu cmdset persistent. Use - with caution - if your menu is buggy you may end up in a state - you can't get out of! Also note that persistent mode requires - that all formatters, menu nodes and callables are possible to - *pickle*. When the server is reloaded, the latest node shown will be completely - re-run with the same input arguments - so be careful if you are counting - up some persistent counter or similar - the counter may be run twice if - reload happens on the node that does that. Note that if `debug` is True, - this setting is ignored and assumed to be False. - startnode_input (str or (str, dict), optional): Send an input text to `startnode` as if - a user input text from a fictional previous node. If including the dict, this will - be passed as **kwargs to that node. When the server reloads, - the latest visited node will be re-run as `node(caller, raw_string, **kwargs)`. - session (Session, optional): This is useful when calling EvMenu from an account - in multisession mode > 2. Note that this session only really relevant - for the very first display of the first node - after that, EvMenu itself - will keep the session updated from the command input. So a persistent - menu will *not* be using this same session anymore after a reload. - debug (bool, optional): If set, the 'menudebug' command will be made available - by default in all nodes of the menu. This will print out the current state of - the menu. Deactivate for production use! When the debug flag is active, the - `persistent` flag is deactivated. - - Keyword Args: - any (any): All kwargs will become initialization variables on `caller.ndb._evmenu`, - to be available at run. - - Raises: - EvMenuError: If the start/end node is not found in menu tree. - - Notes: - While running, the menu is stored on the caller as `caller.ndb._evmenu`. Also - the current Session (from the Command, so this is still valid in multisession - environments) is available through `caller.ndb._evmenu._session`. The `_evmenu` - property is a good one for storing intermediary data on between nodes since it - will be automatically deleted when the menu closes. - - In persistent mode, all nodes, formatters and callbacks in the menu must be - possible to be *pickled*, this excludes e.g. callables that are class methods - or functions defined dynamically or as part of another function. In - non-persistent mode no such restrictions exist. - - """ - self._startnode = startnode - self._menutree = self._parse_menudata(menudata) - self._persistent = persistent if not debug else False - self._quitting = False - - if startnode not in self._menutree: - raise EvMenuError("Start node '%s' not in menu tree!" % startnode) - - # public variables made available to the command - - self.caller = caller - - # track EvMenu kwargs - self.auto_quit = auto_quit - self.auto_look = auto_look - self.auto_help = auto_help - self.debug_mode = debug - self._session = session - if isinstance(cmd_on_exit, str): - # At this point menu._session will have been replaced by the - # menu command to the actual session calling. - self.cmd_on_exit = lambda caller, menu: caller.execute_cmd( - cmd_on_exit, session=menu._session - ) - elif callable(cmd_on_exit): - self.cmd_on_exit = cmd_on_exit - else: - self.cmd_on_exit = None - # current menu state - self.default = None - self.nodetext = None - self.helptext = None - self.options = None - self.nodename = None - self.node_kwargs = {} - - # used for testing - self.test_options = {} - self.test_nodetext = "" - - # assign kwargs as initialization vars on ourselves. - reserved_clash = set( - ( - "_startnode", - "_menutree", - "_session", - "_persistent", - "cmd_on_exit", - "default", - "nodetext", - "helptext", - "options", - "cmdset_mergetype", - "auto_quit", - ) - ).intersection(set(kwargs.keys())) - if reserved_clash: - raise RuntimeError( - f"One or more of the EvMenu `**kwargs` ({list(reserved_clash)}) is reserved by EvMenu for internal use." - ) - for key, val in kwargs.items(): - setattr(self, key, val) - - if self.caller.ndb._evmenu: - # an evmenu already exists - we try to close it cleanly. Note that this will - # not fire the previous menu's end node. - try: - self.caller.ndb._evmenu.close_menu() - except Exception: - pass - - # store ourself on the object - self.caller.ndb._evmenu = self - - # DEPRECATED - for backwards-compatibility - self.caller.ndb._menutree = self - - if persistent: - # save the menu to the database - calldict = { - "startnode": startnode, - "cmdset_mergetype": cmdset_mergetype, - "cmdset_priority": cmdset_priority, - "auto_quit": auto_quit, - "auto_look": auto_look, - "auto_help": auto_help, - "cmd_on_exit": cmd_on_exit, - "persistent": persistent, - } - calldict.update(kwargs) - try: - caller.attributes.add("_menutree_saved", (self.__class__, (menudata,), calldict)) - caller.attributes.add("_menutree_saved_startnode", (startnode, startnode_input)) - except Exception as err: - self.msg(_ERROR_PERSISTENT_SAVING.format(error=err)) - logger.log_trace(_TRACE_PERSISTENT_SAVING) - persistent = False - - # set up the menu command on the caller - menu_cmdset = EvMenuCmdSet() - menu_cmdset.mergetype = str(cmdset_mergetype).lower().capitalize() or "Replace" - menu_cmdset.priority = int(cmdset_priority) - self.caller.cmdset.add(menu_cmdset, permanent=persistent) - - reserved_startnode_kwargs = set(("nodename", "raw_string")) - startnode_kwargs = {} - if isinstance(startnode_input, (tuple, list)) and len(startnode_input) > 1: - startnode_input, startnode_kwargs = startnode_input[:2] - if not isinstance(startnode_kwargs, dict): - raise EvMenuError("startnode_input must be either a str or a tuple (str, dict).") - clashing_kwargs = reserved_startnode_kwargs.intersection(set(startnode_kwargs.keys())) - if clashing_kwargs: - raise RuntimeError( - f"Evmenu startnode_inputs includes kwargs {tuple(clashing_kwargs)} that " - "clashes with EvMenu's internal usage." - ) - - # start the menu - self.goto(self._startnode, startnode_input, **startnode_kwargs)
- - def _parse_menudata(self, menudata): - """ - Parse a menufile for node functions and store in dictionary - map. Alternatively, accept a pre-made mapping dictionary of - node functions. - - Args: - menudata (str, module or dict): The python.path to the menufile, - or the python module itself. If a dict, this should be a - mapping nodename:callable, where the callable must match - the criteria for a menu node. - - Returns: - menutree (dict): A {nodekey: func} - - """ - if isinstance(menudata, dict): - # This is assumed to be a pre-loaded menu tree. - return menudata - else: - # a python path of a module - module = mod_import(menudata) - return dict( - (key, func) - for key, func in module.__dict__.items() - if isfunction(func) and not key.startswith("_") - ) - - def _format_node(self, nodetext, optionlist): - """ - Format the node text + option section - - Args: - nodetext (str): The node text - optionlist (list): List of (key, desc) pairs. - - Returns: - string (str): The options section, including - all needed spaces. - - Notes: - This will adjust the columns of the options, first to use - a maxiumum of 4 rows (expanding in columns), then gradually - growing to make use of the screen space. - - """ - - # handle the node text - nodetext = self.nodetext_formatter(nodetext) - - # handle the options - optionstext = self.options_formatter(optionlist) - - # format the entire node - return self.node_formatter(nodetext, optionstext) - - def _safe_call(self, callback, raw_string, **kwargs): - """ - Call a node-like callable, with a variable number of raw_string, *args, **kwargs, all of - which should work also if not present (only `caller` is always required). Return its result. - - Viable node-like callable forms: - :: - - _callname(caller) - _callname(caller, raw_string) - _callname(caller, **kwargs) - _callname(caller, raw_string, **kwargs) - - If this is a node: - - - `caller` is the one using the menu. - - `raw_string` is the users exact input on the *previous* node. - - `**kwargs` is either passed through the previous node or returned - along with the node name from the goto-callable leading to this node. - - If this is a goto-callable: - - - `caller` is the one using the menu. - - `raw_string` is the user's exact input when chosing the option that triggered - this goto-callable. - - `**kwargs` is any extra dict passed to the callable in the option - definition, or (if no explit kwarg was given to the callable) the - previous node's kwarg, if any. - - """ - try: - try: - nargs = len(getargspec(callback).args) - except TypeError: - raise EvMenuError("Callable {} doesn't accept any arguments!".format(callback)) - supports_kwargs = bool(getargspec(callback).keywords) - if nargs <= 0: - raise EvMenuError("Callable {} doesn't accept any arguments!".format(callback)) - - if supports_kwargs: - if nargs > 1: - ret = callback(self.caller, raw_string, **kwargs) - # callback accepting raw_string, **kwargs - else: - # callback accepting **kwargs - ret = callback(self.caller, **kwargs) - elif nargs > 1: - # callback accepting raw_string - ret = callback(self.caller, raw_string) - else: - # normal callback, only the caller as arg - ret = callback(self.caller) - except EvMenuError: - errmsg = _ERR_GENERAL.format(nodename=callback) - self.msg(errmsg) - logger.log_trace() - raise - - return ret - - def _execute_node(self, nodename, raw_string, **kwargs): - """ - Execute a node. - - Args: - nodename (str): Name of node. - raw_string (str): The raw default string entered on the - previous node (only used if the node accepts it as an - argument) - kwargs (any, optional): Optional kwargs for the node. - - Returns: - nodetext, options (tuple): The node text (a string or a - tuple and the options tuple, if any. - - """ - try: - node = self._menutree[nodename] - except KeyError: - self.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename)) - raise EvMenuError - try: - kwargs["_current_nodename"] = nodename - ret = self._safe_call(node, raw_string, **kwargs) - if isinstance(ret, (tuple, list)) and len(ret) > 1: - nodetext, options = ret[:2] - else: - nodetext, options = ret, None - except KeyError: - self.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename)) - logger.log_trace() - raise EvMenuError - except Exception: - self.msg(_ERR_GENERAL.format(nodename=nodename)) - logger.log_trace() - raise - - # store options to make them easier to test - self.test_options = options - self.test_nodetext = nodetext - - return nodetext, options - -
[docs] def msg(self, txt): - """ - This is a central point for sending return texts to the caller. It - allows for a central point to add custom messaging when creating custom - EvMenu overrides. - - Args: - txt (str): The text to send. - - Notes: - By default this will send to the same session provided to EvMenu - (if `session` kwarg was provided to `EvMenu.__init__`). It will - also send it with a `type=menu` for the benefit of OOB/webclient. - - """ - self.caller.msg(text=(txt, {"type": "menu"}), session=self._session)
- -
[docs] def run_exec(self, nodename, raw_string, **kwargs): - """ - NOTE: This is deprecated. Use `goto` directly instead. - - Run a function or node as a callback (with the 'exec' option key). - - Args: - nodename (callable or str): A callable to run as - `callable(caller, raw_string)`, or the Name of an existing - node to run as a callable. This may or may not return - a string. - raw_string (str): The raw default string entered on the - previous node (only used if the node accepts it as an - argument) - kwargs (any): These are optional kwargs passed into goto - - Returns: - new_goto (str or None): A replacement goto location string or - None (no replacement). - Notes: - Relying on exec callbacks to set the goto location is - very powerful but will easily lead to spaghetti structure and - hard-to-trace paths through the menu logic. So be careful with - relying on this. - - """ - try: - if callable(nodename): - # this is a direct callable - execute it directly - ret = self._safe_call(nodename, raw_string, **kwargs) - if isinstance(ret, (tuple, list)): - if not len(ret) > 1 or not isinstance(ret[1], dict): - raise EvMenuError( - "exec callable must return either None, str or (str, dict)" - ) - ret, kwargs = ret[:2] - else: - # nodename is a string; lookup as node and run as node in-place (don't goto it) - # execute the node - ret = self._execute_node(nodename, raw_string, **kwargs) - if isinstance(ret, (tuple, list)): - if not len(ret) > 1 and ret[1] and not isinstance(ret[1], dict): - raise EvMenuError("exec node must return either None, str or (str, dict)") - ret, kwargs = ret[:2] - except EvMenuError as err: - errmsg = "Error in exec '%s' (input: '%s'): %s" % (nodename, raw_string.rstrip(), err) - self.msg("|r%s|n" % errmsg) - logger.log_trace(errmsg) - return - - if isinstance(ret, str): - # only return a value if a string (a goto target), ignore all other returns - if not ret: - # an empty string - rerun the same node - return self.nodename - return ret, kwargs - return None
- -
[docs] def extract_goto_exec(self, nodename, option_dict): - """ - Helper: Get callables and their eventual kwargs. - - Args: - nodename (str): The current node name (used for error reporting). - option_dict (dict): The seleted option's dict. - - Returns: - goto (str, callable or None): The goto directive in the option. - goto_kwargs (dict): Kwargs for `goto` if the former is callable, otherwise empty. - execute (callable or None): Executable given by the `exec` directive. - exec_kwargs (dict): Kwargs for `execute` if it's callable, otherwise empty. - - """ - goto_kwargs, exec_kwargs = {}, {} - goto, execute = option_dict.get("goto", None), option_dict.get("exec", None) - if goto and isinstance(goto, (tuple, list)): - if len(goto) > 1: - goto, goto_kwargs = goto[:2] # ignore any extra arguments - if not hasattr(goto_kwargs, "__getitem__"): - # not a dict-like structure - raise EvMenuError( - "EvMenu node {}: goto kwargs is not a dict: {}".format( - nodename, goto_kwargs - ) - ) - else: - goto = goto[0] - if execute and isinstance(execute, (tuple, list)): - if len(execute) > 1: - execute, exec_kwargs = execute[:2] # ignore any extra arguments - if not hasattr(exec_kwargs, "__getitem__"): - # not a dict-like structure - raise EvMenuError( - "EvMenu node {}: exec kwargs is not a dict: {}".format( - nodename, goto_kwargs - ) - ) - else: - execute = execute[0] - return goto, goto_kwargs, execute, exec_kwargs
- -
[docs] def goto(self, nodename, raw_string, **kwargs): - """ - Run a node by name, optionally dynamically generating that name first. - - Args: - nodename (str or callable): Name of node or a callable - to be called as `function(caller, raw_string, **kwargs)` or - `function(caller, **kwargs)` to return the actual goto string or - a ("nodename", kwargs) tuple. - raw_string (str): The raw default string entered on the - previous node (only used if the node accepts it as an - argument) - Keyword Args: - any: Extra arguments to goto callables. - - """ - - if callable(nodename): - # run the "goto" callable, if possible - inp_nodename = nodename - nodename = self._safe_call(nodename, raw_string, **kwargs) - if isinstance(nodename, (tuple, list)): - if not len(nodename) > 1 or not isinstance(nodename[1], dict): - raise EvMenuError( - "{}: goto callable must return str or (str, dict)".format(inp_nodename) - ) - nodename, kwargs = nodename[:2] - if not nodename: - # no nodename return. Re-run current node - nodename = self.nodename - try: - # execute the found node, make use of the returns. - nodetext, options = self._execute_node(nodename, raw_string, **kwargs) - except EvMenuError: - return - - if self._persistent: - self.caller.attributes.add( - "_menutree_saved_startnode", (nodename, (raw_string, kwargs)) - ) - - # validation of the node return values - helptext = "" - if is_iter(nodetext): - if len(nodetext) > 1: - nodetext, helptext = nodetext[:2] - else: - nodetext = nodetext[0] - nodetext = "" if nodetext is None else str(nodetext) - options = [options] if isinstance(options, dict) else options - - # this will be displayed in the given order - display_options = [] - # this is used for lookup - self.options = {} - self.default = None - if options: - for inum, dic in enumerate(options): - # fix up the option dicts - keys = make_iter(dic.get("key")) - desc = dic.get("desc", dic.get("text", None)) - if "_default" in keys: - keys = [key for key in keys if key != "_default"] - goto, goto_kwargs, execute, exec_kwargs = self.extract_goto_exec(nodename, dic) - self.default = (goto, goto_kwargs, execute, exec_kwargs) - else: - # use the key (only) if set, otherwise use the running number - keys = list(make_iter(dic.get("key", str(inum + 1).strip()))) - goto, goto_kwargs, execute, exec_kwargs = self.extract_goto_exec(nodename, dic) - if keys: - display_options.append((keys[0], desc)) - for key in keys: - if goto or execute: - self.options[strip_ansi(key).strip().lower()] = ( - goto, - goto_kwargs, - execute, - exec_kwargs, - ) - - self.nodetext = self._format_node(nodetext, display_options) - self.node_kwargs = kwargs - self.nodename = nodename - - # handle the helptext - if helptext: - self.helptext = self.helptext_formatter(helptext) - elif options: - self.helptext = _HELP_FULL if self.auto_quit else _HELP_NO_QUIT - else: - self.helptext = _HELP_NO_OPTIONS if self.auto_quit else _HELP_NO_OPTIONS_NO_QUIT - - self.display_nodetext() - if not options: - self.close_menu()
- -
[docs] def run_exec_then_goto(self, runexec, goto, raw_string, runexec_kwargs=None, goto_kwargs=None): - """ - Call 'exec' callback and goto (which may also be a callable) in sequence. - - Args: - runexec (callable or str): Callback to run before goto. If - the callback returns a string, this is used to replace - the `goto` string/callable before being passed into the goto handler. - goto (str): The target node to go to next (may be replaced - by `runexec`).. - raw_string (str): The original user input. - runexec_kwargs (dict, optional): Optional kwargs for runexec. - goto_kwargs (dict, optional): Optional kwargs for goto. - - """ - if runexec: - # replace goto only if callback returns - goto, goto_kwargs = self.run_exec( - runexec, raw_string, **(runexec_kwargs if runexec_kwargs else {}) - ) or (goto, goto_kwargs) - if goto: - self.goto(goto, raw_string, **(goto_kwargs if goto_kwargs else {}))
- -
[docs] def close_menu(self): - """ - Shutdown menu; occurs when reaching the end node or using the quit command. - """ - if not self._quitting: - # avoid multiple calls from different sources - self._quitting = True - self.caller.cmdset.remove(EvMenuCmdSet) - del self.caller.ndb._evmenu - if self._persistent: - self.caller.attributes.remove("_menutree_saved") - self.caller.attributes.remove("_menutree_saved_startnode") - if self.cmd_on_exit is not None: - self.cmd_on_exit(self.caller, self) - # special for template-generated menues - del self.caller.db._evmenu_template_contents
- -
[docs] def print_debug_info(self, arg): - """ - Messages the caller with the current menu state, for debug purposes. - - Args: - arg (str): Arg to debug instruction, either nothing, 'full' or the name - of a property to inspect. - - """ - all_props = inspect.getmembers(self) - all_methods = [name for name, _ in inspect.getmembers(self, predicate=inspect.ismethod)] - all_builtins = [name for name, _ in inspect.getmembers(self, predicate=inspect.isbuiltin)] - props = { - prop: value - for prop, value in all_props - if prop not in all_methods and prop not in all_builtins and not prop.endswith("__") - } - - local = { - key: var - for key, var in locals().items() - if key not in all_props and not key.endswith("__") - } - - if arg: - if arg in props: - debugtxt = " |y* {}:|n\n{}".format(arg, props[arg]) - elif arg in local: - debugtxt = " |y* {}:|n\n{}".format(arg, local[arg]) - elif arg == "full": - debugtxt = ( - "|yMENU DEBUG full ... |n\n" - + "\n".join( - "|y *|n {}: {}".format(key, val) for key, val in sorted(props.items()) - ) - + "\n |yLOCAL VARS:|n\n" - + "\n".join( - "|y *|n {}: {}".format(key, val) for key, val in sorted(local.items()) - ) - + "\n |y... END MENU DEBUG|n" - ) - else: - debugtxt = "|yUsage: menudebug full|<name of property>|n" - else: - debugtxt = ( - "|yMENU DEBUG properties ... |n\n" - + "\n".join( - "|y *|n {}: {}".format(key, crop(to_str(val, force_string=True), width=50)) - for key, val in sorted(props.items()) - ) - + "\n |yLOCAL VARS:|n\n" - + "\n".join( - "|y *|n {}: {}".format(key, crop(to_str(val, force_string=True), width=50)) - for key, val in sorted(local.items()) - ) - + "\n |y... END MENU DEBUG|n" - ) - self.msg(debugtxt)
- -
[docs] def parse_input(self, raw_string): - """ - Parses the incoming string from the menu user. - - Args: - raw_string (str): The incoming, unmodified string - from the user. - Notes: - This method is expected to parse input and use the result - to relay execution to the relevant methods of the menu. It - should also report errors directly to the user. - - """ - cmd = strip_ansi(raw_string.strip().lower()) - - try: - if self.options and cmd in self.options: - # this will take precedence over the default commands - # below - goto, goto_kwargs, execfunc, exec_kwargs = self.options[cmd] - self.run_exec_then_goto(execfunc, goto, raw_string, exec_kwargs, goto_kwargs) - elif self.auto_look and cmd in ("look", "l"): - self.display_nodetext() - elif self.auto_help and cmd in ("help", "h"): - self.display_helptext() - elif self.auto_quit and cmd in ("quit", "q", "exit"): - self.close_menu() - elif self.debug_mode and cmd.startswith("menudebug"): - self.print_debug_info(cmd[9:].strip()) - elif self.default: - goto, goto_kwargs, execfunc, exec_kwargs = self.default - self.run_exec_then_goto(execfunc, goto, raw_string, exec_kwargs, goto_kwargs) - else: - self.msg(_HELP_NO_OPTION_MATCH) - except EvMenuGotoAbortMessage as err: - # custom interrupt from inside a goto callable - print the message and - # stay on the current node. - self.msg(str(err))
- -
[docs] def display_nodetext(self): - self.msg(self.nodetext)
- -
[docs] def display_helptext(self): - self.msg(self.helptext)
- - # formatters - override in a child class - -
[docs] def nodetext_formatter(self, nodetext): - """ - Format the node text itself. - - Args: - nodetext (str): The full node text (the text describing the node). - - Returns: - nodetext (str): The formatted node text. - - """ - return dedent(nodetext.strip("\n"), baseline_index=0).rstrip()
- -
[docs] def helptext_formatter(self, helptext): - """ - Format the node's help text - - Args: - helptext (str): The unformatted help text for the node. - - Returns: - helptext (str): The formatted help text. - - """ - return dedent(helptext.strip("\n"), baseline_index=0).rstrip()
- -
[docs] def options_formatter(self, optionlist): - """ - Formats the option block. - - Args: - optionlist (list): List of (key, description) tuples for every - option related to this node. - caller (Object, Account or None, optional): The caller of the node. - - Returns: - options (str): The formatted option display. - - """ - if not optionlist: - return "" - - # column separation distance - colsep = 4 - - nlist = len(optionlist) - - # get the widest option line in the table. - table_width_max = -1 - table = [] - for key, desc in optionlist: - if key or desc: - desc_string = ": %s" % desc if desc else "" - table_width_max = max( - table_width_max, - max(m_len(p) for p in key.split("\n")) - + max(m_len(p) for p in desc_string.split("\n")) - + colsep, - ) - raw_key = strip_ansi(key) - if raw_key != key: - # already decorations in key definition - table.append(" |lc%s|lt%s|le%s" % (raw_key, key, desc_string)) - else: - # add a default white color to key - table.append(" |lc%s|lt|w%s|n|le%s" % (raw_key, raw_key, desc_string)) - ncols = _MAX_TEXT_WIDTH // table_width_max # number of ncols - - if ncols < 0: - # no visible option at all - return "" - - ncols = ncols + 1 if ncols == 0 else ncols - # get the amount of rows needed (start with 4 rows) - nrows = 4 - while nrows * ncols < nlist: - nrows += 1 - ncols = nlist // nrows # number of full columns - nlastcol = nlist % nrows # number of elements in last column - - # get the final column count - ncols = ncols + 1 if nlastcol > 0 else ncols - if ncols > 1: - # only extend if longer than one column - table.extend([" " for i in range(nrows - nlastcol)]) - - # build the actual table grid - table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)] - - # adjust the width of each column - for icol in range(len(table)): - col_width = ( - max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep - ) - table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]] - - # format the table into columns - return str(EvTable(table=table, border="none"))
- -
[docs] def node_formatter(self, nodetext, optionstext): - """ - Formats the entirety of the node. - - Args: - nodetext (str): The node text as returned by `self.nodetext_formatter`. - optionstext (str): The options display as returned by `self.options_formatter`. - caller (Object, Account or None, optional): The caller of the node. - - Returns: - node (str): The formatted node to display. - - """ - sep = self.node_border_char - - if self._session: - screen_width = self._session.protocol_flags.get("SCREENWIDTH", {0: _MAX_TEXT_WIDTH})[0] - else: - screen_width = _MAX_TEXT_WIDTH - - nodetext_width_max = max(m_len(line) for line in nodetext.split("\n")) - options_width_max = max(m_len(line) for line in optionstext.split("\n")) - total_width = min(screen_width, max(options_width_max, nodetext_width_max)) - separator1 = sep * total_width + "\n\n" if nodetext_width_max else "" - separator2 = "\n" + sep * total_width + "\n\n" if total_width else "" - return separator1 + "|n" + nodetext + "|n" + separator2 + "|n" + optionstext
- - -# ----------------------------------------------------------- -# -# List node (decorator turning a node into a list with -# look/edit/add functionality for the elements) -# -# ----------------------------------------------------------- - - -
[docs]def list_node(option_generator, select=None, pagesize=10): - """ - Decorator for making an EvMenu node into a multi-page list node. Will add new options, - prepending those options added in the node. - - Args: - option_generator (callable or list): A list of strings indicating the options, or a callable - that is called as option_generator(caller) to produce such a list. - select (callable or str, optional): Node to redirect a selection to. Its `**kwargs` will - contain the `available_choices` list and `selection` will hold one - of the elements in that list. If a callable, it will be called as - `select(caller, menuchoice, **kwargs)` where menuchoice is the - chosen option as a string and `available_choices` is the list of available - options offered by the option_generator. The callable whould return - the name of the target node to goto after this selection (or None to repeat the - list-node). Note that if this is not given, the decorated node - must itself provide a way to continue from the node! - pagesize (int): How many options to show per page. - - Example: - :: - - def _selectfunc(caller, menuchoice, **kwargs): - # menuchoice would be either 'foo' or 'bar' here - # kwargs['available_choices'] would be the list ['foo', 'bar'] - return "the_next_node_to_go_to" - - @list_node(['foo', 'bar'], _selectfunc) - def node_index(caller): - text = "describing the list" - return text, [] - - Notes: - All normal `goto` or `exec` callables returned from the decorated nodes will, if they accept - `**kwargs`, get a new kwarg `available_choices` injected. This is the ordered list of named - options (descs) visible on the current node page. - - """ - - def decorator(func): - def _select_parser(caller, raw_string, **kwargs): - """ - Parse the select action - """ - available_choices = kwargs.get("available_choices", []) - - try: - index = int(raw_string.strip()) - 1 - selection = available_choices[index] - except Exception: - caller.msg("|rInvalid choice.|n") - else: - if callable(select): - try: - if bool(getargspec(select).keywords): - return select(caller, selection, available_choices=available_choices) - else: - return select(caller, selection) - except Exception: - logger.log_trace() - elif select: - # we assume a string was given, we inject the result into the kwargs - # to pass on to the next node - kwargs["selection"] = selection - return str(select) - # this means the previous node will be re-run with these same kwargs - return None - - def _list_node(caller, raw_string, **kwargs): - - option_list = ( - option_generator(caller) if callable(option_generator) else option_generator - ) - - npages = 0 - page_index = 0 - page = [] - options = [] - - if option_list: - nall_options = len(option_list) - pages = [ - option_list[ind : ind + pagesize] for ind in range(0, nall_options, pagesize) - ] - npages = len(pages) - - page_index = max(0, min(npages - 1, kwargs.get("optionpage_index", 0))) - page = pages[page_index] - - text = "" - extra_text = None - - # dynamic, multi-page option list. Each selection leads to the `select` - # callback being called with a result from the available choices - options.extend( - [ - {"desc": opt, "goto": (_select_parser, {"available_choices": page})} - for opt in page - ] - ) - - if npages > 1: - # if the goto callable returns None, the same node is rerun, and - # kwargs not used by the callable are passed on to the node. This - # allows us to call ourselves over and over, using different kwargs. - options.append( - { - "key": ("|Wcurrent|n", "c"), - "desc": "|W({}/{})|n".format(page_index + 1, npages), - "goto": (lambda caller: None, {"optionpage_index": page_index}), - } - ) - if page_index > 0: - options.append( - { - "key": ("|wp|Wrevious page|n", "p"), - "goto": (lambda caller: None, {"optionpage_index": page_index - 1}), - } - ) - if page_index < npages - 1: - options.append( - { - "key": ("|wn|Wext page|n", "n"), - "goto": (lambda caller: None, {"optionpage_index": page_index + 1}), - } - ) - - # add data from the decorated node - - decorated_options = [] - supports_kwargs = bool(getargspec(func).keywords) - try: - if supports_kwargs: - text, decorated_options = func(caller, raw_string, **kwargs) - else: - text, decorated_options = func(caller, raw_string) - except TypeError: - try: - if supports_kwargs: - text, decorated_options = func(caller, **kwargs) - else: - text, decorated_options = func(caller) - except Exception: - raise - except Exception: - logger.log_trace() - else: - if isinstance(decorated_options, dict): - decorated_options = [decorated_options] - else: - decorated_options = make_iter(decorated_options) - - extra_options = [] - if isinstance(decorated_options, dict): - decorated_options = [decorated_options] - for eopt in decorated_options: - cback = ("goto" in eopt and "goto") or ("exec" in eopt and "exec") or None - if cback: - signature = eopt[cback] - if callable(signature): - # callable with no kwargs defined - eopt[cback] = (signature, {"available_choices": page}) - elif is_iter(signature): - if len(signature) > 1 and isinstance(signature[1], dict): - signature[1]["available_choices"] = page - eopt[cback] = signature - elif signature: - # a callable alone in a tuple (i.e. no previous kwargs) - eopt[cback] = (signature[0], {"available_choices": page}) - else: - # malformed input. - logger.log_err( - "EvMenu @list_node decorator found " - "malformed option to decorate: {}".format(eopt) - ) - extra_options.append(eopt) - - options.extend(extra_options) - text = text + "\n\n" + extra_text if extra_text else text - - return text, options - - return _list_node - - return decorator
- - -# ------------------------------------------------------------------------------------------------- -# -# Simple input shortcuts -# -# ------------------------------------------------------------------------------------------------- - - -
[docs]class CmdGetInput(Command): - """ - Enter your data and press return. - """ - - key = _CMD_NOMATCH - aliases = _CMD_NOINPUT - -
[docs] def func(self): - """This is called when user enters anything.""" - caller = self.caller - try: - getinput = caller.ndb._getinput - if not getinput and hasattr(caller, "account"): - getinput = caller.account.ndb._getinput - caller = caller.account - callback = getinput._callback - - caller.ndb._getinput._session = self.session - prompt = caller.ndb._getinput._prompt - args = caller.ndb._getinput._args - kwargs = caller.ndb._getinput._kwargs - result = self.raw_string.rstrip() # we strip the ending line break caused by sending - - ok = not callback(caller, prompt, result, *args, **kwargs) - if ok: - # only clear the state if the callback does not return - # anything - del caller.ndb._getinput - caller.cmdset.remove(InputCmdSet) - except Exception: - # make sure to clean up cmdset if something goes wrong - caller.msg("|rError in get_input. Choice not confirmed (report to admin)|n") - logger.log_trace("Error in get_input") - caller.cmdset.remove(InputCmdSet)
- - -
[docs]class InputCmdSet(CmdSet): - """ - This stores the input command - """ - - key = "input_cmdset" - priority = 1 - mergetype = "Replace" - no_objs = True - no_exits = True - no_channels = False - -
[docs] def at_cmdset_creation(self): - """called once at creation""" - self.add(CmdGetInput())
- - -class _Prompt(object): - """Dummy holder""" - - pass - - -
[docs]def get_input(caller, prompt, callback, session=None, *args, **kwargs): - """ - This is a helper function for easily request input from - the caller. - - Args: - caller (Account or Object): The entity being asked - the question. This should usually be an object - controlled by a user. - prompt (str): This text will be shown to the user, - in order to let them know their input is needed. - callback (callable): A function that will be called - when the user enters a reply. It must take three - arguments: the `caller`, the `prompt` text and the - `result` of the input given by the user. If the - callback doesn't return anything or return False, - the input prompt will be cleaned up and exited. If - returning True, the prompt will remain and continue to - accept input. - session (Session, optional): This allows to specify the - session to send the prompt to. It's usually only - needed if `caller` is an Account in multisession modes - greater than 2. The session is then updated by the - command and is available (for example in callbacks) - through `caller.ndb.getinput._session`. - args, kwargs (optional): Extra arguments will be - passed to the fall back function as a list 'args' - and all keyword arguments as a dictionary 'kwargs'. - To utilise `*args` and `**kwargs`, a value for the - session argument must be provided (None by default) - and the callback function must take `*args` and - `**kwargs` as arguments. - - Raises: - RuntimeError: If the given callback is not callable. - - Notes: - The result value sent to the callback is raw and not - processed in any way. This means that you will get - the ending line return character from most types of - client inputs. So make sure to strip that before - doing a comparison. - - When the prompt is running, a temporary object - `caller.ndb._getinput` is stored; this will be removed - when the prompt finishes. - If you need the specific Session of the caller (which - may not be easy to get if caller is an account in higher - multisession modes), then it is available in the - callback through `caller.ndb._getinput._session`. - - Chaining get_input functions will result in the caller - stacking ever more instances of InputCmdSets. Whilst - they will all be cleared on concluding the get_input - chain, EvMenu should be considered for anything beyond - a single question. - - """ - if not callable(callback): - raise RuntimeError("get_input: input callback is not callable.") - caller.ndb._getinput = _Prompt() - caller.ndb._getinput._callback = callback - caller.ndb._getinput._prompt = prompt - caller.ndb._getinput._session = session - caller.ndb._getinput._args = args - caller.ndb._getinput._kwargs = kwargs - caller.cmdset.add(InputCmdSet) - caller.msg(prompt, session=session)
- - -# ------------------------------------------------------------- -# -# Menu generation from menu template string -# -# ------------------------------------------------------------- - -_RE_NODE = re.compile(r"##\s*?NODE\s+?(?P<nodename>\S[\S\s]*?)$", re.I + re.M) -_RE_OPTIONS_SEP = re.compile(r"##\s*?OPTIONS\s*?$", re.I + re.M) -_RE_CALLABLE = re.compile(r"\S+?\(\)", re.I + re.M) -_RE_CALLABLE = re.compile(r"(?P<funcname>\S+?)(?:\((?P<kwargs>[\S\s]+?)\)|\(\))", re.I + re.M) - -_HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.") - -_OPTION_INPUT_MARKER = ">" -_OPTION_ALIAS_MARKER = ";" -_OPTION_SEP_MARKER = ":" -_OPTION_CALL_MARKER = "->" -_OPTION_COMMENT_START = "#" - - -# Input/option/goto handler functions that allows for dynamically generated -# nodes read from the menu template. - - -def _process_callable(caller, goto, goto_callables, raw_string, current_nodename, kwargs): - """ - Central helper for parsing a goto-callable (`funcname(**kwargs)`) out of - the right-hand-side of the template options and map this to an actual - callable registered with the template generator. This involves parsing the - func-name and running literal-eval on its kwargs. - - """ - match = _RE_CALLABLE.match(goto) - if match: - gotofunc = match.group("funcname") - gotokwargs = match.group("kwargs") or "" - if gotofunc in goto_callables: - for kwarg in gotokwargs.split(","): - if kwarg and "=" in kwarg: - key, value = [part.strip() for part in kwarg.split("=", 1)] - if key in ( - "evmenu_goto", - "evmenu_gotomap", - "_current_nodename", - "evmenu_current_nodename", - "evmenu_goto_callables", - ): - raise RuntimeError( - f"EvMenu template error: goto-callable '{goto}' uses a " - f"kwarg ({kwarg}) that is reserved for the EvMenu templating " - "system. Rename the kwarg." - ) - try: - key = literal_eval(key) - except ValueError: - pass - try: - value = literal_eval(value) - except ValueError: - pass - kwargs[key] = value - - goto = goto_callables[gotofunc](caller, raw_string, **kwargs) - if goto is None: - return goto, {"generated_nodename": current_nodename} - return goto, {"generated_nodename": goto} - - -def _generated_goto_func(caller, raw_string, **kwargs): - """ - This rerouter handles normal direct goto func call matches. - - key : ... -> goto_callable(**kwargs) - - """ - goto = kwargs["evmenu_goto"] - goto_callables = kwargs["evmenu_goto_callables"] - current_nodename = kwargs["evmenu_current_nodename"] - return _process_callable(caller, goto, goto_callables, raw_string, current_nodename, kwargs) - - -def _generated_input_goto_func(caller, raw_string, **kwargs): - """ - This goto-func acts as a rerouter for >-type line parsing (by acting as the - _default option). The patterns discovered in the menu maps to different - *actual* goto-funcs. We map to those here. - - >pattern: ... -> goto_callable - - """ - gotomap = kwargs["evmenu_gotomap"] - goto_callables = kwargs["evmenu_goto_callables"] - current_nodename = kwargs["evmenu_current_nodename"] - raw_string = raw_string.strip("\n") # strip is necessary to catch empty return - - # start with glob patterns - for pattern, goto in gotomap.items(): - if fnmatch(raw_string.lower(), pattern): - return _process_callable( - caller, goto, goto_callables, raw_string, current_nodename, kwargs - ) - # no glob pattern match; try regex - for pattern, goto in gotomap.items(): - if pattern and re.match(pattern, raw_string.lower(), flags=re.I + re.M): - return _process_callable( - caller, goto, goto_callables, raw_string, current_nodename, kwargs - ) - # no match, show error - raise EvMenuGotoAbortMessage(_HELP_NO_OPTION_MATCH) - - -def _generated_node(caller, raw_string, **kwargs): - """ - Every node in the templated menu will be this node, but with dynamically - changing text/options. It must be a global function like this because - otherwise we could not make the templated-menu persistent. - - """ - text, options = caller.db._evmenu_template_contents[kwargs["_current_nodename"]] - return text, options - - -
[docs]def parse_menu_template(caller, menu_template, goto_callables=None): - """ - Parse menu-template string. The main function of the EvMenu templating system. - - Args: - caller (Object or Account): Entity using the menu. - menu_template (str): Menu described using the templating format. - goto_callables (dict, optional): Mapping between call-names and callables - on the form `callable(caller, raw_string, **kwargs)`. These are what is - available to use in the `menu_template` string. - - Returns: - dict: A `{"node": nodefunc}` menutree suitable to pass into EvMenu. - - """ - - def _validate_kwarg(goto, kwarg): - """ - Validate goto-callable kwarg is on correct form. - """ - if not "=" in kwarg: - raise RuntimeError( - f"EvMenu template error: goto-callable '{goto}' has a " - f"non-kwarg argument ({kwarg}). All callables in the " - "template must have only keyword-arguments, or no " - "args at all." - ) - key, _ = [part.strip() for part in kwarg.split("=", 1)] - if key in ( - "evmenu_goto", - "evmenu_gotomap", - "_current_nodename", - "evmenu_current_nodename", - "evmenu_goto_callables", - ): - raise RuntimeError( - f"EvMenu template error: goto-callable '{goto}' uses a " - f"kwarg ({kwarg}) that is reserved for the EvMenu templating " - "system. Rename the kwarg." - ) - - def _parse_options(nodename, optiontxt, goto_callables): - """ - Parse option section into option dict. - """ - options = [] - optiontxt = optiontxt[0].strip() if optiontxt else "" - optionlist = [optline.strip() for optline in optiontxt.split("\n")] - inputparsemap = {} - - for inum, optline in enumerate(optionlist): - if optline.startswith(_OPTION_COMMENT_START) or _OPTION_SEP_MARKER not in optline: - # skip comments or invalid syntax - continue - key = "" - desc = "" - pattern = None - - key, goto = [part.strip() for part in optline.split(_OPTION_SEP_MARKER, 1)] - - # desc -> goto - if _OPTION_CALL_MARKER in goto: - desc, goto = [part.strip() for part in goto.split(_OPTION_CALL_MARKER, 1)] - - # validate callable - match = _RE_CALLABLE.match(goto) - if match: - kwargs = match.group("kwargs") - if kwargs: - for kwarg in kwargs.split(","): - _validate_kwarg(goto, kwarg) - - # parse key [;aliases|pattern] - key = [part.strip() for part in key.split(_OPTION_ALIAS_MARKER)] - if not key: - # fall back to this being the Nth option - key = [f"{inum + 1}"] - main_key = key[0] - - if main_key.startswith(_OPTION_INPUT_MARKER): - # if we have a pattern, build the arguments for _default later - pattern = main_key[len(_OPTION_INPUT_MARKER) :].strip() - inputparsemap[pattern] = goto - else: - # a regular goto string/callable target - option = { - "key": key, - "goto": ( - _generated_goto_func, - { - "evmenu_goto": goto, - "evmenu_current_nodename": nodename, - "evmenu_goto_callables": goto_callables, - }, - ), - } - if desc: - option["desc"] = desc - options.append(option) - - if inputparsemap: - # if this exists we must create a _default entry too - options.append( - { - "key": "_default", - "goto": ( - _generated_input_goto_func, - { - "evmenu_gotomap": inputparsemap, - "evmenu_current_nodename": nodename, - "evmenu_goto_callables": goto_callables, - }, - ), - } - ) - - return options - - def _parse(caller, menu_template, goto_callables): - """ - Parse the menu string format into a node tree. - """ - nodetree = {} - splits = _RE_NODE.split(menu_template) - splits = splits[1:] if splits else [] - - # from evennia import set_trace;set_trace(term_size=(140,120)) - content_map = {} - for node_ind in range(0, len(splits), 2): - nodename, nodetxt = splits[node_ind], splits[node_ind + 1] - text, *optiontxt = _RE_OPTIONS_SEP.split(nodetxt, maxsplit=2) - options = _parse_options(nodename, optiontxt, goto_callables) - content_map[nodename] = (text, options) - nodetree[nodename] = _generated_node - caller.db._evmenu_template_contents = content_map - - return nodetree - - return _parse(caller, menu_template, goto_callables)
- - -
[docs]def template2menu( - caller, menu_template, goto_callables=None, startnode="start", persistent=False, **kwargs, -): - """ - Helper function to generate and start an EvMenu based on a menu template - string. This will internall call `parse_menu_template` and run a default - EvMenu with its results. - - Args: - caller (Object or Account): The entity using the menu. - menu_template (str): The menu-template string describing the content - and structure of the menu. It can also be the python-path to, or a module - containing a `MENU_TEMPLATE` global variable with the template. - goto_callables (dict, optional): Mapping of callable-names to - module-global objects to reference by name in the menu-template. - Must be on the form `callable(caller, raw_string, **kwargs)`. - startnode (str, optional): The name of the startnode, if not 'start'. - persistent (bool, optional): If the generated menu should be persistent. - **kwargs: All kwargs will be passed into EvMenu. - - Returns: - EvMenu: The generated EvMenu. - - """ - goto_callables = goto_callables or {} - menu_tree = parse_menu_template(caller, menu_template, goto_callables) - return EvMenu(caller, menu_tree, persistent=persistent, **kwargs,)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/evmore.html b/docs/0.9.5/_modules/evennia/utils/evmore.html deleted file mode 100644 index 916d7e9a06..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/evmore.html +++ /dev/null @@ -1,702 +0,0 @@ - - - - - - - - evennia.utils.evmore — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.evmore

-# -*- coding: utf-8 -*-
-"""
-EvMore - pager mechanism
-
-This is a pager for displaying long texts and allows stepping up and
-down in the text (the name comes from the traditional 'more' unix
-command).
-
-To use, simply pass the text through the EvMore object:
-::
-
-    from evennia.utils.evmore import EvMore
-
-    text = some_long_text_output()
-    EvMore(caller, text, always_page=False, session=None, justify_kwargs=None, **kwargs)
-
-One can also use the convenience function msg from this module:
-::
-
-    from evennia.utils import evmore
-
-    text = some_long_text_output()
-    evmore.msg(caller, text, always_page=False, session=None, justify_kwargs=None, **kwargs)
-
-Where always_page decides if the pager is used also if the text is not long
-enough to need to scroll, session is used to determine which session to relay
-to and `justify_kwargs` are kwargs to pass to `utils.utils.justify` in order to
-change the formatting of the text. The remaining `**kwargs` will be passed on to
-the `caller.msg()` construct every time the page is updated.
-
-----
-
-"""
-from django.conf import settings
-from django.db.models.query import QuerySet
-from django.core.paginator import Paginator
-from evennia import Command, CmdSet
-from evennia.commands import cmdhandler
-from evennia.utils.utils import make_iter, inherits_from, justify
-
-_CMD_NOMATCH = cmdhandler.CMD_NOMATCH
-_CMD_NOINPUT = cmdhandler.CMD_NOINPUT
-
-# we need to use NAWS for this
-_SCREEN_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-_SCREEN_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
-
-_EVTABLE = None
-
-# text
-
-_DISPLAY = """{text}
-(|wmore|n [{pageno}/{pagemax}] retur|wn|n|||wb|nack|||wt|nop|||we|nnd|||wq|nuit)"""
-
-
-
[docs]class CmdMore(Command): - """ - Manipulate the text paging - """ - - key = _CMD_NOINPUT - aliases = ["quit", "q", "abort", "a", "next", "n", "back", "b", "top", "t", "end", "e"] - auto_help = False - -
[docs] def func(self): - """ - Implement the command - """ - more = self.caller.ndb._more - if not more and hasattr(self.caller, "account"): - more = self.caller.account.ndb._more - if not more: - self.caller.msg("Error in loading the pager. Contact an admin.") - return - - cmd = self.cmdstring - - if cmd in ("abort", "a", "q"): - more.page_quit() - elif cmd in ("back", "b"): - more.page_back() - elif cmd in ("top", "t", "look", "l"): - more.page_top() - elif cmd in ("end", "e"): - more.page_end() - else: - # return or n, next - more.page_next()
- - -
[docs]class CmdMoreLook(Command): - """ - Override look to display window and prevent OOCLook from firing - """ - - key = "look" - aliases = ["l"] - auto_help = False - -
[docs] def func(self): - """ - Implement the command - """ - more = self.caller.ndb._more - if not more and hasattr(self.caller, "account"): - more = self.caller.account.ndb._more - if not more: - self.caller.msg("Error in loading the pager. Contact an admin.") - return - more.display()
- - -
[docs]class CmdSetMore(CmdSet): - """ - Stores the more command - """ - - key = "more_commands" - priority = 110 - -
[docs] def at_cmdset_creation(self): - self.add(CmdMore()) - self.add(CmdMoreLook())
- - -# resources for handling queryset inputs -
[docs]def queryset_maxsize(qs): - return qs.count()
- - -
[docs]class EvMore: - """ - The main pager object. - - """ - -
[docs] def __init__( - self, - caller, - inp, - always_page=False, - session=None, - justify=False, - justify_kwargs=None, - exit_on_lastpage=False, - exit_cmd=None, - page_formatter=str, - **kwargs, - ): - - """ - Initialization of the Evmore input handler. - - Args: - caller (Object or Account): Entity reading the text. - inp (str, EvTable, Paginator or iterator): The text or data to put under paging. - - - If a string, paginage normally. If this text contains - one or more \\\\f (backslash + f) format symbols, automatic - pagination and justification are force-disabled and - page-breaks will only happen after each \\\\f. - - If `EvTable`, the EvTable will be paginated with the same - setting on each page if it is too long. The table - decorations will be considered in the size of the page. - - Otherwise `inp` is converted to an iterator, where each step is - expected to be a line in the final display. Each line - will be run through `iter_callable`. - - always_page (bool, optional): If `False`, the pager will only kick - in if `inp` is too big to fit the screen. - session (Session, optional): If given, this session will be used - to determine the screen width and will receive all output. - justify (bool, optional): If set, auto-justify long lines. This must be turned - off for fixed-width or formatted output, like tables. It's force-disabled - if `inp` is an EvTable. - justify_kwargs (dict, optional): Keywords for the justifiy function. Used only - if `justify` is True. If this is not set, default arguments will be used. - exit_on_lastpage (bool, optional): If reaching the last page without the - page being completely filled, exit pager immediately. If unset, - another move forward is required to exit. If set, the pager - exit message will not be shown. - exit_cmd (str, optional): If given, this command-string will be executed on - the caller when the more page exits. Note that this will be using whatever - cmdset the user had *before* the evmore pager was activated (so none of - the evmore commands will be available when this is run). - kwargs (any, any): These will be passed on to the `caller.msg` method. - - Examples: - Basic use: - :: - - super_long_text = " ... " - EvMore(caller, super_long_text) - - Paginated query data - this is an optimization to avoid fetching - database data until it's actually paged to. - :: - - from django.core.paginator import Paginator - - query = ObjectDB.objects.all() - pages = Paginator(query, 10) # 10 objs per page - EvMore(caller, pages) - - Automatic split EvTable over multiple EvMore pages - :: - - table = EvMore(*header, table=tabledata) - EvMore(caller, table) - - Every page a separate EvTable (optimization for very large data sets) - :: - - from evennia import EvTable, EvMore - - class TableEvMore(EvMore): - def init_pages(self, data): - pages = # depends on data type - super().init_pages(pages) - - def page_formatter(self, page): - table = EvTable() - - for line in page: - cols = # split raw line into columns - table.add_row(*cols) - - return str(table) - - TableEvMore(caller, pages) - - """ - self._caller = caller - self._always_page = always_page - - if not session: - # if not supplied, use the first session to - # determine screen size - sessions = caller.sessions.get() - if not sessions: - return - session = sessions[0] - self._session = session - - self._justify = justify - self._justify_kwargs = justify_kwargs - self.exit_on_lastpage = exit_on_lastpage - self.exit_cmd = exit_cmd - self._exit_msg = "Exited |wmore|n pager." - self._kwargs = kwargs - - self._data = None - - self._pages = [] - self._npos = 0 - - self._npages = 1 - self._paginator = self.paginator_index - self._page_formatter = str - - # set up individual pages for different sessions - height = max(4, session.protocol_flags.get("SCREENHEIGHT", {0: _SCREEN_HEIGHT})[0] - 4) - self.width = session.protocol_flags.get("SCREENWIDTH", {0: _SCREEN_WIDTH})[0] - # always limit number of chars to 10 000 per page - self.height = min(10000 // max(1, self.width), height) - - # does initial parsing of input - self.init_pages(inp) - - # kick things into gear - self.start()
- - # EvMore functional methods - -
[docs] def display(self, show_footer=True): - """ - Pretty-print the page. - """ - pos = 0 - text = "[no content]" - if self._npages > 0: - pos = self._npos - text = self.page_formatter(self.paginator(pos)) - if show_footer: - page = _DISPLAY.format(text=text, pageno=pos + 1, pagemax=self._npages) - else: - page = text - # check to make sure our session is still valid - sessions = self._caller.sessions.get() - if not sessions: - self.page_quit() - return - # this must be an 'is', not == check - if not any(ses for ses in sessions if self._session is ses): - self._session = sessions[0] - self._caller.msg(text=page, session=self._session, **self._kwargs)
- -
[docs] def page_top(self): - """ - Display the top page - """ - self._npos = 0 - self.display()
- -
[docs] def page_end(self): - """ - Display the bottom page. - """ - self._npos = self._npages - 1 - self.display()
- -
[docs] def page_next(self): - """ - Scroll the text to the next page. Quit if already at the end - of the page. - """ - if self._npos >= self._npages - 1: - # exit if we are already at the end - self.page_quit() - else: - self._npos += 1 - if self.exit_on_lastpage and self._npos >= (self._npages - 1): - self.display(show_footer=False) - self.page_quit(quiet=True) - else: - self.display()
- -
[docs] def page_back(self): - """ - Scroll the text back up, at the most to the top. - """ - self._npos = max(0, self._npos - 1) - self.display()
- -
[docs] def page_quit(self, quiet=False): - """ - Quit the pager - """ - del self._caller.ndb._more - if not quiet: - self._caller.msg(text=self._exit_msg, **self._kwargs) - self._caller.cmdset.remove(CmdSetMore) - if self.exit_cmd: - self._caller.execute_cmd(self.exit_cmd, session=self._session)
- -
[docs] def start(self): - """ - Starts the pagination - """ - if self._npages <= 1 and not self._always_page: - # no need for paging; just pass-through. - self.display(show_footer=False) - else: - # go into paging mode - # first pass on the msg kwargs - self._caller.ndb._more = self - self._caller.cmdset.add(CmdSetMore) - - # goto top of the text - self.page_top()
- - # default paginators - responsible for extracting a specific page number - -
[docs] def paginator_index(self, pageno): - """Paginate to specific, known index""" - return self._data[pageno]
- -
[docs] def paginator_slice(self, pageno): - """ - Paginate by slice. This is done with an eye on memory efficiency (usually for - querysets); to avoid fetching all objects at the same time. - """ - return self._data[pageno * self.height : pageno * self.height + self.height]
- -
[docs] def paginator_django(self, pageno): - """ - Paginate using the django queryset Paginator API. Note that his is indexed from 1. - """ - return self._data.page(pageno + 1)
- - # default helpers to set up particular input types - -
[docs] def init_evtable(self, table): - """The input is an EvTable.""" - if table.height: - # enforced height of each paged table, plus space for evmore extras - self.height = table.height - 4 - - # convert table to string - text = str(table) - self._justify = False - self._justify_kwargs = None # enforce - self.init_str(text)
- -
[docs] def init_queryset(self, qs): - """The input is a queryset""" - nsize = qs.count() # we assume each will be a line - self._npages = nsize // self.height + (0 if nsize % self.height == 0 else 1) - self._data = qs
- -
[docs] def init_django_paginator(self, pages): - """ - The input is a django Paginator object. - """ - self._npages = pages.num_pages - self._data = pages
- -
[docs] def init_iterable(self, inp): - """The input is something other than a string - convert to iterable of strings""" - inp = make_iter(inp) - nsize = len(inp) - self._npages = nsize // self.height + (0 if nsize % self.height == 0 else 1) - self._data = inp
- -
[docs] def init_f_str(self, text): - """ - The input contains \\\\f (backslash + f) markers. We use \\\\f to indicate - the user wants to enforce their line breaks on their own. If so, we do - no automatic line-breaking/justification at all. - - """ - self._data = text.split("\f") - self._npages = len(self._data)
- -
[docs] def init_str(self, text): - """The input is a string""" - - if self._justify: - # we must break very long lines into multiple ones. Note that this - # will also remove spurious whitespace. - justify_kwargs = self._justify_kwargs or {} - width = self._justify_kwargs.get("width", self.width) - justify_kwargs["width"] = width - justify_kwargs["align"] = self._justify_kwargs.get("align", "l") - justify_kwargs["indent"] = self._justify_kwargs.get("indent", 0) - - lines = [] - for line in text.split("\n"): - if len(line) > width: - lines.extend(justify(line, **justify_kwargs).split("\n")) - else: - lines.append(line) - else: - # no justification. Simple division by line - lines = text.split("\n") - - self._data = [ - "\n".join(lines[i : i + self.height]) for i in range(0, len(lines), self.height) - ] - self._npages = len(self._data)
- - # Hooks for customizing input handling and formatting (override in a child class) - -
[docs] def init_pages(self, inp): - """ - Initialize the pagination. By default, will analyze input type to determine - how pagination automatically. - - Args: - inp (any): Incoming data to be paginated. By default, handles pagination of - strings, querysets, django.Paginator, EvTables and any iterables with strings. - - Notes: - If overridden, this method must perform the following actions: - - - read and re-store `self._data` (the incoming data set) if needed - for pagination to work. - - set `self._npages` to the total number of pages. Default is 1. - - set `self._paginator` to a callable that will take a page number 1...N and return - the data to display on that page (not any decorations or next/prev buttons). If only - wanting to change the paginator, override `self.paginator` instead. - - set `self._page_formatter` to a callable that will receive the - page from `self._paginator` and format it with one element per - line. Default is `str`. Or override `self.page_formatter` - directly instead. - - By default, helper methods are called that perform these actions - depending on supported inputs. - - """ - if inherits_from(inp, "evennia.utils.evtable.EvTable"): - # an EvTable - self.init_evtable(inp) - self._paginator = self.paginator_index - elif isinstance(inp, QuerySet): - # a queryset - self.init_queryset(inp) - self._paginator = self.paginator_slice - elif isinstance(inp, Paginator): - self.init_django_paginator(inp) - self._paginator = self.paginator_django - elif not isinstance(inp, str): - # anything else not a str - self.init_iterable(inp) - self._paginator = self.paginator_slice - elif "\f" in inp: - # string with \f line-break markers in it - self.init_f_str(inp) - self._paginator = self.paginator_index - else: - # a string - self.init_str(inp) - self._paginator = self.paginator_index
- -
[docs] def paginator(self, pageno): - """ - Paginator. The data operated upon is in `self._data`. - - Args: - pageno (int): The page number to view, from 0...N-1 - Returns: - str: The page to display (without any decorations, those are added - by EvMore). - - """ - return self._paginator(pageno)
- -
[docs] def page_formatter(self, page): - """ - Page formatter. Every page passes through this method. Override - it to customize behvaior per-page. A common use is to generate a new - EvTable for every page (this is more efficient than to generate one huge - EvTable across many pages and feed it into EvMore all at once). - - Args: - page (any): A piece of data representing one page to display. This must - - Returns: - str: A ready-formatted page to display. Extra footer with help about - switching to the next/prev page will be added automatically - - """ - return self._page_formatter(page)
- - -# helper function - - -
[docs]def msg( - caller, - text="", - always_page=False, - session=None, - justify=False, - justify_kwargs=None, - exit_on_lastpage=True, - **kwargs, -): - """ - EvMore-supported version of msg, mimicking the normal msg method. - - Args: - caller (Object or Account): Entity reading the text. - text (str, EvTable or iterator): The text or data to put under paging. - - - If a string, paginage normally. If this text contains - one or more \\\\f (backslash + f) format symbol, automatic pagination is disabled - and page-breaks will only happen after each \\\\f. - - If `EvTable`, the EvTable will be paginated with the same - setting on each page if it is too long. The table - decorations will be considered in the size of the page. - - Otherwise `text` is converted to an iterator, where each step is - is expected to be a line in the final display, and each line - will be run through repr(). - - always_page (bool, optional): If `False`, the - pager will only kick in if `text` is too big - to fit the screen. - session (Session, optional): If given, this session will be used - to determine the screen width and will receive all output. - justify (bool, optional): If set, justify long lines in output. Disable for - fixed-format output, like tables. - justify_kwargs (dict, bool or None, optional): If given, this should - be valid keyword arguments to the utils.justify() function. If False, - no justification will be done. - exit_on_lastpage (bool, optional): Immediately exit pager when reaching the last page. - use_evtable (bool, optional): If True, each page will be rendered as an - EvTable. For this to work, `text` must be an iterable, where each element - is the table (list of list) to render on that page. - evtable_args (tuple, optional): The args to use for EvTable on each page. - evtable_kwargs (dict, optional): The kwargs to use for EvTable on each - page (except `table`, which is supplied by EvMore per-page). - kwargs (any, optional): These will be passed on - to the `caller.msg` method. - - """ - EvMore( - caller, - text, - always_page=always_page, - session=session, - justify=justify, - justify_kwargs=justify_kwargs, - exit_on_lastpage=exit_on_lastpage, - **kwargs, - )
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/evtable.html b/docs/0.9.5/_modules/evennia/utils/evtable.html deleted file mode 100644 index 14f68c6882..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/evtable.html +++ /dev/null @@ -1,1860 +0,0 @@ - - - - - - - - evennia.utils.evtable — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.evtable

-"""
-This is an advanced ASCII table creator. It was inspired by
-[prettytable](https://code.google.com/p/prettytable/) but shares no code.
-
-Example usage:
-::
-
-    from evennia.utils import evtable
-
-    table = evtable.EvTable("Heading1", "Heading2",
-                  table=[[1,2,3],[4,5,6],[7,8,9]], border="cells")
-    table.add_column("This is long data", "This is even longer data")
-    table.add_row("This is a single row")
-    print table
-
-Result:
-::
-
-    +----------------------+----------+---+--------------------------+
-    |       Heading1       | Heading2 |   |                          |
-    +~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~+~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~+
-    |           1          |     4    | 7 |     This is long data    |
-    +----------------------+----------+---+--------------------------+
-    |           2          |     5    | 8 | This is even longer data |
-    +----------------------+----------+---+--------------------------+
-    |           3          |     6    | 9 |                          |
-    +----------------------+----------+---+--------------------------+
-    | This is a single row |          |   |                          |
-    +----------------------+----------+---+--------------------------+
-
-As seen, the table will automatically expand with empty cells to make
-the table symmetric. Tables can be restricted to a given width:
-::
-
-    table.reformat(width=50, align="l")
-
-(We could just have added these keywords to the table creation call)
-
-This yields the following result:
-::
-
-    +-----------+------------+-----------+-----------+
-    | Heading1  | Heading2   |           |           |
-    +~~~~~~~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~+
-    | 1         | 4          | 7         | This is   |
-    |           |            |           | long data |
-    +-----------+------------+-----------+-----------+
-    |           |            |           | This is   |
-    | 2         | 5          | 8         | even      |
-    |           |            |           | longer    |
-    |           |            |           | data      |
-    +-----------+------------+-----------+-----------+
-    | 3         | 6          | 9         |           |
-    +-----------+------------+-----------+-----------+
-    | This is a |            |           |           |
-    |  single   |            |           |           |
-    | row       |            |           |           |
-    +-----------+------------+-----------+-----------+
-
-Table-columns can be individually formatted. Note that if an
-individual column is set with a specific width, table auto-balancing
-will not affect this column (this may lead to the full table being too
-wide, so be careful mixing fixed-width columns with auto- balancing).
-Here we change the width and alignment of the column at index 3
-(Python starts from 0):
-::
-
-    table.reformat_column(3, width=30, align="r")
-    print table
-
-    +-----------+-------+-----+-----------------------------+---------+
-    | Heading1  | Headi |     |                             |         |
-    |           | ng2   |     |                             |         |
-    +~~~~~~~~~~~+~~~~~~~+~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~+
-    | 1         | 4     | 7   |           This is long data | Test1   |
-    +-----------+-------+-----+-----------------------------+---------+
-    | 2         | 5     | 8   |    This is even longer data | Test3   |
-    +-----------+-------+-----+-----------------------------+---------+
-    | 3         | 6     | 9   |                             | Test4   |
-    +-----------+-------+-----+-----------------------------+---------+
-    | This is a |       |     |                             |         |
-    |  single   |       |     |                             |         |
-    | row       |       |     |                             |         |
-    +-----------+-------+-----+-----------------------------+---------+
-
-When adding new rows/columns their data can have its own alignments
-(left/center/right, top/center/bottom).
-
-If the height is restricted, cells will be restricted from expanding
-vertically. This will lead to text contents being cropped. Each cell
-can only shrink to a minimum width and height of 1.
-
-`EvTable` is intended to be used with [ANSIString](evennia.utils.ansi#ansistring)
-for supporting ANSI-coloured string types.
-
-When a cell is auto-wrapped across multiple lines, ANSI-reset
-sequences will be put at the end of each wrapped line. This means that
-the colour of a wrapped cell will not "bleed", but it also means that
-eventual colour outside the table will not transfer "across" a table,
-you need to re-set the color to have it appear on both sides of the
-table string.
-
-----
-
-"""
-
-from django.conf import settings
-from textwrap import TextWrapper
-from copy import deepcopy, copy
-from evennia.utils.utils import is_iter, display_len as d_len
-from evennia.utils.ansi import ANSIString
-
-_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
-
-
-def _to_ansi(obj):
-    """
-    convert to ANSIString.
-
-    Args:
-        obj (str): Convert incoming text to
-            be ANSI aware ANSIStrings.
-    """
-    if is_iter(obj):
-        return [_to_ansi(o) for o in obj]
-    else:
-        return ANSIString(obj)
-
-
-_whitespace = "\t\n\x0b\x0c\r "
-
-
-
[docs]class ANSITextWrapper(TextWrapper): - """ - This is a wrapper work class for handling strings with ANSI tags - in it. It overloads the standard library `TextWrapper` class and - is used internally in `EvTable` and has no public methods. - - """ - - def _munge_whitespace(self, text): - """_munge_whitespace(text : string) -> string - - Munge whitespace in text: expand tabs and convert all other - whitespace characters to spaces. Eg. " foo\tbar\n\nbaz" - becomes " foo bar baz". - """ - return text - - # TODO: Ignore expand_tabs/replace_whitespace until ANSIString handles them. - # - don't remove this code. /Griatch - # if self.expand_tabs: - # text = text.expandtabs() - # if self.replace_whitespace: - # if isinstance(text, str): - # text = text.translate(self.whitespace_trans) - # return text - - def _split(self, text): - """_split(text : string) -> [string] - - Split the text to wrap into indivisible chunks. Chunks are - not quite the same as words; see _wrap_chunks() for full - details. As an example, the text - Look, goof-ball -- use the -b option! - breaks into the following chunks: - 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', - 'use', ' ', 'the', ' ', '-b', ' ', 'option!' - if break_on_hyphens is True, or in: - 'Look,', ' ', 'goof-ball', ' ', '--', ' ', - 'use', ' ', 'the', ' ', '-b', ' ', option!' - otherwise. - """ - # NOTE-PYTHON3: The following code only roughly approximates what this - # function used to do. Regex splitting on ANSIStrings is - # dropping ANSI codes, so we're using ANSIString.split - # for the time being. - # - # A less hackier solution would be appreciated. - chunks = _to_ansi(text).split() - - chunks = [chunk + " " for chunk in chunks if chunk] # remove empty chunks - - if len(chunks) > 1: - chunks[-1] = chunks[-1][0:-1] - - return chunks - - def _wrap_chunks(self, chunks): - """_wrap_chunks(chunks : [string]) -> [string] - - Wrap a sequence of text chunks and return a list of lines of - length 'self.width' or less. (If 'break_long_words' is false, - some lines may be longer than this.) Chunks correspond roughly - to words and the whitespace between them: each chunk is - indivisible (modulo 'break_long_words'), but a line break can - come between any two chunks. Chunks should not have internal - whitespace; ie. a chunk is either all whitespace or a "word". - Whitespace chunks will be removed from the beginning and end of - lines, but apart from that whitespace is preserved. - """ - lines = [] - if self.width <= 0: - raise ValueError("invalid width %r (must be > 0)" % self.width) - - # Arrange in reverse order so items can be efficiently popped - # from a stack of chucks. - chunks.reverse() - - while chunks: - - # Start the list of chunks that will make up the current line. - # cur_len is just the length of all the chunks in cur_line. - cur_line = [] - cur_len = 0 - - # Figure out which static string will prefix this line. - if lines: - indent = self.subsequent_indent - else: - indent = self.initial_indent - - # Maximum width for this line. - width = self.width - d_len(indent) - - # First chunk on line is whitespace -- drop it, unless this - # is the very beginning of the text (ie. no lines started yet). - if self.drop_whitespace and chunks[-1].strip() == "" and lines: - del chunks[-1] - - while chunks: - l = d_len(chunks[-1]) - - # Can at least squeeze this chunk onto the current line. - if cur_len + l <= width: - cur_line.append(chunks.pop()) - cur_len += l - - # Nope, this line is full. - else: - break - - # The current line is full, and the next chunk is too big to - # fit on *any* line (not just this one). - if chunks and d_len(chunks[-1]) > width: - self._handle_long_word(chunks, cur_line, cur_len, width) - - # If the last chunk on this line is all whitespace, drop it. - if self.drop_whitespace and cur_line and cur_line[-1].strip() == "": - del cur_line[-1] - - # Convert current line back to a string and store it in list - # of all lines (return value). - if cur_line: - l = "" - for w in cur_line: # ANSI fix - l += w # - lines.append(indent + l) - return lines
- - -# -- Convenience interface --------------------------------------------- - - -
[docs]def wrap(text, width=_DEFAULT_WIDTH, **kwargs): - """ - Wrap a single paragraph of text, returning a list of wrapped lines. - - Reformat the single paragraph in 'text' so it fits in lines of no - more than 'width' columns, and return a list of wrapped lines. By - default, tabs in 'text' are expanded with string.expandtabs(), and - all other whitespace characters (including newline) are converted to - - Args: - text (str): Text to wrap. - width (int, optional): Width to wrap `text` to. - - Keyword Args: - See TextWrapper class for available keyword args to customize - wrapping behaviour. - - """ - w = ANSITextWrapper(width=width, **kwargs) - return w.wrap(text)
- - -
[docs]def fill(text, width=_DEFAULT_WIDTH, **kwargs): - """Fill a single paragraph of text, returning a new string. - - Reformat the single paragraph in 'text' to fit in lines of no more - than 'width' columns, and return a new string containing the entire - wrapped paragraph. As with wrap(), tabs are expanded and other - whitespace characters converted to space. - - Args: - text (str): Text to fill. - width (int, optional): Width of fill area. - - Keyword Args: - See TextWrapper class for available keyword args to customize - filling behaviour. - - """ - w = ANSITextWrapper(width=width, **kwargs) - return w.fill(text)
- - -# EvCell class (see further down for the EvTable itself) - - -
[docs]class EvCell: - """ - Holds a single data cell for the table. A cell has a certain width - and height and contains one or more lines of data. It can shrink - and resize as needed. - - """ - -
[docs] def __init__(self, data, **kwargs): - """ - Args: - data (str): The un-padded data of the entry. - - Keyword Args: - width (int): Desired width of cell. It will pad - to this size. - height (int): Desired height of cell. it will pad - to this size. - pad_width (int): General padding width. This can be overruled - by individual settings below. - pad_left (int): Number of extra pad characters on the left. - pad_right (int): Number of extra pad characters on the right. - pad_top (int): Number of extra pad lines top (will pad with `vpad_char`). - pad_bottom (int): Number of extra pad lines bottom (will pad with `vpad_char`). - pad_char (str)- pad character to use for padding. This is overruled - by individual settings below (default `" "`). - hpad_char (str): Pad character to use both for extra horizontal - padding (default `" "`). - vpad_char (str): Pad character to use for extra vertical padding - and for vertical fill (default `" "`). - fill_char (str): Character used to filling (expanding cells to - desired size). This can be overruled by individual settings below. - hfill_char (str): Character used for horizontal fill (default `" "`). - vfill_char (str): Character used for vertical fill (default `" "`). - align (str): Should be one of "l", "r" or "c" for left-, right- or center - horizontal alignment respectively. Default is left-aligned. - valign (str): Should be one of "t", "b" or "c" for top-, bottom and center - vertical alignment respectively. Default is centered. - border_width (int): General border width. This is overruled - by individual settings below. - border_left (int): Left border width. - border_right (int): Right border width. - border_top (int): Top border width. - border_bottom (int): Bottom border width. - border_char (str): This will use a single border char for all borders. - overruled by individual settings below. - border_left_char (str): Char used for left border. - border_right_char (str): Char used for right border. - border_top_char (str): Char used for top border. - border_bottom_char (str): Char user for bottom border. - corner_char (str): Character used when two borders cross. (default is ""). - This is overruled by individual settings below. - corner_top_left_char (str): Char used for "nw" corner. - corner_top_right_char (str): Char used for "ne" corner. - corner_bottom_left_char (str): Char used for "sw" corner. - corner_bottom_right_char (str): Char used for "se" corner. - crop_string (str): String to use when cropping sideways, default is `'[...]'`. - crop (bool): Crop contentof cell rather than expand vertically, default=`False`. - enforce_size (bool): If true, the width/height of the cell is - strictly enforced and extra text will be cropped rather than the - cell growing vertically. - - Raises: - Exception: for impossible cell size requirements where the - border width or height cannot fit, or the content is too - small. - - """ - - self.formatted = None - padwidth = kwargs.get("pad_width", None) - padwidth = int(padwidth) if padwidth is not None else None - self.pad_left = int(kwargs.get("pad_left", padwidth if padwidth is not None else 1)) - self.pad_right = int(kwargs.get("pad_right", padwidth if padwidth is not None else 1)) - self.pad_top = int(kwargs.get("pad_top", padwidth if padwidth is not None else 0)) - self.pad_bottom = int(kwargs.get("pad_bottom", padwidth if padwidth is not None else 0)) - - self.enforce_size = kwargs.get("enforce_size", False) - - # avoid multi-char pad_chars messing up counting - pad_char = kwargs.get("pad_char", " ") - pad_char = pad_char[0] if pad_char else " " - hpad_char = kwargs.get("hpad_char", pad_char) - self.hpad_char = hpad_char[0] if hpad_char else pad_char - vpad_char = kwargs.get("vpad_char", pad_char) - self.vpad_char = vpad_char[0] if vpad_char else pad_char - - fill_char = kwargs.get("fill_char", " ") - fill_char = fill_char[0] if fill_char else " " - hfill_char = kwargs.get("hfill_char", fill_char) - self.hfill_char = hfill_char[0] if hfill_char else " " - vfill_char = kwargs.get("vfill_char", fill_char) - self.vfill_char = vfill_char[0] if vfill_char else " " - - self.crop_string = kwargs.get("crop_string", "[...]") - - # borders and corners - borderwidth = kwargs.get("border_width", 0) - self.border_left = kwargs.get("border_left", borderwidth) - self.border_right = kwargs.get("border_right", borderwidth) - self.border_top = kwargs.get("border_top", borderwidth) - self.border_bottom = kwargs.get("border_bottom", borderwidth) - - borderchar = kwargs.get("border_char", None) - self.border_left_char = kwargs.get("border_left_char", borderchar if borderchar else "|") - self.border_right_char = kwargs.get( - "border_right_char", borderchar if borderchar else self.border_left_char - ) - self.border_top_char = kwargs.get("border_top_char", borderchar if borderchar else "-") - self.border_bottom_char = kwargs.get( - "border_bottom_char", borderchar if borderchar else self.border_top_char - ) - - corner_char = kwargs.get("corner_char", "+") - self.corner_top_left_char = kwargs.get("corner_top_left_char", corner_char) - self.corner_top_right_char = kwargs.get("corner_top_right_char", corner_char) - self.corner_bottom_left_char = kwargs.get("corner_bottom_left_char", corner_char) - self.corner_bottom_right_char = kwargs.get("corner_bottom_right_char", corner_char) - - # alignments - self.align = kwargs.get("align", "l") - self.valign = kwargs.get("valign", "c") - - self.data = self._split_lines(_to_ansi(data)) - self.raw_width = max(d_len(line) for line in self.data) - self.raw_height = len(self.data) - - # this is extra trimming required for cels in the middle of a table only - self.trim_horizontal = 0 - self.trim_vertical = 0 - - # width/height is given without left/right or top/bottom padding - if "width" in kwargs: - width = kwargs.pop("width") - self.width = ( - width - self.pad_left - self.pad_right - self.border_left - self.border_right - ) - if self.width <= 0 < self.raw_width: - raise Exception("Cell width too small - no space for data.") - else: - self.width = self.raw_width - if "height" in kwargs: - height = kwargs.pop("height") - self.height = ( - height - self.pad_top - self.pad_bottom - self.border_top - self.border_bottom - ) - if self.height <= 0 < self.raw_height: - raise Exception("Cell height too small - no space for data.") - else: - self.height = self.raw_height
- - # prepare data - # self.formatted = self._reformat() - - def _crop(self, text, width): - """ - Apply cropping of text. - - Args: - text (str): The text to crop. - width (int): The width to crop `text` to. - - """ - if d_len(text) > width: - crop_string = self.crop_string - return text[: width - d_len(crop_string)] + crop_string - return text - - def _reformat(self): - """ - Apply all EvCells' formatting operations. - - """ - data = self._border(self._pad(self._valign(self._align(self._fit_width(self.data))))) - return data - - def _split_lines(self, text): - """ - Simply split by linebreaks - - Args: - text (str): text to split. - - Returns: - split (list): split text. - """ - return text.split("\n") - - def _fit_width(self, data): - """ - Split too-long lines to fit the desired width of the Cell. - - Args: - data (str): Text to adjust to the cell's width. - - Returns: - adjusted data (str): The adjusted text. - - Notes: - This also updates `raw_width`. - - - """ - width = self.width - adjusted_data = [] - for line in data: - if 0 < width < d_len(line): - # replace_whitespace=False, expand_tabs=False is a - # fix for ANSIString not supporting expand_tabs/translate - adjusted_data.extend( - [ - ANSIString(part + ANSIString("|n")) - for part in wrap(line, width=width, drop_whitespace=False) - ] - ) - else: - adjusted_data.append(line) - if self.enforce_size: - # don't allow too high cells - excess = len(adjusted_data) - self.height - if excess > 0: - # too many lines. Crop and mark last line with crop_string - crop_string = self.crop_string - adjusted_data = adjusted_data[:-excess] - crop_string_length = len(crop_string) - if len(adjusted_data[-1]) > crop_string_length: - adjusted_data[-1] = adjusted_data[-1][:-crop_string_length] + crop_string - else: - adjusted_data[-1] += crop_string - elif excess < 0: - # too few lines. Fill to height. - adjusted_data.extend(["" for _ in range(excess)]) - - return adjusted_data - - def _center(self, text, width, pad_char): - """ - Horizontally center text on line of certain width, using padding. - - Args: - text (str): The text to center. - width (int): How wide the area is (in characters) where `text` - should be centered. - pad_char (str): Which padding character to use. - - Returns: - text (str): Centered text. - - """ - excess = width - d_len(text) - if excess <= 0: - return text - if excess % 2: - # uneven padding - narrowside = (excess // 2) * pad_char - widerside = narrowside + pad_char - if width % 2: - return narrowside + text + widerside - else: - return widerside + text + narrowside - else: - # even padding - same on both sides - side = (excess // 2) * pad_char - return side + text + side - - def _align(self, data): - """ - Align list of rows of cell. Whitespace characters will be stripped - if there is only one whitespace character - otherwise, it's assumed - the caller may be trying some manual formatting in the text. - - Args: - data (str): Text to align. - - Returns: - text (str): Aligned result. - - """ - align = self.align - hfill_char = self.hfill_char - width = self.width - if align == "l": - lines = [ - ( - line.lstrip(" ") + " " - if line.startswith(" ") and not line.startswith(" ") - else line - ) - + hfill_char * (width - d_len(line)) - for line in data - ] - return lines - elif align == "r": - return [ - hfill_char * (width - d_len(line)) - + ( - " " + line.rstrip(" ") - if line.endswith(" ") and not line.endswith(" ") - else line - ) - for line in data - ] - else: # center, 'c' - return [self._center(line, self.width, self.hfill_char) for line in data] - - def _valign(self, data): - """ - Align cell vertically - - Args: - data (str): Text to align. - - Returns: - text (str): Vertically aligned text. - - """ - valign = self.valign - height = self.height - cheight = len(data) - excess = height - cheight - padline = self.vfill_char * self.width - - if excess <= 0: - return data - # only care if we need to add new lines - if valign == "t": - return data + [padline for _ in range(excess)] - elif valign == "b": - return [padline for _ in range(excess)] + data - else: # center - narrowside = [padline for _ in range(excess // 2)] - widerside = narrowside + [padline] - if excess % 2: - # uneven padding - if height % 2: - return widerside + data + narrowside - else: - return narrowside + data + widerside - else: - # even padding, same on both sides - return narrowside + data + narrowside - - def _pad(self, data): - """ - Pad data with extra characters on all sides. - - Args: - data (str): Text to pad. - - Returns: - text (str): Padded text. - - """ - left = self.hpad_char * self.pad_left - right = self.hpad_char * self.pad_right - vfill = (self.width + self.pad_left + self.pad_right) * self.vpad_char - top = [vfill for _ in range(self.pad_top)] - bottom = [vfill for _ in range(self.pad_bottom)] - return top + [left + line + right for line in data] + bottom - - def _border(self, data): - """ - Add borders to the cell. - - Args: - data (str): Text to surround with borders. - - Return: - text (str): Text with borders. - - """ - - left = self.border_left_char * self.border_left + ANSIString("|n") - right = ANSIString("|n") + self.border_right_char * self.border_right - - cwidth = ( - self.width - + self.pad_left - + self.pad_right - + max(0, self.border_left - 1) - + max(0, self.border_right - 1) - ) - - vfill = self.corner_top_left_char if left else "" - vfill += cwidth * self.border_top_char - vfill += self.corner_top_right_char if right else "" - top = [vfill for _ in range(self.border_top)] - - vfill = self.corner_bottom_left_char if left else "" - vfill += cwidth * self.border_bottom_char - vfill += self.corner_bottom_right_char if right else "" - bottom = [vfill for _ in range(self.border_bottom)] - - return top + [left + line + right for line in data] + bottom - -
[docs] def get_min_height(self): - """ - Get the minimum possible height of cell, including at least - one line for data. - - Returns: - min_height (int): The mininum height of cell. - - """ - return self.pad_top + self.pad_bottom + self.border_bottom + self.border_top + 1
- -
[docs] def get_min_width(self): - """ - Get the minimum possible width of cell, including at least one - character-width for data. - - Returns: - min_width (int): The minimum width of cell. - - """ - return self.pad_left + self.pad_right + self.border_left + self.border_right + 1
- -
[docs] def get_height(self): - """ - Get natural height of cell, including padding. - - Returns: - natural_height (int): Height of cell. - - """ - return len(self.formatted) # if self.formatted else 0
- -
[docs] def get_width(self): - """ - Get natural width of cell, including padding. - - Returns: - natural_width (int): Width of cell. - - """ - return d_len(self.formatted[0]) # if self.formatted else 0
- -
[docs] def replace_data(self, data, **kwargs): - """ - Replace cell data. This causes a full reformat of the cell. - - Args: - data (str): Cell data. - - Notes: - The available keyword arguments are the same as for - `EvCell.__init__`. - - """ - self.data = self._split_lines(_to_ansi(data)) - self.raw_width = max(d_len(line) for line in self.data) - self.raw_height = len(self.data) - self.reformat(**kwargs)
- -
[docs] def reformat(self, **kwargs): - """ - Reformat the EvCell with new options - - Keyword Args: - The available keyword arguments are the same as for `EvCell.__init__`. - - Raises: - Exception: If the cells cannot shrink enough to accomodate - the options or the data given. - - """ - # keywords that require manipulation - padwidth = kwargs.get("pad_width", None) - padwidth = int(padwidth) if padwidth is not None else None - self.pad_left = int( - kwargs.pop("pad_left", padwidth if padwidth is not None else self.pad_left) - ) - self.pad_right = int( - kwargs.pop("pad_right", padwidth if padwidth is not None else self.pad_right) - ) - self.pad_top = int( - kwargs.pop("pad_top", padwidth if padwidth is not None else self.pad_top) - ) - self.pad_bottom = int( - kwargs.pop("pad_bottom", padwidth if padwidth is not None else self.pad_bottom) - ) - - self.enforce_size = kwargs.get("enforce_size", False) - - pad_char = kwargs.pop("pad_char", None) - hpad_char = kwargs.pop("hpad_char", pad_char) - self.hpad_char = hpad_char[0] if hpad_char else self.hpad_char - vpad_char = kwargs.pop("vpad_char", pad_char) - self.vpad_char = vpad_char[0] if vpad_char else self.vpad_char - - fillchar = kwargs.pop("fill_char", None) - hfill_char = kwargs.pop("hfill_char", fillchar) - self.hfill_char = hfill_char[0] if hfill_char else self.hfill_char - vfill_char = kwargs.pop("vfill_char", fillchar) - self.vfill_char = vfill_char[0] if vfill_char else self.vfill_char - - borderwidth = kwargs.get("border_width", None) - self.border_left = kwargs.pop( - "border_left", borderwidth if borderwidth is not None else self.border_left - ) - self.border_right = kwargs.pop( - "border_right", borderwidth if borderwidth is not None else self.border_right - ) - self.border_top = kwargs.pop( - "border_top", borderwidth if borderwidth is not None else self.border_top - ) - self.border_bottom = kwargs.pop( - "border_bottom", borderwidth if borderwidth is not None else self.border_bottom - ) - - borderchar = kwargs.get("border_char", None) - self.border_left_char = kwargs.pop( - "border_left_char", borderchar if borderchar else self.border_left_char - ) - self.border_right_char = kwargs.pop( - "border_right_char", borderchar if borderchar else self.border_right_char - ) - self.border_top_char = kwargs.pop( - "border_topchar", borderchar if borderchar else self.border_top_char - ) - self.border_bottom_char = kwargs.pop( - "border_bottom_char", borderchar if borderchar else self.border_bottom_char - ) - - corner_char = kwargs.get("corner_char", None) - self.corner_top_left_char = kwargs.pop( - "corner_top_left", corner_char if corner_char is not None else self.corner_top_left_char - ) - self.corner_top_right_char = kwargs.pop( - "corner_top_right", - corner_char if corner_char is not None else self.corner_top_right_char, - ) - self.corner_bottom_left_char = kwargs.pop( - "corner_bottom_left", - corner_char if corner_char is not None else self.corner_bottom_left_char, - ) - self.corner_bottom_right_char = kwargs.pop( - "corner_bottom_right", - corner_char if corner_char is not None else self.corner_bottom_right_char, - ) - - # this is used by the table to adjust size of cells with borders in the middle - # of the table - self.trim_horizontal = kwargs.pop("trim_horizontal", self.trim_horizontal) - self.trim_vertical = kwargs.pop("trim_vertical", self.trim_vertical) - - # fill all other properties - for key, value in kwargs.items(): - setattr(self, key, value) - - # Handle sizes - if "width" in kwargs: - width = kwargs.pop("width") - self.width = ( - width - - self.pad_left - - self.pad_right - - self.border_left - - self.border_right - + self.trim_horizontal - ) - # if self.width <= 0 and self.raw_width > 0: - if self.width <= 0 < self.raw_width: - raise Exception("Cell width too small, no room for data.") - if "height" in kwargs: - height = kwargs.pop("height") - self.height = ( - height - - self.pad_top - - self.pad_bottom - - self.border_top - - self.border_bottom - + self.trim_vertical - ) - if self.height <= 0 < self.raw_height: - raise Exception("Cell height too small, no room for data.") - - # reformat (to new sizes, padding, header and borders) - self.formatted = self._reformat()
- -
[docs] def get(self): - """ - Get data, padded and aligned in the form of a list of lines. - - """ - self.formatted = self._reformat() - return self.formatted
- - def __repr__(self): - self.formatted = self._reformat() - return str(ANSIString("<EvCel %s>" % self.formatted)) - - def __str__(self): - "returns cell contents on string form" - self.formatted = self._reformat() - return str(ANSIString("\n").join(self.formatted))
- - -# EvColumn class - - -
[docs]class EvColumn(object): - """ - This class holds a list of Cells to represent a column of a table. - It holds operations and settings that affect *all* cells in the - column. - - Columns are not intended to be used stand-alone; they should be - incorporated into an EvTable (like EvCells) - - """ - -
[docs] def __init__(self, *args, **kwargs): - """ - Args: - Text for each row in the column - - Keyword Args: - All `EvCell.__init_` keywords are available, these - settings will be persistently applied to every Cell in the - column. - - """ - self.options = kwargs # column-specific options - self.column = [EvCell(data, **kwargs) for data in args]
- - def _balance(self, **kwargs): - """ - Make sure to adjust the width of all cells so we form a - coherent and lined-up column. Will enforce column-specific - options to cells. - - Keyword Args: - Extra keywords to modify the column setting. Same keywords - as in `EvCell.__init__`. - - """ - col = self.column - # fixed options for the column will override those requested in the call! - # this is particularly relevant to things like width/height, to avoid - # fixed-widths columns from being auto-balanced - kwargs.update(self.options) - # use fixed width or adjust to the largest cell - if "width" not in kwargs: - [ - cell.reformat() for cell in col - ] # this is necessary to get initial widths of all cells - kwargs["width"] = max(cell.get_width() for cell in col) if col else 0 - [cell.reformat(**kwargs) for cell in col] - -
[docs] def add_rows(self, *args, **kwargs): - """ - Add new cells to column. They will be inserted as - a series of rows. It will inherit the options - of the rest of the column's cells (use update to change - options). - - Args: - Texts for the new cells - ypos (int, optional): Index position in table before which to insert the - new column. Uses Python indexing, so to insert at the top, - use `ypos=0`. If not given, data will be inserted at the end - of the column. - - Keyword Args: - Available keywods as per `EvCell.__init__`. - - """ - # column-level options override those in kwargs - options = {**kwargs, **self.options} - - ypos = kwargs.get("ypos", None) - if ypos is None or ypos > len(self.column): - # add to the end - self.column.extend([EvCell(data, **options) for data in args]) - else: - # insert cells before given index - ypos = min(len(self.column) - 1, max(0, int(ypos))) - new_cells = [EvCell(data, **options) for data in args] - self.column = self.column[:ypos] + new_cells + self.column[ypos:]
- # self._balance(**kwargs) - -
[docs] def reformat(self, **kwargs): - """ - Change the options for the column. - - Keyword Args: - Keywords as per `EvCell.__init__`. - - """ - self._balance(**kwargs)
- -
[docs] def reformat_cell(self, index, **kwargs): - """ - reformat cell at given index, keeping column options if - necessary. - - Args: - index (int): Index location of the cell in the column, - starting from 0 for the first row to Nrows-1. - - Keyword Args: - Keywords as per `EvCell.__init__`. - - """ - # column-level options take precedence here - kwargs.update(self.options) - self.column[index].reformat(**kwargs)
- - def __repr__(self): - return "<EvColumn\n %s>" % ("\n ".join([repr(cell) for cell in self.column])) - - def __len__(self): - return len(self.column) - - def __iter__(self): - return iter(self.column) - - def __getitem__(self, index): - return self.column[index] - - def __setitem__(self, index, value): - self.column[index] = value - - def __delitem__(self, index): - del self.column[index]
- - -# Main Evtable class - - -
[docs]class EvTable(object): - """ - The table class holds a list of EvColumns, each consisting of EvCells so - that the result is a 2D matrix. - """ - -
[docs] def __init__(self, *args, **kwargs): - """ - Args: - Header texts for the table. - - Keyword Args: - table (list of lists or list of `EvColumns`, optional): - This is used to build the table in a quick way. If not - given, the table will start out empty and `add_` methods - need to be used to add rows/columns. - header (bool, optional): `True`/`False` - turn off the - header texts (`*args`) being treated as a header (such as - not adding extra underlining) - pad_width (int, optional): How much empty space to pad your cells with - (default is 1) - border (str, optional)): The border style to use. This is one of - - `None` - No border drawing at all. - - "table" - only a border around the whole table. - - "tablecols" - table and column borders. (default) - - "header" - only border under header. - - "cols" - only vertical borders. - - "incols" - vertical borders, no outer edges. - - "rows" - only borders between rows. - - "cells" - border around all cells. - border_width (int, optional): Width of table borders, if border is active. - Note that widths wider than 1 may give artifacts in the corners. Default is 1. - corner_char (str, optional): Character to use in corners when border is active. - Default is `+`. - corner_top_left_char (str, optional): Character used for "nw" corner of table. - Defaults to `corner_char`. - corner_top_right_char (str, optional): Character used for "ne" corner of table. - Defaults to `corner_char`. - corner_bottom_left_char (str, optional): Character used for "sw" corner of table. - Defaults to `corner_char`. - corner_bottom_right_char (str, optional): Character used for "se" corner of table. - Defaults to `corner_char`. - pretty_corners (bool, optional): Use custom characters to - make the table corners look "rounded". Uses UTF-8 - characters. Defaults to `False` for maximum compatibility with various displays - that may occationally have issues with UTF-8 characters. - header_line_char (str, optional): Character to use for underlining - the header row (default is '~'). Requires `border` to not be `None`. - width (int, optional): Fixed width of table. If not set, - width is set by the total width of each column. This will - resize individual columns in the vertical direction to fit. - height (int, optional): Fixed height of table. Defaults to being unset. Width is - still given precedence. If given, table cells will crop text rather - than expand vertically. - evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as even width as - possible. This often looks best also for mixed-length tables. Default is `False`. - maxwidth (int, optional): This will set a maximum width - of the table while allowing it to be smaller. Only if it grows wider than this - size will it be resized by expanding horizontally (or crop `height` is given). - This keyword has no meaning if `width` is set. - - Raises: - Exception: If given erroneous input or width settings for the data. - - Notes: - Beyond those table-specific keywords, the non-overlapping keywords - of `EvCell.__init__` are also available. These will be passed down - to every cell in the table. - - """ - # at this point table is a 2D grid - a list of columns - # x is the column position, y the row - table = kwargs.pop("table", []) - - # header is a list of texts. We merge it to the table's top - header = [_to_ansi(head) for head in args] - self.header = header != [] - if self.header: - if table: - excess = len(header) - len(table) - if excess > 0: - # header bigger than table - table.extend([] for _ in range(excess)) - elif excess < 0: - # too short header - header.extend(_to_ansi(["" for _ in range(abs(excess))])) - for ix, heading in enumerate(header): - table[ix].insert(0, heading) - else: - table = [[heading] for heading in header] - # even though we inserted the header, we can still turn off - # header border underling etc. We only allow this if a header - # was actually set - self.header = kwargs.pop("header", self.header) if self.header else False - hchar = kwargs.pop("header_line_char", "~") - self.header_line_char = hchar[0] if hchar else "~" - - border = kwargs.pop("border", "tablecols") - if border is None: - border = "none" - if border not in ( - "none", - "table", - "tablecols", - "header", - "incols", - "cols", - "rows", - "cells", - ): - raise Exception("Unsupported border type: '%s'" % border) - self.border = border - - # border settings are passed into Cell as well (so kwargs.get and not pop) - self.border_width = kwargs.get("border_width", 1) - self.corner_char = kwargs.get("corner_char", "+") - pcorners = kwargs.pop("pretty_corners", False) - self.corner_top_left_char = _to_ansi( - kwargs.pop("corner_top_left_char", "." if pcorners else self.corner_char) - ) - self.corner_top_right_char = _to_ansi( - kwargs.pop("corner_top_right_char", "." if pcorners else self.corner_char) - ) - self.corner_bottom_left_char = _to_ansi( - kwargs.pop("corner_bottom_left_char", " " if pcorners else self.corner_char) - ) - self.corner_bottom_right_char = _to_ansi( - kwargs.pop("corner_bottom_right_char", " " if pcorners else self.corner_char) - ) - - self.width = kwargs.pop("width", None) - self.height = kwargs.pop("height", None) - self.evenwidth = kwargs.pop("evenwidth", False) - self.maxwidth = kwargs.pop("maxwidth", None) - if self.maxwidth and self.width and self.maxwidth < self.width: - raise Exception("table maxwidth < table width!") - # size in cell cols/rows - self.ncols = len(table) - self.nrows = max(len(col) for col in table) if table else 0 - # size in characters (gets set when _balance is called) - self.nwidth = 0 - self.nheight = 0 - # save options - self.options = kwargs - - # use the temporary table to generate the table on the fly, as a list of EvColumns - self.table = [EvColumn(*col, **kwargs) for col in table] - - # this is the actual working table - self.worktable = None
- - # balance the table - # self._balance() - - def _cellborders(self, ix, iy, nx, ny, **kwargs): - """ - Adds borders to the table by adjusting the input kwarg to - instruct cells to build a border in the right positions. - - Args: - ix (int): x index positions in table. - iy (int): y index positions in table. - nx (int): x size of table. - ny (int): y size of table. - - Keyword Args: - Keywords as per `EvTable.__init__`. - - Returns: - table (str): string with the correct borders. - - Notes: - A copy of the kwarg is returned to the cell. This is method - is called by self._borders. - - """ - - ret = kwargs.copy() - - # handle the various border modes - border = self.border - header = self.header - - bwidth = self.border_width - headchar = self.header_line_char - - def corners(ret): - """Handle corners of table""" - if ix == 0 and iy == 0: - ret["corner_top_left_char"] = self.corner_top_left_char - if ix == nx and iy == 0: - ret["corner_top_right_char"] = self.corner_top_right_char - if ix == 0 and iy == ny: - ret["corner_bottom_left_char"] = self.corner_bottom_left_char - if ix == nx and iy == ny: - ret["corner_bottom_right_char"] = self.corner_bottom_right_char - return ret - - def left_edge(ret): - """add vertical border along left table edge""" - if ix == 0: - ret["border_left"] = bwidth - # ret["trim_horizontal"] = bwidth - return ret - - def top_edge(ret): - """add border along top table edge""" - if iy == 0: - ret["border_top"] = bwidth - # ret["trim_vertical"] = bwidth - return ret - - def right_edge(ret): - """add vertical border along right table edge""" - if ix == nx: # and 0 < iy < ny: - ret["border_right"] = bwidth - # ret["trim_horizontal"] = 0 - return ret - - def bottom_edge(ret): - """add border along bottom table edge""" - if iy == ny: - ret["border_bottom"] = bwidth - # ret["trim_vertical"] = bwidth - return ret - - def cols(ret): - """Adding vertical borders inside the table""" - if 0 <= ix < nx: - ret["border_right"] = bwidth - return ret - - def rows(ret): - """Adding horizontal borders inside the table""" - if 0 <= iy < ny: - ret["border_bottom"] = bwidth - return ret - - def head(ret): - """Add header underline""" - if iy == 0: - # put different bottom line for header - ret["border_bottom"] = bwidth - ret["border_bottom_char"] = headchar - return ret - - # use the helper functions to define various - # table "styles" - - if border in ("table", "tablecols", "cells"): - ret = bottom_edge(right_edge(top_edge(left_edge(corners(ret))))) - if border in ("cols", "tablecols", "cells"): - ret = cols(right_edge(left_edge(ret))) - if border in "incols": - ret = cols(ret) - if border in ("rows", "cells"): - ret = rows(bottom_edge(top_edge(ret))) - if header and border not in ("none", None): - ret = head(ret) - - return ret - - def _borders(self): - """ - Add borders to table. This is called from self._balance. - """ - nx, ny = self.ncols - 1, self.nrows - 1 - options = self.options - for ix, col in enumerate(self.worktable): - for iy, cell in enumerate(col): - col.reformat_cell(iy, **self._cellborders(ix, iy, nx, ny, **options)) - - def _balance(self): - """ - Balance the table. This means to make sure - all cells on the same row have the same height, - that all columns have the same number of rows - and that the table fits within the given width. - """ - - # we make all modifications on a working copy of the - # actual table. This allows us to add columns/rows - # and re-balance over and over without issue. - self.worktable = deepcopy(self.table) - # self._borders() - # return - options = copy(self.options) - - # balance number of rows to make a rectangular table - # column by column - ncols = len(self.worktable) - nrows = [len(col) for col in self.worktable] - nrowmax = max(nrows) if nrows else 0 - for icol, nrow in enumerate(nrows): - self.worktable[icol].reformat(**options) - if nrow < nrowmax: - # add more rows to too-short columns - empty_rows = ["" for _ in range(nrowmax - nrow)] - self.worktable[icol].add_rows(*empty_rows) - self.ncols = ncols - self.nrows = nrowmax - - # add borders - these add to the width/height, so we must do this before calculating width/height - self._borders() - - # equalize widths within each column - cwidths = [max(cell.get_width() for cell in col) for col in self.worktable] - - if self.width or self.maxwidth and self.maxwidth < sum(cwidths): - # we set a table width. Horizontal cells will be evenly distributed and - # expand vertically as needed (unless self.height is set, see below) - - # use fixed width, or set to maxwidth - width = self.width if self.width else self.maxwidth - - if ncols: - # get minimum possible cell widths for each row - cwidths_min = [max(cell.get_min_width() for cell in col) for col in self.worktable] - cwmin = sum(cwidths_min) - - # get which cols have separately set widths - these should be locked - # note that we need to remove cwidths_min for each lock to avoid counting - # it twice (in cwmin and in locked_cols) - locked_cols = { - icol: col.options["width"] - cwidths_min[icol] - for icol, col in enumerate(self.worktable) - if "width" in col.options - } - locked_width = sum(locked_cols.values()) - - excess = width - cwmin - locked_width - - if len(locked_cols) >= ncols and excess: - # we can't adjust the width at all - all columns are locked - raise Exception( - "Cannot balance table to width %s - " - "all columns have a set, fixed width summing to %s!" - % (self.width, sum(cwidths)) - ) - - if excess < 0: - # the locked cols makes it impossible - raise Exception( - "Cannot shrink table width to %s. " - "Minimum size (and/or fixed-width columns) " - "sets minimum at %s." % (self.width, cwmin + locked_width) - ) - - if self.evenwidth: - # make each column of equal width - # use cwidths as a work-array to track weights - cwidths = copy(cwidths_min) - correction = 0 - while correction < excess: - # flood-fill the minimum table starting with the smallest columns - ci = cwidths.index(min(cwidths)) - if ci in locked_cols: - # locked column, make sure it's not picked again - cwidths[ci] += 9999 - cwidths_min[ci] = locked_cols[ci] - else: - cwidths_min[ci] += 1 - correction += 1 - cwidths = cwidths_min - else: - # make each column expand more proportional to their data size - # we use cwidth as a work-array to track weights - correction = 0 - while correction < excess: - # fill wider columns first - ci = cwidths.index(max(cwidths)) - if ci in locked_cols: - # locked column, make sure it's not picked again - cwidths[ci] -= 9999 - cwidths_min[ci] = locked_cols[ci] - else: - cwidths_min[ci] += 1 - correction += 1 - # give a just changed col less prio next run - cwidths[ci] -= 3 - cwidths = cwidths_min - - # reformat worktable (for width align) - for ix, col in enumerate(self.worktable): - try: - col.reformat(width=cwidths[ix], **options) - except Exception: - raise - - # equalize heights for each row (we must do this here, since it may have changed to fit new widths) - cheights = [ - max(cell.get_height() for cell in (col[iy] for col in self.worktable)) - for iy in range(nrowmax) - ] - - if self.height: - # if we are fixing the table height, it means cells must crop text instead of resizing. - if nrowmax: - - # get minimum possible cell heights for each column - cheights_min = [ - max(cell.get_min_height() for cell in (col[iy] for col in self.worktable)) - for iy in range(nrowmax) - ] - chmin = sum(cheights_min) - - # get which cols have separately set heights - these should be locked - # note that we need to remove cheights_min for each lock to avoid counting - # it twice (in chmin and in locked_cols) - locked_cols = { - icol: col.options["height"] - cheights_min[icol] - for icol, col in enumerate(self.worktable) - if "height" in col.options - } - locked_height = sum(locked_cols.values()) - - excess = self.height - chmin - locked_height - - if chmin > self.height: - # we cannot shrink any more - raise Exception( - "Cannot shrink table height to %s. Minimum " - "size (and/or fixed-height rows) sets minimum at %s." - % (self.height, chmin + locked_height) - ) - - # now we add all the extra height up to the desired table-height. - # We do this so that the tallest cells gets expanded first (and - # thus avoid getting cropped) - - even = self.height % 2 == 0 - correction = 0 - while correction < excess: - # expand the cells with the most rows first - if 0 <= correction < nrowmax and nrowmax > 1: - # avoid adding to header first round (looks bad on very small tables) - ci = cheights[1:].index(max(cheights[1:])) + 1 - else: - ci = cheights.index(max(cheights)) - if ci in locked_cols: - # locked row, make sure it's not picked again - cheights[ci] -= 9999 - cheights_min[ci] = locked_cols[ci] - else: - cheights_min[ci] += 1 - # change balance - if ci == 0 and self.header: - # it doesn't look very good if header expands too fast - cheights[ci] -= 2 if even else 3 - cheights[ci] -= 2 if even else 1 - correction += 1 - cheights = cheights_min - - # we must tell cells to crop instead of expanding - options["enforce_size"] = True - - # reformat table (for vertical align) - for ix, col in enumerate(self.worktable): - for iy, cell in enumerate(col): - try: - col.reformat_cell(iy, height=cheights[iy], **options) - except Exception as e: - msg = "ix=%s, iy=%s, height=%s: %s" % (ix, iy, cheights[iy], e.message) - raise Exception("Error in vertical align:\n %s" % msg) - - # calculate actual table width/height in characters - self.cwidth = sum(cwidths) - self.cheight = sum(cheights) - - def _generate_lines(self): - """ - Generates lines across all columns - (each cell may contain multiple lines) - This will also balance the table. - """ - self._balance() - for iy in range(self.nrows): - cell_row = [col[iy] for col in self.worktable] - # this produces a list of lists, each of equal length - cell_data = [cell.get() for cell in cell_row] - cell_height = min(len(lines) for lines in cell_data) - for iline in range(cell_height): - yield ANSIString("").join(_to_ansi(celldata[iline] for celldata in cell_data)) - -
[docs] def add_header(self, *args, **kwargs): - """ - Add header to table. This is a number of texts to be put at - the top of the table. They will replace an existing header. - - Args: - args (str): These strings will be used as the header texts. - - Keyword Args: - Same keywords as per `EvTable.__init__`. Will be applied - to the new header's cells. - - """ - self.header = True - self.add_row(ypos=0, *args, **kwargs)
- -
[docs] def add_column(self, *args, **kwargs): - """ - Add a column to table. If there are more rows in new column - than there are rows in the current table, the table will - expand with empty rows in the other columns. If too few, the - new column with get new empty rows. All filling rows are added - to the end. - - Args: - args (`EvColumn` or multiple strings): Either a single EvColumn instance or - a number of data string arguments to be used to create a new column. - header (str, optional): The header text for the column - xpos (int, optional): Index position in table *before* which - to input new column. If not given, column will be added to the end - of the table. Uses Python indexing (so first column is `xpos=0`) - - Keyword Args: - Other keywords as per `Cell.__init__`. - - """ - # this will replace default options with new ones without changing default - options = dict(list(self.options.items()) + list(kwargs.items())) - - xpos = kwargs.get("xpos", None) - column = EvColumn(*args, **options) - wtable = self.ncols - htable = self.nrows - - header = kwargs.get("header", None) - if header: - column.add_rows(str(header), ypos=0, **options) - self.header = True - elif self.header: - # we have a header already. Offset - column.add_rows("", ypos=0, **options) - - # Calculate whether the new column needs to expand to the - # current table size, or if the table needs to expand to - # the column size. - # This needs to happen after the header rows have already been - # added to the column in order for the size calculations to match. - excess = len(column) - htable - if excess > 0: - # we need to add new rows to table - for col in self.table: - empty_rows = ["" for _ in range(excess)] - col.add_rows(*empty_rows, **options) - self.nrows += excess - elif excess < 0: - # we need to add new rows to new column - empty_rows = ["" for _ in range(abs(excess))] - column.add_rows(*empty_rows, **options) - self.nrows -= excess - - if xpos is None or xpos > wtable - 1: - # add to the end - self.table.append(column) - else: - # insert column - xpos = min(wtable - 1, max(0, int(xpos))) - self.table.insert(xpos, column) - self.ncols += 1
- # self._balance() - -
[docs] def add_row(self, *args, **kwargs): - """ - Add a row to table (not a header). If there are more cells in - the given row than there are cells in the current table the - table will be expanded with empty columns to match. These will - be added to the end of the table. In the same way, adding a - line with too few cells will lead to the last ones getting - padded. - - Args: - args (str): Any number of string argumnets to use as the - data in the row (one cell per argument). - ypos (int, optional): Index position in table before which to - input new row. If not given, will be added to the end of the table. - Uses Python indexing (so first row is `ypos=0`) - - Keyword Args: - Other keywords are as per `EvCell.__init__`. - - """ - # this will replace default options with new ones without changing default - row = list(args) - options = dict(list(self.options.items()) + list(kwargs.items())) - - ypos = kwargs.get("ypos", None) - wtable = self.ncols - htable = self.nrows - excess = len(row) - wtable - - if excess > 0: - # we need to add new empty columns to table - empty_rows = ["" for _ in range(htable)] - self.table.extend([EvColumn(*empty_rows, **options) for _ in range(excess)]) - self.ncols += excess - elif excess < 0: - # we need to add more cells to row - row.extend(["" for _ in range(abs(excess))]) - self.ncols -= excess - - if ypos is None or ypos > htable - 1: - # add new row to the end - for icol, col in enumerate(self.table): - col.add_rows(row[icol], **options) - else: - # insert row elsewhere - ypos = min(htable - 1, max(0, int(ypos))) - for icol, col in enumerate(self.table): - col.add_rows(row[icol], ypos=ypos, **options) - self.nrows += 1
- # self._balance() - -
[docs] def reformat(self, **kwargs): - """ - Force a re-shape of the entire table. - - Keyword Args: - Table options as per `EvTable.__init__`. - - """ - self.width = kwargs.pop("width", self.width) - self.height = kwargs.pop("height", self.height) - for key, value in kwargs.items(): - setattr(self, key, value) - - hchar = kwargs.pop("header_line_char", self.header_line_char) - - # border settings are also passed on into EvCells (so kwargs.get, not kwargs.pop) - self.header_line_char = hchar[0] if hchar else self.header_line_char - self.border_width = kwargs.get("border_width", self.border_width) - self.corner_char = kwargs.get("corner_char", self.corner_char) - self.header_line_char = kwargs.get("header_line_char", self.header_line_char) - - self.corner_top_left_char = _to_ansi(kwargs.pop("corner_top_left_char", self.corner_char)) - self.corner_top_right_char = _to_ansi(kwargs.pop("corner_top_right_char", self.corner_char)) - self.corner_bottom_left_char = _to_ansi( - kwargs.pop("corner_bottom_left_char", self.corner_char) - ) - self.corner_bottom_right_char = _to_ansi( - kwargs.pop("corner_bottom_right_char", self.corner_char) - ) - - self.options.update(kwargs)
- -
[docs] def reformat_column(self, index, **kwargs): - """ - Sends custom options to a specific column in the table. - - Args: - index (int): Which column to reformat. The column index is - given from 0 to Ncolumns-1. - - Keyword Args: - Column options as per `EvCell.__init__`. - - Raises: - Exception: if an invalid index is found. - - """ - if index > len(self.table): - raise Exception("Not a valid column index") - # we update the columns' options which means eventual width/height - # will be 'locked in' and withstand auto-balancing width/height from the table later - self.table[index].options.update(kwargs) - self.table[index].reformat(**kwargs)
- -
[docs] def get(self): - """ - Return lines of table as a list. - - Returns: - table_lines (list): The lines of the table, in order. - - """ - return [line for line in self._generate_lines()]
- - def __str__(self): - """print table (this also balances it)""" - # h = "12345678901234567890123456789012345678901234567890123456789012345678901234567890" - return str(str(ANSIString("\n").join([line for line in self._generate_lines()])))
- - -def _test(): - """Test""" - table = EvTable( - "|yHeading1|n", - "|gHeading2|n", - table=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], - border="cells", - align="l", - ) - table.add_column("|rThis is long data|n", "|bThis is even longer data|n") - table.add_row("This is a single row") - print(str(table)) - table.reformat(width=50) - print(str(table)) - table.reformat_column(3, width=30, align="r") - print(str(table)) - return table - - -def _test2(): - table = EvTable("|yHeading1|n", "|B|[GHeading2|n", "Heading3") - for i in range(100): - table.add_row( - "This is col 0, row %i" % i, - "|gThis is col 1, row |w%i|n|g.|n" % i, - "This is col 2, row %i" % i, - ) - return table -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/gametime.html b/docs/0.9.5/_modules/evennia/utils/gametime.html deleted file mode 100644 index e044562734..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/gametime.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - evennia.utils.gametime — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.gametime

-"""
-The gametime module handles the global passage of time in the mud.
-
-It also supplies some useful methods to convert between
-in-mud time and real-world time as well allows to get the
-total runtime of the server and the current uptime.
-"""
-
-import time
-from calendar import monthrange
-from datetime import datetime, timedelta
-
-from django.db.utils import OperationalError
-from django.conf import settings
-from evennia import DefaultScript
-from evennia.server.models import ServerConfig
-from evennia.utils.create import create_script
-
-# Speed-up factor of the in-game time compared
-# to real time.
-
-TIMEFACTOR = settings.TIME_FACTOR
-IGNORE_DOWNTIMES = settings.TIME_IGNORE_DOWNTIMES
-
-
-# Only set if gametime_reset was called at some point.
-try:
-    GAME_TIME_OFFSET = ServerConfig.objects.conf("gametime_offset", default=0)
-except OperationalError:
-    print("Gametime offset could not load - db not set up.")
-    GAME_TIME_OFFSET = 0
-
-# Common real-life time measure, in seconds.
-# You should not change this.
-
-# these are kept updated by the server maintenance loop
-SERVER_START_TIME = 0.0
-SERVER_RUNTIME_LAST_UPDATED = 0.0
-SERVER_RUNTIME = 0.0
-
-# note that these should not be accessed directly since they may
-# need further processing. Access from server_epoch() and game_epoch().
-_SERVER_EPOCH = None
-_GAME_EPOCH = None
-
-# Helper Script dealing in gametime (created by `schedule` function
-# below).
-
-
-
[docs]class TimeScript(DefaultScript): - """Gametime-sensitive script.""" - -
[docs] def at_script_creation(self): - """The script is created.""" - self.key = "unknown scr" - self.interval = 100 - self.start_delay = True - self.persistent = True
- -
[docs] def at_repeat(self): - """Call the callback and reset interval.""" - callback = self.db.callback - if callback: - callback() - - seconds = real_seconds_until(**self.db.gametime) - self.restart(interval=seconds)
- - -# Access functions - - -
[docs]def runtime(): - """ - Get the total runtime of the server since first start (minus - downtimes) - - Args: - format (bool, optional): Format into a time representation. - - Returns: - time (float or tuple): The runtime or the same time split up - into time units. - - """ - return SERVER_RUNTIME + time.time() - SERVER_RUNTIME_LAST_UPDATED
- - -
[docs]def server_epoch(): - """ - Get the server epoch. We may need to calculate this on the fly. - - """ - global _SERVER_EPOCH - if not _SERVER_EPOCH: - _SERVER_EPOCH = ( - ServerConfig.objects.conf("server_epoch", default=None) or time.time() - runtime() - ) - return _SERVER_EPOCH
- - -
[docs]def uptime(): - """ - Get the current uptime of the server since last reload - - Args: - format (bool, optional): Format into time representation. - - Returns: - time (float or tuple): The uptime or the same time split up - into time units. - - """ - return time.time() - SERVER_START_TIME
- - -
[docs]def portal_uptime(): - """ - Get the current uptime of the portal. - - Returns: - time (float): The uptime of the portal. - """ - from evennia.server.sessionhandler import SESSIONS - - return time.time() - SESSIONS.portal_start_time
- - -
[docs]def game_epoch(): - """ - Get the game epoch. - - """ - game_epoch = settings.TIME_GAME_EPOCH - return game_epoch if game_epoch is not None else server_epoch()
- - -
[docs]def gametime(absolute=False): - """ - Get the total gametime of the server since first start (minus downtimes) - - Args: - absolute (bool, optional): Get the absolute game time, including - the epoch. This could be converted to an absolute in-game - date. - - Returns: - time (float): The gametime as a virtual timestamp. - - Notes: - If one is using a standard calendar, one could convert the unformatted - return to a date using Python's standard `datetime` module like this: - `datetime.datetime.fromtimestamp(gametime(absolute=True))` - - """ - epoch = game_epoch() if absolute else 0 - if IGNORE_DOWNTIMES: - gtime = epoch + (time.time() - server_epoch()) * TIMEFACTOR - else: - gtime = epoch + (runtime() - GAME_TIME_OFFSET) * TIMEFACTOR - return gtime
- - -
[docs]def real_seconds_until(sec=None, min=None, hour=None, day=None, month=None, year=None): - """ - Return the real seconds until game time. - - Args: - sec (int or None): number of absolute seconds. - min (int or None): number of absolute minutes. - hour (int or None): number of absolute hours. - day (int or None): number of absolute days. - month (int or None): number of absolute months. - year (int or None): number of absolute years. - - Returns: - The number of real seconds before the given game time is up. - - Example: - real_seconds_until(hour=5, min=10, sec=0) - - If the game time is 5:00, TIME_FACTOR is set to 2 and you ask - the number of seconds until it's 5:10, then this function should - return 300 (5 minutes). - - - """ - current = datetime.fromtimestamp(gametime(absolute=True)) - s_sec = sec if sec is not None else current.second - s_min = min if min is not None else current.minute - s_hour = hour if hour is not None else current.hour - s_day = day if day is not None else current.day - s_month = month if month is not None else current.month - s_year = year if year is not None else current.year - projected = datetime(s_year, s_month, s_day, s_hour, s_min, s_sec) - - if projected <= current: - # We increase one unit of time depending on parameters - if month is not None: - projected = projected.replace(year=s_year + 1) - elif day is not None: - try: - projected = projected.replace(month=s_month + 1) - except ValueError: - projected = projected.replace(month=1) - elif hour is not None: - projected += timedelta(days=1) - elif min is not None: - projected += timedelta(seconds=3600) - else: - projected += timedelta(seconds=60) - - # Get the number of gametime seconds between these two dates - seconds = (projected - current).total_seconds() - return seconds / TIMEFACTOR
- - -
[docs]def schedule( - callback, repeat=False, sec=None, min=None, hour=None, day=None, month=None, year=None -): - """ - Call a callback at a given in-game time. - - Args: - callback (function): The callback function that will be called. Note - that the callback must be a module-level function, since the script will - be persistent. - repeat (bool, optional): Defines if the callback should be called regularly - at the specified time. - sec (int or None): Number of absolute game seconds at which to run repeat. - min (int or None): Number of absolute minutes. - hour (int or None): Number of absolute hours. - day (int or None): Number of absolute days. - month (int or None): Number of absolute months. - year (int or None): Number of absolute years. - - Returns: - script (Script): The created Script handling the sceduling. - - Examples: - schedule(func, min=5, sec=0) # Will call 5 minutes past the next (in-game) hour. - schedule(func, hour=2, min=30, sec=0) # Will call the next (in-game) day at 02:30. - """ - seconds = real_seconds_until(sec=sec, min=min, hour=hour, day=day, month=month, year=year) - script = create_script( - "evennia.utils.gametime.TimeScript", - key="TimeScript", - desc="A gametime-sensitive script", - interval=seconds, - start_delay=True, - repeats=-1 if repeat else 1, - ) - script.db.callback = callback - script.db.gametime = { - "sec": sec, - "min": min, - "hour": hour, - "day": day, - "month": month, - "year": year, - } - return script
- - -
[docs]def reset_gametime(): - """ - Resets the game time to make it start from the current time. Note that - the epoch set by `settings.TIME_GAME_EPOCH` will still apply. - - """ - global GAME_TIME_OFFSET - GAME_TIME_OFFSET = runtime() - ServerConfig.objects.conf("gametime_offset", GAME_TIME_OFFSET)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/idmapper/manager.html b/docs/0.9.5/_modules/evennia/utils/idmapper/manager.html deleted file mode 100644 index 0e0d388696..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/idmapper/manager.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - evennia.utils.idmapper.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.idmapper.manager

-"""
-IDmapper extension to the default manager.
-"""
-from django.db.models.manager import Manager
-
-
-
[docs]class SharedMemoryManager(Manager): - # TODO: improve on this implementation - # We need a way to handle reverse lookups so that this model can - # still use the singleton cache, but the active model isn't required - # to be a SharedMemoryModel. -
[docs] def get(self, *args, **kwargs): - """ - Data entity lookup. - """ - items = list(kwargs) - inst = None - if len(items) == 1: - # CL: support __exact - key = items[0] - if key.endswith("__exact"): - key = key[: -len("__exact")] - if key in ("pk", self.model._meta.pk.attname): - try: - inst = self.model.get_cached_instance(kwargs[items[0]]) - # we got the item from cache, but if this is a fk, check it's ours - if getattr(inst, str(self.field).split(".")[-1]) != self.instance: - inst = None - except Exception: - pass - if inst is None: - inst = super().get(*args, **kwargs) - return inst
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/idmapper/models.html b/docs/0.9.5/_modules/evennia/utils/idmapper/models.html deleted file mode 100644 index 2dd992fed9..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/idmapper/models.html +++ /dev/null @@ -1,779 +0,0 @@ - - - - - - - - evennia.utils.idmapper.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.idmapper.models

-"""
-Django ID mapper
-
-Modified for Evennia by making sure that no model references
-leave caching unexpectedly (no use of WeakRefs).
-
-Also adds `cache_size()` for monitoring the size of the cache.
-"""
-
-import os
-import threading
-import gc
-import time
-from weakref import WeakValueDictionary
-from twisted.internet.reactor import callFromThread
-from django.core.exceptions import ObjectDoesNotExist, FieldError
-from django.db.models.signals import post_save
-from django.db.models.base import Model, ModelBase
-from django.db.models.signals import pre_delete, post_migrate
-from django.db.utils import DatabaseError
-from evennia.utils import logger
-from evennia.utils.utils import dbref, get_evennia_pids, to_str
-
-from .manager import SharedMemoryManager
-
-AUTO_FLUSH_MIN_INTERVAL = 60.0 * 5  # at least 5 mins between cache flushes
-
-_GA = object.__getattribute__
-_SA = object.__setattr__
-_DA = object.__delattr__
-_MONITOR_HANDLER = None
-
-# References to db-updated objects are stored here so the
-# main process can be informed to re-cache itself.
-PROC_MODIFIED_COUNT = 0
-PROC_MODIFIED_OBJS = WeakValueDictionary()
-
-# get info about the current process and thread; determine if our
-# current pid is different from the server PID (i.e.  # if we are in a
-# subprocess or not)
-_SELF_PID = os.getpid()
-_SERVER_PID, _PORTAL_PID = get_evennia_pids()
-_IS_SUBPROCESS = (_SERVER_PID and _PORTAL_PID) and _SELF_PID not in (_SERVER_PID, _PORTAL_PID)
-_IS_MAIN_THREAD = threading.currentThread().getName() == "MainThread"
-
-
-
[docs]class SharedMemoryModelBase(ModelBase): - # CL: upstream had a __new__ method that skipped ModelBase's __new__ if - # SharedMemoryModelBase was not in the model class's ancestors. It's not - # clear what was the intended purpose, but skipping ModelBase.__new__ - # broke things; in particular, default manager inheritance. - - def __call__(cls, *args, **kwargs): - """ - this method will either create an instance (by calling the default implementation) - or try to retrieve one from the class-wide cache by inferring the pk value from - `args` and `kwargs`. If instance caching is enabled for this class, the cache is - populated whenever possible (ie when it is possible to infer the pk value). - - """ - - def new_instance(): - return super(SharedMemoryModelBase, cls).__call__(*args, **kwargs) - - instance_key = cls._get_cache_key(args, kwargs) - # depending on the arguments, we might not be able to infer the PK, so in that case we create a new instance - if instance_key is None: - return new_instance() - cached_instance = cls.get_cached_instance(instance_key) - if cached_instance is None: - cached_instance = new_instance() - cls.cache_instance(cached_instance, new=True) - return cached_instance - - def _prepare(cls): - """ - Prepare the cache, making sure that proxies of the same db base - share the same cache. - - """ - # the dbmodel is either the proxy base or ourselves - dbmodel = cls._meta.concrete_model if cls._meta.proxy else cls - cls.__dbclass__ = dbmodel - if not hasattr(dbmodel, "__instance_cache__"): - # we store __instance_cache__ only on the dbmodel base - dbmodel.__instance_cache__ = {} - super()._prepare() - - def __new__(cls, name, bases, attrs): - """ - Field shortcut creation: - - Takes field names `db_*` and creates property wrappers named - without the `db_` prefix. So db_key -> key - - This wrapper happens on the class level, so there is no - overhead when creating objects. If a class already has a - wrapper of the given name, the automatic creation is skipped. - - Notes: - Remember to document this auto-wrapping in the class - header, this could seem very much like magic to the user - otherwise. - """ - - attrs["typename"] = cls.__name__ - attrs["path"] = "%s.%s" % (attrs["__module__"], name) - attrs["_is_deleted"] = False - - # set up the typeclass handling only if a variable _is_typeclass is set on the class - def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False): - "Helper method to create property wrappers with unique names (must be in separate call)" - - def _get(cls, fname): - "Wrapper for getting database field" - if _GA(cls, "_is_deleted"): - raise ObjectDoesNotExist( - "Cannot access %s: Hosting object was already deleted." % fname - ) - return _GA(cls, fieldname) - - def _get_foreign(cls, fname): - "Wrapper for returning foreignkey fields" - if _GA(cls, "_is_deleted"): - raise ObjectDoesNotExist( - "Cannot access %s: Hosting object was already deleted." % fname - ) - return _GA(cls, fieldname) - - def _set_nonedit(cls, fname, value): - "Wrapper for blocking editing of field" - raise FieldError("Field %s cannot be edited." % fname) - - def _set(cls, fname, value): - "Wrapper for setting database field" - if _GA(cls, "_is_deleted"): - raise ObjectDoesNotExist( - "Cannot set %s to %s: Hosting object was already deleted!" % (fname, value) - ) - _SA(cls, fname, value) - # only use explicit update_fields in save if we actually have a - # primary key assigned already (won't be set when first creating object) - update_fields = ( - [fname] if _GA(cls, "_get_pk_val")(_GA(cls, "_meta")) is not None else None - ) - _GA(cls, "save")(update_fields=update_fields) - - def _set_foreign(cls, fname, value): - "Setter only used on foreign key relations, allows setting with #dbref" - if _GA(cls, "_is_deleted"): - raise ObjectDoesNotExist( - "Cannot set %s to %s: Hosting object was already deleted!" % (fname, value) - ) - if isinstance(value, (str, int)): - value = to_str(value) - if value.isdigit() or value.startswith("#"): - # we also allow setting using dbrefs, if so we try to load the matching object. - # (we assume the object is of the same type as the class holding the field, if - # not a custom handler must be used for that field) - dbid = dbref(value, reqhash=False) - if dbid: - model = _GA(cls, "_meta").get_field(fname).model - try: - value = model._default_manager.get(id=dbid) - except ObjectDoesNotExist: - # maybe it is just a name that happens to look like a dbid - pass - _SA(cls, fname, value) - # only use explicit update_fields in save if we actually have a - # primary key assigned already (won't be set when first creating object) - update_fields = ( - [fname] if _GA(cls, "_get_pk_val")(_GA(cls, "_meta")) is not None else None - ) - _GA(cls, "save")(update_fields=update_fields) - - def _del_nonedit(cls, fname): - "wrapper for not allowing deletion" - raise FieldError("Field %s cannot be edited." % fname) - - def _del(cls, fname): - "Wrapper for clearing database field - sets it to None" - _SA(cls, fname, None) - update_fields = ( - [fname] if _GA(cls, "_get_pk_val")(_GA(cls, "_meta")) is not None else None - ) - _GA(cls, "save")(update_fields=update_fields) - - # wrapper factories - if not editable: - - def fget(cls): - return _get(cls, fieldname) - - def fset(cls, val): - return _set_nonedit(cls, fieldname, val) - - elif foreignkey: - - def fget(cls): - return _get_foreign(cls, fieldname) - - def fset(cls, val): - return _set_foreign(cls, fieldname, val) - - else: - - def fget(cls): - return _get(cls, fieldname) - - def fset(cls, val): - return _set(cls, fieldname, val) - - def fdel(cls): - return _del(cls, fieldname) if editable else _del_nonedit(cls, fieldname) - - # set docstrings for auto-doc - fget.__doc__ = "A wrapper for getting database field `%s`." % fieldname - fset.__doc__ = "A wrapper for setting (and saving) database field `%s`." % fieldname - fdel.__doc__ = "A wrapper for deleting database field `%s`." % fieldname - # assigning - attrs[wrappername] = property(fget, fset, fdel) - # type(cls).__setattr__(cls, wrappername, property(fget, fset, fdel))#, doc)) - - # exclude some models that should not auto-create wrapper fields - if cls.__name__ in ("ServerConfig", "TypeNick"): - return - # dynamically create the wrapper properties for all fields not already handled - # (manytomanyfields are always handlers) - for fieldname, field in ( - (fname, field) - for fname, field in list(attrs.items()) - if fname.startswith("db_") and type(field).__name__ != "ManyToManyField" - ): - foreignkey = type(field).__name__ == "ForeignKey" - wrappername = "dbid" if fieldname == "id" else fieldname.replace("db_", "", 1) - if wrappername not in attrs: - # makes sure not to overload manually created wrappers on the model - create_wrapper( - cls, fieldname, wrappername, editable=field.editable, foreignkey=foreignkey - ) - - return super().__new__(cls, name, bases, attrs)
- - -
[docs]class SharedMemoryModel(Model, metaclass=SharedMemoryModelBase): - """ - Base class for idmapped objects. Inherit from `this`. - """ - - objects = SharedMemoryManager() - -
[docs] class Meta(object): - abstract = True
- - @classmethod - def _get_cache_key(cls, args, kwargs): - """ - This method is used by the caching subsystem to infer the PK - value from the constructor arguments. It is used to decide if - an instance has to be built or is already in the cache. - - """ - result = None - # Quick hack for my composites work for now. - if hasattr(cls._meta, "pks"): - pk = cls._meta.pks[0] - else: - pk = cls._meta.pk - # get the index of the pk in the class fields. this should be calculated *once*, but isn't atm - pk_position = cls._meta.fields.index(pk) - if len(args) > pk_position: - # if it's in the args, we can get it easily by index - result = args[pk_position] - elif pk.attname in kwargs: - # retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a - # a ForeignKey. - result = kwargs[pk.attname] - elif pk.name != pk.attname and pk.name in kwargs: - # ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead - result = kwargs[pk.name] - - if result is not None and isinstance(result, Model): - # if the pk value happens to be a model instance (which can happen wich a FK), we'd rather use its own pk as the key - result = result._get_pk_val() - return result - -
[docs] @classmethod - def get_cached_instance(cls, id): - """ - Method to retrieve a cached instance by pk value. Returns None - when not found (which will always be the case when caching is - disabled for this class). Please note that the lookup will be - done even when instance caching is disabled. - - """ - return cls.__dbclass__.__instance_cache__.get(id)
- -
[docs] @classmethod - def cache_instance(cls, instance, new=False): - """ - Method to store an instance in the cache. - - Args: - instance (Class instance): the instance to cache. - new (bool, optional): this is the first time this instance is - cached (i.e. this is not an update operation like after a - db save). - - """ - pk = instance._get_pk_val() - if pk is not None: - cls.__dbclass__.__instance_cache__[pk] = instance - if new: - try: - # trigger the at_init hook only - # at first initialization - instance.at_init() - except AttributeError: - # The at_init hook is not assigned to all entities - pass
- -
[docs] @classmethod - def get_all_cached_instances(cls): - """ - Return the objects so far cached by idmapper for this class. - - """ - return list(cls.__dbclass__.__instance_cache__.values())
- - @classmethod - def _flush_cached_by_key(cls, key, force=True): - """ - Remove the cached reference. - - """ - try: - if force or cls.at_idmapper_flush(): - del cls.__dbclass__.__instance_cache__[key] - else: - cls._dbclass__.__instance_cache__[key].refresh_from_db() - except KeyError: - # No need to remove if cache doesn't contain it already - pass - -
[docs] @classmethod - def flush_cached_instance(cls, instance, force=True): - """ - Method to flush an instance from the cache. The instance will - always be flushed from the cache, since this is most likely - called from delete(), and we want to make sure we don't cache - dead objects. - - """ - cls._flush_cached_by_key(instance._get_pk_val(), force=force)
- - # flush_cached_instance = classmethod(flush_cached_instance) - -
[docs] @classmethod - def flush_instance_cache(cls, force=False): - """ - This will clean safe objects from the cache. Use `force` - keyword to remove all objects, safe or not. - - """ - if force: - cls.__dbclass__.__instance_cache__ = {} - else: - cls.__dbclass__.__instance_cache__ = dict( - (key, obj) - for key, obj in cls.__dbclass__.__instance_cache__.items() - if not obj.at_idmapper_flush() - )
- - # flush_instance_cache = classmethod(flush_instance_cache) - - # per-instance methods - - def __eq__(self, other): - return super().__eq__(other) - - def __hash__(self): - # this is required to maintain hashing - return super().__hash__() - -
[docs] def at_idmapper_flush(self): - """ - This is called when the idmapper cache is flushed and - allows customized actions when this happens. - - Returns: - do_flush (bool): If True, flush this object as normal. If - False, don't flush and expect this object to handle - the flushing on its own. - """ - return True
- -
[docs] def flush_from_cache(self, force=False): - """ - Flush this instance from the instance cache. Use - `force` to override the result of at_idmapper_flush() for the object. - - """ - pk = self._get_pk_val() - if pk: - if force or self.at_idmapper_flush(): - self.__class__.__dbclass__.__instance_cache__.pop(pk, None)
- -
[docs] def delete(self, *args, **kwargs): - """ - Delete the object, clearing cache. - - """ - self.flush_from_cache() - self._is_deleted = True - super().delete(*args, **kwargs)
- -
[docs] def save(self, *args, **kwargs): - """ - Central database save operation. - - Notes: - Arguments as per Django documentation. - Calls `self.at_<fieldname>_postsave(new)` - (this is a wrapper set by oobhandler: - self._oob_at_<fieldname>_postsave()) - - """ - global _MONITOR_HANDLER - if not _MONITOR_HANDLER: - from evennia.scripts.monitorhandler import MONITOR_HANDLER as _MONITOR_HANDLER - - if _IS_SUBPROCESS: - # we keep a store of objects modified in subprocesses so - # we know to update their caches in the central process - global PROC_MODIFIED_COUNT, PROC_MODIFIED_OBJS - PROC_MODIFIED_COUNT += 1 - PROC_MODIFIED_OBJS[PROC_MODIFIED_COUNT] = self - - if _IS_MAIN_THREAD: - # in main thread - normal operation - try: - super().save(*args, **kwargs) - except DatabaseError: - # we handle the 'update_fields did not update any rows' error that - # may happen due to timing issues with attributes - ufields_removed = kwargs.pop("update_fields", None) - if ufields_removed: - super().save(*args, **kwargs) - else: - raise - else: - # in another thread; make sure to save in reactor thread - def _save_callback(cls, *args, **kwargs): - super().save(*args, **kwargs) - - callFromThread(_save_callback, self, *args, **kwargs) - - if not self.pk: - # this can happen if some of the startup methods immediately - # delete the object (an example are Scripts that start and die immediately) - return - - # update field-update hooks and eventual OOB watchers - new = False - if "update_fields" in kwargs and kwargs["update_fields"]: - # get field objects from their names - update_fields = ( - self._meta.get_field(fieldname) for fieldname in kwargs.get("update_fields") - ) - else: - # meta.fields are already field objects; get them all - new = True - update_fields = self._meta.fields - for field in update_fields: - fieldname = field.name - # trigger eventual monitors - _MONITOR_HANDLER.at_update(self, fieldname) - # if a hook is defined it must be named exactly on this form - hookname = "at_%s_postsave" % fieldname - if hasattr(self, hookname) and callable(_GA(self, hookname)): - _GA(self, hookname)(new) - - # # if a trackerhandler is set on this object, update it with the - # # fieldname and the new value - # fieldtracker = "_oob_at_%s_postsave" % fieldname - # if hasattr(self, fieldtracker): - # _GA(self, fieldtracker)(fieldname) - pass
- - -
[docs]class WeakSharedMemoryModelBase(SharedMemoryModelBase): - """ - Uses a WeakValue dictionary for caching instead of a regular one. - - """ - - def _prepare(cls): - super()._prepare() - cls.__dbclass__.__instance_cache__ = WeakValueDictionary()
- - -
[docs]class WeakSharedMemoryModel(SharedMemoryModel, metaclass=WeakSharedMemoryModelBase): - """ - Uses a WeakValue dictionary for caching instead of a regular one - - """ - -
[docs] class Meta(object): - abstract = True
- - -
[docs]def flush_cache(**kwargs): - """ - Flush idmapper cache. When doing so the cache will fire the - at_idmapper_flush hook to allow the object to optionally handle - its own flushing. - - Uses a signal so we make sure to catch cascades. - - """ - - def class_hierarchy(clslist): - """Recursively yield a class hierarchy""" - for cls in clslist: - subclass_list = cls.__subclasses__() - if subclass_list: - for subcls in class_hierarchy(subclass_list): - yield subcls - else: - yield cls - - for cls in class_hierarchy([SharedMemoryModel]): - cls.flush_instance_cache() - # run the python garbage collector - return gc.collect()
- - -# request_finished.connect(flush_cache) -post_migrate.connect(flush_cache) - - -
[docs]def flush_cached_instance(sender, instance, **kwargs): - """ - Flush the idmapper cache only for a given instance. - - """ - # XXX: Is this the best way to make sure we can flush? - if not hasattr(instance, "flush_cached_instance"): - return - sender.flush_cached_instance(instance, force=True)
- - -pre_delete.connect(flush_cached_instance) - - -
[docs]def update_cached_instance(sender, instance, **kwargs): - """ - Re-cache the given instance in the idmapper cache. - - """ - if not hasattr(instance, "cache_instance"): - return - sender.cache_instance(instance)
- - -post_save.connect(update_cached_instance) - - -LAST_FLUSH = None - - -
[docs]def conditional_flush(max_rmem, force=False): - """ - Flush the cache if the estimated memory usage exceeds `max_rmem`. - - The flusher has a timeout to avoid flushing over and over - in particular situations (this means that for some setups - the memory usage will exceed the requirement and a server with - more memory is probably required for the given game). - - Args: - max_rmem (int): memory-usage estimation-treshold after which - cache is flushed. - force (bool, optional): forces a flush, regardless of timeout. - Defaults to `False`. - - """ - global LAST_FLUSH - - def mem2cachesize(desired_rmem): - """ - Estimate the size of the idmapper cache based on the memory - desired. This is used to optionally cap the cache size. - - desired_rmem - memory in MB (minimum 50MB) - - The formula is empirically estimated from usage tests (Linux) - and is - Ncache = RMEM - 35.0 / 0.0157 - where RMEM is given in MB and Ncache is the size of the cache - for this memory usage. VMEM tends to be about 100MB higher - than RMEM for large memory usage. - """ - vmem = max(desired_rmem, 50.0) - Ncache = int(abs(float(vmem) - 35.0) / 0.0157) - return Ncache - - if not max_rmem: - # auto-flush is disabled - return - - now = time.time() - if not LAST_FLUSH: - # server is just starting - LAST_FLUSH = now - return - - if ((now - LAST_FLUSH) < AUTO_FLUSH_MIN_INTERVAL) and not force: - # too soon after last flush. - logger.log_warn( - "Warning: Idmapper flush called more than " - "once in %s min interval. Check memory usage." % (AUTO_FLUSH_MIN_INTERVAL / 60.0) - ) - return - - if os.name == "nt": - # we can't look for mem info in Windows at the moment - return - - # check actual memory usage - Ncache_max = mem2cachesize(max_rmem) - Ncache, _ = cache_size() - actual_rmem = ( - float(os.popen("ps -p %d -o %s | tail -1" % (os.getpid(), "rss")).read()) / 1000.0 - ) # resident memory - - if Ncache >= Ncache_max and actual_rmem > max_rmem * 0.9: - # flush cache when number of objects in cache is big enough and our - # actual memory use is within 10% of our set max - flush_cache() - LAST_FLUSH = now
- - -
[docs]def cache_size(mb=True): - """ - Calculate statistics about the cache. - - Note: we cannot get reliable memory statistics from the cache - - whereas we could do `getsizof` each object in cache, the result is - highly imprecise and for a large number of objects the result is - many times larger than the actual memory usage of the entire server; - Python is clearly reusing memory behind the scenes that we cannot - catch in an easy way here. Ideas are appreciated. /Griatch - - Returns: - total_num, {objclass:total_num, ...} - - """ - numtotal = [0] # use mutable to keep reference through recursion - classdict = {} - - def get_recurse(submodels): - for submodel in submodels: - subclasses = submodel.__subclasses__() - if not subclasses: - num = len(submodel.get_all_cached_instances()) - numtotal[0] += num - classdict[submodel.__dbclass__.__name__] = num - else: - get_recurse(subclasses) - - get_recurse(SharedMemoryModel.__subclasses__()) - return numtotal[0], classdict
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/idmapper/tests.html b/docs/0.9.5/_modules/evennia/utils/idmapper/tests.html deleted file mode 100644 index 7a14dfe0ca..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/idmapper/tests.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - evennia.utils.idmapper.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.idmapper.tests

-from django.test import TestCase
-
-from .models import SharedMemoryModel
-from django.db import models
-
-
-
[docs]class Category(SharedMemoryModel): - name = models.CharField(max_length=32)
- - -
[docs]class RegularCategory(models.Model): - name = models.CharField(max_length=32)
- - -
[docs]class Article(SharedMemoryModel): - name = models.CharField(max_length=32) - category = models.ForeignKey(Category, on_delete=models.CASCADE) - category2 = models.ForeignKey(RegularCategory, on_delete=models.CASCADE)
- - -
[docs]class RegularArticle(models.Model): - name = models.CharField(max_length=32) - category = models.ForeignKey(Category, on_delete=models.CASCADE) - category2 = models.ForeignKey(RegularCategory, on_delete=models.CASCADE)
- - -
[docs]class SharedMemorysTest(TestCase): - # TODO: test for cross model relation (singleton to regular) - -
[docs] def setUp(self): - super().setUp() - n = 0 - category = Category.objects.create(name="Category %d" % (n,)) - regcategory = RegularCategory.objects.create(name="Category %d" % (n,)) - - for n in range(0, 10): - Article.objects.create( - name="Article %d" % (n,), category=category, category2=regcategory - ) - RegularArticle.objects.create( - name="Article %d" % (n,), category=category, category2=regcategory - )
- -
[docs] def testSharedMemoryReferences(self): - article_list = Article.objects.all().select_related("category") - last_article = article_list[0] - for article in article_list[1:]: - self.assertEqual(article.category is last_article.category, True) - last_article = article
- -
[docs] def testRegularReferences(self): - article_list = RegularArticle.objects.all().select_related("category") - last_article = article_list[0] - for article in article_list[1:]: - self.assertEqual(article.category2 is last_article.category2, False) - last_article = article
- -
[docs] def testMixedReferences(self): - article_list = RegularArticle.objects.all().select_related("category") - last_article = article_list[0] - for article in article_list[1:]: - self.assertEqual(article.category is last_article.category, True) - last_article = article
- - # article_list = Article.objects.all().select_related('category') - # last_article = article_list[0] - # for article in article_list[1:]: - # self.assertEquals(article.category2 is last_article.category2, False) - # last_article = article - -
[docs] def testObjectDeletion(self): - # This must execute first so its guaranteed to be in memory. - list(Article.objects.all().select_related("category")) - - article = Article.objects.all()[0:1].get() - pk = article.pk - article.delete() - self.assertEqual(pk not in Article.__instance_cache__, True)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/inlinefuncs.html b/docs/0.9.5/_modules/evennia/utils/inlinefuncs.html deleted file mode 100644 index 2cabef803a..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/inlinefuncs.html +++ /dev/null @@ -1,728 +0,0 @@ - - - - - - - - evennia.utils.inlinefuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.inlinefuncs

-"""
-Inline functions (nested form).
-
-This parser accepts nested inlinefunctions on the form
-::
-
-    $funcname(arg, arg, ...)
-
-embedded in any text where any arg can be another `$funcname{}` call.
-This functionality is turned off by default - to activate,
-`settings.INLINEFUNC_ENABLED` must be set to `True`.
-
-Each token starts with `$funcname(` where there must be no space between the
-$funcname and "(". It ends with a matched ending parentesis ")".
-
-Inside the inlinefunc definition, one can use \\\\ to escape. This is
-mainly needed for escaping commas in flowing text (which would
-otherwise be interpreted as an argument separator), or to escape `}`
-when not intended to close the function block. Enclosing text in
-matched `\"\"\"` (triple quotes) or `'''` (triple single-quotes) will
-also escape *everything* within without needing to escape individual
-characters.
-
-The available inlinefuncs are defined as global-level functions in
-modules defined by `settings.INLINEFUNC_MODULES`. They are identified
-by their function name (and ignored if this name starts with `_`). They
-should be on the following form:
-::
-
-    def funcname (*args, **kwargs):
-    # ...
-
-Here, the arguments given to `$funcname(arg1,arg2)` will appear as the
-`*args` tuple. This will be populated by the arguments given to the
-inlinefunc in-game - the only part that will be available from
-in-game. `**kwargs` are not supported from in-game but are only used
-internally by Evennia to make details about the caller available to
-the function. The kwarg passed to all functions is `session`, the
-Sessionobject for the object seeing the string. This may be `None` if
-the string is sent to a non-puppetable object. The inlinefunc should
-never raise an exception.
-
-There are two reserved function names:
-
-- "nomatch": This is called if the user uses a functionname that is
-  not registered. The nomatch function will get the name of the
-  not-found function as its first argument followed by the normal
-  arguments to the given function. If not defined the default effect is
-  to print `<UNKNOWN>` to replace the unknown function.
-- "stackfull": This is called when the maximum nested function stack is reached.
-  When this happens, the original parsed string is returned and the result of
-  the `stackfull` inlinefunc is appended to the end. By default this is an
-  error message.
-
-Syntax errors, notably not completely closing all inlinefunc blocks, will lead
-to the entire string remaining unparsed.
-
-----
-
-"""
-
-import re
-import fnmatch
-import random as base_random
-from django.conf import settings
-
-from evennia.utils import utils, logger
-
-# The stack size is a security measure. Set to <=0 to disable.
-_STACK_MAXSIZE = settings.INLINEFUNC_STACK_MAXSIZE
-
-
-# example/testing inline functions
-
-
-
[docs]def random(*args, **kwargs): - """ - Inlinefunc. Returns a random number between - 0 and 1, from 0 to a maximum value, or within a given range (inclusive). - - Args: - minval (str, optional): Minimum value. If not given, assumed 0. - maxval (str, optional): Maximum value. - - Keyword argumuents: - session (Session): Session getting the string. - - Notes: - If either of the min/maxvalue has a '.' in it, a floating-point random - value will be returned. Otherwise it will be an integer value in the - given range. - - Example: - - - `$random()` - - `$random(5)` - - `$random(5, 10)` - - """ - nargs = len(args) - if nargs == 1: - # only maxval given - minval, maxval = "0", args[0] - elif nargs > 1: - minval, maxval = args[:2] - else: - minval, maxval = ("0", "1") - - if "." in minval or "." in maxval: - # float mode - try: - minval, maxval = float(minval), float(maxval) - except ValueError: - minval, maxval = 0, 1 - return "{:.2f}".format(minval + maxval * base_random.random()) - else: - # int mode - try: - minval, maxval = int(minval), int(maxval) - except ValueError: - minval, maxval = 0, 1 - return str(base_random.randint(minval, maxval))
- - -
[docs]def pad(*args, **kwargs): - """ - Inlinefunc. Pads text to given width. - - Args: - text (str, optional): Text to pad. - width (str, optional): Will be converted to integer. Width - of padding. - align (str, optional): Alignment of padding; one of 'c', 'l' or 'r'. - fillchar (str, optional): Character used for padding. Defaults to a - space. - - Keyword Args: - session (Session): Session performing the pad. - - Example: - `$pad(text, width, align, fillchar)` - - """ - text, width, align, fillchar = "", 78, "c", " " - nargs = len(args) - if nargs > 0: - text = args[0] - if nargs > 1: - width = int(args[1]) if args[1].strip().isdigit() else 78 - if nargs > 2: - align = args[2] if args[2] in ("c", "l", "r") else "c" - if nargs > 3: - fillchar = args[3] - return utils.pad(text, width=width, align=align, fillchar=fillchar)
- - -
[docs]def crop(*args, **kwargs): - """ - Inlinefunc. Crops ingoing text to given widths. - - Args: - text (str, optional): Text to crop. - width (str, optional): Will be converted to an integer. Width of - crop in characters. - suffix (str, optional): End string to mark the fact that a part - of the string was cropped. Defaults to `[...]`. - Keyword Args: - session (Session): Session performing the crop. - - Example: - `$crop(text, width=78, suffix='[...]')` - - """ - text, width, suffix = "", 78, "[...]" - nargs = len(args) - if nargs > 0: - text = args[0] - if nargs > 1: - width = int(args[1]) if args[1].strip().isdigit() else 78 - if nargs > 2: - suffix = args[2] - return utils.crop(text, width=width, suffix=suffix)
- - -
[docs]def space(*args, **kwargs): - """ - Inlinefunc. Inserts an arbitrary number of spaces. Defaults to 4 spaces. - - Args: - spaces (int, optional): The number of spaces to insert. - - Keyword Args: - session (Session): Session performing the crop. - - Example: - `$space(20)` - - """ - width = 4 - if args: - width = abs(int(args[0])) if args[0].strip().isdigit() else 4 - return " " * width
- - -
[docs]def clr(*args, **kwargs): - """ - Inlinefunc. Colorizes nested text. - - Args: - startclr (str, optional): An ANSI color abbreviation without the - prefix `|`, such as `r` (red foreground) or `[r` (red background). - text (str, optional): Text - endclr (str, optional): The color to use at the end of the string. Defaults - to `|n` (reset-color). - Keyword Args: - session (Session): Session object triggering inlinefunc. - - Example: - `$clr(startclr, text, endclr)` - - """ - text = "" - nargs = len(args) - if nargs > 0: - color = args[0].strip() - if nargs > 1: - text = args[1] - text = "|" + color + text - if nargs > 2: - text += "|" + args[2].strip() - else: - text += "|n" - return text
- - -
[docs]def null(*args, **kwargs): - return args[0] if args else ""
- - -
[docs]def nomatch(name, *args, **kwargs): - """ - Default implementation of nomatch returns the function as-is as a string. - - """ - kwargs.pop("inlinefunc_stack_depth", None) - kwargs.pop("session") - - return "${name}({args}{kwargs})".format( - name=name, - args=",".join(args), - kwargs=",".join("{}={}".format(key, val) for key, val in kwargs.items()), - )
- - -_INLINE_FUNCS = {} - -# we specify a default nomatch function to use if no matching func was -# found. This will be overloaded by any nomatch function defined in -# the imported modules. -_DEFAULT_FUNCS = { - "nomatch": lambda *args, **kwargs: "<UNKNOWN>", - "stackfull": lambda *args, **kwargs: "\n (not parsed: ", -} - -_INLINE_FUNCS.update(_DEFAULT_FUNCS) - -# load custom inline func modules. -for module in utils.make_iter(settings.INLINEFUNC_MODULES): - try: - _INLINE_FUNCS.update(utils.callables_from_module(module)) - except ImportError as err: - if module == "server.conf.inlinefuncs": - # a temporary warning since the default module changed name - raise ImportError( - "Error: %s\nPossible reason: mygame/server/conf/inlinefunc.py should " - "be renamed to mygame/server/conf/inlinefuncs.py (note " - "the S at the end)." % err - ) - else: - raise - - -# regex definitions - -_RE_STARTTOKEN = re.compile(r"(?<!\\)\$(\w+)\(") # unescaped $funcname( (start of function call) - -# note: this regex can be experimented with at https://regex101.com/r/kGR3vE/2 -_RE_TOKEN = re.compile( - r""" - (?<!\\)\'\'\'(?P<singlequote>.*?)(?<!\\)\'\'\'| # single-triplets escape all inside - (?<!\\)\"\"\"(?P<doublequote>.*?)(?<!\\)\"\"\"| # double-triplets escape all inside - (?P<comma>(?<!\\)\,)| # , (argument sep) - (?P<end>(?<!\\)\))| # ) (possible end of func call) - (?P<leftparens>(?<!\\)\()| # ( (lone left-parens) - (?P<start>(?<!\\)\$\w+\()| # $funcname (start of func call) - (?P<escaped> # escaped tokens to re-insert sans backslash - \\\'|\\\"|\\\)|\\\$\w+\(|\\\()| - (?P<rest> # everything else to re-insert verbatim - \$(?!\w+\()|\'|\"|\\|[^),$\'\"\\\(]+)""", - re.UNICODE | re.IGNORECASE | re.VERBOSE | re.DOTALL, -) - -# Cache for function lookups. -_PARSING_CACHE = utils.LimitedSizeOrderedDict(size_limit=1000) - - -
[docs]class ParseStack(list): - """ - Custom stack that always concatenates strings together when the - strings are added next to one another. Tuples are stored - separately and None is used to mark that a string should be broken - up into a new chunk. Below is the resulting stack after separately - appending 3 strings, None, 2 strings, a tuple and finally 2 - strings: - - [string + string + string, - None - string + string, - tuple, - string + string] - - """ - -
[docs] def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # always start stack with the empty string - list.append(self, "") - # indicates if the top of the stack is a string or not - self._string_last = True
- - def __eq__(self, other): - return ( - super().__eq__(other) - and hasattr(other, "_string_last") - and self._string_last == other._string_last - ) - - def __ne__(self, other): - return not self.__eq__(other) - -
[docs] def append(self, item): - """ - The stack will merge strings, add other things as normal - """ - if isinstance(item, str): - if self._string_last: - self[-1] += item - else: - list.append(self, item) - self._string_last = True - else: - # everything else is added as normal - list.append(self, item) - self._string_last = False
- - -
[docs]class InlinefuncError(RuntimeError): - pass
- - -
[docs]def parse_inlinefunc(string, strip=False, available_funcs=None, stacktrace=False, **kwargs): - """ - Parse the incoming string. - - Args: - string (str): The incoming string to parse. - strip (bool, optional): Whether to strip function calls rather than - execute them. - available_funcs (dict, optional): Define an alternative source of functions to parse for. - If unset, use the functions found through `settings.INLINEFUNC_MODULES`. - stacktrace (bool, optional): If set, print the stacktrace to log. - Keyword Args: - session (Session): This is sent to this function by Evennia when triggering - it. It is passed to the inlinefunc. - kwargs (any): All other kwargs are also passed on to the inlinefunc. - - - """ - global _PARSING_CACHE - usecache = False - if not available_funcs: - available_funcs = _INLINE_FUNCS - usecache = True - else: - # make sure the default keys are available, but also allow overriding - tmp = _DEFAULT_FUNCS.copy() - tmp.update(available_funcs) - available_funcs = tmp - - if usecache and string in _PARSING_CACHE: - # stack is already cached - stack = _PARSING_CACHE[string] - elif not _RE_STARTTOKEN.search(string): - # if there are no unescaped start tokens at all, return immediately. - return string - else: - # no cached stack; build a new stack and continue - stack = ParseStack() - - # process string on stack - ncallable = 0 - nlparens = 0 - nvalid = 0 - - if stacktrace: - out = "STRING: {} =>".format(string) - print(out) - logger.log_info(out) - - for match in _RE_TOKEN.finditer(string): - gdict = match.groupdict() - - if stacktrace: - out = " MATCH: {}".format({key: val for key, val in gdict.items() if val}) - print(out) - logger.log_info(out) - - if gdict["singlequote"]: - stack.append(gdict["singlequote"]) - elif gdict["doublequote"]: - stack.append(gdict["doublequote"]) - elif gdict["leftparens"]: - # we have a left-parens inside a callable - if ncallable: - nlparens += 1 - stack.append("(") - elif gdict["end"]: - if nlparens > 0: - nlparens -= 1 - stack.append(")") - continue - if ncallable <= 0: - stack.append(")") - continue - args = [] - while stack: - operation = stack.pop() - if callable(operation): - if not strip: - stack.append((operation, [arg for arg in reversed(args)])) - ncallable -= 1 - break - else: - args.append(operation) - elif gdict["start"]: - funcname = _RE_STARTTOKEN.match(gdict["start"]).group(1) - try: - # try to fetch the matching inlinefunc from storage - stack.append(available_funcs[funcname]) - nvalid += 1 - except KeyError: - stack.append(available_funcs["nomatch"]) - stack.append(funcname) - stack.append(None) - ncallable += 1 - elif gdict["escaped"]: - # escaped tokens - token = gdict["escaped"].lstrip("\\") - stack.append(token) - elif gdict["comma"]: - if ncallable > 0: - # commas outside strings and inside a callable are - # used to mark argument separation - we use None - # in the stack to indicate such a separation. - stack.append(None) - else: - # no callable active - just a string - stack.append(",") - else: - # the rest - stack.append(gdict["rest"]) - - if ncallable > 0: - # this means not all inlinefuncs were complete - return string - - if _STACK_MAXSIZE > 0 and _STACK_MAXSIZE < nvalid: - # if stack is larger than limit, throw away parsing - return string + available_funcs["stackfull"](*args, **kwargs) - elif usecache: - # cache the stack - we do this also if we don't check the cache above - _PARSING_CACHE[string] = stack - - # run the stack recursively - def _run_stack(item, depth=0): - retval = item - if isinstance(item, tuple): - if strip: - return "" - else: - func, arglist = item - args = [""] - for arg in arglist: - if arg is None: - # an argument-separating comma - start a new arg - args.append("") - else: - # all other args should merge into one string - args[-1] += _run_stack(arg, depth=depth + 1) - # execute the inlinefunc at this point or strip it. - kwargs["inlinefunc_stack_depth"] = depth - retval = "" if strip else func(*args, **kwargs) - return utils.to_str(retval) - - retval = "".join(_run_stack(item) for item in stack) - if stacktrace: - out = "STACK: \n{} => {}\n".format(stack, retval) - print(out) - logger.log_info(out) - - # execute the stack - return retval
- - -
[docs]def raw(string): - """ - Escape all inlinefuncs in a string so they won't get parsed. - - Args: - string (str): String with inlinefuncs to escape. - """ - - def _escape(match): - return "\\" + match.group(0) - - return _RE_STARTTOKEN.sub(_escape, string)
- - -# -# Nick templating -# - - -""" -This supports the use of replacement templates in nicks: - -This happens in two steps: - -1) The user supplies a template that is converted to a regex according - to the unix-like templating language. -2) This regex is tested against nicks depending on which nick replacement - strategy is considered (most commonly inputline). -3) If there is a template match and there are templating markers, - these are replaced with the arguments actually given. - -@desc $1 $2 $3 - -This will be converted to the following regex: - -\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+) - -Supported template markers (through fnmatch) - * matches anything (non-greedy) -> .*? - ? matches any single character -> - [seq] matches any entry in sequence - [!seq] matches entries not in sequence -Custom arg markers - $N argument position (1-99) - -""" -_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)") -_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)") -_RE_NICK_SPACE = re.compile(r"\\ ") - - -
[docs]class NickTemplateInvalid(ValueError): - pass
- - -
[docs]def initialize_nick_templates(in_template, out_template): - """ - Initialize the nick templates for matching and remapping a string. - - Args: - in_template (str): The template to be used for nick recognition. - out_template (str): The template to be used to replace the string - matched by the `in_template`. - - Returns: - regex, template (regex, str): Regex to match against strings and a - template with markers `{arg1}`, `{arg2}`, etc for replacement using the - standard `.format` method. - - Raises: - inlinefuncs.NickTemplateInvalid: If the in/out template does not have a matching - number of $args. - - """ - # create the regex for in_template - regex_string = fnmatch.translate(in_template) - n_inargs = len(_RE_NICK_ARG.findall(regex_string)) - regex_string = _RE_NICK_SPACE.sub("\s+", regex_string) - regex_string = _RE_NICK_ARG.sub(lambda m: "(?P<arg%s>.+?)" % m.group(2), regex_string) - - # create the out_template - template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template) - - # validate the tempaltes - they should at least have the same number of args - n_outargs = len(_RE_NICK_TEMPLATE_ARG.findall(out_template)) - if n_inargs != n_outargs: - raise NickTemplateInvalid - - return re.compile(regex_string), template_string
- - -
[docs]def parse_nick_template(string, template_regex, outtemplate): - """ - Parse a text using a template and map it to another template - - Args: - string (str): The input string to processj - template_regex (regex): A template regex created with - initialize_nick_template. - outtemplate (str): The template to which to map the matches - produced by the template_regex. This should have $1, $2, - etc to match the regex. - - """ - match = template_regex.match(string) - if match: - return outtemplate.format(**match.groupdict()) - return string
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/logger.html b/docs/0.9.5/_modules/evennia/utils/logger.html deleted file mode 100644 index efafd3cf7d..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/logger.html +++ /dev/null @@ -1,649 +0,0 @@ - - - - - - - - evennia.utils.logger — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.logger

-"""
-Logging facilities
-
-These are thin wrappers on top of Twisted's logging facilities; logs
-are all directed either to stdout (if Evennia is running in
-interactive mode) or to $GAME_DIR/server/logs.
-
-The log_file() function uses its own threading system to log to
-arbitrary files in $GAME_DIR/server/logs.
-
-Note: All logging functions have two aliases, log_type() and
-log_typemsg(). This is for historical, back-compatible reasons.
-
-"""
-
-
-import os
-import time
-import glob
-from datetime import datetime
-from traceback import format_exc
-from twisted.python import log, logfile
-from twisted.python import util as twisted_util
-from twisted.internet.threads import deferToThread
-
-
-_LOGDIR = None
-_LOG_ROTATE_SIZE = None
-_TIMEZONE = None
-_CHANNEL_LOG_NUM_TAIL_LINES = None
-
-
-# logging overrides
-
-
-
[docs]def timeformat(when=None): - """ - This helper function will format the current time in the same - way as the twisted logger does, including time zone info. Only - difference from official logger is that we only use two digits - for the year and don't show timezone for CET times. - - Args: - when (int, optional): This is a time in POSIX seconds on the form - given by time.time(). If not given, this function will - use the current time. - - Returns: - timestring (str): A formatted string of the given time. - """ - when = when if when else time.time() - - # time zone offset: UTC - the actual offset - tz_offset = datetime.utcfromtimestamp(when) - datetime.fromtimestamp(when) - tz_offset = tz_offset.days * 86400 + tz_offset.seconds - # correct given time to utc - when = datetime.utcfromtimestamp(when - tz_offset) - - if tz_offset == 0: - tz = "" - else: - tz_hour = abs(int(tz_offset // 3600)) - tz_mins = abs(int(tz_offset // 60 % 60)) - tz_sign = "-" if tz_offset >= 0 else "+" - tz = "%s%02d%s" % (tz_sign, tz_hour, (":%02d" % tz_mins if tz_mins else "")) - - return "%d-%02d-%02d %02d:%02d:%02d%s" % ( - when.year - 2000, - when.month, - when.day, - when.hour, - when.minute, - when.second, - tz, - )
- - -
[docs]class WeeklyLogFile(logfile.DailyLogFile): - """ - Log file that rotates once per week by default. Overrides key methods to change format. - - """ - -
[docs] def __init__(self, name, directory, defaultMode=None, day_rotation=7, max_size=1000000): - """ - Args: - name (str): Name of log file. - directory (str): Directory holding the file. - defaultMode (str): Permissions used to create file. Defaults to - current permissions of this file if it exists. - day_rotation (int): How often to rotate the file. - max_size (int): Max size of log file before rotation (regardless of - time). Defaults to 1M. - - """ - self.day_rotation = day_rotation - self.max_size = max_size - self.size = 0 - logfile.DailyLogFile.__init__(self, name, directory, defaultMode=defaultMode)
- - def _openFile(self): - logfile.DailyLogFile._openFile(self) - self.size = self._file.tell() - -
[docs] def shouldRotate(self): - """Rotate when the date has changed since last write""" - # all dates here are tuples (year, month, day) - now = self.toDate() - then = self.lastDate - return ( - now[0] > then[0] - or now[1] > then[1] - or now[2] > (then[2] + self.day_rotation) - or self.size >= self.max_size - )
- -
[docs] def suffix(self, tupledate): - """Return the suffix given a (year, month, day) tuple or unixtime. - Format changed to have 03 for march instead of 3 etc (retaining unix - file order) - - If we get duplicate suffixes in location (due to hitting size limit), - we append __1, __2 etc. - - Examples: - server.log.2020_01_29 - server.log.2020_01_29__1 - server.log.2020_01_29__2 - """ - suffix = "" - copy_suffix = 0 - while True: - try: - suffix = "_".join(["{:02d}".format(part) for part in tupledate]) - except Exception: - # try taking a float unixtime - suffix = "_".join(["{:02d}".format(part) for part in self.toDate(tupledate)]) - - suffix += f"__{copy_suffix}" if copy_suffix else "" - - if os.path.exists(f"{self.path}.{suffix}"): - # Append a higher copy_suffix to try to break the tie (starting from 2) - copy_suffix += 1 - else: - break - return suffix
- -
[docs] def write(self, data): - "Write data to log file" - logfile.BaseLogFile.write(self, data) - self.lastDate = max(self.lastDate, self.toDate()) - self.size += len(data)
- - -
[docs]class PortalLogObserver(log.FileLogObserver): - """ - Reformat logging - """ - - timeFormat = None - prefix = " |Portal| " - -
[docs] def emit(self, eventDict): - """ - Copied from Twisted parent, to change logging output - - """ - text = log.textFromEventDict(eventDict) - if text is None: - return - - # timeStr = self.formatTime(eventDict["time"]) - timeStr = timeformat(eventDict["time"]) - fmtDict = {"text": text.replace("\n", "\n\t")} - - msgStr = log._safeFormat("%(text)s\n", fmtDict) - - twisted_util.untilConcludes(self.write, timeStr + "%s" % self.prefix + msgStr) - twisted_util.untilConcludes(self.flush)
- - -
[docs]class ServerLogObserver(PortalLogObserver): - prefix = " "
- - -
[docs]def log_msg(msg): - """ - Wrapper around log.msg call to catch any exceptions that might - occur in logging. If an exception is raised, we'll print to - stdout instead. - - Args: - msg: The message that was passed to log.msg - - """ - try: - log.msg(msg) - except Exception: - print("Exception raised while writing message to log. Original message: %s" % msg)
- - -
[docs]def log_trace(errmsg=None): - """ - Log a traceback to the log. This should be called from within an - exception. - - Args: - errmsg (str, optional): Adds an extra line with added info - at the end of the traceback in the log. - - """ - tracestring = format_exc() - try: - if tracestring: - for line in tracestring.splitlines(): - log.msg("[::] %s" % line) - if errmsg: - try: - errmsg = str(errmsg) - except Exception as e: - errmsg = str(e) - for line in errmsg.splitlines(): - log_msg("[EE] %s" % line) - except Exception: - log_msg("[EE] %s" % errmsg)
- - -log_tracemsg = log_trace - - -
[docs]def log_err(errmsg): - """ - Prints/logs an error message to the server log. - - Args: - errmsg (str): The message to be logged. - - """ - try: - errmsg = str(errmsg) - except Exception as e: - errmsg = str(e) - for line in errmsg.splitlines(): - log_msg("[EE] %s" % line)
- - # log.err('ERROR: %s' % (errmsg,)) - - -log_errmsg = log_err - - -
[docs]def log_server(servermsg): - """ - This is for the Portal to log captured Server stdout messages (it's - usually only used during startup, before Server log is open) - - """ - try: - servermsg = str(servermsg) - except Exception as e: - servermsg = str(e) - for line in servermsg.splitlines(): - log_msg("[Server] %s" % line)
- - -
[docs]def log_warn(warnmsg): - """ - Prints/logs any warnings that aren't critical but should be noted. - - Args: - warnmsg (str): The message to be logged. - - """ - try: - warnmsg = str(warnmsg) - except Exception as e: - warnmsg = str(e) - for line in warnmsg.splitlines(): - log_msg("[WW] %s" % line)
- - # log.msg('WARNING: %s' % (warnmsg,)) - - -log_warnmsg = log_warn - - -
[docs]def log_info(infomsg): - """ - Prints any generic debugging/informative info that should appear in the log. - - infomsg: (string) The message to be logged. - """ - try: - infomsg = str(infomsg) - except Exception as e: - infomsg = str(e) - for line in infomsg.splitlines(): - log_msg("[..] %s" % line)
- - -log_infomsg = log_info - - -
[docs]def log_dep(depmsg): - """ - Prints a deprecation message. - - Args: - depmsg (str): The deprecation message to log. - """ - try: - depmsg = str(depmsg) - except Exception as e: - depmsg = str(e) - for line in depmsg.splitlines(): - log_msg("[DP] %s" % line)
- - -log_depmsg = log_dep - - -
[docs]def log_sec(secmsg): - """ - Prints a security-related message. - - Args: - secmsg (str): The security message to log. - """ - try: - secmsg = str(secmsg) - except Exception as e: - secmsg = str(e) - for line in secmsg.splitlines(): - log_msg("[SS] %s" % line)
- - -log_secmsg = log_sec - - -# Arbitrary file logger - - -
[docs]class EvenniaLogFile(logfile.LogFile): - """ - A rotating logfile based off Twisted's LogFile. It overrides - the LogFile's rotate method in order to append some of the last - lines of the previous log to the start of the new log, in order - to preserve a continuous chat history for channel log files. - """ - - # we delay import of settings to keep logger module as free - # from django as possible. - global _CHANNEL_LOG_NUM_TAIL_LINES - if _CHANNEL_LOG_NUM_TAIL_LINES is None: - from django.conf import settings - - _CHANNEL_LOG_NUM_TAIL_LINES = settings.CHANNEL_LOG_NUM_TAIL_LINES - num_lines_to_append = _CHANNEL_LOG_NUM_TAIL_LINES - -
[docs] def rotate(self): - """ - Rotates our log file and appends some number of lines from - the previous log to the start of the new one. - """ - append_tail = self.num_lines_to_append > 0 - if not append_tail: - logfile.LogFile.rotate(self) - return - lines = tail_log_file(self.path, 0, self.num_lines_to_append) - super().rotate() - for line in lines: - self.write(line)
- -
[docs] def seek(self, *args, **kwargs): - """ - Convenience method for accessing our _file attribute's seek method, - which is used in tail_log_function. - Args: - *args: Same args as file.seek - **kwargs: Same kwargs as file.seek - """ - return self._file.seek(*args, **kwargs)
- -
[docs] def readlines(self, *args, **kwargs): - """ - Convenience method for accessing our _file attribute's readlines method, - which is used in tail_log_function. - Args: - *args: same args as file.readlines - **kwargs: same kwargs as file.readlines - - Returns: - lines (list): lines from our _file attribute. - """ - lines = [] - for line in self._file.readlines(*args, **kwargs): - try: - lines.append(line.decode("utf-8")) - except UnicodeDecodeError: - try: - lines.append(str(line)) - except Exception: - lines.append("") - return lines
- - -_LOG_FILE_HANDLES = {} # holds open log handles -_LOG_FILE_HANDLE_COUNTS = {} -_LOG_FILE_HANDLE_RESET = 500 - - -def _open_log_file(filename): - """ - Helper to open the log file (always in the log dir) and cache its - handle. Will create a new file in the log dir if one didn't - exist. - - To avoid keeping the filehandle open indefinitely we reset it every - _LOG_FILE_HANDLE_RESET accesses. This may help resolve issues for very - long uptimes and heavy log use. - - """ - # we delay import of settings to keep logger module as free - # from django as possible. - global _LOG_FILE_HANDLES, _LOG_FILE_HANDLE_COUNTS, _LOGDIR, _LOG_ROTATE_SIZE - if not _LOGDIR: - from django.conf import settings - - _LOGDIR = settings.LOG_DIR - _LOG_ROTATE_SIZE = settings.CHANNEL_LOG_ROTATE_SIZE - - filename = os.path.join(_LOGDIR, filename) - if filename in _LOG_FILE_HANDLES: - _LOG_FILE_HANDLE_COUNTS[filename] += 1 - if _LOG_FILE_HANDLE_COUNTS[filename] > _LOG_FILE_HANDLE_RESET: - # close/refresh handle - _LOG_FILE_HANDLES[filename].close() - del _LOG_FILE_HANDLES[filename] - else: - # return cached handle - return _LOG_FILE_HANDLES[filename] - try: - filehandle = EvenniaLogFile.fromFullPath(filename, rotateLength=_LOG_ROTATE_SIZE) - # filehandle = open(filename, "a+") # append mode + reading - _LOG_FILE_HANDLES[filename] = filehandle - _LOG_FILE_HANDLE_COUNTS[filename] = 0 - return filehandle - except IOError: - log_trace() - return None - - -
[docs]def log_file(msg, filename="game.log"): - """ - Arbitrary file logger using threads. - - Args: - msg (str): String to append to logfile. - filename (str, optional): Defaults to 'game.log'. All logs - will appear in the logs directory and log entries will start - on new lines following datetime info. - - """ - - def callback(filehandle, msg): - """Writing to file and flushing result""" - msg = "\n%s [-] %s" % (timeformat(), msg.strip()) - filehandle.write(msg) - # since we don't close the handle, we need to flush - # manually or log file won't be written to until the - # write buffer is full. - filehandle.flush() - - def errback(failure): - """Catching errors to normal log""" - log_trace() - - # save to server/logs/ directory - filehandle = _open_log_file(filename) - if filehandle: - deferToThread(callback, filehandle, msg).addErrback(errback)
- - -
[docs]def tail_log_file(filename, offset, nlines, callback=None): - """ - Return the tail of the log file. - - Args: - filename (str): The name of the log file, presumed to be in - the Evennia log dir. - offset (int): The line offset *from the end of the file* to start - reading from. 0 means to start at the latest entry. - nlines (int): How many lines to return, counting backwards - from the offset. If file is shorter, will get all lines. - callback (callable, optional): A function to manage the result of the - asynchronous file access. This will get a list of lines. If unset, - the tail will happen synchronously. - - Returns: - lines (deferred or list): This will be a deferred if `callable` is given, - otherwise it will be a list with The nline entries from the end of the file, or - all if the file is shorter than nlines. - - """ - - def seek_file(filehandle, offset, nlines, callback): - """step backwards in chunks and stop only when we have enough lines""" - lines_found = [] - buffer_size = 4098 - block_count = -1 - while len(lines_found) < (offset + nlines): - try: - # scan backwards in file, starting from the end - filehandle.seek(block_count * buffer_size, os.SEEK_END) - except IOError: - # file too small for this seek, take what we've got - filehandle.seek(0) - lines_found = filehandle.readlines() - break - lines_found = filehandle.readlines() - block_count -= 1 - # return the right number of lines - lines_found = lines_found[-nlines - offset: -offset if offset else None] - if callback: - callback(lines_found) - return None - else: - return lines_found - - def errback(failure): - """Catching errors to normal log""" - log_trace() - - filehandle = _open_log_file(filename) - if filehandle: - if callback: - return deferToThread(seek_file, filehandle, offset, nlines, callback).addErrback( - errback - ) - else: - return seek_file(filehandle, offset, nlines, callback) - else: - return None
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/optionclasses.html b/docs/0.9.5/_modules/evennia/utils/optionclasses.html deleted file mode 100644 index 76a3b5562c..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/optionclasses.html +++ /dev/null @@ -1,435 +0,0 @@ - - - - - - - - evennia.utils.optionclasses — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.optionclasses

-"""
-Option classes store user- or server Options in a generic way
-while also providing validation.
-
-"""
-
-import datetime
-from evennia import logger
-from evennia.utils.ansi import strip_ansi
-from evennia.utils.validatorfuncs import _TZ_DICT
-from evennia.utils.utils import crop
-from evennia.utils import validatorfuncs
-
-
-
[docs]class BaseOption: - """ - Abstract Class to deal with encapsulating individual Options. An Option has - a name/key, a description to display in relevant commands and menus, and a - default value. It saves to the owner's Attributes using its Handler's save - category. - - Designed to be extremely overloadable as some options can be cantankerous. - - Properties: - valid: Shortcut to the loaded VALID_HANDLER. - validator_key (str): The key of the Validator this uses. - - """ - - def __str__(self): - return "<Option {key}: {value}>".format(key=self.key, value=crop(str(self.value), width=10)) - - def __repr__(self): - return str(self) - -
[docs] def __init__(self, handler, key, description, default): - """ - - Args: - handler (OptionHandler): The OptionHandler that 'owns' this Option. - key (str): The name this will be used for storage in a dictionary. - Must be unique per OptionHandler. - description (str): What this Option's text will show in commands and menus. - default: A default value for this Option. - - """ - self.handler = handler - self.key = key - self.default_value = default - self.description = description - - # Value Storage contains None until the Option is loaded. - self.value_storage = None - - # And it's not loaded until it's called upon to spit out its contents. - self.loaded = False
- - @property - def changed(self): - return self.value_storage != self.default_value - - @property - def default(self): - return self.default_value - - @property - def value(self): - if not self.loaded: - self.load() - if self.loaded: - return self.value_storage - else: - return self.default - - @value.setter - def value(self, value): - self.set(value) - -
[docs] def set(self, value, **kwargs): - """ - Takes user input and stores appropriately. This method allows for - passing extra instructions into the validator. - - Args: - value (str): The new value of this Option. - kwargs (any): Any kwargs will be passed into - `self.validate(value, **kwargs)` and `self.save(**kwargs)`. - - """ - final_value = self.validate(value, **kwargs) - self.value_storage = final_value - self.loaded = True - self.save(**kwargs)
- -
[docs] def load(self): - """ - Takes the provided save data, validates it, and gets this Option ready to use. - - Returns: - Boolean: Whether loading was successful. - - """ - loadfunc = self.handler.loadfunc - load_kwargs = self.handler.load_kwargs - - try: - self.value_storage = self.deserialize( - loadfunc(self.key, default=self.default_value, **load_kwargs) - ) - except Exception: - logger.log_trace() - return False - self.loaded = True - return True
- -
[docs] def save(self, **kwargs): - """ - Stores the current value using `.handler.save_handler(self.key, value, **kwargs)` - where kwargs are a combination of those passed into this function and the - ones specified by the OptionHandler. - - Keyword Args: - any (any): Not used by default. These are passed in from self.set - and allows the option to let the caller customize saving by - overriding or extend the default save kwargs - - """ - value = self.serialize() - save_kwargs = {**self.handler.save_kwargs, **kwargs} - savefunc = self.handler.savefunc - savefunc(self.key, value=value, **save_kwargs)
- -
[docs] def deserialize(self, save_data): - """ - Perform sanity-checking on the save data as it is loaded from storage. - This isn't the same as what validator-functions provide (those work on - user input). For example, save data might be a timedelta or a list or - some other object. - - Args: - save_data: The data to check. - - Returns: - any (any): Whatever the Option needs to track, like a string or a - datetime. The display hook is responsible for what is actually - displayed to user. - """ - return save_data
- -
[docs] def serialize(self): - """ - Serializes the save data for Attribute storage. - - Returns: - any (any): Whatever is best for storage. - - """ - return self.value_storage
- -
[docs] def validate(self, value, **kwargs): - """ - Validate user input, which is presumed to be a string. - - Args: - value (str): User input. - account (AccountDB): The Account that is performing the validation. - This is necessary because of other settings which may affect the - check, such as an Account's timezone affecting how their datetime - entries are processed. - Returns: - any (any): The results of the validation. - Raises: - ValidationError: If input value failed validation. - - """ - return validatorfuncs.text(value, option_key=self.key, **kwargs)
- -
[docs] def display(self, **kwargs): - """ - Renders the Option's value as something pretty to look at. - - Keyword Args: - any (any): These are options passed by the caller to potentially - customize display dynamically. - - Returns: - str: How the stored value should be projected to users (e.g. a raw - timedelta is pretty ugly). - - """ - return self.value
- - -# Option classes - - -
[docs]class Text(BaseOption): -
[docs] def deserialize(self, save_data): - got_data = str(save_data) - if not got_data: - raise ValueError(f"{self.key} expected Text data, got '{save_data}'") - return got_data
- - -
[docs]class Email(BaseOption): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.email(value, option_key=self.key, **kwargs)
- -
[docs] def deserialize(self, save_data): - got_data = str(save_data) - if not got_data: - raise ValueError(f"{self.key} expected String data, got '{save_data}'") - return got_data
- - -
[docs]class Boolean(BaseOption): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.boolean(value, option_key=self.key, **kwargs)
- -
[docs] def display(self, **kwargs): - if self.value: - return "1 - On/True" - return "0 - Off/False"
- -
[docs] def serialize(self): - return self.value
- -
[docs] def deserialize(self, save_data): - if not isinstance(save_data, bool): - raise ValueError(f"{self.key} expected Boolean, got '{save_data}'") - return save_data
- - -
[docs]class Color(BaseOption): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.color(value, option_key=self.key, **kwargs)
- -
[docs] def display(self, **kwargs): - return f"{self.value} - |{self.value}this|n"
- -
[docs] def deserialize(self, save_data): - if not save_data or len(strip_ansi(f"|{save_data}|n")) > 0: - raise ValueError(f"{self.key} expected Color Code, got '{save_data}'") - return save_data
- - -
[docs]class Timezone(BaseOption): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.timezone(value, option_key=self.key, **kwargs)
- - @property - def default(self): - return _TZ_DICT[self.default_value] - -
[docs] def deserialize(self, save_data): - if save_data not in _TZ_DICT: - raise ValueError(f"{self.key} expected Timezone Data, got '{save_data}'") - return _TZ_DICT[save_data]
- -
[docs] def serialize(self): - return str(self.value_storage)
- - -
[docs]class UnsignedInteger(BaseOption): - validator_key = "unsigned_integer" - -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.unsigned_integer(value, option_key=self.key, **kwargs)
- -
[docs] def deserialize(self, save_data): - if isinstance(save_data, int) and save_data >= 0: - return save_data - raise ValueError(f"{self.key} expected Whole Number 0+, got '{save_data}'")
- - -
[docs]class SignedInteger(BaseOption): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.signed_integer(value, option_key=self.key, **kwargs)
- -
[docs] def deserialize(self, save_data): - if isinstance(save_data, int): - return save_data - raise ValueError(f"{self.key} expected Whole Number, got '{save_data}'")
- - -
[docs]class PositiveInteger(BaseOption): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.positive_integer(value, option_key=self.key, **kwargs)
- -
[docs] def deserialize(self, save_data): - if isinstance(save_data, int) and save_data > 0: - return save_data - raise ValueError(f"{self.key} expected Whole Number 1+, got '{save_data}'")
- - -
[docs]class Duration(BaseOption): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.duration(value, option_key=self.key, **kwargs)
- -
[docs] def deserialize(self, save_data): - if isinstance(save_data, int): - return datetime.timedelta(0, save_data, 0, 0, 0, 0, 0) - raise ValueError(f"{self.key} expected Timedelta in seconds, got '{save_data}'")
- -
[docs] def serialize(self): - return self.value_storage.seconds
- - -
[docs]class Datetime(BaseOption): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.datetime(value, option_key=self.key, **kwargs)
- -
[docs] def deserialize(self, save_data): - if isinstance(save_data, int): - return datetime.datetime.utcfromtimestamp(save_data) - raise ValueError(f"{self.key} expected UTC Datetime in EPOCH format, got '{save_data}'")
- -
[docs] def serialize(self): - return int(self.value_storage.strftime("%s"))
- - -
[docs]class Future(Datetime): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.future(value, option_key=self.key, **kwargs)
- - -
[docs]class Lock(Text): -
[docs] def validate(self, value, **kwargs): - return validatorfuncs.lock(value, option_key=self.key, **kwargs)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/optionhandler.html b/docs/0.9.5/_modules/evennia/utils/optionhandler.html deleted file mode 100644 index cd93184823..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/optionhandler.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - - - evennia.utils.optionhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.optionhandler

-from evennia.utils.utils import string_partial_matching
-from evennia.utils.containers import OPTION_CLASSES
-
-_GA = object.__getattribute__
-_SA = object.__setattr__
-
-
-
[docs]class InMemorySaveHandler(object): - """ - Fallback SaveHandler, implementing a minimum of the required save mechanism - and storing data in memory. - - """ - -
[docs] def __init__(self): - self.storage = {}
- -
[docs] def add(self, key, value=None, **kwargs): - self.storage[key] = value
- -
[docs] def get(self, key, default=None, **kwargs): - return self.storage.get(key, default)
- - -
[docs]class OptionHandler(object): - """ - This is a generic Option handler. Retrieve options either as properties on - this handler or by using the .get method. - - This is used for Account.options but it could be used by Scripts or Objects - just as easily. All it needs to be provided is an options_dict. - - """ - -
[docs] def __init__( - self, - obj, - options_dict=None, - savefunc=None, - loadfunc=None, - save_kwargs=None, - load_kwargs=None, - ): - """ - Initialize an OptionHandler. - - Args: - obj (object): The object this handler sits on. This is usually a TypedObject. - options_dict (dict): A dictionary of option keys, where the values - are options. The format of those tuples is: ('key', "Description to - show", 'option_type', <default value>) - savefunc (callable): A callable for all options to call when saving itself. - It will be called as `savefunc(key, value, **save_kwargs)`. A common one - to pass would be AttributeHandler.add. - loadfunc (callable): A callable for all options to call when loading data into - itself. It will be called as `loadfunc(key, default=default, **load_kwargs)`. - A common one to pass would be AttributeHandler.get. - save_kwargs (any): Optional extra kwargs to pass into `savefunc` above. - load_kwargs (any): Optional extra kwargs to pass into `loadfunc` above. - Notes: - Both loadfunc and savefunc must be specified. If only one is given, the other - will be ignored and in-memory storage will be used. - - """ - self.obj = obj - self.options_dict = {} if options_dict is None else options_dict - - if not savefunc and loadfunc: - self._in_memory_handler = InMemorySaveHandler() - savefunc = InMemorySaveHandler.add - loadfunc = InMemorySaveHandler.get - self.savefunc = savefunc - self.loadfunc = loadfunc - self.save_kwargs = {} if save_kwargs is None else save_kwargs - self.load_kwargs = {} if load_kwargs is None else load_kwargs - - # This dictionary stores the in-memory Options objects by their key for - # quick lookup. - self.options = {}
- - def __getattr__(self, key): - """ - Allow for obj.options.key - - """ - return self.get(key) - - def __setattr__(self, key, value): - """ - Allow for obj.options.key = value - - But we must be careful to avoid infinite loops! - - """ - try: - if key in _GA(self, "options_dict"): - _GA(self, "set")(key, value) - except AttributeError: - pass - _SA(self, key, value) - - def _load_option(self, key): - """ - Loads option on-demand if it has not been loaded yet. - - Args: - key (str): The option being loaded. - - Returns: - - """ - desc, clsname, default_val = self.options_dict[key] - loaded_option = OPTION_CLASSES.get(clsname)(self, key, desc, default_val) - # store the value for future easy access - self.options[key] = loaded_option - return loaded_option - -
[docs] def get(self, key, default=None, return_obj=False, raise_error=False): - """ - Retrieves an Option stored in the handler. Will load it if it doesn't exist. - - Args: - key (str): The option key to retrieve. - default (any): What to return if the option is defined. - return_obj (bool, optional): If True, returns the actual option - object instead of its value. - raise_error (bool, optional): Raise Exception if key is not found in options. - Returns: - option_value (any or Option): An option value the Option itself. - Raises: - KeyError: If option is not defined. - - """ - if key not in self.options_dict: - if raise_error: - raise KeyError("Option not found!") - return default - # get the options or load/recache it - op_found = self.options.get(key) or self._load_option(key) - return op_found if return_obj else op_found.value
- -
[docs] def set(self, key, value, **kwargs): - """ - Change an individual option. - - Args: - key (str): The key of an option that can be changed. Allows partial matching. - value (str): The value that should be checked, coerced, and stored.: - kwargs (any, optional): These are passed into the Option's validation function, - save function and display function and allows to customize either. - - Returns: - value (any): Value stored in option, after validation. - - """ - if not key: - raise ValueError("Option field blank!") - match = string_partial_matching(list(self.options_dict.keys()), key, ret_index=False) - if not match: - raise ValueError("Option not found!") - if len(match) > 1: - raise ValueError(f"Multiple matches: {', '.join(match)}. Please be more specific.") - match = match[0] - op = self.get(match, return_obj=True) - op.set(value, **kwargs) - return op.value
- -
[docs] def all(self, return_objs=False): - """ - Get all options defined on this handler. - - Args: - return_objs (bool, optional): Return the actual Option objects rather - than their values. - Returns: - all_options (dict): All options on this handler, either `{key: value}` - or `{key: <Option>}` if `return_objs` is `True`. - - """ - return [self.get(key, return_obj=return_objs) for key in self.options_dict]
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/picklefield.html b/docs/0.9.5/_modules/evennia/utils/picklefield.html deleted file mode 100644 index 32d6d0f634..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/picklefield.html +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - evennia.utils.picklefield — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.picklefield

-#
-#  Copyright (c) 2009-2010 Gintautas Miliauskas
-#
-#   Permission is hereby granted, free of charge, to any person
-#   obtaining a copy of this software and associated documentation
-#   files (the "Software"), to deal in the Software without
-#   restriction, including without limitation the rights to use,
-#   copy, modify, merge, publish, distribute, sublicense, and/or sell
-#   copies of the Software, and to permit persons to whom the
-#   Software is furnished to do so, subject to the following
-#   conditions:
-#
-#   The above copyright notice and this permission notice shall be
-#   included in all copies or substantial portions of the Software.
-#
-#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-#   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-#   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-#   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-#   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-#   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-#   OTHER DEALINGS IN THE SOFTWARE.
-
-"""
-Pickle field implementation for Django.
-
-Modified for Evennia by Griatch and the Evennia community.
-
-"""
-from ast import literal_eval
-from datetime import datetime
-
-from copy import deepcopy, Error as CopyError
-from base64 import b64encode, b64decode
-from zlib import compress, decompress
-
-# import six # this is actually a pypy component, not in default syslib
-from django.core.exceptions import ValidationError
-from django.db import models
-
-from django.forms.fields import CharField
-from django.forms.widgets import Textarea
-
-from pickle import loads, dumps
-from django.utils.encoding import force_str
-from evennia.utils.dbserialize import pack_dbobj
-
-
-DEFAULT_PROTOCOL = 4
-
-
-
[docs]class PickledObject(str): - """ - A subclass of string so it can be told whether a string is a pickled - object or not (if the object is an instance of this class then it must - [well, should] be a pickled one). - - Only really useful for passing pre-encoded values to ``default`` - with ``dbsafe_encode``, not that doing so is necessary. If you - remove PickledObject and its references, you won't be able to pass - in pre-encoded values anymore, but you can always just pass in the - python objects themselves. - """
- - -class _ObjectWrapper(object): - """ - A class used to wrap object that have properties that may clash with the - ORM internals. - - For example, objects with the `prepare_database_save` property such as - `django.db.Model` subclasses won't work under certain conditions and the - same apply for trying to retrieve any `callable` object. - """ - - __slots__ = ("_obj",) - - def __init__(self, obj): - self._obj = obj - - -
[docs]def wrap_conflictual_object(obj): - if hasattr(obj, "prepare_database_save") or callable(obj): - obj = _ObjectWrapper(obj) - return obj
- - -
[docs]def dbsafe_encode(value, compress_object=False, pickle_protocol=DEFAULT_PROTOCOL): - # We use deepcopy() here to avoid a problem with cPickle, where dumps - # can generate different character streams for same lookup value if - # they are referenced differently. - # The reason this is important is because we do all of our lookups as - # simple string matches, thus the character streams must be the same - # for the lookups to work properly. See tests.py for more information. - try: - value = deepcopy(value) - except CopyError: - # this can happen on a manager query where the search query string is a - # database model. - value = pack_dbobj(value) - - value = dumps(value, protocol=pickle_protocol) - - if compress_object: - value = compress(value) - value = b64encode(value).decode() # decode bytes to str - return PickledObject(value)
- - -
[docs]def dbsafe_decode(value, compress_object=False): - value = value.encode() # encode str to bytes - value = b64decode(value) - if compress_object: - value = decompress(value) - return loads(value)
- - -
[docs]class PickledWidget(Textarea): - """ - This is responsible for outputting HTML representing a given field. - """ - -
[docs] def render(self, name, value, attrs=None, renderer=None): - """Display of the PickledField in django admin""" - - repr_value = repr(value) - - # analyze represented value to see how big the field should be - if attrs is not None: - attrs["name"] = name - else: - attrs = {"name": name} - attrs["cols"] = 30 - # adapt number of rows to number of lines in string - rows = 1 - if isinstance(value, str) and "\n" in repr_value: - rows = max(1, len(value.split("\n"))) - attrs["rows"] = rows - attrs = self.build_attrs(attrs) - - try: - # necessary to convert it back after repr(), otherwise validation errors will mutate it - value = literal_eval(repr_value) - except (ValueError, SyntaxError): - # we could not eval it, just show its prepresentation - value = repr_value - return super().render(name, value, attrs=attrs, renderer=renderer)
- -
[docs] def value_from_datadict(self, data, files, name): - dat = data.get(name) - # import evennia;evennia.set_trace() - return dat
- - -
[docs]class PickledFormField(CharField): - """ - This represents one input field for the form. - - """ - - widget = PickledWidget - default_error_messages = dict(CharField.default_error_messages) - default_error_messages["invalid"] = ( - "This is not a Python Literal. You can store things like strings, " - "integers, or floats, but you must do it by typing them as you would " - "type them in the Python Interpreter. For instance, strings must be " - "surrounded by quote marks. We have converted it to a string for your " - "convenience. If it is acceptable, please hit save again." - ) - -
[docs] def __init__(self, *args, **kwargs): - # This needs to fall through to literal_eval. - kwargs["required"] = False - super().__init__(*args, **kwargs)
- -
[docs] def clean(self, value): - value = super().clean(value) - - # handle empty input - try: - if not value.strip(): - # Field was left blank. Make this None. - value = "None" - except AttributeError: - pass - - # parse raw Python - try: - return literal_eval(value) - except (ValueError, SyntaxError): - pass - - # fall through to parsing the repr() of the data - try: - value = repr(value) - return literal_eval(value) - except (ValueError, SyntaxError): - raise ValidationError(self.error_messages["invalid"])
- - -
[docs]class PickledObjectField(models.Field): - """ - A field that will accept *any* python object and store it in the - database. PickledObjectField will optionally compress its values if - declared with the keyword argument ``compress=True``. - - Does not actually encode and compress ``None`` objects (although you - can still do lookups using None). This way, it is still possible to - use the ``isnull`` lookup type correctly. - """ - -
[docs] def __init__(self, *args, **kwargs): - self.compress = kwargs.pop("compress", False) - self.protocol = kwargs.pop("protocol", DEFAULT_PROTOCOL) - super().__init__(*args, **kwargs)
- -
[docs] def get_default(self): - """ - Returns the default value for this field. - - The default implementation on models.Field calls force_str - on the default, which means you can't set arbitrary Python - objects as the default. To fix this, we just return the value - without calling force_str on it. Note that if you set a - callable as a default, the field will still call it. It will - *not* try to pickle and encode it. - - """ - if self.has_default(): - if callable(self.default): - return self.default() - return self.default - # If the field doesn't have a default, then we punt to models.Field. - return super().get_default()
- -
[docs] def from_db_value(self, value, *args): - """ - B64decode and unpickle the object, optionally decompressing it. - - If an error is raised in de-pickling and we're sure the value is - a definite pickle, the error is allowed to propagate. If we - aren't sure if the value is a pickle or not, then we catch the - error and return the original value instead. - - """ - if value is not None: - try: - value = dbsafe_decode(value, self.compress) - except Exception: - # If the value is a definite pickle; and an error is raised in - # de-pickling it should be allowed to propogate. - if isinstance(value, PickledObject): - raise - else: - if isinstance(value, _ObjectWrapper): - return value._obj - return value
- -
[docs] def formfield(self, **kwargs): - return PickledFormField(**kwargs)
- -
[docs] def pre_save(self, model_instance, add): - value = super().pre_save(model_instance, add) - return wrap_conflictual_object(value)
- -
[docs] def get_db_prep_value(self, value, connection=None, prepared=False): - """ - Pickle and b64encode the object, optionally compressing it. - - The pickling protocol is specified explicitly (by default 2), - rather than as -1 or HIGHEST_PROTOCOL, because we don't want the - protocol to change over time. If it did, ``exact`` and ``in`` - lookups would likely fail, since pickle would now be generating - a different string. - - """ - if value is not None and not isinstance(value, PickledObject): - # We call force_str here explicitly, so that the encoded string - # isn't rejected by the postgresql backend. Alternatively, - # we could have just registered PickledObject with the psycopg - # marshaller (telling it to store it like it would a string), but - # since both of these methods result in the same value being stored, - # doing things this way is much easier. - value = force_str(dbsafe_encode(value, self.compress, self.protocol)) - return value
- -
[docs] def value_to_string(self, obj): - value = self._get_val_from_obj(obj) - return self.get_db_prep_value(value)
- -
[docs] def get_internal_type(self): - return "TextField"
- -
[docs] def get_db_prep_lookup(self, lookup_type, value, connection=None, prepared=False): - if lookup_type not in ["exact", "in", "isnull"]: - raise TypeError("Lookup type %s is not supported." % lookup_type) - # The Field model already calls get_db_prep_value before doing the - # actual lookup, so all we need to do is limit the lookup types. - return super().get_db_prep_lookup( - lookup_type, value, connection=connection, prepared=prepared - )
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/search.html b/docs/0.9.5/_modules/evennia/utils/search.html deleted file mode 100644 index 7b397d7029..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/search.html +++ /dev/null @@ -1,470 +0,0 @@ - - - - - - - - evennia.utils.search — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.search

-"""
-This is a convenient container gathering all the main
-search methods for the various database tables.
-
-It is intended to be used e.g. as
-
-> from evennia.utils import search
-> match = search.objects(...)
-
-Note that this is not intended to be a complete listing of all search
-methods! You need to refer to the respective manager to get all
-possible search methods. To get to the managers from your code, import
-the database model and call its 'objects' property.
-
-Also remember that all commands in this file return lists (also if
-there is only one match) unless noted otherwise.
-
-Example: To reach the search method 'get_object_with_account'
-         in evennia/objects/managers.py:
-
-> from evennia.objects.models import ObjectDB
-> match = Object.objects.get_object_with_account(...)
-
-
-"""
-
-# Import the manager methods to be wrapped
-
-from django.db.utils import OperationalError
-from django.contrib.contenttypes.models import ContentType
-
-# limit symbol import from API
-__all__ = (
-    "search_object",
-    "search_account",
-    "search_script",
-    "search_message",
-    "search_channel",
-    "search_help_entry",
-    "search_tag",  # object-tag
-    "search_script_tag",
-    "search_account_tag",
-    "search_channel_tag",
-)
-
-
-# import objects this way to avoid circular import problems
-try:
-    ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class()
-    AccountDB = ContentType.objects.get(app_label="accounts", model="accountdb").model_class()
-    ScriptDB = ContentType.objects.get(app_label="scripts", model="scriptdb").model_class()
-    Msg = ContentType.objects.get(app_label="comms", model="msg").model_class()
-    ChannelDB = ContentType.objects.get(app_label="comms", model="channeldb").model_class()
-    HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class()
-    Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class()
-except OperationalError:
-    # this is a fallback used during tests/doc building
-   print("Couldn't initialize search managers - db not set up.")
-   from evennia.objects.models import ObjectDB
-   from evennia.accounts.models import AccountDB
-   from evennia.scripts.models import ScriptDB
-   from evennia.comms.models import Msg, ChannelDB
-   from evennia.help.models import HelpEntry
-   from evennia.typeclasses.tags import Tag
-
-# -------------------------------------------------------------------
-# Search manager-wrappers
-# -------------------------------------------------------------------
-
-#
-# Search objects as a character
-#
-# NOTE: A more powerful wrapper of this method
-#  is reachable from within each command class
-#  by using self.caller.search()!
-#
-#    def object_search(self, ostring=None,
-#                      attribute_name=None,
-#                      typeclass=None,
-#                      candidates=None,
-#                      exact=True):
-#
-#        Search globally or in a list of candidates and return results.
-#        The result is always a list of Objects (or the empty list)
-#
-#        Arguments:
-#        ostring: (str) The string to compare names against. By default (if
-#                  not attribute_name is set), this will search object.key
-#                  and object.aliases in order. Can also be on the form #dbref,
-#                  which will, if exact=True be matched against primary key.
-#        attribute_name: (str): Use this named ObjectAttribute to match ostring
-#                        against, instead of the defaults.
-#        typeclass (str or TypeClass): restrict matches to objects having
-#                  this typeclass. This will help speed up global searches.
-#        candidates (list obj ObjectDBs): If supplied, search will only be
-#                  performed among the candidates in this list. A common list
-#                  of candidates is the contents of the current location.
-#        exact (bool): Match names/aliases exactly or partially. Partial
-#                  matching matches the beginning of words in the names/aliases,
-#                  using a matching routine to separate multiple matches in
-#                  names with multiple components (so "bi sw" will match
-#                  "Big sword"). Since this is more expensive than exact
-#                  matching, it is recommended to be used together with
-#                  the objlist keyword to limit the number of possibilities.
-#                  This keyword has no meaning if attribute_name is set.
-#
-#        Returns:
-#        A list of matching objects (or a list with one unique match)
-#    def object_search(self, ostring, caller=None,
-#                      candidates=None,
-#                      attribute_name=None):
-#
-search_object = ObjectDB.objects.object_search
-search_objects = search_object
-object_search = search_object
-objects = search_objects
-
-#
-# Search for accounts
-#
-# account_search(self, ostring)
-
-#     Searches for a particular account by name or
-#     database id.
-#
-#     ostring = a string or database id.
-#
-
-search_account = AccountDB.objects.account_search
-search_accounts = search_account
-account_search = search_account
-accounts = search_accounts
-
-#
-#   Searching for scripts
-#
-# script_search(self, ostring, obj=None, only_timed=False)
-#
-#     Search for a particular script.
-#
-#     ostring - search criterion - a script ID or key
-#     obj - limit search to scripts defined on this object
-#     only_timed - limit search only to scripts that run
-#                  on a timer.
-#
-
-search_script = ScriptDB.objects.script_search
-search_scripts = search_script
-script_search = search_script
-scripts = search_scripts
-#
-# Searching for communication messages
-#
-#
-# message_search(self, sender=None, receiver=None, channel=None, freetext=None)
-#
-#     Search the message database for particular messages. At least one
-#     of the arguments must be given to do a search.
-#
-#     sender - get messages sent by a particular account
-#     receiver - get messages received by a certain account
-#     channel - get messages sent to a particular channel
-#     freetext - Search for a text string in a message.
-#                NOTE: This can potentially be slow, so make sure to supply
-#                one of the other arguments to limit the search.
-#
-
-search_message = Msg.objects.message_search
-search_messages = search_message
-message_search = search_message
-messages = search_messages
-
-#
-# Search for Communication Channels
-#
-# channel_search(self, ostring)
-#
-#     Search the channel database for a particular channel.
-#
-#     ostring - the key or database id of the channel.
-#     exact -  requires an exact ostring match (not case sensitive)
-#
-
-search_channel = ChannelDB.objects.channel_search
-search_channels = search_channel
-channel_search = search_channel
-channels = search_channels
-
-#
-# Find help entry objects.
-#
-# search_help(self, ostring, help_category=None)
-#
-#     Retrieve a search entry object.
-#
-#     ostring - the help topic to look for
-#     category - limit the search to a particular help topic
-#
-
-search_help = HelpEntry.objects.search_help
-search_help_entry = search_help
-search_help_entries = search_help
-help_entry_search = search_help
-help_entries = search_help
-
-
-# Locate Attributes
-
-#    search_object_attribute(key, category, value, strvalue) (also search_attribute works)
-#    search_account_attribute(key, category, value, strvalue) (also search_attribute works)
-#    search_script_attribute(key, category, value, strvalue) (also search_attribute works)
-#    search_channel_attribute(key, category, value, strvalue) (also search_attribute works)
-
-# Note that these return the object attached to the Attribute,
-# not the attribute object itself (this is usually what you want)
-
-
-def search_object_attribute(
-    key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
-):
-    return ObjectDB.objects.get_by_attribute(
-        key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
-    )
-
-
-def search_account_attribute(
-    key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
-):
-    return AccountDB.objects.get_by_attribute(
-        key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
-    )
-
-
-def search_script_attribute(
-    key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
-):
-    return ScriptDB.objects.get_by_attribute(
-        key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
-    )
-
-
-def search_channel_attribute(
-    key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
-):
-    return ChannelDB.objects.get_by_attribute(
-        key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
-    )
-
-
-# search for attribute objects
-search_attribute_object = ObjectDB.objects.get_attribute
-
-# Locate Tags
-
-#    search_object_tag(key=None, category=None) (also search_tag works)
-#    search_account_tag(key=None, category=None)
-#    search_script_tag(key=None, category=None)
-#    search_channel_tag(key=None, category=None)
-
-# Note that this returns the object attached to the tag, not the tag
-# object itself (this is usually what you want)
-
-
-def search_object_by_tag(key=None, category=None, tagtype=None, **kwargs):
-    """
-    Find object based on tag or category.
-
-    Args:
-        key (str, optional): The tag key to search for.
-        category (str, optional): The category of tag
-            to search for. If not set, uncategorized
-            tags will be searched.
-        tagtype (str, optional): 'type' of Tag, by default
-            this is either `None` (a normal Tag), `alias` or
-            `permission`. This always apply to all queried tags.
-        kwargs (any): Other optional parameter that may be supported
-            by the manager method.
-
-    Returns:
-        matches (list): List of Objects with tags matching
-            the search criteria, or an empty list if no
-            matches were found.
-
-    """
-    return ObjectDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
-
-
-search_tag = search_object_by_tag  # this is the most common case
-
-
-
[docs]def search_account_tag(key=None, category=None, tagtype=None, **kwargs): - """ - Find account based on tag or category. - - Args: - key (str, optional): The tag key to search for. - category (str, optional): The category of tag - to search for. If not set, uncategorized - tags will be searched. - tagtype (str, optional): 'type' of Tag, by default - this is either `None` (a normal Tag), `alias` or - `permission`. This always apply to all queried tags. - kwargs (any): Other optional parameter that may be supported - by the manager method. - - Returns: - matches (list): List of Accounts with tags matching - the search criteria, or an empty list if no - matches were found. - - """ - return AccountDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
- - -
[docs]def search_script_tag(key=None, category=None, tagtype=None, **kwargs): - """ - Find script based on tag or category. - - Args: - key (str, optional): The tag key to search for. - category (str, optional): The category of tag - to search for. If not set, uncategorized - tags will be searched. - tagtype (str, optional): 'type' of Tag, by default - this is either `None` (a normal Tag), `alias` or - `permission`. This always apply to all queried tags. - kwargs (any): Other optional parameter that may be supported - by the manager method. - - Returns: - matches (list): List of Scripts with tags matching - the search criteria, or an empty list if no - matches were found. - - """ - return ScriptDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
- - -
[docs]def search_channel_tag(key=None, category=None, tagtype=None, **kwargs): - """ - Find channel based on tag or category. - - Args: - key (str, optional): The tag key to search for. - category (str, optional): The category of tag - to search for. If not set, uncategorized - tags will be searched. - tagtype (str, optional): 'type' of Tag, by default - this is either `None` (a normal Tag), `alias` or - `permission`. This always apply to all queried tags. - kwargs (any): Other optional parameter that may be supported - by the manager method. - - Returns: - matches (list): List of Channels with tags matching - the search criteria, or an empty list if no - matches were found. - - """ - return ChannelDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
- - -# search for tag objects (not the objects they are attached to -search_tag_object = ObjectDB.objects.get_tag -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/test_resources.html b/docs/0.9.5/_modules/evennia/utils/test_resources.html deleted file mode 100644 index ff14bc0879..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/test_resources.html +++ /dev/null @@ -1,281 +0,0 @@ - - - - - - - - evennia.utils.test_resources — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.test_resources

-"""
-Various helper resources for writing unittests.
-
-"""
-import sys
-from twisted.internet.defer import Deferred
-from django.conf import settings
-from django.test import TestCase
-from mock import Mock, patch
-from evennia.objects.objects import DefaultObject, DefaultCharacter, DefaultRoom, DefaultExit
-from evennia.accounts.accounts import DefaultAccount
-from evennia.scripts.scripts import DefaultScript
-from evennia.server.serversession import ServerSession
-from evennia.server.sessionhandler import SESSIONS
-from evennia.utils import create
-from evennia.utils.idmapper.models import flush_cache
-
-
-# mocking of evennia.utils.utils.delay
-
[docs]def mockdelay(timedelay, callback, *args, **kwargs): - callback(*args, **kwargs) - return Deferred()
- - -# mocking of twisted's deferLater -
[docs]def mockdeferLater(reactor, timedelay, callback, *args, **kwargs): - callback(*args, **kwargs) - return Deferred()
- - -
[docs]def unload_module(module): - """ - Reset import so one can mock global constants. - - Args: - module (module, object or str): The module will - be removed so it will have to be imported again. If given - an object, the module in which that object sits will be unloaded. A string - should directly give the module pathname to unload. - - Example: - :: - - # (in a test method) - unload_module(foo) - with mock.patch("foo.GLOBALTHING", "mockval"): - import foo - ... # test code using foo.GLOBALTHING, now set to 'mockval' - - Notes: - This allows for mocking constants global to the module, since - otherwise those would not be mocked (since a module is only - loaded once). - - """ - if isinstance(module, str): - modulename = module - elif hasattr(module, "__module__"): - modulename = module.__module__ - else: - modulename = module.__name__ - - if modulename in sys.modules: - del sys.modules[modulename]
- - -def _mock_deferlater(reactor, timedelay, callback, *args, **kwargs): - callback(*args, **kwargs) - return Deferred() - - -
[docs]class EvenniaTest(TestCase): - """ - Base test for Evennia, sets up a basic environment. - """ - - account_typeclass = DefaultAccount - object_typeclass = DefaultObject - character_typeclass = DefaultCharacter - exit_typeclass = DefaultExit - room_typeclass = DefaultRoom - script_typeclass = DefaultScript - -
[docs] @patch("evennia.scripts.taskhandler.deferLater", _mock_deferlater) - def setUp(self): - """ - Sets up testing environment - """ - self.backups = ( - SESSIONS.data_out, - SESSIONS.disconnect, - settings.DEFAULT_HOME, - settings.PROTOTYPE_MODULES, - ) - SESSIONS.data_out = Mock() - SESSIONS.disconnect = Mock() - - self.account = create.create_account( - "TestAccount", - email="test@test.com", - password="testpassword", - typeclass=self.account_typeclass, - ) - self.account2 = create.create_account( - "TestAccount2", - email="test@test.com", - password="testpassword", - typeclass=self.account_typeclass, - ) - self.room1 = create.create_object(self.room_typeclass, key="Room", nohome=True) - self.room1.db.desc = "room_desc" - settings.DEFAULT_HOME = "#%i" % self.room1.id # we must have a default home - # Set up fake prototype module for allowing tests to use named prototypes. - settings.PROTOTYPE_MODULES = "evennia.utils.tests.data.prototypes_example" - self.room2 = create.create_object(self.room_typeclass, key="Room2") - self.exit = create.create_object( - self.exit_typeclass, key="out", location=self.room1, destination=self.room2 - ) - self.obj1 = create.create_object( - self.object_typeclass, key="Obj", location=self.room1, home=self.room1 - ) - self.obj2 = create.create_object( - self.object_typeclass, key="Obj2", location=self.room1, home=self.room1 - ) - self.char1 = create.create_object( - self.character_typeclass, key="Char", location=self.room1, home=self.room1 - ) - self.char1.permissions.add("Developer") - self.char2 = create.create_object( - self.character_typeclass, key="Char2", location=self.room1, home=self.room1 - ) - self.char1.account = self.account - self.account.db._last_puppet = self.char1 - self.char2.account = self.account2 - self.account2.db._last_puppet = self.char2 - self.script = create.create_script(self.script_typeclass, key="Script") - self.account.permissions.add("Developer") - - # set up a fake session - - dummysession = ServerSession() - dummysession.init_session("telnet", ("localhost", "testmode"), SESSIONS) - dummysession.sessid = 1 - SESSIONS.portal_connect( - dummysession.get_sync_data() - ) # note that this creates a new Session! - session = SESSIONS.session_from_sessid(1) # the real session - SESSIONS.login(session, self.account, testmode=True) - self.session = session
- -
[docs] def tearDown(self): - flush_cache() - SESSIONS.data_out = self.backups[0] - SESSIONS.disconnect = self.backups[1] - settings.DEFAULT_HOME = self.backups[2] - settings.PROTOTYPE_MODULES = self.backups[3] - - del SESSIONS[self.session.sessid] - self.account.delete() - self.account2.delete() - super().tearDown()
- - -
[docs]class LocalEvenniaTest(EvenniaTest): - """ - This test class is intended for inheriting in mygame tests. - It helps ensure your tests are run with your own objects. - """ - - account_typeclass = settings.BASE_ACCOUNT_TYPECLASS - object_typeclass = settings.BASE_OBJECT_TYPECLASS - character_typeclass = settings.BASE_CHARACTER_TYPECLASS - exit_typeclass = settings.BASE_EXIT_TYPECLASS - room_typeclass = settings.BASE_ROOM_TYPECLASS - script_typeclass = settings.BASE_SCRIPT_TYPECLASS
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/text2html.html b/docs/0.9.5/_modules/evennia/utils/text2html.html deleted file mode 100644 index 2f39a975e7..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/text2html.html +++ /dev/null @@ -1,474 +0,0 @@ - - - - - - - - evennia.utils.text2html — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.text2html

-"""
-ANSI -> html converter
-
-Credit for original idea and implementation
-goes to Muhammad Alkarouri and his
-snippet #577349 on http://code.activestate.com.
-
-(extensively modified by Griatch 2010)
-"""
-
-import re
-from html import escape as html_escape
-from .ansi import *
-
-
-# All xterm256 RGB equivalents
-
-XTERM256_FG = "\033[38;5;%sm"
-XTERM256_BG = "\033[48;5;%sm"
-
-
-
[docs]class TextToHTMLparser(object): - """ - This class describes a parser for converting from ANSI to html. - """ - - tabstop = 4 - # mapping html color name <-> ansi code. - hilite = ANSI_HILITE - unhilite = ANSI_UNHILITE # this will be stripped - there is no css equivalent. - normal = ANSI_NORMAL # " - underline = ANSI_UNDERLINE - blink = ANSI_BLINK - inverse = ANSI_INVERSE # this will produce an outline; no obvious css equivalent? - colorcodes = [ - ("color-000", unhilite + ANSI_BLACK), # pure black - ("color-001", unhilite + ANSI_RED), - ("color-002", unhilite + ANSI_GREEN), - ("color-003", unhilite + ANSI_YELLOW), - ("color-004", unhilite + ANSI_BLUE), - ("color-005", unhilite + ANSI_MAGENTA), - ("color-006", unhilite + ANSI_CYAN), - ("color-007", unhilite + ANSI_WHITE), # light grey - ("color-008", hilite + ANSI_BLACK), # dark grey - ("color-009", hilite + ANSI_RED), - ("color-010", hilite + ANSI_GREEN), - ("color-011", hilite + ANSI_YELLOW), - ("color-012", hilite + ANSI_BLUE), - ("color-013", hilite + ANSI_MAGENTA), - ("color-014", hilite + ANSI_CYAN), - ("color-015", hilite + ANSI_WHITE), # pure white - ] + [("color-%03i" % (i + 16), XTERM256_FG % ("%i" % (i + 16))) for i in range(240)] - - colorback = [ - ("bgcolor-000", ANSI_BACK_BLACK), # pure black - ("bgcolor-001", ANSI_BACK_RED), - ("bgcolor-002", ANSI_BACK_GREEN), - ("bgcolor-003", ANSI_BACK_YELLOW), - ("bgcolor-004", ANSI_BACK_BLUE), - ("bgcolor-005", ANSI_BACK_MAGENTA), - ("bgcolor-006", ANSI_BACK_CYAN), - ("bgcolor-007", ANSI_BACK_WHITE), # light grey - ("bgcolor-008", hilite + ANSI_BACK_BLACK), # dark grey - ("bgcolor-009", hilite + ANSI_BACK_RED), - ("bgcolor-010", hilite + ANSI_BACK_GREEN), - ("bgcolor-011", hilite + ANSI_BACK_YELLOW), - ("bgcolor-012", hilite + ANSI_BACK_BLUE), - ("bgcolor-013", hilite + ANSI_BACK_MAGENTA), - ("bgcolor-014", hilite + ANSI_BACK_CYAN), - ("bgcolor-015", hilite + ANSI_BACK_WHITE), # pure white - ] + [("bgcolor-%03i" % (i + 16), XTERM256_BG % ("%i" % (i + 16))) for i in range(240)] - - # make sure to escape [ - # colorcodes = [(c, code.replace("[", r"\[")) for c, code in colorcodes] - # colorback = [(c, code.replace("[", r"\[")) for c, code in colorback] - fg_colormap = dict((code, clr) for clr, code in colorcodes) - bg_colormap = dict((code, clr) for clr, code in colorback) - - # create stop markers - fgstop = "(?:\033\[1m|\033\[22m){0,1}\033\[3[0-8].*?m|\033\[0m|$" - bgstop = "(?:\033\[1m|\033\[22m){0,1}\033\[4[0-8].*?m|\033\[0m|$" - bgfgstop = bgstop[:-2] + r"(\s*)" + fgstop - - fgstart = "((?:\033\[1m|\033\[22m){0,1}\033\[3[0-8].*?m)" - bgstart = "((?:\033\[1m|\033\[22m){0,1}\033\[4[0-8].*?m)" - bgfgstart = bgstart + r"(\s*)" + "((?:\033\[1m|\033\[22m){0,1}\033\[[3-4][0-8].*?m){0,1}" - - # extract color markers, tagging the start marker and the text marked - re_fgs = re.compile(fgstart + "(.*?)(?=" + fgstop + ")") - re_bgs = re.compile(bgstart + "(.*?)(?=" + bgstop + ")") - re_bgfg = re.compile(bgfgstart + "(.*?)(?=" + bgfgstop + ")") - - re_normal = re.compile(normal.replace("[", r"\[")) - re_hilite = re.compile("(?:%s)(.*)(?=%s|%s)" % (hilite.replace("[", r"\["), fgstop, bgstop)) - re_unhilite = re.compile("(?:%s)(.*)(?=%s|%s)" % (unhilite.replace("[", r"\["), fgstop, bgstop)) - re_uline = re.compile("(?:%s)(.*?)(?=%s|%s)" % (underline.replace("[", r"\["), fgstop, bgstop)) - re_blink = re.compile("(?:%s)(.*?)(?=%s|%s)" % (blink.replace("[", r"\["), fgstop, bgstop)) - re_inverse = re.compile("(?:%s)(.*?)(?=%s|%s)" % (inverse.replace("[", r"\["), fgstop, bgstop)) - re_string = re.compile( - r"(?P<htmlchars>[<&>])|(?P<tab>[\t]+)|(?P<space> +)|" - r"(?P<spacestart>^ )|(?P<lineend>\r\n|\r|\n)", - re.S | re.M | re.I, - ) - re_dblspace = re.compile(r" {2,}", re.M) - re_url = re.compile( - r'((?:ftp|www|https?)\W+(?:(?!\.(?:\s|$)|&\w+;)[^"\',;$*^\\(){}<>\[\]\s])+)(\.(?:\s|$)|&\w+;|)' - ) - re_mxplink = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL) - - def _sub_bgfg(self, colormatch): - # print("colormatch.groups()", colormatch.groups()) - bgcode, prespace, fgcode, text, postspace = colormatch.groups() - if not fgcode: - ret = r"""<span class="%s">%s%s%s</span>""" % ( - self.bg_colormap.get(bgcode, self.fg_colormap.get(bgcode, "err")), - prespace and "&nbsp;" * len(prespace) or "", - postspace and "&nbsp;" * len(postspace) or "", - text, - ) - else: - ret = r"""<span class="%s"><span class="%s">%s%s%s</span></span>""" % ( - self.bg_colormap.get(bgcode, self.fg_colormap.get(bgcode, "err")), - self.fg_colormap.get(fgcode, self.bg_colormap.get(fgcode, "err")), - prespace and "&nbsp;" * len(prespace) or "", - postspace and "&nbsp;" * len(postspace) or "", - text, - ) - return ret - - def _sub_fg(self, colormatch): - code, text = colormatch.groups() - return r"""<span class="%s">%s</span>""" % (self.fg_colormap.get(code, "err"), text) - - def _sub_bg(self, colormatch): - code, text = colormatch.groups() - return r"""<span class="%s">%s</span>""" % (self.bg_colormap.get(code, "err"), text) - -
[docs] def re_color(self, text): - """ - Replace ansi colors with html color class names. Let the - client choose how it will display colors, if it wishes to. - - Args: - text (str): the string with color to replace. - - Returns: - text (str): Re-colored text. - - """ - text = self.re_bgfg.sub(self._sub_bgfg, text) - text = self.re_fgs.sub(self._sub_fg, text) - text = self.re_bgs.sub(self._sub_bg, text) - text = self.re_normal.sub("", text) - return text
- -
[docs] def re_bold(self, text): - """ - Clean out superfluous hilights rather than set <strong>to make - it match the look of telnet. - - Args: - text (str): Text to process. - - Returns: - text (str): Processed text. - - """ - text = self.re_hilite.sub(r"<strong>\1</strong>", text) - return self.re_unhilite.sub(r"\1", text) # strip unhilite - there is no equivalent in css.
- -
[docs] def re_underline(self, text): - """ - Replace ansi underline with html underline class name. - - Args: - text (str): Text to process. - - Returns: - text (str): Processed text. - - """ - return self.re_uline.sub(r'<span class="underline">\1</span>', text)
- -
[docs] def re_blinking(self, text): - """ - Replace ansi blink with custom blink css class - - Args: - text (str): Text to process. - - Returns: - text (str): Processed text. - """ - return self.re_blink.sub(r'<span class="blink">\1</span>', text)
- -
[docs] def re_inversing(self, text): - """ - Replace ansi inverse with custom inverse css class - - Args: - text (str): Text to process. - - Returns: - text (str): Processed text. - """ - return self.re_inverse.sub(r'<span class="inverse">\1</span>', text)
- -
[docs] def remove_bells(self, text): - """ - Remove ansi specials - - Args: - text (str): Text to process. - - Returns: - text (str): Processed text. - - """ - return text.replace("\07", "")
- -
[docs] def remove_backspaces(self, text): - """ - Removes special escape sequences - - Args: - text (str): Text to process. - - Returns: - text (str): Processed text. - - """ - backspace_or_eol = r"(.\010)|(\033\[K)" - n = 1 - while n > 0: - text, n = re.subn(backspace_or_eol, "", text, 1) - return text
- -
[docs] def convert_linebreaks(self, text): - """ - Extra method for cleaning linebreaks - - Args: - text (str): Text to process. - - Returns: - text (str): Processed text. - - """ - return text.replace("\n", r"<br>")
- -
[docs] def convert_urls(self, text): - """ - Replace urls (http://...) by valid HTML. - - Args: - text (str): Text to process. - - Returns: - text (str): Processed text. - - """ - # -> added target to output prevent the web browser from attempting to - # change pages (and losing our webclient session). - return self.re_url.sub(r'<a href="\1" target="_blank">\1</a>\2', text)
- -
[docs] def re_double_space(self, text): - """ - HTML will swallow any normal space after the first, so if any slipped - through we must make sure to replace them with " &nbsp;" - """ - return self.re_dblspace.sub(self.sub_dblspace, text)
- - - -
[docs] def sub_text(self, match): - """ - Helper method to be passed to re.sub, - for handling all substitutions. - - Args: - match (re.Matchobject): Match for substitution. - - Returns: - text (str): Processed text. - - """ - cdict = match.groupdict() - if cdict["htmlchars"]: - return html_escape(cdict["htmlchars"]) - elif cdict["lineend"]: - return "<br>" - elif cdict["tab"]: - text = cdict["tab"].replace("\t", " " + "&nbsp;" * (self.tabstop - 1)) - return text - elif cdict["space"] or cdict["spacestart"]: - text = cdict["space"] - text = " " if len(text) == 1 else " " + text[1:].replace(" ", "&nbsp;") - return text - return None
- -
[docs] def sub_dblspace(self, match): - "clean up double-spaces" - return " " + "&nbsp;" * (len(match.group()) - 1)
- -
[docs] def parse(self, text, strip_ansi=False): - """ - Main access function, converts a text containing ANSI codes - into html statements. - - Args: - text (str): Text to process. - strip_ansi (bool, optional): - - Returns: - text (str): Parsed text. - """ - # parse everything to ansi first - text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=True, mxp=True) - # convert all ansi to html - result = re.sub(self.re_string, self.sub_text, text) - result = re.sub(self.re_mxplink, self.sub_mxp_links, result) - result = self.re_color(result) - result = self.re_bold(result) - result = self.re_underline(result) - result = self.re_blinking(result) - result = self.re_inversing(result) - result = self.remove_bells(result) - result = self.convert_linebreaks(result) - result = self.remove_backspaces(result) - result = self.convert_urls(result) - result = self.re_double_space(result) - # clean out eventual ansi that was missed - # result = parse_ansi(result, strip_ansi=True) - - return result
- - -HTML_PARSER = TextToHTMLparser() - - -# -# Access function -# - - -
[docs]def parse_html(string, strip_ansi=False, parser=HTML_PARSER): - """ - Parses a string, replace ANSI markup with html - """ - return parser.parse(string, strip_ansi=strip_ansi)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/utils.html b/docs/0.9.5/_modules/evennia/utils/utils.html deleted file mode 100644 index 2d7a8db299..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/utils.html +++ /dev/null @@ -1,2241 +0,0 @@ - - - - - - - - evennia.utils.utils — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.utils

-# -*- encoding: utf-8 -*-
-"""
-General helper functions that don't fit neatly under any given category.
-
-They provide some useful string and conversion methods that might
-be of use when designing your own game.
-
-"""
-import os
-import gc
-import sys
-import copy
-import types
-import math
-import re
-import textwrap
-import random
-import inspect
-import traceback
-import importlib
-import importlib.util
-import importlib.machinery
-from unicodedata import east_asian_width
-from twisted.internet.task import deferLater
-from twisted.internet.defer import returnValue  # noqa - used as import target
-from os.path import join as osjoin
-from inspect import ismodule, trace, getmembers, getmodule, getmro
-from collections import defaultdict, OrderedDict
-from twisted.internet import threads, reactor
-from django.conf import settings
-from django.utils import timezone
-from django.utils.translation import gettext as _
-from django.apps import apps
-from django.core.validators import validate_email as django_validate_email
-from django.core.exceptions import ValidationError as DjangoValidationError
-from evennia.utils import logger
-
-_MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE
-_EVENNIA_DIR = settings.EVENNIA_DIR
-_GAME_DIR = settings.GAME_DIR
-ENCODINGS = settings.ENCODINGS
-_GA = object.__getattribute__
-_SA = object.__setattr__
-_DA = object.__delattr__
-
-
-
[docs]def is_iter(obj): - """ - Checks if an object behaves iterably. - - Args: - obj (any): Entity to check for iterability. - - Returns: - is_iterable (bool): If `obj` is iterable or not. - - Notes: - Strings are *not* accepted as iterable (although they are - actually iterable), since string iterations are usually not - what we want to do with a string. - - """ - if isinstance(obj, (str, bytes)): - return False - - try: - return iter(obj) and True - except TypeError: - return False
- - -
[docs]def make_iter(obj): - """ - Makes sure that the object is always iterable. - - Args: - obj (any): Object to make iterable. - - Returns: - iterable (list or iterable): The same object - passed-through or made iterable. - - """ - return not is_iter(obj) and [obj] or obj
- - -
[docs]def wrap(text, width=None, indent=0): - """ - Safely wrap text to a certain number of characters. - - Args: - text (str): The text to wrap. - width (int, optional): The number of characters to wrap to. - indent (int): How much to indent each line (with whitespace). - - Returns: - text (str): Properly wrapped text. - - """ - width = width if width else settings.CLIENT_DEFAULT_WIDTH - if not text: - return "" - indent = " " * indent - return to_str(textwrap.fill(text, width, initial_indent=indent, subsequent_indent=indent))
- - -# alias - fill -fill = wrap - - -
[docs]def pad(text, width=None, align="c", fillchar=" "): - """ - Pads to a given width. - - Args: - text (str): Text to pad. - width (int, optional): The width to pad to, in characters. - align (str, optional): This is one of 'c', 'l' or 'r' (center, - left or right). - fillchar (str, optional): The character to fill with. - - Returns: - text (str): The padded text. - - """ - width = width if width else settings.CLIENT_DEFAULT_WIDTH - align = align if align in ("c", "l", "r") else "c" - fillchar = fillchar[0] if fillchar else " " - if align == "l": - return text.ljust(width, fillchar) - elif align == "r": - return text.rjust(width, fillchar) - else: - return text.center(width, fillchar)
- - -
[docs]def crop(text, width=None, suffix="[...]"): - """ - Crop text to a certain width, throwing away text from too-long - lines. - - Args: - text (str): Text to crop. - width (int, optional): Width of line to crop, in characters. - suffix (str, optional): This is appended to the end of cropped - lines to show that the line actually continues. Cropping - will be done so that the suffix will also fit within the - given width. If width is too small to fit both crop and - suffix, the suffix will be dropped. - - Returns: - text (str): The cropped text. - - """ - width = width if width else settings.CLIENT_DEFAULT_WIDTH - ltext = len(text) - if ltext <= width: - return text - else: - lsuffix = len(suffix) - text = text[:width] if lsuffix >= width else "%s%s" % (text[: width - lsuffix], suffix) - return to_str(text)
- - -
[docs]def dedent(text, baseline_index=None): - """ - Safely clean all whitespace at the left of a paragraph. - - Args: - text (str): The text to dedent. - baseline_index (int or None, optional): Which row to use as a 'base' - for the indentation. Lines will be dedented to this level but - no further. If None, indent so as to completely deindent the - least indented text. - - Returns: - text (str): Dedented string. - - Notes: - This is useful for preserving triple-quoted string indentation - while still shifting it all to be next to the left edge of the - display. - - """ - if not text: - return "" - if baseline_index is None: - return textwrap.dedent(text) - else: - lines = text.split("\n") - baseline = lines[baseline_index] - spaceremove = len(baseline) - len(baseline.lstrip(" ")) - return "\n".join( - line[min(spaceremove, len(line) - len(line.lstrip(" "))) :] for line in lines - )
- - -
[docs]def justify(text, width=None, align="f", indent=0): - """ - Fully justify a text so that it fits inside `width`. When using - full justification (default) this will be done by padding between - words with extra whitespace where necessary. Paragraphs will - be retained. - - Args: - text (str): Text to justify. - width (int, optional): The length of each line, in characters. - align (str, optional): The alignment, 'l', 'c', 'r' or 'f' - for left, center, right or full justification respectively. - indent (int, optional): Number of characters indentation of - entire justified text block. - - Returns: - justified (str): The justified and indented block of text. - - """ - width = width if width else settings.CLIENT_DEFAULT_WIDTH - - def _process_line(line): - """ - helper function that distributes extra spaces between words. The number - of gaps is nwords - 1 but must be at least 1 for single-word lines. We - distribute odd spaces randomly to one of the gaps. - """ - line_rest = width - (wlen + ngaps) - gap = " " # minimum gap between words - if line_rest > 0: - if align == "l": - if line[-1] == "\n\n": - line[-1] = " " * (line_rest - 1) + "\n" + " " * width + "\n" + " " * width - else: - line[-1] += " " * line_rest - elif align == "r": - line[0] = " " * line_rest + line[0] - elif align == "c": - pad = " " * (line_rest // 2) - line[0] = pad + line[0] - if line[-1] == "\n\n": - line[-1] += ( - pad + " " * (line_rest % 2 - 1) + "\n" + " " * width + "\n" + " " * width - ) - else: - line[-1] = line[-1] + pad + " " * (line_rest % 2) - else: # align 'f' - gap += " " * (line_rest // max(1, ngaps)) - rest_gap = line_rest % max(1, ngaps) - for i in range(rest_gap): - line[i] += " " - elif not any(line): - return [" " * width] - return gap.join(line) - - # split into paragraphs and words - paragraphs = re.split("\n\s*?\n", text, re.MULTILINE) - words = [] - for ip, paragraph in enumerate(paragraphs): - if ip > 0: - words.append(("\n", 0)) - words.extend((word, len(word)) for word in paragraph.split()) - ngaps, wlen, line = 0, 0, [] - - lines = [] - while words: - if not line: - # start a new line - word = words.pop(0) - wlen = word[1] - line.append(word[0]) - elif (words[0][1] + wlen + ngaps) >= width: - # next word would exceed word length of line + smallest gaps - lines.append(_process_line(line)) - ngaps, wlen, line = 0, 0, [] - else: - # put a new word on the line - word = words.pop(0) - line.append(word[0]) - if word[1] == 0: - # a new paragraph, process immediately - lines.append(_process_line(line)) - ngaps, wlen, line = 0, 0, [] - else: - wlen += word[1] - ngaps += 1 - - if line: # catch any line left behind - lines.append(_process_line(line)) - indentstring = " " * indent - return "\n".join([indentstring + line for line in lines])
- - -
[docs]def columnize(string, columns=2, spacing=4, align="l", width=None): - """ - Break a string into a number of columns, using as little - vertical space as possible. - - Args: - string (str): The string to columnize. - columns (int, optional): The number of columns to use. - spacing (int, optional): How much space to have between columns. - width (int, optional): The max width of the columns. - Defaults to client's default width. - - Returns: - columns (str): Text divided into columns. - - Raises: - RuntimeError: If given invalid values. - - """ - columns = max(1, columns) - spacing = max(1, spacing) - width = width if width else settings.CLIENT_DEFAULT_WIDTH - - w_spaces = (columns - 1) * spacing - w_txt = max(1, width - w_spaces) - - if w_spaces + columns > width: # require at least 1 char per column - raise RuntimeError("Width too small to fit columns") - - colwidth = int(w_txt / (1.0 * columns)) - - # first make a single column which we then split - onecol = justify(string, width=colwidth, align=align) - onecol = onecol.split("\n") - - nrows, dangling = divmod(len(onecol), columns) - nrows = [nrows + 1 if i < dangling else nrows for i in range(columns)] - - height = max(nrows) - cols = [] - istart = 0 - for irows in nrows: - cols.append(onecol[istart : istart + irows]) - istart = istart + irows - for col in cols: - if len(col) < height: - col.append(" " * colwidth) - - sep = " " * spacing - rows = [] - for irow in range(height): - rows.append(sep.join(col[irow] for col in cols)) - - return "\n".join(rows)
- - -
[docs]def iter_to_string(initer, endsep="and", addquote=False): - """ - This pretty-formats an iterable list as string output, adding an optional - alternative separator to the second to last entry. If `addquote` - is `True`, the outgoing strings will be surrounded by quotes. - - Args: - initer (any): Usually an iterable to print. Each element must be possible to - present with a string. Note that if this is a generator, it will be - consumed by this operation. - endsep (str, optional): If set, the last item separator will - be replaced with this value. - addquote (bool, optional): This will surround all outgoing - values with double quotes. - - Returns: - liststr (str): The list represented as a string. - - Examples: - - ```python - # no endsep: - [1,2,3] -> '1, 2, 3' - # with endsep=='and': - [1,2,3] -> '1, 2 and 3' - # with addquote and endsep - [1,2,3] -> '"1", "2" and "3"' - ``` - - """ - if not endsep: - endsep = "," - else: - endsep = " " + endsep - if not initer: - return "" - initer = tuple(str(val) for val in make_iter(initer)) - if addquote: - if len(initer) == 1: - return '"%s"' % initer[0] - return ", ".join('"%s"' % v for v in initer[:-1]) + "%s %s" % (endsep, '"%s"' % initer[-1]) - else: - if len(initer) == 1: - return str(initer[0]) - return ", ".join(str(v) for v in initer[:-1]) + "%s %s" % (endsep, initer[-1])
- - -# legacy alias -list_to_string = iter_to_string - - -
[docs]def wildcard_to_regexp(instring): - """ - Converts a player-supplied string that may have wildcards in it to - regular expressions. This is useful for name matching. - - Args: - instring (string): A string that may potentially contain - wildcards (`*` or `?`). - - Returns: - regex (str): A string where wildcards were replaced with - regular expressions. - - """ - regexp_string = "" - - # If the string starts with an asterisk, we can't impose the beginning of - # string (^) limiter. - if instring[0] != "*": - regexp_string += "^" - - # Replace any occurances of * or ? with the appropriate groups. - regexp_string += instring.replace("*", "(.*)").replace("?", "(.{1})") - - # If there's an asterisk at the end of the string, we can't impose the - # end of string ($) limiter. - if instring[-1] != "*": - regexp_string += "$" - - return regexp_string
- - -
[docs]def time_format(seconds, style=0): - """ - Function to return a 'prettified' version of a value in seconds. - - Args: - seconds (int): Number if seconds to format. - style (int): One of the following styles: - 0. "1d 08:30" - 1. "1d" - 2. "1 day, 8 hours, 30 minutes" - 3. "1 day, 8 hours, 30 minutes, 10 seconds" - 4. highest unit (like "3 years" or "8 months" or "1 second") - Returns: - timeformatted (str): A pretty time string. - """ - if seconds < 0: - seconds = 0 - else: - # We'll just use integer math, no need for decimal precision. - seconds = int(seconds) - - days = seconds // 86400 - seconds -= days * 86400 - hours = seconds // 3600 - seconds -= hours * 3600 - minutes = seconds // 60 - seconds -= minutes * 60 - - retval = "" - if style == 0: - """ - Standard colon-style output. - """ - if days > 0: - retval = "%id %02i:%02i" % (days, hours, minutes) - else: - retval = "%02i:%02i" % (hours, minutes) - return retval - - elif style == 1: - """ - Simple, abbreviated form that only shows the highest time amount. - """ - if days > 0: - return "%id" % (days,) - elif hours > 0: - return "%ih" % (hours,) - elif minutes > 0: - return "%im" % (minutes,) - else: - return "%is" % (seconds,) - elif style == 2: - """ - Full-detailed, long-winded format. We ignore seconds. - """ - days_str = hours_str = "" - minutes_str = "0 minutes" - - if days > 0: - if days == 1: - days_str = "%i day, " % days - else: - days_str = "%i days, " % days - if days or hours > 0: - if hours == 1: - hours_str = "%i hour, " % hours - else: - hours_str = "%i hours, " % hours - if hours or minutes > 0: - if minutes == 1: - minutes_str = "%i minute " % minutes - else: - minutes_str = "%i minutes " % minutes - retval = "%s%s%s" % (days_str, hours_str, minutes_str) - elif style == 3: - """ - Full-detailed, long-winded format. Includes seconds. - """ - days_str = hours_str = minutes_str = seconds_str = "" - if days > 0: - if days == 1: - days_str = "%i day, " % days - else: - days_str = "%i days, " % days - if days or hours > 0: - if hours == 1: - hours_str = "%i hour, " % hours - else: - hours_str = "%i hours, " % hours - if hours or minutes > 0: - if minutes == 1: - minutes_str = "%i minute " % minutes - else: - minutes_str = "%i minutes " % minutes - if minutes or seconds > 0: - if seconds == 1: - seconds_str = "%i second " % seconds - else: - seconds_str = "%i seconds " % seconds - retval = "%s%s%s%s" % (days_str, hours_str, minutes_str, seconds_str) - elif style == 4: - """ - Only return the highest unit. - """ - if days >= 730: # Several years - return "{} years".format(days // 365) - elif days >= 365: # One year - return "a year" - elif days >= 62: # Several months - return "{} months".format(days // 31) - elif days >= 31: # One month - return "a month" - elif days >= 2: # Several days - return "{} days".format(days) - elif days > 0: - return "a day" - elif hours >= 2: # Several hours - return "{} hours".format(hours) - elif hours > 0: # One hour - return "an hour" - elif minutes >= 2: # Several minutes - return "{} minutes".format(minutes) - elif minutes > 0: # One minute - return "a minute" - elif seconds >= 2: # Several seconds - return "{} seconds".format(seconds) - elif seconds == 1: - return "a second" - else: - return "0 seconds" - else: - raise ValueError("Unknown style for time format: %s" % style) - - return retval.strip()
- - -
[docs]def datetime_format(dtobj): - """ - Pretty-prints the time since a given time. - - Args: - dtobj (datetime): An datetime object, e.g. from Django's - `DateTimeField`. - - Returns: - deltatime (str): A string describing how long ago `dtobj` - took place. - - """ - - now = timezone.now() - - if dtobj.year < now.year: - # another year (Apr 5, 2019) - timestring = dtobj.strftime(f"%b {dtobj.day}, %Y") - elif dtobj.date() < now.date(): - # another date, same year (Apr 5) - timestring = dtobj.strftime(f"%b {dtobj.day}") - elif dtobj.hour < now.hour - 1: - # same day, more than 1 hour ago (10:45) - timestring = dtobj.strftime("%H:%M") - else: - # same day, less than 1 hour ago (10:45:33) - timestring = dtobj.strftime("%H:%M:%S") - return timestring
- - -
[docs]def host_os_is(osname): - """ - Check to see if the host OS matches the query. - - Args: - osname (str): Common names are "posix" (linux/unix/mac) and - "nt" (windows). - - Args: - is_os (bool): If the os matches or not. - - """ - return os.name == osname
- - -
[docs]def get_evennia_version(mode="long"): - """ - Helper method for getting the current evennia version. - - Args: - mode (str, optional): One of: - - long: 0.9.0 rev342453534 - - short: 0.9.0 - - pretty: Evennia 0.9.0 - - Returns: - version (str): The version string. - - """ - import evennia - - vers = evennia.__version__ - if mode == "short": - return vers.split()[0].strip() - elif mode == "pretty": - vers = vers.split()[0].strip() - return f"Evennia {vers}" - else: # mode "long": - return vers
- - -
[docs]def pypath_to_realpath(python_path, file_ending=".py", pypath_prefixes=None): - """ - Converts a dotted Python path to an absolute path under the - Evennia library directory or under the current game directory. - - Args: - python_path (str): A dot-python path - file_ending (str): A file ending, including the period. - pypath_prefixes (list): A list of paths to test for existence. These - should be on python.path form. EVENNIA_DIR and GAME_DIR are automatically - checked, they need not be added to this list. - - Returns: - abspaths (list): All existing, absolute paths created by - converting `python_path` to an absolute paths and/or - prepending `python_path` by `settings.EVENNIA_DIR`, - `settings.GAME_DIR` and by`pypath_prefixes` respectively. - - Notes: - This will also try a few combinations of paths to allow cases - where pypath is given including the "evennia." or "mygame." - prefixes. - - """ - path = python_path.strip().split(".") - plong = osjoin(*path) + file_ending - pshort = ( - osjoin(*path[1:]) + file_ending if len(path) > 1 else plong - ) # in case we had evennia. or mygame. - prefixlong = ( - [osjoin(*ppath.strip().split(".")) for ppath in make_iter(pypath_prefixes)] - if pypath_prefixes - else [] - ) - prefixshort = ( - [ - osjoin(*ppath.strip().split(".")[1:]) - for ppath in make_iter(pypath_prefixes) - if len(ppath.strip().split(".")) > 1 - ] - if pypath_prefixes - else [] - ) - paths = ( - [plong] - + [osjoin(_EVENNIA_DIR, prefix, plong) for prefix in prefixlong] - + [osjoin(_GAME_DIR, prefix, plong) for prefix in prefixlong] - + [osjoin(_EVENNIA_DIR, prefix, plong) for prefix in prefixshort] - + [osjoin(_GAME_DIR, prefix, plong) for prefix in prefixshort] - + [osjoin(_EVENNIA_DIR, plong), osjoin(_GAME_DIR, plong)] - + [osjoin(_EVENNIA_DIR, prefix, pshort) for prefix in prefixshort] - + [osjoin(_GAME_DIR, prefix, pshort) for prefix in prefixshort] - + [osjoin(_EVENNIA_DIR, prefix, pshort) for prefix in prefixlong] - + [osjoin(_GAME_DIR, prefix, pshort) for prefix in prefixlong] - + [osjoin(_EVENNIA_DIR, pshort), osjoin(_GAME_DIR, pshort)] - ) - # filter out non-existing paths - return list(set(p for p in paths if os.path.isfile(p)))
- - -
[docs]def dbref(inp, reqhash=True): - """ - Converts/checks if input is a valid dbref. - - Args: - inp (int, str): A database ref on the form N or #N. - reqhash (bool, optional): Require the #N form to accept - input as a valid dbref. - - Returns: - dbref (int or None): The integer part of the dbref or `None` - if input was not a valid dbref. - - """ - if reqhash: - num = ( - int(inp.lstrip("#")) - if (isinstance(inp, str) and inp.startswith("#") and inp.lstrip("#").isdigit()) - else None - ) - return num if isinstance(num, int) and num > 0 else None - elif isinstance(inp, str): - inp = inp.lstrip("#") - return int(inp) if inp.isdigit() and int(inp) > 0 else None - else: - return inp if isinstance(inp, int) else None
- - -
[docs]def dbref_to_obj(inp, objclass, raise_errors=True): - """ - Convert a #dbref to a valid object. - - Args: - inp (str or int): A valid #dbref. - objclass (class): A valid django model to filter against. - raise_errors (bool, optional): Whether to raise errors - or return `None` on errors. - - Returns: - obj (Object or None): An entity loaded from the dbref. - - Raises: - Exception: If `raise_errors` is `True` and - `objclass.objects.get(id=dbref)` did not return a valid - object. - - """ - dbid = dbref(inp) - if not dbid: - # we only convert #dbrefs - return inp - try: - if dbid < 0: - return None - except ValueError: - return None - - # if we get to this point, inp is an integer dbref; get the matching object - try: - return objclass.objects.get(id=dbid) - except Exception: - if raise_errors: - raise - return inp
- - -# legacy alias -dbid_to_obj = dbref_to_obj - - -# some direct translations for the latinify -_UNICODE_MAP = { - "EM DASH": "-", - "FIGURE DASH": "-", - "EN DASH": "-", - "HORIZONTAL BAR": "-", - "HORIZONTAL ELLIPSIS": "...", - "LEFT SINGLE QUOTATION MARK": "'", - "RIGHT SINGLE QUOTATION MARK": "'", - "LEFT DOUBLE QUOTATION MARK": '"', - "RIGHT DOUBLE QUOTATION MARK": '"', -} - - -
[docs]def latinify(string, default="?", pure_ascii=False): - """ - Convert a unicode string to "safe" ascii/latin-1 characters. - This is used as a last resort when normal encoding does not work. - - Arguments: - string (str): A string to convert to 'safe characters' convertable - to an latin-1 bytestring later. - default (str, optional): Characters resisting mapping will be replaced - with this character or string. The intent is to apply an encode operation - on the string soon after. - - Returns: - string (str): A 'latinified' string where each unicode character has been - replaced with a 'safe' equivalent available in the ascii/latin-1 charset. - Notes: - This is inspired by the gist by Ricardo Murri: - https://gist.github.com/riccardomurri/3c3ccec30f037be174d3 - - """ - - from unicodedata import name - - if isinstance(string, bytes): - string = string.decode("utf8") - - converted = [] - for unich in iter(string): - try: - ch = unich.encode("utf8").decode("ascii") - except UnicodeDecodeError: - # deduce a latin letter equivalent from the Unicode data - # point name; e.g., since `name(u'á') == 'LATIN SMALL - # LETTER A WITH ACUTE'` translate `á` to `a`. However, in - # some cases the unicode name is still "LATIN LETTER" - # although no direct equivalent in the Latin alphabet - # exists (e.g., Þ, "LATIN CAPITAL LETTER THORN") -- we can - # avoid these cases by checking that the letter name is - # composed of one letter only. - # We also supply some direct-translations for some particular - # common cases. - what = name(unich) - if what in _UNICODE_MAP: - ch = _UNICODE_MAP[what] - else: - what = what.split() - if what[0] == "LATIN" and what[2] == "LETTER" and len(what[3]) == 1: - ch = what[3].lower() if what[1] == "SMALL" else what[3].upper() - else: - ch = default - converted.append(chr(ord(ch))) - return "".join(converted)
- - -
[docs]def to_bytes(text, session=None): - """ - Try to encode the given text to bytes, using encodings from settings or from Session. Will - always return a bytes, even if given something that is not str or bytes. - - Args: - text (any): The text to encode to bytes. If bytes, return unchanged. If not a str, convert - to str before converting. - session (Session, optional): A Session to get encoding info from. Will try this before - falling back to settings.ENCODINGS. - - Returns: - encoded_text (bytes): the encoded text following the session's protocol flag followed by the - encodings specified in settings.ENCODINGS. If all attempt fail, log the error and send - the text with "?" in place of problematic characters. If the specified encoding cannot - be found, the protocol flag is reset to utf-8. In any case, returns bytes. - - Note: - If `text` is already bytes, return it as is. - - """ - if isinstance(text, bytes): - return text - if not isinstance(text, str): - # convert to a str representation before encoding - try: - text = str(text) - except Exception: - text = repr(text) - - default_encoding = session.protocol_flags.get("ENCODING", "utf-8") if session else "utf-8" - try: - return text.encode(default_encoding) - except (LookupError, UnicodeEncodeError): - for encoding in settings.ENCODINGS: - try: - return text.encode(encoding) - except (LookupError, UnicodeEncodeError): - pass - # no valid encoding found. Replace unconvertable parts with ? - return text.encode(default_encoding, errors="replace")
- - -
[docs]def to_str(text, session=None): - """ - Try to decode a bytestream to a python str, using encoding schemas from settings - or from Session. Will always return a str(), also if not given a str/bytes. - - Args: - text (any): The text to encode to bytes. If a str, return it. If also not bytes, convert - to str using str() or repr() as a fallback. - session (Session, optional): A Session to get encoding info from. Will try this before - falling back to settings.ENCODINGS. - - Returns: - decoded_text (str): The decoded text. - - Note: - If `text` is already str, return it as is. - """ - if isinstance(text, str): - return text - if not isinstance(text, bytes): - # not a byte, convert directly to str - try: - return str(text) - except Exception: - return repr(text) - - default_encoding = session.protocol_flags.get("ENCODING", "utf-8") if session else "utf-8" - try: - return text.decode(default_encoding) - except (LookupError, UnicodeDecodeError): - for encoding in settings.ENCODINGS: - try: - return text.decode(encoding) - except (LookupError, UnicodeDecodeError): - pass - # no valid encoding found. Replace unconvertable parts with ? - return text.decode(default_encoding, errors="replace")
- - -
[docs]def validate_email_address(emailaddress): - """ - Checks if an email address is syntactically correct. Makes use - of the django email-validator for consistency. - - Args: - emailaddress (str): Email address to validate. - - Returns: - bool: If this is a valid email or not. - - """ - try: - django_validate_email(str(emailaddress)) - except DjangoValidationError: - return False - except Exception: - logger.log_trace() - return False - else: - return True
- - -
[docs]def inherits_from(obj, parent): - """ - Takes an object and tries to determine if it inherits at *any* - distance from parent. - - Args: - obj (any): Object to analyze. This may be either an instance - or a class. - parent (any): Can be either instance, class or python path to class. - - Returns: - inherits_from (bool): If `parent` is a parent to `obj` or not. - - Notes: - What differs this function from e.g. `isinstance()` is that `obj` - may be both an instance and a class, and parent may be an - instance, a class, or the python path to a class (counting from - the evennia root directory). - - """ - - if callable(obj): - # this is a class - obj_paths = ["%s.%s" % (mod.__module__, mod.__name__) for mod in obj.mro()] - else: - obj_paths = ["%s.%s" % (mod.__module__, mod.__name__) for mod in obj.__class__.mro()] - - if isinstance(parent, str): - # a given string path, for direct matching - parent_path = parent - elif callable(parent): - # this is a class - parent_path = "%s.%s" % (parent.__module__, parent.__name__) - else: - parent_path = "%s.%s" % (parent.__class__.__module__, parent.__class__.__name__) - return any(1 for obj_path in obj_paths if obj_path == parent_path)
- - -
[docs]def server_services(): - """ - Lists all services active on the Server. Observe that since - services are launched in memory, this function will only return - any results if called from inside the game. - - Returns: - services (dict): A dict of available services. - - """ - from evennia.server.sessionhandler import SESSIONS - - if hasattr(SESSIONS, "server") and hasattr(SESSIONS.server, "services"): - server = SESSIONS.server.services.namedServices - else: - # This function must be called from inside the evennia process. - server = {} - del SESSIONS - return server
- - -
[docs]def uses_database(name="sqlite3"): - """ - Checks if the game is currently using a given database. This is a - shortcut to having to use the full backend name. - - Args: - name (str): One of 'sqlite3', 'mysql', 'postgresql' - or 'oracle'. - - Returns: - uses (bool): If the given database is used or not. - - """ - try: - engine = settings.DATABASES["default"]["ENGINE"] - except KeyError: - engine = settings.DATABASE_ENGINE - return engine == "django.db.backends.%s" % name
- - -_TASK_HANDLER = None - - -
[docs]def delay(timedelay, callback, *args, **kwargs): - """ - Delay the calling of a callback (function). - - Args: - timedelay (int or float): The delay in seconds. - callback (callable): Will be called as `callback(*args, **kwargs)` - after `timedelay` seconds. - args (any): Will be used as arguments to callback. - - Keyword Args: - persistent (bool, optional): If True the delay remains after a server restart. - persistent is False by default. - any (any): Will be used as keyword arguments to callback. - - Returns: - task (TaskHandlerTask): An instance of a task. - Refer to, evennia.scripts.taskhandler.TaskHandlerTask - - Note: - The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will - be called for persistent or non-persistent tasks. - If persistent is set to True, the callback, its arguments - and other keyword arguments will be saved (serialized) in the database, - assuming they can be. The callback will be executed even after - a server restart/reload, taking into account the specified delay - (and server down time). - Keep in mind that persistent tasks arguments and callback should not - use memory references. - If persistent is set to True the delay function will return an int - which is the task's id itended for use with TASK_HANDLER's do_task - and remove methods. - All persistent tasks whose time delays have passed will be called on server startup. - - """ - global _TASK_HANDLER - # Do some imports here to avoid circular import and speed things up - if _TASK_HANDLER is None: - from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER - return _TASK_HANDLER.add(timedelay, callback, *args, **kwargs)
- - -_PPOOL = None -_PCMD = None -_PROC_ERR = "A process has ended with a probable error condition: process ended by signal 9." - - -
[docs]def run_async(to_execute, *args, **kwargs): - """ - Runs a function or executes a code snippet asynchronously. - - Args: - to_execute (callable): If this is a callable, it will be - executed with `*args` and non-reserved `**kwargs` as arguments. - The callable will be executed using ProcPool, or in a thread - if ProcPool is not available. - - Keyword Args: - at_return (callable): Should point to a callable with one - argument. It will be called with the return value from - to_execute. - at_return_kwargs (dict): This dictionary will be used as - keyword arguments to the at_return callback. - at_err (callable): This will be called with a Failure instance - if there is an error in to_execute. - at_err_kwargs (dict): This dictionary will be used as keyword - arguments to the at_err errback. - - Notes: - All other `*args` and `**kwargs` will be passed on to - `to_execute`. Run_async will relay executed code to a thread - or procpool. - - Use this function with restrain and only for features/commands - that you know has no influence on the cause-and-effect order of your - game (commands given after the async function might be executed before - it has finished). Accessing the same property from different threads - can lead to unpredicted behaviour if you are not careful (this is called a - "race condition"). - - Also note that some databases, notably sqlite3, don't support access from - multiple threads simultaneously, so if you do heavy database access from - your `to_execute` under sqlite3 you will probably run very slow or even get - tracebacks. - - """ - - # handle special reserved input kwargs - callback = kwargs.pop("at_return", None) - errback = kwargs.pop("at_err", None) - callback_kwargs = kwargs.pop("at_return_kwargs", {}) - errback_kwargs = kwargs.pop("at_err_kwargs", {}) - - if callable(to_execute): - # no process pool available, fall back to old deferToThread mechanism. - deferred = threads.deferToThread(to_execute, *args, **kwargs) - else: - # no appropriate input for this server setup - raise RuntimeError("'%s' could not be handled by run_async" % to_execute) - - # attach callbacks - if callback: - deferred.addCallback(callback, **callback_kwargs) - deferred.addErrback(errback, **errback_kwargs)
- - -
[docs]def check_evennia_dependencies(): - """ - Checks the versions of Evennia's dependencies including making - some checks for runtime libraries. - - Returns: - result (bool): `False` if a show-stopping version mismatch is - found. - - """ - - # check main dependencies - from evennia.server.evennia_launcher import check_main_evennia_dependencies - - not_error = check_main_evennia_dependencies() - - errstring = "" - # South is no longer used ... - if "south" in settings.INSTALLED_APPS: - errstring += ( - "\n ERROR: 'south' found in settings.INSTALLED_APPS. " - "\n South is no longer used. If this was added manually, remove it." - ) - not_error = False - # IRC support - if settings.IRC_ENABLED: - try: - import twisted.words - - twisted.words # set to avoid debug info about not-used import - except ImportError: - errstring += ( - "\n ERROR: IRC is enabled, but twisted.words is not installed. Please install it." - "\n Linux Debian/Ubuntu users should install package 'python-twisted-words', others" - "\n can get it from http://twistedmatrix.com/trac/wiki/TwistedWords." - ) - not_error = False - errstring = errstring.strip() - if errstring: - mlen = max(len(line) for line in errstring.split("\n")) - logger.log_err("%s\n%s\n%s" % ("-" * mlen, errstring, "-" * mlen)) - return not_error
- - -
[docs]def has_parent(basepath, obj): - """ - Checks if `basepath` is somewhere in `obj`'s parent tree. - - Args: - basepath (str): Python dotpath to compare against obj path. - obj (any): Object whose path is to be checked. - - Returns: - has_parent (bool): If the check was successful or not. - - """ - try: - return any( - cls - for cls in obj.__class__.mro() - if basepath == "%s.%s" % (cls.__module__, cls.__name__) - ) - except (TypeError, AttributeError): - # this can occur if we tried to store a class object, not an - # instance. Not sure if one should defend against this. - return False
- - -
[docs]def mod_import_from_path(path): - """ - Load a Python module at the specified path. - - Args: - path (str): An absolute path to a Python module to load. - - Returns: - (module or None): An imported module if the path was a valid - Python module. Returns `None` if the import failed. - - """ - if not os.path.isabs(path): - path = os.path.abspath(path) - dirpath, filename = path.rsplit(os.path.sep, 1) - modname = filename.rstrip(".py") - - try: - return importlib.machinery.SourceFileLoader(modname, path).load_module() - except OSError: - logger.log_trace(f"Could not find module '{modname}' ({modname}.py) at path '{dirpath}'") - return None
- - -
[docs]def mod_import(module): - """ - A generic Python module loader. - - Args: - module (str, module): This can be either a Python path - (dot-notation like `evennia.objects.models`), an absolute path - (e.g. `/home/eve/evennia/evennia/objects/models.py`) or an - already imported module object (e.g. `models`) - Returns: - (module or None): An imported module. If the input argument was - already a module, this is returned as-is, otherwise the path is - parsed and imported. Returns `None` and logs error if import failed. - - """ - if not module: - return None - - if isinstance(module, types.ModuleType): - # if this is already a module, we are done - return module - - if module.endswith(".py") and os.path.exists(module): - return mod_import_from_path(module) - - try: - return importlib.import_module(module) - except ImportError: - return None
- - -
[docs]def all_from_module(module): - """ - Return all global-level variables defined in a module. - - Args: - module (str, module): This can be either a Python path - (dot-notation like `evennia.objects.models`), an absolute path - (e.g. `/home/eve/evennia/evennia/objects.models.py`) or an - already imported module object (e.g. `models`) - - Returns: - variables (dict): A dict of {variablename: variable} for all - variables in the given module. - - Notes: - Ignores modules and variable names starting with an underscore. - - """ - mod = mod_import(module) - if not mod: - return {} - # make sure to only return variables actually defined in this - # module if available (try to avoid not imports) - members = getmembers(mod, predicate=lambda obj: getmodule(obj) in (mod, None)) - return dict((key, val) for key, val in members if not key.startswith("_"))
- - -
[docs]def callables_from_module(module): - """ - Return all global-level callables defined in a module. - - Args: - module (str, module): A python-path to a module or an actual - module object. - - Returns: - callables (dict): A dict of {name: callable, ...} from the module. - - Notes: - Will ignore callables whose names start with underscore "_". - - """ - mod = mod_import(module) - if not mod: - return {} - # make sure to only return callables actually defined in this module (not imports) - members = getmembers(mod, predicate=lambda obj: callable(obj) and getmodule(obj) == mod) - return dict((key, val) for key, val in members if not key.startswith("_"))
- - -
[docs]def variable_from_module(module, variable=None, default=None): - """ - Retrieve a variable or list of variables from a module. The - variable(s) must be defined globally in the module. If no variable - is given (or a list entry is `None`), all global variables are - extracted from the module. - - Args: - module (string or module): Python path, absolute path or a module. - variable (string or iterable, optional): Single variable name or iterable - of variable names to extract. If not given, all variables in - the module will be returned. - default (string, optional): Default value to use if a variable fails to - be extracted. Ignored if `variable` is not given. - - Returns: - variables (value or list): A single value or a list of values - depending on if `variable` is given or not. Errors in lists - are replaced by the `default` argument. - - """ - - if not module: - return default - - mod = mod_import(module) - - if not mod: - return default - - if variable: - result = [] - for var in make_iter(variable): - if var: - # try to pick a named variable - result.append(mod.__dict__.get(var, default)) - else: - # get all - result = [ - val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val)) - ] - - if len(result) == 1: - return result[0] - return result
- - -
[docs]def string_from_module(module, variable=None, default=None): - """ - This is a wrapper for `variable_from_module` that requires return - value to be a string to pass. It's primarily used by login screen. - - Args: - module (string or module): Python path, absolute path or a module. - variable (string or iterable, optional): Single variable name or iterable - of variable names to extract. If not given, all variables in - the module will be returned. - default (string, optional): Default value to use if a variable fails to - be extracted. Ignored if `variable` is not given. - - Returns: - variables (value or list): A single (string) value or a list of values - depending on if `variable` is given or not. Errors in lists (such - as the value not being a string) are replaced by the `default` argument. - - """ - val = variable_from_module(module, variable=variable, default=default) - if val: - if variable: - return val - else: - result = [v for v in make_iter(val) if isinstance(v, str)] - return result if result else default - return default
- - -
[docs]def random_string_from_module(module): - """ - Returns a random global string from a module. - - Args: - module (string or module): Python path, absolute path or a module. - - Returns: - random (string): A random stribg variable from `module`. - """ - return random.choice(string_from_module(module))
- - -
[docs]def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None): - """ - Import a variable based on a fuzzy path. First the literal - `path` will be tried, then all given `defaultpaths` will be - prepended to see a match is found. - - Args: - path (str): Full or partial python path. - variable (str): Name of variable to import from module. - default (string, optional): Default value to use if a variable fails to - be extracted. Ignored if `variable` is not given. - defaultpaths (iterable, options): Python paths to attempt in order if - importing directly from `path` doesn't work. - - Returns: - value (any): The variable imported from the module, or `default`, if - not found. - - """ - paths = [path] + make_iter(defaultpaths) - for modpath in paths: - try: - mod = importlib.import_module(modpath) - except ImportError as ex: - if not str(ex).startswith("No module named %s" % modpath): - # this means the module was found but it - # triggers an ImportError on import. - raise ex - return getattr(mod, variable, default) - return default
- - -
[docs]def class_from_module(path, defaultpaths=None, fallback=None): - """ - Return a class from a module, given the module's path. This is - primarily used to convert db_typeclass_path:s to classes. - - Args: - path (str): Full Python dot-path to module. - defaultpaths (iterable, optional): If a direct import from `path` fails, - try subsequent imports by prepending those paths to `path`. - fallback (str): If all other attempts fail, use this path as a fallback. - This is intended as a last-resport. In the example of Evennia - loading, this would be a path to a default parent class in the - evennia repo itself. - - Returns: - class (Class): An uninstatiated class recovered from path. - - Raises: - ImportError: If all loading failed. - - """ - cls = None - err = "" - if defaultpaths: - paths = ( - [path] + ["%s.%s" % (dpath, path) for dpath in make_iter(defaultpaths)] - if defaultpaths - else [] - ) - else: - paths = [path] - - for testpath in paths: - if "." in path: - testpath, clsname = testpath.rsplit(".", 1) - else: - raise ImportError("the path '%s' is not on the form modulepath.Classname." % path) - - try: - if not importlib.util.find_spec(testpath, package="evennia"): - continue - except ModuleNotFoundError: - continue - - try: - mod = importlib.import_module(testpath, package="evennia") - except ModuleNotFoundError: - err = traceback.format_exc(30) - break - - try: - cls = getattr(mod, clsname) - break - except AttributeError: - if len(trace()) > 2: - # AttributeError within the module, don't hide it - err = traceback.format_exc(30) - break - if not cls: - err = "\nCould not load typeclass '{}'{}".format( - path, " with the following traceback:\n" + err if err else "" - ) - if defaultpaths: - err += "\nPaths searched:\n %s" % "\n ".join(paths) - else: - err += "." - logger.log_err(err) - if fallback: - logger.log_warn(f"Falling back to {fallback}.") - return class_from_module(fallback) - else: - # even fallback fails - raise ImportError(err) - return cls
- - -# alias -object_from_module = class_from_module - - -
[docs]def init_new_account(account): - """ - Deprecated. - """ - from evennia.utils import logger - - logger.log_dep("evennia.utils.utils.init_new_account is DEPRECATED and should not be used.")
- - -
[docs]def string_similarity(string1, string2): - """ - This implements a "cosine-similarity" algorithm as described for example in - *Proceedings of the 22nd International Conference on Computation - Linguistics* (Coling 2008), pages 593-600, Manchester, August 2008. - The measure-vectors used is simply a "bag of words" type histogram - (but for letters). - - Args: - string1 (str): String to compare (may contain any number of words). - string2 (str): Second string to compare (any number of words). - - Returns: - similarity (float): A value 0...1 rating how similar the two - strings are. - - """ - vocabulary = set(list(string1 + string2)) - vec1 = [string1.count(v) for v in vocabulary] - vec2 = [string2.count(v) for v in vocabulary] - try: - return float(sum(vec1[i] * vec2[i] for i in range(len(vocabulary)))) / ( - math.sqrt(sum(v1 ** 2 for v1 in vec1)) * math.sqrt(sum(v2 ** 2 for v2 in vec2)) - ) - except ZeroDivisionError: - # can happen if empty-string cmdnames appear for some reason. - # This is a no-match. - return 0
- - -
[docs]def string_suggestions(string, vocabulary, cutoff=0.6, maxnum=3): - """ - Given a `string` and a `vocabulary`, return a match or a list of - suggestions based on string similarity. - - Args: - string (str): A string to search for. - vocabulary (iterable): A list of available strings. - cutoff (int, 0-1): Limit the similarity matches (the higher - the value, the more exact a match is required). - maxnum (int): Maximum number of suggestions to return. - - Returns: - suggestions (list): Suggestions from `vocabulary` with a - similarity-rating that higher than or equal to `cutoff`. - Could be empty if there are no matches. - - """ - return [ - tup[1] - for tup in sorted( - [(string_similarity(string, sugg), sugg) for sugg in vocabulary], - key=lambda tup: tup[0], - reverse=True, - ) - if tup[0] >= cutoff - ][:maxnum]
- - -
[docs]def string_partial_matching(alternatives, inp, ret_index=True): - """ - Partially matches a string based on a list of `alternatives`. - Matching is made from the start of each subword in each - alternative. Case is not important. So e.g. "bi sh sw" or just - "big" or "shiny" or "sw" will match "Big shiny sword". Scoring is - done to allow to separate by most common demoninator. You will get - multiple matches returned if appropriate. - - Args: - alternatives (list of str): A list of possible strings to - match. - inp (str): Search criterion. - ret_index (bool, optional): Return list of indices (from alternatives - array) instead of strings. - Returns: - matches (list): String-matches or indices if `ret_index` is `True`. - - """ - if not alternatives or not inp: - return [] - - matches = defaultdict(list) - inp_words = inp.lower().split() - for altindex, alt in enumerate(alternatives): - alt_words = alt.lower().split() - last_index = 0 - score = 0 - for inp_word in inp_words: - # loop over parts, making sure only to visit each part once - # (this will invalidate input in the wrong word order) - submatch = [ - last_index + alt_num - for alt_num, alt_word in enumerate(alt_words[last_index:]) - if alt_word.startswith(inp_word) - ] - if submatch: - last_index = min(submatch) + 1 - score += 1 - else: - score = 0 - break - if score: - if ret_index: - matches[score].append(altindex) - else: - matches[score].append(alt) - if matches: - return matches[max(matches)] - return []
- - -
[docs]def format_table(table, extra_space=1): - """ - Note: `evennia.utils.evtable` is more powerful than this, but this function - can be useful when the number of columns and rows are unknown and must be - calculated on the fly. - - Args. - table (list): A list of lists to represent columns in the - table: `[[val,val,val,...], [val,val,val,...], ...]`, where - each val will be placed on a separate row in the - column. All columns must have the same number of rows (some - positions may be empty though). - extra_space (int, optional): Sets how much *minimum* extra - padding (in characters) should be left between columns. - - Returns: - table (list): A list of lists representing the rows to print - out one by one. - - Notes: - The function formats the columns to be as wide as the widest member - of each column. - - Example: - :: - - ftable = format_table([[...], [...], ...]) - for ir, row in enumarate(ftable): - if ir == 0: - # make first row white - string += "\\\\n|w" + ""join(row) + "|n" - else: - string += "\\\\n" + "".join(row) - print(string) - - """ - if not table: - return [[]] - - max_widths = [max([len(str(val)) for val in col]) for col in table] - ftable = [] - for irow in range(len(table[0])): - ftable.append( - [ - str(col[irow]).ljust(max_widths[icol]) + " " * extra_space - for icol, col in enumerate(table) - ] - ) - return ftable
- - -
[docs]def get_evennia_pids(): - """ - Get the currently valid PIDs (Process IDs) of the Portal and - Server by trying to access a PID file. - - Returns: - server, portal (tuple): The PIDs of the respective processes, - or two `None` values if not found. - - Examples: - This can be used to determine if we are in a subprocess by - something like: - - ```python - self_pid = os.getpid() - server_pid, portal_pid = get_evennia_pids() - is_subprocess = self_pid not in (server_pid, portal_pid) - ``` - """ - server_pidfile = os.path.join(settings.GAME_DIR, "server.pid") - portal_pidfile = os.path.join(settings.GAME_DIR, "portal.pid") - server_pid, portal_pid = None, None - if os.path.exists(server_pidfile): - with open(server_pidfile, "r") as f: - server_pid = f.read() - if os.path.exists(portal_pidfile): - with open(portal_pidfile, "r") as f: - portal_pid = f.read() - if server_pid and portal_pid: - return int(server_pid), int(portal_pid) - return None, None
- - -
[docs]def deepsize(obj, max_depth=4): - """ - Get not only size of the given object, but also the size of - objects referenced by the object, down to `max_depth` distance - from the object. - - Args: - obj (object): the object to be measured. - max_depth (int, optional): maximum referential distance - from `obj` that `deepsize()` should cover for - measuring objects referenced by `obj`. - - Returns: - size (int): deepsize of `obj` in Bytes. - - Notes: - This measure is necessarily approximate since some - memory is shared between objects. The `max_depth` of 4 is roughly - tested to give reasonable size information about database models - and their handlers. - - """ - - def _recurse(o, dct, depth): - if 0 <= max_depth < depth: - return - for ref in gc.get_referents(o): - idr = id(ref) - if idr not in dct: - dct[idr] = (ref, sys.getsizeof(ref, default=0)) - _recurse(ref, dct, depth + 1) - - sizedict = {} - _recurse(obj, sizedict, 0) - size = sys.getsizeof(obj) + sum([p[1] for p in sizedict.values()]) - return size
- - -# lazy load handler -_missing = object() - - -
[docs]class lazy_property(object): - """ - Delays loading of property until first access. Credit goes to the - Implementation in the werkzeug suite: - http://werkzeug.pocoo.org/docs/utils/#werkzeug.utils.cached_property - - This should be used as a decorator in a class and in Evennia is - mainly used to lazy-load handlers: - - ```python - @lazy_property - def attributes(self): - return AttributeHandler(self) - ``` - - Once initialized, the `AttributeHandler` will be available as a - property "attributes" on the object. - - """ - -
[docs] def __init__(self, func, name=None, doc=None): - """Store all properties for now""" - self.__name__ = name or func.__name__ - self.__module__ = func.__module__ - self.__doc__ = doc or func.__doc__ - self.func = func
- - def __get__(self, obj, type=None): - """Triggers initialization""" - if obj is None: - return self - value = obj.__dict__.get(self.__name__, _missing) - if value is _missing: - value = self.func(obj) - obj.__dict__[self.__name__] = value - return value
- - -_STRIP_ANSI = None -_RE_CONTROL_CHAR = re.compile( - "[%s]" % re.escape("".join([chr(c) for c in range(0, 32)])) -) # + range(127,160)]))) - - -
[docs]def strip_control_sequences(string): - """ - Remove non-print text sequences. - - Args: - string (str): Text to strip. - - Returns. - text (str): Stripped text. - - """ - global _STRIP_ANSI - if not _STRIP_ANSI: - from evennia.utils.ansi import strip_raw_ansi as _STRIP_ANSI - return _RE_CONTROL_CHAR.sub("", _STRIP_ANSI(string))
- - -
[docs]def calledby(callerdepth=1): - """ - Only to be used for debug purposes. Insert this debug function in - another function; it will print which function called it. - - Args: - callerdepth (int): Must be larger than 0. When > 1, it will - print the caller of the caller etc. - - Returns: - calledby (str): A debug string detailing which routine called - us. - - """ - import inspect - - stack = inspect.stack() - # we must step one extra level back in stack since we don't want - # to include the call of this function itself. - callerdepth = min(max(2, callerdepth + 1), len(stack) - 1) - frame = inspect.stack()[callerdepth] - path = os.path.sep.join(frame[1].rsplit(os.path.sep, 2)[-2:]) - return "[called by '%s': %s:%s %s]" % (frame[3], path, frame[2], frame[4])
- - -
[docs]def m_len(target): - """ - Provides length checking for strings with MXP patterns, and falls - back to normal len for other objects. - - Args: - target (str): A string with potential MXP components - to search. - - Returns: - length (int): The length of `target`, ignoring MXP components. - - """ - # Would create circular import if in module root. - from evennia.utils.ansi import ANSI_PARSER - - if inherits_from(target, str) and "|lt" in target: - return len(ANSI_PARSER.strip_mxp(target)) - return len(target)
- - -
[docs]def display_len(target): - """ - Calculate the 'visible width' of text. This is not necessarily the same as the - number of characters in the case of certain asian characters. This will also - strip MXP patterns. - - Args: - target (any): Something to measure the length of. If a string, it will be - measured keeping asian-character and MXP links in mind. - - Return: - int: The visible width of the target. - - """ - # Would create circular import if in module root. - from evennia.utils.ansi import ANSI_PARSER - - if inherits_from(target, str): - # str or ANSIString - target = ANSI_PARSER.strip_mxp(target) - target = ANSI_PARSER.parse_ansi(target, strip_ansi=True) - extra_wide = ("F", "W") - return sum(2 if east_asian_width(char) in extra_wide else 1 for char in target) - else: - return len(target)
- - -# ------------------------------------------------------------------- -# Search handler function -# ------------------------------------------------------------------- -# -# Replace this hook function by changing settings.SEARCH_AT_RESULT. - - -
[docs]def at_search_result(matches, caller, query="", quiet=False, **kwargs): - """ - This is a generic hook for handling all processing of a search - result, including error reporting. This is also called by the cmdhandler - to manage errors in command lookup. - - Args: - matches (list): This is a list of 0, 1 or more typeclass - instances or Command instances, the matched result of the - search. If 0, a nomatch error should be echoed, and if >1, - multimatch errors should be given. Only if a single match - should the result pass through. - caller (Object): The object performing the search and/or which should - receive error messages. - query (str, optional): The search query used to produce `matches`. - quiet (bool, optional): If `True`, no messages will be echoed to caller - on errors. - - Keyword Args: - nofound_string (str): Replacement string to echo on a notfound error. - multimatch_string (str): Replacement string to echo on a multimatch error. - - Returns: - processed_result (Object or None): This is always a single result - or `None`. If `None`, any error reporting/handling should - already have happened. The returned object is of the type we are - checking multimatches for (e.g. Objects or Commands) - - """ - - error = "" - if not matches: - # no results. - error = kwargs.get("nofound_string") or _("Could not find '%s'." % query) - matches = None - elif len(matches) > 1: - multimatch_string = kwargs.get("multimatch_string") - if multimatch_string: - error = "%s\n" % multimatch_string - else: - error = _("More than one match for '{query}' (please narrow target):\n").format( - query=query - ) - - for num, result in enumerate(matches): - # we need to consider Commands, where .aliases is a list - aliases = result.aliases.all() if hasattr(result.aliases, "all") else result.aliases - # remove any pluralization aliases - aliases = [ - alias - for alias in aliases - if hasattr(alias, "category") and alias.category not in ("plural_key",) - ] - error += _MULTIMATCH_TEMPLATE.format( - number=num + 1, - name=result.get_display_name(caller) - if hasattr(result, "get_display_name") - else query, - aliases=" [%s]" % ";".join(aliases) if aliases else "", - info=result.get_extra_info(caller), - ) - matches = None - else: - # exactly one match - matches = matches[0] - - if error and not quiet: - caller.msg(error.strip()) - return matches
- - -
[docs]class LimitedSizeOrderedDict(OrderedDict): - """ - This dictionary subclass is both ordered and limited to a maximum - number of elements. Its main use is to hold a cache that can never - grow out of bounds. - - """ - -
[docs] def __init__(self, *args, **kwargs): - """ - Limited-size ordered dict. - - Keyword Args: - size_limit (int): Use this to limit the number of elements - alloweds to be in this list. By default the overshooting elements - will be removed in FIFO order. - fifo (bool, optional): Defaults to `True`. Remove overshooting elements - in FIFO order. If `False`, remove in FILO order. - - """ - super().__init__() - self.size_limit = kwargs.get("size_limit", None) - self.filo = not kwargs.get("fifo", True) # FIFO inverse of FILO - self._check_size()
- - def __eq__(self, other): - ret = super().__eq__(other) - if ret: - return ( - ret - and hasattr(other, "size_limit") - and self.size_limit == other.size_limit - and hasattr(other, "fifo") - and self.fifo == other.fifo - ) - return False - - def __ne__(self, other): - return not self.__eq__(other) - - def _check_size(self): - filo = self.filo - if self.size_limit is not None: - while self.size_limit < len(self): - self.popitem(last=filo) - - def __setitem__(self, key, value): - super().__setitem__(key, value) - self._check_size() - -
[docs] def update(self, *args, **kwargs): - super().update(*args, **kwargs) - self._check_size()
- - -
[docs]def get_game_dir_path(): - """ - This is called by settings_default in order to determine the path - of the game directory. - - Returns: - path (str): Full OS path to the game dir - - """ - # current working directory, assumed to be somewhere inside gamedir. - for _ in range(10): - gpath = os.getcwd() - if "server" in os.listdir(gpath): - if os.path.isfile(os.path.join("server", "conf", "settings.py")): - return gpath - else: - os.chdir(os.pardir) - raise RuntimeError("server/conf/settings.py not found: Must start from inside game dir.")
- - -
[docs]def get_all_typeclasses(parent=None): - """ - List available typeclasses from all available modules. - - Args: - parent (str, optional): If given, only return typeclasses inheriting (at any distance) - from this parent. - - Returns: - typeclasses (dict): On the form {"typeclass.path": typeclass, ...} - - Notes: - This will dynamicall retrieve all abstract django models inheriting at any distance - from the TypedObject base (aka a Typeclass) so it will work fine with any custom - classes being added. - - """ - from evennia.typeclasses.models import TypedObject - - typeclasses = { - "{}.{}".format(model.__module__, model.__name__): model - for model in apps.get_models() - if TypedObject in getmro(model) - } - if parent: - typeclasses = { - name: typeclass - for name, typeclass in typeclasses.items() - if inherits_from(typeclass, parent) - } - return typeclasses
- - -
[docs]def interactive(func): - """ - Decorator to make a method pausable with yield(seconds) and able to ask for - user-input with `response=yield(question)`. For the question-asking to - work, 'caller' must the name of an argument or kwarg to the decorated - function. - - Example: - :: - - @interactive - def myfunc(caller): - caller.msg("This is a test") - # wait five seconds - yield(5) - # ask user (caller) a question - response = yield("Do you want to continue waiting?") - if response == "yes": - yield(5) - else: - # ... - - Notes: - This turns the method into a generator! - - """ - from evennia.utils.evmenu import get_input - - def _process_input(caller, prompt, result, generator): - deferLater(reactor, 0, _iterate, generator, caller, response=result) - return False - - def _iterate(generator, caller=None, response=None): - try: - if response is None: - value = next(generator) - else: - value = generator.send(response) - except StopIteration: - pass - else: - if isinstance(value, (int, float)): - delay(value, _iterate, generator, caller=caller) - elif isinstance(value, str): - if not caller: - raise ValueError( - "To retrieve input from a @pausable method, that method " - "must be called with a 'caller' argument)" - ) - get_input(caller, value, _process_input, generator=generator) - else: - raise ValueError("yield(val) in a @pausable method must have an int/float as arg.") - - def decorator(*args, **kwargs): - argnames = inspect.getfullargspec(func).args - caller = None - if "caller" in argnames: - # we assume this is an object - caller = args[argnames.index("caller")] - - ret = func(*args, **kwargs) - if isinstance(ret, types.GeneratorType): - _iterate(ret, caller) - else: - return ret - - return decorator
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/utils/validatorfuncs.html b/docs/0.9.5/_modules/evennia/utils/validatorfuncs.html deleted file mode 100644 index 3f83255862..0000000000 --- a/docs/0.9.5/_modules/evennia/utils/validatorfuncs.html +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - - - evennia.utils.validatorfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.utils.validatorfuncs

-"""
-Contains all the validation functions.
-
-All validation functions must have a checker (probably a session) and entry arg.
-
-They can employ more paramters at your leisure.
-
-
-"""
-
-import re as _re
-import pytz as _pytz
-import datetime as _dt
-from evennia.utils.ansi import strip_ansi
-from evennia.utils.utils import string_partial_matching as _partial, validate_email_address
-from django.utils.translation import gettext as _
-
-_TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}
-
-
-
[docs]def text(entry, option_key="Text", **kwargs): - try: - return str(entry) - except Exception as err: - raise ValueError(f"Input could not be converted to text ({err})")
- - -
[docs]def color(entry, option_key="Color", **kwargs): - """ - The color should be just a color character, so 'r' if red color is desired. - """ - if not entry: - raise ValueError(f"Nothing entered for a {option_key}!") - test_str = strip_ansi(f"|{entry}|n") - if test_str: - raise ValueError(f"'{entry}' is not a valid {option_key}.") - return entry
- - -
[docs]def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs): - """ - Process a datetime string in standard forms while accounting for the - inputer's timezone. Always returns a result in UTC. - - Args: - entry (str): A date string from a user. - option_key (str): Name to display this datetime as. - account (AccountDB): The Account performing this lookup. Unless `from_tz` is provided, - the account's timezone option will be used. - from_tz (pytz.timezone): An instance of a pytz timezone object from the - user. If not provided, tries to use the timezone option of the `account`. - If neither one is provided, defaults to UTC. - - Returns: - datetime in UTC. - - Raises: - ValueError: If encountering a malformed timezone, date string or other format error. - - """ - if not entry: - raise ValueError(_("No {option_key} entered!").format(option_key=option_key)) - if not from_tz: - from_tz = _pytz.UTC - if account: - acct_tz = account.options.get("timezone", "UTC") - try: - from_tz = _pytz.timezone(acct_tz) - except Exception as err: - raise ValueError( - _("Timezone string '{acct_tz}' is not a valid timezone ({err})").format( - acct_tz=acct_tz, err=err - ) - ) - else: - from_tz = _pytz.UTC - - utc = _pytz.UTC - now = _dt.datetime.utcnow().replace(tzinfo=utc) - cur_year = now.strftime("%Y") - split_time = entry.split(" ") - if len(split_time) == 3: - entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {cur_year}" - elif len(split_time) == 4: - entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {split_time[3]}" - else: - raise ValueError( - f"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}" - ) - try: - local = _dt.datetime.strptime(entry, "%b %d %H:%M %Y") - except ValueError: - raise ValueError( - f"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}" - ) - local_tz = from_tz.localize(local) - return local_tz.astimezone(utc)
- - -
[docs]def duration(entry, option_key="Duration", **kwargs): - """ - Take a string and derive a datetime timedelta from it. - - Args: - entry (string): This is a string from user-input. The intended format is, for example: "5d 2w 90s" for - 'five days, two weeks, and ninety seconds.' Invalid sections are ignored. - option_key (str): Name to display this query as. - - Returns: - timedelta - - """ - time_string = entry.lower().split(" ") - seconds = 0 - minutes = 0 - hours = 0 - days = 0 - weeks = 0 - - for interval in time_string: - if _re.match(r"^[\d]+s$", interval): - seconds += int(interval.rstrip("s")) - elif _re.match(r"^[\d]+m$", interval): - minutes += int(interval.rstrip("m")) - elif _re.match(r"^[\d]+h$", interval): - hours += int(interval.rstrip("h")) - elif _re.match(r"^[\d]+d$", interval): - days += int(interval.rstrip("d")) - elif _re.match(r"^[\d]+w$", interval): - weeks += int(interval.rstrip("w")) - elif _re.match(r"^[\d]+y$", interval): - days += int(interval.rstrip("y")) * 365 - else: - raise ValueError(f"Could not convert section '{interval}' to a {option_key}.") - - return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)
- - -
[docs]def future(entry, option_key="Future Datetime", from_tz=None, **kwargs): - time = datetime(entry, option_key, from_tz=from_tz) - if time < _dt.datetime.utcnow().replace(tzinfo=_dt.timezone.utc): - raise ValueError(f"That {option_key} is in the past! Must give a Future datetime!") - return time
- - -
[docs]def signed_integer(entry, option_key="Signed Integer", **kwargs): - if not entry: - raise ValueError(f"Must enter a whole number for {option_key}!") - try: - num = int(entry) - except ValueError: - raise ValueError(f"Could not convert '{entry}' to a whole number for {option_key}!") - return num
- - -
[docs]def positive_integer(entry, option_key="Positive Integer", **kwargs): - num = signed_integer(entry, option_key) - if not num >= 1: - raise ValueError(f"Must enter a whole number greater than 0 for {option_key}!") - return num
- - -
[docs]def unsigned_integer(entry, option_key="Unsigned Integer", **kwargs): - num = signed_integer(entry, option_key) - if not num >= 0: - raise ValueError(f"{option_key} must be a whole number greater than or equal to 0!") - return num
- - -
[docs]def boolean(entry, option_key="True/False", **kwargs): - """ - Simplest check in computer logic, right? This will take user input to flick the switch on or off - Args: - entry (str): A value such as True, On, Enabled, Disabled, False, 0, or 1. - option_key (str): What kind of Boolean we are setting. What Option is this for? - - Returns: - Boolean - """ - error = f"Must enter 0 (false) or 1 (true) for {option_key}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled" - if not isinstance(entry, str): - raise ValueError(error) - entry = entry.upper() - if entry in ("1", "TRUE", "ON", "ENABLED", "ENABLE", "YES"): - return True - if entry in ("0", "FALSE", "OFF", "DISABLED", "DISABLE", "NO"): - return False - raise ValueError(error)
- - -
[docs]def timezone(entry, option_key="Timezone", **kwargs): - """ - Takes user input as string, and partial matches a Timezone. - - Args: - entry (str): The name of the Timezone. - option_key (str): What this Timezone is used for. - - Returns: - A PYTZ timezone. - """ - if not entry: - raise ValueError(f"No {option_key} entered!") - found = _partial(list(_TZ_DICT.keys()), entry, ret_index=False) - if len(found) > 1: - raise ValueError( - f"That matched: {', '.join(str(t) for t in found)}. Please be more specific!" - ) - if found: - return _TZ_DICT[found[0]] - raise ValueError(f"Could not find timezone '{entry}' for {option_key}!")
- - -
[docs]def email(entry, option_key="Email Address", **kwargs): - if not entry: - raise ValueError("Email address field empty!") - valid = validate_email_address(entry) - if not valid: - raise ValueError(f"That isn't a valid {option_key}!") - return entry
- - -
[docs]def lock(entry, option_key="locks", access_options=None, **kwargs): - entry = entry.strip() - if not entry: - raise ValueError(f"No {option_key} entered to set!") - for locksetting in entry.split(";"): - access_type, lockfunc = locksetting.split(":", 1) - if not access_type: - raise ValueError("Must enter an access type!") - if access_options: - if access_type not in access_options: - raise ValueError(f"Access type must be one of: {', '.join(access_options)}") - if not lockfunc: - raise ValueError("Lock func not entered.") - return entry
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/utils/backends.html b/docs/0.9.5/_modules/evennia/web/utils/backends.html deleted file mode 100644 index 608f4373d8..0000000000 --- a/docs/0.9.5/_modules/evennia/web/utils/backends.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - evennia.web.utils.backends — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.utils.backends

-from django.contrib.auth.backends import ModelBackend
-from django.contrib.auth import get_user_model
-
-
-
[docs]class CaseInsensitiveModelBackend(ModelBackend): - """ - By default ModelBackend does case _sensitive_ username - authentication, which isn't what is generally expected. This - backend supports case insensitive username authentication. - - """ - -
[docs] def authenticate(self, request, username=None, password=None, autologin=None): - """ - Custom authenticate with bypass for auto-logins - - Args: - request (Request): Request object. - username (str, optional): Name of user to authenticate. - password (str, optional): Password of user - autologin (Account, optional): If given, assume this is - an already authenticated account and bypass authentication. - """ - if autologin: - # Note: Setting .backend on account is critical in order to - # be allowed to call django.auth.login(account) later. This - # is necessary for the auto-login feature of the webclient, - # but it's important to make sure Django doesn't change this - # requirement or the name of the property down the line. /Griatch - autologin.backend = "evennia.web.utils.backends.CaseInsensitiveModelBackend" - return autologin - else: - # In this case .backend will be assigned automatically - # somewhere along the way. - Account = get_user_model() - try: - account = Account.objects.get(username__iexact=username) - if account.check_password(password): - return account - else: - return None - except Account.DoesNotExist: - return None
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/utils/general_context.html b/docs/0.9.5/_modules/evennia/web/utils/general_context.html deleted file mode 100644 index 3db954ff83..0000000000 --- a/docs/0.9.5/_modules/evennia/web/utils/general_context.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - evennia.web.utils.general_context — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.utils.general_context

-#
-# This file defines global variables that will always be
-# available in a view context without having to repeatedly
-# include it. For this to work, this file is included in
-# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
-# tuple.
-#
-
-import os
-from django.conf import settings
-from evennia.utils.utils import get_evennia_version
-
-# Determine the site name and server version
-
[docs]def set_game_name_and_slogan(): - """ - Sets global variables GAME_NAME and GAME_SLOGAN which are used by - general_context. - - Notes: - This function is used for unit testing the values of the globals. - """ - global GAME_NAME, GAME_SLOGAN, SERVER_VERSION - try: - GAME_NAME = settings.SERVERNAME.strip() - except AttributeError: - GAME_NAME = "Evennia" - SERVER_VERSION = get_evennia_version() - try: - GAME_SLOGAN = settings.GAME_SLOGAN.strip() - except AttributeError: - GAME_SLOGAN = SERVER_VERSION
- - -set_game_name_and_slogan() - -# Setup lists of the most relevant apps so -# the adminsite becomes more readable. - -ACCOUNT_RELATED = ["Accounts"] -GAME_ENTITIES = ["Objects", "Scripts", "Comms", "Help"] -GAME_SETUP = ["Permissions", "Config"] -CONNECTIONS = ["Irc"] -WEBSITE = ["Flatpages", "News", "Sites"] - - -
[docs]def set_webclient_settings(): - """ - As with set_game_name_and_slogan above, this sets global variables pertaining - to webclient settings. - - Notes: - Used for unit testing. - """ - global WEBCLIENT_ENABLED, WEBSOCKET_CLIENT_ENABLED, WEBSOCKET_PORT, WEBSOCKET_URL - WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED - WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED - # if we are working through a proxy or uses docker port-remapping, the webclient port encoded - # in the webclient should be different than the one the server expects. Use the environment - # variable WEBSOCKET_CLIENT_PROXY_PORT if this is the case. - WEBSOCKET_PORT = int( - os.environ.get("WEBSOCKET_CLIENT_PROXY_PORT", settings.WEBSOCKET_CLIENT_PORT) - ) - # this is determined dynamically by the client and is less of an issue - WEBSOCKET_URL = settings.WEBSOCKET_CLIENT_URL
- - -set_webclient_settings() - -# The main context processor function -
[docs]def general_context(request): - """ - Returns common Evennia-related context stuff, which - is automatically added to context of all views. - """ - account = None - if request.user.is_authenticated: - account = request.user - - puppet = None - if account and request.session.get("puppet"): - pk = int(request.session.get("puppet")) - puppet = next((x for x in account.characters if x.pk == pk), None) - - return { - "account": account, - "puppet": puppet, - "game_name": GAME_NAME, - "game_slogan": GAME_SLOGAN, - "evennia_userapps": ACCOUNT_RELATED, - "evennia_entityapps": GAME_ENTITIES, - "evennia_setupapps": GAME_SETUP, - "evennia_connectapps": CONNECTIONS, - "evennia_websiteapps": WEBSITE, - "webclient_enabled": WEBCLIENT_ENABLED, - "websocket_enabled": WEBSOCKET_CLIENT_ENABLED, - "websocket_port": WEBSOCKET_PORT, - "websocket_url": WEBSOCKET_URL, - }
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/utils/middleware.html b/docs/0.9.5/_modules/evennia/web/utils/middleware.html deleted file mode 100644 index 140ca479cb..0000000000 --- a/docs/0.9.5/_modules/evennia/web/utils/middleware.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - evennia.web.utils.middleware — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.utils.middleware

-from django.contrib.auth import authenticate, login
-from evennia.accounts.models import AccountDB
-from evennia.utils import logger
-
-
-
[docs]class SharedLoginMiddleware(object): - """ - Handle the shared login between website and webclient. - - """ - -
[docs] def __init__(self, get_response): - # One-time configuration and initialization. - self.get_response = get_response
- - def __call__(self, request): - # Code to be executed for each request before - # the view (and later middleware) are called. - - # Synchronize credentials between webclient and website - # Must be performed *before* rendering the view (issue #1723) - self.make_shared_login(request) - - # Process view - response = self.get_response(request) - - # Code to be executed for each request/response after - # the view is called. - - # Return processed view - return response - -
[docs] @classmethod - def make_shared_login(cls, request): - csession = request.session - account = request.user - website_uid = csession.get("website_authenticated_uid", None) - webclient_uid = csession.get("webclient_authenticated_uid", None) - - if not csession.session_key: - # this is necessary to build the sessid key - csession.save() - - if account.is_authenticated: - # Logged into website - if website_uid is None: - # fresh website login (just from login page) - csession["website_authenticated_uid"] = account.id - if webclient_uid is None: - # auto-login web client - csession["webclient_authenticated_uid"] = account.id - - elif webclient_uid: - # Not logged into website, but logged into webclient - if website_uid is None: - csession["website_authenticated_uid"] = account.id - account = AccountDB.objects.get(id=webclient_uid) - try: - # calls our custom authenticate, in web/utils/backend.py - authenticate(autologin=account) - login(request, account) - except AttributeError: - logger.log_trace() - - if csession.get("webclient_authenticated_uid", None): - # set a nonce to prevent the webclient from erasing the webclient_authenticated_uid value - csession["webclient_authenticated_nonce"] = ( - csession.get("webclient_authenticated_nonce", 0) + 1 - ) - # wrap around to prevent integer overflows - if csession["webclient_authenticated_nonce"] > 32: - csession["webclient_authenticated_nonce"] = 0
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/utils/tests.html b/docs/0.9.5/_modules/evennia/web/utils/tests.html deleted file mode 100644 index 7ce4726bfa..0000000000 --- a/docs/0.9.5/_modules/evennia/web/utils/tests.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - evennia.web.utils.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.utils.tests

-from django.contrib.auth.models import AnonymousUser
-from django.test import RequestFactory, TestCase
-from mock import MagicMock, patch
-from . import general_context
-
-
-
[docs]class TestGeneralContext(TestCase): - maxDiff = None - -
[docs] @patch("evennia.web.utils.general_context.GAME_NAME", "test_name") - @patch("evennia.web.utils.general_context.GAME_SLOGAN", "test_game_slogan") - @patch( - "evennia.web.utils.general_context.WEBSOCKET_CLIENT_ENABLED", - "websocket_client_enabled_testvalue", - ) - @patch("evennia.web.utils.general_context.WEBCLIENT_ENABLED", "webclient_enabled_testvalue") - @patch("evennia.web.utils.general_context.WEBSOCKET_PORT", "websocket_client_port_testvalue") - @patch("evennia.web.utils.general_context.WEBSOCKET_URL", "websocket_client_url_testvalue") - def test_general_context(self): - request = RequestFactory().get("/") - request.user = AnonymousUser() - request.session = {"account": None, "puppet": None} - - response = general_context.general_context(request) - - self.assertEqual( - response, - { - "account": None, - "puppet": None, - "game_name": "test_name", - "game_slogan": "test_game_slogan", - "evennia_userapps": ["Accounts"], - "evennia_entityapps": ["Objects", "Scripts", "Comms", "Help"], - "evennia_setupapps": ["Permissions", "Config"], - "evennia_connectapps": ["Irc"], - "evennia_websiteapps": ["Flatpages", "News", "Sites"], - "webclient_enabled": "webclient_enabled_testvalue", - "websocket_enabled": "websocket_client_enabled_testvalue", - "websocket_port": "websocket_client_port_testvalue", - "websocket_url": "websocket_client_url_testvalue", - }, - )
- - # spec being an empty list will initially raise AttributeError in set_game_name_and_slogan to test defaults -
[docs] @patch("evennia.web.utils.general_context.settings", spec=[]) - @patch("evennia.web.utils.general_context.get_evennia_version") - def test_set_game_name_and_slogan(self, mock_get_version, mock_settings): - mock_get_version.return_value = "version 1" - # test default/fallback values - general_context.set_game_name_and_slogan() - self.assertEqual(general_context.GAME_NAME, "Evennia") - self.assertEqual(general_context.GAME_SLOGAN, "version 1") - # test values when the settings are defined - mock_settings.SERVERNAME = "test_name" - mock_settings.GAME_SLOGAN = "test_game_slogan" - general_context.set_game_name_and_slogan() - self.assertEqual(general_context.GAME_NAME, "test_name") - self.assertEqual(general_context.GAME_SLOGAN, "test_game_slogan")
- -
[docs] @patch("evennia.web.utils.general_context.settings") - def test_set_webclient_settings(self, mock_settings): - mock_settings.WEBCLIENT_ENABLED = "webclient" - mock_settings.WEBSOCKET_CLIENT_URL = "websocket_url" - mock_settings.WEBSOCKET_CLIENT_ENABLED = "websocket_client" - mock_settings.WEBSOCKET_CLIENT_PORT = 5000 - general_context.set_webclient_settings() - self.assertEqual(general_context.WEBCLIENT_ENABLED, "webclient") - self.assertEqual(general_context.WEBSOCKET_URL, "websocket_url") - self.assertEqual(general_context.WEBSOCKET_CLIENT_ENABLED, "websocket_client") - self.assertEqual(general_context.WEBSOCKET_PORT, 5000)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/webclient/views.html b/docs/0.9.5/_modules/evennia/web/webclient/views.html deleted file mode 100644 index ff7c1c1fe2..0000000000 --- a/docs/0.9.5/_modules/evennia/web/webclient/views.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - evennia.web.webclient.views — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.webclient.views

-"""
-This contains a simple view for rendering the webclient
-page and serve it eventual static content.
-
-"""
-
-from django.conf import settings
-from django.http import Http404
-from django.shortcuts import render
-from django.contrib.auth import login, authenticate
-
-from evennia.accounts.models import AccountDB
-from evennia.utils import logger
-
-
-
[docs]def webclient(request): - """ - Webclient page template loading. - - """ - # auto-login is now handled by evennia.web.utils.middleware - - # check if webclient should be enabled - if not settings.WEBCLIENT_ENABLED: - raise Http404 - - # make sure to store the browser session's hash so the webclient can get to it! - pagevars = {"browser_sessid": request.session.session_key} - - return render(request, "webclient.html", pagevars)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/website/forms.html b/docs/0.9.5/_modules/evennia/web/website/forms.html deleted file mode 100644 index 9c874bc3e9..0000000000 --- a/docs/0.9.5/_modules/evennia/web/website/forms.html +++ /dev/null @@ -1,281 +0,0 @@ - - - - - - - - evennia.web.website.forms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.website.forms

-from django import forms
-from django.conf import settings
-from django.contrib.auth.forms import UserCreationForm, UsernameField
-from django.forms import ModelForm
-from django.utils.html import escape
-from evennia.utils import class_from_module
-
-
-
[docs]class EvenniaForm(forms.Form): - """ - This is a stock Django form, but modified so that all values provided - through it are escaped (sanitized). Validation is performed by the fields - you define in the form. - - This has little to do with Evennia itself and is more general web security- - related. - - https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#Goals_of_Input_Validation - - """ - -
[docs] def clean(self): - """ - Django hook. Performed on form submission. - - Returns: - cleaned (dict): Dictionary of key:value pairs submitted on the form. - - """ - # Call parent function - cleaned = super(EvenniaForm, self).clean() - - # Escape all values provided by user - cleaned = {k: escape(v) for k, v in cleaned.items()} - return cleaned
- - -
[docs]class AccountForm(UserCreationForm): - """ - This is a generic Django form tailored to the Account model. - - In this incarnation it does not allow getting/setting of attributes, only - core User model fields (username, email, password). - - """ - -
[docs] class Meta: - """ - This is a Django construct that provides additional configuration to - the form. - - """ - - # The model/typeclass this form creates - model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS, - fallback=settings.FALLBACK_ACCOUNT_TYPECLASS) - - # The fields to display on the form, in the given order - fields = ("username", "email") - - # Any overrides of field classes - field_classes = {"username": UsernameField}
- - # Username is collected as part of the core UserCreationForm, so we just need - # to add a field to (optionally) capture email. - email = forms.EmailField( - help_text="A valid email address. Optional; used for password resets.", required=False - )
- - -
[docs]class ObjectForm(EvenniaForm, ModelForm): - """ - This is a Django form for generic Evennia Objects that allows modification - of attributes when called from a descendent of ObjectUpdate or ObjectCreate - views. - - It defines no fields by default; you have to do that by extending this class - and defining what fields you want to be recorded. See the CharacterForm for - a simple example of how to do this. - - """ - -
[docs] class Meta: - """ - This is a Django construct that provides additional configuration to - the form. - - """ - - # The model/typeclass this form creates - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) - - # The fields to display on the form, in the given order - fields = ("db_key",) - - # This lets us rename ugly db-specific keys to something more human - labels = {"db_key": "Name"}
- - -
[docs]class CharacterForm(ObjectForm): - """ - This is a Django form for Evennia Character objects. - - Since Evennia characters only have one attribute by default, this form only - defines a field for that single attribute. The names of fields you define should - correspond to their names as stored in the dbhandler; you can display - 'prettier' versions of the fieldname on the form using the 'label' kwarg. - - The basic field types are CharFields and IntegerFields, which let you enter - text and numbers respectively. IntegerFields have some neat validation tricks - they can do, like mandating values fall within a certain range. - - For example, a complete "age" field (which stores its value to - `character.db.age` might look like: - - age = forms.IntegerField( - label="Your Age", - min_value=18, max_value=9000, - help_text="Years since your birth.") - - Default input fields are generic single-line text boxes. You can control what - sort of input field users will see by specifying a "widget." An example of - this is used for the 'desc' field to show a Textarea box instead of a Textbox. - - For help in building out your form, please see: - https://docs.djangoproject.com/en/1.11/topics/forms/#building-a-form-in-django - - For more information on fields and their capabilities, see: - https://docs.djangoproject.com/en/1.11/ref/forms/fields/ - - For more on widgets, see: - https://docs.djangoproject.com/en/1.11/ref/forms/widgets/ - - """ - -
[docs] class Meta: - """ - This is a Django construct that provides additional configuration to - the form. - - """ - - # Get the correct object model - model = class_from_module(settings.BASE_CHARACTER_TYPECLASS, - fallback=settings.FALLBACK_CHARACTER_TYPECLASS) - - # Allow entry of the 'key' field - fields = ("db_key",) - - # Rename 'key' to something more intelligible - labels = {"db_key": "Name"}
- - # Fields pertaining to configurable attributes on the Character object. - desc = forms.CharField( - label="Description", - max_length=2048, - required=False, - widget=forms.Textarea(attrs={"rows": 3}), - help_text="A brief description of your character.", - )
- - -
[docs]class CharacterUpdateForm(CharacterForm): - """ - This is a Django form for updating Evennia Character objects. - - By default it is the same as the CharacterForm, but if there are circumstances - in which you don't want to let players edit all the same attributes they had - access to during creation, you can redefine this form with those fields you do - wish to allow. - - """ - - pass
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/website/templatetags/addclass.html b/docs/0.9.5/_modules/evennia/web/website/templatetags/addclass.html deleted file mode 100644 index e38fd0675a..0000000000 --- a/docs/0.9.5/_modules/evennia/web/website/templatetags/addclass.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - evennia.web.website.templatetags.addclass — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.website.templatetags.addclass

-from django import template
-
-register = template.Library()
-
-
-
[docs]@register.filter(name="addclass") -def addclass(field, given_class): - existing_classes = field.field.widget.attrs.get("class", None) - if existing_classes: - if existing_classes.find(given_class) == -1: - # if the given class doesn't exist in the existing classes - classes = existing_classes + " " + given_class - else: - classes = existing_classes - else: - classes = given_class - return field.as_widget(attrs={"class": classes})
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/website/tests.html b/docs/0.9.5/_modules/evennia/web/website/tests.html deleted file mode 100644 index 1285397380..0000000000 --- a/docs/0.9.5/_modules/evennia/web/website/tests.html +++ /dev/null @@ -1,396 +0,0 @@ - - - - - - - - evennia.web.website.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.website.tests

-from django.conf import settings
-from django.utils.text import slugify
-from django.test import Client, override_settings
-from django.urls import reverse
-from evennia.utils import class_from_module
-from evennia.utils.test_resources import EvenniaTest
-
-
-
[docs]class EvenniaWebTest(EvenniaTest): - - # Use the same classes the views are expecting - account_typeclass = settings.BASE_ACCOUNT_TYPECLASS - object_typeclass = settings.BASE_OBJECT_TYPECLASS - character_typeclass = settings.BASE_CHARACTER_TYPECLASS - exit_typeclass = settings.BASE_EXIT_TYPECLASS - room_typeclass = settings.BASE_ROOM_TYPECLASS - script_typeclass = settings.BASE_SCRIPT_TYPECLASS - channel_typeclass = settings.BASE_CHANNEL_TYPECLASS - - # Default named url - url_name = "index" - - # Response to expect for unauthenticated requests - unauthenticated_response = 200 - - # Response to expect for authenticated requests - authenticated_response = 200 - -
[docs] def setUp(self): - super(EvenniaWebTest, self).setUp() - - # Add chars to account rosters - self.account.db._playable_characters = [self.char1] - self.account2.db._playable_characters = [self.char2] - - for account in (self.account, self.account2): - # Demote accounts to Player permissions - account.permissions.add("Player") - account.permissions.remove("Developer") - - # Grant permissions to chars - for char in account.db._playable_characters: - char.locks.add("edit:id(%s) or perm(Admin)" % account.pk) - char.locks.add("delete:id(%s) or perm(Admin)" % account.pk) - char.locks.add("view:all()")
- -
[docs] def test_valid_chars(self): - "Make sure account has playable characters" - self.assertTrue(self.char1 in self.account.db._playable_characters) - self.assertTrue(self.char2 in self.account2.db._playable_characters)
- -
[docs] def get_kwargs(self): - return {}
- -
[docs] def test_get(self): - # Try accessing page while not logged in - response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs())) - self.assertEqual(response.status_code, self.unauthenticated_response)
- -
[docs] def login(self): - return self.client.login(username="TestAccount", password="testpassword")
- -
[docs] def test_get_authenticated(self): - logged_in = self.login() - self.assertTrue(logged_in, "Account failed to log in!") - - # Try accessing page while logged in - response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True) - - self.assertEqual(response.status_code, self.authenticated_response)
- - -# ------------------------------------------------------------------------------ - - -
[docs]class AdminTest(EvenniaWebTest): - url_name = "django_admin" - unauthenticated_response = 302
- - -
[docs]class IndexTest(EvenniaWebTest): - url_name = "index"
- - -
[docs]class RegisterTest(EvenniaWebTest): - url_name = "register"
- - -
[docs]class LoginTest(EvenniaWebTest): - url_name = "login"
- - -
[docs]class LogoutTest(EvenniaWebTest): - url_name = "logout"
- - -
[docs]class PasswordResetTest(EvenniaWebTest): - url_name = "password_change" - unauthenticated_response = 302
- - -
[docs]class WebclientTest(EvenniaWebTest): - url_name = "webclient:index" - -
[docs] @override_settings(WEBCLIENT_ENABLED=True) - def test_get(self): - self.authenticated_response = 200 - self.unauthenticated_response = 200 - super(WebclientTest, self).test_get()
- -
[docs] @override_settings(WEBCLIENT_ENABLED=False) - def test_get_disabled(self): - self.authenticated_response = 404 - self.unauthenticated_response = 404 - super(WebclientTest, self).test_get()
- - -
[docs]class ChannelListTest(EvenniaWebTest): - url_name = "channels"
- - -
[docs]class ChannelDetailTest(EvenniaWebTest): - url_name = "channel-detail" - -
[docs] def setUp(self): - super(ChannelDetailTest, self).setUp() - - klass = class_from_module(self.channel_typeclass, - fallback=settings.FALLBACK_CHANNEL_TYPECLASS) - - # Create a channel - klass.create("demo")
- -
[docs] def get_kwargs(self): - return {"slug": slugify("demo")}
- - -
[docs]class CharacterCreateView(EvenniaWebTest): - url_name = "character-create" - unauthenticated_response = 302 - -
[docs] @override_settings(MULTISESSION_MODE=0) - def test_valid_access_multisession_0(self): - "Account1 with no characters should be able to create a new one" - self.account.db._playable_characters = [] - - # Login account - self.login() - - # Post data for a new character - data = {"db_key": "gannon", "desc": "Some dude."} - - response = self.client.post(reverse(self.url_name), data=data, follow=True) - self.assertEqual(response.status_code, 200) - - # Make sure the character was actually created - self.assertTrue( - len(self.account.db._playable_characters) == 1, - "Account only has the following characters attributed to it: %s" - % self.account.db._playable_characters, - )
- -
[docs] @override_settings(MULTISESSION_MODE=2) - @override_settings(MAX_NR_CHARACTERS=10) - def test_valid_access_multisession_2(self): - "Account1 should be able to create a new character" - # Login account - self.login() - - # Post data for a new character - data = {"db_key": "gannon", "desc": "Some dude."} - - response = self.client.post(reverse(self.url_name), data=data, follow=True) - self.assertEqual(response.status_code, 200) - - # Make sure the character was actually created - self.assertTrue( - len(self.account.db._playable_characters) > 1, - "Account only has the following characters attributed to it: %s" - % self.account.db._playable_characters, - )
- - -
[docs]class CharacterPuppetView(EvenniaWebTest): - url_name = "character-puppet" - unauthenticated_response = 302 - -
[docs] def get_kwargs(self): - return {"pk": self.char1.pk, "slug": slugify(self.char1.name)}
- -
[docs] def test_invalid_access(self): - "Account1 should not be able to puppet Account2:Char2" - # Login account - self.login() - - # Try to access puppet page for char2 - kwargs = {"pk": self.char2.pk, "slug": slugify(self.char2.name)} - response = self.client.get(reverse(self.url_name, kwargs=kwargs), follow=True) - self.assertTrue( - response.status_code >= 400, - "Invalid access should return a 4xx code-- either obj not found or permission denied! (Returned %s)" - % response.status_code, - )
- - -
[docs]class CharacterListView(EvenniaWebTest): - url_name = "characters" - unauthenticated_response = 302
- - -
[docs]class CharacterManageView(EvenniaWebTest): - url_name = "character-manage" - unauthenticated_response = 302
- - -
[docs]class CharacterUpdateView(EvenniaWebTest): - url_name = "character-update" - unauthenticated_response = 302 - -
[docs] def get_kwargs(self): - return {"pk": self.char1.pk, "slug": slugify(self.char1.name)}
- -
[docs] def test_valid_access(self): - "Account1 should be able to update Account1:Char1" - # Login account - self.login() - - # Try to access update page for char1 - response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True) - self.assertEqual(response.status_code, 200) - - # Try to update char1 desc - data = {"db_key": self.char1.db_key, "desc": "Just a regular type of dude."} - response = self.client.post( - reverse(self.url_name, kwargs=self.get_kwargs()), data=data, follow=True - ) - self.assertEqual(response.status_code, 200) - - # Make sure the change was made successfully - self.assertEqual(self.char1.db.desc, data["desc"])
- -
[docs] def test_invalid_access(self): - "Account1 should not be able to update Account2:Char2" - # Login account - self.login() - - # Try to access update page for char2 - kwargs = {"pk": self.char2.pk, "slug": slugify(self.char2.name)} - response = self.client.get(reverse(self.url_name, kwargs=kwargs), follow=True) - self.assertEqual(response.status_code, 403)
- - -
[docs]class CharacterDeleteView(EvenniaWebTest): - url_name = "character-delete" - unauthenticated_response = 302 - -
[docs] def get_kwargs(self): - return {"pk": self.char1.pk, "slug": slugify(self.char1.name)}
- -
[docs] def test_valid_access(self): - "Account1 should be able to delete Account1:Char1" - # Login account - self.login() - - # Try to access delete page for char1 - response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True) - self.assertEqual(response.status_code, 200) - - # Proceed with deleting it - data = {"value": "yes"} - response = self.client.post( - reverse(self.url_name, kwargs=self.get_kwargs()), data=data, follow=True - ) - self.assertEqual(response.status_code, 200) - - # Make sure it deleted - self.assertFalse( - self.char1 in self.account.db._playable_characters, - "Char1 is still in Account playable characters list.", - )
- -
[docs] def test_invalid_access(self): - "Account1 should not be able to delete Account2:Char2" - # Login account - self.login() - - # Try to access delete page for char2 - kwargs = {"pk": self.char2.pk, "slug": slugify(self.char2.name)} - response = self.client.get(reverse(self.url_name, kwargs=kwargs), follow=True) - self.assertEqual(response.status_code, 403)
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/evennia/web/website/views.html b/docs/0.9.5/_modules/evennia/web/website/views.html deleted file mode 100644 index a34d7fad90..0000000000 --- a/docs/0.9.5/_modules/evennia/web/website/views.html +++ /dev/null @@ -1,1241 +0,0 @@ - - - - - - - - evennia.web.website.views — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for evennia.web.website.views

-"""
-This file contains the generic, assorted views that don't fall under one of the other applications.
-Views are django's way of processing e.g. html templates on the fly.
-
-"""
-
-from collections import OrderedDict
-
-from django.contrib.admin.sites import site
-from django.conf import settings
-from django.contrib import messages
-from django.contrib.auth.mixins import LoginRequiredMixin
-from django.contrib.admin.views.decorators import staff_member_required
-from django.core.exceptions import PermissionDenied
-from django.db.models.functions import Lower
-from django.http import HttpResponseBadRequest, HttpResponseRedirect
-from django.shortcuts import render
-from django.urls import reverse_lazy
-from django.views.generic import TemplateView, ListView, DetailView
-from django.views.generic.base import RedirectView
-from django.views.generic.edit import CreateView, UpdateView, DeleteView
-
-from evennia import SESSION_HANDLER
-from evennia.help.models import HelpEntry
-from evennia.objects.models import ObjectDB
-from evennia.accounts.models import AccountDB
-from evennia.utils import class_from_module
-from evennia.utils.logger import tail_log_file
-from evennia.web.website import forms as website_forms
-
-from django.utils.text import slugify
-
-_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
-
-# typeclass fallbacks
-
-def _gamestats():
-    # Some misc. configurable stuff.
-    # TODO: Move this to either SQL or settings.py based configuration.
-    fpage_account_limit = 4
-
-    # A QuerySet of the most recently connected accounts.
-    recent_users = AccountDB.objects.get_recently_connected_accounts()[:fpage_account_limit]
-    nplyrs_conn_recent = len(recent_users) or "none"
-    nplyrs = AccountDB.objects.num_total_accounts() or "none"
-    nplyrs_reg_recent = len(AccountDB.objects.get_recently_created_accounts()) or "none"
-    nsess = SESSION_HANDLER.account_count()
-    # nsess = len(AccountDB.objects.get_connected_accounts()) or "no one"
-
-    nobjs = ObjectDB.objects.count()
-    nobjs = nobjs or 1  # fix zero-div error with empty database
-    Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS,
-                                  fallback=settings.FALLBACK_CHARACTER_TYPECLASS)
-    nchars = Character.objects.all_family().count()
-    Room = class_from_module(settings.BASE_ROOM_TYPECLASS,
-                             fallback=settings.FALLBACK_ROOM_TYPECLASS)
-    nrooms = Room.objects.all_family().count()
-    Exit = class_from_module(settings.BASE_EXIT_TYPECLASS,
-                             fallback=settings.FALLBACK_EXIT_TYPECLASS)
-    nexits = Exit.objects.all_family().count()
-    nothers = nobjs - nchars - nrooms - nexits
-
-    pagevars = {
-        "page_title": "Front Page",
-        "accounts_connected_recent": recent_users,
-        "num_accounts_connected": nsess or "no one",
-        "num_accounts_registered": nplyrs or "no",
-        "num_accounts_connected_recent": nplyrs_conn_recent or "no",
-        "num_accounts_registered_recent": nplyrs_reg_recent or "no one",
-        "num_rooms": nrooms or "none",
-        "num_exits": nexits or "no",
-        "num_objects": nobjs or "none",
-        "num_characters": nchars or "no",
-        "num_others": nothers or "no",
-    }
-    return pagevars
-
-
-
[docs]def to_be_implemented(request): - """ - A notice letting the user know that this particular feature hasn't been - implemented yet. - """ - - pagevars = {"page_title": "To Be Implemented..."} - - return render(request, "tbi.html", pagevars)
- - -
[docs]@staff_member_required -def evennia_admin(request): - """ - Helpful Evennia-specific admin page. - """ - return render(request, "evennia_admin.html", {"accountdb": AccountDB})
- - -
[docs]def admin_wrapper(request): - """ - Wrapper that allows us to properly use the base Django admin site, if needed. - """ - return staff_member_required(site.index)(request)
- - -# -# Class-based views -# - - -
[docs]class EvenniaIndexView(TemplateView): - """ - This is a basic example of a Django class-based view, which are functionally - very similar to Evennia Commands but differ in structure. Commands are used - to interface with users using a terminal client. Views are used to interface - with users using a web browser. - - To use a class-based view, you need to have written a template in HTML, and - then you write a view like this to tell Django what values to display on it. - - While there are simpler ways of writing views using plain functions (and - Evennia currently contains a few examples of them), just like Commands, - writing views as classes provides you with more flexibility-- you can extend - classes and change things to suit your needs rather than having to copy and - paste entire code blocks over and over. Django also comes with many default - views for displaying things, all of them implemented as classes. - - This particular example displays the index page. - - """ - - # Tell the view what HTML template to use for the page - template_name = "website/index.html" - - # This method tells the view what data should be displayed on the template. -
[docs] def get_context_data(self, **kwargs): - """ - This is a common Django method. Think of this as the website - equivalent of the Evennia Command.func() method. - - If you just want to display a static page with no customization, you - don't need to define this method-- just create a view, define - template_name and you're done. - - The only catch here is that if you extend or overwrite this method, - you'll always want to make sure you call the parent method to get a - context object. It's just a dict, but it comes prepopulated with all - sorts of background data intended for display on the page. - - You can do whatever you want to it, but it must be returned at the end - of this method. - - Keyword Args: - any (any): Passed through. - - Returns: - context (dict): Dictionary of data you want to display on the page. - - """ - # Always call the base implementation first to get a context object - context = super(EvenniaIndexView, self).get_context_data(**kwargs) - - # Add game statistics and other pagevars - context.update(_gamestats()) - - return context
- - -
[docs]class TypeclassMixin(object): - """ - This is a "mixin", a modifier of sorts. - - Django views typically work with classes called "models." Evennia objects - are an enhancement upon these Django models and are called "typeclasses." - But Django itself has no idea what a "typeclass" is. - - For the sake of mitigating confusion, any view class with this in its - inheritance list will be modified to work with Evennia Typeclass objects or - Django models interchangeably. - - """ - - @property - def typeclass(self): - return self.model - - @typeclass.setter - def typeclass(self, value): - self.model = value
- - -
[docs]class EvenniaCreateView(CreateView, TypeclassMixin): - """ - This view extends Django's default CreateView. - - CreateView is used for creating new objects, be they Accounts, Characters or - otherwise. - - """ - - @property - def page_title(self): - # Makes sure the page has a sensible title. - return "Create %s" % self.typeclass._meta.verbose_name.title()
- - -
[docs]class EvenniaDetailView(DetailView, TypeclassMixin): - """ - This view extends Django's default DetailView. - - DetailView is used for displaying objects, be they Accounts, Characters or - otherwise. - - """ - - @property - def page_title(self): - # Makes sure the page has a sensible title. - return "%s Detail" % self.typeclass._meta.verbose_name.title()
- - -
[docs]class EvenniaUpdateView(UpdateView, TypeclassMixin): - """ - This view extends Django's default UpdateView. - - UpdateView is used for updating objects, be they Accounts, Characters or - otherwise. - - """ - - @property - def page_title(self): - # Makes sure the page has a sensible title. - return "Update %s" % self.typeclass._meta.verbose_name.title()
- - -
[docs]class EvenniaDeleteView(DeleteView, TypeclassMixin): - """ - This view extends Django's default DeleteView. - - DeleteView is used for deleting objects, be they Accounts, Characters or - otherwise. - - """ - - @property - def page_title(self): - # Makes sure the page has a sensible title. - return "Delete %s" % self.typeclass._meta.verbose_name.title()
- - -# -# Object views -# - - -
[docs]class ObjectDetailView(EvenniaDetailView): - """ - This is an important view. - - Any view you write that deals with displaying, updating or deleting a - specific object will want to inherit from this. It provides the mechanisms - by which to retrieve the object and make sure the user requesting it has - permissions to actually *do* things to it. - - """ - - # -- Django constructs -- - # - # Choose what class of object this view will display. Note that this should - # be an actual Python class (i.e. do `from typeclasses.characters import - # Character`, then put `Character`), not an Evennia typeclass path - # (i.e. `typeclasses.characters.Character`). - # - # So when you extend it, this line should look simple, like: - # model = Object - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) - - # What HTML template you wish to use to display this page. - template_name = "website/object_detail.html" - - # -- Evennia constructs -- - # - # What lock type to check for the requesting user, authenticated or not. - # https://github.com/evennia/evennia/wiki/Locks#valid-access_types - access_type = "view" - - # What attributes of the object you wish to display on the page. Model-level - # attributes will take precedence over identically-named db.attributes! - # The order you specify here will be followed. - attributes = ["name", "desc"] - -
[docs] def get_context_data(self, **kwargs): - """ - Adds an 'attributes' list to the request context consisting of the - attributes specified at the class level, and in the order provided. - - Django views do not provide a way to reference dynamic attributes, so - we have to grab them all before we render the template. - - Returns: - context (dict): Django context object - - """ - # Get the base Django context object - context = super(ObjectDetailView, self).get_context_data(**kwargs) - - # Get the object in question - obj = self.get_object() - - # Create an ordered dictionary to contain the attribute map - attribute_list = OrderedDict() - - for attribute in self.attributes: - # Check if the attribute is a core fieldname (name, desc) - if attribute in self.typeclass._meta._property_names: - attribute_list[attribute.title()] = getattr(obj, attribute, "") - - # Check if the attribute is a db attribute (char1.db.favorite_color) - else: - attribute_list[attribute.title()] = getattr(obj.db, attribute, "") - - # Add our attribute map to the Django request context, so it gets - # displayed on the template - context["attribute_list"] = attribute_list - - # Return the comprehensive context object - return context
- -
[docs] def get_object(self, queryset=None): - """ - Override of Django hook that provides some important Evennia-specific - functionality. - - Evennia does not natively store slugs, so where a slug is provided, - calculate the same for the object and make sure it matches. - - This also checks to make sure the user has access to view/edit/delete - this object! - - """ - # A queryset can be provided to pre-emptively limit what objects can - # possibly be returned. For example, you can supply a queryset that - # only returns objects whose name begins with "a". - if not queryset: - queryset = self.get_queryset() - - # Get the object, ignoring all checks and filters for now - obj = self.typeclass.objects.get(pk=self.kwargs.get("pk")) - - # Check if this object was requested in a valid manner - if slugify(obj.name) != self.kwargs.get(self.slug_url_kwarg): - raise HttpResponseBadRequest( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} - ) - - # Check if the requestor account has permissions to access object - account = self.request.user - if not obj.access(account, self.access_type): - raise PermissionDenied("You are not authorized to %s this object." % self.access_type) - - # Get the object, if it is in the specified queryset - obj = super(ObjectDetailView, self).get_object(queryset) - - return obj
- - -
[docs]class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView): - """ - This is an important view. - - Any view you write that deals with creating a specific object will want to - inherit from this. It provides the mechanisms by which to make sure the user - requesting creation of an object is authenticated, and provides a sane - default title for the page. - - """ - - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS)
- - -
[docs]class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView): - """ - This is an important view for obvious reasons! - - Any view you write that deals with deleting a specific object will want to - inherit from this. It provides the mechanisms by which to make sure the user - requesting deletion of an object is authenticated, and that they have - permissions to delete the requested object. - - """ - - # -- Django constructs -- - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) - template_name = "website/object_confirm_delete.html" - - # -- Evennia constructs -- - access_type = "delete" - -
[docs] def delete(self, request, *args, **kwargs): - """ - Calls the delete() method on the fetched object and then - redirects to the success URL. - - We extend this so we can capture the name for the sake of confirmation. - - """ - # Get the object in question. ObjectDetailView.get_object() will also - # check to make sure the current user (authenticated or not) has - # permission to delete it! - obj = str(self.get_object()) - - # Perform the actual deletion (the parent class handles this, which will - # in turn call the delete() method on the object) - response = super(ObjectDeleteView, self).delete(request, *args, **kwargs) - - # Notify the user of the deletion - messages.success(request, "Successfully deleted '%s'." % obj) - return response
- - -
[docs]class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView): - """ - This is an important view. - - Any view you write that deals with updating a specific object will want to - inherit from this. It provides the mechanisms by which to make sure the user - requesting editing of an object is authenticated, and that they have - permissions to edit the requested object. - - This functions slightly different from default Django UpdateViews in that - it does not update core model fields, *only* object attributes! - - """ - - # -- Django constructs -- - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) - - # -- Evennia constructs -- - access_type = "edit" - -
[docs] def get_success_url(self): - """ - Django hook. - - Can be overridden to return any URL you want to redirect the user to - after the object is successfully updated, but by default it goes to the - object detail page so the user can see their changes reflected. - - """ - if self.success_url: - return self.success_url - return self.object.web_get_detail_url()
- -
[docs] def get_initial(self): - """ - Django hook, modified for Evennia. - - Prepopulates the update form field values based on object db attributes. - - Returns: - data (dict): Dictionary of key:value pairs containing initial form - data. - - """ - # Get the object we want to update - obj = self.get_object() - - # Get attributes - data = {k: getattr(obj.db, k, "") for k in self.form_class.base_fields} - - # Get model fields - data.update({k: getattr(obj, k, "") for k in self.form_class.Meta.fields}) - - return data
- -
[docs] def form_valid(self, form): - """ - Override of Django hook. - - Updates object attributes based on values submitted. - - This is run when the form is submitted and the data on it is deemed - valid-- all values are within expected ranges, all strings contain - valid characters and lengths, etc. - - This method is only called if all values for the fields submitted - passed form validation, so at this point we can assume the data is - validated and sanitized. - - """ - # Get the attributes after they've been cleaned and validated - data = {k: v for k, v in form.cleaned_data.items() if k not in self.form_class.Meta.fields} - - # Update the object attributes - for key, value in data.items(): - self.object.attributes.add(key, value) - messages.success(self.request, "Successfully updated '%s' for %s." % (key, self.object)) - - # Do not return super().form_valid; we don't want to update the model - # instance, just its attributes. - return HttpResponseRedirect(self.get_success_url())
- - -# -# Account views -# - - -
[docs]class AccountMixin(TypeclassMixin): - """ - This is a "mixin", a modifier of sorts. - - Any view class with this in its inheritance list will be modified to work - with Account objects instead of generic Objects or otherwise. - - """ - - # -- Django constructs -- - model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS, - fallback=settings.FALLBACK_ACCOUNT_TYPECLASS) - form_class = website_forms.AccountForm
- - -
[docs]class AccountCreateView(AccountMixin, EvenniaCreateView): - """ - Account creation view. - - """ - - # -- Django constructs -- - template_name = "website/registration/register.html" - success_url = reverse_lazy("login") - -
[docs] def form_valid(self, form): - """ - Django hook, modified for Evennia. - - This hook is called after a valid form is submitted. - - When an account creation form is submitted and the data is deemed valid, - proceeds with creating the Account object. - - """ - # Get values provided - username = form.cleaned_data["username"] - password = form.cleaned_data["password1"] - email = form.cleaned_data.get("email", "") - - # Create account - account, errs = self.typeclass.create(username=username, password=password, email=email) - - # If unsuccessful, display error messages to user - if not account: - [messages.error(self.request, err) for err in errs] - - # Call the Django "form failure" hook - return self.form_invalid(form) - - # Inform user of success - messages.success( - self.request, - "Your account '%s' was successfully created! " - "You may log in using it now." % account.name, - ) - - # Redirect the user to the login page - return HttpResponseRedirect(self.success_url)
- - -# -# Character views -# - - -
[docs]class CharacterMixin(TypeclassMixin): - """ - This is a "mixin", a modifier of sorts. - - Any view class with this in its inheritance list will be modified to work - with Character objects instead of generic Objects or otherwise. - - """ - - # -- Django constructs -- - model = class_from_module(settings.BASE_CHARACTER_TYPECLASS, - fallback=settings.FALLBACK_CHARACTER_TYPECLASS) - form_class = website_forms.CharacterForm - success_url = reverse_lazy("character-manage") - -
[docs] def get_queryset(self): - """ - This method will override the Django get_queryset method to only - return a list of characters associated with the current authenticated - user. - - Returns: - queryset (QuerySet): Django queryset for use in the given view. - - """ - # Get IDs of characters owned by account - account = self.request.user - ids = [getattr(x, "id") for x in account.characters if x] - - # Return a queryset consisting of those characters - return self.typeclass.objects.filter(id__in=ids).order_by(Lower("db_key"))
- - -
[docs]class CharacterListView(LoginRequiredMixin, CharacterMixin, ListView): - """ - This view provides a mechanism by which a logged-in player can view a list - of all other characters. - - This view requires authentication by default as a nominal effort to prevent - human stalkers and automated bots/scrapers from harvesting data on your users. - - """ - - # -- Django constructs -- - template_name = "website/character_list.html" - paginate_by = 100 - - # -- Evennia constructs -- - page_title = "Character List" - access_type = "view" - -
[docs] def get_queryset(self): - """ - This method will override the Django get_queryset method to return a - list of all characters (filtered/sorted) instead of just those limited - to the account. - - Returns: - queryset (QuerySet): Django queryset for use in the given view. - - """ - account = self.request.user - - # Return a queryset consisting of characters the user is allowed to - # see. - ids = [ - obj.id for obj in self.typeclass.objects.all() if obj.access(account, self.access_type) - ] - - return self.typeclass.objects.filter(id__in=ids).order_by(Lower("db_key"))
- - -
[docs]class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, ObjectDetailView): - """ - This view provides a mechanism by which a logged-in player can "puppet" one - of their characters within the context of the website. - - It also ensures that any user attempting to puppet something is logged in, - and that their intended puppet is one that they own. - - """ - -
[docs] def get_redirect_url(self, *args, **kwargs): - """ - Django hook. - - This view returns the URL to which the user should be redirected after - a passed or failed puppet attempt. - - Returns: - url (str): Path to post-puppet destination. - - """ - # Get the requested character, if it belongs to the authenticated user - char = self.get_object() - - # Get the page the user came from - next_page = self.request.GET.get("next", self.success_url) - - if char: - # If the account owns the char, store the ID of the char in the - # Django request's session (different from Evennia session!). - # We do this because characters don't serialize well. - self.request.session["puppet"] = int(char.pk) - messages.success(self.request, "You become '%s'!" % char) - else: - # If the puppeting failed, clear out the cached puppet value - self.request.session["puppet"] = None - messages.error(self.request, "You cannot become '%s'." % char) - - return next_page
- - -
[docs]class CharacterManageView(LoginRequiredMixin, CharacterMixin, ListView): - """ - This view provides a mechanism by which a logged-in player can browse, - edit, or delete their own characters. - - """ - - # -- Django constructs -- - paginate_by = 10 - template_name = "website/character_manage_list.html" - - # -- Evennia constructs -- - page_title = "Manage Characters"
- - -
[docs]class CharacterUpdateView(CharacterMixin, ObjectUpdateView): - """ - This view provides a mechanism by which a logged-in player (enforced by - ObjectUpdateView) can edit the attributes of a character they own. - - """ - - # -- Django constructs -- - form_class = website_forms.CharacterUpdateForm - template_name = "website/character_form.html"
- - -
[docs]class CharacterDetailView(CharacterMixin, ObjectDetailView): - """ - This view provides a mechanism by which a user can view the attributes of - a character, owned by them or not. - - """ - - # -- Django constructs -- - template_name = "website/object_detail.html" - - # -- Evennia constructs -- - # What attributes to display for this object - attributes = ["name", "desc"] - access_type = "view" - -
[docs] def get_queryset(self): - """ - This method will override the Django get_queryset method to return a - list of all characters the user may access. - - Returns: - queryset (QuerySet): Django queryset for use in the given view. - - """ - account = self.request.user - - # Return a queryset consisting of characters the user is allowed to - # see. - ids = [ - obj.id for obj in self.typeclass.objects.all() if obj.access(account, self.access_type) - ] - - return self.typeclass.objects.filter(id__in=ids).order_by(Lower("db_key"))
- - -
[docs]class CharacterDeleteView(CharacterMixin, ObjectDeleteView): - """ - This view provides a mechanism by which a logged-in player (enforced by - ObjectDeleteView) can delete a character they own. - - """ - - pass
- - -
[docs]class CharacterCreateView(CharacterMixin, ObjectCreateView): - """ - This view provides a mechanism by which a logged-in player (enforced by - ObjectCreateView) can create a new character. - - """ - - # -- Django constructs -- - template_name = "website/character_form.html" - -
[docs] def form_valid(self, form): - """ - Django hook, modified for Evennia. - - This hook is called after a valid form is submitted. - - When an character creation form is submitted and the data is deemed valid, - proceeds with creating the Character object. - - """ - # Get account object creating the character - account = self.request.user - character = None - - # Get attributes from the form - self.attributes = {k: form.cleaned_data[k] for k in form.cleaned_data.keys()} - charname = self.attributes.pop("db_key") - description = self.attributes.pop("desc") - # Create a character - character, errors = self.typeclass.create(charname, account, description=description) - - if errors: - # Echo error messages to the user - [messages.error(self.request, x) for x in errors] - - if character: - # Assign attributes from form - for key, value in self.attributes.items(): - setattr(character.db, key, value) - - # Return the user to the character management page, unless overridden - messages.success(self.request, "Your character '%s' was created!" % character.name) - return HttpResponseRedirect(self.success_url) - - else: - # Call the Django "form failed" hook - messages.error(self.request, "Your character could not be created.") - return self.form_invalid(form)
- - -# -# Channel views -# - - -
[docs]class ChannelMixin(TypeclassMixin): - """ - This is a "mixin", a modifier of sorts. - - Any view class with this in its inheritance list will be modified to work - with HelpEntry objects instead of generic Objects or otherwise. - - """ - - # -- Django constructs -- - model = class_from_module(settings.BASE_CHANNEL_TYPECLASS, - fallback=settings.FALLBACK_CHANNEL_TYPECLASS) - - # -- Evennia constructs -- - page_title = "Channels" - - # What lock type to check for the requesting user, authenticated or not. - # https://github.com/evennia/evennia/wiki/Locks#valid-access_types - access_type = "listen" - -
[docs] def get_queryset(self): - """ - Django hook; here we want to return a list of only those Channels - and other documentation that the current user is allowed to see. - - Returns: - queryset (QuerySet): List of Channels available to the user. - - """ - account = self.request.user - - # Get list of all Channels - channels = self.typeclass.objects.all().iterator() - - # Now figure out which ones the current user is allowed to see - bucket = [channel.id for channel in channels if channel.access(account, "listen")] - - # Re-query and set a sorted list - filtered = self.typeclass.objects.filter(id__in=bucket).order_by(Lower("db_key")) - - return filtered
- - -
[docs]class ChannelListView(ChannelMixin, ListView): - """ - Returns a list of channels that can be viewed by a user, authenticated - or not. - - """ - - # -- Django constructs -- - paginate_by = 100 - template_name = "website/channel_list.html" - - # -- Evennia constructs -- - page_title = "Channel Index" - - max_popular = 10 - -
[docs] def get_context_data(self, **kwargs): - """ - Django hook; we override it to calculate the most popular channels. - - Returns: - context (dict): Django context object - - """ - context = super(ChannelListView, self).get_context_data(**kwargs) - - # Calculate which channels are most popular - context["most_popular"] = sorted( - list(self.get_queryset()), - key=lambda channel: len(channel.subscriptions.all()), - reverse=True, - )[: self.max_popular] - - return context
- - -
[docs]class ChannelDetailView(ChannelMixin, ObjectDetailView): - """ - Returns the log entries for a given channel. - - """ - - # -- Django constructs -- - template_name = "website/channel_detail.html" - - # -- Evennia constructs -- - # What attributes of the object you wish to display on the page. Model-level - # attributes will take precedence over identically-named db.attributes! - # The order you specify here will be followed. - attributes = ["name"] - - # How many log entries to read and display. - max_num_lines = 1000 - -
[docs] def get_context_data(self, **kwargs): - """ - Django hook; before we can display the channel logs, we need to recall - the logfile and read its lines. - - Returns: - context (dict): Django context object - - """ - # Get the parent context object, necessary first step - context = super(ChannelDetailView, self).get_context_data(**kwargs) - - # Get the filename this Channel is recording to - filename = self.object.attributes.get( - "log_file", default="channel_%s.log" % self.object.key - ) - - # Split log entries so we can filter by time - bucket = [] - for log in (x.strip() for x in tail_log_file(filename, 0, self.max_num_lines)): - if not log: - continue - try: - time, msg = log.split(" [-] ") - time_key = time.split(":")[0] - except ValueError: - # malformed log line - skip line - continue - bucket.append({"key": time_key, "timestamp": time, "message": msg}) - - # Add the processed entries to the context - context["object_list"] = bucket - - # Get a list of unique timestamps by hour and sort them - context["object_filters"] = sorted(set([x["key"] for x in bucket])) - - return context
- -
[docs] def get_object(self, queryset=None): - """ - Override of Django hook that retrieves an object by slugified channel - name. - - Returns: - channel (Channel): Channel requested in the URL. - - """ - # Get the queryset for the help entries the user can access - if not queryset: - queryset = self.get_queryset() - - # Find the object in the queryset - channel = slugify(self.kwargs.get("slug", "")) - obj = next((x for x in queryset if slugify(x.db_key) == channel), None) - - # Check if this object was requested in a valid manner - if not obj: - raise HttpResponseBadRequest( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} - ) - - return obj
- - -# -# Help views -# - - -
[docs]class HelpMixin(TypeclassMixin): - """ - This is a "mixin", a modifier of sorts. - - Any view class with this in its inheritance list will be modified to work - with HelpEntry objects instead of generic Objects or otherwise. - - """ - - # -- Django constructs -- - model = HelpEntry - - # -- Evennia constructs -- - page_title = "Help" - -
[docs] def get_queryset(self): - """ - Django hook; here we want to return a list of only those HelpEntries - and other documentation that the current user is allowed to see. - - Returns: - queryset (QuerySet): List of Help entries available to the user. - - """ - account = self.request.user - - # Get list of all HelpEntries - entries = self.typeclass.objects.all().iterator() - - # Now figure out which ones the current user is allowed to see - bucket = [entry.id for entry in entries if entry.access(account, "view")] - - # Re-query and set a sorted list - filtered = ( - self.typeclass.objects.filter(id__in=bucket) - .order_by(Lower("db_key")) - .order_by(Lower("db_help_category")) - ) - - return filtered
- - -
[docs]class HelpListView(HelpMixin, ListView): - """ - Returns a list of help entries that can be viewed by a user, authenticated - or not. - - """ - - # -- Django constructs -- - paginate_by = 500 - template_name = "website/help_list.html" - - # -- Evennia constructs -- - page_title = "Help Index"
- - -
[docs]class HelpDetailView(HelpMixin, EvenniaDetailView): - """ - Returns the detail page for a given help entry. - - """ - - # -- Django constructs -- - template_name = "website/help_detail.html" - -
[docs] def get_context_data(self, **kwargs): - """ - Adds navigational data to the template to let browsers go to the next - or previous entry in the help list. - - Returns: - context (dict): Django context object - - """ - context = super(HelpDetailView, self).get_context_data(**kwargs) - - # Get the object in question - obj = self.get_object() - - # Get queryset and filter out non-related categories - queryset = ( - self.get_queryset() - .filter(db_help_category=obj.db_help_category) - .order_by(Lower("db_key")) - ) - context["topic_list"] = queryset - - # Find the index position of the given obj in the queryset - objs = list(queryset) - for i, x in enumerate(objs): - if obj is x: - break - - # Find the previous and next topics, if either exist - try: - assert i + 1 <= len(objs) and objs[i + 1] is not obj - context["topic_next"] = objs[i + 1] - except: - context["topic_next"] = None - - try: - assert i - 1 >= 0 and objs[i - 1] is not obj - context["topic_previous"] = objs[i - 1] - except: - context["topic_previous"] = None - - # Format the help entry using HTML instead of newlines - text = obj.db_entrytext - text = text.replace("\r\n\r\n", "\n\n") - text = text.replace("\r\n", "\n") - text = text.replace("\n", "<br />") - context["entry_text"] = text - - return context
- -
[docs] def get_object(self, queryset=None): - """ - Override of Django hook that retrieves an object by category and topic - instead of pk and slug. - - Returns: - entry (HelpEntry): HelpEntry requested in the URL. - - """ - # Get the queryset for the help entries the user can access - if not queryset: - queryset = self.get_queryset() - - # Find the object in the queryset - category = slugify(self.kwargs.get("category", "")) - topic = slugify(self.kwargs.get("topic", "")) - obj = next( - ( - x - for x in queryset - if slugify(x.db_help_category) == category and slugify(x.db_key) == topic - ), - None, - ) - - # Check if this object was requested in a valid manner - if not obj: - return HttpResponseBadRequest( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} - ) - - return obj
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/functools.html b/docs/0.9.5/_modules/functools.html deleted file mode 100644 index 62ecd09d98..0000000000 --- a/docs/0.9.5/_modules/functools.html +++ /dev/null @@ -1,953 +0,0 @@ - - - - - - - - functools — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

Source code for functools

-"""functools.py - Tools for working with functions and callable objects
-"""
-# Python module wrapper for _functools C module
-# to allow utilities written in Python to be added
-# to the functools module.
-# Written by Nick Coghlan <ncoghlan at gmail.com>,
-# Raymond Hettinger <python at rcn.com>,
-# and Łukasz Langa <lukasz at langa.pl>.
-#   Copyright (C) 2006-2013 Python Software Foundation.
-# See C source code for _functools credits/copyright
-
-__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
-           'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial',
-           'partialmethod', 'singledispatch']
-
-try:
-    from _functools import reduce
-except ImportError:
-    pass
-from abc import get_cache_token
-from collections import namedtuple
-# import types, weakref  # Deferred to single_dispatch()
-from reprlib import recursive_repr
-from _thread import RLock
-
-
-################################################################################
-### update_wrapper() and wraps() decorator
-################################################################################
-
-# update_wrapper() and wraps() are tools to help write
-# wrapper functions that can handle naive introspection
-
-WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
-                       '__annotations__')
-WRAPPER_UPDATES = ('__dict__',)
-def update_wrapper(wrapper,
-                   wrapped,
-                   assigned = WRAPPER_ASSIGNMENTS,
-                   updated = WRAPPER_UPDATES):
-    """Update a wrapper function to look like the wrapped function
-
-       wrapper is the function to be updated
-       wrapped is the original function
-       assigned is a tuple naming the attributes assigned directly
-       from the wrapped function to the wrapper function (defaults to
-       functools.WRAPPER_ASSIGNMENTS)
-       updated is a tuple naming the attributes of the wrapper that
-       are updated with the corresponding attribute from the wrapped
-       function (defaults to functools.WRAPPER_UPDATES)
-    """
-    for attr in assigned:
-        try:
-            value = getattr(wrapped, attr)
-        except AttributeError:
-            pass
-        else:
-            setattr(wrapper, attr, value)
-    for attr in updated:
-        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
-    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
-    # from the wrapped function when updating __dict__
-    wrapper.__wrapped__ = wrapped
-    # Return the wrapper so this can be used as a decorator via partial()
-    return wrapper
-
-def wraps(wrapped,
-          assigned = WRAPPER_ASSIGNMENTS,
-          updated = WRAPPER_UPDATES):
-    """Decorator factory to apply update_wrapper() to a wrapper function
-
-       Returns a decorator that invokes update_wrapper() with the decorated
-       function as the wrapper argument and the arguments to wraps() as the
-       remaining arguments. Default arguments are as for update_wrapper().
-       This is a convenience function to simplify applying partial() to
-       update_wrapper().
-    """
-    return partial(update_wrapper, wrapped=wrapped,
-                   assigned=assigned, updated=updated)
-
-
-################################################################################
-### total_ordering class decorator
-################################################################################
-
-# The total ordering functions all invoke the root magic method directly
-# rather than using the corresponding operator.  This avoids possible
-# infinite recursion that could occur when the operator dispatch logic
-# detects a NotImplemented result and then calls a reflected method.
-
-def _gt_from_lt(self, other, NotImplemented=NotImplemented):
-    'Return a > b.  Computed by @total_ordering from (not a < b) and (a != b).'
-    op_result = self.__lt__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return not op_result and self != other
-
-def _le_from_lt(self, other, NotImplemented=NotImplemented):
-    'Return a <= b.  Computed by @total_ordering from (a < b) or (a == b).'
-    op_result = self.__lt__(other)
-    return op_result or self == other
-
-def _ge_from_lt(self, other, NotImplemented=NotImplemented):
-    'Return a >= b.  Computed by @total_ordering from (not a < b).'
-    op_result = self.__lt__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return not op_result
-
-def _ge_from_le(self, other, NotImplemented=NotImplemented):
-    'Return a >= b.  Computed by @total_ordering from (not a <= b) or (a == b).'
-    op_result = self.__le__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return not op_result or self == other
-
-def _lt_from_le(self, other, NotImplemented=NotImplemented):
-    'Return a < b.  Computed by @total_ordering from (a <= b) and (a != b).'
-    op_result = self.__le__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return op_result and self != other
-
-def _gt_from_le(self, other, NotImplemented=NotImplemented):
-    'Return a > b.  Computed by @total_ordering from (not a <= b).'
-    op_result = self.__le__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return not op_result
-
-def _lt_from_gt(self, other, NotImplemented=NotImplemented):
-    'Return a < b.  Computed by @total_ordering from (not a > b) and (a != b).'
-    op_result = self.__gt__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return not op_result and self != other
-
-def _ge_from_gt(self, other, NotImplemented=NotImplemented):
-    'Return a >= b.  Computed by @total_ordering from (a > b) or (a == b).'
-    op_result = self.__gt__(other)
-    return op_result or self == other
-
-def _le_from_gt(self, other, NotImplemented=NotImplemented):
-    'Return a <= b.  Computed by @total_ordering from (not a > b).'
-    op_result = self.__gt__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return not op_result
-
-def _le_from_ge(self, other, NotImplemented=NotImplemented):
-    'Return a <= b.  Computed by @total_ordering from (not a >= b) or (a == b).'
-    op_result = self.__ge__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return not op_result or self == other
-
-def _gt_from_ge(self, other, NotImplemented=NotImplemented):
-    'Return a > b.  Computed by @total_ordering from (a >= b) and (a != b).'
-    op_result = self.__ge__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return op_result and self != other
-
-def _lt_from_ge(self, other, NotImplemented=NotImplemented):
-    'Return a < b.  Computed by @total_ordering from (not a >= b).'
-    op_result = self.__ge__(other)
-    if op_result is NotImplemented:
-        return op_result
-    return not op_result
-
-_convert = {
-    '__lt__': [('__gt__', _gt_from_lt),
-               ('__le__', _le_from_lt),
-               ('__ge__', _ge_from_lt)],
-    '__le__': [('__ge__', _ge_from_le),
-               ('__lt__', _lt_from_le),
-               ('__gt__', _gt_from_le)],
-    '__gt__': [('__lt__', _lt_from_gt),
-               ('__ge__', _ge_from_gt),
-               ('__le__', _le_from_gt)],
-    '__ge__': [('__le__', _le_from_ge),
-               ('__gt__', _gt_from_ge),
-               ('__lt__', _lt_from_ge)]
-}
-
-def total_ordering(cls):
-    """Class decorator that fills in missing ordering methods"""
-    # Find user-defined comparisons (not those inherited from object).
-    roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
-    if not roots:
-        raise ValueError('must define at least one ordering operation: < > <= >=')
-    root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
-    for opname, opfunc in _convert[root]:
-        if opname not in roots:
-            opfunc.__name__ = opname
-            setattr(cls, opname, opfunc)
-    return cls
-
-
-################################################################################
-### cmp_to_key() function converter
-################################################################################
-
-def cmp_to_key(mycmp):
-    """Convert a cmp= function into a key= function"""
-    class K(object):
-        __slots__ = ['obj']
-        def __init__(self, obj):
-            self.obj = obj
-        def __lt__(self, other):
-            return mycmp(self.obj, other.obj) < 0
-        def __gt__(self, other):
-            return mycmp(self.obj, other.obj) > 0
-        def __eq__(self, other):
-            return mycmp(self.obj, other.obj) == 0
-        def __le__(self, other):
-            return mycmp(self.obj, other.obj) <= 0
-        def __ge__(self, other):
-            return mycmp(self.obj, other.obj) >= 0
-        __hash__ = None
-    return K
-
-try:
-    from _functools import cmp_to_key
-except ImportError:
-    pass
-
-
-################################################################################
-### partial() argument application
-################################################################################
-
-# Purely functional, no descriptor behaviour
-class partial:
-    """New function with partial application of the given arguments
-    and keywords.
-    """
-
-    __slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
-
-    def __new__(*args, **keywords):
-        if not args:
-            raise TypeError("descriptor '__new__' of partial needs an argument")
-        if len(args) < 2:
-            raise TypeError("type 'partial' takes at least one argument")
-        cls, func, *args = args
-        if not callable(func):
-            raise TypeError("the first argument must be callable")
-        args = tuple(args)
-
-        if hasattr(func, "func"):
-            args = func.args + args
-            tmpkw = func.keywords.copy()
-            tmpkw.update(keywords)
-            keywords = tmpkw
-            del tmpkw
-            func = func.func
-
-        self = super(partial, cls).__new__(cls)
-
-        self.func = func
-        self.args = args
-        self.keywords = keywords
-        return self
-
-    def __call__(*args, **keywords):
-        if not args:
-            raise TypeError("descriptor '__call__' of partial needs an argument")
-        self, *args = args
-        newkeywords = self.keywords.copy()
-        newkeywords.update(keywords)
-        return self.func(*self.args, *args, **newkeywords)
-
-    @recursive_repr()
-    def __repr__(self):
-        qualname = type(self).__qualname__
-        args = [repr(self.func)]
-        args.extend(repr(x) for x in self.args)
-        args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
-        if type(self).__module__ == "functools":
-            return f"functools.{qualname}({', '.join(args)})"
-        return f"{qualname}({', '.join(args)})"
-
-    def __reduce__(self):
-        return type(self), (self.func,), (self.func, self.args,
-               self.keywords or None, self.__dict__ or None)
-
-    def __setstate__(self, state):
-        if not isinstance(state, tuple):
-            raise TypeError("argument to __setstate__ must be a tuple")
-        if len(state) != 4:
-            raise TypeError(f"expected 4 items in state, got {len(state)}")
-        func, args, kwds, namespace = state
-        if (not callable(func) or not isinstance(args, tuple) or
-           (kwds is not None and not isinstance(kwds, dict)) or
-           (namespace is not None and not isinstance(namespace, dict))):
-            raise TypeError("invalid partial state")
-
-        args = tuple(args) # just in case it's a subclass
-        if kwds is None:
-            kwds = {}
-        elif type(kwds) is not dict: # XXX does it need to be *exactly* dict?
-            kwds = dict(kwds)
-        if namespace is None:
-            namespace = {}
-
-        self.__dict__ = namespace
-        self.func = func
-        self.args = args
-        self.keywords = kwds
-
-try:
-    from _functools import partial
-except ImportError:
-    pass
-
-# Descriptor version
-class partialmethod(object):
-    """Method descriptor with partial application of the given arguments
-    and keywords.
-
-    Supports wrapping existing descriptors and handles non-descriptor
-    callables as instance methods.
-    """
-
-    def __init__(*args, **keywords):
-        if len(args) >= 2:
-            self, func, *args = args
-        elif not args:
-            raise TypeError("descriptor '__init__' of partialmethod "
-                            "needs an argument")
-        elif 'func' in keywords:
-            func = keywords.pop('func')
-            self, *args = args
-        else:
-            raise TypeError("type 'partialmethod' takes at least one argument, "
-                            "got %d" % (len(args)-1))
-        args = tuple(args)
-
-        if not callable(func) and not hasattr(func, "__get__"):
-            raise TypeError("{!r} is not callable or a descriptor"
-                                 .format(func))
-
-        # func could be a descriptor like classmethod which isn't callable,
-        # so we can't inherit from partial (it verifies func is callable)
-        if isinstance(func, partialmethod):
-            # flattening is mandatory in order to place cls/self before all
-            # other arguments
-            # it's also more efficient since only one function will be called
-            self.func = func.func
-            self.args = func.args + args
-            self.keywords = func.keywords.copy()
-            self.keywords.update(keywords)
-        else:
-            self.func = func
-            self.args = args
-            self.keywords = keywords
-
-    def __repr__(self):
-        args = ", ".join(map(repr, self.args))
-        keywords = ", ".join("{}={!r}".format(k, v)
-                                 for k, v in self.keywords.items())
-        format_string = "{module}.{cls}({func}, {args}, {keywords})"
-        return format_string.format(module=self.__class__.__module__,
-                                    cls=self.__class__.__qualname__,
-                                    func=self.func,
-                                    args=args,
-                                    keywords=keywords)
-
-    def _make_unbound_method(self):
-        def _method(*args, **keywords):
-            call_keywords = self.keywords.copy()
-            call_keywords.update(keywords)
-            cls_or_self, *rest = args
-            call_args = (cls_or_self,) + self.args + tuple(rest)
-            return self.func(*call_args, **call_keywords)
-        _method.__isabstractmethod__ = self.__isabstractmethod__
-        _method._partialmethod = self
-        return _method
-
-    def __get__(self, obj, cls):
-        get = getattr(self.func, "__get__", None)
-        result = None
-        if get is not None:
-            new_func = get(obj, cls)
-            if new_func is not self.func:
-                # Assume __get__ returning something new indicates the
-                # creation of an appropriate callable
-                result = partial(new_func, *self.args, **self.keywords)
-                try:
-                    result.__self__ = new_func.__self__
-                except AttributeError:
-                    pass
-        if result is None:
-            # If the underlying descriptor didn't do anything, treat this
-            # like an instance method
-            result = self._make_unbound_method().__get__(obj, cls)
-        return result
-
-    @property
-    def __isabstractmethod__(self):
-        return getattr(self.func, "__isabstractmethod__", False)
-
-
-################################################################################
-### LRU Cache function decorator
-################################################################################
-
-_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
-
-class _HashedSeq(list):
-    """ This class guarantees that hash() will be called no more than once
-        per element.  This is important because the lru_cache() will hash
-        the key multiple times on a cache miss.
-
-    """
-
-    __slots__ = 'hashvalue'
-
-    def __init__(self, tup, hash=hash):
-        self[:] = tup
-        self.hashvalue = hash(tup)
-
-    def __hash__(self):
-        return self.hashvalue
-
-def _make_key(args, kwds, typed,
-             kwd_mark = (object(),),
-             fasttypes = {int, str},
-             tuple=tuple, type=type, len=len):
-    """Make a cache key from optionally typed positional and keyword arguments
-
-    The key is constructed in a way that is flat as possible rather than
-    as a nested structure that would take more memory.
-
-    If there is only a single argument and its data type is known to cache
-    its hash value, then that argument is returned without a wrapper.  This
-    saves space and improves lookup speed.
-
-    """
-    # All of code below relies on kwds preserving the order input by the user.
-    # Formerly, we sorted() the kwds before looping.  The new way is *much*
-    # faster; however, it means that f(x=1, y=2) will now be treated as a
-    # distinct call from f(y=2, x=1) which will be cached separately.
-    key = args
-    if kwds:
-        key += kwd_mark
-        for item in kwds.items():
-            key += item
-    if typed:
-        key += tuple(type(v) for v in args)
-        if kwds:
-            key += tuple(type(v) for v in kwds.values())
-    elif len(key) == 1 and type(key[0]) in fasttypes:
-        return key[0]
-    return _HashedSeq(key)
-
-def lru_cache(maxsize=128, typed=False):
-    """Least-recently-used cache decorator.
-
-    If *maxsize* is set to None, the LRU features are disabled and the cache
-    can grow without bound.
-
-    If *typed* is True, arguments of different types will be cached separately.
-    For example, f(3.0) and f(3) will be treated as distinct calls with
-    distinct results.
-
-    Arguments to the cached function must be hashable.
-
-    View the cache statistics named tuple (hits, misses, maxsize, currsize)
-    with f.cache_info().  Clear the cache and statistics with f.cache_clear().
-    Access the underlying function with f.__wrapped__.
-
-    See:  http://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
-
-    """
-
-    # Users should only access the lru_cache through its public API:
-    #       cache_info, cache_clear, and f.__wrapped__
-    # The internals of the lru_cache are encapsulated for thread safety and
-    # to allow the implementation to change (including a possible C version).
-
-    # Early detection of an erroneous call to @lru_cache without any arguments
-    # resulting in the inner function being passed to maxsize instead of an
-    # integer or None.  Negative maxsize is treated as 0.
-    if isinstance(maxsize, int):
-        if maxsize < 0:
-            maxsize = 0
-    elif maxsize is not None:
-        raise TypeError('Expected maxsize to be an integer or None')
-
-    def decorating_function(user_function):
-        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
-        return update_wrapper(wrapper, user_function)
-
-    return decorating_function
-
-def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
-    # Constants shared by all lru cache instances:
-    sentinel = object()          # unique object used to signal cache misses
-    make_key = _make_key         # build a key from the function arguments
-    PREV, NEXT, KEY, RESULT = 0, 1, 2, 3   # names for the link fields
-
-    cache = {}
-    hits = misses = 0
-    full = False
-    cache_get = cache.get    # bound method to lookup a key or return None
-    cache_len = cache.__len__  # get cache size without calling len()
-    lock = RLock()           # because linkedlist updates aren't threadsafe
-    root = []                # root of the circular doubly linked list
-    root[:] = [root, root, None, None]     # initialize by pointing to self
-
-    if maxsize == 0:
-
-        def wrapper(*args, **kwds):
-            # No caching -- just a statistics update
-            nonlocal misses
-            misses += 1
-            result = user_function(*args, **kwds)
-            return result
-
-    elif maxsize is None:
-
-        def wrapper(*args, **kwds):
-            # Simple caching without ordering or size limit
-            nonlocal hits, misses
-            key = make_key(args, kwds, typed)
-            result = cache_get(key, sentinel)
-            if result is not sentinel:
-                hits += 1
-                return result
-            misses += 1
-            result = user_function(*args, **kwds)
-            cache[key] = result
-            return result
-
-    else:
-
-        def wrapper(*args, **kwds):
-            # Size limited caching that tracks accesses by recency
-            nonlocal root, hits, misses, full
-            key = make_key(args, kwds, typed)
-            with lock:
-                link = cache_get(key)
-                if link is not None:
-                    # Move the link to the front of the circular queue
-                    link_prev, link_next, _key, result = link
-                    link_prev[NEXT] = link_next
-                    link_next[PREV] = link_prev
-                    last = root[PREV]
-                    last[NEXT] = root[PREV] = link
-                    link[PREV] = last
-                    link[NEXT] = root
-                    hits += 1
-                    return result
-                misses += 1
-            result = user_function(*args, **kwds)
-            with lock:
-                if key in cache:
-                    # Getting here means that this same key was added to the
-                    # cache while the lock was released.  Since the link
-                    # update is already done, we need only return the
-                    # computed result and update the count of misses.
-                    pass
-                elif full:
-                    # Use the old root to store the new key and result.
-                    oldroot = root
-                    oldroot[KEY] = key
-                    oldroot[RESULT] = result
-                    # Empty the oldest link and make it the new root.
-                    # Keep a reference to the old key and old result to
-                    # prevent their ref counts from going to zero during the
-                    # update. That will prevent potentially arbitrary object
-                    # clean-up code (i.e. __del__) from running while we're
-                    # still adjusting the links.
-                    root = oldroot[NEXT]
-                    oldkey = root[KEY]
-                    oldresult = root[RESULT]
-                    root[KEY] = root[RESULT] = None
-                    # Now update the cache dictionary.
-                    del cache[oldkey]
-                    # Save the potentially reentrant cache[key] assignment
-                    # for last, after the root and links have been put in
-                    # a consistent state.
-                    cache[key] = oldroot
-                else:
-                    # Put result in a new link at the front of the queue.
-                    last = root[PREV]
-                    link = [last, root, key, result]
-                    last[NEXT] = root[PREV] = cache[key] = link
-                    # Use the cache_len bound method instead of the len() function
-                    # which could potentially be wrapped in an lru_cache itself.
-                    full = (cache_len() >= maxsize)
-            return result
-
-    def cache_info():
-        """Report cache statistics"""
-        with lock:
-            return _CacheInfo(hits, misses, maxsize, cache_len())
-
-    def cache_clear():
-        """Clear the cache and cache statistics"""
-        nonlocal hits, misses, full
-        with lock:
-            cache.clear()
-            root[:] = [root, root, None, None]
-            hits = misses = 0
-            full = False
-
-    wrapper.cache_info = cache_info
-    wrapper.cache_clear = cache_clear
-    return wrapper
-
-try:
-    from _functools import _lru_cache_wrapper
-except ImportError:
-    pass
-
-
-################################################################################
-### singledispatch() - single-dispatch generic function decorator
-################################################################################
-
-def _c3_merge(sequences):
-    """Merges MROs in *sequences* to a single MRO using the C3 algorithm.
-
-    Adapted from http://www.python.org/download/releases/2.3/mro/.
-
-    """
-    result = []
-    while True:
-        sequences = [s for s in sequences if s]   # purge empty sequences
-        if not sequences:
-            return result
-        for s1 in sequences:   # find merge candidates among seq heads
-            candidate = s1[0]
-            for s2 in sequences:
-                if candidate in s2[1:]:
-                    candidate = None
-                    break      # reject the current head, it appears later
-            else:
-                break
-        if candidate is None:
-            raise RuntimeError("Inconsistent hierarchy")
-        result.append(candidate)
-        # remove the chosen candidate
-        for seq in sequences:
-            if seq[0] == candidate:
-                del seq[0]
-
-def _c3_mro(cls, abcs=None):
-    """Computes the method resolution order using extended C3 linearization.
-
-    If no *abcs* are given, the algorithm works exactly like the built-in C3
-    linearization used for method resolution.
-
-    If given, *abcs* is a list of abstract base classes that should be inserted
-    into the resulting MRO. Unrelated ABCs are ignored and don't end up in the
-    result. The algorithm inserts ABCs where their functionality is introduced,
-    i.e. issubclass(cls, abc) returns True for the class itself but returns
-    False for all its direct base classes. Implicit ABCs for a given class
-    (either registered or inferred from the presence of a special method like
-    __len__) are inserted directly after the last ABC explicitly listed in the
-    MRO of said class. If two implicit ABCs end up next to each other in the
-    resulting MRO, their ordering depends on the order of types in *abcs*.
-
-    """
-    for i, base in enumerate(reversed(cls.__bases__)):
-        if hasattr(base, '__abstractmethods__'):
-            boundary = len(cls.__bases__) - i
-            break   # Bases up to the last explicit ABC are considered first.
-    else:
-        boundary = 0
-    abcs = list(abcs) if abcs else []
-    explicit_bases = list(cls.__bases__[:boundary])
-    abstract_bases = []
-    other_bases = list(cls.__bases__[boundary:])
-    for base in abcs:
-        if issubclass(cls, base) and not any(
-                issubclass(b, base) for b in cls.__bases__
-            ):
-            # If *cls* is the class that introduces behaviour described by
-            # an ABC *base*, insert said ABC to its MRO.
-            abstract_bases.append(base)
-    for base in abstract_bases:
-        abcs.remove(base)
-    explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases]
-    abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases]
-    other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases]
-    return _c3_merge(
-        [[cls]] +
-        explicit_c3_mros + abstract_c3_mros + other_c3_mros +
-        [explicit_bases] + [abstract_bases] + [other_bases]
-    )
-
-def _compose_mro(cls, types):
-    """Calculates the method resolution order for a given class *cls*.
-
-    Includes relevant abstract base classes (with their respective bases) from
-    the *types* iterable. Uses a modified C3 linearization algorithm.
-
-    """
-    bases = set(cls.__mro__)
-    # Remove entries which are already present in the __mro__ or unrelated.
-    def is_related(typ):
-        return (typ not in bases and hasattr(typ, '__mro__')
-                                 and issubclass(cls, typ))
-    types = [n for n in types if is_related(n)]
-    # Remove entries which are strict bases of other entries (they will end up
-    # in the MRO anyway.
-    def is_strict_base(typ):
-        for other in types:
-            if typ != other and typ in other.__mro__:
-                return True
-        return False
-    types = [n for n in types if not is_strict_base(n)]
-    # Subclasses of the ABCs in *types* which are also implemented by
-    # *cls* can be used to stabilize ABC ordering.
-    type_set = set(types)
-    mro = []
-    for typ in types:
-        found = []
-        for sub in typ.__subclasses__():
-            if sub not in bases and issubclass(cls, sub):
-                found.append([s for s in sub.__mro__ if s in type_set])
-        if not found:
-            mro.append(typ)
-            continue
-        # Favor subclasses with the biggest number of useful bases
-        found.sort(key=len, reverse=True)
-        for sub in found:
-            for subcls in sub:
-                if subcls not in mro:
-                    mro.append(subcls)
-    return _c3_mro(cls, abcs=mro)
-
-def _find_impl(cls, registry):
-    """Returns the best matching implementation from *registry* for type *cls*.
-
-    Where there is no registered implementation for a specific type, its method
-    resolution order is used to find a more generic implementation.
-
-    Note: if *registry* does not contain an implementation for the base
-    *object* type, this function may return None.
-
-    """
-    mro = _compose_mro(cls, registry.keys())
-    match = None
-    for t in mro:
-        if match is not None:
-            # If *match* is an implicit ABC but there is another unrelated,
-            # equally matching implicit ABC, refuse the temptation to guess.
-            if (t in registry and t not in cls.__mro__
-                              and match not in cls.__mro__
-                              and not issubclass(match, t)):
-                raise RuntimeError("Ambiguous dispatch: {} or {}".format(
-                    match, t))
-            break
-        if t in registry:
-            match = t
-    return registry.get(match)
-
-def singledispatch(func):
-    """Single-dispatch generic function decorator.
-
-    Transforms a function into a generic function, which can have different
-    behaviours depending upon the type of its first argument. The decorated
-    function acts as the default implementation, and additional
-    implementations can be registered using the register() attribute of the
-    generic function.
-    """
-    # There are many programs that use functools without singledispatch, so we
-    # trade-off making singledispatch marginally slower for the benefit of
-    # making start-up of such applications slightly faster.
-    import types, weakref
-
-    registry = {}
-    dispatch_cache = weakref.WeakKeyDictionary()
-    cache_token = None
-
-    def dispatch(cls):
-        """generic_func.dispatch(cls) -> <function implementation>
-
-        Runs the dispatch algorithm to return the best available implementation
-        for the given *cls* registered on *generic_func*.
-
-        """
-        nonlocal cache_token
-        if cache_token is not None:
-            current_token = get_cache_token()
-            if cache_token != current_token:
-                dispatch_cache.clear()
-                cache_token = current_token
-        try:
-            impl = dispatch_cache[cls]
-        except KeyError:
-            try:
-                impl = registry[cls]
-            except KeyError:
-                impl = _find_impl(cls, registry)
-            dispatch_cache[cls] = impl
-        return impl
-
-    def register(cls, func=None):
-        """generic_func.register(cls, func) -> func
-
-        Registers a new implementation for the given *cls* on a *generic_func*.
-
-        """
-        nonlocal cache_token
-        if func is None:
-            if isinstance(cls, type):
-                return lambda f: register(cls, f)
-            ann = getattr(cls, '__annotations__', {})
-            if not ann:
-                raise TypeError(
-                    f"Invalid first argument to `register()`: {cls!r}. "
-                    f"Use either `@register(some_class)` or plain `@register` "
-                    f"on an annotated function."
-                )
-            func = cls
-
-            # only import typing if annotation parsing is necessary
-            from typing import get_type_hints
-            argname, cls = next(iter(get_type_hints(func).items()))
-            assert isinstance(cls, type), (
-                f"Invalid annotation for {argname!r}. {cls!r} is not a class."
-            )
-        registry[cls] = func
-        if cache_token is None and hasattr(cls, '__abstractmethods__'):
-            cache_token = get_cache_token()
-        dispatch_cache.clear()
-        return func
-
-    def wrapper(*args, **kw):
-        if not args:
-            raise TypeError(f'{funcname} requires at least '
-                            '1 positional argument')
-
-        return dispatch(args[0].__class__)(*args, **kw)
-
-    funcname = getattr(func, '__name__', 'singledispatch function')
-    registry[object] = func
-    wrapper.register = register
-    wrapper.dispatch = dispatch
-    wrapper.registry = types.MappingProxyType(registry)
-    wrapper._clear_cache = dispatch_cache.clear
-    update_wrapper(wrapper, func)
-    return wrapper
-
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_modules/index.html b/docs/0.9.5/_modules/index.html deleted file mode 100644 index 39c23a5592..0000000000 --- a/docs/0.9.5/_modules/index.html +++ /dev/null @@ -1,290 +0,0 @@ - - - - - - - - Overview: module code — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -

All modules for which code is available

- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/_sources/A-voice-operated-elevator-using-events.md.txt b/docs/0.9.5/_sources/A-voice-operated-elevator-using-events.md.txt deleted file mode 100644 index 24f6bdef33..0000000000 --- a/docs/0.9.5/_sources/A-voice-operated-elevator-using-events.md.txt +++ /dev/null @@ -1,436 +0,0 @@ -# A voice operated elevator using events - - -- Previous tutorial: [Adding dialogues in events](./Dialogues-in-events.md) - -This tutorial will walk you through the steps to create a voice-operated elevator, using the [in- -game Python -system](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md). -This tutorial assumes the in-game Python system is installed in your game. If it isn't, you can -follow the installation steps given in [the documentation on in-game -Python](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md), and -come back on this tutorial once the system is installed. **You do not need to read** the entire -documentation, it's a good reference, but not the easiest way to learn about it. Hence these -tutorials. - -The in-game Python system allows to run code on individual objects in some situations. You don't -have to modify the source code to add these features, past the installation. The entire system -makes it easy to add specific features to some objects, but not all. - -> What will we try to do? - -In this tutorial, we are going to create a simple voice-operated elevator. In terms of features, we -will: - -- Explore events with parameters. -- Work on more interesting callbacks. -- Learn about chained events. -- Play with variable modification in callbacks. - -## Our study case - -Let's summarize what we want to achieve first. We would like to create a room that will represent -the inside of our elevator. In this room, a character could just say "1", "2" or "3", and the -elevator will start moving. The doors will close and open on the new floor (the exits leading in -and out of the elevator will be modified). - -We will work on basic features first, and then will adjust some, showing you how easy and powerfully -independent actions can be configured through the in-game Python system. - -## Creating the rooms and exits we need - -We'll create an elevator right in our room (generally called "Limbo", of ID 2). You could easily -adapt the following instructions if you already have some rooms and exits, of course, just remember -to check the IDs. - -> Note: the in-game Python system uses IDs for a lot of things. While it is not mandatory, it is -good practice to know the IDs you have for your callbacks, because it will make manipulation much -quicker. There are other ways to identify objects, but as they depend on many factors, IDs are -usually the safest path in our callbacks. - -Let's go into limbo (`#2`) to add our elevator. We'll add it to the north. To create this room, -in-game you could type: - - tunnel n = Inside of an elevator - -The game should respond by telling you: - - Created room Inside of an elevator(#3) of type typeclasses.rooms.Room. - Created Exit from Limbo to Inside of an elevator: north(#4) (n). - Created Exit back from Inside of an elevator to Limbo: south(#5) (s). - -Note the given IDs: - -- `#2` is limbo, the first room the system created. -- `#3` is our room inside of an elevator. -- `#4` is the north exit from Limbo to our elevator. -- `#5` is the south exit from an elevator to Limbo. - -Keep these IDs somewhere for the demonstration. You will shortly see why they are important. - -> Why have we created exits to our elevator and back to Limbo? Isn't the elevator supposed to move? - -It is. But we need to have exits that will represent the way inside the elevator and out. What we -will do, at every floor, will be to change these exits so they become connected to the right room. -You'll see this process a bit later. - -We have two more rooms to create: our floor 2 and 3. This time, we'll use `dig`, because we don't -need exits leading there, not yet anyway. - - dig The second floor - dig The third floor - -Evennia should answer with: - - Created room The second floor(#6) of type typeclasses.rooms.Room. - Created room The third floor(#7) of type typeclasses.rooms.Room. - -Add these IDs to your list, we will use them too. - -## Our first callback in the elevator - -Let's go to the elevator (you could use `tel #3` if you have the same IDs I have). - -This is our elevator room. It looks a bit empty, feel free to add a prettier description or other -things to decorate it a bit. - -But what we want now is to be able to say "1", "2" or "3" and have the elevator move in that -direction. - -If you have read [the previous tutorial about adding dialogues in events](./Dialogues-in-events.md), you -may remember what we need to do. If not, here's a summary: we need to run some code when somebody -speaks in the room. So we need to create a callback (the callback will contain our lines of code). -We just need to know on which event this should be set. You can enter `call here` to see the -possible events in this room. - -In the table, you should see the "say" event, which is called when somebody says something in the -room. So we'll need to add a callback to this event. Don't worry if you're a bit lost, just follow -the following steps, the way they connect together will become more obvious. - - call/add here = say 1, 2, 3 - -1. We need to add a callback. A callback contains the code that will be executed at a given time. -So we use the `call/add` command and switch. -2. `here` is our object, the room in which we are. -3. An equal sign. -4. The name of the event to which the callback should be connected. Here, the event is "say". -Meaning this callback will be executed every time somebody says something in the room. -5. But we add an event parameter to indicate the keywords said in the room that should execute our -callback. Otherwise, our callback would be called every time somebody speaks, no matter what. Here -we limit, indicating our callback should be executed only if the spoken message contains "1", "2" or -"3". - -An editor should open, inviting you to enter the Python code that should be executed. The first -thing to remember is to read the text provided (it can contain important information) and, most of -all, the list of variables that are available in this callback: - -``` -Variables you can use in this event: - - character: the character having spoken in this room. - room: the room connected to this event. - message: the text having been spoken by the character. - -----------Line Editor [Callback say of Inside of an elevator]--------------------- -01| -----------[l:01 w:000 c:0000]------------(:h for help)---------------------------- -``` - -This is important, in order to know what variables we can use in our callback out-of-the-box. Let's -write a single line to be sure our callback is called when we expect it to: - -```python -character.msg("You just said {}.".format(message)) -``` - -You can paste this line in-game, then type the `:wq` command to exit the editor and save your -modifications. - -Let's check. Try to say "hello" in the room. You should see the standard message, but nothing -more. Now try to say "1". Below the standard message, you should see: - - You just said 1. - -You can try it. Our callback is only called when we say "1", "2" or "3". Which is just what we -want. - -Let's go back in our code editor and add something more useful. - - call/edit here = say - -> Notice that we used the "edit" switch this time, since the callback exists, we just want to edit -it. - -The editor opens again. Let's empty it first: - - :DD - -And turn off automatic indentation, which will help us: - - := - -> Auto-indentation is an interesting feature of the code editor, but we'd better not use it at this -point, it will make copy/pasting more complicated. - -## Our entire callback in the elevator - -So here's the time to truly code our callback in-game. Here's a little reminder: - -1. We have all the IDs of our three rooms and two exits. -2. When we say "1", "2" or "3", the elevator should move to the right room, that is change the -exits. Remember, we already have the exits, we just need to change their location and destination. - -It's a good idea to try to write this callback yourself, but don't feel bad about checking the -solution right now. Here's a possible code that you could paste in the code editor: - -```python -# First let's have some constants -ELEVATOR = get(id=3) -FLOORS = { - "1": get(id=2), - "2": get(id=6), - "3": get(id=7), -} -TO_EXIT = get(id=4) -BACK_EXIT = get(id=5) - -# Now we check that the elevator isn't already at this floor -floor = FLOORS.get(message) -if floor is None: - character.msg("Which floor do you want?") -elif TO_EXIT.location is floor: - character.msg("The elevator already is at this floor.") -else: - # 'floor' contains the new room where the elevator should be - room.msg_contents("The doors of the elevator close with a clank.") - TO_EXIT.location = floor - BACK_EXIT.destination = floor - room.msg_contents("The doors of the elevator open to {floor}.", - mapping=dict(floor=floor)) -``` - -Let's review this longer callback: - -1. We first obtain the objects of both exits and our three floors. We use the `get()` eventfunc, -which is a shortcut to obtaining objects. We usually use it to retrieve specific objects with an -ID. We put the floors in a dictionary. The keys of the dictionary are the floor number (as str), -the values are room objects. -2. Remember, the `message` variable contains the message spoken in the room. So either "1", "2", or -"3". We still need to check it, however, because if the character says something like "1 2" in the -room, our callback will be executed. Let's be sure what she says is a floor number. -3. We then check if the elevator is already at this floor. Notice that we use `TO_EXIT.location`. -`TO_EXIT` contains our "north" exit, leading inside of our elevator. Therefore, its `location` will -be the room where the elevator currently is. -4. If the floor is a different one, have the elevator "move", changing just the location and -destination of both exits. - - The `BACK_EXIT` (that is "north") should change its location. The elevator shouldn't be -accessible through our old floor. - - The `TO_EXIT` (that is "south", the exit leading out of the elevator) should have a different -destination. When we go out of the elevator, we should find ourselves in the new floor, not the old -one. - -Feel free to expand on this example, changing messages, making further checks. Usage and practice -are keys. - -You can quit the editor as usual with `:wq` and test it out. - -## Adding a pause in our callback - -Let's improve our callback. One thing that's worth adding would be a pause: for the time being, -when we say the floor number in the elevator, the doors close and open right away. It would be -better to have a pause of several seconds. More logical. - -This is a great opportunity to learn about chained events. Chained events are very useful to create -pauses. Contrary to the events we have seen so far, chained events aren't called automatically. -They must be called by you, and can be called after some time. - -- Chained events always have the name "chain_X". Usually, X is a number, but you can give the -chained event a more explicit name. -- In our original callback, we will call our chained events in, say, 15 seconds. -- We'll also have to make sure the elevator isn't already moving. - -Other than that, a chained event can be connected to a callback as usual. We'll create a chained -event in our elevator, that will only contain the code necessary to open the doors to the new floor. - - call/add here = chain_1 - -The callback is added to the "chain_1" event, an event that will not be automatically called by the -system when something happens. Inside this event, you can paste the code to open the doors at the -new floor. You can notice a few differences: - -```python -TO_EXIT.location = floor -TO_EXIT.destination = ELEVATOR -BACK_EXIT.location = ELEVATOR -BACK_EXIT.destination = floor -room.msg_contents("The doors of the elevator open to {floor}.", - mapping=dict(floor=floor)) -``` - -Paste this code into the editor, then use `:wq` to save and quit the editor. - -Now let's edit our callback in the "say" event. We'll have to change it a bit: - -- The callback will have to check the elevator isn't already moving. -- It must change the exits when the elevator move. -- It has to call the "chain_1" event we have defined. It should call it 15 seconds later. - -Let's see the code in our callback. - - call/edit here = say - -Remove the current code and disable auto-indentation again: - - :DD - := - -And you can paste instead the following code. Notice the differences with our first attempt: - -```python -# First let's have some constants -ELEVATOR = get(id=3) -FLOORS = { - "1": get(id=2), - "2": get(id=6), - "3": get(id=7), -} -TO_EXIT = get(id=4) -BACK_EXIT = get(id=5) - -# Now we check that the elevator isn't already at this floor -floor = FLOORS.get(message) -if floor is None: - character.msg("Which floor do you want?") -elif BACK_EXIT.location is None: - character.msg("The elevator is between floors.") -elif TO_EXIT.location is floor: - character.msg("The elevator already is at this floor.") -else: - # 'floor' contains the new room where the elevator should be - room.msg_contents("The doors of the elevator close with a clank.") - TO_EXIT.location = None - BACK_EXIT.location = None - call_event(room, "chain_1", 15) -``` - -What changed? - -1. We added a little test to make sure the elevator wasn't already moving. If it is, the -`BACK_EXIT.location` (the "south" exit leading out of the elevator) should be `None`. We'll remove -the exit while the elevator is moving. -2. When the doors close, we set both exits' `location` to `None`. Which "removes" them from their -room but doesn't destroy them. The exits still exist but they don't connect anything. If you say -"2" in the elevator and look around while the elevator is moving, you won't see any exits. -3. Instead of opening the doors immediately, we call `call_event`. We give it the object containing -the event to be called (here, our elevator), the name of the event to be called (here, "chain_1") -and the number of seconds from now when the event should be called (here, `15`). -4. The `chain_1` callback we have created contains the code to "re-open" the elevator doors. That -is, besides displaying a message, it reset the exits' `location` and `destination`. - -If you try to say "3" in the elevator, you should see the doors closing. Look around you and you -won't see any exit. Then, 15 seconds later, the doors should open, and you can leave the elevator -to go to the third floor. While the elevator is moving, the exit leading to it will be -inaccessible. - -> Note: we don't define the variables again in our chained event, we just call them. When we -execute `call_event`, a copy of our current variables is placed in the database. These variables -will be restored and accessible again when the chained event is called. - -You can use the `call/tasks` command to see the tasks waiting to be executed. For instance, say "2" -in the room, notice the doors closing, and then type the `call/tasks` command. You will see a task -in the elevator, waiting to call the `chain_1` event. - -## Changing exit messages - -Here's another nice little feature of events: you can modify the message of a single exit without -altering the others. In this case, when someone goes north into our elevator, we'd like to see -something like: "someone walks into the elevator." Something similar for the back exit would be -great too. - -Inside of the elevator, you can look at the available events on the exit leading outside (south). - - call south - -You should see two interesting rows in this table: - -``` -| msg_arrive | 0 (0) | Customize the message when a character | -| | | arrives through this exit. | -| msg_leave | 0 (0) | Customize the message when a character leaves | -| | | through this exit. | -``` - -So we can change the message others see when a character leaves, by editing the "msg_leave" event. -Let's do that: - - call/add south = msg_leave - -Take the time to read the help. It gives you all the information you should need. We'll need to -change the "message" variable, and use custom mapping (between braces) to alter the message. We're -given an example, let's use it. In the code editor, you can paste the following line: - -```python -message = "{character} walks out of the elevator." -``` - -Again, save and quit the editor by entering `:wq`. You can create a new character to see it leave. - - charcreate A beggar - tel #8 = here - -(Obviously, adapt the ID if necessary.) - - py self.search("beggar").move_to(self.search("south")) - -This is a crude way to force our beggar out of the elevator, but it allows us to test. You should -see: - - A beggar(#8) walks out of the elevator. - -Great! Let's do the same thing for the exit leading inside of the elevator. Follow the beggar, -then edit "msg_leave" of "north": - - call/add north = msg_leave - -```python -message = "{character} walks into the elevator." -``` - -Again, you can force our beggar to move and see the message we have just set. This modification -applies to these two exits, obviously: the custom message won't be used for other exits. Since we -use the same exits for every floor, this will be available no matter at what floor the elevator is, -which is pretty neat! - -## Tutorial F.A.Q. - -- **Q:** what happens if the game reloads or shuts down while a task is waiting to happen? -- **A:** if your game reloads while a task is in pause (like our elevator between floors), when the -game is accessible again, the task will be called (if necessary, with a new time difference to take -into account the reload). If the server shuts down, obviously, the task will not be called, but -will be stored and executed when the server is up again. -- **Q:** can I use all kinds of variables in my callback? Whether chained or not? -- **A:** you can use every variable type you like in your original callback. However, if you -execute `call_event`, since your variables are stored in the database, they will need to respect the -constraints on persistent attributes. A callback will not be stored in this way, for instance. -This variable will not be available in your chained event. -- **Q:** when you say I can call my chained events something else than "chain_1", "chain_2" and -such, what is the naming convention? -- **A:** chained events have names beginning by "chain_". This is useful for you and for the -system. But after the underscore, you can give a more useful name, like "chain_open_doors" in our -case. -- **Q:** do I have to pause several seconds to call a chained event? -- **A:** no, you can call it right away. Just leave the third parameter of `call_event` out (it -will default to 0, meaning the chained event will be called right away). This will not create a -task. -- **Q:** can I have chained events calling themselves? -- **A:** you can. There's no limitation. Just be careful, a callback that calls itself, -particularly without delay, might be a good recipe for an infinite loop. However, in some cases, it -is useful to have chained events calling themselves, to do the same repeated action every X seconds -for instance. -- **Q:** what if I need several elevators, do I need to copy/paste these callbacks each time? -- **A:** not advisable. There are definitely better ways to handle this situation. One of them is -to consider adding the code in the source itself. Another possibility is to call chained events -with the expected behavior, which makes porting code very easy. This side of chained events will be -shown in the next tutorial. - -- Previous tutorial: [Adding dialogues in events](./Dialogues-in-events.md) diff --git a/docs/0.9.5/_sources/API-refactoring.md.txt b/docs/0.9.5/_sources/API-refactoring.md.txt deleted file mode 100644 index 689b5893f5..0000000000 --- a/docs/0.9.5/_sources/API-refactoring.md.txt +++ /dev/null @@ -1,46 +0,0 @@ -# API refactoring - -Building up to Evennia 1.0 and beyond, it's time to comb through the Evennia API for old cruft. This -whitepage is for anyone interested to contribute with their views on what part of the API needs -refactoring, cleanup or clarification (or extension!) - -Note that this is not a forum. To keep things clean, each opinion text should ideally present a -clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be -held in chat or forum. - ---- - -### Griatch (Aug 13, 2019) - -This is how to enter an opinion. Use any markdown needed but stay within your section. Also remember -to copy your text to the clipboard before saving since if someone else edited the wiki in the -meantime you'll have to start over. - -### Griatch (Sept 2, 2019) - -I don't agree with removing explicit keywords as suggested by [Johnny on Aug 29 below](API- -refactoring#reduce-usage-of-optionalpositional-arguments-aug-29-2019). Overriding such a method can -still be done by `get(self, **kwargs)` if so desired, making the kwargs explicit helps IMO -readability of the API. If just giving a generic `**kwargs`, one must read the docstring or even the -code to see which keywords are valid. - -On the other hand, I think it makes sense to as a standard offer an extra `**kwargs` at the end of -arg-lists for common methods that are expected to be over-ridden. This make the API more flexible by -hinting to the dev that they could expand their own over-ridden implementation with their own -keyword arguments if so desired. - ---- - -### Johnny - -#### Reduce usage of optional/positional arguments (Aug 29, 2019) -``` -# AttributeHandler -def get(self, key=None, default=None, category=None, return_obj=False, - strattr=False, raise_exception=False, accessing_obj=None, - default_access=True, return_list=False): -``` -Many classes have methods requiring lengthy positional argument lists, which are tedious and error- -prone to extend and override especially in cases where not all arguments are even required. It would -be useful if arguments were reserved for required inputs and anything else relegated to kwargs for -easier passthrough on extension. diff --git a/docs/0.9.5/_sources/Accounts.md.txt b/docs/0.9.5/_sources/Accounts.md.txt deleted file mode 100644 index 6d61136583..0000000000 --- a/docs/0.9.5/_sources/Accounts.md.txt +++ /dev/null @@ -1,108 +0,0 @@ -# Accounts - - -All *users* (real people) that starts a game [Session](./Sessions.md) on Evennia are doing so through an -object called *Account*. The Account object has no in-game representation, it represents a unique -game account. In order to actually get on the game the Account must *puppet* an [Object](./Objects.md) -(normally a [Character](./Objects.md#characters)). - -Exactly how many Sessions can interact with an Account and its Puppets at once is determined by -Evennia's [MULTISESSION_MODE](./Sessions.md#multisession-mode) setting. - -Apart from storing login information and other account-specific data, the Account object is what is -chatting on [Channels](./Communications.md). It is also a good place to store [Permissions](./Locks.md) to be -consistent between different in-game characters as well as configuration options. The Account -object also has its own [CmdSet](./Command-Sets.md), the `AccountCmdSet`. - -Logged into default evennia, you can use the `ooc` command to leave your current -[character](./Objects.md) and go into OOC mode. You are quite limited in this mode, basically it works -like a simple chat program. It acts as a staging area for switching between Characters (if your -game supports that) or as a safety mode if your Character gets deleted. Use `ic` to attempt to -(re)puppet a Character. - -Note that the Account object can have, and often does have, a different set of -[Permissions](./Locks.md#permissions) from the Character they control. Normally you should put your -permissions on the Account level - this will overrule permissions set on the Character level. For -the permissions of the Character to come into play the default `quell` command can be used. This -allows for exploring the game using a different permission set (but you can't escalate your -permissions this way - for hierarchical permissions like `Builder`, `Admin` etc, the *lower* of the -permissions on the Character/Account will always be used). - -## How to create your own Account types - -You will usually not want more than one Account typeclass for all new accounts (but you could in -principle create a system that changes an account's typeclass dynamically). - -An Evennia Account is, per definition, a Python class that includes `evennia.DefaultAccount` among -its parents. In `mygame/typeclasses/accounts.py` there is an empty class ready for you to modify. -Evennia defaults to using this (it inherits directly from `DefaultAccount`). - -Here's an example of modifying the default Account class in code: - -```python - # in mygame/typeclasses/accounts.py - - from evennia import DefaultAccount - - class Account(DefaultAccount): # [...] - - at_account_creation(self): "this is called only once, when account is first created" - self.db.real_name = None # this is set later self.db.real_address = None # -" - self.db.config_1 = True # default config self.db.config_2 = False # " - self.db.config_3 = 1 # " - - # ... whatever else our game needs to know ``` Reload the server with `reload`. - -``` - -... However, if you use `examine *self` (the asterisk makes you examine your Account object rather -than your Character), you won't see your new Attributes yet. This is because `at_account_creation` -is only called the very *first* time the Account is called and your Account object already exists -(any new Accounts that connect will see them though). To update yourself you need to make sure to -re-fire the hook on all the Accounts you have already created. Here is an example of how to do this -using `py`: - - -``` py [account.at_account_creation() for account in evennia.managers.accounts.all()] ``` - -You should now see the Attributes on yourself. - - -> If you wanted Evennia to default to a completely *different* Account class located elsewhere, you -> must point Evennia to it. Add `BASE_ACCOUNT_TYPECLASS` to your settings file, and give the python -> path to your custom class as its value. By default this points to `typeclasses.accounts.Account`, -> the empty template we used above. - - -## Properties on Accounts - -Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), the -Account also has the following custom properties: - -- `user` - a unique link to a `User` Django object, representing the logged-in user. -- `obj` - an alias for `character`. -- `name` - an alias for `user.username` -- `sessions` - an instance of - [ObjectSessionHandler](github:evennia.objects.objects#objectsessionhandler) - managing all connected Sessions (physical connections) this object listens to (Note: In older - versions of Evennia, this was a list). The so-called `session-id` (used in many places) is found -as - a property `sessid` on each Session instance. -- `is_superuser` (bool: True/False) - if this account is a superuser. - -Special handlers: -- `cmdset` - This holds all the current [Commands](./Commands.md) of this Account. By default these are - the commands found in the cmdset defined by `settings.CMDSET_ACCOUNT`. -- `nicks` - This stores and handles [Nicks](./Nicks.md), in the same way as nicks it works on Objects. - For Accounts, nicks are primarily used to store custom aliases for -[Channels](./Communications.md#channels). - -Selection of special methods (see `evennia.DefaultAccount` for details): -- `get_puppet` - get a currently puppeted object connected to the Account and a given session id, if - any. -- `puppet_object` - connect a session to a puppetable Object. -- `unpuppet_object` - disconnect a session from a puppetable Object. -- `msg` - send text to the Account -- `execute_cmd` - runs a command as if this Account did it. -- `search` - search for Accounts. diff --git a/docs/0.9.5/_sources/Add-a-simple-new-web-page.md.txt b/docs/0.9.5/_sources/Add-a-simple-new-web-page.md.txt deleted file mode 100644 index c146a54f10..0000000000 --- a/docs/0.9.5/_sources/Add-a-simple-new-web-page.md.txt +++ /dev/null @@ -1,100 +0,0 @@ -# Add a simple new web page - - -Evennia leverages [Django](https://docs.djangoproject.com) which is a web development framework. -Huge professional websites are made in Django and there is extensive documentation (and books) on it -. You are encouraged to at least look at the Django basic tutorials. Here we will just give a brief -introduction for how things hang together, to get you started. - -We assume you have installed and set up Evennia to run. A webserver and website comes out of the -box. You can get to that by entering `http://localhost:4001` in your web browser - you should see a -welcome page with some game statistics and a link to the web client. Let us add a new page that you -can get to by going to `http://localhost:4001/story`. - -### Create the view - -A django "view" is a normal Python function that django calls to render the HTML page you will see -in the web browser. Here we will just have it spit back the raw html, but Django can do all sorts of -cool stuff with the page in the view, like adding dynamic content or change it on the fly. Open -`mygame/web` folder and add a new module there named `story.py` (you could also put it in its own -folder if you wanted to be neat. Don't forget to add an empty `__init__.py` file if you do, to tell -Python you can import from the new folder). Here's how it looks: - -```python -# in mygame/web/story.py - -from django.shortcuts import render - -def storypage(request): - return render(request, "story.html") -``` - -This view takes advantage of a shortcut provided to use by Django, _render_. This shortcut gives the -template some information from the request, for instance, the game name, and then renders it. - -### The HTML page - -We need to find a place where Evennia (and Django) looks for html files (called *templates* in -Django parlance). You can specify such places in your settings (see the `TEMPLATES` variable in -`default_settings.py` for more info), but here we'll use an existing one. Go to -`mygame/template/overrides/website/` and create a page `story.html` there. - -This is not a HTML tutorial, so we'll go simple: - -```html -{% extends "base.html" %} -{% block content %} -
-
-

A story about a tree

-

- This is a story about a tree, a classic tale ... -

-
-
-{% endblock %} -``` - -Since we've used the _render_ shortcut, Django will allow us to extend our base styles easily. - -If you'd rather not take advantage of Evennia's base styles, you can do something like this instead: - -```html - - -

A story about a tree

-

- This is a story about a tree, a classic tale ... - - -``` - - -### The URL - -When you enter the address `http://localhost:4001/story` in your web browser, Django will parse that -field to figure out which page you want to go to. You tell it which patterns are relevant in the -file -[mygame/web/urls.py](https://github.com/evennia/evennia/blob/master/evennia/game_template/web/urls.py). -Open it now. - -Django looks for the variable `urlpatterns` in this file. You want to add your new pattern to the -`custom_patterns` list we have prepared - that is then merged with the default `urlpatterns`. Here's -how it could look: - -```python -from web import story - -# ... - -custom_patterns = [ - url(r'story', story.storypage, name='Story'), -] -``` - -That is, we import our story view module from where we created it earlier and then create an `url` -instance. The first argument to `url` is the pattern of the url we want to find (`"story"`) (this is -a regular expression if you are familiar with those) and then our view function we want to direct -to. - -That should be it. Reload Evennia and you should be able to browse to your new story page! diff --git a/docs/0.9.5/_sources/Add-a-wiki-on-your-website.md.txt b/docs/0.9.5/_sources/Add-a-wiki-on-your-website.md.txt deleted file mode 100644 index a8f476d28e..0000000000 --- a/docs/0.9.5/_sources/Add-a-wiki-on-your-website.md.txt +++ /dev/null @@ -1,232 +0,0 @@ -# Add a wiki on your website - - -**Before doing this tutorial you will probably want to read the intro in -[Basic Web tutorial](./Web-Tutorial.md).** Reading the three first parts of the -[Django tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/) might help as well. - -This tutorial will provide a step-by-step process to installing a wiki on your website. -Fortunately, you don't have to create the features manually, since it has been done by others, and -we can integrate their work quite easily with Django. I have decided to focus on -the [Django-wiki](http://django-wiki.readthedocs.io/). - -> Note: this article has been updated for Evennia 0.9. If you're not yet using this version, be -careful, as the django wiki doesn't support Python 2 anymore. (Remove this note when enough time -has passed.) - -The [Django-wiki](http://django-wiki.readthedocs.io/) offers a lot of features associated with -wikis, is -actively maintained (at this time, anyway), and isn't too difficult to install in Evennia. You can -see a [demonstration of Django-wiki here](https://demo.django.wiki). - -## Basic installation - -You should begin by shutting down the Evennia server if it is running. We will run migrations and -alter the virtual environment just a bit. Open a terminal and activate your Python environment, the -one you use to run the `evennia` command. - -* On Linux: - ``` - source evenv/bin/activate - ``` -* Or Windows: - ``` - evenv\bin\activate - ``` - -### Installing with pip - -Install the wiki using pip: - - pip install wiki - -> Note: this will install the last version of Django wiki. Version >0.4 doesn't support Python 2, so -install wiki 0.3 if you haven't updated to Python 3 yet. - -It might take some time, the Django-wiki having some dependencies. - -### Adding the wiki in the settings - -You will need to add a few settings to have the wiki app on your website. Open your -`server/conf/settings.py` file and add the following at the bottom (but before importing -`secret_settings`). Here's what you'll find in my own setting file (add the whole Django-wiki -section): - -```python -r""" -Evennia settings file. - -... - -""" - -# Use the defaults from Evennia unless explicitly overridden -from evennia.settings_default import * - -###################################################################### -# Evennia base server config -###################################################################### - -# This is the name of your game. Make it catchy! -SERVERNAME = "demowiki" - -###################################################################### -# Django-wiki settings -###################################################################### -INSTALLED_APPS += ( - 'django.contrib.humanize.apps.HumanizeConfig', - 'django_nyt.apps.DjangoNytConfig', - 'mptt', - 'sorl.thumbnail', - 'wiki.apps.WikiConfig', - 'wiki.plugins.attachments.apps.AttachmentsConfig', - 'wiki.plugins.notifications.apps.NotificationsConfig', - 'wiki.plugins.images.apps.ImagesConfig', - 'wiki.plugins.macros.apps.MacrosConfig', -) - -# Disable wiki handling of login/signup -WIKI_ACCOUNT_HANDLING = False -WIKI_ACCOUNT_SIGNUP_ALLOWED = False - -###################################################################### -# Settings given in secret_settings.py override those in this file. -###################################################################### -try: - from server.conf.secret_settings import * -except ImportError: - print("secret_settings.py file not found or failed to import.") -``` - -### Adding the new URLs - -Next we need to add two URLs in our `web/urls.py` file. Open it and compare the following output: -you will need to add two URLs in `custom_patterns` and add one import line: - -```python -from django.conf.urls import url, include -from django.urls import path # NEW! - -# default evenni a patterns -from evennia.web.urls import urlpatterns - -# eventual custom patterns -custom_patterns = [ - # url(r'/desired/url/', view, name='example'), - url('notifications/', include('django_nyt.urls')), # NEW! - url('wiki/', include('wiki.urls')), # NEW! -] - -# this is required by Django. -urlpatterns = custom_patterns + urlpatterns -``` - -You will probably need to copy line 2, 10, and 11. Be sure to place them correctly, as shown in -the example above. - -### Running migrations - -It's time to run the new migrations. The wiki app adds a few tables in our database. We'll need to -run: - - evennia migrate - -And that's it, you can start the server. If you go to http://localhost:4001/wiki , you should see -the wiki. Use your account's username and password to connect to it. That's how simple it is. - -## Customizing privileges - -A wiki can be a great collaborative tool, but who can see it? Who can modify it? Django-wiki comes -with a privilege system centered around four values per wiki page. The owner of an article can -always read and write in it (which is somewhat logical). The group of the article defines who can -read and who can write, if the user seeing the page belongs to this group. The topic of groups in -wiki pages will not be discussed here. A last setting determines which other user (that is, these -who aren't in the groups, and aren't the article's owner) can read and write. Each article has -these four settings (group read, group write, other read, other write). Depending on your purpose, -it might not be a good default choice, particularly if you have to remind every builder to keep the -pages private. Fortunately, Django-wiki gives us additional settings to customize who can read, and -who can write, a specific article. - -These settings must be placed, as usual, in your `server/conf/settings.py` file. They take a -function as argument, said function (or callback) will be called with the article and the user. -Remember, a Django user, for us, is an account. So we could check lockstrings on them if needed. -Here is a default setting to restrict the wiki: only builders can write in it, but anyone (including -non-logged in users) can read it. The superuser has some additional privileges. - -```python -# In server/conf/settings.py -# ... - -def is_superuser(article, user): - """Return True if user is a superuser, False otherwise.""" - return not user.is_anonymous() and user.is_superuser - -def is_builder(article, user): - """Return True if user is a builder, False otherwise.""" - return not user.is_anonymous() and user.locks.check_lockstring(user, "perm(Builders)") - -def is_anyone(article, user): - """Return True even if the user is anonymous.""" - return True - -# Who can create new groups and users from the wiki? -WIKI_CAN_ADMIN = is_superuser -# Who can change owner and group membership? -WIKI_CAN_ASSIGN = is_superuser -# Who can change group membership? -WIKI_CAN_ASSIGN_OWNER = is_superuser -# Who can change read/write access to groups or others? -WIKI_CAN_CHANGE_PERMISSIONS = is_superuser -# Who can soft-delete an article? -WIKI_CAN_DELETE = is_builder -# Who can lock an article and permanently delete it? -WIKI_CAN_MODERATE = is_superuser -# Who can edit articles? -WIKI_CAN_WRITE = is_builder -# Who can read articles? -WIKI_CAN_READ = is_anyone -``` - -Here, we have created three functions: one to return `True` if the user is the superuser, one to -return `True` if the user is a builder, one to return `True` no matter what (this includes if the -user is anonymous, E.G. if it's not logged-in). We then change settings to allow either the -superuser or -each builder to moderate, read, write, delete, and more. You can, of course, add more functions, -adapting them to your need. This is just a demonstration. - -Providing the `WIKI_CAN*...` settings will bypass the original permission system. The superuser -could change permissions of an article, but still, only builders would be able to write it. If you -need something more custom, you will have to expand on the functions you use. - -### Managing wiki pages from Evennia - -Unfortunately, Django wiki doesn't provide a clear and clean entry point to read and write articles -from Evennia and it doesn't seem to be a very high priority. If you really need to keep Django wiki -and to create and manage wiki pages from your code, you can do so, but this article won't elaborate, -as this is somewhat more technical. - -However, it is a good opportunity to present a small project that has been created more recently: -[evennia-wiki](https://github.com/vincent-lg/evennia-wiki) has been created to provide a simple -wiki, more tailored to Evennia and easier to connect. It doesn't, as yet, provide as many options -as does Django wiki, but it's perfectly usable: - -- Pages have an inherent and much-easier to understand hierarchy based on URLs. -- Article permissions are connected to Evennia groups and are much easier to accommodate specific -requirements. -- Articles can easily be created, read or updated from the Evennia code itself. -- Markdown is fully-supported with a default integration to Bootstrap to look good on an Evennia -website. Tables and table of contents are supported as well as wiki links. -- The process to override wiki templates makes full use of the `template_overrides` directory. - -However evennia-wiki doesn't yet support: - -- Images in markdown and the uploading schema. If images are important to you, please consider -contributing to this new project. -- Modifying permissions on a per page/setting basis. -- Moving pages to new locations. -- Viewing page history. - -Considering the list of features in Django wiki, obviously other things could be added to the list. -However, these features may be the most important and useful. Additional ones might not be that -necessary. If you're interested in supporting this little project, you are more than welcome to -[contribute to it](https://github.com/vincent-lg/evennia-wiki). Thanks! \ No newline at end of file diff --git a/docs/0.9.5/_sources/Adding-Command-Tutorial.md.txt b/docs/0.9.5/_sources/Adding-Command-Tutorial.md.txt deleted file mode 100644 index 569c12252d..0000000000 --- a/docs/0.9.5/_sources/Adding-Command-Tutorial.md.txt +++ /dev/null @@ -1,171 +0,0 @@ -# Adding Command Tutorial - -This is a quick first-time tutorial expanding on the [Commands](./Commands.md) documentation. - -Let's assume you have just downloaded Evennia, installed it and created your game folder (let's call -it just `mygame` here). Now you want to try to add a new command. This is the fastest way to do it. - -## Step 1: Creating a custom command - -1. Open `mygame/commands/command.py` in a text editor. This is just one place commands could be -placed but you get it setup from the onset as an easy place to start. It also already contains some -example code. -1. Create a new class in `command.py` inheriting from `default_cmds.MuxCommand`. Let's call it - `CmdEcho` in this example. -1. Set the class variable `key` to a good command name, like `echo`. -1. Give your class a useful _docstring_. A docstring is the string at the very top of a class or -function/method. The docstring at the top of the command class is read by Evennia to become the help -entry for the Command (see - [Command Auto-help](./Help-System.md#command-auto-help-system)). -1. Define a class method `func(self)` that echoes your input back to you. - -Below is an example how this all could look for the echo command: - -```python - # file mygame/commands/command.py - #[...] - from evennia import default_cmds - class CmdEcho(default_cmds.MuxCommand): - """ - Simple command example - - Usage: - echo [text] - - This command simply echoes text back to the caller. - """ - - key = "echo" - - def func(self): - "This actually does things" - if not self.args: - self.caller.msg("You didn't enter anything!") - else: - self.caller.msg("You gave the string: '%s'" % self.args) -``` - -## Step 2: Adding the Command to a default Cmdset - -The command is not available to use until it is part of a [Command Set](./Command-Sets.md). In this -example we will go the easiest route and add it to the default Character commandset that already -exists. - -1. Edit `mygame/commands/default_cmdsets.py` -1. Import your new command with `from commands.command import CmdEcho`. -1. Add a line `self.add(CmdEcho())` to `CharacterCmdSet`, in the `at_cmdset_creation` method (the - template tells you where). - -This is approximately how it should look at this point: - -```python - # file mygame/commands/default_cmdsets.py - #[...] - from commands.command import CmdEcho - #[...] - class CharacterCmdSet(default_cmds.CharacterCmdSet): - - key = "DefaultCharacter" - - def at_cmdset_creation(self): - - # this first adds all default commands - super().at_cmdset_creation() - - # all commands added after this point will extend or - # overwrite the default commands. - self.add(CmdEcho()) -``` - -Next, run the `@reload` command. You should now be able to use your new `echo` command from inside -the game. Use `help echo` to see the documentation for the command. - -If you have trouble, make sure to check the log for error messages (probably due to syntax errors in -your command definition). - -> Note: Typing `echotest` will also work. It will be handled as the command `echo` directly followed -by -its argument `test` (which will end up in `self.args). To change this behavior, you can add the -`arg_regex` property alongside `key`, `help_category` etc. [See the arg_regex -documentation](./Commands.md#on-arg_regex) for more info. - -If you want to overload existing default commands (such as `look` or `get`), just add your new -command with the same key as the old one - it will then replace it. Just remember that you must use -`@reload` to see any changes. - -See [Commands](./Commands.md) for many more details and possibilities when defining Commands and using -Cmdsets in various ways. - - -## Adding the command to specific object types - -Adding your Command to the `CharacterCmdSet` is just one easy exapmple. The cmdset system is very -generic. You can create your own cmdsets (let's say in a module `mycmdsets.py`) and add them to -objects as you please (how to control their merging is described in detail in the [Command Set -documentation](./Command-Sets.md)). - -```python - # file mygame/commands/mycmdsets.py - #[...] - from commands.command import CmdEcho - from evennia import CmdSet - #[...] - class MyCmdSet(CmdSet): - - key = "MyCmdSet" - - def at_cmdset_creation(self): - self.add(CmdEcho()) -``` -Now you just need to add this to an object. To test things (as superuser) you can do - - @py self.cmdset.add("mycmdsets.MyCmdSet") - -This will add this cmdset (along with its echo command) to yourself so you can test it. Note that -you cannot add a single Command to an object on its own, it must be part of a CommandSet in order to -do so. - -The Command you added is not there permanently at this point. If you do a `@reload` the merger will -be gone. You *could* add the `permanent=True` keyword to the `cmdset.add` call. This will however -only make the new merged cmdset permanent on that *single* object. Often you want *all* objects of -this particular class to have this cmdset. - -To make sure all new created objects get your new merged set, put the `cmdset.add` call in your -custom [Typeclasses](./Typeclasses.md)' `at_object_creation` method: - -```python - # e.g. in mygame/typeclasses/objects.py - - from evennia import DefaultObject - class MyObject(DefaultObject): - - def at_object_creation(self): - "called when the object is first created" - self.cmdset.add("mycmdset.MyCmdSet", permanent=True) -``` - -All new objects of this typeclass will now start with this cmdset and it will survive a `@reload`. - -*Note:* An important caveat with this is that `at_object_creation` is only called *once*, when the -object is first created. This means that if you already have existing objects in your databases -using that typeclass, they will not have been initiated the same way. There are many ways to update -them; since it's a one-time update you can usually just simply loop through them. As superuser, try -the following: - - @py from typeclasses.objects import MyObject; [o.cmdset.add("mycmdset.MyCmdSet") for o in -MyObject.objects.all()] - -This goes through all objects in your database having the right typeclass, adding the new cmdset to -each. The good news is that you only have to do this if you want to post-add *cmdsets*. If you just -want to add a new *command*, you can simply add that command to the cmdset's `at_cmdset_creation` -and `@reload` to make the Command immediately available. - -## Change where Evennia looks for command sets - -Evennia uses settings variables to know where to look for its default command sets. These are -normally not changed unless you want to re-organize your game folder in some way. For example, the -default character cmdset defaults to being defined as - - CMDSET_CHARACTER="commands.default_cmdset.CharacterCmdSet" - -See `evennia/settings_default.py` for the other settings. diff --git a/docs/0.9.5/_sources/Adding-Object-Typeclass-Tutorial.md.txt b/docs/0.9.5/_sources/Adding-Object-Typeclass-Tutorial.md.txt deleted file mode 100644 index 4e4e0b66bb..0000000000 --- a/docs/0.9.5/_sources/Adding-Object-Typeclass-Tutorial.md.txt +++ /dev/null @@ -1,109 +0,0 @@ -# Adding Object Typeclass Tutorial - -Evennia comes with a few very basic classes of in-game entities: - - DefaultObject - | - DefaultCharacter - DefaultRoom - DefaultExit - DefaultChannel - -When you create a new Evennia game (with for example `evennia --init mygame`) Evennia will -automatically create empty child classes `Object`, `Character`, `Room` and `Exit` respectively. They -are found `mygame/typeclasses/objects.py`, `mygame/typeclasses/rooms.py` etc. - -> Technically these are all [Typeclassed](./Typeclasses.md), which can be ignored for now. In -> `mygame/typeclasses` are also base typeclasses for out-of-character things, notably -> [Channels](./Communications.md), [Accounts](./Accounts.md) and [Scripts](./Scripts.md). We don't cover those in -> this tutorial. - -For your own game you will most likely want to expand on these very simple beginnings. It's normal -to want your Characters to have various attributes, for example. Maybe Rooms should hold extra -information or even *all* Objects in your game should have properties not included in basic Evennia. - -## Change Default Rooms, Exits, Character Typeclass - -This is the simplest case. - -The default build commands of a new Evennia game is set up to use the `Room`, `Exit` and `Character` -classes found in the same-named modules under `mygame/typeclasses/`. By default these are empty and -just implements the default parents from the Evennia library (`DefaultRoom`etc). Just add the -changes you want to these classes and run `@reload` to add your new functionality. - -## Create a new type of object - -Say you want to create a new "Heavy" object-type that characters should not have the ability to pick -up. - -1. Edit `mygame/typeclasses/objects.py` (you could also create a new module there, named something - like `heavy.py`, that's up to how you want to organize things). -1. Create a new class inheriting at any distance from `DefaultObject`. It could look something like - this: -```python - # end of file mygame/typeclasses/objects.py - from evennia import DefaultObject - - class Heavy(DefaultObject): - "Heavy object" - def at_object_creation(self): - "Called whenever a new object is created" - # lock the object down by default - self.locks.add("get:false()") - # the default "get" command looks for this Attribute in order - # to return a customized error message (we just happen to know - # this, you'd have to look at the code of the 'get' command to - # find out). - self.db.get_err_msg = "This is too heavy to pick up." -``` -1. Once you are done, log into the game with a build-capable account and do `@create/drop - rock:objects.Heavy` to drop a new heavy "rock" object in your location. Next try to pick it up -(`@quell` yourself first if you are a superuser). If you get errors, look at your log files where -you will find the traceback. The most common error is that you have some sort of syntax error in -your class. - -Note that the [Locks](./Locks.md) and [Attribute](./Attributes.md) which are set in the typeclass could just -as well have been set using commands in-game, so this is a *very* simple example. - -## Storing data on initialization - -The `at_object_creation` is only called once, when the object is first created. This makes it ideal -for database-bound things like [Attributes](./Attributes.md). But sometimes you want to create temporary -properties (things that are not to be stored in the database but still always exist every time the -object is created). Such properties can be initialized in the `at_init` method on the object. -`at_init` is called every time the object is loaded into memory. - -> Note: It's usually pointless and wasteful to assign database data in `at_init`, since this will -> hit the database with the same value over and over. Put those in `at_object_creation` instead. - -You are wise to use `ndb` (non-database Attributes) to store these non-persistent properties, since -ndb-properties are protected against being cached out in various ways and also allows you to list -them using various in-game tools: - -```python -def at_init(self): - self.ndb.counter = 0 - self.ndb.mylist = [] -``` - -> Note: As mentioned in the [Typeclasses](./Typeclasses.md) documentation, `at_init` replaces the use of -> the standard `__init__` method of typeclasses due to how the latter may be called in situations -> other than you'd expect. So use `at_init` where you would normally use `__init__`. - - -## Updating existing objects - -If you already have some `Heavy` objects created and you add a new `Attribute` in -`at_object_creation`, you will find that those existing objects will not have this Attribute. This -is not so strange, since `at_object_creation` is only called once, it will not be called again just -because you update it. You need to update existing objects manually. - -If the number of objects is limited, you can use `@typeclass/force/reload objectname` to force a -re-load of the `at_object_creation` method (only) on the object. This case is common enough that -there is an alias `@update objectname` you can use to get the same effect. If there are multiple -objects you can use `@py` to loop over the objects you need: - -``` -@py from typeclasses.objects import Heavy; [obj.at_object_creation() for obj in Heavy.objects.all()] - -``` diff --git a/docs/0.9.5/_sources/Administrative-Docs.md.txt b/docs/0.9.5/_sources/Administrative-Docs.md.txt deleted file mode 100644 index 154fbd83ec..0000000000 --- a/docs/0.9.5/_sources/Administrative-Docs.md.txt +++ /dev/null @@ -1,76 +0,0 @@ -# Administrative Docs - -The following pages are aimed at game administrators -- the higher-ups that possess shell access and -are responsible for managing the game. - -### Installation and Early Life - -- [Choosing (and installing) an SQL Server](./Choosing-An-SQL-Server.md) -- [Getting Started - Installing Evennia](./Getting-Started.md) -- [Running Evennia in Docker Containers](./Running-Evennia-in-Docker.md) -- [Starting, stopping, reloading and resetting Evennia](./Start-Stop-Reload.md) -- [Keeping your game up to date](./Updating-Your-Game.md) - - [Resetting your database](./Updating-Your-Game.md#resetting-your-database) -- [Making your game available online](./Online-Setup.md) - - [Hosting options](./Online-Setup.md#hosting-options) - - [Securing your server with SSL/Let's Encrypt](./Online-Setup.md#ssl) -- [Listing your game](./Evennia-Game-Index.md) at the online [Evennia game -index](http://games.evennia.com) - -### Customizing the server - -- [Changing the Settings](./Server-Conf.md#settings-file) - - [Available Master -Settings](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py) -- [Change Evennia's language](./Internationalization.md) (internationalization) -- [Apache webserver configuration](./Apache-Config.md) (optional) -- [Changing text encodings used by the server](./Text-Encodings.md) -- [The Connection Screen](./Connection-Screen.md) -- [Guest Logins](./Guest-Logins.md) -- [How to connect Evennia to IRC channels](./IRC.md) -- [How to connect Evennia to RSS feeds](./RSS.md) -- [How to connect Evennia to Grapevine](./Grapevine.md) -- [How to connect Evennia to Twitter](./How-to-connect-Evennia-to-Twitter.md) - -### Administrating the running game - -- [Supported clients](./Client-Support-Grid.md) (grid of known client issues) -- [Changing Permissions](./Building-Permissions.md) of users -- [Banning](./Banning.md) and deleting users - - [Summary of abuse-handling tools](./Banning.md#summary-of-abuse-handling-tools) in the default cmdset - -### Working with Evennia - -- [Setting up your work environment with version control](./Version-Control.md) -- [First steps coding with Evennia](./First-Steps-Coding.md) -- [Setting up a continuous integration build environment](./Continuous-Integration.md) - - -```{toctree} - :hidden: - - Choosing-An-SQL-Server - Getting-Started - Running-Evennia-in-Docker - Start-Stop-Reload - Updating-Your-Game - Online-Setup - Evennia-Game-Index - Server-Conf - Internationalization - Apache-Config - Text-Encodings - Connection-Screen - Guest-Logins - IRC - RSS - Grapevine - How-to-connect-Evennia-to-Twitter - Client-Support-Grid - Building-Permissions - Banning - Version-Control - First-Steps-Coding - Continuous-Integration - -``` \ No newline at end of file diff --git a/docs/0.9.5/_sources/Apache-Config.md.txt b/docs/0.9.5/_sources/Apache-Config.md.txt deleted file mode 100644 index 76a21ee24f..0000000000 --- a/docs/0.9.5/_sources/Apache-Config.md.txt +++ /dev/null @@ -1,171 +0,0 @@ -# Apache Config - - -**Warning**: This information is presented as a convenience, using another webserver than Evennia's -own is not directly supported and you are on your own if you want to do so. Evennia's webserver -works out of the box without any extra configuration and also runs in-process making sure to avoid -caching race conditions. The browser web client will most likely not work (at least not without -tweaking) on a third-party web server. - -One reason for wanting to use an external webserver like Apache would be to act as a *proxy* in -front of the Evennia webserver. Getting this working with TLS (encryption) requires some extra work -covered at the end of this page. - -Note that the Apache instructions below might be outdated. If something is not working right, or you -use Evennia with a different server, please let us know. Also, if there is a particular Linux distro -you would like covered, please let us know. - -## `mod_wsgi` Setup - -### Install `mod_wsgi` - -- *Fedora/RHEL* - Apache HTTP Server and `mod_wsgi` are available in the standard package -repositories for Fedora and RHEL: - ``` - $ dnf install httpd mod_wsgi - or - $ yum install httpd mod_wsgi - ``` -- *Ubuntu/Debian* - Apache HTTP Server and `mod_wsgi` are available in the standard package -repositories for Ubuntu and Debian: - ``` - $ apt-get update - $ apt-get install apache2 libapache2-mod-wsgi - ``` - -### Copy and modify the VHOST - -After `mod_wsgi` is installed, copy the `evennia/web/utils/evennia_wsgi_apache.conf` file to your -apache2 vhosts/sites folder. On Debian/Ubuntu, this is `/etc/apache2/sites-enabled/`. Make your -modifications **after** copying the file there. - -Read the comments and change the paths to point to the appropriate locations within your setup. - -### Restart/Reload Apache - -You'll then want to reload or restart apache2 after changing the configurations. - -- *Fedora/RHEL/Ubuntu* - ``` - $ systemctl restart httpd - ``` -- *Ubuntu/Debian* - ``` - $ systemctl restart apache2 - ``` - -### Enjoy - -With any luck, you'll be able to point your browser at your domain or subdomain that you set up in -your vhost and see the nifty default Evennia webpage. If not, read the hopefully informative error -message and work from there. Questions may be directed to our [Evennia Community -site](http://evennia.com). - -### A note on code reloading - -If your `mod_wsgi` is set up to run on daemon mode (as will be the case by default on Debian and -Ubuntu), you may tell `mod_wsgi` to reload by using the `touch` command on -`evennia/game/web/utils/apache_wsgi.conf`. When `mod_wsgi` sees that the file modification time has -changed, it will force a code reload. Any modifications to the code will not be propagated to the -live instance of your site until reloaded. - -If you are not running in daemon mode or want to force the issue, simply restart or reload apache2 -to apply your changes. - -### Further notes and hints: - -If you get strange (and usually uninformative) `Permission denied` errors from Apache, make sure -that your `evennia` directory is located in a place the webserver may actually access. For example, -some Linux distributions may default to very restrictive access permissions on a user's `/home` -directory. - -One user commented that they had to add the following to their Apache config to get things to work. -Not confirmed, but worth trying if there are trouble. - - /evennia/game/web"> - Options +ExecCGI - Allow from all - - -## `mod_proxy` and `mod_ssl` setup - -Below are steps on running Evennia using a front-end proxy (Apache HTTP), `mod_proxy_http`, -`mod_proxy_wstunnel`, and `mod_ssl`. `mod_proxy_http` and `mod_proxy_wstunnel` will simply be -referred to as -`mod_proxy` below. - -### Install `mod_ssl` - -- *Fedora/RHEL* - Apache HTTP Server and `mod_ssl` are available in the standard package -repositories for Fedora and RHEL: - ``` - $ dnf install httpd mod_ssl - or - $ yum install httpd mod_ssl - - ``` -- *Ubuntu/Debian* - Apache HTTP Server and `mod_sslj`kl are installed together in the `apache2` -package and available in the -standard package repositories for Ubuntu and Debian. `mod_ssl` needs to be enabled after -installation: - ``` - $ apt-get update - $ apt-get install apache2 - $ a2enmod ssl - - ``` - -### TLS proxy+websocket configuration - -Below is a sample configuration for Evennia with a TLS-enabled http and websocket proxy. - -#### Apache HTTP Server Configuration - -``` - - # Always redirect to https/443 - ServerName mud.example.com - Redirect / https://mud.example.com - - - - ServerName mud.example.com - - SSLEngine On - - # Location of certificate and key - SSLCertificateFile /etc/pki/tls/certs/mud.example.com.crt - SSLCertificateKeyFile /etc/pki/tls/private/mud.example.com.key - - # Use a tool https://www.ssllabs.com/ssltest/ to scan your set after setting up. - SSLProtocol TLSv1.2 - SSLCipherSuite HIGH:!eNULL:!NULL:!aNULL - - # Proxy all websocket traffic to port 4002 in Evennia - ProxyPass /ws ws://127.0.0.1:4002/ - ProxyPassReverse /ws ws://127.0.0.1:4002/ - - # Proxy all HTTP traffic to port 4001 in Evennia - ProxyPass / http://127.0.0.1:4001/ - ProxyPassReverse / http://127.0.0.1:4001/ - - # Configure separate logging for this Evennia proxy - ErrorLog logs/evennia_error.log - CustomLog logs/evennia_access.log combined - -``` - -#### Evennia secure websocket configuration - -There is a slight trick in setting up Evennia so websocket traffic is handled correctly by the -proxy. You must set the `WEBSOCKET_CLIENT_URL` setting in your `mymud/server/conf/settings.py` file: - -``` -WEBSOCKET_CLIENT_URL = "wss://external.example.com/ws" -``` - -The setting above is what the client's browser will actually use. Note the use of `wss://` is -because our client will be communicating over an encrypted connection ("wss" indicates websocket -over SSL/TLS). Also, especially note the additional path `/ws` at the end of the URL. This is how -Apache HTTP Server identifies that a particular request should be proxied to Evennia's websocket -port but this should be applicable also to other types of proxies (like nginx). diff --git a/docs/0.9.5/_sources/Arxcode-installing-help.md.txt b/docs/0.9.5/_sources/Arxcode-installing-help.md.txt deleted file mode 100644 index e7efdfae28..0000000000 --- a/docs/0.9.5/_sources/Arxcode-installing-help.md.txt +++ /dev/null @@ -1,272 +0,0 @@ -# Arxcode installing help - -## Introduction - -[Arx - After the Reckoning](http://play.arxmush.org/) is a big and very popular -[Evennia](http://www.evennia.com)-based game. Arx is heavily roleplaying-centric, relying on game -masters to drive the story. Technically it's maybe best described as "a MUSH, but with more coded -systems". In August of 2018, the game's developer, Tehom, generously released the [source code of -Arx on github](https://github.com/Arx-Game/arxcode). This is a treasure-trove for developers wanting -to pick ideas or even get a starting game to build on. These instructions are based on the Arx-code -released as of *Aug 12, 2018*. - -If you are not familiar with what Evennia is, you can read -[an introduction here](./Evennia-Introduction.md). - -It's not too hard to run Arx from the sources (of course you'll start with an empty database) but -since part of Arx has grown organically, it doesn't follow standard Evennia paradigms everywhere. -This page covers one take on installing and setting things up while making your new Arx-based game -better match with the vanilla Evennia install. - -## Installing Evennia - -Firstly, set aside a folder/directory on your drive for everything to follow. - -You need to start by installing [Evennia](http://www.evennia.com) by following most of the [Getting -Started -Instructions](./Getting-Started.md) for your OS. The difference is that you need to `git clone -https://github.com/TehomCD/evennia.git` instead of Evennia's repo because Arx uses TehomCD's older -Evennia 0.8 [fork](https://github.com/TehomCD/evennia), notably still using Python2. This detail is -important if referring to newer Evennia documentation. - -If you are new to Evennia it's *highly* recommended that you run through the -instructions in full - including initializing and starting a new empty game and connecting to it. -That way you can be sure Evennia works correctly as a base line. If you have trouble, make sure to -read the [Troubleshooting instructions](./Getting-Started.md#troubleshooting) for your -operating system. You can also drop into our -[forums](https://groups.google.com/forum/#%21forum/evennia), join `#evennia` on `irc.freenode.net` -or chat from the linked [Discord Server](https://discord.gg/NecFePw). - -After installing you should have a `virtualenv` running and you should have the following file -structure in your set-aside folder: - -``` -vienv/ -evennia/ -mygame/ - -``` - -Here `mygame` is the empty game you created during the Evennia install, with `evennia --init`. Go to -that and run `evennia stop` to make sure your empty game is not running. We'll instead let Evenna -run Arx, so in principle you could erase `mygame` - but it could also be good to have a clean game -to compare to. - -## Installing Arxcode - -### Clone the arxcode repo - -Cd to the root of your directory and clone the released source code from github: - - git clone https://github.com/Arx-Game/arxcode.git myarx - -A new folder `myarx` should appear next to the ones you already had. You could rename this to -something else if you want. - -Cd into `myarx`. If you wonder about the structure of the game dir, you can [read more about it -here](./Directory-Overview.md). - -### Clean up settings - -Arx has split evennia's normal settings into `base_settings.py` and `production_settings.py`. It -also has its own solution for managing 'secret' parts of the settings file. We'll keep most of Arx -way but remove the secret-handling and replace it with the normal Evennia method. - -Cd into `myarx/server/conf/` and open the file `settings.py` in a text editor. The top part (within -`"""..."""`) is just help text. Wipe everything underneath that and make it look like this instead -(don't forget to save): - -``` -from base_settings import * - -TELNET_PORTS = [4000] -SERVERNAME = "MyArx" -GAME_SLOGAN = "The cool game" - -try: - from server.conf.secret_settings import * -except ImportError: - print("secret_settings.py file not found or failed to import.") -``` - -> Note: Indents and capitalization matter in Python. Make indents 4 spaces (not tabs) for your own -> sanity. If you want a starter on Python in Evennia, [you can look here](Python-basic- -introduction). - -This will import Arx' base settings and override them with the Evennia-default telnet port and give -the game a name. The slogan changes the sub-text shown under the name of your game in the website -header. You can tweak these to your own liking later. - -Next, create a new, empty file `secret_settings.py` in the same location as the `settings.py` file. -This can just contain the following: - -```python -SECRET_KEY = "sefsefiwwj3 jnwidufhjw4545_oifej whewiu hwejfpoiwjrpw09&4er43233fwefwfw" - -``` - -Replace the long random string with random ASCII characters of your own. The secret key should not -be shared. - -Next, open `myarx/server/conf/base_settings.py` in your text editor. We want to remove/comment out -all mentions of the `decouple` package, which Evennia doesn't use (we use `private_settings.py` to -hide away settings that should not be shared). - -Comment out `from decouple import config` by adding a `#` to the start of the line: `# from decouple -import config`. Then search for `config(` in the file and comment out all lines where this is used. -Many of these are specific to the server environment where the original Arx runs, so is not that -relevant to us. - -### Install Arx dependencies - -Arx has some further dependencies beyond vanilla Evennia. Start by `cd`:ing to the root of your -`myarx` folder. - -> If you run *Linux* or *Mac*: Edit `myarx/requirements.txt` and comment out the line -> `pypiwin32==219` - it's only needed on Windows and will give an error on other platforms. - -Make sure your `virtualenv` is active, then run - - pip install -r requirements.txt - -The needed Python packages will be installed for you. - -### Adding logs/ folder - -The Arx repo does not contain the `myarx/server/logs/` folder Evennia expects for storing server -logs. This is simple to add: - - # linux/mac - mkdir server/logs - # windows - mkdir server\logs - -### Setting up the database and starting - -From the `myarx` folder, run - - evennia migrate - -This creates the database and will step through all database migrations needed. - - evennia start - -If all goes well Evennia will now start up, running Arx! You can connect to it on `localhost` (or -`127.0.0.1` if your platform doesn't alias `localhost`), port `4000` using a Telnet client. -Alternatively, you can use your web browser to browse to `http://localhost:4001` to see the game's -website and get to the web client. - -When you log in you'll get the standard Evennia greeting (since the database is empty), but you can -try `help` to see that it's indeed Arx that is running. - -### Additional Setup Steps - -The first time you start Evennia after creating the database with the `evennia migrate` step above, -it should create a few starting objects for you - your superuser account, which it will prompt you -to enter, a starting room (Limbo), and a character object for you. If for some reason this does not -occur, you may have to follow the steps below. For the first time Superuser login you may have to -run steps 7-8 and 10 to create and connect to your in-came Character. - -1. Login to the game website with your Superuser account. -2. Press the `Admin` button to get into the (Django-) Admin Interface. -3. Navigate to the `Accounts` section. -4. Add a new Account named for the new staffer. Use a place holder password and dummy e-mail - address. -5. Flag account as `Staff` and apply the `Admin` permission group (This assumes you have already set - up an Admin Group in Django). -6. Add Tags named `player` and `developer`. -7. Log into the game using the web client (or a third-party telnet client) using your superuser - account. Move to where you want the new staffer character to appear. -8. In the game client, run `@create/drop :typeclasses.characters.Character`, where - `` is usually the same name you used for the Staffer account you created in the - Admin earlier (if you are creating a Character for your superuser, use your superuser account -name). - This creates a new in-game Character and places it in your current location. -9. Have the new Admin player log into the game. -10. Have the new Admin puppet the character with `@ic StafferName`. -11. Have the new Admin change their password - `@password = `. - -Now that you have a Character and an Account object, there's a few additional things you may need to -do in order for some commands to function properly. You can either execute these as in-game commands -while `@ic` (controlling your character object). - -1. `@py from web.character.models import RosterEntry;RosterEntry.objects.create(player=self.player, -character=self)` -2. `@py from world.dominion.models import PlayerOrNpc, AssetOwner;dompc = -PlayerOrNpc.objects.create(player = self.player);AssetOwner.objects.create(player=dompc)` - -Those steps will give you a 'RosterEntry', 'PlayerOrNpc', and 'AssetOwner' objects. RosterEntry -explicitly connects a character and account object together, even while offline, and contains -additional information about a character's current presence in game (such as which 'roster' they're -in, if you choose to use an active roster of characters). PlayerOrNpc are more character extensions, -as well as support for npcs with no in-game presence and just represented by a name which can be -offscreen members of a character's family. It also allows for membership in Organizations. -AssetOwner holds information about a character or organization's money and resources. - -## Alternate guide by Pax for installing on Windows - -If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions -identical to the ones above), it's possible to get Evennia running under Anaconda for Windows. The -process is a little bit trickier. - - Make sure you have: - * Git for Windows https://git-scm.com/download/win - * Anaconda for Windows https://www.anaconda.com/distribution/ - * VC++ Compiler for Python 2.7 http://aka.ms/vcpython27 - -conda update conda -conda create -n arx python=2.7 -source activate arx - - Set up a convenient repository place for things. - -cd ~ -mkdir Source -cd Source -mkdir Arx -cd Arx - - Replace the SSH git clone links below with your own github forks. - If you don't plan to change Evennia at all, you can use the - evennia/evennia.git repo instead of a forked one. - -git clone git@github.com:/evennia.git -git clone git@github.com:/arxcode.git - - Evennia is a package itself, so we want to install it and all of its - prerequisites, after switching to the appropriately-tagged branch for - Arxcode. - -cd evennia -git checkout tags/v0.7 -b arx-master -pip install -e . - - Arx has some dependencies of its own, so now we'll go install them - As it is not a package, we'll use the normal requirements file. - -cd ../arxcode -pip install -r requirements.txt - - The git repo doesn't include the empty log directory and Evennia is unhappy if you - don't have it, so while still in the arxcode directory... - -mkdir server/logs - - Now hit https://github.com/evennia/evennia/wiki/Arxcode-installing-help and - change the setup stuff as in the 'Clean up settings' section. - - Then we will create our default database... - -../evennia/bin/windows/evennia.bat migrate - - ...and do the first run. You need winpty because Windows does not have a TTY/PTY - by default, and so the Python console input commands (used for prompts on first - run) will fail and you will end up in an unhappy place. Future runs, you should - not need winpty. - -winpty ../evennia/bin/windows/evennia.bat start - - Once this is done, you should have your Evennia server running Arxcode up - on localhost at port 4000, and the webserver at http://localhost:4001/ - - And you are done! Huzzah! \ No newline at end of file diff --git a/docs/0.9.5/_sources/Async-Process.md.txt b/docs/0.9.5/_sources/Async-Process.md.txt deleted file mode 100644 index e44e9330a2..0000000000 --- a/docs/0.9.5/_sources/Async-Process.md.txt +++ /dev/null @@ -1,233 +0,0 @@ -# Async Process - - -*This is considered an advanced topic.* - -## Synchronous versus Asynchronous - -Most program code operates *synchronously*. This means that each statement in your code gets -processed and finishes before the next can begin. This makes for easy-to-understand code. It is also -a *requirement* in many cases - a subsequent piece of code often depend on something calculated or -defined in a previous statement. - -Consider this piece of code in a traditional Python program: - -```python - print("before call ...") - long_running_function() - print("after call ...") - -``` - -When run, this will print `"before call ..."`, after which the `long_running_function` gets to work -for however long time. Only once that is done, the system prints `"after call ..."`. Easy and -logical to follow. Most of Evennia work in this way and often it's important that commands get -executed in the same strict order they were coded. - -Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it -swiftly switches between dealing with player input so quickly that each player feels like they do -things at the same time. This is a clever illusion however: If one user, say, runs a command -containing that `long_running_function`, *all* other players are effectively forced to wait until it -finishes. - -Now, it should be said that on a modern computer system this is rarely an issue. Very few commands -run so long that other users notice it. And as mentioned, most of the time you *want* to enforce -all commands to occur in strict sequence. - -When delays do become noticeable and you don't care in which order the command actually completes, -you can run it *asynchronously*. This makes use of the `run_async()` function in -`src/utils/utils.py`: - -```python - run_async(function, *args, **kwargs) -``` - -Where `function` will be called asynchronously with `*args` and `**kwargs`. Example: - -```python - from evennia import utils - print("before call ...") - utils.run_async(long_running_function) - print("after call ...") -``` - -Now, when running this you will find that the program will not wait around for -`long_running_function` to finish. In fact you will see `"before call ..."` and `"after call ..."` -printed out right away. The long-running function will run in the background and you (and other -users) can go on as normal. - -## Customizing asynchronous operation - -A complication with using asynchronous calls is what to do with the result from that call. What if -`long_running_function` returns a value that you need? It makes no real sense to put any lines of -code after the call to try to deal with the result from `long_running_function` above - as we saw -the `"after call ..."` got printed long before `long_running_function` was finished, making that -line quite pointless for processing any data from the function. Instead one has to use *callbacks*. - -`utils.run_async` takes reserved kwargs that won't be passed into the long-running function: - -- `at_return(r)` (the *callback*) is called when the asynchronous function (`long_running_function` - above) finishes successfully. The argument `r` will then be the return value of that function (or - `None`). - - ```python - def at_return(r): - print(r) - ``` - -- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the -`at_return` callback. -- `at_err(e)` (the *errback*) is called if the asynchronous function fails and raises an exception. - This exception is passed to the errback wrapped in a *Failure* object `e`. If you do not supply an - errback of your own, Evennia will automatically add one that silently writes errors to the evennia - log. An example of an errback is found below: - -```python - def at_err(e): - print("There was an error:", str(e)) -``` - -- `at_err_kwargs` - an optional dictionary that will be fed as keyword arguments to the `at_err` - errback. - -An example of making an asynchronous call from inside a [Command](./Commands.md) definition: - -```python - from evennia import utils, Command - - class CmdAsync(Command): - - key = "asynccommand" - - def func(self): - - def long_running_function(): - #[... lots of time-consuming code ...] - return final_value - - def at_return_function(r): - self.caller.msg("The final value is %s" % r) - - def at_err_function(e): - self.caller.msg("There was an error: %s" % e) - - # do the async call, setting all callbacks - utils.run_async(long_running_function, at_return=at_return_function, -at_err=at_err_function) -``` - -That's it - from here on we can forget about `long_running_function` and go on with what else need -to be done. *Whenever* it finishes, the `at_return_function` function will be called and the final -value will -pop up for us to see. If not we will see an error message. - -## delay - -The `delay` function is a much simpler sibling to `run_async`. It is in fact just a way to delay the -execution of a command until a future time. This is equivalent to something like `time.sleep()` -except delay is asynchronous while `sleep` would lock the entire server for the duration of the -sleep. - -```python - from evennia.utils import delay - - # [...] - # e.g. inside a Command, where `self.caller` is available - def callback(obj): - obj.msg("Returning!") - delay(10, callback, self.caller) -``` - -This will delay the execution of the callback for 10 seconds. This function is explored much more in -the [Command Duration Tutorial](./Command-Duration.md). - -You can also try the following snippet just see how it works: - - @py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self) - -Wait 10 seconds and 'Test!' should be echoed back to you. - - -## The @interactive decorator - -As of Evennia 0.9, the `@interactive` [decorator](https://realpython.com/primer-on-python-decorators/) -is available. This makes any function or method possible to 'pause' and/or await player input -in an interactive way. - -```python - from evennia.utils import interactive - - @interactive - def myfunc(caller): - - while True: - caller.msg("Getting ready to wait ...") - yield(5) - caller.msg("Now 5 seconds have passed.") - - response = yield("Do you want to wait another 5 secs?") - - if response.lower() not in ("yes", "y"): - break -``` - -The `@interactive` decorator gives the function the ability to pause. The use -of `yield(seconds)` will do just that - it will asynchronously pause for the -number of seconds given before continuing. This is technically equivalent to -using `call_async` with a callback that continues after 5 secs. But the code -with `@interactive` is a little easier to follow. - -Within the `@interactive` function, the `response = yield("question")` question -allows you to ask the user for input. You can then process the input, just like -you would if you used the Python `input` function. There is one caveat to this -functionality though - _it will only work if the function/method has an -argument named exactly `caller`_. This is because internally Evennia will look -for the `caller` argument and treat that as the source of input. - -All of this makes the `@interactive` decorator very useful. But it comes with a -few caveats. Notably, decorating a function/method with `@interactive` turns it -into a Python [generator](https://wiki.python.org/moin/Generators). The most -common issue is that you cannot use `return ` from a generator (just an -empty `return` works). To return a value from a function/method you have decorated -with `@interactive`, you must instead use a special Twisted function -`twisted.internet.defer.returnValue`. Evennia also makes this function -conveniently available from `evennia.utils`: - -```python - from evennia.utils import interactive, returnValue - - @interactive - def myfunc(): - - # ... - result = 10 - - # this must be used instead of `return result` - returnValue(result) - -``` - - - -## Assorted notes - -Overall, be careful with choosing when to use asynchronous calls. It is mainly useful for large -administration operations that have no direct influence on the game world (imports and backup -operations come to mind). Since there is no telling exactly when an asynchronous call actually ends, -using them for in-game commands is to potentially invite confusion and inconsistencies (and very -hard-to-reproduce bugs). - -The very first synchronous example above is not *really* correct in the case of Twisted, which is -inherently an asynchronous server. Notably you might find that you will *not* see the first `before -call ...` text being printed out right away. Instead all texts could end up being delayed until -after the long-running process finishes. So all commands will retain their relative order as -expected, but they may appear with delays or in groups. - -## Further reading - -Technically, `run_async` is just a very thin and simplified wrapper around a -[Twisted Deferred](http://twistedmatrix.com/documents/9.0.0/core/howto/defer.html) object; the -wrapper sets -up a default errback also if none is supplied. If you know what you are doing there is nothing -stopping you from bypassing the utility function, building a more sophisticated callback chain after -your own liking. diff --git a/docs/0.9.5/_sources/Attributes.md.txt b/docs/0.9.5/_sources/Attributes.md.txt deleted file mode 100644 index b04d18f654..0000000000 --- a/docs/0.9.5/_sources/Attributes.md.txt +++ /dev/null @@ -1,393 +0,0 @@ -# Attributes - - -When performing actions in Evennia it is often important that you store data for later. If you write -a menu system, you have to keep track of the current location in the menu tree so that the player -can give correct subsequent commands. If you are writing a combat system, you might have a -combattant's next roll get easier dependent on if their opponent failed. Your characters will -probably need to store roleplaying-attributes like strength and agility. And so on. - -[Typeclassed](./Typeclasses.md) game entities ([Accounts](./Accounts.md), [Objects](./Objects.md), -[Scripts](./Scripts.md) and [Channels](./Communications.md)) always have *Attributes* associated with them. -Attributes are used to store any type of data 'on' such entities. This is different from storing -data in properties already defined on entities (such as `key` or `location`) - these have very -specific names and require very specific types of data (for example you couldn't assign a python -*list* to the `key` property no matter how hard you tried). `Attributes` come into play when you -want to assign arbitrary data to arbitrary names. - -**Attributes are _not_ secure by default and any player may be able to change them unless you -[prevent this behavior](./Attributes.md#locking-and-checking-attributes).** - -## The .db and .ndb shortcuts - -To save persistent data on a Typeclassed object you normally use the `db` (DataBase) operator. Let's -try to save some data to a *Rose* (an [Object](./Objects.md)): - -```python - # saving - rose.db.has_thorns = True - # getting it back - is_ouch = rose.db.has_thorns - -``` - -This looks like any normal Python assignment, but that `db` makes sure that an *Attribute* is -created behind the scenes and is stored in the database. Your rose will continue to have thorns -throughout the life of the server now, until you deliberately remove them. - -To be sure to save **non-persistently**, i.e. to make sure NOT to create a database entry, you use -`ndb` (NonDataBase). It works in the same way: - -```python - # saving - rose.ndb.has_thorns = True - # getting it back - is_ouch = rose.ndb.has_thorns -``` - -Technically, `ndb` has nothing to do with `Attributes`, despite how similar they look. No -`Attribute` object is created behind the scenes when using `ndb`. In fact the database is not -invoked at all since we are not interested in persistence. There is however an important reason to -use `ndb` to store data rather than to just store variables direct on entities - `ndb`-stored data -is tracked by the server and will not be purged in various cache-cleanup operations Evennia may do -while it runs. Data stored on `ndb` (as well as `db`) will also be easily listed by example the -`@examine` command. - -You can also `del` properties on `db` and `ndb` as normal. This will for example delete an -`Attribute`: - -```python - del rose.db.has_thorns -``` - -Both `db` and `ndb` defaults to offering an `all` property on themselves. This returns all -associated attributes or non-persistent properties. - -```python - list_of_all_rose_attributes = rose.db.all - list_of_all_rose_ndb_attrs = rose.ndb.all -``` - -If you use `all` as the name of an attribute, this will be used instead. Later deleting your custom -`all` will return the default behaviour. - -## The AttributeHandler - -The `.db` and `.ndb` properties are very convenient but if you don't know the name of the Attribute -beforehand they cannot be used. Behind the scenes `.db` actually accesses the `AttributeHandler` -which sits on typeclassed entities as the `.attributes` property. `.ndb` does the same for the -`.nattributes` property. - -The handlers have normal access methods that allow you to manage and retrieve `Attributes` and -`NAttributes`: - -- `has('attrname')` - this checks if the object has an Attribute with this key. This is equivalent - to doing `obj.db.attrname`. -- `get(...)` - this retrieves the given Attribute. Normally the `value` property of the Attribute is - returned, but the method takes keywords for returning the Attribute object itself. By supplying an - `accessing_object` to the call one can also make sure to check permissions before modifying - anything. -- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be - supplied here to restrict future access and also the call itself may be checked against locks. -- `remove(...)` - Remove the given Attribute. This can optionally be made to check for permission - before performing the deletion. - `clear(...)` - removes all Attributes from object. -- `all(...)` - returns all Attributes (of the given category) attached to this object. - -See [this section](./Attributes.md#locking-and-checking-attributes) for more about locking down Attribute -access and editing. The `Nattribute` offers no concept of access control. - -Some examples: - -```python - import evennia - obj = evennia.search_object("MyObject") - - obj.attributes.add("test", "testvalue") - print(obj.db.test) # prints "testvalue" - print(obj.attributes.get("test")) # " - print(obj.attributes.all()) # prints [] - obj.attributes.remove("test") -``` - - -## Properties of Attributes - -An Attribute object is stored in the database. It has the following properties: - -- `key` - the name of the Attribute. When doing e.g. `obj.db.attrname = value`, this property is set - to `attrname`. -- `value` - this is the value of the Attribute. This value can be anything which can be pickled - - objects, lists, numbers or what have you (see - [this section](./Attributes.md#what-types-of-data-can-i-save-in-an-attribute) for more info). In the -example - `obj.db.attrname = value`, the `value` is stored here. -- `category` - this is an optional property that is set to None for most Attributes. Setting this - allows to use Attributes for different functionality. This is usually not needed unless you want - to use Attributes for very different functionality ([Nicks](./Nicks.md) is an example of using -Attributes - in this way). To modify this property you need to use the [Attribute -Handler](#the-attributehandler). -- `strvalue` - this is a separate value field that only accepts strings. This severely limits the - data possible to store, but allows for easier database lookups. This property is usually not used - except when re-using Attributes for some other purpose ([Nicks](./Nicks.md) use it). It is only - accessible via the [Attribute Handler](./Attributes.md#the-attributehandler). - -There are also two special properties: - -- `attrtype` - this is used internally by Evennia to separate [Nicks](./Nicks.md), from Attributes (Nicks - use Attributes behind the scenes). -- `model` - this is a *natural-key* describing the model this Attribute is attached to. This is on - the form *appname.modelclass*, like `objects.objectdb`. It is used by the Attribute and - NickHandler to quickly sort matches in the database. Neither this nor `attrtype` should normally - need to be modified. - -Non-database attributes have no equivalence to `category` nor `strvalue`, `attrtype` or `model`. - -## Persistent vs non-persistent - -So *persistent* data means that your data will survive a server reboot, whereas with -*non-persistent* data it will not ... - -... So why would you ever want to use non-persistent data? The answer is, you don't have to. Most of -the time you really want to save as much as you possibly can. Non-persistent data is potentially -useful in a few situations though. - -- You are worried about database performance. Since Evennia caches Attributes very aggressively, - this is not an issue unless you are reading *and* writing to your Attribute very often (like many - times per second). Reading from an already cached Attribute is as fast as reading any Python - property. But even then this is not likely something to worry about: Apart from Evennia's own - caching, modern database systems themselves also cache data very efficiently for speed. Our -default - database even runs completely in RAM if possible, alleviating much of the need to write to disk - during heavy loads. -- A more valid reason for using non-persistent data is if you *want* to lose your state when logging - off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you - are implementing some caching of your own. Or maybe you are testing a buggy [Script](./Scripts.md) that - does potentially harmful stuff to your character object. With non-persistent storage you can be -sure - that whatever is messed up, it's nothing a server reboot can't clear up. -- NAttributes have no restrictions at all on what they can store (see next section), since they - don't need to worry about being saved to the database - they work very well for temporary storage. -- You want to implement a fully or partly *non-persistent world*. Who are we to argue with your - grand vision! - -## What types of data can I save in an Attribute? - -> None of the following affects NAttributes, which does not invoke the database at all. There are no -> restrictions to what can be stored in a NAttribute. - -The database doesn't know anything about Python objects, so Evennia must *serialize* Attribute -values into a string representation in order to store it to the database. This is done using the -`pickle` module of Python (the only exception is if you use the `strattr` keyword of the -AttributeHandler to save to the `strvalue` field of the Attribute. In that case you can only save -*strings* which will not be pickled). - -It's important to note that when you access the data in an Attribute you are *always* de-serializing -it from the database representation every time. This is because we allow for storing -database-entities in Attributes too. If we cached it as its Python form, we might end up with -situations where the database entity was deleted since we last accessed the Attribute. -De-serializing data with a database-entity in it means querying the database for that object and -making sure it still exists (otherwise it will be set to `None`). Performance-wise this is usually -not a big deal. But if you are accessing the Attribute as part of some big loop or doing a large -amount of reads/writes you should first extract it to a temporary variable, operate on *that* and -then save the result back to the Attribute. If you are storing a more complex structure like a -`dict` or a `list` you should make sure to "disconnect" it from the database before looping over it, -as mentioned in the [Retrieving Mutable Objects](./Attributes.md#retrieving-mutable-objects) section -below. - -### Storing single objects - -With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class -instances without the `__iter__` method. - -* You can generally store any non-iterable Python entity that can be - [pickled](http://docs.python.org/library/pickle.html). -* Single database objects/typeclasses can be stored as any other in the Attribute. These can - normally *not* be pickled, but Evennia will behind the scenes convert them to an internal - representation using their classname, database-id and creation-date with a microsecond precision, - guaranteeing you get the same object back when you access the Attribute later. -* If you *hide* a database object inside a non-iterable custom class (like stored as a variable - inside it), Evennia will not know it's there and won't convert it safely. Storing classes with - such hidden database objects is *not* supported and will lead to errors! - -```python -# Examples of valid single-value attribute data: -obj.db.test1 = 23 -obj.db.test1 = False -# a database object (will be stored as an internal representation) -obj.db.test2 = myobj - -# example of an invalid, "hidden" dbobject -class Invalid(object): - def __init__(self, dbobj): - # no way for Evennia to know this is a dbobj - self.dbobj = dbobj -invalid = Invalid(myobj) -obj.db.invalid = invalid # will cause error! -``` - -### Storing multiple objects - -This means storing objects in a collection of some kind and are examples of *iterables*, pickle-able -entities you can loop over in a for-loop. Attribute-saving supports the following iterables: - -* [Tuples](https://docs.python.org/2/library/functions.html#tuple), like `(1,2,"test", )`. -* [Lists](https://docs.python.org/2/tutorial/datastructures.html#more-on-lists), like `[1,2,"test", -]`. -* [Dicts](https://docs.python.org/2/tutorial/datastructures.html#dictionaries), like `{1:2, -"test":]`. -* [Sets](https://docs.python.org/2/tutorial/datastructures.html#sets), like `{1,2,"test",}`. -* [collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict), like `OrderedDict((1,2), ("test", ))`. -* [collections.Deque](https://docs.python.org/2/library/collections.html#collections.deque), like -`deque((1,2,"test",))`. -* *Nestings* of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each -containing dicts, etc. -* All other iterables (i.e. entities with the `__iter__` method) will be converted to a *list*. - Since you can use any combination of the above iterables, this is generally not much of a - limitation. - -Any entity listed in the [Single object](./Attributes.md#storing-single-objects) section above can be -stored in the iterable. - -> As mentioned in the previous section, database entities (aka typeclasses) are not possible to -> pickle. So when storing an iterable, Evennia must recursively traverse the iterable *and all its -> nested sub-iterables* in order to find eventual database objects to convert. This is a very fast -> process but for efficiency you may want to avoid too deeply nested structures if you can. - -```python -# examples of valid iterables to store -obj.db.test3 = [obj1, 45, obj2, 67] -# a dictionary -obj.db.test4 = {'str':34, 'dex':56, 'agi':22, 'int':77} -# a mixed dictionary/list -obj.db.test5 = {'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5]} -# a tuple with a list in it -obj.db.test6 = (1,3,4,8, ["test", "test2"], 9) -# a set -obj.db.test7 = set([1,2,3,4,5]) -# in-situ manipulation -obj.db.test8 = [1,2,{"test":1}] -obj.db.test8[0] = 4 -obj.db.test8[2]["test"] = 5 -# test8 is now [4,2,{"test":5}] -``` - -### Retrieving Mutable objects - -A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can -be modified in-place after they were created, which is everything except tuples) are handled by -custom objects called `_SaverList`, `_SaverDict` etc. These `_Saver...` classes behave just like the -normal variant except that they are aware of the database and saves to it whenever new data gets -assigned to them. This is what allows you to do things like `self.db.mylist[7] = val` and be sure -that the new version of list is saved. Without this you would have to load the list into a temporary -variable, change it and then re-assign it to the Attribute in order for it to save. - -There is however an important thing to remember. If you retrieve your mutable iterable into another -variable, e.g. `mylist2 = obj.db.mylist`, your new variable (`mylist2`) will *still* be a -`_SaverList`. This means it will continue to save itself to the database whenever it is updated! - - -```python - obj.db.mylist = [1,2,3,4] - mylist = obj.db.mylist - mylist[3] = 5 # this will also update database - print(mylist) # this is now [1,2,3,5] - print(obj.db.mylist) # this is also [1,2,3,5] -``` - -To "disconnect" your extracted mutable variable from the database you simply need to convert the -`_Saver...` iterable to a normal Python structure. So to convert a `_SaverList`, you use the -`list()` function, for a `_SaverDict` you use `dict()` and so on. - -```python - obj.db.mylist = [1,2,3,4] - mylist = list(obj.db.mylist) # convert to normal list - mylist[3] = 5 - print(mylist) # this is now [1,2,3,5] - print(obj.db.mylist) # this is still [1,2,3,4] -``` - -A further problem comes with *nested mutables*, like a dict containing lists of dicts or something -like that. Each of these nested mutables would be `_Saver*` structures connected to the database and -disconnecting the outermost one of them would not disconnect those nested within. To make really -sure you disonnect a nested structure entirely from the database, Evennia provides a special -function `evennia.utils.dbserialize.deserialize`: - -``` -from evennia.utils.dbserialize import deserialize - -decoupled_mutables = deserialize(nested_mutables) - -``` - -The result of this operation will be a structure only consisting of normal Python mutables (`list` -instead of `_SaverList` and so on). - - -Remember, this is only valid for *mutable* iterables. -[Immutable](http://en.wikipedia.org/wiki/Immutable) objects (strings, numbers, tuples etc) are -already disconnected from the database from the onset. - -```python - obj.db.mytup = (1,2,[3,4]) - obj.db.mytup[0] = 5 # this fails since tuples are immutable - - # this works but will NOT update database since outermost is a tuple - obj.db.mytup[2][1] = 5 - print(obj.db.mytup[2][1]) # this still returns 4, not 5 - - mytup1 = obj.db.mytup # mytup1 is already disconnected from database since outermost - # iterable is a tuple, so we can edit the internal list as we want - # without affecting the database. -``` - -> Attributes will fetch data fresh from the database whenever you read them, so -> if you are performing big operations on a mutable Attribute property (such as looping over a list -> or dict) you should make sure to "disconnect" the Attribute's value first and operate on this -> rather than on the Attribute. You can gain dramatic speed improvements to big loops this -> way. - - -## Locking and checking Attributes - -Attributes are normally not locked down by default, but you can easily change that for individual -Attributes (like those that may be game-sensitive in games with user-level building). - -First you need to set a *lock string* on your Attribute. Lock strings are specified [Locks](./Locks.md). -The relevant lock types are - -- `attrread` - limits who may read the value of the Attribute -- `attredit` - limits who may set/change this Attribute - -You cannot use the `db` handler to modify Attribute object (such as setting a lock on them) - The -`db` handler will return the Attribute's *value*, not the Attribute object itself. Instead you use -the AttributeHandler and set it to return the object instead of the value: - -```python - lockstring = "attread:all();attredit:perm(Admins)" - obj.attributes.get("myattr", return_obj=True).locks.add(lockstring) -``` - -Note the `return_obj` keyword which makes sure to return the `Attribute` object so its LockHandler -could be accessed. - -A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes. -You have to add a check to your commands/code wherever it fits (such as before setting an -Attribute). - -```python - # in some command code where we want to limit - # setting of a given attribute name on an object - attr = obj.attributes.get(attrname, - return_obj=True, - accessing_obj=caller, - default=None, - default_access=False) - if not attr: - caller.msg("You cannot edit that Attribute!") - return - # edit the Attribute here -``` - -The same keywords are available to use with `obj.attributes.set()` and `obj.attributes.remove()`, -those will check for the `attredit` lock type. diff --git a/docs/0.9.5/_sources/Banning.md.txt b/docs/0.9.5/_sources/Banning.md.txt deleted file mode 100644 index 09baae2b95..0000000000 --- a/docs/0.9.5/_sources/Banning.md.txt +++ /dev/null @@ -1,148 +0,0 @@ -# Banning - - -Whether due to abuse, blatant breaking of your rules, or some other reason, you will eventually find -no other recourse but to kick out a particularly troublesome player. The default command set has -admin tools to handle this, primarily `ban`, `unban`, and `boot`. - -## Creating a ban - -Say we have a troublesome player "YouSuck" - this is a person that refuses common courtesy - an -abusive -and spammy account that is clearly created by some bored internet hooligan only to cause grief. You -have tried to be nice. Now you just want this troll gone. - -### Name ban - -The easiest recourse is to block the account YouSuck from ever connecting again. - - ban YouSuck - -This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and -next time they try to log in with this name the server will not let them! - -You can also give a reason so you remember later why this was a good thing (the banned account will -never see this) - - ban YouSuck:This is just a troll. - -If you are sure this is just a spam account, you might even consider deleting the player account -outright: - - account/delete YouSuck - -Generally, banning the name is the easier and safer way to stop the use of an account -- if you -change your mind you can always remove the block later whereas a deletion is permanent. - -### IP ban - -Just because you block YouSuck's name might not mean the trolling human behind that account gives -up. They can just create a new account YouSuckMore and be back at it. One way to make things harder -for them is to tell the server to not allow connections from their particular IP address. - -First, when the offending account is online, check which IP address they use. This you can do with -the `who` command, which will show you something like this: - - Account Name On for Idle Room Cmds Host - YouSuckMore 01:12 2m 22 212 237.333.0.223 - -The "Host" bit is the IP address from which the account is connecting. Use this to define the ban -instead of the name: - - ban 237.333.0.223 - -This will stop YouSuckMore connecting from their computer. Note however that IP address might change -easily - either due to how the player's Internet Service Provider operates or by the user simply -changing computers. You can make a more general ban by putting asterisks `*` as wildcards for the -groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from -237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea -to put down a ban like this to include any number in that subnet: - - ban 237.333.0.* - -You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly -locked regardless of where they connect from. - -Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be -blocking out innocent players who just happen to connect from the same subnet as the offender. - -## Booting - -YouSuck is not really noticing all this banning yet though - and won't until having logged out and -trying to log back in again. Let's help the troll along. - - boot YouSuck - -Good riddance. You can give a reason for booting too (to be echoed to the player before getting -kicked out). - - boot YouSuck:Go troll somewhere else. - -### Lifting a ban - -Use the `unban` (or `ban`) command without any arguments and you will see a list of all currently -active bans: - - Active bans - id name/ip date reason - 1 yousuck Fri Jan 3 23:00:22 2020 This is just a Troll. - 2 237.333.0.* Fri Jan 3 23:01:03 2020 YouSuck's IP. - -Use the `id` from this list to find out which ban to lift. - - unban 2 - - Cleared ban 2: 237.333.0.* - -## Summary of abuse-handling tools - -Below are other useful commands for dealing with annoying players. - -- **who** -- (as admin) Find the IP of a account. Note that one account can be connected to from -multiple IPs depending on what you allow in your settings. -- **examine/account thomas** -- Get all details about an account. You can also use `*thomas` to get -the account. If not given, you will get the *Object* thomas if it exists in the same location, which -is not what you want in this case. -- **boot thomas** -- Boot all sessions of the given account name. -- **boot 23** -- Boot one specific client session/IP by its unique id. -- **ban** -- List all bans (listed with ids) -- **ban thomas** -- Ban the user with the given account name -- **ban/ip `134.233.2.111`** -- Ban by IP -- **ban/ip `134.233.2.*`** -- Widen IP ban -- **ban/ip `134.233.*.*`** -- Even wider IP ban -- **unban 34** -- Remove ban with id #34 - -- **cboot mychannel = thomas** -- Boot a subscriber from a channel you control -- **clock mychannel = control:perm(Admin);listen:all();send:all()** -- Fine control of access to -your channel using [lock definitions](./Locks.md). - -Locking a specific command (like `page`) is accomplished like so: -1. Examine the source of the command. [The default `page` command class]( -https://github.com/evennia/evennia/blob/master/evennia/commands/default/comms.py#L686) has the lock -string **"cmd:not pperm(page_banned)"**. This means that unless the player has the 'permission' -"page_banned" they can use this command. You can assign any lock string to allow finer customization -in your commands. You might look for the value of an [Attribute](./Attributes.md) or [Tag](./Tags.md), your -current location etc. -2. **perm/account thomas = page_banned** -- Give the account the 'permission' which causes (in this -case) the lock to fail. - -- **perm/del/account thomas = page_banned** -- Remove the given permission - -- **tel thomas = jail** -- Teleport a player to a specified location or #dbref -- **type thomas = FlowerPot** -- Turn an annoying player into a flower pot (assuming you have a -`FlowerPot` typeclass ready) -- **userpassword thomas = fooBarFoo** -- Change a user's password -- **account/delete thomas** -- Delete a player account (not recommended, use **ban** instead) - -- **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are -cached -- **time** -- Gives server uptime, runtime, etc -- **reload** -- Reloads the server without disconnecting anyone -- **reset** -- Restarts the server, kicking all connections -- **shutdown** -- Stops the server cold without it auto-starting again -- **py** -- Executes raw Python code, allows for direct inspection of the database and account -objects on the fly. For advanced users. - - -**Useful Tip:** `evennia changepassword ` entered into the command prompt will reset the -password of any account, including the superuser or admin accounts. This is a feature of Django. diff --git a/docs/0.9.5/_sources/Batch-Code-Processor.md.txt b/docs/0.9.5/_sources/Batch-Code-Processor.md.txt deleted file mode 100644 index df8eaff1ff..0000000000 --- a/docs/0.9.5/_sources/Batch-Code-Processor.md.txt +++ /dev/null @@ -1,229 +0,0 @@ -# Batch Code Processor - - -For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This -page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command- -Processor). - -## Basic Usage - -The batch-code processor is a superuser-only function, invoked by - - > @batchcode path.to.batchcodefile - -Where `path.to.batchcodefile` is the path to a *batch-code file*. Such a file should have a name -ending in "`.py`" (but you shouldn't include that in the path). The path is given like a python path -relative to a folder you define to hold your batch files, set by `BATCH_IMPORT_PATH` in your -settings. Default folder is (assuming your game is called "mygame") `mygame/world/`. So if you want -to run the example batch file in `mygame/world/batch_code.py`, you could simply use - - > @batchcode batch_code - -This will try to run through the entire batch file in one go. For more gradual, *interactive* -control you can use the `/interactive` switch. The switch `/debug` will put the processor in -*debug* mode. Read below for more info. - -## The batch file - -A batch-code file is a normal Python file. The difference is that since the batch processor loads -and executes the file rather than importing it, you can reliably update the file, then call it -again, over and over and see your changes without needing to `@reload` the server. This makes for -easy testing. In the batch-code file you have also access to the following global variables: - -- `caller` - This is a reference to the object running the batchprocessor. -- `DEBUG` - This is a boolean that lets you determine if this file is currently being run in debug- -mode or not. See below how this can be useful. - -Running a plain Python file through the processor will just execute the file from beginning to end. -If you want to get more control over the execution you can use the processor's *interactive* mode. -This runs certain code blocks on their own, rerunning only that part until you are happy with it. In -order to do this you need to add special markers to your file to divide it up into smaller chunks. -These take the form of comments, so the file remains valid Python. - -Here are the rules of syntax of the batch-code `*.py` file. - -- `#CODE` as the first on a line marks the start of a *code* block. It will last until the beginning -of another marker or the end of the file. Code blocks contain functional python code. Each `#CODE` -block will be run in complete isolation from other parts of the file, so make sure it's self- -contained. -- `#HEADER` as the first on a line marks the start of a *header* block. It lasts until the next -marker or the end of the file. This is intended to hold imports and variables you will need for all -other blocks .All python code defined in a header block will always be inserted at the top of every -`#CODE` blocks in the file. You may have more than one `#HEADER` block, but that is equivalent to -having one big one. Note that you can't exchange data between code blocks, so editing a header- -variable in one code block won't affect that variable in any other code block! -- `#INSERT path.to.file` will insert another batchcode (Python) file at that position. -- A `#` that is not starting a `#HEADER`, `#CODE` or `#INSERT` instruction is considered a comment. -- Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as -a separate python module. - -Below is a version of the example file found in `evennia/contrib/tutorial_examples/`. - -```python - # - # This is an example batch-code build file for Evennia. - # - - #HEADER - - # This will be included in all other #CODE blocks - - from evennia import create_object, search_object - from evennia.contrib.tutorial_examples import red_button - from typeclasses.objects import Object - - limbo = search_object('Limbo')[0] - - - #CODE - - red_button = create_object(red_button.RedButton, key="Red button", - location=limbo, aliases=["button"]) - - # caller points to the one running the script - caller.msg("A red button was created.") - - # importing more code from another batch-code file - #INSERT batch_code_insert - - #CODE - - table = create_object(Object, key="Blue Table", location=limbo) - chair = create_object(Object, key="Blue Chair", location=limbo) - - string = "A %s and %s were created." - if DEBUG: - table.delete() - chair.delete() - string += " Since debug was active, " \ - "they were deleted again." - caller.msg(string % (table, chair)) -``` - -This uses Evennia's Python API to create three objects in sequence. - -## Debug mode - -Try to run the example script with - - > @batchcode/debug tutorial_examples.example_batch_code - -The batch script will run to the end and tell you it completed. You will also get messages that the -button and the two pieces of furniture were created. Look around and you should see the button -there. But you won't see any chair nor a table! This is because we ran this with the `/debug` -switch, which is directly visible as `DEBUG==True` inside the script. In the above example we -handled this state by deleting the chair and table again. - -The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for -bugs in your code or try to see if things behave as they should. Running the script over and over -would then create an ever-growing stack of chairs and tables, all with the same name. You would have -to go back and painstakingly delete them later. - -## Interactive mode - -Interactive mode works very similar to the [batch-command processor counterpart](Batch-Command- -Processor). It allows you more step-wise control over how the batch file is executed. This is useful -for debugging or for picking and choosing only particular blocks to run. Use `@batchcode` with the -`/interactive` flag to enter interactive mode. - - > @batchcode/interactive tutorial_examples.example_batch_code - -You should see the following: - - 01/02: red_button = create_object(red_button.RedButton, [...] (hh for help) - -This shows that you are on the first `#CODE` block, the first of only two commands in this batch -file. Observe that the block has *not* actually been executed at this point! - -To take a look at the full code snippet you are about to run, use `ll` (a batch-processor version of -`look`). - -```python - from evennia.utils import create, search - from evennia.contrib.tutorial_examples import red_button - from typeclasses.objects import Object - - limbo = search.objects(caller, 'Limbo', global_search=True)[0] - - red_button = create.create_object(red_button.RedButton, key="Red button", - location=limbo, aliases=["button"]) - - # caller points to the one running the script - caller.msg("A red button was created.") -``` - -Compare with the example code given earlier. Notice how the content of `#HEADER` has been pasted at -the top of the `#CODE` block. Use `pp` to actually execute this block (this will create the button -and give you a message). Use `nn` (next) to go to the next command. Use `hh` for a list of commands. - -If there are tracebacks, fix them in the batch file, then use `rr` to reload the file. You will -still be at the same code block and can rerun it easily with `pp` as needed. This makes for a simple -debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large -batch file this can be very useful (don't forget the `/debug` mode either). - -Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward -(without processing any blocks in between). All normal commands of Evennia should work too while -working in interactive mode. - -## Limitations and Caveats - -The batch-code processor is by far the most flexible way to build a world in Evennia. There are -however some caveats you need to keep in mind. - -### Safety -Or rather the lack of it. There is a reason only *superusers* are allowed to run the batch-code -processor by default. The code-processor runs **without any Evennia security checks** and allows -full access to Python. If an untrusted party could run the code-processor they could execute -arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to -allow other users to access the batch-code processor you should make sure to run Evennia as a -separate and very limited-access user on your machine (i.e. in a 'jail'). By comparison, the batch- -command processor is much safer since the user running it is still 'inside' the game and can't -really do anything outside what the game commands allow them to. - -### No communication between code blocks -Global variables won't work in code batch files, each block is executed as stand-alone environments. -`#HEADER` blocks are literally pasted on top of each `#CODE` block so updating some header-variable -in your block will not make that change available in another block. Whereas a python execution -limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode -- this would be a classical example of "spaghetti code". - -The main practical issue with this is when building e.g. a room in one code block and later want to -connect that room with a room you built in the current block. There are two ways to do this: - -- Perform a database search for the name of the room you created (since you cannot know in advance -which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of "A -dark forest" rooms). There is an easy way to handle this though - use [Tags](./Tags.md) or *Aliases*. You -can assign any number of tags and/or aliases to any object. Make sure that one of those tags or -aliases is unique to the room (like "room56") and you will henceforth be able to always uniquely -search and find it later. -- Use the `caller` global property as an inter-block storage. For example, you could have a -dictionary of room references in an `ndb`: - ```python - #HEADER - if caller.ndb.all_rooms is None: - caller.ndb.all_rooms = {} - - #CODE - # create and store the castle - castle = create_object("rooms.Room", key="Castle") - caller.ndb.all_rooms["castle"] = castle - - #CODE - # in another node we want to access the castle - castle = caller.ndb.all_rooms.get("castle") - ``` -Note how we check in `#HEADER` if `caller.ndb.all_rooms` doesn't already exist before creating the -dict. Remember that `#HEADER` is copied in front of every `#CODE` block. Without that `if` statement -we'd be wiping the dict every block! - -### Don't treat a batchcode file like any Python file -Despite being a valid Python file, a batchcode file should *only* be run by the batchcode processor. -You should not do things like define Typeclasses or Commands in them, or import them into other -code. Importing a module in Python will execute base level of the module, which in the case of your -average batchcode file could mean creating a lot of new objects every time. -### Don't let code rely on the batch-file's real file path - -When you import things into your batchcode file, don't use relative imports but always import with -paths starting from the root of your game directory or evennia library. Code that relies on the -batch file's "actual" location *will fail*. Batch code files are read as text and the strings -executed. When the code runs it has no knowledge of what file those strings where once a part of. diff --git a/docs/0.9.5/_sources/Batch-Command-Processor.md.txt b/docs/0.9.5/_sources/Batch-Command-Processor.md.txt deleted file mode 100644 index 9278b4be7f..0000000000 --- a/docs/0.9.5/_sources/Batch-Command-Processor.md.txt +++ /dev/null @@ -1,182 +0,0 @@ -# Batch Command Processor - - -For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This -page describes the Batch-*command* processor. The Batch-*code* one is covered [here](Batch-Code- -Processor). - -## Basic Usage - -The batch-command processor is a superuser-only function, invoked by - - > @batchcommand path.to.batchcmdfile - -Where `path.to.batchcmdfile` is the path to a *batch-command file* with the "`.ev`" file ending. -This path is given like a python path relative to a folder you define to hold your batch files, set -with `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is in the `mygame` -folder) `mygame/world`. So if you want to run the example batch file in -`mygame/world/batch_cmds.ev`, you could use - - > @batchcommand batch_cmds - -A batch-command file contains a list of Evennia in-game commands separated by comments. The -processor will run the batch file from beginning to end. Note that *it will not stop if commands in -it fail* (there is no universal way for the processor to know what a failure looks like for all -different commands). So keep a close watch on the output, or use *Interactive mode* (see below) to -run the file in a more controlled, gradual manner. - -## The batch file - -The batch file is a simple plain-text file containing Evennia commands. Just like you would write -them in-game, except you have more freedom with line breaks. - -Here are the rules of syntax of an `*.ev` file. You'll find it's really, really simple: - -- All lines having the `#` (hash)-symbol *as the first one on the line* are considered *comments*. -All non-comment lines are treated as a command and/or their arguments. -- Comment lines have an actual function -- they mark the *end of the previous command definition*. -So never put two commands directly after one another in the file - separate them with a comment, or -the second of the two will be considered an argument to the first one. Besides, using plenty of -comments is good practice anyway. -- A line that starts with the word `#INSERT` is a comment line but also signifies a special -instruction. The syntax is `#INSERT ` and tries to import a given batch-cmd file -into this one. The inserted batch file (file ending `.ev`) will run normally from the point of the -`#INSERT` instruction. -- Extra whitespace in a command definition is *ignored*. - A completely empty line translates in to -a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant -for commands accepting such formatting, such as the `@desc` command). -- The very last command in the file is not required to end with a comment. -- You *cannot* nest another `@batchcommand` statement into your batch file. If you want to link many -batch-files together, use the `#INSERT` batch instruction instead. You also cannot launch the -`@batchcode` command from your batch file, the two batch processors are not compatible. - -Below is a version of the example file found in `evennia/contrib/tutorial_examples/batch_cmds.ev`. - -```bash - # - # This is an example batch build file for Evennia. - # - - # This creates a red button - @create button:tutorial_examples.red_button.RedButton - # (This comment ends input for @create) - # Next command. Let's create something. - @set button/desc = - This is a large red button. Now and then - it flashes in an evil, yet strangely tantalizing way. - - A big sign sits next to it. It says: - - - ----------- - - Press me! - - ----------- - - - ... It really begs to be pressed! You - know you want to! - - # This inserts the commands from another batch-cmd file named - # batch_insert_file.ev. - #INSERT examples.batch_insert_file - - - # (This ends the @set command). Note that single line breaks - # and extra whitespace in the argument are ignored. Empty lines - # translate into line breaks in the output. - # Now let's place the button where it belongs (let's say limbo #2 is - # the evil lair in our example) - @teleport #2 - # (This comments ends the @teleport command.) - # Now we drop it so others can see it. - # The very last command in the file needs not be ended with #. - drop button -``` - -To test this, run `@batchcommand` on the file: - - > @batchcommand contrib.tutorial_examples.batch_cmds - -A button will be created, described and dropped in Limbo. All commands will be executed by the user -calling the command. - -> Note that if you interact with the button, you might find that its description changes, loosing -your custom-set description above. This is just the way this particular object works. - -## Interactive mode - -Interactive mode allows you to more step-wise control over how the batch file is executed. This is -useful for debugging and also if you have a large batch file and is only updating a small part of it --- running the entire file again would be a waste of time (and in the case of `@create`-ing objects -you would to end up with multiple copies of same-named objects, for example). Use `@batchcommand` -with the `/interactive` flag to enter interactive mode. - - > @batchcommand/interactive tutorial_examples.batch_cmds - -You will see this: - - 01/04: @create button:tutorial_examples.red_button.RedButton (hh for help) - -This shows that you are on the `@create` command, the first out of only four commands in this batch -file. Observe that the command `@create` has *not* been actually processed at this point! - -To take a look at the full command you are about to run, use `ll` (a batch-processor version of -`look`). Use `pp` to actually process the current command (this will actually `@create` the button) --- and make sure it worked as planned. Use `nn` (next) to go to the next command. Use `hh` for a -list of commands. - -If there are errors, fix them in the batch file, then use `rr` to reload the file. You will still be -at the same command and can rerun it easily with `pp` as needed. This makes for a simple debug -cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch -file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g. -if `@create` in the example above had failed, the following commands would have had nothing to -operate on). - -Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward -(without processing any command in between). All normal commands of Evennia should work too while -working in interactive mode. - -## Limitations and Caveats - -The batch-command processor is great for automating smaller builds or for testing new commands and -objects repeatedly without having to write so much. There are several caveats you have to be aware -of when using the batch-command processor for building larger, complex worlds though. - -The main issue is that when you run a batch-command script you (*you*, as in your superuser -character) are actually moving around in the game creating and building rooms in sequence, just as -if you had been entering those commands manually, one by one. You have to take this into account -when creating the file, so that you can 'walk' (or teleport) to the right places in order. - -This also means there are several pitfalls when designing and adding certain types of objects. Here -are some examples: - -- *Rooms that change your [Command Set](./Command-Sets.md)*: Imagine that you build a 'dark' room, which -severely limits the cmdsets of those entering it (maybe you have to find the light switch to -proceed). In your batch script you would create this room, then teleport to it - and promptly be -shifted into the dark state where none of your normal build commands work ... -- *Auto-teleportation*: Rooms that automatically teleport those that enter them to another place -(like a trap room, for example). You would be teleported away too. -- *Mobiles*: If you add aggressive mobs, they might attack you, drawing you into combat. If they -have AI they might even follow you around when building - or they might move away from you before -you've had time to finish describing and equipping them! - -The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever -effects are in play. Add an on/off switch to objects and make sure it's always set to *off* upon -creation. It's all doable, one just needs to keep it in mind. - -## Assorted notes - -The fact that you build as 'yourself' can also be considered an advantage however, should you ever -decide to change the default command to allow others than superusers to call the processor. Since -normal access-checks are still performed, a malevolent builder with access to the processor should -not be able to do all that much damage (this is the main drawback of the [Batch Code -Processor](./Batch-Code-Processor.md)) - -- [GNU Emacs](https://www.gnu.org/software/emacs/) users might find it interesting to use emacs' -*evennia mode*. This is an Emacs major mode found in `evennia/utils/evennia-mode.el`. It offers -correct syntax highlighting and indentation with `` when editing `.ev` files in Emacs. See the -header of that file for installation instructions. -- [VIM](http://www.vim.org/) users can use amfl's [vim-evennia](https://github.com/amfl/vim-evennia) -mode instead, see its readme for install instructions. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Batch-Processors.md.txt b/docs/0.9.5/_sources/Batch-Processors.md.txt deleted file mode 100644 index 85a4c8a30f..0000000000 --- a/docs/0.9.5/_sources/Batch-Processors.md.txt +++ /dev/null @@ -1,82 +0,0 @@ -# Batch Processors - - -Building a game world is a lot of work, especially when starting out. Rooms should be created, -descriptions have to be written, objects must be detailed and placed in their proper places. In many -traditional MUD setups you had to do all this online, line by line, over a telnet session. - -Evennia already moves away from much of this by shifting the main coding work to external Python -modules. But also building would be helped if one could do some or all of it externally. Enter -Evennia's *batch processors* (there are two of them). The processors allows you, as a game admin, to -build your game completely offline in normal text files (*batch files*) that the processors -understands. Then, when you are ready, you use the processors to read it all into Evennia (and into -the database) in one go. - -You can of course still build completely online should you want to - this is certainly the easiest -way to go when learning and for small build projects. But for major building work, the advantages of -using the batch-processors are many: -- It's hard to compete with the comfort of a modern desktop text editor; Compared to a traditional -MUD line input, you can get much better overview and many more features. Also, accidentally pressing -Return won't immediately commit things to the database. -- You might run external spell checkers on your batch files. In the case of one of the batch- -processors (the one that deals with Python code), you could also run external debuggers and code -analyzers on your file to catch problems before feeding it to Evennia. -- The batch files (as long as you keep them) are records of your work. They make a natural starting -point for quickly re-building your world should you ever decide to start over. -- If you are an Evennia developer, using a batch file is a fast way to setup a test-game after -having reset the database. -- The batch files might come in useful should you ever decide to distribute all or part of your -world to others. - - -There are two batch processors, the Batch-*command* processor and the Batch-*code* processor. The -first one is the simpler of the two. It doesn't require any programming knowledge - you basically -just list in-game commands in a text file. The code-processor on the other hand is much more -powerful but also more complex - it lets you use Evennia's API to code your world in full-fledged -Python code. - -- The [Batch Command Processor](./Batch-Command-Processor.md) -- The [Batch Code Processor](./Batch-Code-Processor.md) - -If you plan to use international characters in your batchfiles you are wise to read about *file -encodings* below. - -## A note on File Encodings - -As mentioned, both the processors take text files as input and then proceed to process them. As long -as you stick to the standard [ASCII](http://en.wikipedia.org/wiki/Ascii) character set (which means -the normal English characters, basically) you should not have to worry much about this section. - -Many languages however use characters outside the simple `ASCII` table. Common examples are various -apostrophes and umlauts but also completely different symbols like those of the greek or cyrillic -alphabets. - -First, we should make it clear that Evennia itself handles international characters just fine. It -(and Django) uses [unicode](http://en.wikipedia.org/wiki/Unicode) strings internally. - -The problem is that when reading a text file like the batchfile, we need to know how to decode the -byte-data stored therein to universal unicode. That means we need an *encoding* (a mapping) for how -the file stores its data. There are many, many byte-encodings used around the world, with opaque -names such as `Latin-1`, `ISO-8859-3` or `ARMSCII-8` to pick just a few examples. Problem is that -it's practially impossible to determine which encoding was used to save a file just by looking at it -(it's just a bunch of bytes!). You have to *know*. - -With this little introduction it should be clear that Evennia can't guess but has to *assume* an -encoding when trying to load a batchfile. The text editor and Evennia must speak the same "language" -so to speak. Evennia will by default first try the international `UTF-8` encoding, but you can have -Evennia try any sequence of different encodings by customizing the `ENCODINGS` list in your settings -file. Evennia will use the first encoding in the list that do not raise any errors. Only if none -work will the server give up and return an error message. - -You can often change the text editor encoding (this depends on your editor though), otherwise you -need to add the editor's encoding to Evennia's `ENCODINGS` list. If you are unsure, write a test -file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works -as it should. - -More help with encodings can be found in the entry [Text Encodings](./Text-Encodings.md) and also in the -Wikipedia article [here](http://en.wikipedia.org/wiki/Text_encodings). - -**A footnote for the batch-code processor**: Just because *Evennia* can parse your file and your -fancy special characters, doesn't mean that *Python* allows their use. Python syntax only allows -international characters inside *strings*. In all other source code only `ASCII` set characters are -allowed. diff --git a/docs/0.9.5/_sources/Bootstrap-&-Evennia.md.txt b/docs/0.9.5/_sources/Bootstrap-&-Evennia.md.txt deleted file mode 100644 index fd7db78909..0000000000 --- a/docs/0.9.5/_sources/Bootstrap-&-Evennia.md.txt +++ /dev/null @@ -1,100 +0,0 @@ -# Bootstrap & Evennia - -# What is Bootstrap? -Evennia's new default web page uses a framework called [Bootstrap](https://getbootstrap.com/). This -framework is in use across the internet - you'll probably start to recognize its influence once you -learn some of the common design patterns. This switch is great for web developers, perhaps like -yourself, because instead of wondering about setting up different grid systems or what custom class -another designer used, we have a base, a bootstrap, to work from. Bootstrap is responsive by -default, and comes with some default styles that Evennia has lightly overrode to keep some of the -same colors and styles you're used to from the previous design. - -For your reading pleasure, a brief overview of Bootstrap follows. For more in-depth info, please -read [the documentation](https://getbootstrap.com/docs/4.0/getting-started/introduction/). -*** - -## The Layout System -Other than the basic styling Bootstrap includes, it also includes [a built in layout and grid -system](https://getbootstrap.com/docs/4.0/layout/overview/). -The first part of this system is [the -container](https://getbootstrap.com/docs/4.0/layout/overview/#containers). - -The container is meant to hold all your page content. Bootstrap provides two types: fixed-width and -full-width. -Fixed-width containers take up a certain max-width of the page - they're useful for limiting the -width on Desktop or Tablet platforms, instead of making the content span the width of the page. -``` -

- -
-``` -Full width containers take up the maximum width available to them - they'll span across a wide- -screen desktop or a smaller screen phone, edge-to-edge. -``` -
- -
-``` - -The second part of the layout system is [the grid](https://getbootstrap.com/docs/4.0/layout/grid/). -This is the bread-and-butter of the layout of Bootstrap - it allows you to change the size of -elements depending on the size of the screen, without writing any media queries. We'll briefly go -over it - to learn more, please read the docs or look at the source code for Evennia's home page in -your browser. -> Important! Grid elements should be in a .container or .container-fluid. This will center the -contents of your site. - -Bootstrap's grid system allows you to create rows and columns by applying classes based on -breakpoints. The default breakpoints are extra small, small, medium, large, and extra-large. If -you'd like to know more about these breakpoints, please [take a look at the documentation for -them.](https://getbootstrap.com/docs/4.0/layout/overview/#responsive-breakpoints) - -To use the grid system, first create a container for your content, then add your rows and columns -like so: -``` -
-
-
- 1 of 3 -
-
- 2 of 3 -
-
- 3 of 3 -
-
-
-``` -This layout would create three equal-width columns. - -To specify your sizes - for instance, Evennia's default site has three columns on desktop and -tablet, but reflows to single-column on smaller screens. Try it out! -``` -
-
-
- 1 of 4 -
-
- 2 of 4 -
-
- 3 of 4 -
-
- 4 of 4 -
-
-
-``` -This layout would be 4 columns on large screens, 2 columns on medium screens, and 1 column on -anything smaller. - -To learn more about Bootstrap's grid, please [take a look at the -docs](https://getbootstrap.com/docs/4.0/layout/grid/) -*** - -## More Bootstrap -Bootstrap also provides a huge amount of utilities, as well as styling and content elements. To -learn more about them, please [read the Bootstrap docs](https://getbootstrap.com/docs/4.0/getting-started/introduction/) or read one of our other web tutorials. diff --git a/docs/0.9.5/_sources/Bootstrap-Components-and-Utilities.md.txt b/docs/0.9.5/_sources/Bootstrap-Components-and-Utilities.md.txt deleted file mode 100644 index 5e46964766..0000000000 --- a/docs/0.9.5/_sources/Bootstrap-Components-and-Utilities.md.txt +++ /dev/null @@ -1,82 +0,0 @@ -# Bootstrap Components and Utilities - -Bootstrap provides many utilities and components you can use when customizing Evennia's web -presence. We'll go over a few examples here that you might find useful. -> Please take a look at either [the basic web tutorial](./Add-a-simple-new-web-page.md) or [the web -character view tutorial](./Web-Character-View-Tutorial.md) -> to get a feel for how to add pages to Evennia's website to test these examples. - -## General Styling -Bootstrap provides base styles for your site. These can be customized through CSS, but the default -styles are intended to provide a consistent, clean look for sites. - -### Color -Most elements can be styled with default colors. [Take a look at the -documentation](https://getbootstrap.com/docs/4.0/utilities/colors/) to learn more about these colors -- suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color -or background color. - -### Borders -Simply adding a class of 'border' to an element adds a border to the element. For more in-depth -info, please [read the documentation on -borders.](https://getbootstrap.com/docs/4.0/utilities/borders/). -``` - -``` -You can also easily round corners just by adding a class. -``` - -``` - -### Spacing -Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might -like to add margins or padding through CSS itself - however these classes are used in the default -Evennia site. [Take a look at the docs](https://getbootstrap.com/docs/4.0/utilities/spacing/) to -learn more. - -*** -## Components - -### Buttons -[Buttons](https://getbootstrap.com/docs/4.0/components/buttons/) in Bootstrap are very easy to use - -button styling can be added to ` - - - -``` -### Cards -[Cards](https://getbootstrap.com/docs/4.0/components/card/) provide a container for other elements -that stands out from the rest of the page. The "Accounts", "Recently Connected", and "Database -Stats" on the default webpage are all in cards. Cards provide quite a bit of formatting options - -the following is a simple example, but read the documentation or look at the site's source for more. -``` -
-
-

Card title

-
Card subtitle
-

Fancy, isn't it?

- Card link -
-
-``` - -### Jumbotron -[Jumbotrons](https://getbootstrap.com/docs/4.0/components/jumbotron/) are useful for featuring an -image or tagline for your game. They can flow with the rest of your content or take up the full -width of the page - Evennia's base site uses the former. -``` -
-
-

Full Width Jumbotron

-

Look at the source of the default Evennia page for a regular Jumbotron

-
-
-``` - -### Forms -[Forms](https://getbootstrap.com/docs/4.0/components/forms/) are highly customizable with Bootstrap. -For a more in-depth look at how to use forms and their styles in your own Evennia site, please read -over [the web character gen tutorial.](./Web-Character-Generation.md) \ No newline at end of file diff --git a/docs/0.9.5/_sources/Builder-Docs.md.txt b/docs/0.9.5/_sources/Builder-Docs.md.txt deleted file mode 100644 index 9bd7a11a05..0000000000 --- a/docs/0.9.5/_sources/Builder-Docs.md.txt +++ /dev/null @@ -1,43 +0,0 @@ -# Builder Docs - -This section contains information useful to world builders. - -### Building basics - -- [Default in-game commands](evennia.commands.default) -- [Building Quick-start](./Building-Quickstart.md) -- [Giving build permissions to others](./Building-Permissions.md) -- [Adding text tags](./TextTags.md) - - [Colored text](./TextTags.md#coloured-text) - - [Clickable links](./TextTags.md#clickable-links) - - [Inline functions](./TextTags.md#inline-functions) -- [Customizing the connection screen](./Connection-Screen.md) - -### Advanced building and World building - -- [Overview of batch processors](./Batch-Processors.md) - - [Batch-command processor](./Batch-Command-Processor.md) - - [Batch-code processor](./Batch-Code-Processor.md) -- [Using the Spawner for individualizing objects](./Spawner-and-Prototypes.md) -- [Adding Zones](./Zones.md) - -### The Tutorial world - -- [Introduction and setup](./Tutorial-World-Introduction.md) - - -```{toctree} - :hidden: - - Building-Quickstart - Building-Permissions - TextTags - Connection-Screen - Batch-Processors - Batch-Command-Processor - Batch-Code-Processor - Spawner-and-Prototypes - Zones - Tutorial-World-Introduction - -``` \ No newline at end of file diff --git a/docs/0.9.5/_sources/Building-Permissions.md.txt b/docs/0.9.5/_sources/Building-Permissions.md.txt deleted file mode 100644 index 2f41d9c95b..0000000000 --- a/docs/0.9.5/_sources/Building-Permissions.md.txt +++ /dev/null @@ -1,72 +0,0 @@ -# Building Permissions - - -*OBS: This gives only a brief introduction to the access system. Locks and permissions are fully -detailed* [here](./Locks.md). - -## The super user - -There are strictly speaking two types of users in Evennia, the *super user* and everyone else. The -superuser is the first user you create, object `#1`. This is the all-powerful server-owner account. -Technically the superuser not only has access to everything, it *bypasses* the permission checks -entirely. This makes the superuser impossible to lock out, but makes it unsuitable to actually play- -test the game's locks and restrictions with (see `@quell` below). Usually there is no need to have -but one superuser. - -## Assigning permissions - -Whereas permissions can be used for anything, those put in `settings.PERMISSION_HIERARCHY` will have -a ranking relative each other as well. We refer to these types of permissions as *hierarchical -permissions*. When building locks to check these permissions, the `perm()` [lock function](./Locks.md) is -used. By default Evennia creates the following hierarchy (spelled exactly like this): - -1. **Developers** basically have the same access as superusers except that they do *not* sidestep -the Permission system. Assign only to really trusted server-admin staff since this level gives -access both to server reload/shutdown functionality as well as (and this may be more critical) gives -access to the all-powerful `@py` command that allows the execution of arbitrary Python code on the -command line. -1. **Admins** can do everything *except* affecting the server functions themselves. So an Admin -couldn't reload or shutdown the server for example. They also cannot execute arbitrary Python code -on the console or import files from the hard drive. -1. **Builders** - have all the build commands, but cannot affect other accounts or mess with the -server. -1. **Helpers** are almost like a normal *Player*, but they can also add help files to the database. -1. **Players** is the default group that new players end up in. A new player have permission to use -tells and to use and create new channels. - -A user having a certain level of permission automatically have access to locks specifying access of -a lower level. - -To assign a new permission from inside the game, you need to be able to use the `@perm` command. -This is an *Developer*-level command, but it could in principle be made lower-access since it only -allows assignments equal or lower to your current level (so you cannot use it to escalate your own -permission level). So, assuming you yourself have *Developer* access (or is superuser), you assign -a new account "Tommy" to your core staff with the command - - @perm/account Tommy = Developer - -or - - @perm *Tommy = Developer - -We use a switch or the `*name` format to make sure to put the permission on the *Account* and not on -any eventual *Character* that may also be named "Tommy". This is usually what you want since the -Account will then remain an Developer regardless of which Character they are currently controlling. -To limit permission to a per-Character level you should instead use *quelling* (see below). Normally -permissions can be any string, but for these special hierarchical permissions you can also use -plural ("Developer" and "Developers" both grant the same powers). - -## Quelling your permissions - -When developing it can be useful to check just how things would look had your permission-level been -lower. For this you can use *quelling*. Normally, when you puppet a Character you are using your -Account-level permission. So even if your Character only has *Accounts* level permissions, your -*Developer*-level Account will take precedence. With the `@quell` command you can change so that the -Character's permission takes precedence instead: - - @quell - -This will allow you to test out the game using the current Character's permission level. A developer -or builder can thus in principle maintain several test characters, all using different permission -levels. Note that you cannot escalate your permissions this way; If the Character happens to have a -*higher* permission level than the Account, the *Account's* (lower) permission will still be used. diff --git a/docs/0.9.5/_sources/Building-Quickstart.md.txt b/docs/0.9.5/_sources/Building-Quickstart.md.txt deleted file mode 100644 index 64fa277a0f..0000000000 --- a/docs/0.9.5/_sources/Building-Quickstart.md.txt +++ /dev/null @@ -1,274 +0,0 @@ -# Building Quickstart - - -The [default command](./Default-Commands.md) definitions coming with Evennia -follows a style [similar](./Using-MUX-as-a-Standard.md) to that of MUX, so the -commands should be familiar if you used any such code bases before. - -> Throughout the larger documentation you may come across commands prefixed -> with `@`. This is just an optional marker used in some places to make a -> command stand out. Evennia defaults to ignoring the use of `@` in front of -> your command (so entering `dig` is the same as entering `@dig`). - -The default commands have the following style (where `[...]` marks optional parts): - - command[/switch/switch...] [arguments ...] - -A _switch_ is a special, optional flag to the command to make it behave differently. It is always -put directly after the command name, and begins with a forward slash (`/`). The _arguments_ are one -or more inputs to the commands. It's common to use an equal sign (`=`) when assigning something to -an object. - -Below are some examples of commands you can try when logged in to the game. Use `help ` for -learning more about each command and their detailed options. - -## Stepping Down From Godhood - -If you just installed Evennia, your very first player account is called user #1, also known as the -_superuser_ or _god user_. This user is very powerful, so powerful that it will override many game -restrictions such as locks. This can be useful, but it also hides some functionality that you might -want to test. - -To temporarily step down from your superuser position you can use the `quell` command in-game: - - quell - -This will make you start using the permission of your current character's level instead of your -superuser level. If you didn't change any settings your game Character should have an _Developer_ -level permission - high as can be without bypassing locks like the superuser does. This will work -fine for the examples on this page. Use `unquell` to get back to superuser status again afterwards. - -## Creating an Object - -Basic objects can be anything -- swords, flowers and non-player characters. They are created using -the `create` command: - - create box - -This created a new 'box' (of the default object type) in your inventory. Use the command `inventory` -(or `i`) to see it. Now, 'box' is a rather short name, let's rename it and tack on a few aliases. - - name box = very large box;box;very;crate - -We now renamed the box to _very large box_ (and this is what we will see when looking at it), but we -will also recognize it by any of the other names we give - like _crate_ or simply _box_ as before. -We could have given these aliases directly after the name in the `create` command, this is true for -all creation commands - you can always tag on a list of `;`-separated aliases to the name of your -new object. If you had wanted to not change the name itself, but to only add aliases, you could have -used the `alias` command. - -We are currently carrying the box. Let's drop it (there is also a short cut to create and drop in -one go by using the `/drop` switch, for example `create/drop box`). - - drop box - -Hey presto - there it is on the ground, in all its normality. - - examine box - -This will show some technical details about the box object. For now we will ignore what this -information means. - -Try to `look` at the box to see the (default) description. - - look box - You see nothing special. - -The description you get is not very exciting. Let's add some flavor. - - describe box = This is a large and very heavy box. - -If you try the `get` command we will pick up the box. So far so good, but if we really want this to -be a large and heavy box, people should _not_ be able to run off with it that easily. To prevent -this we need to lock it down. This is done by assigning a _Lock_ to it. Make sure the box was -dropped in the room, then try this: - - lock box = get:false() - -Locks represent a rather [big topic](./Locks.md), but for now that will do what we want. This will lock -the box so noone can lift it. The exception is superusers, they override all locks and will pick it -up anyway. Make sure you are quelling your superuser powers and try to get the box now: - - > get box - You can't get that. - -Think thís default error message looks dull? The `get` command looks for an [Attribute](./Attributes.md) -named `get_err_msg` for returning a nicer error message (we just happen to know this, you would need -to peek into the -[code](https://github.com/evennia/evennia/blob/master/evennia/commands/default/general.py#L235) for -the `get` command to find out.). You set attributes using the `set` command: - - set box/get_err_msg = It's way too heavy for you to lift. - -Try to get it now and you should see a nicer error message echoed back to you. To see what this -message string is in the future, you can use 'examine.' - - examine box/get_err_msg - -Examine will return the value of attributes, including color codes. `examine here/desc` would return -the raw description of your current room (including color codes), so that you can copy-and-paste to -set its description to something else. - -You create new Commands (or modify existing ones) in Python outside the game. See the [Adding -Commands tutorial](./Adding-Command-Tutorial.md) for help with creating your first own Command. - -## Get a Personality - -[Scripts](./Scripts.md) are powerful out-of-character objects useful for many "under the hood" things. -One of their optional abilities is to do things on a timer. To try out a first script, let's put one -on ourselves. There is an example script in `evennia/contrib/tutorial_examples/bodyfunctions.py` -that is called `BodyFunctions`. To add this to us we will use the `script` command: - - script self = tutorial_examples.bodyfunctions.BodyFunctions - -(note that you don't have to give the full path as long as you are pointing to a place inside the -`contrib` directory, it's one of the places Evennia looks for Scripts). Wait a while and you will -notice yourself starting making random observations. - - script self - -This will show details about scripts on yourself (also `examine` works). You will see how long it is -until it "fires" next. Don't be alarmed if nothing happens when the countdown reaches zero - this -particular script has a randomizer to determine if it will say something or not. So you will not see -output every time it fires. - -When you are tired of your character's "insights", kill the script with - - script/stop self = tutorial_examples.bodyfunctions.BodyFunctions - -You create your own scripts in Python, outside the game; the path you give to `script` is literally -the Python path to your script file. The [Scripts](./Scripts.md) page explains more details. - -## Pushing Your Buttons - -If we get back to the box we made, there is only so much fun you can do with it at this point. It's -just a dumb generic object. If you renamed it to `stone` and changed its description noone would be -the wiser. However, with the combined use of custom [Typeclasses](./Typeclasses.md), [Scripts](./Scripts.md) -and object-based [Commands](./Commands.md), you could expand it and other items to be as unique, complex -and interactive as you want. - -Let's take an example. So far we have only created objects that use the default object typeclass -named simply `Object`. Let's create an object that is a little more interesting. Under -`evennia/contrib/tutorial_examples` there is a module `red_button.py`. It contains the enigmatic -`RedButton` typeclass. - -Let's make us one of _those_! - - create/drop button:tutorial_examples.red_button.RedButton - -We import the RedButton python class the same way you would import it in Python except Evennia makes -sure to look in`evennia/contrib/` so you don't have to write the full path every time. There you go -- one red button. - -The RedButton is an example object intended to show off a few of Evennia's features. You will find -that the [Typeclass](./Typeclasses.md) and [Commands](./Commands.md) controlling it are inside -`evennia/contrib/tutorial_examples/`. - -If you wait for a while (make sure you dropped it!) the button will blink invitingly. Why don't you -try to push it ...? Surely a big red button is meant to be pushed. You know you want to. - -## Making Yourself a House - -The main command for shaping the game world is `dig`. For example, if you are standing in Limbo you -can dig a route to your new house location like this: - - dig house = large red door;door;in,to the outside;out - -This will create a new room named 'house'. Spaces at the start/end of names and aliases are ignored -so you could put more air if you wanted. This call will directly create an exit from your current -location named 'large red door' and a corresponding exit named 'to the outside' in the house room -leading back to Limbo. We also define a few aliases to those exits, so people don't have to write -the full thing all the time. - -If you wanted to use normal compass directions (north, west, southwest etc), you could do that with -`dig` too. But Evennia also has a limited version of `dig` that helps for compass directions (and -also up/down and in/out). It's called `tunnel`: - - tunnel sw = cliff - -This will create a new room "cliff" with an exit "southwest" leading there and a path "northeast" -leading back from the cliff to your current location. - -You can create new exits from where you are using the `open` command: - - open north;n = house - -This opens an exit `north` (with an alias `n`) to the previously created room `house`. - -If you have many rooms named `house` you will get a list of matches and have to select which one you -want to link to. You can also give its database (#dbref) number, which is unique to every object. -This can be found with the `examine` command or by looking at the latest constructions with -`objects`. - -Follow the north exit to your 'house' or `teleport` to it: - - north - -or: - - teleport house - -To manually open an exit back to Limbo (if you didn't do so with the `dig` command): - - open door = limbo - -(or give limbo's dbref which is #2) - -## Reshuffling the World - -You can find things using the `find` command. Assuming you are back at `Limbo`, let's teleport the -_large box to our house_. - - > teleport box = house - very large box is leaving Limbo, heading for house. - Teleported very large box -> house. - -We can still find the box by using find: - - > find box - One Match(#1-#8): - very large box(#8) - src.objects.objects.Object - -Knowing the `#dbref` of the box (#8 in this example), you can grab the box and get it back here -without actually yourself going to `house` first: - - teleport #8 = here - -(You can usually use `here` to refer to your current location. To refer to yourself you can use -`self` or `me`). The box should now be back in Limbo with you. - -We are getting tired of the box. Let's destroy it. - - destroy box - -You can destroy many objects in one go by giving a comma-separated list of objects (or their -#dbrefs, if they are not in the same location) to the command. - -## Adding a Help Entry - -An important part of building is keeping the help files updated. You can add, delete and append to -existing help entries using the `sethelp` command. - - sethelp/add MyTopic = This help topic is about ... - -## Adding a World - -After this brief introduction to building you may be ready to see a more fleshed-out example. -Evennia comes with a tutorial world for you to explore. - -First you need to switch back to _superuser_ by using the `unquell` command. Next, place yourself in -`Limbo` and run the following command: - - batchcommand tutorial_world.build - -This will take a while (be patient and don't re-run the command). You will see all the commands used -to build the world scroll by as the world is built for you. - -You will end up with a new exit from Limbo named _tutorial_. Apart from being a little solo- -adventure in its own right, the tutorial world is a good source for learning Evennia building (and -coding). - -Read [the batch -file](https://github.com/evennia/evennia/blob/master/evennia/contrib/tutorial_world/build.ev) to see -exactly how it's built, step by step. See also more info about the tutorial world [here](Tutorial- -World-Introduction). diff --git a/docs/0.9.5/_sources/Building-a-mech-tutorial.md.txt b/docs/0.9.5/_sources/Building-a-mech-tutorial.md.txt deleted file mode 100644 index 9d14bcbfc3..0000000000 --- a/docs/0.9.5/_sources/Building-a-mech-tutorial.md.txt +++ /dev/null @@ -1,236 +0,0 @@ -# Building a mech tutorial - -> This page was adapted from the article "Building a Giant Mech in Evennia" by Griatch, published in -Imaginary Realities Volume 6, issue 1, 2014. The original article is no longer available online, -this is a version adopted to be compatible with the latest Evennia. - -## Creating the Mech - -Let us create a functioning giant mech using the Python MUD-creation system Evennia. Everyone likes -a giant mech, right? Start in-game as a character with build privileges (or the superuser). - - @create/drop Giant Mech ; mech - -Boom. We created a Giant Mech Object and dropped it in the room. We also gave it an alias *mech*. -Let’s describe it. - - @desc mech = This is a huge mech. It has missiles and stuff. - -Next we define who can “puppet” the mech object. - - @lock mech = puppet:all() - -This makes it so that everyone can control the mech. More mechs to the people! (Note that whereas -Evennia’s default commands may look vaguely MUX-like, you can change the syntax to look like -whatever interface style you prefer.) - -Before we continue, let’s make a brief detour. Evennia is very flexible about its objects and even -more flexible about using and adding commands to those objects. Here are some ground rules well -worth remembering for the remainder of this article: - -- The [Account](./Accounts.md) represents the real person logging in and has no game-world existence. -- Any [Object](./Objects.md) can be puppeted by an Account (with proper permissions). -- [Characters](./Objects.md#characters), [Rooms](./Objects.md#rooms), and [Exits](./Objects.md#exits) are just -children of normal Objects. -- Any Object can be inside another (except if it creates a loop). -- Any Object can store custom sets of commands on it. Those commands can: - - be made available to the puppeteer (Account), - - be made available to anyone in the same location as the Object, and - - be made available to anyone “inside” the Object - - Also Accounts can store commands on themselves. Account commands are always available unless -commands on a puppeted Object explicitly override them. - -In Evennia, using the `@ic` command will allow you to puppet a given Object (assuming you have -puppet-access to do so). As mentioned above, the bog-standard Character class is in fact like any -Object: it is auto-puppeted when logging in and just has a command set on it containing the normal -in-game commands, like look, inventory, get and so on. - - @ic mech - -You just jumped out of your Character and *are* now the mech! If people look at you in-game, they -will look at a mech. The problem at this point is that the mech Object has no commands of its own. -The usual things like look, inventory and get sat on the Character object, remember? So at the -moment the mech is not quite as cool as it could be. - - @ic - -You just jumped back to puppeting your normal, mundane Character again. All is well. - -> (But, you ask, where did that `@ic` command come from, if the mech had no commands on it? The -answer is that it came from the Account's command set. This is important. Without the Account being -the one with the `@ic` command, we would not have been able to get back out of our mech again.) - - -### Arming the Mech - -Let us make the mech a little more interesting. In our favorite text editor, we will create some new -mech-suitable commands. In Evennia, commands are defined as Python classes. - -```python -# in a new file mygame/commands/mechcommands.py - -from evennia import Command - -class CmdShoot(Command): - """ - Firing the mech’s gun - - Usage: - shoot [target] - - This will fire your mech’s main gun. If no - target is given, you will shoot in the air. - """ - key = "shoot" - aliases = ["fire", "fire!"] - - def func(self): - "This actually does the shooting" - - caller = self.caller - location = caller.location - - if not self.args: - # no argument given to command - shoot in the air - message = "BOOM! The mech fires its gun in the air!" - location.msg_contents(message) - return - - # we have an argument, search for target - target = caller.search(self.args.strip()) - if target: - message = "BOOM! The mech fires its gun at %s" % target.key - location.msg_contents(message) - -class CmdLaunch(Command): - # make your own 'launch'-command here as an exercise! - # (it's very similar to the 'shoot' command above). - -``` - -This is saved as a normal Python module (let’s call it `mechcommands.py`), in a place Evennia looks -for such modules (`mygame/commands/`). This command will trigger when the player gives the command -“shoot”, “fire,” or even “fire!” with an exclamation mark. The mech can shoot in the air or at a -target if you give one. In a real game the gun would probably be given a chance to hit and give -damage to the target, but this is enough for now. - -We also make a second command for launching missiles (`CmdLaunch`). To save -space we won’t describe it here; it looks the same except it returns a text -about the missiles being fired and has different `key` and `aliases`. We leave -that up to you to create as an exercise. You could have it print "WOOSH! The -mech launches missiles against !", for example. - -Now we shove our commands into a command set. A [Command Set](./Command-Sets.md) (CmdSet) is a container -holding any number of commands. The command set is what we will store on the mech. - -```python -# in the same file mygame/commands/mechcommands.py - -from evennia import CmdSet -from evennia import default_cmds - -class MechCmdSet(CmdSet): - """ - This allows mechs to do do mech stuff. - """ - key = "mechcmdset" - - def at_cmdset_creation(self): - "Called once, when cmdset is first created" - self.add(CmdShoot()) - self.add(CmdLaunch()) -``` - -This simply groups all the commands we want. We add our new shoot/launch commands. Let’s head back -into the game. For testing we will manually attach our new CmdSet to the mech. - - @py self.search("mech").cmdset.add("commands.mechcommands.MechCmdSet") - -This is a little Python snippet (run from the command line as an admin) that searches for the mech -in our current location and attaches our new MechCmdSet to it. What we add is actually the Python -path to our cmdset class. Evennia will import and initialize it behind the scenes. - - @ic mech - -We are back as the mech! Let’s do some shooting! - - fire! - BOOM! The mech fires its gun in the air! - -There we go, one functioning mech. Try your own `launch` command and see that it works too. We can -not only walk around as the mech — since the CharacterCmdSet is included in our MechCmdSet, the mech -can also do everything a Character could do, like look around, pick up stuff, and have an inventory. -We could now shoot the gun at a target or try the missile launch command. Once you have your own -mech, what else do you need? - -> Note: You'll find that the mech's commands are available to you by just standing in the same -location (not just by puppeting it). We'll solve this with a *lock* in the next section. - -## Making a Mech production line - -What we’ve done so far is just to make a normal Object, describe it and put some commands on it. -This is great for testing. The way we added it, the MechCmdSet will even go away if we reload the -server. Now we want to make the mech an actual object “type” so we can create mechs without those -extra steps. For this we need to create a new Typeclass. - -A [Typeclass](./Typeclasses.md) is a near-normal Python class that stores its existence to the database -behind the scenes. A Typeclass is created in a normal Python source file: - -```python -# in the new file mygame/typeclasses/mech.py - -from typeclasses.objects import Object -from commands.mechcommands import MechCmdSet -from evennia import default_cmds - -class Mech(Object): - """ - This typeclass describes an armed Mech. - """ - def at_object_creation(self): - "This is called only when object is first created" - self.cmdset.add_default(default_cmds.CharacterCmdSet) - self.cmdset.add(MechCmdSet, permanent=True) - self.locks.add("puppet:all();call:false()") - self.db.desc = "This is a huge mech. It has missiles and stuff." -``` - -For convenience we include the full contents of the default `CharacterCmdSet` in there. This will -make a Character’s normal commands available to the mech. We also add the mech-commands from before, -making sure they are stored persistently in the database. The locks specify that anyone can puppet -the meck and no-one can "call" the mech's Commands from 'outside' it - you have to puppet it to be -able to shoot. - -That’s it. When Objects of this type are created, they will always start out with the mech’s command -set and the correct lock. We set a default description, but you would probably change this with -`@desc` to individualize your mechs as you build them. - -Back in the game, just exit the old mech (`@ic` back to your old character) then do - - @create/drop The Bigger Mech ; bigmech : mech.Mech - -We create a new, bigger mech with an alias bigmech. Note how we give the python-path to our -Typeclass at the end — this tells Evennia to create the new object based on that class (we don't -have to give the full path in our game dir `typeclasses.mech.Mech` because Evennia knows to look in -the `typeclasses` folder already). A shining new mech will appear in the room! Just use - - @ic bigmech - -to take it on a test drive. - -## Future Mechs - -To expand on this you could add more commands to the mech and remove others. Maybe the mech -shouldn’t work just like a Character after all. Maybe it makes loud noises every time it passes from -room to room. Maybe it cannot pick up things without crushing them. Maybe it needs fuel, ammo and -repairs. Maybe you’ll lock it down so it can only be puppeted by emo teenagers. - -Having you puppet the mech-object directly is also just one way to implement a giant mech in -Evennia. - -For example, you could instead picture a mech as a “vehicle” that you “enter” as your normal -Character (since any Object can move inside another). In that case the “insides” of the mech Object -could be the “cockpit”. The cockpit would have the `MechCommandSet` stored on itself and all the -shooting goodness would be made available to you only when you enter it. - -And of course you could put more guns on it. And make it fly. diff --git a/docs/0.9.5/_sources/Building-menus.md.txt b/docs/0.9.5/_sources/Building-menus.md.txt deleted file mode 100644 index 4389a44695..0000000000 --- a/docs/0.9.5/_sources/Building-menus.md.txt +++ /dev/null @@ -1,1233 +0,0 @@ -# Building menus - - -# The building_menu contrib - -This contrib allows you to write custom and easy to use building menus. As the name implies, these -menus are most useful for building things, that is, your builders might appreciate them, although -you can use them for your players as well. - -Building menus are somewhat similar to `EvMenu` although they don't use the same system at all and -are intended to make building easier. They replicate what other engines refer to as "building -editors", which allow to you to build in a menu instead of having to enter a lot of complex -commands. Builders might appreciate this simplicity, and if the code that was used to create them -is simple as well, coders could find this contrib useful. - -## A simple menu - -Before diving in, there are some things to point out: - -- Building menus work on an object. This object will be edited by manipulations in the menu. So -you can create a menu to add/edit a room, an exit, a character and so on. -- Building menus are arranged in layers of choices. A choice gives access to an option or to a sub- -menu. Choices are linked to commands (usually very short). For instance, in the example shown -below, to edit the room key, after opening the building menu, you can type `k`. That will lead you -to the key choice where you can enter a new key for the room. Then you can enter `@` to leave this -choice and go back to the entire menu. (All of this can be changed). -- To open the menu, you will need something like a command. This contrib offers a basic command for -demonstration, but we will override it in this example, using the same code with more flexibility. - -So let's add a very basic example to begin with. - -### A generic editing command - -Let's begin by adding a new command. You could add or edit the following file (there's no trick -here, feel free to organize the code differently): - -```python -# file: commands/building.py -from evennia.contrib.building_menu import BuildingMenu -from commands.command import Command - -class EditCmd(Command): - - """ - Editing command. - - Usage: - @edit [object] - - Open a building menu to edit the specified object. This menu allows to - specific information about this object. - - Examples: - @edit here - @edit self - @edit #142 - - """ - - key = "@edit" - locks = "cmd:id(1) or perm(Builders)" - help_category = "Building" - - def func(self): - if not self.args.strip(): - self.msg("|rYou should provide an argument to this function: the object to edit.|n") - return - - obj = self.caller.search(self.args.strip(), global_search=True) - if not obj: - return - - if obj.typename == "Room": - Menu = RoomBuildingMenu - else: - self.msg("|rThe object {} cannot be -edited.|n".format(obj.get_display_name(self.caller))) - return - - menu = Menu(self.caller, obj) - menu.open() -``` - -This command is rather simple in itself: - -1. It has a key `@edit` and a lock to only allow builders to use it. -2. In its `func` method, it begins by checking the arguments, returning an error if no argument is -specified. -3. It then searches for the given argument. We search globally. The `search` method used in this -way will return the found object or `None`. It will also send the error message to the caller if -necessary. -4. Assuming we have found an object, we check the object `typename`. This will be used later when -we want to display several building menus. For the time being, we only handle `Room`. If the -caller specified something else, we'll display an error. -5. Assuming this object is a `Room`, we have defined a `Menu` object containing the class of our -building menu. We build this class (creating an instance), giving it the caller and the object to -edit. -6. We then open the building menu, using the `open` method. - -The end might sound a bit surprising at first glance. But the process is still very simple: we -create an instance of our building menu and call its `open` method. Nothing more. - -> Where is our building menu? - -If you go ahead and add this command and test it, you'll get an error. We haven't defined -`RoomBuildingMenu` yet. - -To add this command, edit `commands/default_cmdsets.py`. Import our command, adding an import line -at the top of the file: - -```python -""" -... -""" - -from evennia import default_cmds - -# The following line is to be added -from commands.building import EditCmd -``` - -And in the class below (`CharacterCmdSet`), add the last line of this code: - -```python -class CharacterCmdSet(default_cmds.CharacterCmdSet): - """ - The `CharacterCmdSet` contains general in-game commands like `look`, - `get`, etc available on in-game Character objects. It is merged with - the `AccountCmdSet` when an Account puppets a Character. - """ - key = "DefaultCharacter" - - def at_cmdset_creation(self): - """ - Populates the cmdset - """ - super().at_cmdset_creation() - # - # any commands you add below will overload the default ones. - # - self.add(EditCmd()) -``` - -### Our first menu - -So far, we can't use our building menu. Our `@edit` command will throw an error. We have to define -the `RoomBuildingMenu` class. Open the `commands/building.py` file and add to the end of the file: - -```python -# ... at the end of commands/building.py -# Our building menu - -class RoomBuildingMenu(BuildingMenu): - - """ - Building menu to edit a room. - - For the time being, we have only one choice: key, to edit the room key. - - """ - - def init(self, room): - self.add_choice("key", "k", attr="key") -``` - -Save these changes, reload your game. You can now use the `@edit` command. Here's what we get -(notice that the commands we enter into the game are prefixed with `> `, though this prefix will -probably not appear in your MUD client): - -``` -> look -Limbo(#2) -Welcome to your new Evennia-based game! Visit http://www.evennia.com if you need -help, want to contribute, report issues or just join the community. -As Account #1 you can create a demo/tutorial area with @batchcommand tutorial_world.build. - -> @edit here -Building menu: Limbo - - [K]ey: Limbo - [Q]uit the menu - -> q -Closing the building menu. - -> @edit here -Building menu: Limbo - - [K]ey: Limbo - [Q]uit the menu - -> k -------------------------------------------------------------------------------- -key for Limbo(#2) - -You can change this value simply by entering it. - -Use @ to go back to the main menu. - -Current value: Limbo - -> A beautiful meadow -------------------------------------------------------------------------------- - -key for A beautiful meadow(#2) - -You can change this value simply by entering it. - -Use @ to go back to the main menu. - -Current value: A beautiful meadow - -> @ -Building menu: A beautiful meadow - - [K]ey: A beautiful meadow - [Q]uit the menu - -> q - -Closing the building menu. - -> look -A beautiful meadow(#2) -Welcome to your new Evennia-based game! Visit http://www.evennia.com if you need -help, want to contribute, report issues or just join the community. -As Account #1 you can create a demo/tutorial area with @batchcommand tutorial_world.build. -``` - -Before diving into the code, let's examine what we have: - -- When we use the `@edit here` command, a building menu for this room appears. -- This menu has two choices: - - Enter `k` to edit the room key. You will go into a choice where you can simply type the key -room key (the way we have done here). You can use `@` to go back to the menu. - - You can use `q` to quit the menu. - -We then check, with the `look` command, that the menu has modified this room key. So by adding a -class, with a method and a single line of code within, we've added a menu with two choices. - -### Code explanation - -Let's examine our code again: - -```python -class RoomBuildingMenu(BuildingMenu): - - """ - Building menu to edit a room. - - For the time being, we have only one choice: key, to edit the room key. - - """ - - def init(self, room): - self.add_choice("key", "k", attr="key") -``` - -- We first create a class inheriting from `BuildingMenu`. This is usually the case when we want to -create a building menu with this contrib. -- In this class, we override the `init` method, which is called when the menu opens. -- In this `init` method, we call `add_choice`. This takes several arguments, but we've defined only -three here: - - The choice name. This is mandatory and will be used by the building menu to know how to -display this choice. - - The command key to access this choice. We've given a simple `"k"`. Menu commands usually are -pretty short (that's part of the reason building menus are appreciated by builders). You can also -specify additional aliases, but we'll see that later. - - We've added a keyword argument, `attr`. This tells the building menu that when we are in this -choice, the text we enter goes into this attribute name. It's called `attr`, but it could be a room -attribute or a typeclass persistent or non-persistent attribute (we'll see other examples as well). - -> We've added the menu choice for `key` here, why is another menu choice defined for `quit`? - -Our building menu creates a choice at the end of our choice list if it's a top-level menu (sub-menus -don't have this feature). You can, however, override it to provide a different "quit" message or to -perform some actions. - -I encourage you to play with this code. As simple as it is, it offers some functionalities already. - -## Customizing building menus - -This somewhat long section explains how to customize building menus. There are different ways -depending on what you would like to achieve. We'll go from specific to more advanced here. - -### Generic choices - -In the previous example, we've used `add_choice`. This is one of three methods you can use to add -choices. The other two are to handle more generic actions: - -- `add_choice_edit`: this is called to add a choice which points to the `EvEditor`. It is used to -edit a description in most cases, although you could edit other things. We'll see an example -shortly. `add_choice_edit` uses most of the `add_choice` keyword arguments we'll see, but usually -we specify only two (sometimes three): - - The choice title as usual. - - The choice key (command key) as usual. - - Optionally, the attribute of the object to edit, with the `attr` keyword argument. By -default, `attr` contains `db.desc`. It means that this persistent data attribute will be edited by -the `EvEditor`. You can change that to whatever you want though. -- `add_choice_quit`: this allows to add a choice to quit the editor. Most advisable! If you don't -do it, the building menu will do it automatically, except if you really tell it not to. Again, you -can specify the title and key of this menu. You can also call a function when this menu closes. - -So here's a more complete example (you can replace your `RoomBuildingMenu` class in -`commands/building.py` to see it): - -```python -class RoomBuildingMenu(BuildingMenu): - - """ - Building menu to edit a room. - """ - - def init(self, room): - self.add_choice("key", "k", attr="key") - self.add_choice_edit("description", "d") - self.add_choice_quit("quit this editor", "q") -``` - -So far, our building menu class is still thin... and yet we already have some interesting feature. -See for yourself the following MUD client output (again, the commands are prefixed with `> ` to -distinguish them): - -``` -> @reload - -> @edit here -Building menu: A beautiful meadow - - [K]ey: A beautiful meadow - [D]escription: - Welcome to your new Evennia-based game! Visit http://www.evennia.com if you need -help, want to contribute, report issues or just join the community. -As Account #1 you can create a demo/tutorial area with @batchcommand tutorial_world.build. - [Q]uit this editor - -> d - -----------Line Editor [editor]---------------------------------------------------- -01| Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need -02| help, want to contribute, report issues or just join the community. -03| As Account #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n. - -> :DD - -----------[l:03 w:034 c:0247]------------(:h for help)---------------------------- -Cleared 3 lines from buffer. - -> This is a beautiful meadow. But so beautiful I can't describe it. - -01| This is a beautiful meadow. But so beautiful I can't describe it. - -> :wq -Building menu: A beautiful meadow - - [K]ey: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [Q]uit this editor - -> q -Closing the building menu. - -> look -A beautiful meadow(#2) -This is a beautiful meadow. But so beautiful I can't describe it. -``` - -So by using the `d` shortcut in our building menu, an `EvEditor` opens. You can use the `EvEditor` -commands (like we did here, `:DD` to remove all, `:wq` to save and quit). When you quit the editor, -the description is saved (here, in `room.db.desc`) and you go back to the building menu. - -Notice that the choice to quit has changed too, which is due to our adding `add_choice_quit`. In -most cases, you will probably not use this method, since the quit menu is added automatically. - -### `add_choice` options - -`add_choice` and the two methods `add_choice_edit` and `add_choice_quit` take a lot of optional -arguments to make customization easier. Some of these options might not apply to `add_choice_edit` -or `add_choice_quit` however. - -Below are the options of `add_choice`, specify them as arguments: - -- The first positional, mandatory argument is the choice title, as we have seen. This will -influence how the choice appears in the menu. -- The second positional, mandatory argument is the command key to access to this menu. It is best -to use keyword arguments for the other arguments. -- The `aliases` keyword argument can contain a list of aliases that can be used to access to this -menu. For instance: `add_choice(..., aliases=['t'])` -- The `attr` keyword argument contains the attribute to edit when this choice is selected. It's a -string, it has to be the name, from the object (specified in the menu constructor) to reach this -attribute. For instance, a `attr` of `"key"` will try to find `obj.key` to read and write the -attribute. You can specify more complex attribute names, for instance, `attr="db.desc"` to set the -`desc` persistent attribute, or `attr="ndb.something"` so use a non-persistent data attribute on the -object. -- The `text` keyword argument is used to change the text that will be displayed when the menu choice -is selected. Menu choices provide a default text that you can change. Since this is a long text, -it's useful to use multi-line strings (see an example below). -- The `glance` keyword argument is used to specify how to display the current information while in -the menu, when the choice hasn't been opened. If you examine the previous examples, you will see -that the current (`key` or `db.desc`) was shown in the menu, next to the command key. This is -useful for seeing at a glance the current value (hence the name). Again, menu choices will provide -a default glance if you don't specify one. -- The `on_enter` keyword argument allows to add a callback to use when the menu choice is opened. -This is more advanced, but sometimes useful. -- The `on_nomatch` keyword argument is called when, once in the menu, the caller enters some text -that doesn't match any command (including the `@` command). By default, this will edit the -specified `attr`. -- The `on_leave` keyword argument allows to specify a callback used when the caller leaves the menu -choice. This can be useful for cleanup as well. - -These are a lot of possibilities, and most of the time you won't need them all. Here is a short -example using some of these arguments (again, replace the `RoomBuildingMenu` class in -`commands/building.py` with the following code to see it working): - -```python -class RoomBuildingMenu(BuildingMenu): - - """ - Building menu to edit a room. - - For the time being, we have only one choice: key, to edit the room key. - - """ - - def init(self, room): - self.add_choice("title", key="t", attr="key", glance="{obj.key}", text=""" - ------------------------------------------------------------------------------- - Editing the title of {{obj.key}}(#{{obj.id}}) - - You can change the title simply by entering it. - Use |y{back}|n to go back to the main menu. - - Current title: |c{{obj.key}}|n - """.format(back="|n or |y".join(self.keys_go_back))) - self.add_choice_edit("description", "d") -``` - -Reload your game and see it in action: - -``` -> @edit here -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [Q]uit the menu - -> t -------------------------------------------------------------------------------- - -Editing the title of A beautiful meadow(#2) - -You can change the title simply by entering it. -Use @ to go back to the main menu. - -Current title: A beautiful meadow - -> @ - -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [Q]uit the menu - -> q -Closing the building menu. -``` - -The most surprising part is no doubt the text. We use the multi-line syntax (with `"""`). -Excessive spaces will be removed from the left for each line automatically. We specify some -information between braces... sometimes using double braces. What might be a bit odd: - -- `{back}` is a direct format argument we'll use (see the `.format` specifiers). -- `{{obj...}} refers to the object being edited. We use two braces, because `.format` will remove -them. - -In `glance`, we also use `{obj.key}` to indicate we want to show the room's key. - -### Everything can be a function - -The keyword arguments of `add_choice` are often strings (type `str`). But each of these arguments -can also be a function. This allows for a lot of customization, since we define the callbacks that -will be executed to achieve such and such an operation. - -To demonstrate, we will try to add a new feature. Our building menu for rooms isn't that bad, but -it would be great to be able to edit exits too. So we can add a new menu choice below -description... but how to actually edit exits? Exits are not just an attribute to set: exits are -objects (of type `Exit` by default) which stands between two rooms (object of type `Room`). So how -can we show that? - -First let's add a couple of exits in limbo, so we have something to work with: - -``` -@tunnel n -@tunnel s -``` - -This should create two new rooms, exits leading to them from limbo and back to limbo. - -``` -> look -A beautiful meadow(#2) -This is a beautiful meadow. But so beautiful I can't describe it. -Exits: north(#4) and south(#7) -``` - -We can access room exits with the `exits` property: - -``` -> @py here.exits -[, ] -``` - -So what we need is to display this list in our building menu... and to allow to edit it would be -great. Perhaps even add new exits? - -First of all, let's write a function to display the `glance` on existing exits. Here's the code, -it's explained below: - -```python -class RoomBuildingMenu(BuildingMenu): - - """ - Building menu to edit a room. - - """ - - def init(self, room): - self.add_choice("title", key="t", attr="key", glance="{obj.key}", text=""" - ------------------------------------------------------------------------------- - Editing the title of {{obj.key}}(#{{obj.id}}) - - You can change the title simply by entering it. - Use |y{back}|n to go back to the main menu. - - Current title: |c{{obj.key}}|n - """.format(back="|n or |y".join(self.keys_go_back))) - self.add_choice_edit("description", "d") - self.add_choice("exits", "e", glance=glance_exits, attr="exits") - - -# Menu functions -def glance_exits(room): - """Show the room exits.""" - if room.exits: - glance = "" - for exit in room.exits: - glance += "\n |y{exit}|n".format(exit=exit.key) - - return glance - - return "\n |gNo exit yet|n" -``` - -When the building menu opens, it displays each choice to the caller. A choice is displayed with its -title (rendered a bit nicely to show the key as well) and the glance. In the case of the `exits` -choice, the glance is a function, so the building menu calls this function giving it the object -being edited (the room here). The function should return the text to see. - -``` -> @edit here -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [E]xits: - north - south - [Q]uit the menu - -> q -Closing the editor. -``` - -> How do I know the parameters of the function to give? - -The function you give can accept a lot of different parameters. This allows for a flexible approach -but might seem complicated at first. Basically, your function can accept any parameter, and the -building menu will send only the parameter based on their names. If your function defines an -argument named `caller` for instance (like `def func(caller):` ), then the building menu knows that -the first argument should contain the caller of the building menu. Here are the arguments, you -don't have to specify them (if you do, they need to have the same name): - -- `menu`: if your function defines an argument named `menu`, it will contain the building menu -itself. -- `choice`: if your function defines an argument named `choice`, it will contain the `Choice` object -representing this menu choice. -- `string`: if your function defines an argument named `string`, it will contain the user input to -reach this menu choice. This is not very useful, except on `nomatch` callbacks which we'll see -later. -- `obj`: if your function defines an argument named `obj`, it will contain the building menu edited -object. -- `caller`: if your function defines an argument named `caller`, it will contain the caller of the -building menu. -- Anything else: any other argument will contain the object being edited by the building menu. - -So in our case: - -```python -def glance_exits(room): -``` - -The only argument we need is `room`. It's not present in the list of possible arguments, so the -editing object of the building menu (the room, here) is given. - -> Why is it useful to get the menu or choice object? - -Most of the time, you will not need these arguments. In very rare cases, you will use them to get -specific data (like the default attribute that was set). This tutorial will not elaborate on these -possibilities. Just know that they exist. - -We should also define a text callback, so that we can enter our menu to see the room exits. We'll -see how to edit them in the next section but this is a good opportunity to show a more complete -callback. To see it in action, as usual, replace the class and functions in `commands/building.py`: - -```python -# Our building menu - -class RoomBuildingMenu(BuildingMenu): - - """ - Building menu to edit a room. - - """ - - def init(self, room): - self.add_choice("title", key="t", attr="key", glance="{obj.key}", text=""" - ------------------------------------------------------------------------------- - Editing the title of {{obj.key}}(#{{obj.id}}) - - You can change the title simply by entering it. - Use |y{back}|n to go back to the main menu. - - Current title: |c{{obj.key}}|n - """.format(back="|n or |y".join(self.keys_go_back))) - self.add_choice_edit("description", "d") - self.add_choice("exits", "e", glance=glance_exits, attr="exits", text=text_exits) - - -# Menu functions -def glance_exits(room): - """Show the room exits.""" - if room.exits: - glance = "" - for exit in room.exits: - glance += "\n |y{exit}|n".format(exit=exit.key) - - return glance - - return "\n |gNo exit yet|n" - -def text_exits(caller, room): - """Show the room exits in the choice itself.""" - text = "-" * 79 - text += "\n\nRoom exits:" - text += "\n Use |y@c|n to create a new exit." - text += "\n\nExisting exits:" - if room.exits: - for exit in room.exits: - text += "\n |y@e {exit}|n".format(exit=exit.key) - if exit.aliases.all(): - text += " (|y{aliases}|n)".format(aliases="|n, |y".join( - alias for alias in exit.aliases.all())) - if exit.destination: - text += " toward {destination}".format(destination=exit.get_display_name(caller)) - else: - text += "\n\n |gNo exit has yet been defined.|n" - - return text -``` - -Look at the second callback in particular. It takes an additional argument, the caller (remember, -the argument names are important, their order is not relevant). This is useful for displaying -destination of exits accurately. Here is a demonstration of this menu: - -``` -> @edit here -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [E]xits: - north - south - [Q]uit the menu - -> e -------------------------------------------------------------------------------- - -Room exits: - Use @c to create a new exit. - -Existing exits: - @e north (n) toward north(#4) - @e south (s) toward south(#7) - -> @ -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [E]xits: - north - south - [Q]uit the menu - -> q -Closing the building menu. -``` - -Using callbacks allows a great flexibility. We'll now see how to handle sub-menus. - -### Sub-menus for complex menus - -A menu is relatively flat: it has a root (where you see all the menu choices) and individual choices -you can go to using the menu choice keys. Once in a choice you can type some input or go back to -the root menu by entering the return command (usually `@`). - -Why shouldn't individual exits have their own menu though? Say, you edit an exit and can change its -key, description or aliases... perhaps even destination? Why ever not? It would make building much -easier! - -The building menu system offers two ways to do that. The first is nested keys: nested keys allow to -go beyond just one menu/choice, to have menus with more layers. Using them is quick but might feel -a bit counter-intuitive at first. Another option is to create a different menu class and redirect -from the first to the second. This option might require more lines but is more explicit and can be -re-used for multiple menus. Adopt one of them depending of your taste. - -#### Nested menu keys - -So far, we've only used menu keys with one letter. We can add more, of course, but menu keys in -their simple shape are just command keys. Press "e" to go to the "exits" choice. - -But menu keys can be nested. Nested keys allow to add choices with sub-menus. For instance, type -"e" to go to the "exits" choice, and then you can type "c" to open a menu to create a new exit, or -"d" to open a menu to delete an exit. The first menu would have the "e.c" key (first e, then c), -the second menu would have key as "e.d". - -That's more advanced and, if the following code doesn't sound very friendly to you, try the next -section which provides a different approach of the same problem. - -So we would like to edit exits. That is, you can type "e" to go into the choice of exits, then -enter `@e` followed by the exit name to edit it... which will open another menu. In this sub-menu -you could change the exit key or description. - -So we have a menu hierarchy similar to that: - -``` -t Change the room title -d Change the room description -e Access the room exits - [exit name] Access the exit name sub-menu - [text] Change the exit key -``` - -Or, if you prefer an example output: - -``` -> look -A beautiful meadow(#2) -This is a beautiful meadow. But so beautiful I can't describe it. -Exits: north(#4) and south(#7) - -> @edit here -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [E]xits: - north - south - [Q]uit the menu - -> e -------------------------------------------------------------------------------- - -Room exits : - Use @c to create a new exit. - -Existing exits: - @e north (n) toward north(#4) - @e south (s) toward south(#7) - -> @e north -Editing: north -Exit north: -Enter the exit key to change it, or @ to go back. - -New exit key: - -> door - -Exit door: -Enter the exit key to change it, or @ to go back. - -New exit key: - -> @ - -------------------------------------------------------------------------------- - -Room exits : - Use @c to create a new exit. - -Existing exits: - @e door (n) toward door(#4) - @e south (s) toward south(#7) - -> @ -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [E]xits: - door - south - [Q]uit the menu - -> q -Closing the building menu. -``` - -This needs a bit of code and a bit of explanation. So here we go... the code first, the -explanations next! - -```python -# ... from commands/building.py -# Our building menu - -class RoomBuildingMenu(BuildingMenu): - - """ - Building menu to edit a room. - - For the time being, we have only one choice: key, to edit the room key. - - """ - - def init(self, room): - self.add_choice("title", key="t", attr="key", glance="{obj.key}", text=""" - ------------------------------------------------------------------------------- - Editing the title of {{obj.key}}(#{{obj.id}}) - - You can change the title simply by entering it. - Use |y{back}|n to go back to the main menu. - - Current title: |c{{obj.key}}|n - """.format(back="|n or |y".join(self.keys_go_back))) - self.add_choice_edit("description", "d") - self.add_choice("exits", "e", glance=glance_exits, text=text_exits, -on_nomatch=nomatch_exits) - - # Exit sub-menu - self.add_choice("exit", "e.*", text=text_single_exit, on_nomatch=nomatch_single_exit) - - - -# Menu functions -def glance_exits(room): - """Show the room exits.""" - if room.exits: - glance = "" - for exit in room.exits: - glance += "\n |y{exit}|n".format(exit=exit.key) - - return glance - - return "\n |gNo exit yet|n" - -def text_exits(caller, room): - """Show the room exits in the choice itself.""" - text = "-" * 79 - text += "\n\nRoom exits:" - text += "\n Use |y@c|n to create a new exit." - text += "\n\nExisting exits:" - if room.exits: - for exit in room.exits: - text += "\n |y@e {exit}|n".format(exit=exit.key) - if exit.aliases.all(): - text += " (|y{aliases}|n)".format(aliases="|n, |y".join( - alias for alias in exit.aliases.all())) - if exit.destination: - text += " toward {destination}".format(destination=exit.get_display_name(caller)) - else: - text += "\n\n |gNo exit has yet been defined.|n" - - return text - -def nomatch_exits(menu, caller, room, string): - """ - The user typed something in the list of exits. Maybe an exit name? - """ - string = string[3:] - exit = caller.search(string, candidates=room.exits) - if exit is None: - return - - # Open a sub-menu, using nested keys - caller.msg("Editing: {}".format(exit.key)) - menu.move(exit) - return False - -# Exit sub-menu -def text_single_exit(menu, caller): - """Show the text to edit single exits.""" - exit = menu.keys[1] - if exit is None: - return "" - - return """ - Exit {exit}: - - Enter the exit key to change it, or |y@|n to go back. - - New exit key: - """.format(exit=exit.key) - -def nomatch_single_exit(menu, caller, room, string): - """The user entered something in the exit sub-menu. Replace the exit key.""" - # exit is the second key element: keys should contain ['e', ] - exit = menu.keys[1] - if exit is None: - caller.msg("|rCannot find the exit.|n") - menu.move(back=True) - return False - - exit.key = string - return True -``` - -> That's a lot of code! And we only handle editing the exit key! - -That's why at some point you might want to write a real sub-menu, instead of using simple nested -keys. But you might need both to build pretty menus too! - -1. The first thing new is in our menu class. After creating a `on_nomatch` callback for the exits -menu (that shouldn't be a surprised), we need to add a nested key. We give this menu a key of -`"e.*"`. That's a bit odd! "e" is our key to the exits menu, . is the separator to indicate a -nested menu, and * means anything. So basically, we create a nested menu that is contains within -the exits menu and anything. We'll see what this "anything" is in practice. -2. The `glance_exits` and `text_exits` are basically the same. -3. The `nomatch_exits` is short but interesting. It's called when we enter some text in the "exits" -menu (that is, in the list of exits). We have said that the user should enter `@e` followed by the -exit name to edit it. So in the `nomatch_exits` callbac, we check for that input. If the entered -text begins by `@e`, we try to find the exit in the room. If we do... -4. We call the `menu.move` method. That's where things get a bit complicated with nested menus: we -need to use `menu.move` to change from layer to layer. Here, we are in the choice of exits (the -exits menu, of key "e"). We need to go down one layer to edit an exit. So we call `menu.move` and -give it an exit object. The menu system remembers what position the user is based on the keys she -has entered: when the user opens the menu, there is no key. If she selects the exits choice, the -menu key being "e", the position of the user is `["e"]` (a list with the menu keys). If we call -`menu.move`, whatever we give to this method will be appended to the list of keys, so that the user -position becomes `["e", ]`. -5. In the menu class, we have defined the menu "e.*", meaning "the menu contained in the exits -choice plus anything". The "anything" here is an exit: we have called `menu.move(exit)`, so the -`"e.*"` menu choice is chosen. -6. In this menu, the text is set to a callback. There is also a `on_nomatch` callback that is -called whenever the user enters some text. If so, we change the exit name. - -Using `menu.move` like this is a bit confusing at first. Sometimes it's useful. In this case, if -we want a more complex menu for exits, it makes sense to use a real sub-menu, not nested keys like -this. But sometimes, you will find yourself in a situation where you don't need a full menu to -handle a choice. - -#### Full sub-menu as separate classes - -The best way to handle individual exits is to create two separate classes: - -- One for the room menu. -- One for the individual exit menu. - -The first one will have to redirect on the second. This might be more intuitive and flexible, -depending on what you want to achieve. So let's build two menus: - -```python -# Still in commands/building.py, replace the menu class and functions by... -# Our building menus - -class RoomBuildingMenu(BuildingMenu): - - """ - Building menu to edit a room. - """ - - def init(self, room): - self.add_choice("title", key="t", attr="key", glance="{obj.key}", text=""" - ------------------------------------------------------------------------------- - Editing the title of {{obj.key}}(#{{obj.id}}) - - You can change the title simply by entering it. - Use |y{back}|n to go back to the main menu. - - Current title: |c{{obj.key}}|n - """.format(back="|n or |y".join(self.keys_go_back))) - self.add_choice_edit("description", "d") - self.add_choice("exits", "e", glance=glance_exits, text=text_exits, -on_nomatch=nomatch_exits) - - -# Menu functions -def glance_exits(room): - """Show the room exits.""" - if room.exits: - glance = "" - for exit in room.exits: - glance += "\n |y{exit}|n".format(exit=exit.key) - - return glance - - return "\n |gNo exit yet|n" - -def text_exits(caller, room): - """Show the room exits in the choice itself.""" - text = "-" * 79 - text += "\n\nRoom exits:" - text += "\n Use |y@c|n to create a new exit." - text += "\n\nExisting exits:" - if room.exits: - for exit in room.exits: - text += "\n |y@e {exit}|n".format(exit=exit.key) - if exit.aliases.all(): - text += " (|y{aliases}|n)".format(aliases="|n, |y".join( - alias for alias in exit.aliases.all())) - if exit.destination: - text += " toward {destination}".format(destination=exit.get_display_name(caller)) - else: - text += "\n\n |gNo exit has yet been defined.|n" - - return text - -def nomatch_exits(menu, caller, room, string): - """ - The user typed something in the list of exits. Maybe an exit name? - """ - string = string[3:] - exit = caller.search(string, candidates=room.exits) - if exit is None: - return - - # Open a sub-menu, using nested keys - caller.msg("Editing: {}".format(exit.key)) - menu.open_submenu("commands.building.ExitBuildingMenu", exit, parent_keys=["e"]) - return False - -class ExitBuildingMenu(BuildingMenu): - - """ - Building menu to edit an exit. - - """ - - def init(self, exit): - self.add_choice("key", key="k", attr="key", glance="{obj.key}") - self.add_choice_edit("description", "d") -``` - -The code might be much easier to read. But before detailing it, let's see how it behaves in the -game: - -``` -> @edit here -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [E]xits: - door - south - [Q]uit the menu - -> e -------------------------------------------------------------------------------- - -Room exits: - Use @c to create a new exit. - -Existing exits: - @e door (n) toward door(#4) - @e south (s) toward south(#7) - -Editing: door - -> @e door -Building menu: door - - [K]ey: door - [D]escription: - None - -> k -------------------------------------------------------------------------------- -key for door(#4) - -You can change this value simply by entering it. - -Use @ to go back to the main menu. - -Current value: door - -> north - -------------------------------------------------------------------------------- -key for north(#4) - -You can change this value simply by entering it. - -Use @ to go back to the main menu. - -Current value: north - -> @ -Building menu: north - - [K]ey: north - [D]escription: - None - -> d -----------Line Editor [editor]---------------------------------------------------- -01| None -----------[l:01 w:001 c:0004]------------(:h for help)---------------------------- - -> :DD -Cleared 1 lines from buffer. - -> This is the northern exit. Cool huh? -01| This is the northern exit. Cool huh? - -> :wq -Building menu: north - [K]ey: north - [D]escription: - This is the northern exit. Cool huh? - -> @ -------------------------------------------------------------------------------- -Room exits: - Use @c to create a new exit. - -Existing exits: - @e north (n) toward north(#4) - @e south (s) toward south(#7) - -> @ -Building menu: A beautiful meadow - - [T]itle: A beautiful meadow - [D]escription: - This is a beautiful meadow. But so beautiful I can't describe it. - [E]xits: - north - south - [Q]uit the menu - -> q -Closing the building menu. - -> look -A beautiful meadow(#2) -This is a beautiful meadow. But so beautiful I can't describe it. -Exits: north(#4) and south(#7) -> @py here.exits[0] ->>> here.exits[0] -north -> @py here.exits[0].db.desc ->>> here.exits[0].db.desc -This is the northern exit. Cool huh? -``` - -Very simply, we created two menus and bridged them together. This needs much less callbacks. There -is only one line in the `nomatch_exits` to add: - -```python - menu.open_submenu("commands.building.ExitBuildingMenu", exit, parent_keys=["e"]) -``` - -We have to call `open_submenu` on the menu object (which opens, as its name implies, a sub menu) -with three arguments: - -- The path of the menu class to create. It's the Python class leading to the menu (notice the -dots). -- The object that will be edited by the menu. Here, it's our exit, so we give it to the sub-menu. -- The keys of the parent to open when the sub-menu closes. Basically, when we're in the root of the -sub-menu and press `@`, we'll open the parent menu, with the parent keys. So we specify `["e"]`, -since the parent menus is the "exits" choice. - -And that's it. The new class will be automatically created. As you can see, we have to create a -`on_nomatch` callback to open the sub-menu, but once opened, it automatically close whenever needed. - -### Generic menu options - -There are some options that can be set on any menu class. These options allow for greater -customization. They are class attributes (see the example below), so just set them in the class -body: - -- `keys_go_back` (default to `["@"]`): the keys to use to go back in the menu hierarchy, from choice -to root menu, from sub-menu to parent-menu. By default, only a `@` is used. You can change this -key for one menu or all of them. You can define multiple return commands if you want. -- `sep_keys` (default `"."`): this is the separator for nested keys. There is no real need to -redefine it except if you really need the dot as a key, and need nested keys in your menu. -- `joker_key` (default to `"*"`): used for nested keys to indicate "any key". Again, you shouldn't -need to change it unless you want to be able to use the @*@ in a command key, and also need nested -keys in your menu. -- `min_shortcut` (default to `1`): although we didn't see it here, one can create a menu choice -without giving it a key. If so, the menu system will try to "guess" the key. This option allows to -change the minimum length of any key for security reasons. - -To set one of them just do so in your menu class(es): - -```python -class RoomBuildingMenu(BuildingMenu): - keys_go_back = ["/"] - min_shortcut = 2 -``` - -## Conclusion - -Building menus mean to save you time and create a rich yet simple interface. But they can be -complicated to learn and require reading the source code to find out how to do such and such a -thing. This documentation, however long, is an attempt at describing this system, but chances are -you'll still have questions about it after reading it, especially if you try to push this system to -a great extent. Do not hesitate to read the documentation of this contrib, it's meant to be -exhaustive but user-friendly. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Choosing-An-SQL-Server.md.txt b/docs/0.9.5/_sources/Choosing-An-SQL-Server.md.txt deleted file mode 100644 index b0cb784826..0000000000 --- a/docs/0.9.5/_sources/Choosing-An-SQL-Server.md.txt +++ /dev/null @@ -1,249 +0,0 @@ -# Choosing An SQL Server - - -This page gives an overview of the supported SQL databases as well as instructions on install: - - - SQLite3 (default) - - PostgreSQL - - MySQL / MariaDB - -Since Evennia uses [Django](http://djangoproject.com), most of our notes are based off of what we -know from the community and their documentation. While the information below may be useful, you can -always find the most up-to-date and "correct" information at Django's [Notes about supported -Databases](http://docs.djangoproject.com/en/dev/ref/databases/#ref-databases) page. - -## SQLite3 - -[SQLite3](https://sqlite.org/) is a light weight single-file database. It is our default database -and Evennia will set this up for you automatically if you give no other options. SQLite stores the -database in a single file (`mygame/server/evennia.db3`). This means it's very easy to reset this -database - just delete (or move) that `evennia.db3` file and run `evennia migrate` again! No server -process is needed and the administrative overhead and resource consumption is tiny. It is also very -fast since it's run in-memory. For the vast majority of Evennia installs it will probably be all -that's ever needed. - -SQLite will generally be much faster than MySQL/PostgreSQL but its performance comes with two -drawbacks: - -* SQLite [ignores length constraints by design](https://www.sqlite.org/faq.html#q9); it is possible -to store very large strings and numbers in fields that technically should not accept them. This is -not something you will notice; your game will read and write them and function normally, but this -*can* create some data migration problems requiring careful thought if you do need to change -databases later. -* SQLite can scale well to storage of millions of objects, but if you end up with a thundering herd -of users trying to access your MUD and web site at the same time, or you find yourself writing long- -running functions to update large numbers of objects on a live game, either will yield errors and -interference. SQLite does not work reliably with multiple concurrent threads or processes accessing -its records. This has to do with file-locking clashes of the database file. So for a production -server making heavy use of process- or thread pools (or when using a third-party webserver like -Apache), a proper database is a more appropriate choice. - -### Install of SQlite3 - -This is installed and configured as part of Evennia. The database file is created as -`mygame/server/evennia.db3` when you run - - evennia migrate - -without changing any database options. An optional requirement is the `sqlite3` client program - -this is required if you want to inspect the database data manually. A shortcut for using it with the -evennia database is `evennia dbshell`. Linux users should look for the `sqlite3` package for their -distro while Mac/Windows should get the [sqlite-tools package from this -page](https://sqlite.org/download.html). - -To inspect the default Evennia database (once it's been created), go to your game dir and do - -```bash - sqlite3 server/evennia.db3 - # or - evennia dbshell -``` - -This will bring you into the sqlite command line. Use `.help` for instructions and `.quit` to exit. -See [here](https://gist.github.com/vincent178/10889334) for a cheat-sheet of commands. - -## PostgreSQL - -[PostgreSQL](https://www.postgresql.org/) is an open-source database engine, recommended by Django. -While not as fast as SQLite for normal usage, it will scale better than SQLite, especially if your -game has an very large database and/or extensive web presence through a separate server process. - -### Install and initial setup of PostgreSQL - -First, install the posgresql server. Version `9.6` is tested with Evennia. Packages are readily -available for all distributions. You need to also get the `psql` client (this is called `postgresql- -client` on debian-derived systems). Windows/Mac users can [find what they need on the postgresql -download page](https://www.postgresql.org/download/). You should be setting up a password for your -database-superuser (always called `postgres`) when you install. - -For interaction with Evennia you need to also install `psycopg2` to your Evennia install (`pip -install psycopg2-binary` in your virtualenv). This acts as the python bridge to the database server. - -Next, start the postgres client: - -```bash - psql -U postgres --password -``` -> :warning: **Warning:** With the `--password` argument, Postgres should prompt you for a password. -If it won't, replace that with `-p yourpassword` instead. Do not use the `-p` argument unless you -have to since the resulting command, and your password, will be logged in the shell history. - -This will open a console to the postgres service using the psql client. - -On the psql command line: - -```sql -CREATE USER evennia WITH PASSWORD 'somepassword'; -CREATE DATABASE evennia; - --- Postgres-specific optimizations --- https://docs.djangoproject.com/en/dev/ref/databases/#optimizing-postgresql-s-configuration -ALTER ROLE evennia SET client_encoding TO 'utf8'; -ALTER ROLE evennia SET default_transaction_isolation TO 'read committed'; -ALTER ROLE evennia SET timezone TO 'UTC'; - -GRANT ALL PRIVILEGES ON DATABASE evennia TO evennia; --- Other useful commands: --- \l (list all databases and permissions) --- \q (exit) - -``` -[Here](https://gist.github.com/Kartones/dd3ff5ec5ea238d4c546) is a cheat-sheet for psql commands. - -We create a database user 'evennia' and a new database named `evennia` (you can call them whatever -you want though). We then grant the 'evennia' user full privileges to the new database so it can -read/write etc to it. -If you in the future wanted to completely wipe the database, an easy way to do is to log in as the -`postgres` superuser again, then do `DROP DATABASE evennia;`, then `CREATE` and `GRANT` steps above -again to recreate the database and grant privileges. - -### Evennia PostgreSQL configuration - -Edit `mygame/server/conf/secret_settings.py and add the following section: - -```python -# -# PostgreSQL Database Configuration -# -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'evennia', - 'USER': 'evennia', - 'PASSWORD': 'somepassword', - 'HOST': 'localhost', - 'PORT': '' # use default - }} -``` - -If you used some other name for the database and user, enter those instead. Run - - evennia migrate - -to populate your database. Should you ever want to inspect the database directly you can from now on -also use - - evennia dbshell - -as a shortcut to get into the postgres command line for the right database and user. - -With the database setup you should now be able to start start Evennia normally with your new -database. - -## MySQL / MariaDB - -[MySQL](https://www.mysql.com/) is a commonly used proprietary database system, on par with -PostgreSQL. There is an open-source alternative called [MariaDB](https://mariadb.org/) that mimics -all functionality and command syntax of the former. So this section covers both. - -### Installing and initial setup of MySQL/MariaDB - -First, install and setup MariaDB or MySQL for your specific server. Linux users should look for the -`mysql-server` or `mariadb-server` packages for their respective distributions. Windows/Mac users -will find what they need from the [MySQL downloads](https://www.mysql.com/downloads/) or [MariaDB -downloads](https://mariadb.org/download/) pages. You also need the respective database clients -(`mysql`, `mariadb-client`), so you can setup the database itself. When you install the server you -should usually be asked to set up the database root user and password. - -You will finally also need a Python interface to allow Evennia to talk to the database. Django -recommends the `mysqlclient` one. Install this into the evennia virtualenv with `pip install -mysqlclient`. - -Start the database client (this is named the same for both mysql and mariadb): - -```bash -mysql -u root -p -``` - -You should get to enter your database root password (set this up when you installed the database -server). - -Inside the database client interface: - -```sql -CREATE USER 'evennia'@'localhost' IDENTIFIED BY 'somepassword'; -CREATE DATABASE evennia; -ALTER DATABASE `evennia` CHARACTER SET utf8; -- note that it's `evennia` with back-ticks, not -quotes! -GRANT ALL PRIVILEGES ON evennia.* TO 'evennia'@'localhost'; -FLUSH PRIVILEGES; --- use 'exit' to quit client -``` -[Here](https://gist.github.com/hofmannsven/9164408) is a mysql command cheat sheet. - -Above we created a new local user and database (we called both 'evennia' here, you can name them -what you prefer). We set the character set to `utf8` to avoid an issue with prefix character length -that can pop up on some installs otherwise. Next we grant the 'evennia' user all privileges on the -`evennia` database and make sure the privileges are applied. Exiting the client brings us back to -the normal terminal/console. - -> Note: If you are not using MySQL for anything else you might consider granting the 'evennia' user -full privileges with `GRANT ALL PRIVILEGES ON *.* TO 'evennia'@'localhost';`. If you do, it means -you can use `evennia dbshell` later to connect to mysql, drop your database and re-create it as a -way of easy reset. Without this extra privilege you will be able to drop the database but not re- -create it without first switching to the database-root user. - -## Add MySQL configuration to Evennia - -To tell Evennia to use your new database you need to edit `mygame/server/conf/settings.py` (or -`secret_settings.py` if you don't want your db info passed around on git repositories). - -> Note: The Django documentation suggests using an external `db.cnf` or other external conf- -formatted file. Evennia users have however found that this leads to problems (see e.g. [issue -#1184](https://git.io/vQdiN)). To avoid trouble we recommend you simply put the configuration in -your settings as below. - -```python - # - # MySQL Database Configuration - # - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'evennia', - 'USER': 'evennia', - 'PASSWORD': 'somepassword', - 'HOST': 'localhost', # or an IP Address that your DB is hosted on - 'PORT': '', # use default port - } - } -``` -Change this to fit your database setup. Next, run: - - evennia migrate - -to populate your database. Should you ever want to inspect the database directly you can from now on -also use - - evennia dbshell - -as a shortcut to get into the postgres command line for the right database and user. - -With the database setup you should now be able to start start Evennia normally with your new -database. - -## Others - -No testing has been performed with Oracle, but it is also supported through Django. There are -community maintained drivers for [MS SQL](http://code.google.com/p/django-mssql/) and possibly a few -others. If you try other databases out, consider expanding this page with instructions. diff --git a/docs/0.9.5/_sources/Client-Support-Grid.md.txt b/docs/0.9.5/_sources/Client-Support-Grid.md.txt deleted file mode 100644 index 1acd9e257c..0000000000 --- a/docs/0.9.5/_sources/Client-Support-Grid.md.txt +++ /dev/null @@ -1,84 +0,0 @@ -# Client Support Grid - -This grid tries to gather info about different MU clients when used with Evennia. -If you want to report a problem, update an entry or add a client, make a -new [documentation issue](github:issue) for it. Everyone's encouraged to report their findings. - -## Client Grid - -Legend: - - - **Name**: The name of the client. Also note if it's OS-specific. - - **Version**: Which version or range of client versions were tested. - - **Comments**: Any quirks on using this client with Evennia should be added here. - - -| Name | Version tested | Comments | -| --- | --- | --- | -| [Evennia Webclient][1] | 1.0+ | Evennia-specific | -| [tintin++][2] | 2.0+ | No MXP support | -| [tinyfugue][3] | 5.0+ | No UTF-8 support | -| [MUSHclient][4] (Win) | 4.94 | NAWS reports full text area | -| [Zmud][5] (Win) | 7.21 | *UNTESTED* | -| [Cmud][6] (Win) | v3 | *UNTESTED* | -| [Potato][7]_ | 2.0.0b16 | No MXP, MCCP support. Win 32bit does not understand | -| | | "localhost", must use `127.0.0.1`. | -| [Mudlet][8] | 3.4+ | No known issues. Some older versions showed <> as html | -| | | under MXP. | -| [SimpleMU][9] (Win) | full | Discontinued. NAWS reports pixel size. | -| [Atlantis][10] (Mac) | 0.9.9.4 | No known issues. | -| [GMUD][11] | 0.0.1 | Can't handle any telnet handshakes. Not recommended. | -| [BeipMU][12] (Win) | 3.0.255 | No MXP support. Best to enable "MUD prompt handling", disable | -| | | "Handle HTML tags". | -| [MudRammer][13] (IOS) | 1.8.7 | Bad Telnet Protocol compliance: displays spurious characters. | -| [MUDMaster][14] | 1.3.1 | *UNTESTED* | -| [BlowTorch][15] (Andr) | 1.1.3 | Telnet NOP displays as spurious character. | -| [Mukluk][16] (Andr) | 2015.11.20| Telnet NOP displays as spurious character. Has UTF-8/Emoji | -| | | support. | -| [Gnome-MUD][17] (Unix) | 0.11.2 | Telnet handshake errors. First (only) attempt at logging in | -| | | fails. | -| [Spyrit][18] | 0.4 | No MXP, OOB support. | -| [JamochaMUD][19] | 5.2 | Does not support ANSI within MXP text. | -| [DuckClient][20] (Chrome) | 4.2 | No MXP support. Displays Telnet Go-Ahead and | -| | | WILL SUPPRESS-GO-AHEAD as ù character. Also seems to run | -| | | the `version` command on connection, which will not work in | -| | | `MULTISESSION_MODES` above 1. | -| [KildClient][21] | 2.11.1 | No known issues. | - - -[1]: ./Webclient -[2]: http://tintin.sourceforge.net/ -[3]: http://tinyfugue.sourceforge.net/ -[4]: https://mushclient.com/ -[5]: http://forums.zuggsoft.com/index.php?page=4&action=file&file_id=65 -[6]: http://forums.zuggsoft.com/index.php?page=4&action=category&cat_id=11 -[7]: https://www.potatomushclient.com/ -[8]: https://www.mudlet.org/ -[9]: https://archive.org/details/tucows_196173_SimpleMU_MU_Client -[10]: https://www.riverdark.net/atlantis/ -[11]: https://sourceforge.net/projects/g-mud/ -[12]: http://www.beipmu.com/ -[13]: https://itunes.apple.com/us/app/mudrammer-a-modern-mud-client/id597157072 -[14]: https://itunes.apple.com/us/app/mudmaster/id341160033 -[15]: https://bt.happygoatstudios.com/ -[16]: https://play.google.com/store/apps/details?id=com.crap.mukluk -[17]: https://github.com/GNOME/gnome-mud -[18]: https://spyrit.ierne.eu.org/ -[19]: https://jamochamud.org/ -[20]: http://duckclient.com/ -[21]: https://www.kildclient.org/ - -## Workarounds for client issues: - -### Issue: Telnet NOP displays as spurious character. - -Known clients: - -* BlowTorch (Andr) -* Mukluk (Andr) - -Workaround: - -* In-game: Use `@option NOPKEEPALIVE=off` for the session, or use the `/save` -parameter to disable it for that Evennia account permanently. -* Client-side: Set a gag-type trigger on the NOP character to make it invisible to the client. diff --git a/docs/0.9.5/_sources/Coding-FAQ.md.txt b/docs/0.9.5/_sources/Coding-FAQ.md.txt deleted file mode 100644 index 8b61ca1a18..0000000000 --- a/docs/0.9.5/_sources/Coding-FAQ.md.txt +++ /dev/null @@ -1,389 +0,0 @@ -# Coding FAQ - -*This FAQ page is for users to share their solutions to coding problems. Keep it brief and link to -the docs if you can rather than too lengthy explanations. Don't forget to check if an answer already -exists before answering - maybe you can clarify that answer rather than to make a new Q&A section.* - - -## Table of Contents - -- [Will I run out of dbrefs?](./Coding-FAQ.md#will-i-run-out-of-dbrefs) -- [Removing default commands](./Coding-FAQ.md#removing-default-commands) -- [Preventing character from moving based on a condition](./Coding-FAQ.md#preventing-character-from- -moving-based-on-a-condition) -- [Reference initiating object in an EvMenu command](./Coding-FAQ.md#reference-initiating-object-in-an- -evmenu-command) -- [Adding color to default Evennia Channels](./Coding-FAQ.md#adding-color-to-default-evennia-channels) -- [Selectively turn off commands in a room](./Coding-FAQ.md#selectively-turn-off-commands-in-a-room) -- [Select Command based on a condition](./Coding-FAQ.md#select-command-based-on-a-condition) -- [Automatically updating code when reloading](./Coding-FAQ.md#automatically-updating-code-when- -reloading) -- [Changing all exit messages](./Coding-FAQ.md#changing-all-exit-messages) -- [Add parsing with the "to" delimiter](./Coding-FAQ.md#add-parsing-with-the-to-delimiter) -- [Store last used session IP address](./Coding-FAQ.md#store-last-used-session-ip-address) -- [Use wide characters with EvTable](./Coding-FAQ.md#non-latin-characters-in-evtable) - -## Will I run out of dbrefs? -**Q:** The `#dbref` of a database object is ever-increasing. Evennia doesn't allow you to change or -reuse them. Will not a big/old game run out of dbref integers eventually? - -**A:** No. For example, the default sqlite3 database's max dbref is `2**64`. If you created `10 000` -objects every second every minute and every day of the year it would take ~60 million years for you -to run out of dbref numbers. That's a database of 140 TeraBytes, if every row was empty. If you are -still using Evennia at that point and has this concern, get back to us and we can discuss adding -dbref reuse then. - -## Removing default commands -**Q:** How does one *remove* (not replace) e.g. the default `get` [Command](./Commands.md) from the -Character [Command Set](./Command-Sets.md)? - -**A:** Go to `mygame/commands/default_cmdsets.py`. Find the `CharacterCmdSet` class. It has one -method named `at_cmdset_creation`. At the end of that method, add the following line: -`self.remove(default_cmds.CmdGet())`. See the [Adding Commands Tutorial](./Adding-Command-Tutorial.md) -for more info. - -## Preventing character from moving based on a condition -**Q:** How does one keep a character from using any exit, if they meet a certain condition? (I.E. in -combat, immobilized, etc.) - -**A:** The `at_before_move` hook is called by Evennia just before performing any move. If it returns -`False`, the move is aborted. Let's say we want to check for an [Attribute](./Attributes.md) `cantmove`. -Add the following code to the `Character` class: - -```python -def at_before_move(self, destination): - "Called just before trying to move" - if self.db.cantmove: # replace with condition you want to test - self.msg("Something is preventing you from moving!") - return False - return True -``` - -## Reference initiating object in an EvMenu command. -**Q:** An object has a Command on it starts up an EvMenu instance. How do I capture a reference to -that object for use in the menu? - -**A:** When an [EvMenu](./EvMenu.md) is started, the menu object is stored as `caller.ndb._menutree`. -This is a good place to store menu-specific things since it will clean itself up when the menu -closes. When initiating the menu, any additional keywords you give will be available for you as -properties on this menu object: - -```python -class MyObjectCommand(Command): - # A Command stored on an object (the object is always accessible from - # the Command as self.obj) - def func(self): - # add the object as the stored_obj menu property - EvMenu(caller, ..., stored_obj=self.obj) - -``` - -Inside the menu you can now access the object through `caller.ndb._menutree.stored_obj`. - - -## Adding color to default Evennia Channels -**Q:** How do I add colors to the names of Evennia channels? - -**A:** The Channel typeclass' `channel_prefix` method decides what is shown at the beginning of a -channel send. Edit `mygame/typeclasses/channels.py` (and then `@reload`): - -```python -# define our custom color names -CHANNEL_COLORS = {'public': '|015Public|n', - 'newbie': '|550N|n|551e|n|552w|n|553b|n|554i|n|555e|n', - 'staff': '|010S|n|020t|n|030a|n|040f|n|050f|n'} - -# Add to the Channel class - # ... - def channel_prefix(self, msg, emit=False): - prefix_string = "" - if self.key in COLORS: - prefix_string = "[%s] " % CHANNEL_COLORS.get(self.key.lower()) - else: - prefix_string = "[%s] " % self.key.capitalize() - return prefix_string -``` -Additional hint: To make colors easier to change from one place you could instead put the -`CHANNEL_COLORS` dict in your settings file and import it as `from django.conf.settings import -CHANNEL_COLORS`. - - -## Selectively turn off commands in a room -**Q:** I want certain commands to turn off in a given room. They should still work normally for -staff. - -**A:** This is done using a custom cmdset on a room [locked with the 'call' lock type](./Locks.md). Only -if this lock is passed will the commands on the room be made available to an object inside it. Here -is an example of a room where certain commands are disabled for non-staff: - -```python -# in mygame/typeclasses/rooms.py - -from evennia import default_commands, CmdSet - -class CmdBlocking(default_commands.MuxCommand): - # block commands give, get, inventory and drop - key = "give" - aliases = ["get", "inventory", "drop"] - def func(self): - self.caller.msg("You cannot do that in this room.") - -class BlockingCmdSet(CmdSet): - key = "blocking_cmdset" - # default commands have prio 0 - priority = 1 - def at_cmdset_creation(self): - self.add(CmdBlocking()) - -class BlockingRoom(Room): - def at_object_creation(self): - self.cmdset.add(BlockingCmdSet, permanent=True) - # only share commands with players in the room that - # are NOT Builders or higher - self.locks.add("call:not perm(Builders)") -``` -After `@reload`, make some `BlockingRooms` (or switch a room to it with `@typeclass`). Entering one -will now replace the given commands for anyone that does not have the `Builders` or higher -permission. Note that the 'call' lock is special in that even the superuser will be affected by it -(otherwise superusers would always see other player's cmdsets and a game would be unplayable for -superusers). - -## Select Command based on a condition -**Q:** I want a command to be available only based on a condition. For example I want the "werewolf" -command to only be available on a full moon, from midnight to three in-game time. - -**A:** This is easiest accomplished by putting the "werewolf" command on the Character as normal, -but to [lock](./Locks.md) it with the "cmd" type lock. Only if the "cmd" lock type is passed will the -command be available. - -```python -# in mygame/commands/command.py - -from evennia import Command - -class CmdWerewolf(Command): - key = "werewolf" - # lock full moon, between 00:00 (midnight) and 03:00. - locks = "cmd:is_full_moon(0, 3)" - def func(self): - # ... -``` -Add this to the [default cmdset as usual](./Adding-Command-Tutorial.md). The `is_full_moon` [lock -function](./Locks.md#lock-functions) does not yet exist. We must create that: - -```python -# in mygame/server/conf/lockfuncs.py - -def is_full_moon(accessing_obj, accessed_obj, - starthour, endhour, *args, **kwargs): - # calculate if the moon is full here and - # if current game time is between starthour and endhour - # return True or False - -``` -After a `@reload`, the `werewolf` command will be available only at the right time, that is when the -`is_full_moon` lock function returns True. - -## Automatically updating code when reloading -**Q:** I have a development server running Evennia. Can I have the server update its code-base when -I reload? - -**A:** Having a development server that pulls updated code whenever you reload it can be really -useful if you have limited shell access to your server, or want to have it done automatically. If -you have your project in a configured Git environment, it's a matter of automatically calling `git -pull` when you reload. And that's pretty straightforward: - -In `/server/conf/at_server_startstop.py`: - -```python -import subprocess - -# ... other hooks ... - -def at_server_reload_stop(): - """ - This is called only time the server stops before a reload. - """ - print("Pulling from the game repository...") - process = subprocess.call(["git", "pull"], shell=False) -``` - -That's all. We call `subprocess` to execute a shell command (that code works on Windows and Linux, -assuming the current directory is your game directory, which is probably the case when you run -Evennia). `call` waits for the process to complete, because otherwise, Evennia would reload on -partially-modified code, which would be problematic. - -Now, when you enter `@reload` on your development server, the game repository is updated from the -configured remote repository (Github, for instance). Your development cycle could resemble -something like: - -1. Coding on the local machine. -2. Testing modifications. -3. Committing once, twice or more (being sure the code is still working, unittests are pretty useful -here). -4. When the time comes, login to the development server and run `@reload`. - -The reloading might take one or two additional seconds, since Evennia will pull from your remote Git -repository. But it will reload on it and you will have your modifications ready, without needing -connecting to your server using SSH or something similar. - -## Changing all exit messages -**Q:** How can I change the default exit messages to something like "XXX leaves east" or "XXX -arrives from the west"? - -**A:** the default exit messages are stored in two hooks, namely `announce_move_from` and -`announce_move_to`, on the `Character` typeclass (if what you want to change is the message other -characters will see when a character exits). - -These two hooks provide some useful features to easily update the message to be displayed. They -take both the default message and mapping as argument. You can easily call the parent hook with -these information: - -* The message represents the string of characters sent to characters in the room when a character -leaves. -* The mapping is a dictionary containing additional mappings (you will probably not need it for -simple customization). - -It is advisable to look in the [code of both -hooks](https://github.com/evennia/evennia/tree/master/evennia/objects/objects.py), and read the -hooks' documentation. The explanations on how to quickly update the message are shown below: - -```python -# In typeclasses/characters.py -""" -Characters - -""" -from evennia import DefaultCharacter - -class Character(DefaultCharacter): - """ - The default character class. - - ... - """ - - def announce_move_from(self, destination, msg=None, mapping=None): - """ - Called if the move is to be announced. This is - called while we are still standing in the old - location. - - Args: - destination (Object): The place we are going to. - msg (str, optional): a replacement message. - mapping (dict, optional): additional mapping objects. - - You can override this method and call its parent with a - message to simply change the default message. In the string, - you can use the following as mappings (between braces): - object: the object which is moving. - exit: the exit from which the object is moving (if found). - origin: the location of the object before the move. - destination: the location of the object after moving. - - """ - super().announce_move_from(destination, msg="{object} leaves {exit}.") - - def announce_move_to(self, source_location, msg=None, mapping=None): - """ - Called after the move if the move was not quiet. At this point - we are standing in the new location. - - Args: - source_location (Object): The place we came from - msg (str, optional): the replacement message if location. - mapping (dict, optional): additional mapping objects. - - You can override this method and call its parent with a - message to simply change the default message. In the string, - you can use the following as mappings (between braces): - object: the object which is moving. - exit: the exit from which the object is moving (if found). - origin: the location of the object before the move. - destination: the location of the object after moving. - - """ - super().announce_move_to(source_location, msg="{object} arrives from the {exit}.") -``` - -We override both hooks, but call the parent hook to display a different message. If you read the -provided docstrings, you will better understand why and how we use mappings (information between -braces). You can provide additional mappings as well, if you want to set a verb to move, for -instance, or other, extra information. - -## Add parsing with the "to" delimiter - -**Q:** How do I change commands to undestand say `give obj to target` as well as the default `give -obj = target`? - -**A:** You can make change the default `MuxCommand` parent with your own class making a small change -in its `parse` method: - -```python - # in mygame/commands/command.py - from evennia import default_cmds - class MuxCommand(default_cmds.MuxCommand): - def parse(self): - """Implement an additional parsing of 'to'""" - super().parse() - if " to " in self.args: - self.lhs, self.rhs = self.args.split(" to ", 1) -``` -Next you change the parent of the default commands in settings: - -```python - COMMAND_DEFAULT_CLASS = "commands.command.MuxCommand" -``` - -Do a `@reload` and all default commands will now use your new tweaked parent class. A copy of the -MuxCommand class is also found commented-out in the `mygame/commands/command.py` file. - -## Store last used session IP address - -**Q:** If a user has already logged out of an Evennia account, their IP is no longer visible to -staff that wants to ban-by-ip (instead of the user) with `@ban/ip`? - -**A:** One approach is to write the IP from the last session onto the "account" account object. - -`typeclasses/accounts.py` -```python - def at_post_login(self, session=None, **kwargs): - super().at_post_login(session=session, **kwargs) - self.db.lastsite = self.sessions.all()[-1].address -``` -Adding timestamp for login time and appending to a list to keep the last N login IP addresses and -timestamps is possible, also. Additionally, if you don't want the list to grow beyond a -`do_not_exceed` length, conditionally pop a value after you've added it, if the length has grown too -long. - -**NOTE:** You'll need to add `import time` to generate the login timestamp. -```python - def at_post_login(self, session=None, **kwargs): - super().at_post_login(session=session, **kwargs) - do_not_exceed = 24 # Keep the last two dozen entries - session = self.sessions.all()[-1] # Most recent session - if not self.db.lastsite: - self.db.lastsite = [] - self.db.lastsite.insert(0, (session.address, int(time.time()))) - if len(self.db.lastsite) > do_not_exceed: - self.db.lastsite.pop() -``` -This only stores the data. You may want to interface the `@ban` command or make a menu-driven viewer -for staff to browse the list and display how long ago the login occurred. - -## Non-latin characters in EvTable - -**Q:** When using e.g. Chinese characters in EvTable, some lines appear to be too wide, for example -``` -+------+------+ -| | | -| 测试 | 测试 | -| | | -+~~~~~~+~~~~~~+ -``` -**A:** The reason for this is because certain non-latin characters are *visually* much wider than -their len() suggests. There is little Evennia can (reliably) do about this. If you are using such -characters, you need to make sure to use a suitable mono-spaced font where are width are equal. You -can set this in your web client and need to recommend it for telnet-client users. See [this -discussion](https://github.com/evennia/evennia/issues/1522) where some suitable fonts are suggested. diff --git a/docs/0.9.5/_sources/Coding-Introduction.md.txt b/docs/0.9.5/_sources/Coding-Introduction.md.txt deleted file mode 100644 index 88232b591f..0000000000 --- a/docs/0.9.5/_sources/Coding-Introduction.md.txt +++ /dev/null @@ -1,99 +0,0 @@ -# Coding Introduction - - -Evennia allows for a lot of freedom when designing your game - but to code efficiently you still -need to adopt some best practices as well as find a good place to start to learn. - -Here are some pointers to get you going. - -### Python - -Evennia is developed using Python. Even if you are more of a designer than a coder, it is wise to -learn how to read and understand basic Python code. If you are new to Python, or need a refresher, -take a look at our two-part [Python introduction](./Python-basic-introduction.md). - -### Explore Evennia interactively - -When new to Evennia it can be hard to find things or figure out what is available. Evennia offers a -special interactive python shell that allows you to experiment and try out things. It's recommended -to use [ipython](http://ipython.org/) for this since the vanilla python prompt is very limited. Here -are some simple commands to get started: - - # [open a new console/terminal] - # [activate your evennia virtualenv in this console/terminal] - pip install ipython # [only needed the first time] - cd mygame - evennia shell - -This will open an Evennia-aware python shell (using ipython). From within this shell, try - - import evennia - evennia. - -That is, enter `evennia.` and press the `` key. This will show you all the resources made -available at the top level of Evennia's "flat API". See the [flat API](./Evennia-API.md) page for more -info on how to explore it efficiently. - -You can complement your exploration by peeking at the sections of the much more detailed -[Developer Central](./Developer-Central.md). The [Tutorials](./Tutorials.md) section also contains a growing collection -of system- or implementation-specific help. - -### Use a python syntax checker - -Evennia works by importing your own modules and running them as part of the server. Whereas Evennia -should just gracefully tell you what errors it finds, it can nevertheless be a good idea for you to -check your code for simple syntax errors *before* you load it into the running server. There are -many python syntax checkers out there. A fast and easy one is -[pyflakes](https://pypi.python.org/pypi/pyflakes), a more verbose one is -[pylint](http://www.pylint.org/). You can also check so that your code looks up to snuff using -[pep8](https://pypi.python.org/pypi/pep8). Even with a syntax checker you will not be able to catch -every possible problem - some bugs or problems will only appear when you actually run the code. But -using such a checker can be a good start to weed out the simple problems. - -### Plan before you code - -Before you start coding away at your dream game, take a look at our [Game Planning](./Game-Planning.md) -page. It might hopefully help you avoid some common pitfalls and time sinks. - -### Code in your game folder, not in the evennia/ repository - -As part of the Evennia setup you will create a game folder to host your game code. This is your -home. You should *never* need to modify anything in the `evennia` library (anything you download -from us, really). You import useful functionality from here and if you see code you like, copy&paste -it out into your game folder and edit it there. - -If you find that Evennia doesn't support some functionality you need, make a -[Feature Request](github:issue) about it. Same goes for [bugs](github:issue). If you add features or fix bugs -yourself, please consider [Contributing](./Contributing.md) your changes upstream! - -### Learn to read tracebacks - -Python is very good at reporting when and where things go wrong. A *traceback* shows everything you -need to know about crashing code. The text can be pretty long, but you usually are only interested -in the last bit, where it says what the error is and at which module and line number it happened - -armed with this info you can resolve most problems. - -Evennia will usually not show the full traceback in-game though. Instead the server outputs errors -to the terminal/console from which you started Evennia in the first place. If you want more to show -in-game you can add `IN_GAME_ERRORS = True` to your settings file. This will echo most (but not all) -tracebacks both in-game as well as to the terminal/console. This is a potential security problem -though, so don't keep this active when your game goes into production. - -> A common confusing error is finding that objects in-game are suddenly of the type `DefaultObject` -rather than your custom typeclass. This happens when you introduce a critical Syntax error to the -module holding your custom class. Since such a module is not valid Python, Evennia can't load it at -all. Instead of crashing, Evennia will then print the full traceback to the terminal/console and -temporarily fall back to the safe `DefaultObject` until you fix the problem and reload. - -### Docs are here to help you - -Some people find reading documentation extremely dull and shun it out of principle. That's your -call, but reading docs really *does* help you, promise! Evennia's documentation is pretty thorough -and knowing what is possible can often give you a lot of new cool game ideas. That said, if you -can't find the answer in the docs, don't be shy to ask questions! The -[discussion group](https://sites.google.com/site/evenniaserver/discussions) and the -[irc chat](http://webchat.freenode.net/?channels=evennia) are also there for you. - -### The most important point - -And finally, of course, have fun! \ No newline at end of file diff --git a/docs/0.9.5/_sources/Coding-Utils.md.txt b/docs/0.9.5/_sources/Coding-Utils.md.txt deleted file mode 100644 index e4f1862dbd..0000000000 --- a/docs/0.9.5/_sources/Coding-Utils.md.txt +++ /dev/null @@ -1,297 +0,0 @@ -# Coding Utils - - -Evennia comes with many utilities to help with common coding tasks. Most are accessible directly -from the flat API, otherwise you can find them in the `evennia/utils/` folder. - -## Searching - -A common thing to do is to search for objects. There it's easiest to use the `search` method defined -on all objects. This will search for objects in the same location and inside the self object: - -```python - obj = self.search(objname) -``` - -The most common time one needs to do this is inside a command body. `obj = -self.caller.search(objname)` will search inside the caller's (typically, the character that typed -the command) `.contents` (their "inventory") and `.location` (their "room"). - -Give the keyword `global_search=True` to extend search to encompass entire database. Aliases will -also be matched by this search. You will find multiple examples of this functionality in the default -command set. - -If you need to search for objects in a code module you can use the functions in -`evennia.utils.search`. You can access these as shortcuts `evennia.search_*`. - -```python - from evennia import search_object - obj = search_object(objname) -``` - -- [evennia.search_account](evennia.accounts.manager.AccountDBManager.search_account) -- [evennia.search_object](evennia.objects.manager.ObjectDBManager.search_object) -- [evennia.search_tag](evennia.utils.search.search_tag) -- [evennia.search_script](evennia.scripts.manager.ScriptDBManager.search_script) -- [evennia.search_channel](evennia.comms.managers.ChannelDBManager.search_channel) -- [evennia.search_message](evennia.comms.managers.MsgManager.search_message) -- [evennia.search_help](evennia.utils.search.search_help_entry) - -Note that these latter methods will always return a `list` of results, even if the list has one or -zero entries. - -## Create - -Apart from the in-game build commands (`@create` etc), you can also build all of Evennia's game -entities directly in code (for example when defining new create commands). -```python - import evennia - - myobj = evennia.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj") -``` - -- [evennia.create_account](create_account) -- [evennia.create_object](create_object) -- [evennia.create_script](create_script) -- [evennia.create_channel](create_channel) -- [evennia.create_help_entry](create_help_entry) -- [evennia.create_message](create_message) - -Each of these create-functions have a host of arguments to further customize the created entity. See -`evennia/utils/create.py` for more information. - -## Logging - -Normally you can use Python `print` statements to see output to the terminal/log. The `print` -statement should only be used for debugging though. For producion output, use the `logger` which -will create proper logs either to terminal or to file. - -```python - from evennia import logger - # - logger.log_err("This is an Error!") - logger.log_warn("This is a Warning!") - logger.log_info("This is normal information") - logger.log_dep("This feature is deprecated") -``` - -There is a special log-message type, `log_trace()` that is intended to be called from inside a -traceback - this can be very useful for relaying the traceback message back to log without having it -kill the server. - -```python - try: - # [some code that may fail...] - except Exception: - logger.log_trace("This text will show beneath the traceback itself.") -``` - -The `log_file` logger, finally, is a very useful logger for outputting arbitrary log messages. This -is a heavily optimized asynchronous log mechanism using -[threads](https://en.wikipedia.org/wiki/Thread_%28computing%29) to avoid overhead. You should be -able to use it for very heavy custom logging without fearing disk-write delays. - -```python - logger.log_file(message, filename="mylog.log") -``` - -If not an absolute path is given, the log file will appear in the `mygame/server/logs/` directory. -If the file already exists, it will be appended to. Timestamps on the same format as the normal -Evennia logs will be automatically added to each entry. If a filename is not specified, output will -be written to a file `game/logs/game.log`. - -## Time Utilities -### Game time - -Evennia tracks the current server time. You can access this time via the `evennia.gametime` -shortcut: - -```python -from evennia import gametime - -# all the functions below return times in seconds). - -# total running time of the server -runtime = gametime.runtime() -# time since latest hard reboot (not including reloads) -uptime = gametime.uptime() -# server epoch (its start time) -server_epoch = gametime.server_epoch() - -# in-game epoch (this can be set by `settings.TIME_GAME_EPOCH`. -# If not, the server epoch is used. -game_epoch = gametime.game_epoch() -# in-game time passed since time started running -gametime = gametime.gametime() -# in-game time plus game epoch (i.e. the current in-game -# time stamp) -gametime = gametime.gametime(absolute=True) -# reset the game time (back to game epoch) -gametime.reset_gametime() - -``` - -The setting `TIME_FACTOR` determines how fast/slow in-game time runs compared to the real world. The -setting `TIME_GAME_EPOCH` sets the starting game epoch (in seconds). The functions from the -`gametime` module all return their times in seconds. You can convert this to whatever units of time -you desire for your game. You can use the `@time` command to view the server time info. - -You can also *schedule* things to happen at specific in-game times using the -[gametime.schedule](github:evennia.utils.gametime#schedule) function: - -```python -import evennia - -def church_clock: - limbo = evennia.search_object(key="Limbo") - limbo.msg_contents("The church clock chimes two.") - -gametime.schedule(church_clock, hour=2) -``` - -### utils.time_format() - -This function takes a number of seconds as input (e.g. from the `gametime` module above) and -converts it to a nice text output in days, hours etc. It's useful when you want to show how old -something is. It converts to four different styles of output using the *style* keyword: - -- style 0 - `5d:45m:12s` (standard colon output) -- style 1 - `5d` (shows only the longest time unit) -- style 2 - `5 days, 45 minutes` (full format, ignores seconds) -- style 3 - `5 days, 45 minutes, 12 seconds` (full format, with seconds) - -### utils.delay() - -```python -from evennia import utils - -def _callback(obj, text): - obj.msg(text) - -# wait 10 seconds before sending "Echo!" to obj (which we assume is defined) -deferred = utils.delay(10, _callback, obj, "Echo!", persistent=False) - -# code here will run immediately, not waiting for the delay to fire! - -``` - -This creates an asynchronous delayed call. It will fire the given callback function after the given -number of seconds. This is a very light wrapper over a Twisted -[Deferred](https://twistedmatrix.com/documents/current/core/howto/defer.html). Normally this is run -non-persistently, which means that if the server is `@reload`ed before the delay is over, the -callback will never run (the server forgets it). If setting `persistent` to True, the delay will be -stored in the database and survive a `@reload` - but for this to work it is susceptible to the same -limitations incurred when saving to an [Attribute](./Attributes.md). - -The `deferred` return object can usually be ignored, but calling its `.cancel()` method will abort -the delay prematurely. - -`utils.delay` is the lightest form of delayed call in Evennia. For other way to create time-bound -tasks, see the [TickerHandler](./TickerHandler.md) and [Scripts](./Scripts.md). - -> Note that many delayed effects can be achieved without any need for an active timer. For example -if you have a trait that should recover a point every 5 seconds you might just need its value when -it's needed, but checking the current time and calculating on the fly what value it should have. - -## Object Classes -### utils.inherits_from() - -This useful function takes two arguments - an object to check and a parent. It returns `True` if -object inherits from parent *at any distance* (as opposed to Python's in-built `is_instance()` that -will only catch immediate dependence). This function also accepts as input any combination of -classes, instances or python-paths-to-classes. - -Note that Python code should usually work with [duck -typing](http://en.wikipedia.org/wiki/Duck_typing). But in Evennia's case it can sometimes be useful -to check if an object inherits from a given [Typeclass](./Typeclasses.md) as a way of identification. Say -for example that we have a typeclass *Animal*. This has a subclass *Felines* which in turn has a -subclass *HouseCat*. Maybe there are a bunch of other animal types too, like horses and dogs. Using -`inherits_from` will allow you to check for all animals in one go: - -```python - from evennia import utils - if (utils.inherits_from(obj, "typeclasses.objects.animals.Animal"): - obj.msg("The bouncer stops you in the door. He says: 'No talking animals allowed.'") -``` - - - -## Text utilities - -In a text game, you are naturally doing a lot of work shuffling text back and forth. Here is a *non- -complete* selection of text utilities found in `evennia/utils/utils.py` (shortcut `evennia.utils`). -If nothing else it can be good to look here before starting to develop a solution of your own. - -### utils.fill() - -This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also -indents as needed. - -```python - outtxt = fill(intxt, width=78, indent=4) -``` - -### utils.crop() - -This function will crop a very long line, adding a suffix to show the line actually continues. This -can be useful in listings when showing multiple lines would mess up things. - -```python - intxt = "This is a long text that we want to crop." - outtxt = crop(intxt, width=19, suffix="[...]") - # outtxt is now "This is a long text[...]" -``` - -### utils.dedent() - -This solves what may at first glance appear to be a trivial problem with text - removing -indentations. It is used to shift entire paragraphs to the left, without disturbing any further -formatting they may have. A common case for this is when using Python triple-quoted strings in code -- they will retain whichever indentation they have in the code, and to make easily-readable source -code one usually don't want to shift the string to the left edge. - -```python - #python code is entered at a given indentation - intxt = """ - This is an example text that will end - up with a lot of whitespace on the left. - It also has indentations of - its own.""" - outtxt = dedent(intxt) - # outtxt will now retain all internal indentation - # but be shifted all the way to the left. -``` - -Normally you do the dedent in the display code (this is for example how the help system homogenizes -help entries). - -### to_str() and to_bytes() - -Evennia supplies two utility functions for converting text to the correct -encodings. `to_str()` and `to_bytes()`. Unless you are adding a custom protocol and -need to send byte-data over the wire, `to_str` is the only one you'll need. - -The difference from Python's in-built `str()` and `bytes()` operators are that -the Evennia ones makes use of the `ENCODINGS` setting and will try very hard to -never raise a traceback but instead echo errors through logging. See -[here](./Text-Encodings.md) for more info. - -### Ansi Coloring Tools -- [evennia.ansi](evennia.utils.ansi) - -## Display utilities -### Making ascii tables - -The [EvTable](github:evennia.utils.evtable#evtable) class (`evennia/utils/evtable.py`) can be used -to create correctly formatted text tables. There is also -[EvForm](github:evennia.utils.evform#evform) (`evennia/utils/evform.py`). This reads a fixed-format -text template from a file in order to create any level of sophisticated ascii layout. Both evtable -and evform have lots of options and inputs so see the header of each module for help. - -The third-party [PrettyTable](https://code.google.com/p/prettytable/) module is also included in -Evennia. PrettyTable is considered deprecated in favor of EvTable since PrettyTable cannot handle -ANSI colour. PrettyTable can be found in `evennia/utils/prettytable/`. See its homepage above for -instructions. - -### Menus -- [evennia.EvMenu](github:evennia.utils.evmenu#evmenu) diff --git a/docs/0.9.5/_sources/Command-Cooldown.md.txt b/docs/0.9.5/_sources/Command-Cooldown.md.txt deleted file mode 100644 index c150a39c62..0000000000 --- a/docs/0.9.5/_sources/Command-Cooldown.md.txt +++ /dev/null @@ -1,98 +0,0 @@ -# Command Cooldown - - -Some types of games want to limit how often a command can be run. If a -character casts the spell *Firestorm*, you might not want them to spam that -command over and over. Or in an advanced combat system, a massive swing may -offer a chance of lots of damage at the cost of not being able to re-do it for -a while. Such effects are called *cooldowns*. - -This page exemplifies a very resource-efficient way to do cooldowns. A more -'active' way is to use asynchronous delays as in the [command duration -tutorial](./Command-Duration.md#blocking-commands), the two might be useful to -combine if you want to echo some message to the user after the cooldown ends. - -## Non-persistent cooldown - -This little recipe will limit how often a particular command can be run. Since -Commands are class instances, and those are cached in memory, a command -instance will remember things you store on it. So just store the current time -of execution! Next time the command is run, it just needs to check if it has -that time stored, and compare it with the current time to see if a desired -delay has passed. - -```python -import time -from evennia import default_cmds - -class CmdSpellFirestorm(default_cmds.MuxCommand): - """ - Spell - Firestorm - - Usage: - cast firestorm - - This will unleash a storm of flame. You can only release one - firestorm every five minutes (assuming you have the mana). - """ - key = "cast firestorm" - locks = "cmd:isFireMage()" - - def func(self): - "Implement the spell" - - # check cooldown (5 minute cooldown) - now = time.time() - if hasattr(self, "lastcast") and \ - now - self.lastcast < 5 * 60: - message = "You cannot cast this spell again yet." - self.caller.msg(message) - return - - #[the spell effect is implemented] - - # if the spell was successfully cast, store the casting time - self.lastcast = now -``` - -We just check the `lastcast` flag, and update it if everything works out. -Simple and very effective since everything is just stored in memory. The -drawback of this simple scheme is that it's non-persistent. If you do -`@reload`, the cache is cleaned and all such ongoing cooldowns will be -forgotten. It is also limited only to this one command, other commands cannot -(easily) check for this value. - -## Persistent cooldown - -This is essentially the same mechanism as the simple one above, except we use -the database to store the information which means the cooldown will survive a -server reload/reboot. Since commands themselves have no representation in the -database, you need to use the caster for the storage. - -```python - # inside the func() of CmdSpellFirestorm as above - - # check cooldown (5 minute cooldown) - - now = time.time() - lastcast = self.caller.db.firestorm_lastcast - - if lastcast and now - lastcast < 5 * 60: - message = "You need to wait before casting this spell again." - self.caller.msg(message) - return - - #[the spell effect is implemented] - - # if the spell was successfully cast, store the casting time - self.caller.db.firestorm_lastcast = now -``` - -Since we are storing as an [Attribute](./Attributes.md), we need to identify the -variable as `firestorm_lastcast` so we are sure we get the right one (we'll - likely have other skills with cooldowns after all). But this method of -using cooldowns also has the advantage of working *between* commands - you can -for example let all fire-related spells check the same cooldown to make sure -the casting of *Firestorm* blocks all fire-related spells for a while. Or, in -the case of taking that big swing with the sword, this could now block all -other types of attacks for a while before the warrior can recover. diff --git a/docs/0.9.5/_sources/Command-Duration.md.txt b/docs/0.9.5/_sources/Command-Duration.md.txt deleted file mode 100644 index 8a6bee7732..0000000000 --- a/docs/0.9.5/_sources/Command-Duration.md.txt +++ /dev/null @@ -1,403 +0,0 @@ -# Command Duration - - -Before reading this tutorial, if you haven't done so already, you might want to -read [the documentation on commands](./Commands.md) to get a basic understanding of -how commands work in Evennia. - -In some types of games a command should not start and finish immediately. -Loading a crossbow might take a bit of time to do - time you don't have when -the enemy comes rushing at you. Crafting that armour will not be immediate -either. For some types of games the very act of moving or changing pose all -comes with a certain time associated with it. - -## The simple way to pause commands with yield - -Evennia allows a shortcut in syntax to create simple pauses in commands. This -syntax uses the `yield` keyword. The `yield` keyword is used in Python to -create generators, although you don't need to know what generators are to use -this syntax. A short example will probably make it clear: - -```python -class CmdTest(Command): - - """ - A test command just to test waiting. - - Usage: - test - - """ - - key = "test" - locks = "cmd:all()" - - def func(self): - self.msg("Before ten seconds...") - yield 10 - self.msg("Afterwards.") -``` -> Important: The `yield` functionality will *only* work in the `func` method of -> Commands. It only works because Evennia has especially -> catered for it in Commands. If you want the same functionality elsewhere you -> must use the [interactive decorator](./Async-Process.md#the-interactive-decorator). - -The important line is the `yield 10`. It tells Evennia to "pause" the command -and to wait for 10 seconds to execute the rest. If you add this command and -run it, you'll see the first message, then, after a pause of ten seconds, the -next message. You can use `yield` several times in your command. - -This syntax will not "freeze" all commands. While the command is "pausing", - you can execute other commands (or even call the same command again). And - other players aren't frozen either. - -> Note: this will not save anything in the database. If you reload the game -> while a command is "paused", it will not resume after the server has -> reloaded. - - -## The more advanced way with utils.delay - -The `yield` syntax is easy to read, easy to understand, easy to use. But it's not that flexible if -you want more advanced options. Learning to use alternatives might be much worth it in the end. - -Below is a simple command example for adding a duration for a command to finish. - -```python -from evennia import default_cmds, utils - -class CmdEcho(default_cmds.MuxCommand): - """ - wait for an echo - - Usage: - echo - - Calls and waits for an echo - """ - key = "echo" - locks = "cmd:all()" - - def func(self): - """ - This is called at the initial shout. - """ - self.caller.msg("You shout '%s' and wait for an echo ..." % self.args) - # this waits non-blocking for 10 seconds, then calls self.echo - utils.delay(10, self.echo) # call echo after 10 seconds - - def echo(self): - "Called after 10 seconds." - shout = self.args - string = "You hear an echo: %s ... %s ... %s" - string = string % (shout.upper(), shout.capitalize(), shout.lower()) - self.caller.msg(string) -``` - -Import this new echo command into the default command set and reload the server. You will find that -it will take 10 seconds before you see your shout coming back. You will also find that this is a -*non-blocking* effect; you can issue other commands in the interim and the game will go on as usual. -The echo will come back to you in its own time. - -### About utils.delay() - -`utils.delay(timedelay, callback, persistent=False, *args, **kwargs)` is a useful function. It will -wait `timedelay` seconds, then call the `callback` function, optionally passing to it the arguments -provided to utils.delay by way of *args and/or **kwargs`. - -> Note: The callback argument should be provided with a python path to the desired function, for -instance `my_object.my_function` instead of `my_object.my_function()`. Otherwise my_function would -get called and run immediately upon attempting to pass it to the delay function. -If you want to provide arguments for utils.delay to use, when calling your callback function, you -have to do it separatly, for instance using the utils.delay *args and/or **kwargs, as mentioned -above. - -> If you are not familiar with the syntax `*args` and `**kwargs`, [see the Python documentation -here](https://docs.python.org/2/tutorial/controlflow.html#arbitrary-argument-lists). - -Looking at it you might think that `utils.delay(10, callback)` in the code above is just an -alternative to some more familiar thing like `time.sleep(10)`. This is *not* the case. If you do -`time.sleep(10)` you will in fact freeze the *entire server* for ten seconds! The `utils.delay()`is -a thin wrapper around a Twisted -[Deferred](http://twistedmatrix.com/documents/11.0.0/core/howto/defer.html) that will delay -execution until 10 seconds have passed, but will do so asynchronously, without bothering anyone else -(not even you - you can continue to do stuff normally while it waits to continue). - -The point to remember here is that the `delay()` call will not "pause" at that point when it is -called (the way `yield` does in the previous section). The lines after the `delay()` call will -actually execute *right away*. What you must do is to tell it which function to call *after the time -has passed* (its "callback"). This may sound strange at first, but it is normal practice in -asynchronous systems. You can also link such calls together as seen below: - -```python -from evennia import default_cmds, utils - -class CmdEcho(default_cmds.MuxCommand): - """ - waits for an echo - - Usage: - echo - - Calls and waits for an echo - """ - key = "echo" - locks = "cmd:all()" - - def func(self): - "This sets off a chain of delayed calls" - self.caller.msg("You shout '%s', waiting for an echo ..." % self.args) - - # wait 2 seconds before calling self.echo1 - utils.delay(2, self.echo1) - - # callback chain, started above - def echo1(self): - "First echo" - self.caller.msg("... %s" % self.args.upper()) - # wait 2 seconds for the next one - utils.delay(2, self.echo2) - - def echo2(self): - "Second echo" - self.caller.msg("... %s" % self.args.capitalize()) - # wait another 2 seconds - utils.delay(2, callback=self.echo3) - - def echo3(self): - "Last echo" - self.caller.msg("... %s ..." % self.args.lower()) -``` - -The above version will have the echoes arrive one after another, each separated by a two second -delay. - - > echo Hello! - ... HELLO! - ... Hello! - ... hello! ... - -## Blocking commands - -As mentioned, a great thing about the delay introduced by `yield` or `utils.delay()` is that it does -not block. It just goes on in the background and you are free to play normally in the interim. In -some cases this is not what you want however. Some commands should simply "block" other commands -while they are running. If you are in the process of crafting a helmet you shouldn't be able to also -start crafting a shield at the same time, or if you just did a huge power-swing with your weapon you -should not be able to do it again immediately. - -The simplest way of implementing blocking is to use the technique covered in the [Command -Cooldown](./Command-Cooldown.md) tutorial. In that tutorial we implemented cooldowns by having the -Command store the current time. Next time the Command was called, we compared the current time to -the stored time to determine if enough time had passed for a renewed use. This is a *very* -efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player -when enough time has passed unless they keep trying. - -Here is an example where we will use `utils.delay` to tell the player when the cooldown has passed: - -```python -from evennia import utils, default_cmds - -class CmdBigSwing(default_cmds.MuxCommand): - """ - swing your weapon in a big way - - Usage: - swing - - Makes a mighty swing. Doing so will make you vulnerable - to counter-attacks before you can recover. - """ - key = "bigswing" - locks = "cmd:all()" - - def func(self): - "Makes the swing" - - if self.caller.ndb.off_balance: - # we are still off-balance. - self.caller.msg("You are off balance and need time to recover!") - return - - # [attack/hit code goes here ...] - self.caller.msg("You swing big! You are off balance now.") - - # set the off-balance flag - self.caller.ndb.off_balance = True - - # wait 8 seconds before we can recover. During this time - # we won't be able to swing again due to the check at the top. - utils.delay(8, self.recover) - - def recover(self): - "This will be called after 8 secs" - del self.caller.ndb.off_balance - self.caller.msg("You regain your balance.") -``` - -Note how, after the cooldown, the user will get a message telling them they are now ready for -another swing. - -By storing the `off_balance` flag on the character (rather than on, say, the Command instance -itself) it can be accessed by other Commands too. Other attacks may also not work when you are off -balance. You could also have an enemy Command check your `off_balance` status to gain bonuses, to -take another example. - -## Abortable commands - -One can imagine that you will want to abort a long-running command before it has a time to finish. -If you are in the middle of crafting your armor you will probably want to stop doing that when a -monster enters your smithy. - -You can implement this in the same way as you do the "blocking" command above, just in reverse. -Below is an example of a crafting command that can be aborted by starting a fight: - -```python -from evennia import utils, default_cmds - -class CmdCraftArmour(default_cmds.MuxCommand): - """ - Craft armour - - Usage: - craft - - This will craft a suit of armour, assuming you - have all the components and tools. Doing some - other action (such as attacking someone) will - abort the crafting process. - """ - key = "craft" - locks = "cmd:all()" - - def func(self): - "starts crafting" - - if self.caller.ndb.is_crafting: - self.caller.msg("You are already crafting!") - return - if self._is_fighting(): - self.caller.msg("You can't start to craft " - "in the middle of a fight!") - return - - # [Crafting code, checking of components, skills etc] - - # Start crafting - self.caller.ndb.is_crafting = True - self.caller.msg("You start crafting ...") - utils.delay(60, self.step1) - - def _is_fighting(self): - "checks if we are in a fight." - if self.caller.ndb.is_fighting: - del self.caller.ndb.is_crafting - return True - - def step1(self): - "first step of armour construction" - if self._is_fighting(): - return - self.msg("You create the first part of the armour.") - utils.delay(60, callback=self.step2) - - def step2(self): - "second step of armour construction" - if self._is_fighting(): - return - self.msg("You create the second part of the armour.") - utils.delay(60, step3) - - def step3(self): - "last step of armour construction" - if self._is_fighting(): - return - - # [code for creating the armour object etc] - - del self.caller.ndb.is_crafting - self.msg("You finalize your armour.") - - -# example of a command that aborts crafting - -class CmdAttack(default_cmds.MuxCommand): - """ - attack someone - - Usage: - attack - - Try to cause harm to someone. This will abort - eventual crafting you may be currently doing. - """ - key = "attack" - aliases = ["hit", "stab"] - locks = "cmd:all()" - - def func(self): - "Implements the command" - - self.caller.ndb.is_fighting = True - - # [...] -``` - -The above code creates a delayed crafting command that will gradually create the armour. If the -`attack` command is issued during this process it will set a flag that causes the crafting to be -quietly canceled next time it tries to update. - -## Persistent delays - -In the latter examples above we used `.ndb` storage. This is fast and easy but it will reset all -cooldowns/blocks/crafting etc if you reload the server. If you don't want that you can replace -`.ndb` with `.db`. But even this won't help because the `yield` keyword is not persisent and nor is -the use of `delay` shown above. To resolve this you can use `delay` with the `persistent=True` -keyword. But wait! Making something persistent will add some extra complications, because now you -must make sure Evennia can properly store things to the database. - -Here is the original echo-command reworked to function with persistence: -```python -from evennia import default_cmds, utils - -# this is now in the outermost scope and takes two args! -def echo(caller, args): - "Called after 10 seconds." - shout = args - string = "You hear an echo: %s ... %s ... %s" - string = string % (shout.upper(), shout.capitalize(), shout.lower()) - caller.msg(string) - -class CmdEcho(default_cmds.MuxCommand): - """ - wait for an echo - - Usage: - echo - - Calls and waits for an echo - """ - key = "echo" - locks = "cmd:all()" - - def func(self): - """ - This is called at the initial shout. - """ - self.caller.msg("You shout '%s' and wait for an echo ..." % self.args) - # this waits non-blocking for 10 seconds, then calls echo(self.caller, self.args) - utils.delay(10, echo, self.caller, self.args, persistent=True) # changes! - -``` - -Above you notice two changes: -- The callback (`echo`) was moved out of the class and became its own stand-alone function in the -outermost scope of the module. It also now takes `caller` and `args` as arguments (it doesn't have -access to them directly since this is now a stand-alone function). -- `utils.delay` specifies the `echo` function (not `self.echo` - it's no longer a method!) and sends -`self.caller` and `self.args` as arguments for it to use. We also set `persistent=True`. - -The reason for this change is because Evennia needs to `pickle` the callback into storage and it -cannot do this correctly when the method sits on the command class. Now this behave the same as the -first version except if you reload (or even shut down) the server mid-delay it will still fire the -callback when the server comes back up (it will resume the countdown and ignore the downtime). diff --git a/docs/0.9.5/_sources/Command-Prompt.md.txt b/docs/0.9.5/_sources/Command-Prompt.md.txt deleted file mode 100644 index bf99b5ddc6..0000000000 --- a/docs/0.9.5/_sources/Command-Prompt.md.txt +++ /dev/null @@ -1,129 +0,0 @@ -# Command Prompt - - -A *prompt* is quite common in MUDs. The prompt display useful details about your character that you -are likely to want to keep tabs on at all times, such as health, magical power etc. It might also -show things like in-game time, weather and so on. Many modern MUD clients (including Evennia's own -webclient) allows for identifying the prompt and have it appear in a correct location (usually just -above the input line). Usually it will remain like that until it is explicitly updated. - -## Sending a prompt - -A prompt is sent using the `prompt` keyword to the `msg()` method on objects. The prompt will be -sent without any line breaks. - -```python - self.msg(prompt="HP: 5, MP: 2, SP: 8") -``` -You can combine the sending of normal text with the sending (updating of the prompt): - -```python - self.msg("This is a text", prompt="This is a prompt") -``` - -You can update the prompt on demand, this is normally done using [OOB](./OOB.md)-tracking of the relevant -Attributes (like the character's health). You could also make sure that attacking commands update -the prompt when they cause a change in health, for example. - -Here is a simple example of the prompt sent/updated from a command class: - -```python - from evennia import Command - - class CmdDiagnose(Command): - """ - see how hurt your are - - Usage: - diagnose [target] - - This will give an estimate of the target's health. Also - the target's prompt will be updated. - """ - key = "diagnose" - - def func(self): - if not self.args: - target = self.caller - else: - target = self.search(self.args) - if not target: - return - # try to get health, mana and stamina - hp = target.db.hp - mp = target.db.mp - sp = target.db.sp - - if None in (hp, mp, sp): - # Attributes not defined - self.caller.msg("Not a valid target!") - return - - text = "You diagnose %s as having " \ - "%i health, %i mana and %i stamina." \ - % (hp, mp, sp) - prompt = "%i HP, %i MP, %i SP" % (hp, mp, sp) - self.caller.msg(text, prompt=prompt) -``` -## A prompt sent with every command - -The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a -special flag). Most MUD telnet clients will understand and allow users to catch this and keep the -prompt in place until it updates. So *in principle* you'd not need to update the prompt every -command. - -However, with a varying user base it can be unclear which clients are used and which skill level the -users have. So sending a prompt with every command is a safe catch-all. You don't need to manually -go in and edit every command you have though. Instead you edit the base command class for your -custom commands (like `MuxCommand` in your `mygame/commands/command.py` folder) and overload the -`at_post_cmd()` hook. This hook is always called *after* the main `func()` method of the Command. - -```python -from evennia import default_cmds - -class MuxCommand(default_cmds.MuxCommand): - # ... - def at_post_cmd(self): - "called after self.func()." - caller = self.caller - prompt = "%i HP, %i MP, %i SP" % (caller.db.hp, - caller.db.mp, - caller.db.sp) - caller.msg(prompt=prompt) - -``` - -### Modifying default commands - -If you want to add something small like this to Evennia's default commands without modifying them -directly the easiest way is to just wrap those with a multiple inheritance to your own base class: - -```python -# in (for example) mygame/commands/mycommands.py - -from evennia import default_cmds -# our custom MuxCommand with at_post_cmd hook -from commands.command import MuxCommand - -# overloading the look command -class CmdLook(default_cmds.CmdLook, MuxCommand): - pass -``` - -The result of this is that the hooks from your custom `MuxCommand` will be mixed into the default -`CmdLook` through multiple inheritance. Next you just add this to your default command set: - -```python -# in mygame/commands/default_cmdsets.py - -from evennia import default_cmds -from commands import mycommands - -class CharacterCmdSet(default_cmds.CharacterCmdSet): - # ... - def at_cmdset_creation(self): - # ... - self.add(mycommands.CmdLook()) -``` - -This will automatically replace the default `look` command in your game with your own version. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Command-Sets.md.txt b/docs/0.9.5/_sources/Command-Sets.md.txt deleted file mode 100644 index ab4d56a446..0000000000 --- a/docs/0.9.5/_sources/Command-Sets.md.txt +++ /dev/null @@ -1,376 +0,0 @@ -# Command Sets - - -Command Sets are intimately linked with [Commands](./Commands.md) and you should be familiar with -Commands before reading this page. The two pages were split for ease of reading. - -A *Command Set* (often referred to as a CmdSet or cmdset) is the basic unit for storing one or more -*Commands*. A given Command can go into any number of different command sets. Storing Command -classes in a command set is the way to make commands available to use in your game. - -When storing a CmdSet on an object, you will make the commands in that command set available to the -object. An example is the default command set stored on new Characters. This command set contains -all the useful commands, from `look` and `inventory` to `@dig` and `@reload` -([permissions](./Locks.md#permissions) then limit which players may use them, but that's a separate -topic). - -When an account enters a command, cmdsets from the Account, Character, its location, and elsewhere -are pulled together into a *merge stack*. This stack is merged together in a specific order to -create a single "merged" cmdset, representing the pool of commands available at that very moment. - -An example would be a `Window` object that has a cmdset with two commands in it: `look through -window` and `open window`. The command set would be visible to players in the room with the window, -allowing them to use those commands only there. You could imagine all sorts of clever uses of this, -like a `Television` object which had multiple commands for looking at it, switching channels and so -on. The tutorial world included with Evennia showcases a dark room that replaces certain critical -commands with its own versions because the Character cannot see. - -If you want a quick start into defining your first commands and using them with command sets, you -can head over to the [Adding Command Tutorial](./Adding-Command-Tutorial.md) which steps through things -without the explanations. - -## Defining Command Sets - -A CmdSet is, as most things in Evennia, defined as a Python class inheriting from the correct parent -(`evennia.CmdSet`, which is a shortcut to `evennia.commands.cmdset.CmdSet`). The CmdSet class only -needs to define one method, called `at_cmdset_creation()`. All other class parameters are optional, -but are used for more advanced set manipulation and coding (see the [merge rules](Command- -Sets#merge-rules) section). - -```python -# file mygame/commands/mycmdset.py - -from evennia import CmdSet - -# this is a theoretical custom module with commands we -# created previously: mygame/commands/mycommands.py -from commands import mycommands - -class MyCmdSet(CmdSet): - def at_cmdset_creation(self): - """ - The only thing this method should need - to do is to add commands to the set. - """ - self.add(mycommands.MyCommand1()) - self.add(mycommands.MyCommand2()) - self.add(mycommands.MyCommand3()) -``` - -The CmdSet's `add()` method can also take another CmdSet as input. In this case all the commands -from that CmdSet will be appended to this one as if you added them line by line: - -```python - def at_cmdset_creation(): - ... - self.add(AdditionalCmdSet) # adds all command from this set - ... -``` - -If you added your command to an existing cmdset (like to the default cmdset), that set is already -loaded into memory. You need to make the server aware of the code changes: - -``` -@reload -``` - -You should now be able to use the command. - -If you created a new, fresh cmdset, this must be added to an object in order to make the commands -within available. A simple way to temporarily test a cmdset on yourself is use the `@py` command to -execute a python snippet: - -```python -@py self.cmdset.add('commands.mycmdset.MyCmdSet') -``` - -This will stay with you until you `@reset` or `@shutdown` the server, or you run - -```python -@py self.cmdset.delete('commands.mycmdset.MyCmdSet') -``` - -In the example above, a specific Cmdset class is removed. Calling `delete` without arguments will -remove the latest added cmdset. - -> Note: Command sets added using `cmdset.add` are, by default, *not* persistent in the database. - -If you want the cmdset to survive a reload, you can do: - -``` -@py self.cmdset.add(commands.mycmdset.MyCmdSet, permanent=True) -``` - -Or you could add the cmdset as the *default* cmdset: - -``` -@py self.cmdset.add_default(commands.mycmdset.MyCmdSet) -``` - -An object can only have one "default" cmdset (but can also have none). This is meant as a safe fall- -back even if all other cmdsets fail or are removed. It is always persistent and will not be affected -by `cmdset.delete()`. To remove a default cmdset you must explicitly call `cmdset.remove_default()`. - -Command sets are often added to an object in its `at_object_creation` method. For more examples of -adding commands, read the [Step by step tutorial](./Adding-Command-Tutorial.md). Generally you can -customize which command sets are added to your objects by using `self.cmdset.add()` or -`self.cmdset.add_default()`. - -> Important: Commands are identified uniquely by key *or* alias (see [Commands](./Commands.md)). If any -overlap exists, two commands are considered identical. Adding a Command to a command set that -already has an identical command will *replace* the previous command. This is very important. You -must take this behavior into account when attempting to overload any default Evennia commands with -your own. Otherwise, you may accidentally "hide" your own command in your command set when adding a -new one that has a matching alias. - -### Properties on Command Sets - -There are several extra flags that you can set on CmdSets in order to modify how they work. All are -optional and will be set to defaults otherwise. Since many of these relate to *merging* cmdsets, -you might want to read the [Adding and Merging Command Sets](./Command-Sets.md#adding-and-merging- -command-sets) section for some of these to make sense. - -- `key` (string) - an identifier for the cmdset. This is optional, but should be unique. It is used -for display in lists, but also to identify special merging behaviours using the `key_mergetype` -dictionary below. -- `mergetype` (string) - allows for one of the following string values: "*Union*", "*Intersect*", -"*Replace*", or "*Remove*". -- `priority` (int) - This defines the merge order of the merge stack - cmdsets will merge in rising -order of priority with the highest priority set merging last. During a merger, the commands from the -set with the higher priority will have precedence (just what happens depends on the [merge -type](./Command-Sets.md#adding-and-merging-command-sets)). If priority is identical, the order in the -merge stack determines preference. The priority value must be greater or equal to `-100`. Most in- -game sets should usually have priorities between `0` and `100`. Evennia default sets have priorities -as follows (these can be changed if you want a different distribution): - - EmptySet: `-101` (should be lower than all other sets) - - SessionCmdSet: `-20` - - AccountCmdSet: `-10` - - CharacterCmdSet: `0` - - ExitCmdSet: ` 101` (generally should always be available) - - ChannelCmdSet: `101` (should usually always be available) - since exits never accept -arguments, there is no collision between exits named the same as a channel even though the commands -"collide". -- `key_mergetype` (dict) - a dict of `key:mergetype` pairs. This allows this cmdset to merge -differently with certain named cmdsets. If the cmdset to merge with has a `key` matching an entry in -`key_mergetype`, it will not be merged according to the setting in `mergetype` but according to the -mode in this dict. Please note that this is more complex than it may seem due to the [merge -order](./Command-Sets.md#adding-and-merging-command-sets) of command sets. Please review that section -before using `key_mergetype`. -- `duplicates` (bool/None default `None`) - this determines what happens when merging same-priority -cmdsets containing same-key commands together. The`dupicate` option will *only* apply when merging -the cmdset with this option onto one other cmdset with the same priority. The resulting cmdset will -*not* retain this `duplicate` setting. - - `None` (default): No duplicates are allowed and the cmdset being merged "onto" the old one -will take precedence. The result will be unique commands. *However*, the system will assume this -value to be `True` for cmdsets on Objects, to avoid dangerous clashes. This is usually the safe bet. - - `False`: Like `None` except the system will not auto-assume any value for cmdsets defined on -Objects. - - `True`: Same-named, same-prio commands will merge into the same cmdset. This will lead to a -multimatch error (the user will get a list of possibilities in order to specify which command they -meant). This is is useful e.g. for on-object cmdsets (example: There is a `red button` and a `green -button` in the room. Both have a `press button` command, in cmdsets with the same priority. This -flag makes sure that just writing `press button` will force the Player to define just which object's -command was intended). -- `no_objs` this is a flag for the cmdhandler that builds the set of commands available at every -moment. It tells the handler not to include cmdsets from objects around the account (nor from rooms -or inventory) when building the merged set. Exit commands will still be included. This option can -have three values: - - `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If never -set explicitly, this acts as `False`. - - `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_objs` are merged, -priority determines what is used. -- `no_exits` - this is a flag for the cmdhandler that builds the set of commands available at every -moment. It tells the handler not to include cmdsets from exits. This flag can have three values: - - `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If -never set explicitly, this acts as `False`. - - `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_exits` are merged, -priority determines what is used. -- `no_channels` (bool) - this is a flag for the cmdhandler that builds the set of commands available -at every moment. It tells the handler not to include cmdsets from available in-game channels. This -flag can have three values: - - `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If -never set explicitly, this acts as `False`. - - `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_channels` are merged, -priority determines what is used. - -## Command Sets Searched - -When a user issues a command, it is matched against the [merged](./Command-Sets.md#adding-and-merging- -command-sets) command sets available to the player at the moment. Which those are may change at any -time (such as when the player walks into the room with the `Window` object described earlier). - -The currently valid command sets are collected from the following sources: - -- The cmdsets stored on the currently active [Session](./Sessions.md). Default is the empty -`SessionCmdSet` with merge priority `-20`. -- The cmdsets defined on the [Account](./Accounts.md). Default is the AccountCmdSet with merge priority -`-10`. -- All cmdsets on the Character/Object (assuming the Account is currently puppeting such a -Character/Object). Merge priority `0`. -- The cmdsets of all objects carried by the puppeted Character (checks the `call` lock). Will not be -included if `no_objs` option is active in the merge stack. -- The cmdsets of the Character's current location (checks the `call` lock). Will not be included if -`no_objs` option is active in the merge stack. -- The cmdsets of objects in the current location (checks the `call` lock). Will not be included if -`no_objs` option is active in the merge stack. -- The cmdsets of Exits in the location. Merge priority `+101`. Will not be included if `no_exits` -*or* `no_objs` option is active in the merge stack. -- The [channel](./Communications.md) cmdset containing commands for posting to all channels the account -or character is currently connected to. Merge priority `+101`. Will not be included if `no_channels` -option is active in the merge stack. - -Note that an object does not *have* to share its commands with its surroundings. A Character's -cmdsets should not be shared for example, or all other Characters would get multi-match errors just -by being in the same room. The ability of an object to share its cmdsets is managed by its `call` -[lock](./Locks.md). For example, [Character objects](./Objects.md) defaults to `call:false()` so that any -cmdsets on them can only be accessed by themselves, not by other objects around them. Another -example might be to lock an object with `call:inside()` to only make their commands available to -objects inside them, or `cmd:holds()` to make their commands available only if they are held. - -## Adding and Merging Command Sets - -*Note: This is an advanced topic. It's very useful to know about, but you might want to skip it if -this is your first time learning about commands.* - -CmdSets have the special ability that they can be *merged* together into new sets. Which of the -ingoing commands end up in the merged set is defined by the *merge rule* and the relative -*priorities* of the two sets. Removing the latest added set will restore things back to the way it -was before the addition. - -CmdSets are non-destructively stored in a stack inside the cmdset handler on the object. This stack -is parsed to create the "combined" cmdset active at the moment. CmdSets from other sources are also -included in the merger such as those on objects in the same room (like buttons to press) or those -introduced by state changes (such as when entering a menu). The cmdsets are all ordered after -priority and then merged together in *reverse order*. That is, the higher priority will be merged -"onto" lower-prio ones. By defining a cmdset with a merge-priority between that of two other sets, -you will make sure it will be merged in between them. -The very first cmdset in this stack is called the *Default cmdset* and is protected from accidental -deletion. Running `obj.cmdset.delete()` will never delete the default set. Instead one should add -new cmdsets on top of the default to "hide" it, as described below. Use the special -`obj.cmdset.delete_default()` only if you really know what you are doing. - -CmdSet merging is an advanced feature useful for implementing powerful game effects. Imagine for -example a player entering a dark room. You don't want the player to be able to find everything in -the room at a glance - maybe you even want them to have a hard time to find stuff in their backpack! -You can then define a different CmdSet with commands that override the normal ones. While they are -in the dark room, maybe the `look` and `inv` commands now just tell the player they cannot see -anything! Another example would be to offer special combat commands only when the player is in -combat. Or when being on a boat. Or when having taken the super power-up. All this can be done on -the fly by merging command sets. - -### Merge Rules - -Basic rule is that command sets are merged in *reverse priority order*. That is, lower-prio sets are -merged first and higher prio sets are merged "on top" of them. Think of it like a layered cake with -the highest priority on top. - -To further understand how sets merge, we need to define some examples. Let's call the first command -set **A** and the second **B**. We assume **B** is the command set already active on our object and -we will merge **A** onto **B**. In code terms this would be done by `object.cdmset.add(A)`. -Remember, B is already active on `object` from before. - -We let the **A** set have higher priority than **B**. A priority is simply an integer number. As -seen in the list above, Evennia's default cmdsets have priorities in the range `-101` to `120`. You -are usually safe to use a priority of `0` or `1` for most game effects. - -In our examples, both sets contain a number of commands which we'll identify by numbers, like `A1, -A2` for set **A** and `B1, B2, B3, B4` for **B**. So for that example both sets contain commands -with the same keys (or aliases) "1" and "2" (this could for example be "look" and "get" in the real -game), whereas commands 3 and 4 are unique to **B**. To describe a merge between these sets, we -would write `A1,A2 + B1,B2,B3,B4 = ?` where `?` is a list of commands that depend on which merge -type **A** has, and which relative priorities the two sets have. By convention, we read this -statement as "New command set **A** is merged onto the old command set **B** to form **?**". - -Below are the available merge types and how they work. Names are partly borrowed from [Set -theory](http://en.wikipedia.org/wiki/Set_theory). - -- **Union** (default) - The two cmdsets are merged so that as many commands as possible from each -cmdset ends up in the merged cmdset. Same-key commands are merged by priority. - - # Union - A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4 - -- **Intersect** - Only commands found in *both* cmdsets (i.e. which have the same keys) end up in -the merged cmdset, with the higher-priority cmdset replacing the lower one's commands. - - # Intersect - A1,A3,A5 + B1,B2,B4,B5 = A1,A5 - -- **Replace** - The commands of the higher-prio cmdset completely replaces the lower-priority -cmdset's commands, regardless of if same-key commands exist or not. - - # Replace - A1,A3 + B1,B2,B4,B5 = A1,A3 - -- **Remove** - The high-priority command sets removes same-key commands from the lower-priority -cmdset. They are not replaced with anything, so this is a sort of filter that prunes the low-prio -set using the high-prio one as a template. - - # Remove - A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5 - -Besides `priority` and `mergetype`, a command-set also takes a few other variables to control how -they merge: - -- `duplicates` (bool) - determines what happens when two sets of equal priority merge. Default is -that the new set in the merger (i.e. **A** above) automatically takes precedence. But if -*duplicates* is true, the result will be a merger with more than one of each name match. This will -usually lead to the player receiving a multiple-match error higher up the road, but can be good for -things like cmdsets on non-player objects in a room, to allow the system to warn that more than one -'ball' in the room has the same 'kick' command defined on it and offer a chance to select which -ball to kick ... Allowing duplicates only makes sense for *Union* and *Intersect*, the setting is -ignored for the other mergetypes. -- `key_mergetypes` (dict) - allows the cmdset to define a unique mergetype for particular cmdsets, -identified by their cmdset `key`. Format is `{CmdSetkey:mergetype}`. Example: -`{'Myevilcmdset','Replace'}` which would make sure for this set to always use 'Replace' on the -cmdset with the key `Myevilcmdset` only, no matter what the main `mergetype` is set to. - -> Warning: The `key_mergetypes` dictionary *can only work on the cmdset we merge onto*. When using -`key_mergetypes` it is thus important to consider the merge priorities - you must make sure that you -pick a priority *between* the cmdset you want to detect and the next higher one, if any. That is, if -we define a cmdset with a high priority and set it to affect a cmdset that is far down in the merge -stack, we would not "see" that set when it's time for us to merge. Example: Merge stack is -`A(prio=-10), B(prio=-5), C(prio=0), D(prio=5)`. We now merge a cmdset `E(prio=10)` onto this stack, -with a `key_mergetype={"B":"Replace"}`. But priorities dictate that we won't be merged onto B, we -will be merged onto E (which is a merger of the lower-prio sets at this point). Since we are merging -onto E and not B, our `key_mergetype` directive won't trigger. To make sure it works we must make -sure we merge onto B. Setting E's priority to, say, -4 will make sure to merge it onto B and affect -it appropriately. - -More advanced cmdset example: - -```python -from commands import mycommands - -class MyCmdSet(CmdSet): - - key = "MyCmdSet" - priority = 4 - mergetype = "Replace" - key_mergetypes = {'MyOtherCmdSet':'Union'} - - def at_cmdset_creation(self): - """ - The only thing this method should need - to do is to add commands to the set. - """ - self.add(mycommands.MyCommand1()) - self.add(mycommands.MyCommand2()) - self.add(mycommands.MyCommand3()) -``` - -### Assorted Notes - -It is very important to remember that two commands are compared *both* by their `key` properties -*and* by their `aliases` properties. If either keys or one of their aliases match, the two commands -are considered the *same*. So consider these two Commands: - - - A Command with key "kick" and alias "fight" - - A Command with key "punch" also with an alias "fight" - -During the cmdset merging (which happens all the time since also things like channel commands and -exits are merged in), these two commands will be considered *identical* since they share alias. It -means only one of them will remain after the merger. Each will also be compared with all other -commands having any combination of the keys and/or aliases "kick", "punch" or "fight". - -... So avoid duplicate aliases, it will only cause confusion. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Command-System.md.txt b/docs/0.9.5/_sources/Command-System.md.txt deleted file mode 100644 index 364d6f09ef..0000000000 --- a/docs/0.9.5/_sources/Command-System.md.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Command System - -- [Commands](./Commands.md) -- [Command Sets](./Command-Sets.md) -- [Command Auto-help](./Help-System.md#command-auto-help-system) - -See also: -- [Default Command Help](./Default-Commands.md) -- [Adding Command Tutorial](./Adding-Command-Tutorial.md) \ No newline at end of file diff --git a/docs/0.9.5/_sources/Commands.md.txt b/docs/0.9.5/_sources/Commands.md.txt deleted file mode 100644 index 934579acb8..0000000000 --- a/docs/0.9.5/_sources/Commands.md.txt +++ /dev/null @@ -1,663 +0,0 @@ -# Commands - - -Commands are intimately linked to [Command Sets](./Command-Sets.md) and you need to read that page too to -be familiar with how the command system works. The two pages were split for easy reading. - -The basic way for users to communicate with the game is through *Commands*. These can be commands -directly related to the game world such as *look*, *get*, *drop* and so on, or administrative -commands such as *examine* or *@dig*. - -The [default commands](./Default-Commands.md) coming with Evennia are 'MUX-like' in that they use @ -for admin commands, support things like switches, syntax with the '=' symbol etc, but there is -nothing that prevents you from implementing a completely different command scheme for your game. You -can find the default commands in `evennia/commands/default`. You should not edit these directly - -they will be updated by the Evennia team as new features are added. Rather you should look to them -for inspiration and inherit your own designs from them. - -There are two components to having a command running - the *Command* class and the [Command -Set](./Command-Sets.md) (command sets were split into a separate wiki page for ease of reading). - -1. A *Command* is a python class containing all the functioning code for what a command does - for -example, a *get* command would contain code for picking up objects. -1. A *Command Set* (often referred to as a CmdSet or cmdset) is like a container for one or more -Commands. A given Command can go into any number of different command sets. Only by putting the -command set on a character object you will make all the commands therein available to use by that -character. You can also store command sets on normal objects if you want users to be able to use the -object in various ways. Consider a "Tree" object with a cmdset defining the commands *climb* and -*chop down*. Or a "Clock" with a cmdset containing the single command *check time*. - -This page goes into full detail about how to use Commands. To fully use them you must also read the -page detailing [Command Sets](./Command-Sets.md). There is also a step-by-step [Adding Command -Tutorial](./Adding-Command-Tutorial.md) that will get you started quickly without the extra explanations. - -## Defining Commands - -All commands are implemented as normal Python classes inheriting from the base class `Command` -(`evennia.Command`). You will find that this base class is very "bare". The default commands of -Evennia actually inherit from a child of `Command` called `MuxCommand` - this is the class that -knows all the mux-like syntax like `/switches`, splitting by "=" etc. Below we'll avoid mux- -specifics and use the base `Command` class directly. - -```python - # basic Command definition - from evennia import Command - - class MyCmd(Command): - """ - This is the help-text for the command - """ - key = "mycommand" - def parse(self): - # parsing the command line here - def func(self): - # executing the command here -``` - -Here is a minimalistic command with no custom parsing: - -```python - from evennia import Command - - class CmdEcho(Command): - key = "echo" - - def func(self): - # echo the caller's input back to the caller - self.caller.msg("Echo: {}".format(self.args) - -``` - -You define a new command by assigning a few class-global properties on your inherited class and -overloading one or two hook functions. The full gritty mechanic behind how commands work are found -towards the end of this page; for now you only need to know that the command handler creates an -instance of this class and uses that instance whenever you use this command - it also dynamically -assigns the new command instance a few useful properties that you can assume to always be available. - -### Who is calling the command? - -In Evennia there are three types of objects that may call the command. It is important to be aware -of this since this will also assign appropriate `caller`, `session`, `sessid` and `account` -properties on the command body at runtime. Most often the calling type is `Session`. - -* A [Session](./Sessions.md). This is by far the most common case when a user is entering a command in -their client. - * `caller` - this is set to the puppeted [Object](./Objects.md) if such an object exists. If no -puppet is found, `caller` is set equal to `account`. Only if an Account is not found either (such as -before being logged in) will this be set to the Session object itself. - * `session` - a reference to the [Session](./Sessions.md) object itself. - * `sessid` - `sessid.id`, a unique integer identifier of the session. - * `account` - the [Account](./Accounts.md) object connected to this Session. None if not logged in. -* An [Account](./Accounts.md). This only happens if `account.execute_cmd()` was used. No Session -information can be obtained in this case. - * `caller` - this is set to the puppeted Object if such an object can be determined (without -Session info this can only be determined in `MULTISESSION_MODE=0` or `1`). If no puppet is found, -this is equal to `account`. - * `session` - `None*` - * `sessid` - `None*` - * `account` - Set to the Account object. -* An [Object](./Objects.md). This only happens if `object.execute_cmd()` was used (for example by an -NPC). - * `caller` - This is set to the calling Object in question. - * `session` - `None*` - * `sessid` - `None*` - * `account` - `None` - -> `*)`: There is a way to make the Session available also inside tests run directly on Accounts and -Objects, and that is to pass it to `execute_cmd` like so: `account.execute_cmd("...", -session=)`. Doing so *will* make the `.session` and `.sessid` properties available in the -command. - -### Properties assigned to the command instance at run-time - -Let's say account *Bob* with a character *BigGuy* enters the command *look at sword*. After the -system having successfully identified this as the "look" command and determined that BigGuy really -has access to a command named `look`, it chugs the `look` command class out of storage and either -loads an existing Command instance from cache or creates one. After some more checks it then assigns -it the following properties: - -- `caller` - The character BigGuy, in this example. This is a reference to the object executing the -command. The value of this depends on what type of object is calling the command; see the previous -section. -- `session` - the [Session](./Sessions.md) Bob uses to connect to the game and control BigGuy (see also -previous section). -- `sessid` - the unique id of `self.session`, for quick lookup. -- `account` - the [Account](./Accounts.md) Bob (see previous section). -- `cmdstring` - the matched key for the command. This would be *look* in our example. -- `args` - this is the rest of the string, except the command name. So if the string entered was -*look at sword*, `args` would be " *at sword*". Note the space kept - Evennia would correctly -interpret `lookat sword` too. This is useful for things like `/switches` that should not use space. -In the `MuxCommand` class used for default commands, this space is stripped. Also see the -`arg_regex` property if you want to enforce a space to make `lookat sword` give a command-not-found -error. -- `obj` - the game [Object](./Objects.md) on which this command is defined. This need not be the caller, -but since `look` is a common (default) command, this is probably defined directly on *BigGuy* - so -`obj` will point to BigGuy. Otherwise `obj` could be an Account or any interactive object with -commands defined on it, like in the example of the "check time" command defined on a "Clock" object. -- `cmdset` - this is a reference to the merged CmdSet (see below) from which this command was -matched. This variable is rarely used, it's main use is for the [auto-help system](Help- -System#command-auto-help-system) (*Advanced note: the merged cmdset need NOT be the same as -`BigGuy.cmdset`. The merged set can be a combination of the cmdsets from other objects in the room, -for example*). -- `raw_string` - this is the raw input coming from the user, without stripping any surrounding -whitespace. The only thing that is stripped is the ending newline marker. - -#### Other useful utility methods: - -- `.get_help(caller, cmdset)` - Get the help entry for this command. By default the arguments are -not - used, but they could be used to implement alternate help-display systems. -- `.client_width()` - Shortcut for getting the client's screen-width. Note that not all clients will - truthfully report this value - that case the `settings.DEFAULT_SCREEN_WIDTH` will be returned. -- `.styled_table(*args, **kwargs)` - This returns an [EvTable](module- -evennia.utils.evtable) styled based on the - session calling this command. The args/kwargs are the same as for EvTable, except styling defaults -are set. -- `.styled_header`, `_footer`, `separator` - These will produce styled decorations for - display to the user. They are useful for creating listings and forms with colors adjustable per- -user. - -### Defining your own command classes - -Beyond the properties Evennia always assigns to the command at run-time (listed above), your job is -to define the following class properties: - -- `key` (string) - the identifier for the command, like `look`. This should (ideally) be unique. A -key can consist of more than one word, like "press button" or "pull left lever". Note that *both* -`key` and `aliases` below determine the identity of a command. So two commands are considered if -either matches. This is important for merging cmdsets described below. -- `aliases` (optional list) - a list of alternate names for the command (`["glance", "see", "l"]`). -Same name rules as for `key` applies. -- `locks` (string) - a [lock definition](./Locks.md), usually on the form `cmd:`. Locks is a -rather big topic, so until you learn more about locks, stick to giving the lockstring `"cmd:all()"` -to make the command available to everyone (if you don't provide a lock string, this will be assigned -for you). -- `help_category` (optional string) - setting this helps to structure the auto-help into categories. -If none is set, this will be set to *General*. -- `save_for_next` (optional boolean). This defaults to `False`. If `True`, a copy of this command -object (along with any changes you have done to it) will be stored by the system and can be accessed -by the next command by retrieving `self.caller.ndb.last_cmd`. The next run command will either clear -or replace the storage. -- `arg_regex` (optional raw string): Used to force the parser to limit itself and tell it when the -command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is -done with a regular expression. [See the arg_regex section](./Commands.md#on-arg_regex) for the details. -- `auto_help` (optional boolean). Defaults to `True`. This allows for turning off the [auto-help -system](./Help-System.md#command-auto-help-system) on a per-command basis. This could be useful if you -either want to write your help entries manually or hide the existence of a command from `help`'s -generated list. -- `is_exit` (bool) - this marks the command as being used for an in-game exit. This is, by default, -set by all Exit objects and you should not need to set it manually unless you make your own Exit -system. It is used for optimization and allows the cmdhandler to easily disregard this command when -the cmdset has its `no_exits` flag set. -- `is_channel` (bool)- this marks the command as being used for an in-game channel. This is, by -default, set by all Channel objects and you should not need to set it manually unless you make your -own Channel system. is used for optimization and allows the cmdhandler to easily disregard this -command when its cmdset has its `no_channels` flag set. -- `msg_all_sessions` (bool): This affects the behavior of the `Command.msg` method. If unset -(default), calling `self.msg(text)` from the Command will always only send text to the Session that -actually triggered this Command. If set however, `self.msg(text)` will send to all Sessions relevant -to the object this Command sits on. Just which Sessions receives the text depends on the object and -the server's `MULTISESSION_MODE`. - -You should also implement at least two methods, `parse()` and `func()` (You could also implement -`perm()`, but that's not needed unless you want to fundamentally change how access checks work). - -- `at_pre_cmd()` is called very first on the command. If this function returns anything that -evaluates to `True` the command execution is aborted at this point. -- `parse()` is intended to parse the arguments (`self.args`) of the function. You can do this in any -way you like, then store the result(s) in variable(s) on the command object itself (i.e. on `self`). -To take an example, the default mux-like system uses this method to detect "command switches" and -store them as a list in `self.switches`. Since the parsing is usually quite similar inside a command -scheme you should make `parse()` as generic as possible and then inherit from it rather than re- -implementing it over and over. In this way, the default `MuxCommand` class implements a `parse()` -for all child commands to use. -- `func()` is called right after `parse()` and should make use of the pre-parsed input to actually -do whatever the command is supposed to do. This is the main body of the command. The return value -from this method will be returned from the execution as a Twisted Deferred. -- `at_post_cmd()` is called after `func()` to handle eventual cleanup. - -Finally, you should always make an informative [doc -string](http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring) (`__doc__`) at the top of your -class. This string is dynamically read by the [Help System](./Help-System.md) to create the help entry -for this command. You should decide on a way to format your help and stick to that. - -Below is how you define a simple alternative "`smile`" command: - -```python -from evennia import Command - -class CmdSmile(Command): - """ - A smile command - - Usage: - smile [at] [] - grin [at] [] - - Smiles to someone in your vicinity or to the room - in general. - - (This initial string (the __doc__ string) - is also used to auto-generate the help - for this command) - """ - - key = "smile" - aliases = ["smile at", "grin", "grin at"] - locks = "cmd:all()" - help_category = "General" - - def parse(self): - "Very trivial parser" - self.target = self.args.strip() - - def func(self): - "This actually does things" - caller = self.caller - - if not self.target or self.target == "here": - string = f"{caller.key} smiles" - else: - target = caller.search(self.target) - if not target: - return - string = f"{caller.key} smiles at {target.key}" - - caller.location.msg_contents(string) - -``` - -The power of having commands as classes and to separate `parse()` and `func()` -lies in the ability to inherit functionality without having to parse every -command individually. For example, as mentioned the default commands all -inherit from `MuxCommand`. `MuxCommand` implements its own version of `parse()` -that understands all the specifics of MUX-like commands. Almost none of the -default commands thus need to implement `parse()` at all, but can assume the -incoming string is already split up and parsed in suitable ways by its parent. - -Before you can actually use the command in your game, you must now store it -within a *command set*. See the [Command Sets](./Command-Sets.md) page. - -### On arg_regex - -The command parser is very general and does not require a space to end your command name. This means -that the alias `:` to `emote` can be used like `:smiles` without modification. It also means -`getstone` will get you the stone (unless there is a command specifically named `getstone`, then -that will be used). If you want to tell the parser to require a certain separator between the -command name and its arguments (so that `get stone` works but `getstone` gives you a 'command not -found' error) you can do so with the `arg_regex` property. - -The `arg_regex` is a [raw regular expression string](http://docs.python.org/library/re.html). The -regex will be compiled by the system at runtime. This allows you to customize how the part -*immediately following* the command name (or alias) must look in order for the parser to match for -this command. Some examples: - -- `commandname argument` (`arg_regex = r"\s.+"`): This forces the parser to require the command name -to be followed by one or more spaces. Whatever is entered after the space will be treated as an -argument. However, if you'd forget the space (like a command having no arguments), this would *not* -match `commandname`. -- `commandname` or `commandname argument` (`arg_regex = r"\s.+|$"`): This makes both `look` and -`look me` work but `lookme` will not. -- `commandname/switches arguments` (`arg_regex = r"(?:^(?:\s+|\/).*$)|^$"`. If you are using -Evennia's `MuxCommand` Command parent, you may wish to use this since it will allow `/switche`s to -work as well as having or not having a space. - -The `arg_regex` allows you to customize the behavior of your commands. You can put it in the parent -class of your command to customize all children of your Commands. However, you can also change the -base default behavior for all Commands by modifying `settings.COMMAND_DEFAULT_ARG_REGEX`. - -## Exiting a command - -Normally you just use `return` in one of your Command class' hook methods to exit that method. That -will however still fire the other hook methods of the Command in sequence. That's usually what you -want but sometimes it may be useful to just abort the command, for example if you find some -unacceptable input in your parse method. To exit the command this way you can raise -`evennia.InterruptCommand`: - -```python -from evennia import InterruptCommand - -class MyCommand(Command): - - # ... - - def parse(self): - # ... - # if this fires, `func()` and `at_post_cmd` will not - # be called at all - raise InterruptCommand() - -``` - -## Pauses in commands - -Sometimes you want to pause the execution of your command for a little while before continuing - -maybe you want to simulate a heavy swing taking some time to finish, maybe you want the echo of your -voice to return to you with an ever-longer delay. Since Evennia is running asynchronously, you -cannot use `time.sleep()` in your commands (or anywhere, really). If you do, the *entire game* will -be frozen for everyone! So don't do that. Fortunately, Evennia offers a really quick syntax for -making pauses in commands. - -In your `func()` method, you can use the `yield` keyword. This is a Python keyword that will freeze -the current execution of your command and wait for more before processing. - -> Note that you *cannot* just drop `yield` into any code and expect it to pause. Evennia will only -pause for you if you `yield` inside the Command's `func()` method. Don't expect it to work anywhere -else. - -Here's an example of a command using a small pause of five seconds between messages: - -```python -from evennia import Command - -class CmdWait(Command): - """ - A dummy command to show how to wait - - Usage: - wait - - """ - - key = "wait" - locks = "cmd:all()" - help_category = "General" - - def func(self): - """Command execution.""" - self.msg("Starting to wait ...") - yield 5 - self.msg("... This shows after 5 seconds. Waiting ...") - yield 2 - self.msg("... And now another 2 seconds have passed.") -``` - -The important line is the `yield 5` and `yield 2` lines. It will tell Evennia to pause execution -here and not continue until the number of seconds given has passed. - -There are two things to remember when using `yield` in your Command's `func` method: - -1. The paused state produced by the `yield` is not saved anywhere. So if the server reloads in the -middle of your command pausing, it will *not* resume when the server comes back up - the remainder -of the command will never fire. So be careful that you are not freezing the character or account in -a way that will not be cleared on reload. -2. If you use `yield` you may not also use `return ` in your `func` method. You'll get an -error explaining this. This is due to how Python generators work. You can however use a "naked" -`return` just fine. Usually there is no need for `func` to return a value, but if you ever do need -to mix `yield` with a final return value in the same `func`, look at [twisted.internet.defer.returnV -alue](https://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#returnValue). - -## Asking for user input - -The `yield` keyword can also be used to ask for user input. Again you can't -use Python's `input` in your command, for it would freeze Evennia for -everyone while waiting for that user to input their text. Inside a Command's -`func` method, the following syntax can also be used: - -```python -answer = yield("Your question") -``` - -Here's a very simple example: - -```python -class CmdConfirm(Command): - - """ - A dummy command to show confirmation. - - Usage: - confirm - - """ - - key = "confirm" - - def func(self): - answer = yield("Are you sure you want to go on?") - if answer.strip().lower() in ("yes", "y"): - self.msg("Yes!") - else: - self.msg("No!") -``` - -This time, when the user enters the 'confirm' command, she will be asked if she wants to go on. -Entering 'yes' or "y" (regardless of case) will give the first reply, otherwise the second reply -will show. - -> Note again that the `yield` keyword does not store state. If the game reloads while waiting for -the user to answer, the user will have to start over. It is not a good idea to use `yield` for -important or complex choices, a persistent [EvMenu](./EvMenu.md) might be more appropriate in this case. - -## System commands - -*Note: This is an advanced topic. Skip it if this is your first time learning about commands.* - -There are several command-situations that are exceptional in the eyes of the server. What happens if -the account enters an empty string? What if the 'command' given is infact the name of a channel the -user wants to send a message to? Or if there are multiple command possibilities? - -Such 'special cases' are handled by what's called *system commands*. A system command is defined -in the same way as other commands, except that their name (key) must be set to one reserved by the -engine (the names are defined at the top of `evennia/commands/cmdhandler.py`). You can find (unused) -implementations of the system commands in `evennia/commands/default/system_commands.py`. Since these -are not (by default) included in any `CmdSet` they are not actually used, they are just there for -show. When the special situation occurs, Evennia will look through all valid `CmdSet`s for your -custom system command. Only after that will it resort to its own, hard-coded implementation. - -Here are the exceptional situations that triggers system commands. You can find the command keys -they use as properties on `evennia.syscmdkeys`: - -- No input (`syscmdkeys.CMD_NOINPUT`) - the account just pressed return without any input. Default -is to do nothing, but it can be useful to do something here for certain implementations such as line -editors that interpret non-commands as text input (an empty line in the editing buffer). -- Command not found (`syscmdkeys.CMD_NOMATCH`) - No matching command was found. Default is to -display the "Huh?" error message. -- Several matching commands where found (`syscmdkeys.CMD_MULTIMATCH`) - Default is to show a list of -matches. -- User is not allowed to execute the command (`syscmdkeys.CMD_NOPERM`) - Default is to display the -"Huh?" error message. -- Channel (`syscmdkeys.CMD_CHANNEL`) - This is a [Channel](./Communications.md) name of a channel you are -subscribing to - Default is to relay the command's argument to that channel. Such commands are -created by the Comm system on the fly depending on your subscriptions. -- New session connection (`syscmdkeys.CMD_LOGINSTART`). This command name should be put in the -`settings.CMDSET_UNLOGGEDIN`. Whenever a new connection is established, this command is always -called on the server (default is to show the login screen). - -Below is an example of redefining what happens when the account doesn't provide any input (e.g. just -presses return). Of course the new system command must be added to a cmdset as well before it will -work. - -```python - from evennia import syscmdkeys, Command - - class MyNoInputCommand(Command): - "Usage: Just press return, I dare you" - key = syscmdkeys.CMD_NOINPUT - def func(self): - self.caller.msg("Don't just press return like that, talk to me!") -``` - -## Dynamic Commands - -*Note: This is an advanced topic.* - -Normally Commands are created as fixed classes and used without modification. There are however -situations when the exact key, alias or other properties is not possible (or impractical) to pre- -code ([Exits](./Commands.md#exits) is an example of this). - -To create a command with a dynamic call signature, first define the command body normally in a class -(set your `key`, `aliases` to default values), then use the following call (assuming the command -class you created is named `MyCommand`): - -```python - cmd = MyCommand(key="newname", - aliases=["test", "test2"], - locks="cmd:all()", - ...) -``` - -*All* keyword arguments you give to the Command constructor will be stored as a property on the -command object. This will overload existing properties defined on the parent class. - -Normally you would define your class and only overload things like `key` and `aliases` at run-time. -But you could in principle also send method objects (like `func`) as keyword arguments in order to -make your command completely customized at run-time. - -## Exits - -*Note: This is an advanced topic.* - -Exits are examples of the use of a [Dynamic Command](./Commands.md#dynamic-commands). - -The functionality of [Exit](./Objects.md) objects in Evennia is not hard-coded in the engine. Instead -Exits are normal [typeclassed](./Typeclasses.md) objects that auto-create a [CmdSet](./Command-Sets.md) on -themselves when they load. This cmdset has a single dynamically created Command with the same -properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit, -this dynamic exit-command is triggered and (after access checks) moves the Character to the exit's -destination. -Whereas you could customize the Exit object and its command to achieve completely different -behaviour, you will usually be fine just using the appropriate `traverse_*` hooks on the Exit -object. But if you are interested in really changing how things work under the hood, check out -`evennia/objects/objects.py` for how the `Exit` typeclass is set up. - -## Command instances are re-used - -*Note: This is an advanced topic that can be skipped when first learning about Commands.* - -A Command class sitting on an object is instantiated once and then re-used. So if you run a command -from object1 over and over you are in fact running the same command instance over and over (if you -run the same command but sitting on object2 however, it will be a different instance). This is -usually not something you'll notice, since every time the Command-instance is used, all the relevant -properties on it will be overwritten. But armed with this knowledge you can implement some of the -more exotic command mechanism out there, like the command having a 'memory' of what you last entered -so that you can back-reference the previous arguments etc. - -> Note: On a server reload, all Commands are rebuilt and memory is flushed. - -To show this in practice, consider this command: - -```python -class CmdTestID(Command): - key = "testid" - - def func(self): - - if not hasattr(self, "xval"): - self.xval = 0 - self.xval += 1 - - self.caller.msg("Command memory ID: {} (xval={})".format(id(self), self.xval)) - -``` - -Adding this to the default character cmdset gives a result like this in-game: - -``` -> testid -Command memory ID: 140313967648552 (xval=1) -> testid -Command memory ID: 140313967648552 (xval=2) -> testid -Command memory ID: 140313967648552 (xval=3) -``` - -Note how the in-memory address of the `testid` command never changes, but `xval` keeps ticking up. - -## Dynamically created commands - -*This is also an advanced topic.* - -Commands can also be created and added to a cmdset on the fly. Creating a class instance with a -keyword argument, will assign that keyword argument as a property on this paricular command: - -``` -class MyCmdSet(CmdSet): - - def at_cmdset_creation(self): - - self.add(MyCommand(myvar=1, foo="test") - -``` - -This will start the `MyCommand` with `myvar` and `foo` set as properties (accessable as `self.myvar` -and `self.foo`). How they are used is up to the Command. Remember however the discussion from the -previous section - since the Command instance is re-used, those properties will *remain* on the -command as long as this cmdset and the object it sits is in memory (i.e. until the next reload). -Unless `myvar` and `foo` are somehow reset when the command runs, they can be modified and that -change will be remembered for subsequent uses of the command. - - -## How commands actually work - -*Note: This is an advanced topic mainly of interest to server developers.* - -Any time the user sends text to Evennia, the server tries to figure out if the text entered -corresponds to a known command. This is how the command handler sequence looks for a logged-in user: - -1. A user enters a string of text and presses enter. -2. The user's Session determines the text is not some protocol-specific control sequence or OOB -command, but sends it on to the command handler. -3. Evennia's *command handler* analyzes the Session and grabs eventual references to Account and -eventual puppeted Characters (these will be stored on the command object later). The *caller* -property is set appropriately. -4. If input is an empty string, resend command as `CMD_NOINPUT`. If no such command is found in -cmdset, ignore. -5. If command.key matches `settings.IDLE_COMMAND`, update timers but don't do anything more. -6. The command handler gathers the CmdSets available to *caller* at this time: - - The caller's own currently active CmdSet. - - CmdSets defined on the current account, if caller is a puppeted object. - - CmdSets defined on the Session itself. - - The active CmdSets of eventual objects in the same location (if any). This includes commands -on [Exits](./Objects.md#exits). - - Sets of dynamically created *System commands* representing available -[Communications](./Communications.md#channels). -7. All CmdSets *of the same priority* are merged together in groups. Grouping avoids order- -dependent issues of merging multiple same-prio sets onto lower ones. -8. All the grouped CmdSets are *merged* in reverse priority into one combined CmdSet according to -each set's merge rules. -9. Evennia's *command parser* takes the merged cmdset and matches each of its commands (using its -key and aliases) against the beginning of the string entered by *caller*. This produces a set of -candidates. -10. The *cmd parser* next rates the matches by how many characters they have and how many percent -matches the respective known command. Only if candidates cannot be separated will it return multiple -matches. - - If multiple matches were returned, resend as `CMD_MULTIMATCH`. If no such command is found in -cmdset, return hard-coded list of matches. - - If no match was found, resend as `CMD_NOMATCH`. If no such command is found in cmdset, give -hard-coded error message. -11. If a single command was found by the parser, the correct command object is plucked out of -storage. This usually doesn't mean a re-initialization. -12. It is checked that the caller actually has access to the command by validating the *lockstring* -of the command. If not, it is not considered as a suitable match and `CMD_NOMATCH` is triggered. -13. If the new command is tagged as a channel-command, resend as `CMD_CHANNEL`. If no such command -is found in cmdset, use hard-coded implementation. -14. Assign several useful variables to the command instance (see previous sections). -15. Call `at_pre_command()` on the command instance. -16. Call `parse()` on the command instance. This is fed the remainder of the string, after the name -of the command. It's intended to pre-parse the string into a form useful for the `func()` method. -17. Call `func()` on the command instance. This is the functional body of the command, actually -doing useful things. -18. Call `at_post_command()` on the command instance. - -## Assorted notes - -The return value of `Command.func()` is a Twisted -[deferred](http://twistedmatrix.com/documents/current/core/howto/defer.html). -Evennia does not use this return value at all by default. If you do, you must -thus do so asynchronously, using callbacks. - -```python - # in command class func() - def callback(ret, caller): - caller.msg("Returned is %s" % ret) - deferred = self.execute_command("longrunning") - deferred.addCallback(callback, self.caller) -``` - -This is probably not relevant to any but the most advanced/exotic designs (one might use it to -create a "nested" command structure for example). - -The `save_for_next` class variable can be used to implement state-persistent commands. For example -it can make a command operate on "it", where it is determined by what the previous command operated -on. diff --git a/docs/0.9.5/_sources/Communications.md.txt b/docs/0.9.5/_sources/Communications.md.txt deleted file mode 100644 index 3c3056c54f..0000000000 --- a/docs/0.9.5/_sources/Communications.md.txt +++ /dev/null @@ -1,113 +0,0 @@ -# Communications - - -Apart from moving around in the game world and talking, players might need other forms of -communication. This is offered by Evennia's `Comm` system. Stock evennia implements a 'MUX-like' -system of channels, but there is nothing stopping you from changing things to better suit your -taste. - -Comms rely on two main database objects - `Msg` and `Channel`. There is also the `TempMsg` which -mimics the API of a `Msg` but has no connection to the database. - -## Msg - -The `Msg` object is the basic unit of communication in Evennia. A message works a little like an -e-mail; it always has a sender (a [Account](./Accounts.md)) and one or more recipients. The recipients -may be either other Accounts, or a *Channel* (see below). You can mix recipients to send the message -to both Channels and Accounts if you like. - -Once created, a `Msg` is normally not changed. It is peristently saved in the database. This allows -for comprehensive logging of communications. This could be useful for allowing senders/receivers to -have 'mailboxes' with the messages they want to keep. - -### Properties defined on `Msg` - -- `senders` - this is a reference to one or many [Account](./Accounts.md) or [Objects](./Objects.md) (normally -*Characters*) sending the message. This could also be an *External Connection* such as a message -coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be -mixed in any combination. -- `receivers` - a list of target [Accounts](./Accounts.md), [Objects](./Objects.md) (usually *Characters*) or -*Channels* to send the message to. The types of receivers can be mixed in any combination. -- `header` - this is a text field for storing a title or header for the message. -- `message` - the actual text being sent. -- `date_sent` - when message was sent (auto-created). -- `locks` - a [lock definition](./Locks.md). -- `hide_from` - this can optionally hold a list of objects, accounts or channels to hide this `Msg` -from. This relationship is stored in the database primarily for optimization reasons, allowing for -quickly post-filter out messages not intended for a given target. There is no in-game methods for -setting this, it's intended to be done in code. - -You create new messages in code using `evennia.create_message` (or -`evennia.utils.create.create_message.`) - -## TempMsg - -`evennia.comms.models` also has `TempMsg` which mimics the API of `Msg` but is not connected to the -database. TempMsgs are used by Evennia for channel messages by default. They can be used for any -system expecting a `Msg` but when you don't actually want to save anything. - -## Channels - -Channels are [Typeclassed](./Typeclasses.md) entities, which mean they can be easily extended and their -functionality modified. To change which channel typeclass Evennia uses, change -settings.BASE_CHANNEL_TYPECLASS. - -Channels act as generic distributors of messages. Think of them as "switch boards" redistributing -`Msg` or `TempMsg` objects. Internally they hold a list of "listening" objects and any `Msg` (or -`TempMsg`) sent to the channel will be distributed out to all channel listeners. Channels have -[Locks](./Locks.md) to limit who may listen and/or send messages through them. - -The *sending* of text to a channel is handled by a dynamically created [Command](./Commands.md) that -always have the same name as the channel. This is created for each channel by the global -`ChannelHandler`. The Channel command is added to the Account's cmdset and normal command locks are -used to determine which channels are possible to write to. When subscribing to a channel, you can -then just write the channel name and the text to send. - -The default ChannelCommand (which can be customized by pointing `settings.CHANNEL_COMMAND_CLASS` to -your own command), implements a few convenient features: - - - It only sends `TempMsg` objects. Instead of storing individual entries in the database it instead -dumps channel output a file log in `server/logs/channel_.log`. This is mainly for -practical reasons - we find one rarely need to query individual Msg objects at a later date. Just -stupidly dumping the log to a file also means a lot less database overhead. - - It adds a `/history` switch to view the 20 last messages in the channel. These are read from the -end of the log file. One can also supply a line number to start further back in the file (but always -20 entries at a time). It's used like this: - - > public/history - > public/history 35 - - -There are two default channels created in stock Evennia - `MudInfo` and `Public`. `MudInfo` -receives server-related messages meant for Admins whereas `Public` is open to everyone to chat on -(all new accounts are automatically joined to it when logging in, it is useful for asking -questions). The default channels are defined by the `DEFAULT_CHANNELS` list (see -`evennia/settings_default.py` for more details). - -You create new channels with `evennia.create_channel` (or `evennia.utils.create.create_channel`). - -In code, messages are sent to a channel using the `msg` or `tempmsg` methods of channels: - - channel.msg(msgobj, header=None, senders=None, persistent=True) - -The argument `msgobj` can be either a string, a previously constructed `Msg` or a `TempMsg` - in the -latter cases all the following keywords are ignored since the message objects already contains all -this information. If `msgobj` is a string, the other keywords are used for creating a new `Msg` or -`TempMsg` on the fly, depending on if `persistent` is set or not. By default, a `TempMsg` is emitted -for channel communication (since the default ChannelCommand instead logs to a file). - -```python - # assume we have a 'sender' object and a channel named 'mychan' - - # manually sending a message to a channel - mychan.msg("Hello!", senders=[sender]) -``` - -### Properties defined on `Channel` - -- `key` - main name for channel -- `aliases` - alternative native names for channels -- `desc` - optional description of channel (seen in listings) -- `keep_log` (bool) - if the channel should store messages (default) -- `locks` - A [lock definition](./Locks.md). Channels normally use the access_types `send, control` and -`listen`. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Connection-Screen.md.txt b/docs/0.9.5/_sources/Connection-Screen.md.txt deleted file mode 100644 index bb15919f30..0000000000 --- a/docs/0.9.5/_sources/Connection-Screen.md.txt +++ /dev/null @@ -1,36 +0,0 @@ -# Connection Screen - - -When you first connect to your game you are greeted by Evennia's default connection screen. - - - ============================================================== - Welcome to Evennia, version Beta-ra4d24e8a3cab+! - - If you have an existing account, connect to it by typing: - connect - If you need to create an account, type (without the <>'s): - create - - If you have spaces in your username, enclose it in quotes. - Enter help for more info. look will re-show this screen. - ============================================================== - -Effective, but not very exciting. You will most likely want to change this to be more unique for -your game. This is simple: - -1. Edit `mygame/server/conf/connection_screens.py`. -1. [Reload](./Start-Stop-Reload.md) Evennia. - -Evennia will look into this module and locate all *globally defined strings* in it. These strings -are used as the text in your connection screen and are shown to the user at startup. If more than -one such string/screen is defined in the module, a *random* screen will be picked from among those -available. - -### Commands available at the Connection Screen - -You can also customize the [Commands](./Commands.md) available to use while the connection screen is -shown (`connect`, `create` etc). These commands are a bit special since when the screen is running -the account is not yet logged in. A command is made available at the login screen by adding them to -`UnloggedinCmdSet` in `mygame/commands/default_cmdset.py`. See [Commands](./Commands.md) and the -tutorial section on how to add new commands to a default command set. diff --git a/docs/0.9.5/_sources/Continuous-Integration.md.txt b/docs/0.9.5/_sources/Continuous-Integration.md.txt deleted file mode 100644 index f4bf7e5199..0000000000 --- a/docs/0.9.5/_sources/Continuous-Integration.md.txt +++ /dev/null @@ -1,222 +0,0 @@ -# Continuous Integration - -One of the advantages of Evennia over traditional MUSH development systems is that Evennia is -capable of integrating into enterprise level integration environments and source control. Because of -this, it can also be the subject of automation for additional convenience, allowing a more -streamlined development environment. - -## What is Continuous Integration? - -[Continuous Integration (CI)](https://www.thoughtworks.com/continuous-integration) is a development -practice that requires developers to integrate code into a shared repository several times a day. -Each check-in is then verified by an automated build, allowing teams to detect problems early. - -For Evennia, continuous integration allows an automated build process to: -* Pull down a latest build from Source Control. -* Run migrations on the backing SQL database. -* Automate additional unique tasks for that project. -* Run unit tests. -* Publish those files to the server directory -* Reload the game. - -## Preparation -To prepare a CI environment for your `MU*`, it will be necessary to set up some prerequisite -software for your server. - -Among those you will need: -* A Continuous Integration Environment. - * I recommend [TeamCity](https://www.jetbrains.com/teamcity/) which has an in-depth [Setup -Guide](https://confluence.jetbrains.com/display/TCD8/Installing+and+Configuring+the+TeamCity+Server) -* [Source Control](./Version-Control.md) - * This could be Git or SVN or any other available SC. - -## Linux TeamCity Setup -For this part of the guide, an example setup will be provided for administrators running a TeamCity -build integration environment on Linux. - -After meeting the preparation steps for your specific environment, log on to your teamcity interface -at `http://:8111/`. - -Create a new project named "Evennia" and in it construct a new template called continuous- -integration. - -### A Quick Overview -Templates are fancy objects in TeamCity that allow an administrator to define build steps that are -shared between one or more build projects. Assigning a VCS Root (Source Control) is unnecessary at -this stage, primarily you'll be worrying about the build steps and your default parameters (both -visible on the tabs to the left.) - -### Template Setup - -In this template, you'll be outlining the steps necessary to build your specific game. (A number of -sample scripts are provided under this section below!) Click Build Steps and prepare your general -flow. For this example, we will be doing a few basic example steps: - -* Transforming the Settings.py file - * We do this to update ports or other information that make your production environment unique - from your development environment. -* Making migrations and migrating the game database. -* Publishing the game files. -* Reloading the server. - -For each step we'll being use the "Command Line Runner" (a fancy name for a shell script executor). - -* Create a build step with the name: Transform Configuration -* For the script add: - - ```bash - #!/bin/bash - # Replaces the game configuration with one - # appropriate for this deployment. - - CONFIG="%system.teamcity.build.checkoutDir%/server/conf/settings.py" - MYCONF="%system.teamcity.build.checkoutDir%/server/conf/my.cnf" - - sed -e 's/TELNET_PORTS = [4000]/TELNET_PORTS = [%game.ports%]/g' "$CONFIG" > "$CONFIG".tmp && mv -"$CONFIG".tmp "$CONFIG" - sed -e 's/WEBSERVER_PORTS = [(4001, 4002)]/WEBSERVER_PORTS = [%game.webports%]/g' "$CONFIG" > -"$CONFIG".tmp && mv "$CONFIG".tmp "$CONFIG" - - # settings.py MySQL DB configuration - echo Configuring Game Database... - echo "" >> "$CONFIG" - echo "######################################################################" >> "$CONFIG" - echo "# MySQL Database Configuration" >> "$CONFIG" - echo "######################################################################" >> "$CONFIG" - - echo "DATABASES = {" >> "$CONFIG" - echo " 'default': {" >> "$CONFIG" - echo " 'ENGINE': 'django.db.backends.mysql'," >> "$CONFIG" - echo " 'OPTIONS': {" >> "$CONFIG" - echo " 'read_default_file': 'server/conf/my.cnf'," >> "$CONFIG" - echo " }," >> "$CONFIG" - echo " }" >> "$CONFIG" - echo "}" >> "$CONFIG" - - # Create the My.CNF file. - echo "[client]" >> "$MYCONF" - echo "database = %mysql.db%" >> "$MYCONF" - echo "user = %mysql.user%" >> "$MYCONF" - echo "password = %mysql.pass%" >> "$MYCONF" - echo "default-character-set = utf8" >> "$MYCONF" - ``` - -If you look at the parameters side of the page after saving this script, you'll notice that some new -parameters have been populated for you. This is because we've included new teamcity configuration -parameters that are populated when the build itself is ran. When creating projects that inherit this -template, we'll be able to fill in or override those parameters for project-specific configuration. - -* Go ahead and create another build step called "Make Database Migration" - * If you're using SQLLite on your game, it will be prudent to change working directory on this -step to: %game.dir% -* In this script include: - - ```bash - #!/bin/bash - # Update the DB migration - - LOGDIR="server/logs" - - . %evenv.dir%/bin/activate - - # Check that the logs directory exists. - if [ ! -d "$LOGDIR" ]; then - # Control will enter here if $LOGDIR doesn't exist. - mkdir "$LOGDIR" - fi - - evennia makemigrations - ``` - -* Create yet another build step, this time named: "Execute Database Migration": - * If you're using SQLLite on your game, it will be prudent to change working directory on this -step to: %game.dir% - ```bash - #!/bin/bash - # Apply the database migration. - - LOGDIR="server/logs" - - . %evenv.dir%/bin/activate - - # Check that the logs directory exists. - if [ ! -d "$LOGDIR" ]; then - # Control will enter here if $LOGDIR doesn't exist. - mkdir "$LOGDIR" - fi - - evennia migrate - - ``` - -Our next build step is where we actually publish our build. Up until now, all work on game has been -done in a 'work' directory on TeamCity's build agent. From that directory we will now copy our files -to where our game actually exists on the local server. - -* Create a new build step called "Publish Build": - * If you're using SQLLite on your game, be sure to order this step ABOVE the Database Migration -steps. The build order will matter! - ```bash - #!/bin/bash - # Publishes the build to the proper build directory. - - DIRECTORY="%game.dir%" - - if [ ! -d "$DIRECTORY" ]; then - # Control will enter here if $DIRECTORY doesn't exist. - mkdir "$DIRECTORY" - fi - - # Copy all the files. - cp -ruv %teamcity.build.checkoutDir%/* "$DIRECTORY" - chmod -R 775 "$DIRECTORY" - - ``` - -Finally the last script will reload our game for us. - -* Create a new script called "Reload Game": - * The working directory on this build step will be: %game.dir% - ```bash - #!/bin/bash - # Apply the database migration. - - LOGDIR="server/logs" - PIDDIR="server/server.pid" - - . %evenv.dir%/bin/activate - - # Check that the logs directory exists. - if [ ! -d "$LOGDIR" ]; then - # Control will enter here if $LOGDIR doesn't exist. - mkdir "$LOGDIR" - fi - - # Check that the server is running. - if [ -d "$PIDDIR" ]; then - # Control will enter here if the game is running. - evennia reload - fi - ``` - -Now the template is ready for use! It would be useful this time to revisit the parameters page and -set the evenv parameter to the directory where your virtualenv exists: IE "/srv/mush/evenv". - -### Creating the Project - -Now it's time for the last few steps to set up a CI environment. - -* Return to the Evennia Project overview/administration page. -* Create a new Sub-Project called "Production" - * This will be the category that holds our actual game. -* Create a new Build Configuration in Production with the name of your MUSH. - * Base this configuration off of the continuous-integration template we made earlier. -* In the build configuration, enter VCS roots and create a new VCS root that points to the -branch/version control that you are using. -* Go to the parameters page and fill in the undefined parameters for your specific configuration. -* If you wish for the CI to run every time a commit is made, go to the VCS triggers and add one for -"On Every Commit". - -And you're done! At this point, you can return to the project overview page and queue a new build -for your game. If everything was set up correctly, the build will complete successfully. Additional -build steps could be added or removed at this point, adding some features like Unit Testing or more! diff --git a/docs/0.9.5/_sources/Contributing-Docs.md.txt b/docs/0.9.5/_sources/Contributing-Docs.md.txt deleted file mode 100644 index a7236cee9c..0000000000 --- a/docs/0.9.5/_sources/Contributing-Docs.md.txt +++ /dev/null @@ -1,681 +0,0 @@ -# Contributing to Evennia Docs - - -```{warning} -This system is still WIP and many things are bound to change! -``` - -Contributing to the docs is is like [contributing to the rest of Evennia][contributing]: Check out the branch of Evennia -you want to edit the documentation for. Create your own work-branch, make your changes to files -in `evennia/docs/source/` and make a PR for it! - -The documentation source files are `*.md` (Markdown) files found in `evennia/docs/source/`. -Markdown files are simple text files that can be edited with a normal text editor. They can also -contain raw HTML directives (but that is very rarely needed). They use -the [Markdown][commonmark] syntax with [MyST extensions][MyST]. - -```{important} -You do _not_ need to be able to test/build the docs locally to contribute a documentation PR. -We'll resolve any issues when we merge and build documentation. If you still want to build -the docs for yourself, instructions are [at the end of this document](#building-the-docs-locally). -``` - -## Source file structure - -The sources are organized into several rough categories, with only a few administrative documents -at the root of `evennia/docs/source/`. The folders are named in singular form since they will -primarily be accessed as link refs (e.g. `Component/Accounts`) - -- `source/Components/` are docs describing separate Evennia building blocks, that is, things - that you can import and use. This extends and elaborates on what can be found out by reading - the api docs themselves. Example are documentation for `Accounts`, `Objects` and `Commands`. -- `source/Concepts/` describes how larger-scale features of Evennia hang together - things that - can't easily be broken down into one isolated component. This can be general descriptions of - how Models and Typeclasses interact to the path a message takes from the client to the server - and back. -- `source/Setup/` holds detailed docs on installing, running and maintaining the Evennia server and - the infrastructure around it. -- `source/Coding/` has help on how to interact with, use and navigate the Evennia codebase itself. - This also has non-Evennia-specific help on general development concepts and how to set up a sane - development environment. -- `source/Contribs/` holds documentation specifically for packages in the `evennia/contribs/` folder. - Any contrib-specific tutorials will be found here instead of in `Howtos` -- `source/Howtos/` holds docs that describe how to achieve a specific goal, effect or - result in Evennia. This is often on a tutorial or FAQ form and will refer to the rest of the - documentation for further reading. - - `source/Howtos/Starting/` holds all documents part of the initial tutorial sequence. - - - Other files and folders: - - `source/api/` contains the auto-generated API documentation as `.rst` files. Don't edit these - files manually, your changes will be lost. To refer to these files, use `api:` followed by - the Python path, for example `[rpsystem contrib](evennia.contrib.rpsystem)`. - - `source/_templates` and `source/_static` should not be modified unless adding a new doc-page - feature or changing the look of the HTML documentation. - - `conf.py` holds the Sphinx configuration. It should usually not be modified except to update - the Evennia version on a new branch. - - -# Editing syntax - -The format used for Evennia's docs is [Markdown][commonmark-help] (Commonmark). While markdown -supports a few alternative forms for some of these, we try to stick to the below forms for consistency. - -## Italic/Bold - -We generally use underscores for italics and double-asterisks for bold: - -- `_Italic text_` - _Italic text_ -- `**Bold Text**` - **Bold text** - -## Headings - -We use `#` to indicate sections/headings. The more `#` the more of a sub-heading it is (will get -smaller and smaller font). - -- `# Heading` -- `## SubHeading` -- `### SubSubHeading` -- `#### SubSubSubHeading` - -> Don't use the same heading/subheading name more than once in one page. While Markdown -does not prevent it, it will make it impossible to refer to that heading uniquely. -The Evennia documentation preparser will detect this and give you an error. - -## Lists - -One can create both bullet-point lists and numbered lists: - -``` -- first bulletpoint -- second bulletpoint -- third bulletpoint -``` - -- first bulletpoint -- second bulletpoint -- third bulletpoint - -``` -1. Numbered point one -2. Numbered point two -3. Numbered point three -``` - -1. Numbered point one -2. Numbered point two -3. Numbered point three - -## Blockquotes - -A blockquote will create an indented block. It's useful for emphasis and is -added by starting one or more lines with `>`. For 'notes' you can also use -an explicit [Note](#note). - -``` -> This is an important -> thing to remember. -``` - -> Note: This is an important -> thing to remember. - -## Links - -The link syntax is `[linktext](url_or_ref)` - this gives a clickable link [linktext](#links). - -### Internal links - -Most links will be to other pages of the documentation or to Evennia's API docs. Each document -heading can be referenced. The reference always starts with `#`. The heading-name is always -given in lowercase and ignores any non-letters. Spaces in the heading title are replaced with -a single dash `-`. - -As an example, let's assume the following is the contents of a file `Menu-stuff.md`: - -``` -# Menu items - -Some text... - -## A yes/no? example - -Some more text... -``` - -- From _inside the same file_ you can refer to each heading as - - [menus](#menu-items) - [example](#a-yesno-example) - -- From _another file_, you reference them as as - - [menus](Menu-Stuff.md#menu-items) - [example](Menu-Stuff.md#a-yesno-example) - -> It's fine to not include the `.md` file ending in the reference. The Evennia doc-build process -> will correct for this (and also insert any needed relative paths in the reference). - -### API links - -The documentation contains auto-generated documentation for all of Evennia's source code. You -can direct the reader to the sources by just giving the python-path to the location of the -resource under the `evennia/` repository: - - [DefaultObject](evennia.objects.objects.DefaultObject) - -[DefaultObject](evennia.objects.objects.DefaultObject) <- like this! - -Note that you can't refer to files in the `mygame` folder this way. The game folder is generated -dynamically and is not part of the api docs. Refer to the parent classes in `evennia` where possible. - -### External links - -These are links to resources outside of the documentation. We also provide some convenient shortcuts. - -- `[linkname](https://evennia.com)` - link to an external website. -- `[linkname](github:evennia/objects/objects.py)` - this is a shortcut to point to a location in the - official Evennia repository on Github. Note that you must use `/` and give the full file name. By - default this is code in the `master` branch. -- `[linkname](github:develop/evennia/objects.objects.py` - this points to code in the `develop` branch. -- `[make an issue](github:issue)` - this is a shortcut to the Evennia github issue-creation page. - -> Note that if you want to refer to code, it's usually better to [link to the API](#api-links) as -> described above. - -### Urls/References in one place - -Urls can get long and if you are using the same url/reference in many places it can get a -little cluttered. So you can also put the url as a 'footnote' at the end of your document. -You can then refer to it by putting your reference within square brackets `[ ]`. Here's an example: - -``` -This is a [clickable link][mylink]. This is [another link][1]. - -... - - -[mylink]: http://... -[1]: My-Document.md#this-is-a-long-ref - -``` - -This makes the main text a little shorter. - -## Tables - -A table is done like this: - -```` -| heading1 | heading2 | heading3 | -| --- | --- | --- | -| value1 | value2 | value3 | -| | value 4 | | -| value 5 | value 6 | | -```` - -| heading1 | heading2 | heading3 | -| --- | --- | --- | -| value1 | value2 | value3 | -| | value 4 | | -| value 5 | value 6 | | - -As seen, the Markdown syntax can be pretty sloppy (columns don't need to line up) as long as you -include the heading separators and make sure to add the correct number of `|` on every line. - - -## Verbatim text - -It's common to want to mark something to be displayed verbatim - just as written - without any -Markdown parsing. In running text, this is done using backticks (\`), like \`verbatim text\` becomes -`verbatim text`. - -If you want to put the verbatim text on its own line, you can do so easily by simply indenting -it 4 spaces (add empty lines on each side for readability too): - -``` -This is normal text - - This is verbatim text - -This is normal text -``` - -Another way is to use triple-backticks: - -```` -``` -Everything within these backticks will be verbatim. - -``` -```` - -### Code blocks - -A special 'verbatim' case is code examples - we want them to get code-highlighting for readability. -This is done by using the triple-backticks and specify which language we use: - -```` -```python -from evennia import Command -class CmdEcho(Command): - """ - Usage: echo - """ - key = "echo" - def func(self): - self.caller.msg(self.args.strip()) -``` -```` - -```python -from evennia import Command -class CmdEcho(Command): - """ - Usage: echo - """ - key = "echo" - def func(self): - self.caller.msg(self.args.strip()) -``` - -## MyST directives - -Markdown is easy to read and use. But while it does most of what we need, there are some things it's -not quite as expressive as it needs to be. For this we use extended [MyST][MyST] syntax. This is -on the form - -```` -```{directive} any_options_here - -content - -``` -```` - - -#### Note - -This kind of note may pop more than doing a `> Note: ...`. - -```` -```{note} - -This is some noteworthy content that stretches over more than one line to show how the content indents. -Also the important/warning notes indents like this. - -``` -```` - -```{note} - -This is some noteworthy content that stretches over more than one line to show how the content indents. -Also the important/warning notes indents like this. - -``` - -### Important - -This is for particularly important and visible notes. - -```` -```{important} - This is important because it is! -``` - -```` -```{important} - This is important because it is! -``` - -### Warning - -A warning block is used to draw attention to particularly dangerous things, or features easy to -mess up. - -```` -```{warning} - Be careful about this ... -``` -```` - -```{warning} - Be careful about this ... -``` - -### Version changes and deprecations - -These will show up as one-line warnings that suggest an added, changed or deprecated -feature beginning with particular version. - -```` -```{versionadded} 1.0 -``` -```` - -```{versionadded} 1.0 -``` - -```` -```{versionchanged} 1.0 - How the feature changed with this version. -``` -```` - -```{versionchanged} 1.0 - How the feature changed with this version. -``` - -```` -```{deprecated} 1.0 -``` -```` - -```{deprecated} 1.0 -``` - -### Sidebar - -This will display an informative sidebar that floats to the side of regular content. This is useful -for example to remind the reader of some concept relevant to the text. - -```` -```{sidebar} Things to remember - -- There can be bullet lists -- in here. - -Separate sections with - -an empty line. -``` -```` - -```{sidebar} Things to remember - -- There can be bullet lists -- in here. - -Separate sections with - -an empty line. -``` - -Hint: If wanting to make sure to have the next header appear on a row of its own (rather than -squeezed to the left of the sidebar), one can embed a plain HTML string in the markdown like so: - -```html -
-``` - -
- -### A more flexible code block - -The regular Markdown Python codeblock is usually enough but for more direct control over the style, one -can also use the `{code-block}` directive that takes a set of additional `:options:`: - -```` -```{code-block} python -:linenos: -:emphasize-lines: 1-2,8 -:caption: An example code block -:name: A full code block example - -from evennia import Command -class CmdEcho(Command): - """ - Usage: echo - """ - key = "echo" - def func(self): - self.caller.msg(self.args.strip()) -``` -```` - -```{code-block} python -:linenos: -:emphasize-lines: 1-2,8 -:caption: An example code block -:name: A full code block example - -from evennia import Command -class CmdEcho(Command): - """ - Usage: echo - """ - key = "echo" - def func(self): - self.caller.msg(self.args.strip()) -``` -Here, `:linenos:` turns on line-numbers and `:emphasize-lines:` allows for emphasizing certain lines -in a different color. The `:caption:` shows an instructive text and `:name:` is used to reference -this -block through the link that will appear (so it should be unique for a given document). - - - -### eval-rst directive - -As a last resort, we can also fall back to writing [ReST][ReST] directives directly: - - -```` -```{eval-rst} - - This will be evaluated as ReST. - All content must be indented. - -``` -```` - -Within a ReST block, one must use Restructured Text syntax, which is not the -same as Markdown. - -- Single backticks around text makes it _italic_. -- Double backticks around text makes it `verbatim`. -- A link is written within back-ticks, with an underscore at the end: - - `python `_ - -[Here is a ReST formatting cheat sheet](https://thomas-cokelaer.info/tutorials/sphinx/rest_syntax.html). - -## Code docstrings - -The source code docstrings will be parsed as Markdown. When writing a module docstring, you can use Markdown formatting, -including header levels down to 4th level (`#### SubSubSubHeader`). After the module documentation it's -a good idea to end with four dashes `----`. This will create a visible line between the documentation and the -class/function docs to follow. - -All non-private classes, methods and functions must have a Google-style docstring, as per the -[Evennia coding style guidelines][github:evennia/CODING_STYLE.md]. This will then be correctly formatted -into pretty api docs. - -## Technical - -Evennia leverages [Sphinx][sphinx] with the [MyST][MyST] extension, which allows us -to write our docs in light-weight Markdown (more specifically [CommonMark][commonmark], like on github) -rather than Sphinx' normal ReST syntax. The `MyST` parser allows for some extra syntax to -make us able to express more complex displays than plain Markdown can. - -For [autodoc-generation][sphinx-autodoc] generation, we use the sphinx-[napoleon][sphinx-napoleon] -extension to understand our friendly Google-style docstrings used in classes and functions etc. - -# Building the docs locally - -The sources in `evennia/docs/source/` are built into a documentation using the -[Sphinx][sphinx] static generator system. To do this locally you need to use a -system with `make` (Linux/Unix/Mac or [Windows-WSL][Windows-WSL]). Lacking -that, you could in principle also run the sphinx build-commands manually - read -the `evennia/docs/Makefile` to see which commands are run by the `make`-commands -referred to in this document. - -You don't necessarily _have_ to build the docs locally to contribute. Markdown is -not hard and is very readable on its raw text-form. - -You can furthermore get a good feel for how things will look using a -Markdown-viewer like [Grip][grip]. Editors like [ReText][retext] or IDE's like -[PyCharm][pycharm] also have native Markdown previews. Building the docs locally is -however the only way to make sure the outcome is exactly as you expect. The process -will also find any mistakes you made, like making a typo in a link. - -### Building only the main documentation - -This is the fastest way to compile and view your changes. It will only build -the main documentation pages and not the API auto-docs or versions. All is -done in your terminal/console. - -- (Optional, but recommended): Activate a virtualenv with Python 3.7. -- `cd` to into the `evennia/docs` folder. -- Install the documentation-build requirements: - - ``` - make install - or - pip install -r requirements.txt - ``` - -- Next, build the html-based documentation (re-run this in the future to build your changes): - - ``` - make quick - ``` -- Note any errors from files you have edited. -- The html-based documentation will appear in the new - folder `evennia/docs/build/html/`. -- Use a web browser to open `file:///evennia/docs/build/html/index.html` and view - the docs. Note that you will get errors if clicking a link to the auto-docs, because you didn't - build them! - -### Building the main documentation and API docs - -The full documentation includes both the doc pages and the API documentation -generated from the Evennia source. For this you must install Evennia and -initialize a new game with a default database (you don't need to have any server -running) - -- It's recommended that you use a virtualenv. Install your cloned version of Evennia into - by pointing to the repo folder (the one containing `/docs`): - - ``` - pip install -e evennia - ``` - -- Make sure you are in the parent folder _containing_ your `evennia/` repo (so _two_ levels - up from `evennia/docs/`). -- Create a new game folder called exactly `gamedir` at the same level as your `evennia` - repo with - - ``` - evennia --init gamedir - ``` - -- Then `cd` into it and create a new, empty database. You don't need to start the - game or do any further changes after this. - - ``` - evennia migrate - ``` - -- This is how the structure should look at this point: - - ``` - (top) - | - ----- evennia/ (the top-level folder, containing docs/) - | - ----- gamedir/ - ``` - -(If you are already working on a game, you may of course have your 'real' game folder there as -well. We won't touch that.) - -- Go to `evennia/docs/` and install the doc-building requirements (you only need to do this once): - - ``` - make install - or - pip install -r requirements.txt - ``` - -- Finally, build the full documentation, including the auto-docs: - - ``` - make local - ``` - -- The rendered files will appear in a new folder `evennia/docs/build/html/`. - Note any errors from files you have edited. -- Point your web browser to `file:///evennia/docs/build/html/index.html` to - view the full docs. - -#### Building with another gamedir - -If you for some reason want to use another location of your `gamedir/`, or want it -named something else (maybe you already use the name 'gamedir' for your development ...), -you can do so by setting the `EVGAMEDIR` environment variable to the absolute path -of your alternative game dir. For example: - -``` -EVGAMEDIR=/my/path/to/mygamedir make local -``` - -### Building for release - -The full Evennia documentation contains docs from many Evennia -versions, old and new. This is done by pulling documentation from Evennia's old release -branches and building them all so readers can choose which one to view. Only -specific official Evennia branches will be built, so you can't use this to -build your own testing branch. - -- All local changes must have been committed to git first, since the versioned - docs are built by looking at the git tree. -- To build for local checking, run (`mv` stands for "multi-version"): - - ``` - make mv-local - ``` - -This is as close to the 'real' version of the docs as you can get locally. The different versions -will be found under `evennia/docs/build/versions/`. During deploy a symlink `latest` will point -to the latest version of the docs. - -#### Release - -Releasing the official docs requires git-push access the the Evennia `gh-pages` branch -on `github`. So there is no risk of you releasing your local changes accidentally. - -- To deploy docs in two steps - - ``` - make mv-local - make deploy - ``` - -- If you know what you are doing you can also do build + deploy in one step: - - ``` - make release - ``` - -After deployment finishes, the updated live documentation will be -available at https://evennia.github.io/evennia/latest/. - - - -[sphinx]: https://www.sphinx-doc.org/en/master/ -[MyST]: https://myst-parser.readthedocs.io/en/latest/syntax/reference.html -[commonmark]: https://spec.commonmark.org/current/ -[commonmark-help]: https://commonmark.org/help/ -[sphinx-autodoc]: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc -[sphinx-napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html -[getting-started]: Setup/Setup-Quickstart -[contributing]: ./Contributing -[ReST]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html -[ReST-tables]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#tables -[ReST-directives]: https://www.sphinx-doc.org/en/master/usage/restruturedtext/directives.html -[Windows-WSL]: https://docs.microsoft.com/en-us/windows/wsl/install-win10 -[linkdemo]: #Links -[retext]: https://github.com/retext-project/retext -[grip]: https://github.com/joeyespo/grip -[pycharm]: https://www.jetbrains.com/pycharm/ \ No newline at end of file diff --git a/docs/0.9.5/_sources/Contributing.md.txt b/docs/0.9.5/_sources/Contributing.md.txt deleted file mode 100644 index e49f033c4d..0000000000 --- a/docs/0.9.5/_sources/Contributing.md.txt +++ /dev/null @@ -1,118 +0,0 @@ -# Contributing - - -Wanna help out? Great! Here's how. - -## Spreading the word - -Even if you are not keen on working on the server code yourself, just spreading the word is a big -help - it will help attract more people which leads to more feedback, motivation and interest. -Consider writing about Evennia on your blog or in your favorite (relevant) forum. Write a review -somewhere (good or bad, we like feedback either way). Rate it on places like [ohloh][ohloh]. Talk -about it to your friends ... that kind of thing. - -## Donations - -The best way to support Evennia is to become an [Evennia patron][patron]. Evennia is a free, -open-source project and any monetary donations you want to offer are completely voluntary. See it as -a way of announcing that you appreciate the work done - a tip of the hat! A patron donates a -(usually small) sum every month to show continued support. If this is not your thing you can also -show your appreciation via a [one-time donation][donate] (this is a PayPal link but you don't need -PayPal yourself). - -## Help with Documentation - -Evennia depends heavily on good documentation and we are always looking for extra eyes and hands to -improve it. Even small things such as fixing typos are a great help! - -The documentation is a wiki and as long as you have a GitHub account you can edit it. It can be a -good idea to discuss in the chat or forums if you want to add new pages/tutorials. Otherwise, it -goes a long way just pointing out wiki errors so we can fix them (in an Issue or just over -chat/forum). - -## Contributing through a forked repository - -We always need more eyes and hands on the code. Even if you don't feel confident with tackling a -[bug or feature][issues], just correcting typos, adjusting formatting or simply *using* the thing -and reporting when stuff doesn't make sense helps us a lot. - -The most elegant way to contribute code to Evennia is to use GitHub to create a *fork* of the -Evennia repository and make your changes to that. Refer to the [Forking Evennia](Version- -Control#forking-evennia) version -control instructions for detailed instructions. - -Once you have a fork set up, you can not only work on your own game in a separate branch, you can -also commit your fixes to Evennia itself. Make separate branches for all Evennia additions you do - -don't edit your local `master` or `develop` branches directly. It will make your life a lot easier. -If you have a change that you think is suitable for the main Evennia repository, you issue a [Pull -Request][pullrequest]. This will let Evennia devs know you have stuff to share. Bug fixes should -generally be done against the `master` branch of Evennia, while new features/contribs should go into -the `develop` branch. If you are unsure, just pick one and we'll figure it out. - -## Contributing with Patches - -To help with Evennia development it's recommended to do so using a fork repository as described -above. But for small, well isolated fixes you are also welcome to submit your suggested Evennia -fixes/addendums as a [patch][patch]. - -You can include your patch in an Issue or a Mailing list post. Please avoid pasting the full patch -text directly in your post though, best is to use a site like [Pastebin](http://pastebin.com/) and -just supply the link. - -## Contributing with Contribs - -While Evennia's core is pretty much game-agnostic, it also has a `contrib/` directory. The `contrib` -directory contains game systems that are specialized or useful only to certain types of games. Users -are welcome to contribute to the `contrib/` directory. Such contributions should always happen via a -Forked repository as described above. - -* If you are unsure if your idea/code is suitable as a contrib, *ask the devs before putting any -work into it*. This can also be a good idea in order to not duplicate efforts. This can also act as -a check that your implementation idea is sound. We are, for example, unlikely to accept contribs -that require large modifications of the game directory structure. -* If your code is intended *primarily* as an example or shows a concept/principle rather than a -working system, it is probably not suitable for `contrib/`. You are instead welcome to use it as -part of a [new tutorial][tutorials]! -* The code should ideally be contained within a single Python module. But if the contribution is -large this may not be practical and it should instead be grouped in its own subdirectory (not as -loose modules). -* The contribution should preferably be isolated (only make use of core Evennia) so it can easily be -dropped into use. If it does depend on other contribs or third-party modules, these must be clearly -documented and part of the installation instructions. -* The code itself should follow Evennia's [Code style guidelines][codestyle]. -* The code must be well documented as described in our [documentation style -guide](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#doc-strings). Expect that your -code will be read and should be possible to understand by others. Include comments as well as a -header in all modules. If a single file, the header should include info about how to include the -contrib in a game (installation instructions). If stored in a subdirectory, this info should go into -a new `README.md` file within that directory. -* Within reason, your contribution should be designed as genre-agnostic as possible. Limit the -amount of game-style-specific code. Assume your code will be applied to a very different game than -you had in mind when creating it. -* To make the licensing situation clear we assume all contributions are released with the same -[license as Evennia](./Licensing.md). If this is not possible for some reason, talk to us and we'll -handle it on a case-by-case basis. -* Your contribution must be covered by [unit tests](./Unit-Testing.md). Having unit tests will both help -make your code more stable and make sure small changes does not break it without it being noticed, -it will also help us test its functionality and merge it quicker. If your contribution is a single -module, you can add your unit tests to `evennia/contribs/tests.py`. If your contribution is bigger -and in its own sub-directory you could just put the tests in your own `tests.py` file (Evennia will -find it automatically). -* Merging of your code into Evennia is not guaranteed. Be ready to receive feedback and to be asked -to make corrections or fix bugs. Furthermore, merging a contrib means the Evennia project takes on -the responsibility of maintaining and supporting it. For various reasons this may be deemed to be -beyond our manpower. However, if your code were to *not* be accepted for merger for some reason, we -will instead add a link to your online repository so people can still find and use your work if they -want. - -[ohloh]: http://www.ohloh.net/p/evennia -[patron]: https://www.patreon.com/griatch -[donate]: https://www.paypal.com/en/cgi-bin/webscr?cmd=_flow&SESSION=TWy_epDPSWqNr4UJCOtVWxl- -pO1X1jbKiv_- -UBBFWIuVDEZxC0M_2pM6ywO&dispatch=5885d80a13c0db1f8e263663d3faee8d66f31424b43e9a70645c907a6cbd8fb4 -[forking]: https://github.com/evennia/evennia/wiki/Version-Control#wiki-forking-from-evennia -[pullrequest]: https://github.com/evennia/evennia/pulls -[issues]: https://github.com/evennia/evennia/issues -[patch]: https://secure.wikimedia.org/wikipedia/en/wiki/Patch_%28computing%29 -[codestyle]: https://github.com/evennia/evennia/blob/master/CODING_STYLE.md -[tutorials]: https://github.com/evennia/evennia/wiki/Tutorials diff --git a/docs/0.9.5/_sources/Coordinates.md.txt b/docs/0.9.5/_sources/Coordinates.md.txt deleted file mode 100644 index 5c5653ffdd..0000000000 --- a/docs/0.9.5/_sources/Coordinates.md.txt +++ /dev/null @@ -1,348 +0,0 @@ -# Coordinates - -# Adding room coordinates in your game - -This tutorial is moderately difficult in content. You might want to be familiar and at ease with -some Python concepts (like properties) and possibly Django concepts (like queries), although this -tutorial will try to walk you through the process and give enough explanations each time. If you -don't feel very confident with math, don't hesitate to pause, go to the example section, which shows -a tiny map, and try to walk around the code or read the explanation. - -Evennia doesn't have a coordinate system by default. Rooms and other objects are linked by location -and content: - -- An object can be in a location, that is, another object. Like an exit in a room. -- An object can access its content. A room can see what objects uses it as location (that would - include exits, rooms, characters and so on). - -This system allows for a lot of flexibility and, fortunately, can be extended by other systems. -Here, I offer you a way to add coordinates to every room in a way most compliant with Evennia -design. This will also show you how to use coordinates, find rooms around a given point for -instance. - -## Coordinates as tags - -The first concept might be the most surprising at first glance: we will create coordinates as -[tags](./Tags.md). - -> Why not attributes, wouldn't that be easier? - -It would. We could just do something like `room.db.x = 3`. The advantage of using tags is that it -will be easy and effective to search. Although this might not seem like a huge advantage right now, -with a database of thousands of rooms, it might make a difference, particularly if you have a lot of -things based on coordinates. - -Rather than giving you a step-by-step process, I'll show you the code. Notice that we use -properties to easily access and update coordinates. This is a Pythonic approach. Here's our first -`Room` class, that you can modify in `typeclasses/rooms.py`: - -```python -# in typeclasses/rooms.py - -from evennia import DefaultRoom - -class Room(DefaultRoom): - """ - Rooms are like any Object, except their location is None - (which is default). They also use basetype_setup() to - add locks so they cannot be puppeted or picked up. - (to change that, use at_object_creation instead) - - See examples/object.py for a list of - properties and methods available on all Objects. - """ - - @property - def x(self): - """Return the X coordinate or None.""" - x = self.tags.get(category="coordx") - return int(x) if isinstance(x, str) else None - - @x.setter - def x(self, x): - """Change the X coordinate.""" - old = self.tags.get(category="coordx") - if old is not None: - self.tags.remove(old, category="coordx") - if x is not None: - self.tags.add(str(x), category="coordx") - - @property - def y(self): - """Return the Y coordinate or None.""" - y = self.tags.get(category="coordy") - return int(y) if isinstance(y, str) else None - - @y.setter - def y(self, y): - """Change the Y coordinate.""" - old = self.tags.get(category="coordy") - if old is not None: - self.tags.remove(old, category="coordy") - if y is not None: - self.tags.add(str(y), category="coordy") - - @property - def z(self): - """Return the Z coordinate or None.""" - z = self.tags.get(category="coordz") - return int(z) if isinstance(z, str) else None - - @z.setter - def z(self, z): - """Change the Z coordinate.""" - old = self.tags.get(category="coordz") - if old is not None: - self.tags.remove(old, category="coordz") - if z is not None: - self.tags.add(str(z), category="coordz") -``` - -If you aren't familiar with the concept of properties in Python, I encourage you to read a good -tutorial on the subject. [This article on Python properties](https://www.programiz.com/python-programming/property) -is well-explained and should help you understand the idea. - -Let's look at our properties for `x`. First of all is the read property. - -```python - @property - def x(self): - """Return the X coordinate or None.""" - x = self.tags.get(category="coordx") - return int(x) if isinstance(x, str) else None -``` - -What it does is pretty simple: - -1. It gets the tag of category `"coordx"`. It's the tag category where we store our X coordinate. - The `tags.get` method will return `None` if the tag can't be found. -2. We convert the value to an integer, if it's a `str`. Remember that tags can only contain `str`, - so we'll need to convert it. - -> I thought tags couldn't contain values? - -Well, technically, they can't: they're either here or not. But using tag categories, as we have -done, we get a tag, knowing only its category. That's the basic approach to coordinates in this -tutorial. - -Now, let's look at the method that will be called when we wish to set `x` in our room: - -```python - @x.setter - def x(self, x): - """Change the X coordinate.""" - old = self.tags.get(category="coordx") - if old is not None: - self.tags.remove(old, category="coordx") - if x is not None: - self.tags.add(str(x), category="coordx") -``` - -1. First, we remove the old X coordinate, if it exists. Otherwise, we'd end up with two tags in our - room with "coordx" as their category, which wouldn't do at all. -2. Then we add the new tag, giving it the proper category. - -> Now what? - -If you add this code and reload your game, once you're logged in with a character in a room as its -location, you can play around: - -``` -@py here.x -@py here.x = 0 -@py here.y = 3 -@py here.z = -2 -@py here.z = None -``` - -The code might not be that easy to read, but you have to admit it's fairly easy to use. - -## Some additional searches - -Having coordinates is useful for several reasons: - -1. It can help in shaping a truly logical world, in its geography, at least. -2. It can allow to look for specific rooms at given coordinates. -3. It can be good in order to quickly find the rooms around a location. -4. It can even be great in path-finding (finding the shortest path between two rooms). - -So far, our coordinate system can help with 1., but not much else. Here are some methods that we -could add to the `Room` typeclass. These methods will just be search methods. Notice that they are -class methods, since we want to get rooms. - -### Finding one room - -First, a simple one: how to find a room at a given coordinate? Say, what is the room at X=0, Y=0, -Z=0? - -```python -class Room(DefaultRoom): - # ... - @classmethod - def get_room_at(cls, x, y, z): - """ - Return the room at the given location or None if not found. - - Args: - x (int): the X coord. - y (int): the Y coord. - z (int): the Z coord. - - Return: - The room at this location (Room) or None if not found. - - """ - rooms = cls.objects.filter( - db_tags__db_key=str(x), db_tags__db_category="coordx").filter( - db_tags__db_key=str(y), db_tags__db_category="coordy").filter( - db_tags__db_key=str(z), db_tags__db_category="coordz") - if rooms: - return rooms[0] - - return None -``` - -This solution includes a bit of [Django -queries](https://docs.djangoproject.com/en/1.11/topics/db/queries/). -Basically, what we do is reach for the object manager and search for objects with the matching tags. -Again, don't spend too much time worrying about the mechanism, the method is quite easy to use: - -``` -Room.get_room_at(5, 2, -3) -``` - -Notice that this is a class method: you will call it from `Room` (the class), not an instance. -Though you still can: - - @py here.get_room_at(3, 8, 0) - -### Finding several rooms - -Here's another useful method that allows us to look for rooms around a given coordinate. This is -more advanced search and doing some calculation, beware! Look at the following section if you're -lost. - -```python -from math import sqrt - -class Room(DefaultRoom): - - # ... - - @classmethod - def get_rooms_around(cls, x, y, z, distance): - """ - Return the list of rooms around the given coordinates. - - This method returns a list of tuples (distance, room) that - can easily be browsed. This list is sorted by distance (the - closest room to the specified position is always at the top - of the list). - - Args: - x (int): the X coord. - y (int): the Y coord. - z (int): the Z coord. - distance (int): the maximum distance to the specified position. - - Returns: - A list of tuples containing the distance to the specified - position and the room at this distance. Several rooms - can be at equal distance from the position. - - """ - # Performs a quick search to only get rooms in a square - x_r = list(reversed([str(x - i) for i in range(0, distance + 1)])) - x_r += [str(x + i) for i in range(1, distance + 1)] - y_r = list(reversed([str(y - i) for i in range(0, distance + 1)])) - y_r += [str(y + i) for i in range(1, distance + 1)] - z_r = list(reversed([str(z - i) for i in range(0, distance + 1)])) - z_r += [str(z + i) for i in range(1, distance + 1)] - wide = cls.objects.filter( - db_tags__db_key__in=x_r, db_tags__db_category="coordx").filter( - db_tags__db_key__in=y_r, db_tags__db_category="coordy").filter( - db_tags__db_key__in=z_r, db_tags__db_category="coordz") - - # We now need to filter down this list to find out whether - # these rooms are really close enough, and at what distance - # In short: we change the square to a circle. - rooms = [] - for room in wide: - x2 = int(room.tags.get(category="coordx")) - y2 = int(room.tags.get(category="coordy")) - z2 = int(room.tags.get(category="coordz")) - distance_to_room = sqrt( - (x2 - x) ** 2 + (y2 - y) ** 2 + (z2 - z) ** 2) - if distance_to_room <= distance: - rooms.append((distance_to_room, room)) - - # Finally sort the rooms by distance - rooms.sort(key=lambda tup: tup[0]) - return rooms -``` - -This gets more serious. - -1. We have specified coordinates as parameters. We determine a broad range using the distance. - That is, for each coordinate, we create a list of possible matches. See the example below. -2. We then search for the rooms within this broader range. It gives us a square - around our location. Some rooms are definitely outside the range. Again, see the example below -to follow the logic. -3. We filter down the list and sort it by distance from the specified coordinates. - -Notice that we only search starting at step 2. Thus, the Django search doesn't look and cache all -objects, just a wider range than what would be really necessary. This method returns a circle of -coordinates around a specified point. Django looks for a square. What wouldn't fit in the circle -is removed at step 3, which is the only part that includes systematic calculation. This method is -optimized to be quick and efficient. - -### An example - -An example might help. Consider this very simple map (a textual description follows): - -``` -4 A B C D -3 E F G H -2 I J K L -1 M N O P - 1 2 3 4 -``` - -The X coordinates are given below. The Y coordinates are given on the left. This is a simple -square with 16 rooms: 4 on each line, 4 lines of them. All the rooms are identified by letters in -this example: the first line at the top has rooms A to D, the second E to H, the third I to L and -the fourth M to P. The bottom-left room, X=1 and Y=1, is M. The upper-right room X=4 and Y=4 is D. - -So let's say we want to find all the neighbors, distance 1, from the room J. J is at X=2, Y=2. - -So we use: - - Room.get_rooms_around(x=2, y=2, z=0, distance=1) - # we'll assume a z coordinate of 0 for simplicity - -1. First, this method gets all the rooms in a square around J. So it gets E F G, I J K, M N O. If -you want, draw the square around these coordinates to see what's happening. -2. Next, we browse over this list and check the real distance between J (X=2, Y=2) and the room. -The four corners of the square are not in this circle. For instance, the distance between J and M -is not 1. If you draw a circle of center J and radius 1, you'll notice that the four corners of our -square (E, G, M and O) are not in this circle. So we remove them. -3. We sort by distance from J. - -So in the end we might obtain something like this: - -``` -[ - (0, J), # yes, J is part of this circle after all, with a distance of 0 - (1, F), - (1, I), - (1, K), - (1, N), -] -``` - -You can try with more examples if you want to see this in action. - -### To conclude - -You can definitely use this system to map other objects, not just rooms. You can easily remove the -`Z coordinate too, if you simply need X and Y. diff --git a/docs/0.9.5/_sources/Custom-Protocols.md.txt b/docs/0.9.5/_sources/Custom-Protocols.md.txt deleted file mode 100644 index 93d17ca218..0000000000 --- a/docs/0.9.5/_sources/Custom-Protocols.md.txt +++ /dev/null @@ -1,239 +0,0 @@ -# Custom Protocols - - -*Note: This is considered an advanced topic and is mostly of interest to users planning to implement -their own custom client protocol.* - - -A [PortalSession](./Sessions.md#portal-and-server-sessions) is the basic data object representing an -external -connection to the Evennia [Portal](./Portal-And-Server.md) -- usually a human player running a mud client -of some kind. The way they connect (the language the player's client and Evennia use to talk to -each other) is called the connection *Protocol*. The most common such protocol for MUD:s is the -*Telnet* protocol. All Portal Sessions are stored and managed by the Portal's *sessionhandler*. - -It's technically sometimes hard to separate the concept of *PortalSession* from the concept of -*Protocol* since both depend heavily on the other (they are often created as the same class). When -data flows through this part of the system, this is how it goes - -``` -# In the Portal -You <-> - Protocol + PortalSession <-> - PortalSessionHandler <-> - (AMP) <-> - ServerSessionHandler <-> - ServerSession <-> - InputFunc -``` - -(See the [Message Path](./Messagepath.md) for the bigger picture of how data flows through Evennia). The -parts that needs to be customized to make your own custom protocol is the `Protocol + PortalSession` -(which translates between data coming in/out over the wire to/from Evennia internal representation) -as well as the `InputFunc` (which handles incoming data). - -## Adding custom Protocols - -Evennia has a plugin-system that add the protocol as a new "service" to the application. - -Take a look at `evennia/server/portal/portal.py`, notably the sections towards the end of that file. -These are where the various in-built services like telnet, ssh, webclient etc are added to the -Portal (there is an equivalent but shorter list in `evennia/server/server.py`). - -To add a new service of your own (for example your own custom client protocol) to the Portal or -Server, look at `mygame/server/conf/server_services_plugins` and `portal_services_plugins`. By -default Evennia will look into these modules to find plugins. If you wanted to have it look for more -modules, you could do the following: - -```python - # add to the Server - SERVER_SERVICES_PLUGIN_MODULES.append('server.conf.my_server_plugins') - # or, if you want to add to the Portal - PORTAL_SERVICES_PLUGIN_MODULES.append('server.conf.my_portal_plugins') -``` - -When adding a new connection you'll most likely only need to add new things to the -`PORTAL_SERVICES_PLUGIN_MODULES`. - -This module can contain whatever you need to define your protocol, but it *must* contain a function -`start_plugin_services(app)`. This is called by the Portal as part of its upstart. The function -`start_plugin_services` must contain all startup code the server need. The `app` argument is a -reference to the Portal/Server application itself so the custom service can be added to it. The -function should not return anything. - -This is how it looks: - -```python - # mygame/server/conf/portal_services_plugins.py - - # here the new Portal Twisted protocol is defined - class MyOwnFactory( ... ): - [...] - - # some configs - MYPROC_ENABLED = True # convenient off-flag to avoid having to edit settings all the time - MY_PORT = 6666 - - def start_plugin_services(portal): - "This is called by the Portal during startup" - if not MYPROC_ENABLED: - return - # output to list this with the other services at startup - print(" myproc: %s" % MY_PORT) - - # some setup (simple example) - factory = MyOwnFactory() - my_service = internet.TCPServer(MY_PORT, factory) - # all Evennia services must be uniquely named - my_service.setName("MyService") - # add to the main portal application - portal.services.addService(my_service) -``` - -Once the module is defined and targeted in settings, just reload the server and your new -protocol/services should start with the others. - -## Writing your own Protocol - -Writing a stable communication protocol from scratch is not something we'll cover here, it's no -trivial task. The good news is that Twisted offers implementations of many common protocols, ready -for adapting. - -Writing a protocol implementation in Twisted usually involves creating a class inheriting from an -already existing Twisted protocol class and from `evennia.server.session.Session` (multiple -inheritance), then overloading the methods that particular protocol uses to link them to the -Evennia-specific inputs. - -Here's a example to show the concept: - -```python -# In module that we'll later add to the system through PORTAL_SERVICE_PLUGIN_MODULES - -# pseudo code -from twisted.something import TwistedClient -# this class is used both for Portal- and Server Sessions -from evennia.server.session import Session - -from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS - -class MyCustomClient(TwistedClient, Session): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.sessionhandler = PORTAL_SESSIONS - - # these are methods we must know that TwistedClient uses for - # communication. Name and arguments could vary for different Twisted protocols - def onOpen(self, *args, **kwargs): - # let's say this is called when the client first connects - - # we need to init the session and connect to the sessionhandler. The .factory - # is available through the Twisted parents - - client_address = self.getClientAddress() # get client address somehow - - self.init_session("mycustom_protocol", client_address, self.factory.sessionhandler) - self.sessionhandler.connect(self) - - def onClose(self, reason, *args, **kwargs): - # called when the client connection is dropped - # link to the Evennia equivalent - self.disconnect(reason) - - def onMessage(self, indata, *args, **kwargs): - # called with incoming data - # convert as needed here - self.data_in(data=indata) - - def sendMessage(self, outdata, *args, **kwargs): - # called to send data out - # modify if needed - super().sendMessage(self, outdata, *args, **kwargs) - - # these are Evennia methods. They must all exist and look exactly like this - # The above twisted-methods call them and vice-versa. This connects the protocol - # the Evennia internals. - - def disconnect(self, reason=None): - """ - Called when connection closes. - This can also be called directly by Evennia when manually closing the connection. - Do any cleanups here. - """ - self.sessionhandler.disconnect(self) - - def at_login(self): - """ - Called when this session authenticates by the server (if applicable) - """ - - def data_in(self, **kwargs): - """ - Data going into the server should go through this method. It - should pass data into `sessionhandler.data_in`. THis will be called - by the sessionhandler with the data it gets from the approrpriate - send_* method found later in this protocol. - """ - self.sessionhandler.data_in(self, text=kwargs['data']) - - def data_out(self, **kwargs): - """ - Data going out from the server should go through this method. It should - hand off to the protocol's send method, whatever it's called. - """ - # we assume we have a 'text' outputfunc - self.onMessage(kwargs['text']) - - # 'outputfuncs' are defined as `send_`. From in-code, they are called - # with `msg(outfunc_name=)`. - - def send_text(self, txt, *args, **kwargs): - """ - Send text, used with e.g. `session.msg(text="foo")` - """ - # we make use of the - self.data_out(text=txt) - - def send_default(self, cmdname, *args, **kwargs): - """ - Handles all outputfuncs without an explicit `send_*` method to handle them. - """ - self.data_out(**{cmdname: str(args)}) - -``` -The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to -the Evennia-specific methods. - -### Sending data out - -To send data out through this protocol, you'd need to get its Session and then you could e.g. - -```python - session.msg(text="foo") -``` - -The message will pass through the system such that the sessionhandler will dig out the session and -check if it has a `send_text` method (it has). It will then pass the "foo" into that method, which -in our case means sending "foo" across the network. - -### Receiving data - -Just because the protocol is there, does not mean Evennia knows what to do with it. An -[Inputfunc](./Inputfuncs.md) must exist to receive it. In the case of the `text` input exemplified above, -Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So -handle that you need to simply add a cmdset with commands on your receiving Session (and/or the -Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the -[Inputfunc](./Inputfuncs.md) page for how to do this. - -These might not be as clear-cut in all protocols, but the principle is there. These four basic -components - however they are accessed - links to the *Portal Session*, which is the actual common -interface between the different low-level protocols and Evennia. - -## Assorted notes - -To take two examples, Evennia supports the *telnet* protocol as well as *webclient*, via ajax or -websockets. You'll find that whereas telnet is a textbook example of a Twisted protocol as seen -above, the ajax protocol looks quite different due to how it interacts with the -webserver through long-polling (comet) style requests. All the necessary parts -mentioned above are still there, but by necessity implemented in very different -ways. diff --git a/docs/0.9.5/_sources/Customize-channels.md.txt b/docs/0.9.5/_sources/Customize-channels.md.txt deleted file mode 100644 index 67bc381750..0000000000 --- a/docs/0.9.5/_sources/Customize-channels.md.txt +++ /dev/null @@ -1,483 +0,0 @@ -# Customize channels - - -# Channel commands in Evennia - -By default, Evennia's default channel commands are inspired by MUX. They all -begin with "c" followed by the action to perform (like "ccreate" or "cdesc"). -If this default seems strange to you compared to other Evennia commands that -rely on switches, you might want to check this tutorial out. - -This tutorial will also give you insight into the workings of the channel system. -So it may be useful even if you don't plan to make the exact changes shown here. - -## What we will try to do - -Our mission: change the default channel commands to have a different syntax. - -This tutorial will do the following changes: - -- Remove all the default commands to handle channels. -- Add a `+` and `-` command to join and leave a channel. So, assuming there is -a `public` channel on your game (most often the case), you could type `+public` -to join it and `-public` to leave it. -- Group the commands to manipulate channels under the channel name, after a -switch. For instance, instead of writing `cdesc public = My public channel`, - you would write `public/desc My public channel`. - - -> I listed removing the default Evennia commands as a first step in the -> process. Actually, we'll move it at the very bottom of the list, since we -> still want to use them, we might get it wrong and rely on Evennia commands -> for a while longer. - -## A command to join, another to leave - -We'll do the most simple task at first: create two commands, one to join a -channel, one to leave. - -> Why not have them as switches? `public/join` and `public/leave` for instance? - -For security reasons, I will hide channels to which the caller is not -connected. It means that if the caller is not connected to the "public" -channel, he won't be able to use the "public" command. This is somewhat -standard: if we create an administrator-only channel, we don't want players to -try (or even know) the channel command. Again, you could design it a different -way should you want to. - -First create a file named `comms.py` in your `commands` package. It's -a rather logical place, since we'll write different commands to handle -communication. - -Okay, let's add the first command to join a channel: - -```python -# in commands/comms.py -from evennia.utils.search import search_channel -from commands.command import Command - -class CmdConnect(Command): - """ - Connect to a channel. - """ - - key = "+" - help_category = "Comms" - locks = "cmd:not pperm(channel_banned)" - auto_help = False - - def func(self): - """Implement the command""" - caller = self.caller - args = self.args - if not args: - self.msg("Which channel do you want to connect to?") - return - - channelname = self.args - channel = search_channel(channelname) - if not channel: - return - - # Check permissions - if not channel.access(caller, 'listen'): - self.msg("%s: You are not allowed to listen to this channel." % channel.key) - return - - # If not connected to the channel, try to connect - if not channel.has_connection(caller): - if not channel.connect(caller): - self.msg("%s: You are not allowed to join this channel." % channel.key) - return - else: - self.msg("You now are connected to the %s channel. " % channel.key.lower()) - else: - self.msg("You already are connected to the %s channel. " % channel.key.lower()) -``` - -Okay, let's review this code, but if you're used to Evennia commands, it shouldn't be too strange: - -1. We import `search_channel`. This is a little helper function that we will use to search for -channels by name and aliases, found in `evennia.utils.search`. It's just more convenient. -2. Our class `CmdConnect` contains the body of our command to join a channel. -3. Notice the key of this command is simply `"+"`. When you enter `+something` in the game, it will -try to find a command key `+something`. Failing that, it will look at other potential matches. -Evennia is smart enough to understand that when we type `+something`, `+` is the command key and -`something` is the command argument. This will, of course, fail if you have a command beginning by -`+` conflicting with the `CmdConnect` key. -4. We have altered some class attributes, like `auto_help`. If you want to know what they do and -why they have changed here, you can check the [documentation on commands](./Commands.md). -5. In the command body, we begin by extracting the channel name. Remember that this name should be -in the command arguments (that is, in `self.args`). Following the same example, if a player enters -`+something`, `self.args` should contain `"something"`. We use `search_channel` to see if this -channel exists. -6. We then check the access level of the channel, to see if the caller can listen to it (not -necessarily use it to speak, mind you, just listen to others speak, as these are two different locks -on Evennia). -7. Finally, we connect the caller if he's not already connected to the channel. We use the -channel's `connect` method to do this. Pretty straightforward eh? - -Now we'll add a command to leave a channel. It's almost the same, turned upside down: - -```python -class CmdDisconnect(Command): - """ - Disconnect from a channel. - """ - - key = "-" - help_category = "Comms" - locks = "cmd:not pperm(channel_banned)" - auto_help = False - - def func(self): - """Implement the command""" - caller = self.caller - args = self.args - if not args: - self.msg("Which channel do you want to disconnect from?") - return - - channelname = self.args - channel = search_channel(channelname) - if not channel: - return - - # If connected to the channel, try to disconnect - if channel.has_connection(caller): - if not channel.disconnect(caller): - self.msg("%s: You are not allowed to disconnect from this channel." % channel.key) - return - else: - self.msg("You stop listening to the %s channel. " % channel.key.lower()) - else: - self.msg("You are not connected to the %s channel. " % channel.key.lower()) -``` - -So far, you shouldn't have trouble following what this command does: it's -pretty much the same as the `CmdConnect` class in logic, though it accomplishes -the opposite. If you are connected to the channel `public` you could -disconnect from it using `-public`. Remember, you can use channel aliases too -(`+pub` and `-pub` will also work, assuming you have the alias `pub` on the - `public` channel). - -It's time to test this code, and to do so, you will need to add these two -commands. Here is a good time to say it: by default, Evennia connects accounts -to channels. Some other games (usually with a higher multisession mode) will -want to connect characters instead of accounts, so that several characters in -the same account can be connected to various channels. You can definitely add -these commands either in the `AccountCmdSet` or `CharacterCmdSet`, the caller -will be different and the command will add or remove accounts of characters. -If you decide to install these commands on the `CharacterCmdSet`, you might -have to disconnect your superuser account (account #1) from the channel before -joining it with your characters, as Evennia tends to subscribe all accounts -automatically if you don't tell it otherwise. - -So here's an example of how to add these commands into your `AccountCmdSet`. -Edit the file `commands/default_cmdsets.py` to change a few things: - -```python -# In commands/default_cmdsets.py -from evennia import default_cmds -from commands.comms import CmdConnect, CmdDisconnect - - -# ... Skip to the AccountCmdSet class ... - -class AccountCmdSet(default_cmds.AccountCmdSet): - """ - This is the cmdset available to the Account at all times. It is - combined with the `CharacterCmdSet` when the Account puppets a - Character. It holds game-account-specific commands, channel - commands, etc. - """ - key = "DefaultAccount" - - def at_cmdset_creation(self): - """ - Populates the cmdset - """ - super().at_cmdset_creation() - - # Channel commands - self.add(CmdConnect()) - self.add(CmdDisconnect()) -``` - -Save, reload your game, and you should be able to use `+public` and `-public` -now! - -## A generic channel command with switches - -It's time to dive a little deeper into channel processing. What happens in -Evennia when a player enters `public Hello everybody!`? - -Like exits, channels are a particular command that Evennia automatically -creates and attaches to individual channels. So when you enter `public -message` in your game, Evennia calls the `public` command. - -> But I didn't add any public command... - -Evennia will just create these commands automatically based on the existing -channels. The base command is the command we'll need to edit. - -> Why edit it? It works just fine to talk. - -Unfortunately, if we want to add switches to our channel names, we'll have to -edit this command. It's not too hard, however, we'll just start writing a -standard command with minor twitches. - -### Some additional imports - -You'll need to add a line of import in your `commands/comms.py` file. We'll -see why this import is important when diving in the command itself: - -```python -from evennia.comms.models import ChannelDB -``` - -### The class layout - -```python -# In commands/comms.py -class ChannelCommand(Command): - """ - {channelkey} channel - - {channeldesc} - - Usage: - {lower_channelkey} - {lower_channelkey}/history [start] - {lower_channelkey}/me - {lower_channelkey}/who - - Switch: - history: View 20 previous messages, either from the end or - from number of messages from the end. - me: Perform an emote on this channel. - who: View who is connected to this channel. - - Example: - {lower_channelkey} Hello World! - {lower_channelkey}/history - {lower_channelkey}/history 30 - {lower_channelkey}/me grins. - {lower_channelkey}/who - """ - # note that channeldesc and lower_channelkey will be filled - # automatically by ChannelHandler - - # this flag is what identifies this cmd as a channel cmd - # and branches off to the system send-to-channel command - # (which is customizable by admin) - is_channel = True - key = "general" - help_category = "Channel Names" - obj = None - arg_regex = "" -``` - -There are some differences here compared to most common commands. - -- There is something disconcerting in the class docstring. Some information is -between curly braces. This is a format-style which is only used for channel -commands. `{channelkey}` will be replaced by the actual channel key (like - public). `{channeldesc}` will be replaced by the channel description (like - "public channel"). And `{lower_channelkey}`. -- We have set `is_channel` to `True` in the command class variables. You -shouldn't worry too much about that: it just tells Evennia this is a special -command just for channels. -- `key` is a bit misleading because it will be replaced eventually. So we -could set it to virtually anything. -- The `obj` class variable is another one we won't detail right now. -- `arg_regex` is important: the default `arg_regex` in the channel command will -forbid to use switches (a slash just after the channel name is not allowed). -That's why we enforce it here, we allow any syntax. - -> What will become of this command? - -Well, when we'll be through with it, and once we'll add it as the default -command to handle channels, Evennia will create one per existing channel. For -instance, the public channel will receive one command of this class, with `key` -set to `public` and `aliases` set to the channel aliases (like `['pub']`). - -> Can I see it work? - -Not just yet, there's still a lot of code needed. - -Okay we have the command structure but it's rather empty. - -### The parse method - -The `parse` method is called before `func` in every command. Its job is to -parse arguments and in our case, we will analyze switches here. - -```python -# ... - def parse(self): - """ - Simple parser - """ - # channel-handler sends channame:msg here. - channelname, msg = self.args.split(":", 1) - self.switch = None - if msg.startswith("/"): - try: - switch, msg = msg[1:].split(" ", 1) - except ValueError: - switch = msg[1:] - msg = "" - - self.switch = switch.lower().strip() - - self.args = (channelname.strip(), msg.strip()) -``` - -Reading the comments we see that the channel handler will send the command in a -strange way: a string with the channel name, a colon and the actual message -entered by the player. So if the player enters "public hello", the command -`args` will contain `"public:hello"`. You can look at the way the channel name -and message are parsed, this can be used in a lot of different commands. - -Next we check if there's any switch, that is, if the message starts with a -slash. This would be the case if a player entered `public/me jumps up and -down`, for instance. If there is a switch, we save it in `self.switch`. We -alter `self.args` at the end to contain a tuple with two values: the channel -name, and the message (if a switch was used, notice that the switch will be - stored in `self.switch`, not in the second element of `self.args`). - -### The command func - -Finally, let's see the `func` method in the command class. It will have to -handle switches and also the raw message to send if no switch was used. - - -```python -# ... - def func(self): - """ - Create a new message and send it to channel, using - the already formatted input. - """ - channelkey, msg = self.args - caller = self.caller - channel = ChannelDB.objects.get_channel(channelkey) - - # Check that the channel exists - if not channel: - self.msg(_("Channel '%s' not found.") % channelkey) - return - - # Check that the caller is connected - if not channel.has_connection(caller): - string = "You are not connected to channel '%s'." - self.msg(string % channelkey) - return - - # Check that the caller has send access - if not channel.access(caller, 'send'): - string = "You are not permitted to send to channel '%s'." - self.msg(string % channelkey) - return - - # Handle the various switches - if self.switch == "me": - if not msg: - self.msg("What do you want to do on this channel?") - else: - msg = "{} {}".format(caller.key, msg) - channel.msg(msg, online=True) - elif self.switch: - self.msg("{}: Invalid switch {}.".format(channel.key, self.switch)) - elif not msg: - self.msg("Say what?") - else: - if caller in channel.mutelist: - self.msg("You currently have %s muted." % channel) - return - channel.msg(msg, senders=self.caller, online=True) -``` - -- First of all, we try to get the channel object from the channel name we have -in the `self.args` tuple. We use `ChannelDB.objects.get_channel` this time -because we know the channel name isn't an alias (that was part of the deal, - `channelname` in the `parse` method contains a command key). -- We check that the channel does exist. -- We then check that the caller is connected to the channel. Remember, if the -caller isn't connected, we shouldn't allow him to use this command (that - includes the switches on channels). -- We then check that the caller has access to the channel's `send` lock. This -time, we make sure the caller can send messages to the channel, no matter what -operation he's trying to perform. -- Finally we handle switches. We try only one switch: `me`. This switch would -be used if a player entered `public/me jumps up and down` (to do a channel - emote). -- We handle the case where the switch is unknown and where there's no switch -(the player simply wants to talk on this channel). - -The good news: The code is not too complicated by itself. The bad news is that -this is just an abridged version of the code. If you want to handle all the -switches mentioned in the command help, you will have more code to write. This -is left as an exercise. - -### End of class - -It's almost done, but we need to add a method in this command class that isn't -often used. I won't detail it's usage too much, just know that Evennia will use -it and will get angry if you don't add it. So at the end of your class, just -add: - -```python -# ... - def get_extra_info(self, caller, **kwargs): - """ - Let users know that this command is for communicating on a channel. - - Args: - caller (TypedObject): A Character or Account who has entered an ambiguous command. - - Returns: - A string with identifying information to disambiguate the object, conventionally with a -preceding space. - """ - return " (channel)" -``` - -### Adding this channel command - -Contrary to most Evennia commands, we won't add our `ChannelCommand` to a -`CmdSet`. Instead we need to tell Evennia that it should use the command we -just created instead of its default channel-command. - -In your `server/conf/settings.py` file, add a new setting: - -```python -# Channel options -CHANNEL_COMMAND_CLASS = "commands.comms.ChannelCommand" -``` - -Then you can reload your game. Try to type `public hello` and `public/me jumps -up and down`. Don't forget to enter `help public` to see if your command has -truly been added. - -## Conclusion and full code - -That was some adventure! And there's still things to do! But hopefully, this -tutorial will have helped you in designing your own channel system. Here are a -few things to do: - -- Add more switches to handle various actions, like changing the description of -a channel for instance, or listing the connected participants. -- Remove the default Evennia commands to handle channels. -- Alter the behavior of the channel system so it better aligns with what you -want to do. - -As a special bonus, you can find a full, working example of a communication -system similar to the one I've shown you: this is a working example, it -integrates all switches and does ever some extra checking, but it's also very -close from the code I've provided here. Notice, however, that this resource is -external to Evennia and not maintained by anyone but the original author of -this article. - -[Read the full example on Github](https://github.com/vincent-lg/avenew/blob/master/commands/comms.py) diff --git a/docs/0.9.5/_sources/Debugging.md.txt b/docs/0.9.5/_sources/Debugging.md.txt deleted file mode 100644 index 62e199037e..0000000000 --- a/docs/0.9.5/_sources/Debugging.md.txt +++ /dev/null @@ -1,296 +0,0 @@ -# Debugging - - -Sometimes, an error is not trivial to resolve. A few simple `print` statements is not enough to find -the cause of the issue. Running a *debugger* can then be very helpful and save a lot of time. -Debugging -means running Evennia under control of a special *debugger* program. This allows you to stop the -action at a given point, view the current state and step forward through the program to see how its -logic works. - -Evennia natively supports these debuggers: - -- [Pdb](https://docs.python.org/2/library/pdb.html) is a part of the Python distribution and - available out-of-the-box. -- [PuDB](https://pypi.org/project/pudb/) is a third-party debugger that has a slightly more - 'graphical', curses-based user interface than pdb. It is installed with `pip install pudb`. - -## Debugging Evennia - -To run Evennia with the debugger, follow these steps: - -1. Find the point in the code where you want to have more insight. Add the following line at that - point. - ```python - from evennia import set_trace;set_trace() - ``` -2. (Re-)start Evennia in interactive (foreground) mode with `evennia istart`. This is important - - without this step the debugger will not start correctly - it will start in this interactive - terminal. -3. Perform the steps that will trigger the line where you added the `set_trace()` call. The debugger - will start in the terminal from which Evennia was interactively started. - -The `evennia.set_trace` function takes the following arguments: - - -```python - evennia.set_trace(debugger='auto', term_size=(140, 40)) -``` - -Here, `debugger` is one of `pdb`, `pudb` or `auto`. If `auto`, use `pudb` if available, otherwise -use `pdb`. The `term_size` tuple sets the viewport size for `pudb` only (it's ignored by `pdb`). - - -## A simple example using pdb - -The debugger is useful in different cases, but to begin with, let's see it working in a command. -Add the following test command (which has a range of deliberate errors) and also add it to your -default cmdset. Then restart Evennia in interactive mode with `evennia istart`. - - -```python -# In file commands/command.py - - -class CmdTest(Command): - - """ - A test command just to test pdb. - - Usage: - test - - """ - - key = "test" - - def func(self): - from evennia import set_trace; set_trace() # <--- start of debugger - obj = self.search(self.args) - self.msg("You've found {}.".format(obj.get_display_name())) - -``` - -If you type `test` in your game, everything will freeze. You won't get any feedback from the game, -and you won't be able to enter any command (nor anyone else). It's because the debugger has started -in your console, and you will find it here. Below is an example with `pdb`. - -``` -... -> .../mygame/commands/command.py(79)func() --> obj = self.search(self.args) -(Pdb) - -``` - -`pdb` notes where it has stopped execution and, what line is about to be executed (in our case, `obj -= self.search(self.args)`), and ask what you would like to do. - -### Listing surrounding lines of code - -When you have the `pdb` prompt `(Pdb)`, you can type in different commands to explore the code. The -first one you should know is `list` (you can type `l` for short): - -``` -(Pdb) l - 43 - 44 key = "test" - 45 - 46 def func(self): - 47 from evennia import set_trace; set_trace() # <--- start of debugger - 48 -> obj = self.search(self.args) - 49 self.msg("You've found {}.".format(obj.get_display_name())) - 50 - 51 # ------------------------------------------------------------- - 52 # - 53 # The default commands inherit from -(Pdb) -``` - -Okay, this didn't do anything spectacular, but when you become more confident with `pdb` and find -yourself in lots of different files, you sometimes need to see what's around in code. Notice that -there is a little arrow (`->`) before the line that is about to be executed. - -This is important: **about to be**, not **has just been**. You need to tell `pdb` to go on (we'll -soon see how). - -### Examining variables - -`pdb` allows you to examine variables (or really, to run any Python instruction). It is very useful -to know the values of variables at a specific line. To see a variable, just type its name (as if -you were in the Python interpreter: - -``` -(Pdb) self - -(Pdb) self.args -u'' -(Pdb) self.caller - -(Pdb) -``` - -If you try to see the variable `obj`, you'll get an error: - -``` -(Pdb) obj -*** NameError: name 'obj' is not defined -(Pdb) -``` - -That figures, since at this point, we haven't created the variable yet. - -> Examining variable in this way is quite powerful. You can even run Python code and keep on -> executing, which can help to check that your fix is actually working when you have identified an -> error. If you have variable names that will conflict with `pdb` commands (like a `list` -> variable), you can prefix your variable with `!`, to tell `pdb` that what follows is Python code. - -### Executing the current line - -It's time we asked `pdb` to execute the current line. To do so, use the `next` command. You can -shorten it by just typing `n`: - -``` -(Pdb) n -AttributeError: "'CmdTest' object has no attribute 'search'" -> .../mygame/commands/command.py(79)func() --> obj = self.search(self.args) -(Pdb) -``` - -`Pdb` is complaining that you try to call the `search` method on a command... whereas there's no -`search` method on commands. The character executing the command is in `self.caller`, so we might -change our line: - -```python -obj = self.caller.search(self.args) -``` - -### Letting the program run - -`pdb` is waiting to execute the same instruction... it provoked an error but it's ready to try -again, just in case. We have fixed it in theory, but we need to reload, so we need to enter a -command. To tell `pdb` to terminate and keep on running the program, use the `continue` (or `c`) -command: - -``` -(Pdb) c -... -``` - -You see an error being caught, that's the error we have fixed... or hope to have. Let's reload the -game and try again. You need to run `evennia istart` again and then run `test` to get into the -command again. - -``` -> .../mygame/commands/command.py(79)func() --> obj = self.caller.search(self.args) -(Pdb) - -``` - -`pdb` is about to run the line again. - -``` -(Pdb) n -> .../mygame/commands/command.py(80)func() --> self.msg("You've found {}.".format(obj.get_display_name())) -(Pdb) -``` - -This time the line ran without error. Let's see what is in the `obj` variable: - -``` -(Pdb) obj -(Pdb) print obj -None -(Pdb) -``` - -We have entered the `test` command without parameter, so no object could be found in the search -(`self.args` is an empty string). - -Let's allow the command to continue and try to use an object name as parameter (although, we should -fix that bug too, it would be better): - -``` -(Pdb) c -... -``` - -Notice that you'll have an error in the game this time. Let's try with a valid parameter. I have -another character, `barkeep`, in this room: - -```test barkeep``` - -And again, the command freezes, and we have the debugger opened in the console. - -Let's execute this line right away: - -``` -> .../mygame/commands/command.py(79)func() --> obj = self.caller.search(self.args) -(Pdb) n -> .../mygame/commands/command.py(80)func() --> self.msg("You've found {}.".format(obj.get_display_name())) -(Pdb) obj - -(Pdb) -``` - -At least this time we have found the object. Let's process... - -``` -(Pdb) n -TypeError: 'get_display_name() takes exactly 2 arguments (1 given)' -> .../mygame/commands/command.py(80)func() --> self.msg("You've found {}.".format(obj.get_display_name())) -(Pdb) -``` - -As an exercise, fix this error, reload and run the debugger again. Nothing better than some -experimenting! - -Your debugging will often follow the same strategy: - -1. Receive an error you don't understand. -2. Put a breaking point **BEFORE** the error occurs. -3. Run the code again and see the debugger open. -4. Run the program line by line,examining variables, checking the logic of instructions. -5. Continue and try again, each step a bit further toward the truth and the working feature. - -### Stepping through a function - -`n` is useful, but it will avoid stepping inside of functions if it can. But most of the time, when -we have an error we don't understand, it's because we use functions or methods in a way that wasn't -intended by the developer of the API. Perhaps using wrong arguments, or calling the function in a -situation that would cause a bug. When we have a line in the debugger that calls a function or -method, we can "step" to examine it further. For instance, in the previous example, when `pdb` was -about to execute `obj = self.caller.search(self.args)`, we may want to see what happens inside of -the `search` method. - -To do so, use the `step` (or `s`) command. This command will show you the definition of the -function/method and you can then use `n` as before to see it line-by-line. In our little example, -stepping through a function or method isn't that useful, but when you have an impressive set of -commands, functions and so on, it might really be handy to examine some feature and make sure they -operate as planned. - -## Cheat-sheet of pdb/pudb commands - -PuDB and Pdb share the same commands. The only real difference is how it's presented. The `look` -command is not needed much in `pudb` since it displays the code directly in its user interface. - -| Pdb/PuDB command | To do what | -| ----------- | ---------- | -| list (or l) | List the lines around the point of execution (not needed for `pudb`, it will show -this directly). | -| print (or p) | Display one or several variables. | -| `!` | Run Python code (using a `!` is often optional). | -| continue (or c) | Continue execution and terminate the debugger for this time. | -| next (or n) | Execute the current line and goes to the next one. | -| step (or s) | Step inside of a function or method to examine it. | -| `` | Repeat the last command (don't type `n` repeatedly, just type it once and then press -`` to repeat it). | - -If you want to learn more about debugging with Pdb, you will find an [interesting tutorial on that -topic here](https://pymotw.com/3/pdb/). diff --git a/docs/0.9.5/_sources/Default-Commands.md.txt b/docs/0.9.5/_sources/Default-Commands.md.txt deleted file mode 100644 index 478c75854a..0000000000 --- a/docs/0.9.5/_sources/Default-Commands.md.txt +++ /dev/null @@ -1,107 +0,0 @@ - - -# Default Commands - -The full set of default Evennia commands currently contains 98 commands in 9 source -files. Our policy for adding default commands is outlined [here](./Using-MUX-as-a-Standard.md). The -[Commands](./Commands.md) documentation explains how Commands work as well as make new or customize -existing ones. Note that this page is auto-generated. Report problems to the [issue -tracker](github:issues). - -```{note} -Some game-states adds their own Commands which are not listed here. Examples include editing a text -with [EvEditor](./EvEditor.md), flipping pages in [EvMore](./EvMore.md) or using the -[Batch-Processor](./Batch-Processors.md)'s interactive mode. -``` - -- [**__unloggedin_look_command** [l, look]](evennia.commands.default.unloggedin.CmdUnconnectedLook) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**about** [version]](evennia.commands.default.system.CmdAbout) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**access** [groups, hierarchy]](evennia.commands.default.general.CmdAccess) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**accounts** [account, listaccounts]](evennia.commands.default.system.CmdAccounts) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**addcom** [chanalias, aliaschan]](evennia.commands.default.comms.CmdAddCom) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**alias** [setobjalias]](evennia.commands.default.building.CmdSetObjAlias) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**allcom**](evennia.commands.default.comms.CmdAllCom) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**batchcode** [batchcodes]](evennia.commands.default.batchprocess.CmdBatchCode) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**batchcommands** [batchcommand, batchcmd]](evennia.commands.default.batchprocess.CmdBatchCommands) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**cboot**](evennia.commands.default.comms.CmdCBoot) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**ccreate** [channelcreate]](evennia.commands.default.comms.CmdChannelCreate) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**cdesc**](evennia.commands.default.comms.CmdCdesc) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**cdestroy**](evennia.commands.default.comms.CmdCdestroy) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**cemit** [cmsg]](evennia.commands.default.comms.CmdCemit) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**channels** [all channels, clist, channellist, chanlist, comlist]](evennia.commands.default.comms.CmdChannels) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**charcreate**](evennia.commands.default.account.CmdCharCreate) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**chardelete**](evennia.commands.default.account.CmdCharDelete) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**clock**](evennia.commands.default.comms.CmdClock) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**cmdsets** [listcmsets]](evennia.commands.default.building.CmdListCmdSets) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**color**](evennia.commands.default.account.CmdColorTest) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**connect** [conn, co, con]](evennia.commands.default.unloggedin.CmdUnconnectedConnect) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**copy**](evennia.commands.default.building.CmdCopy) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**cpattr**](evennia.commands.default.building.CmdCpAttr) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**create**](evennia.commands.default.building.CmdCreate) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**create** [cr, cre]](evennia.commands.default.unloggedin.CmdUnconnectedCreate) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**cwho**](evennia.commands.default.comms.CmdCWho) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**delcom** [delchanalias, delaliaschan]](evennia.commands.default.comms.CmdDelCom) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**desc** [describe]](evennia.commands.default.building.CmdDesc) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**destroy** [delete, del]](evennia.commands.default.building.CmdDestroy) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**dig**](evennia.commands.default.building.CmdDig) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**drop**](evennia.commands.default.general.CmdDrop) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**encoding** [encode]](evennia.commands.default.unloggedin.CmdUnconnectedEncoding) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**examine** [exam, ex]](evennia.commands.default.building.CmdExamine) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Building_) -- [**find** [search, locate]](evennia.commands.default.building.CmdFind) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**get** [grab]](evennia.commands.default.general.CmdGet) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**give**](evennia.commands.default.general.CmdGive) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**grapevine2chan**](evennia.commands.default.comms.CmdGrapevine2Chan) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**help** [?]](evennia.commands.default.help.CmdHelp) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**help** [h, ?]](evennia.commands.default.unloggedin.CmdUnconnectedHelp) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**home**](evennia.commands.default.general.CmdHome) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**ic** [puppet]](evennia.commands.default.account.CmdIC) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**info**](evennia.commands.default.unloggedin.CmdUnconnectedInfo) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**inventory** [inv, i]](evennia.commands.default.general.CmdInventory) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**irc2chan**](evennia.commands.default.comms.CmdIRC2Chan) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**ircstatus**](evennia.commands.default.comms.CmdIRCStatus) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**link**](evennia.commands.default.building.CmdLink) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**lock** [locks]](evennia.commands.default.building.CmdLock) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**look** [l, ls]](evennia.commands.default.account.CmdOOCLook) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**look** [l, ls]](evennia.commands.default.general.CmdLook) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**mvattr**](evennia.commands.default.building.CmdMvAttr) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**name** [rename]](evennia.commands.default.building.CmdName) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**nick** [nickname, nicks]](evennia.commands.default.general.CmdNick) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**objects** [listobjs, listobjects, db, stats]](evennia.commands.default.system.CmdObjects) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**ooc** [unpuppet]](evennia.commands.default.account.CmdOOC) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**open**](evennia.commands.default.building.CmdOpen) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**option** [options]](evennia.commands.default.account.CmdOption) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**page** [tell]](evennia.commands.default.comms.CmdPage) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**password**](evennia.commands.default.account.CmdPassword) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**pose** [:, emote]](evennia.commands.default.general.CmdPose) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**py** [!]](evennia.commands.default.system.CmdPy) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) -- [**quell** [unquell]](evennia.commands.default.account.CmdQuell) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**quit**](evennia.commands.default.account.CmdQuit) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**quit** [qu, q]](evennia.commands.default.unloggedin.CmdUnconnectedQuit) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**reload** [restart]](evennia.commands.default.system.CmdReload) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) -- [**reset** [reboot]](evennia.commands.default.system.CmdReset) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) -- [**rss2chan**](evennia.commands.default.comms.CmdRSS2Chan) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**say** [', "]](evennia.commands.default.general.CmdSay) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**screenreader**](evennia.commands.default.unloggedin.CmdUnconnectedScreenreader) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**script** [addscript]](evennia.commands.default.building.CmdScript) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**scripts** [globalscript, listscripts]](evennia.commands.default.system.CmdScripts) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**server** [serverprocess, serverload]](evennia.commands.default.system.CmdServerLoad) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**service** [services]](evennia.commands.default.system.CmdService) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**sessions**](evennia.commands.default.account.CmdSessions) (cmdset: [SessionCmdSet](evennia.commands.default.cmdset_session.SessionCmdSet), help-category: _General_) -- [**set**](evennia.commands.default.building.CmdSetAttribute) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**setdesc**](evennia.commands.default.general.CmdSetDesc) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**sethelp**](evennia.commands.default.help.CmdSetHelp) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**sethome**](evennia.commands.default.building.CmdSetHome) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**shutdown**](evennia.commands.default.system.CmdShutdown) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) -- [**spawn** [olc]](evennia.commands.default.building.CmdSpawn) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**style**](evennia.commands.default.account.CmdStyle) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**tag** [tags]](evennia.commands.default.building.CmdTag) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**tel** [teleport]](evennia.commands.default.building.CmdTeleport) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**tickers**](evennia.commands.default.system.CmdTickers) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**time** [uptime]](evennia.commands.default.system.CmdTime) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**tunnel** [tun]](evennia.commands.default.building.CmdTunnel) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**typeclass** [swap, type, parent, update]](evennia.commands.default.building.CmdTypeclass) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**unlink**](evennia.commands.default.building.CmdUnLink) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**whisper**](evennia.commands.default.general.CmdWhisper) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**who** [doing]](evennia.commands.default.account.CmdWho) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**wipe**](evennia.commands.default.building.CmdWipe) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - diff --git a/docs/0.9.5/_sources/Default-Exit-Errors.md.txt b/docs/0.9.5/_sources/Default-Exit-Errors.md.txt deleted file mode 100644 index 81a26d45e3..0000000000 --- a/docs/0.9.5/_sources/Default-Exit-Errors.md.txt +++ /dev/null @@ -1,122 +0,0 @@ -# Default Exit Errors - - -Evennia allows for exits to have any name. The command "kitchen" is a valid exit name as well as -"jump out the window" or "north". An exit actually consists of two parts: an [Exit Object](./Objects.md) -and an [Exit Command](./Commands.md) stored on said exit object. The command has the same key and aliases -as the object, which is why you can see the exit in the room and just write its name to traverse it. - -If you try to enter the name of a non-existing exit, it is thus the same as trying a non-exising -command; Evennia doesn't care about the difference: - - > jump out the window - Command 'jump out the window' is not available. Type "help" for help. - -Many games don't need this type of freedom however. They define only the cardinal directions as -valid exit names (Evennia's `@tunnel` command also offers this functionality). In this case, the -error starts to look less logical: - - > west - Command 'west' is not available. Maybe you meant "@set" or "@reset"? - -Since we for our particular game *know* that west is an exit direction, it would be better if the -error message just told us that we couldn't go there. - -## Adding default error commands - -To solve this you need to be aware of how to [write and add new commands](./Adding-Command-Tutorial.md). -What you need to do is to create new commands for all directions you want to support in your game. -In this example all we'll do is echo an error message, but you could certainly consider more -advanced uses. You add these commands to the default command set. Here is an example of such a set -of commands: - -```python -# for example in a file mygame/commands/movecommands.py - -from evennia import default_cmds - -class CmdExitError(default_cmds.MuxCommand): - "Parent class for all exit-errors." - locks = "cmd:all()" - arg_regex = r"\s|$" - auto_help = False - def func(self): - "returns the error" - self.caller.msg("You cannot move %s." % self.key) - -class CmdExitErrorNorth(CmdExitError): - key = "north" - aliases = ["n"] - -class CmdExitErrorEast(CmdExitError): - key = "east" - aliases = ["e"] - -class CmdExitErrorSouth(CmdExitError): - key = "south" - aliases = ["s"] - -class CmdExitErrorWest(CmdExitError): - key = "west" - aliases = ["w"] -``` - -Make sure to add the directional commands (not their parent) to the `CharacterCmdSet` class in -`mygame/commands/default_cmdsets.py`: - -```python -# in mygame/commands/default_cmdsets.py - -from commands import movecommands - -# [...] -class CharacterCmdSet(default_cmds.CharacterCmdSet): - # [...] - def at_cmdset_creation(self): - # [...] - self.add(movecommands.CmdExitErrorNorth()) - self.add(movecommands.CmdExitErrorEast()) - self.add(movecommands.CmdExitErrorSouth()) - self.add(movecommands.CmdExitErrorWest()) -``` - -After a `@reload` these commands (assuming you don't get any errors - check your log) will be -loaded. What happens henceforth is that if you are in a room with an Exitobject (let's say it's -"north"), the proper Exit-command will overload your error command (also named "north"). But if you -enter an direction without having a matching exit for it, you will fallback to your default error -commands: - - > east - You cannot move east. - -Further expansions by the exit system (including manipulating the way the Exit command itself is -created) can be done by modifying the [Exit typeclass](./Typeclasses.md) directly. - -## Additional Comments - -So why didn't we create a single error command above? Something like this: - -```python - class CmdExitError(default_cmds.MuxCommand): - "Handles all exit-errors." - key = "error_cmd" - aliases = ["north", "n", - "east", "e", - "south", "s", - "west", "w"] - #[...] -``` -The anwer is that this would *not* work and understanding why is important in order to not be -confused when working with commands and command sets. - -The reason it doesn't work is because Evennia's [command system](./Commands.md) compares commands *both* -by `key` and by `aliases`. If *either* of those match, the two commands are considered *identical* -as far as cmdset merging system is concerned. - -So the above example would work fine as long as there were no Exits at all in the room. But what -happens when we enter a room with an exit "north"? The Exit's cmdset is merged onto the default one, -and since there is an alias match, the system determines our `CmdExitError` to be identical. It is -thus overloaded by the Exit command (which also correctly defaults to a higher priority). The result -is that you can go through the north exit normally but none of the error messages for the other -directions are available since the single error command was completely overloaded by the single -matching "north" exit-command. diff --git a/docs/0.9.5/_sources/Developer-Central.md.txt b/docs/0.9.5/_sources/Developer-Central.md.txt deleted file mode 100644 index e2ee32fc53..0000000000 --- a/docs/0.9.5/_sources/Developer-Central.md.txt +++ /dev/null @@ -1,170 +0,0 @@ -# Developer Central - - -This page serves as a central nexus for information on using Evennia as well as developing the -library itself. - -### General Evennia development information - -- [Introduction to coding with Evennia](./Coding-Introduction.md) -- [Evennia Licensing FAQ](./Licensing.md) -- [Contributing to Evennia](./Contributing.md) -- [Code Style Guide](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md) (Important!) -- [Policy for 'MUX-like' default commands](./Using-MUX-as-a-Standard.md) -- [Setting up a Git environment for coding](./Version-Control.md) -- [Getting started with Travis and Github for continuous integration testing](./Using-Travis.md) -- [Planning your own Evennia game](./Game-Planning.md) -- [First steps coding Evennia](./First-Steps-Coding.md) -- [Translating Evennia](./Internationalization.md#translating-evennia) -- [Evennia Quirks](./Quirks.md) to keep in mind. -- [Directions for configuring PyCharm with Evennia on Windows](./Setting-up-PyCharm.md) - -### Evennia API - -- [Directory Overview](./Directory-Overview.md) -- [evennia - the flat API](./Evennia-API.md) - - [Running and Testing Python code](./Execute-Python-Code.md) - -#### Core components and protocols - -- [Server and Portal](./Portal-And-Server.md) - - [Sessions](./Sessions.md) - - [Configuration and module plugins](./Server-Conf.md) -- [The message path](./Messagepath.md) - - [OOB](./OOB.md) - Out-of-band communication - - [Inputfuncs](./Inputfuncs.md) - - [Adding new protocols (client APIs) and services](./Custom-Protocols.md) -- [Adding new database models](./New-Models.md) -- [Unit Testing](./Unit-Testing.md) -- [Running profiling](./Profiling.md) -- [Debugging your code](./Debugging.md) - -#### In-game Commands - -- [Command System overview](./Command-System.md) -- [Commands](./Commands.md) -- [Command Sets](./Command-Sets.md) -- [Command Auto-help](./Help-System.md#command-auto-help-system) - -#### Typeclasses and related concepts - -- [General about Typeclasses](./Typeclasses.md) -- [Objects](./Objects.md) - - [Characters](./Objects.md#characters) - - [Rooms](./Objects.md#rooms) - - [Exits](./Objects.md#exits) -- [Accounts](./Accounts.md) -- [Communications](./Communications.md) - - [Channels](./Communications.md#channels) -- [Scripts](./Scripts.md) - - [Global Scripts](./Scripts.md#global-scripts) - - [TickerHandler](./TickerHandler.md) - - [utils.delay](./Coding-Utils.md#utilsdelay) - - [MonitorHandler](./MonitorHandler.md) -- [Attributes](./Attributes.md) -- [Nicks](./Nicks.md) -- [Tags](./Tags.md) - - [Tags for Aliases and Permissions](./Tags.md#using-aliases-and-permissions) - -#### Web - -- [Web features overview](./Web-Features.md) -- [The Webclient](./Webclient.md) -- [Web tutorials](./Web-Tutorial.md) - -#### Other systems - -- [Locks](./Locks.md) - - [Permissions](./Locks.md#permissions) -- [Help System](./Help-System.md) -- [Signals](./Signals.md) -- [General coding utilities](./Coding-Utils.md) - - [Utils in evennia.utils.utils](evennia.utils.utils) -- [Game time](./Coding-Utils.md#game-time) -- [Game Menus](./EvMenu.md) (EvMenu) -- [Text paging/scrolling](./EvMore.md) (EvMore) -- [Text Line Editor](./EvEditor.md) (EvEditor) -- [Text Tables](github:evennia.utils.evtable) (EvTable) -- [Text Form generation](github:evennia.utils.evform) (EvForm) -- [Spawner and Prototypes](./Spawner-and-Prototypes.md) -- [Inlinefuncs](./TextTags.md#inline-functions) -- [Asynchronous execution](./Async-Process.md) - -### Developer brainstorms and whitepages - -- [API refactoring](./API-refactoring.md), discussing what parts of the Evennia API needs a -refactoring/cleanup/simplification -- [Docs refactoring](./Docs-refactoring.md), discussing how to reorganize and structure this wiki/docs -better going forward -- [Webclient brainstorm](./Webclient-brainstorm.md), some ideas for a future webclient gui -- [Roadmap](./Roadmap.md), a tentative list of future major features -- [Change log](https://github.com/evennia/evennia/blob/master/CHANGELOG.md) of big Evennia updates -over time - - -[group]: https://groups.google.com/forum/#!forum/evennia -[online-form]: https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDE -zY1RoZGc6MQ#gid=0 -[issues]: https://github.com/evennia/evennia/issues - - -```{toctree} - :hidden: - - Coding-Introduction - Licensing - Contributing - Using-MUX-as-a-Standard - Version-Control - Using-Travis - Game-Planning - First-Steps-Coding - Internationalization - Quirks - Setting-up-PyCharm - Directory-Overview - Evennia-API - Execute-Python-Code - Portal-And-Server - Sessions - Server-Conf - Messagepath - OOB - Inputfuncs - Custom-Protocols - New-Models - Unit-Testing - Profiling - Debugging - Command-System - Commands - Command-Sets - Help-System - Typeclasses - Objects - Accounts - Communications - Scripts - TickerHandler - Coding-Utils - MonitorHandler - Attributes - Nicks - Tags - Web-Features - Webclient - Web-Tutorial - Locks - Signals - Coding-Utils - EvMenu - EvMore - EvEditor - Spawner-and-Prototypes - TextTags - Async-Process - API-refactoring - Docs-refactoring - Webclient-brainstorm - -``` diff --git a/docs/0.9.5/_sources/Dialogues-in-events.md.txt b/docs/0.9.5/_sources/Dialogues-in-events.md.txt deleted file mode 100644 index f3bb5c0d06..0000000000 --- a/docs/0.9.5/_sources/Dialogues-in-events.md.txt +++ /dev/null @@ -1,247 +0,0 @@ -# Dialogues in events - - -- Next tutorial: [adding a voice-operated elevator with events](A-voice-operated-elevator-using- -events). - -This tutorial will walk you through the steps to create several dialogues with characters, using the -[in-game Python -system](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md). -This tutorial assumes the in-game Python system is installed in your game. If it isn't, you can -follow the installation steps given in [the documentation on in-game -Python](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md), and -come back on this tutorial once the system is installed. **You do not need to read** the entire -documentation, it's a good reference, but not the easiest way to learn about it. Hence these -tutorials. - -The in-game Python system allows to run code on individual objects in some situations. You don't -have to modify the source code to add these features, past the installation. The entire system -makes it easy to add specific features to some objects, but not all. This is why it can be very -useful to create a dialogue system taking advantage of the in-game Python system. - -> What will we try to do? - -In this tutorial, we are going to create a basic dialogue to have several characters automatically -respond to specific messages said by others. - -## A first example with a first character - -Let's create a character to begin with. - - @charcreate a merchant - -This will create a merchant in the room where you currently are. It doesn't have anything, like a -description, you can decorate it a bit if you like. - -As said above, the in-game Python system consists in linking objects with arbitrary code. This code -will be executed in some circumstances. Here, the circumstance is "when someone says something in -the same room", and might be more specific like "when someone says hello". We'll decide what code -to run (we'll actually type the code in-game). Using the vocabulary of the in-game Python system, -we'll create a callback: a callback is just a set of lines of code that will run under some -conditions. - -You can have an overview of every "conditions" in which callbacks can be created using the `@call` -command (short for `@callback`). You need to give it an object as argument. Here for instance, we -could do: - - @call a merchant - -You should see a table with three columns, showing the list of events existing on our newly-created -merchant. There are quite a lot of them, as it is, althougn no line of code has been set yet. For -our system, you might be more interested by the line describing the `say` event: - - | say | 0 (0) | After another character has said something in | - | | | the character's room. | - -We'll create a callback on the `say` event, called when we say "hello" in the merchant's room: - - @call/add a merchant = say hello - -Before seeing what this command displays, let's see the command syntax itself: - -- `@call` is the command name, `/add` is a switch. You can read the help of the command to get the -help of available switches and a brief overview of syntax. -- We then enter the object's name, here "a merchant". You can enter the ID too ("#3" in my case), -which is useful to edit the object when you're not in the same room. You can even enter part of the -name, as usual. -- An equal sign, a simple separator. -- The event's name. Here, it's "say". The available events are displayed when you use `@call` -without switch. -- After a space, we enter the conditions in which this callback should be called. Here, the -conditions represent what the other character should say. We enter "hello". Meaning that if -someone says something containing "hello" in the room, the callback we are now creating will be -called. - -When you enter this command, you should see something like this: - -``` -After another character has said something in the character's room. -This event is called right after another character has said -something in the same location. The action cannot be prevented -at this moment. Instead, this event is ideal to create keywords -that would trigger a character (like a NPC) in doing something -if a specific phrase is spoken in the same location. - -To use this event, you have to specify a list of keywords as -parameters that should be present, as separate words, in the -spoken phrase. For instance, you can set a callback that would -fire if the phrase spoken by the character contains "menu" or -"dinner" or "lunch": - @call/add ... = say menu, dinner, lunch -Then if one of the words is present in what the character says, -this callback will fire. - -Variables you can use in this event: - speaker: the character speaking in this room. - character: the character connected to this event. - message: the text having been spoken by the character. -``` - -That's some list of information. What's most important to us now is: - -- The "say" event is called whenever someone else speaks in the room. -- We can set callbacks to fire when specific keywords are present in the phrase by putting them as -additional parameters. Here we have set this parameter to "hello". We can have several keywords -separated by a comma (we'll see this in more details later). -- We have three default variables we can use in this callback: `speaker` which contains the -character who speaks, `character` which contains the character who's modified by the in-game Python -system (here, or merchant), and `message` which contains the spoken phrase. - -This concept of variables is important. If it makes things more simple to you, think of them as -parameters in a function: they can be used inside of the function body because they have been set -when the function was called. - -This command has opened an editor where we can type our Python code. - -``` -----------Line Editor [Callback say of a merchant]-------------------------------- -01| -----------[l:01 w:000 c:0000]------------(:h for help)---------------------------- -``` - -For our first test, let's type something like: - -```python -character.location.msg_contents("{character} shrugs and says: 'well, yes, hello to you!'", -mapping=dict(character=character)) -``` - -Once you have entered this line, you can type `:wq` to save the editor and quit it. - -And now if you use the "say" command with a message containing "hello": - -``` -You say, "Hello sir merchant!" -a merchant(#3) shrugs and says: 'well, yes, hello to you!' -``` - -If you say something that doesn't contain "hello", our callback won't execute. - -**In summary**: - -1. When we say something in the room, using the "say" command, the "say" event of all characters -(except us) is called. -2. The in-game Python system looks at what we have said, and checks whether one of our callbacks in -the "say" event contains a keyword that we have spoken. -3. If so, call it, defining the event variables as we have seen. -4. The callback is then executed as normal Python code. Here we have called the `msg_contents` -method on the character's location (probably a room) to display a message to the entire room. We -have also used mapping to easily display the character's name. This is not specific to the in-game -Python system. If you feel overwhelmed by the code we've used, just shorten it and use something -more simple, for instance: - -```python -speaker.msg("You have said something to me.") -``` - -## The same callback for several keywords - -It's easy to create a callback that will be triggered if the sentence contains one of several -keywords. - - @call/add merchant = say trade, trader, goods - -And in the editor that opens: - -```python -character.location.msg_contents("{character} says: 'Ho well, trade's fine as long as roads are -safe.'", mapping=dict(character=character)) -``` - -Then you can say something with either "trade", "trader" or "goods" in your sentence, which should -call the callback: - -``` -You say, "and how is your trade going?" -a merchant(#3) says: 'Ho well, trade's fine as long as roads are safe.' -``` - -We can set several keywords when adding the callback. We just need to separate them with commas. - -## A longer callback - -So far, we have only set one line in our callbacks. Which is useful, but we often need more. For -an entire dialogue, you might want to do a bit more than that. - - @call/add merchant = say bandit, bandits - -And in the editor you can paste the following lines: - -```python -character.location.msg_contents("{character} says: 'Bandits he?'", -mapping=dict(character=character)) -character.location.msg_contents("{character} scratches his head, considering.", -mapping=dict(character=character)) -character.location.msg_contents("{character} whispers: 'Aye, saw some of them, north from here. No -trouble o' mine, but...'", mapping=dict(character=character)) -speaker.msg("{character} looks at you more -closely.".format(character=character.get_display_name(speaker))) -speaker.msg("{character} continues in a low voice: 'Ain't my place to say, but if you need to find -'em, they're encamped some distance away from the road, I guess near a cave or -something.'.".format(character=character.get_display_name(speaker))) -``` - -Now try to ask the merchant about bandits: - -``` -You say, "have you seen bandits?" -a merchant(#3) says: 'Bandits he?' -a merchant(#3) scratches his head, considering. -a merchant(#3) whispers: 'Aye, saw some of them, north from here. No trouble o' mine, but...' -a merchant(#3) looks at you more closely. -a merchant(#3) continues in a low voice: 'Ain't my place to say, but if you need to find 'em, -they're encamped some distance away from the road, I guess near a cave or something.'. -``` - -Notice here that the first lines of dialogue are spoken to the entire room, but then the merchant is -talking directly to the speaker, and only the speaker hears it. There's no real limit to what you -can do with this. - -- You can set a mood system, storing attributes in the NPC itself to tell you in what mood he is, -which will influence the information he will give... perhaps the accuracy of it as well. -- You can add random phrases spoken in some context. -- You can use other actions (you're not limited to having the merchant say something, you can ask -him to move, gives you something, attack if you have a combat system, or whatever else). -- The callbacks are in pure Python, so you can write conditions or loops. -- You can add in "pauses" between some instructions using chained events. This tutorial won't -describe how to do that however. You already have a lot to play with. - -## Tutorial F.A.Q. - -- **Q:** can I create several characters who would answer to specific dialogue? -- **A:** of course. Te in-game Python system is so powerful because you can set unique code for -various objects. You can have several characters answering to different things. You can even have -different characters in the room answering to greetings. All callbacks will be executed one after -another. -- **Q:** can I have two characters answering to the same dialogue in exactly the same way? -- **A:** It's possible but not so easy to do. Usually, event grouping is set in code, and depends -on different games. However, if it is for some infrequent occurrences, it's easy to do using -[chained events](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md#chained-events). -- **Q:** is it possible to deploy callbacks on all characters sharing the same prototype? -- **A:** not out of the box. This depends on individual settings in code. One can imagine that all -characters of some type would share some events, but this is game-specific. Rooms of the same zone -could share the same events as well. It is possible to do but requires modification of the source -code. - -- Next tutorial: [adding a voice-operated elevator with events](A-voice-operated-elevator-using- -events). diff --git a/docs/0.9.5/_sources/Directory-Overview.md.txt b/docs/0.9.5/_sources/Directory-Overview.md.txt deleted file mode 100644 index be69c1bf1b..0000000000 --- a/docs/0.9.5/_sources/Directory-Overview.md.txt +++ /dev/null @@ -1,68 +0,0 @@ -# Directory Overview - - -This is an overview of the directories relevant to Evennia coding. - -## The Game directory - -The game directory is created with `evennia --init `. In the Evennia documentation we always -assume it's called `mygame`. Apart from the `server/` subfolder within, you could reorganize this -folder if you preferred a different code structure for your game. - - - `mygame/` - - `commands/` - Overload default [Commands](./Commands.md) or add your own Commands/[Command -sets](./Command-Sets.md) here. - - `server`/ - The structure of this folder should not change since Evennia expects it. - - [`conf/`](https://github.com/evennia/evennia/tree/master/evennia/game_template/server) - All -server configuration files sits here. The most important file is `settings.py`. - - `logs/` - Portal log files are stored here (Server is logging to the terminal by default) - - `typeclasses/` - this folder contains empty templates for overloading default game entities of -Evennia. Evennia will automatically use the changes in those templates for the game entities it -creates. - - `web/` - This holds the [Web features](./Web-Features.md) of your game. - - `world/` - this is a "miscellaneous" folder holding everything related to the world you are -building, such as build scripts and rules modules that don't fit with one of the other folders. - -## Evennia library layout: - -If you cloned the GIT repo following the instructions, you will have a folder named `evennia`. The -top level of it contains Python package specific stuff such as a readme file, `setup.py` etc. It -also has two subfolders`bin/` and `evennia/` (again). - -The `bin/` directory holds OS-specific binaries that will be used when installing Evennia with `pip` -as per the [Getting started](./Getting-Started.md) instructions. The library itself is in the `evennia` -subfolder. From your code you will access this subfolder simply by `import evennia`. - - - evennia - - [`__init__.py`](./Evennia-API.md) - The "flat API" of Evennia resides here. - - [`commands/`](./Commands.md) - The command parser and handler. - - `default/` - The [default commands](./Default-Commands.md) and cmdsets. - - [`comms/`](./Communications.md) - Systems for communicating in-game. - - `contrib/` - Optional plugins too game-specific for core Evennia. - - `game_template/` - Copied to become the "game directory" when using `evennia --init`. - - [`help/`](./Help-System.md) - Handles the storage and creation of help entries. - - `locale/` - Language files ([i18n](./Internationalization.md)). - - [`locks/`](./Locks.md) - Lock system for restricting access to in-game entities. - - [`objects/`](./Objects.md) - In-game entities (all types of items and Characters). - - [`prototypes/`](./Spawner-and-Prototypes.md) - Object Prototype/spawning system and OLC menu - - [`accounts/`](./Accounts.md) - Out-of-game Session-controlled entities (accounts, bots etc) - - [`scripts/`](./Scripts.md) - Out-of-game entities equivalence to Objects, also with timer support. - - [`server/`](./Portal-And-Server.md) - Core server code and Session handling. - - `portal/` - Portal proxy and connection protocols. - - [`settings_default.py`](./Server-Conf.md#settings-file) - Root settings of Evennia. Copy settings -from here to `mygame/server/settings.py` file. - - [`typeclasses/`](./Typeclasses.md) - Abstract classes for the typeclass storage and database system. - - [`utils/`](./Coding-Utils.md) - Various miscellaneous useful coding resources. - - [`web/`](./Web-Features.md) - Web resources and webserver. Partly copied into game directory on -initialization. - -All directories contain files ending in `.py`. These are Python *modules* and are the basic units of -Python code. The roots of directories also have (usually empty) files named `__init__.py`. These are -required by Python so as to be able to find and import modules in other directories. When you have -run Evennia at least once you will find that there will also be `.pyc` files appearing, these are -pre-compiled binary versions of the `.py` files to speed up execution. - -The root of the `evennia` folder has an `__init__.py` file containing the "[flat API](./Evennia-API.md)". -This holds shortcuts to various subfolders in the evennia library. It is provided to make it easier -to find things; it allows you to just import `evennia` and access things from that rather than -having to import from their actual locations inside the source tree. diff --git a/docs/0.9.5/_sources/Docs-refactoring.md.txt b/docs/0.9.5/_sources/Docs-refactoring.md.txt deleted file mode 100644 index f8473beaac..0000000000 --- a/docs/0.9.5/_sources/Docs-refactoring.md.txt +++ /dev/null @@ -1,117 +0,0 @@ -# Docs refactoring - -This is a whitepage for free discussion about the wiki docs and refactorings needed. - -Note that this is not a forum. To keep things clean, each opinion text should ideally present a -clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be -held in chat or forum. - -### Griatch (Aug 13, 2019) - -This is how to make a discussion entry for the whitepage. Use any markdown formatting you need. Also -remember to copy your work to the clipboard before saving the page since if someone else edited the -page since you started, you'll have to reload and write again. - -#### (Sept 23, 2019) - -[This (now closed) issue by DamnedScholar](https://github.com/evennia/evennia/issues/1431) gives the -following suggestion: -> I think it would be useful for the pages that explain how to use various features of Evennia to -have explicit and easily visible links to the respective API entry or entries. Some pages do, but -not all. I imagine this as a single entry at the top of the page [...]. - -[This (now closed) issue by taladan](https://github.com/evennia/evennia/issues/1578) gives the -following suggestion: -> It would help me (and probably a couple of others) if there is a way to show the file path where a -particular thing exists. Maybe up under the 'last edited' line we could have a line like: -evennia/locks/lockhandler.py - -This would help in development to quickly refer to where a resource is located. - - -### Kovitikus (Sept. 11, 2019) - -[Batch Code](./Batch-Code-Processor.md) should have a link in the developer area. It is currently only -listed in the tutorials section as an afterthought to a tutorial title. - -*** - -In regards to the general structure of each wiki page: I'd like to see a table of contents at the -top of each one, so that it can be quickly navigated and is immediately apparent what sections are -covered on the page. Similar to the current [Getting Started](./Getting-Started.md) page. - -*** - -The structuring of the page should also include a quick reference cheatsheet for certain aspects. -Such as [Tags](./Tags.md) including a quick reference section at the top that lists an example of every -available method you can use in a clear and consistent format, along with a comment. Readers -shouldn't have to decipher the article to gather such basic information and it should instead be -available at first glance. - -Example of a quick reference: - -**Tags** -``` -# Add a tag. -obj.tags.add("label") - -# Remove a tag. -obj.tags.remove("label") - -# Remove all tags. -obj.tags.clear() - -# Search for a tag. Evennia must be imported first. -store_result = evennia.search_tag("label") - -# Return a list of all tags. -obj.tags.all() -``` - -**Aliases** -``` -# Add an alias. -obj.aliases.add("label") - -ETC... -``` - -*** - -In regards to comment structure, I often find that smushing together lines with comments to be too -obscure. White space should be used to clearly delineate what information the comment is for. I -understand that the current format is that a comment references whatever is below it, but newbies -may not know that until they realize it. - -Example of poor formatting: -``` -#comment -command/code -#comment -command/code -``` - -Example of good formatting: -``` -# Comment. -command/code - -# Comment. -command/code -``` - -### Sage (3/28/20) - -If I want to find information on the correct syntax for is_typeclass(), here's what I do: -* Pop over to the wiki. Okay, this is a developer functionality. Let's try that. -* Ctrl+F on Developer page. No results. -* Ctrl+F on API page. No results. Ctrl+F on Flat API page. No results -* Ctrl+F on utils page. No results. -* Ctrl+F on utils.utils page. No results. -* Ctrl+F in my IDE. Results. -* Fortunately, there's only one result for def is_typeclass. If this was at_look, there would be -several results, and I'd have to go through each of those individually, and most of them would just -call return_appearance - -An important part of a refactor, in my opinion, is separating out the "Tutorials" from the -"Reference" documentation. diff --git a/docs/0.9.5/_sources/Dynamic-In-Game-Map.md.txt b/docs/0.9.5/_sources/Dynamic-In-Game-Map.md.txt deleted file mode 100644 index 5d21a9fc94..0000000000 --- a/docs/0.9.5/_sources/Dynamic-In-Game-Map.md.txt +++ /dev/null @@ -1,495 +0,0 @@ -# Dynamic In Game Map - - -## Introduction - -An often desired feature in a MUD is to show an in-game map to help navigation. The [Static in-game -map](./Static-In-Game-Map.md) tutorial solves this by creating a *static* map, meaning the map is pre- -drawn once and for all - the rooms are then created to match that map. When walking around, parts of -the static map is then cut out and displayed next to the room description. - -In this tutorial we'll instead do it the other way around; We will dynamically draw the map based on -the relationships we find between already existing rooms. - -## The Grid of Rooms - -There are at least two requirements needed for this tutorial to work. - -1. The structure of your mud has to follow a logical layout. Evennia supports the layout of your -world to be 'logically' impossible with rooms looping to themselves or exits leading to the other -side of the map. Exits can also be named anything, from "jumping out the window" to "into the fifth -dimension". This tutorial assumes you can only move in the cardinal directions (N, E, S and W). -2. Rooms must be connected and linked together for the map to be generated correctly. Vanilla -Evennia comes with a admin command [tunnel](evennia.commands.default.building.CmdTunnel) that allows a -user to create rooms in the cardinal directions, but additional work is needed to assure that rooms -are connected. For example, if you `tunnel east` and then immediately do `tunnel west` you'll find -that you have created two completely stand-alone rooms. So care is needed if you want to create a -"logical" layout. In this tutorial we assume you have such a grid of rooms that we can generate the -map from. - -## Concept - -Before getting into the code, it is beneficial to understand and conceptualize how this is going to -work. The idea is analogous to a worm that starts at your current position. It chooses a direction -and 'walks' outward from it, mapping its route as it goes. Once it has traveled a pre-set distance -it stops and starts over in another direction. An important note is that we want a system which is -easily callable and not too complicated. Therefore we will wrap this entire code into a custom -Python class (not a typeclass as this doesn't use any core objects from evennia itself). - -We are going to create something that displays like this when you type 'look': - -``` - Hallway - - [.] [.] - [@][.][.][.][.] - [.] [.] [.] - - The distant echoes of the forgotten - wail throughout the empty halls. - - Exits: North, East, South -``` - -Your current location is defined by `[@]` while the `[.]`s are other rooms that the "worm" has seen -since departing from your location. - -## Setting up the Map Display - -First we must define the components for displaying the map. For the "worm" to know what symbol to -draw on the map we will have it check an Attribute on the room it visits called `sector_type`. For -this tutorial we understand two symbols - a normal room and the room with us in it. We also define a -fallback symbol for rooms without said Attribute - that way the map will still work even if we -didn't prepare the room correctly. Assuming your game folder is named `mygame`, we create this code -in `mygame/world/map.py.` - -```python -# in mygame/world/map.py - -# the symbol is identified with a key "sector_type" on the -# Room. Keys None and "you" must always exist. -SYMBOLS = { None : ' . ', # for rooms without sector_type Attribute - 'you' : '[@]', - 'SECT_INSIDE': '[.]' } -``` - -Since trying to access an unset Attribute returns `None`, this means rooms without the `sector_type` -Atttribute will show as ` . `. Next we start building the custom class `Map`. It will hold all -methods we need. - -```python -# in mygame/world/map.py - -class Map(object): - - def __init__(self, caller, max_width=9, max_length=9): - self.caller = caller - self.max_width = max_width - self.max_length = max_length - self.worm_has_mapped = {} - self.curX = None - self.curY = None -``` - -- `self.caller` is normally your Character object, the one using the map. -- `self.max_width/length` determine the max width and length of the map that will be generated. Note -that it's important that these variables are set to *odd* numbers to make sure the display area has -a center point. -- ` self.worm_has_mapped` is building off the worm analogy above. This dictionary will store all -rooms the "worm" has mapped as well as its relative position within the grid. This is the most -important variable as it acts as a 'checker' and 'address book' that is able to tell us where the -worm has been and what it has mapped so far. -- `self.curX/Y` are coordinates representing the worm's current location on the grid. - - -Before any sort of mapping can actually be done we need to create an empty display area and do some -sanity checks on it by using the following methods. - -```python -# in mygame/world/map.py - -class Map(object): - # [... continued] - - def create_grid(self): - # This method simply creates an empty grid/display area - # with the specified variables from __init__(self): - board = [] - for row in range(self.max_width): - board.append([]) - for column in range(self.max_length): - board[row].append(' ') - return board - - def check_grid(self): - # this method simply checks the grid to make sure - # that both max_l and max_w are odd numbers. - return True if self.max_length % 2 != 0 or self.max_width % 2 != 0\ - else False -``` - -Before we can set our worm on its way, we need to know some of the computer science behind all this -called 'Graph Traversing'. In Pseudo code what we are trying to accomplish is this: - -```python -# pseudo code - -def draw_room_on_map(room, max_distance): - self.draw(room) - - if max_distance == 0: - return - - for exit in room.exits: - if self.has_drawn(exit.destination): - # skip drawing if we already visited the destination - continue - else: - # first time here! - self.draw_room_on_map(exit.destination, max_distance - 1) -``` - -The beauty of Python is that our actual code of doing this doesn't differ much if at all from this -Pseudo code example. - -- `max_distance` is a variable indicating to our Worm how many rooms AWAY from your current location -will it map. Obviously the larger the number the more time it will take if your current location has -many many rooms around you. - -The first hurdle here is what value to use for 'max_distance'. There is no reason for the worm to -travel further than what is actually displayed to you. For example, if your current location is -placed in the center of a display area of size `max_length = max_width = 9`, then the worm need only -go `4` spaces in either direction: - -``` -[.][.][.][.][@][.][.][.][.] - 4 3 2 1 0 1 2 3 4 -``` - -The max_distance can be set dynamically based on the size of the display area. As your width/length -changes it becomes a simple algebraic linear relationship which is simply `max_distance = -(min(max_width, max_length) -1) / 2`. - -## Building the Mapper - -Now we can start to fill our Map object with some methods. We are still missing a few methods that -are very important: - -* `self.draw(self, room)` - responsible for actually drawing room to grid. -* `self.has_drawn(self, room)` - checks to see if the room has been mapped and worm has already been -here. -* `self.median(self, number)` - a simple utility method that finds the median (middle point) from 0, -n -* `self.update_pos(self, room, exit_name)` - updates the worm's physical position by reassigning -self.curX/Y. .accordingly -* `self.start_loc_on_grid(self)` - the very first initial draw on the grid representing your -location in the middle of the grid -* 'self.show_map` - after everything is done convert the map into a readable string` -* `self.draw_room_on_map(self, room, max_distance)` - the main method that ties it all together.` - - -Now that we know which methods we need, let's refine our initial `__init__(self)` to pass some -conditional statements and set it up to start building the display. - - -```python -#mygame/world/map.py - -class Map(object): - - def __init__(self, caller, max_width=9, max_length=9): - self.caller = caller - self.max_width = max_width - self.max_length = max_length - self.worm_has_mapped = {} - self.curX = None - self.curY = None - - if self.check_grid(): - # we have to store the grid into a variable - self.grid = self.create_grid() - # we use the algebraic relationship - self.draw_room_on_map(caller.location, - ((min(max_width, max_length) -1 ) / 2) - -``` - -Here we check to see if the parameters for the grid are okay, then we create an empty canvas and map -our initial location as the first room! - -As mentioned above, the code for the `self.draw_room_on_map()` is not much different than the Pseudo -code. The method is shown below: - -```python -# in mygame/world/map.py, in the Map class - -def draw_room_on_map(self, room, max_distance): - self.draw(room) - - if max_distance == 0: - return - - for exit in room.exits: - if exit.name not in ("north", "east", "west", "south"): - # we only map in the cardinal directions. Mapping up/down would be - # an interesting learning project for someone who wanted to try it. - continue - if self.has_drawn(exit.destination): - # we've been to the destination already, skip ahead. - continue - - self.update_pos(room, exit.name.lower()) - self.draw_room_on_map(exit.destination, max_distance - 1) -``` - -The first thing the "worm" does is to draw your current location in `self.draw`. Lets define that... - -```python -#in mygame/word/map.py, in the Map class - -def draw(self, room): - # draw initial ch location on map first! - if room == self.caller.location: - self.start_loc_on_grid() - self.worm_has_mapped[room] = [self.curX, self.curY] - else: - # map all other rooms - self.worm_has_mapped[room] = [self.curX, self.curY] - # this will use the sector_type Attribute or None if not set. - self.grid[self.curX][self.curY] = SYMBOLS[room.db.sector_type] -``` - -In `self.start_loc_on_grid()`: - -```python -def median(self, num): - lst = sorted(range(0, num)) - n = len(lst) - m = n -1 - return (lst[n//2] + lst[m//2]) / 2.0 - -def start_loc_on_grid(self): - x = self.median(self.max_width) - y = self.median(self.max_length) - # x and y are floats by default, can't index lists with float types - x, y = int(x), int(y) - - self.grid[x][y] = SYMBOLS['you'] - self.curX, self.curY = x, y # updating worms current location -``` - -After the system has drawn the current map it checks to see if the `max_distance` is `0` (since this -is the inital start phase it is not). Now we handle the iteration once we have each individual exit -in the room. The first thing it does is check if the room the Worm is in has been mapped already.. -lets define that... - - -```python -def has_drawn(self, room): - return True if room in self.worm_has_mapped.keys() else False -``` - -If `has_drawn` returns `False` that means the worm has found a room that hasn't been mapped yet. It -will then 'move' there. The self.curX/Y sort of lags behind, so we have to make sure to track the -position of the worm; we do this in `self.update_pos()` below. - -```python -def update_pos(self, room, exit_name): - # this ensures the coordinates stays up to date - # to where the worm is currently at. - self.curX, self.curY = \ - self.worm_has_mapped[room][0], self.worm_has_mapped[room][1] - - # now we have to actually move the pointer - # variables depending on which 'exit' it found - if exit_name == 'east': - self.curY += 1 - elif exit_name == 'west': - self.curY -= 1 - elif exit_name == 'north': - self.curX -= 1 - elif exit_name == 'south': - self.curX += 1 -``` - -Once the system updates the position of the worm it feeds the new room back into the original -`draw_room_on_map()` and starts the process all over again.. - -That is essentially the entire thing. The final method is to bring it all together and make a nice -presentational string out of it using the `self.show_map()` method. - -```python -def show_map(self): - map_string = "" - for row in self.grid: - map_string += " ".join(row) - map_string += "\n" - - return map_string -``` - -## Using the Map - -In order for the map to get triggered we store it on the Room typeclass. If we put it in -`return_appearance` we will get the map back every time we look at the room. - -> `return_appearance` is a default Evennia hook available on all objects; it is called e.g. by the -`look` command to get the description of something (the room in this case). - -```python -# in mygame/typeclasses/rooms.py - -from evennia import DefaultRoom -from world.map import Map - -class Room(DefaultRoom): - - def return_appearance(self, looker): - # [...] - string = "%s\n" % Map(looker).show_map() - # Add all the normal stuff like room description, - # contents, exits etc. - string += "\n" + super().return_appearance(looker) - return string -``` - -Obviously this method of generating maps doesn't take into account of any doors or exits that are -hidden.. etc.. but hopefully it serves as a good base to start with. Like previously mentioned, it -is very important to have a solid foundation on rooms before implementing this. You can try this on -vanilla evennia by using @tunnel and essentially you can just create a long straight/edgy non- -looping rooms that will show on your in-game map. - -The above example will display the map above the room description. You could also use an -[EvTable](github:evennia.utils.evtable) to place description and map next to each other. Some other -things you can do is to have a [Command](./Commands.md) that displays with a larger radius, maybe with a -legend and other features. - -Below is the whole `map.py` for your reference. You need to update your `Room` typeclass (see above) -to actually call it. Remember that to see different symbols for a location you also need to set the -`sector_type` Attribute on the room to one of the keys in the `SYMBOLS` dictionary. So in this -example, to make a room be mapped as `[.]` you would set the room's `sector_type` to -`"SECT_INSIDE"`. Try it out with `@set here/sector_type = "SECT_INSIDE"`. If you wanted all new -rooms to have a given sector symbol, you could change the default in the `SYMBOLS´ dictionary below, -or you could add the Attribute in the Room's `at_object_creation` method. - -```python -#mygame/world/map.py - -# These are keys set with the Attribute sector_type on the room. -# The keys None and "you" must always exist. -SYMBOLS = { None : ' . ', # for rooms without a sector_type attr - 'you' : '[@]', - 'SECT_INSIDE': '[.]' } - -class Map(object): - - def __init__(self, caller, max_width=9, max_length=9): - self.caller = caller - self.max_width = max_width - self.max_length = max_length - self.worm_has_mapped = {} - self.curX = None - self.curY = None - - if self.check_grid(): - # we actually have to store the grid into a variable - self.grid = self.create_grid() - self.draw_room_on_map(caller.location, - ((min(max_width, max_length) -1 ) / 2)) - - def update_pos(self, room, exit_name): - # this ensures the pointer variables always - # stays up to date to where the worm is currently at. - self.curX, self.curY = \ - self.worm_has_mapped[room][0], self.worm_has_mapped[room][1] - - # now we have to actually move the pointer - # variables depending on which 'exit' it found - if exit_name == 'east': - self.curY += 1 - elif exit_name == 'west': - self.curY -= 1 - elif exit_name == 'north': - self.curX -= 1 - elif exit_name == 'south': - self.curX += 1 - - def draw_room_on_map(self, room, max_distance): - self.draw(room) - - if max_distance == 0: - return - - for exit in room.exits: - if exit.name not in ("north", "east", "west", "south"): - # we only map in the cardinal directions. Mapping up/down would be - # an interesting learning project for someone who wanted to try it. - continue - if self.has_drawn(exit.destination): - # we've been to the destination already, skip ahead. - continue - - self.update_pos(room, exit.name.lower()) - self.draw_room_on_map(exit.destination, max_distance - 1) - - def draw(self, room): - # draw initial caller location on map first! - if room == self.caller.location: - self.start_loc_on_grid() - self.worm_has_mapped[room] = [self.curX, self.curY] - else: - # map all other rooms - self.worm_has_mapped[room] = [self.curX, self.curY] - # this will use the sector_type Attribute or None if not set. - self.grid[self.curX][self.curY] = SYMBOLS[room.db.sector_type] - - def median(self, num): - lst = sorted(range(0, num)) - n = len(lst) - m = n -1 - return (lst[n//2] + lst[m//2]) / 2.0 - - def start_loc_on_grid(self): - x = self.median(self.max_width) - y = self.median(self.max_length) - # x and y are floats by default, can't index lists with float types - x, y = int(x), int(y) - - self.grid[x][y] = SYMBOLS['you'] - self.curX, self.curY = x, y # updating worms current location - - - def has_drawn(self, room): - return True if room in self.worm_has_mapped.keys() else False - - - def create_grid(self): - # This method simply creates an empty grid - # with the specified variables from __init__(self): - board = [] - for row in range(self.max_width): - board.append([]) - for column in range(self.max_length): - board[row].append(' ') - return board - - def check_grid(self): - # this method simply checks the grid to make sure - # both max_l and max_w are odd numbers - return True if self.max_length % 2 != 0 or \ - self.max_width % 2 != 0 else False - - def show_map(self): - map_string = "" - for row in self.grid: - map_string += " ".join(row) - map_string += "\n" - - return map_string -``` - -## Final Comments - -The Dynamic map could be expanded with further capabilities. For example, it could mark exits or -allow NE, SE etc directions as well. It could have colors for different terrain types. One could -also look into up/down directions and figure out how to display that in a good way. diff --git a/docs/0.9.5/_sources/EvEditor.md.txt b/docs/0.9.5/_sources/EvEditor.md.txt deleted file mode 100644 index 4b9dde97d7..0000000000 --- a/docs/0.9.5/_sources/EvEditor.md.txt +++ /dev/null @@ -1,181 +0,0 @@ -# EvEditor - - -Evennia offers a powerful in-game line editor in `evennia.utils.eveditor.EvEditor`. This editor, -mimicking the well-known VI line editor. It offers line-by-line editing, undo/redo, line deletes, -search/replace, fill, dedent and more. - -### Launching the editor - -The editor is created as follows: - -```python -from evennia.utils.eveditor import EvEditor - -EvEditor(caller, - loadfunc=None, savefunc=None, quitfunc=None, - key="") -``` - - - `caller` (Object or Account): The user of the editor. - - `loadfunc` (callable, optional): This is a function called when the editor is first started. It -is called with `caller` as its only argument. The return value from this function is used as the -starting text in the editor buffer. - - `savefunc` (callable, optional): This is called when the user saves their buffer in the editor is -called with two arguments, `caller` and `buffer`, where `buffer` is the current buffer. - - `quitfunc` (callable, optional): This is called when the user quits the editor. If given, all -cleanup and exit messages to the user must be handled by this function. - - `key` (str, optional): This text will be displayed as an identifier and reminder while editing. -It has no other mechanical function. - - `persistent` (default `False`): if set to `True`, the editor will survive a reboot. - -### Example of usage - -This is an example command for setting a specific Attribute using the editor. - -```python -from evennia import Command -from evennia.utils import eveditor - -class CmdSetTestAttr(Command): - """ - Set the "test" Attribute using - the line editor. - - Usage: - settestattr - - """ - key = "settestattr" - def func(self): - "Set up the callbacks and launch the editor" - def load(caller): - "get the current value" - return caller.attributes.get("test") - def save(caller, buffer): - "save the buffer" - caller.attributes.set("test", buffer) - def quit(caller): - "Since we define it, we must handle messages" - caller.msg("Editor exited") - key = "%s/test" % self.caller - # launch the editor - eveditor.EvEditor(self.caller, - loadfunc=load, savefunc=save, quitfunc=quit, - key=key) -``` - -### Persistent editor - -If you set the `persistent` keyword to `True` when creating the editor, it will remain open even -when reloading the game. In order to be persistent, an editor needs to have its callback functions -(`loadfunc`, `savefunc` and `quitfunc`) as top-level functions defined in the module. Since these -functions will be stored, Python will need to find them. - -```python -from evennia import Command -from evennia.utils import eveditor - -def load(caller): - "get the current value" - return caller.attributes.get("test") - -def save(caller, buffer): - "save the buffer" - caller.attributes.set("test", buffer) - -def quit(caller): - "Since we define it, we must handle messages" - caller.msg("Editor exited") - -class CmdSetTestAttr(Command): - """ - Set the "test" Attribute using - the line editor. - - Usage: - settestattr - - """ - key = "settestattr" - def func(self): - "Set up the callbacks and launch the editor" - key = "%s/test" % self.caller - # launch the editor - eveditor.EvEditor(self.caller, - loadfunc=load, savefunc=save, quitfunc=quit, - key=key, persistent=True) -``` - -### Line editor usage - -The editor mimics the `VIM` editor as best as possible. The below is an excerpt of the return from -the in-editor help command (`:h`). - -``` - - any non-command is appended to the end of the buffer. - : - view buffer or only line - :: - view buffer without line numbers or other parsing - ::: - print a ':' as the only character on the line... - :h - this help. - - :w - save the buffer (don't quit) - :wq - save buffer and quit - :q - quit (will be asked to save if buffer was changed) - :q! - quit without saving, no questions asked - - :u - (undo) step backwards in undo history - :uu - (redo) step forward in undo history - :UU - reset all changes back to initial state - - :dd - delete line - :dw - delete word or regex in entire buffer or on line - :DD - clear buffer - - :y - yank (copy) line to the copy buffer - :x - cut line and store it in the copy buffer - :p - put (paste) previously copied line directly after - :i - insert new text at line . Old line will move down - :r - replace line with text - :I - insert text at the beginning of line - :A - append text after the end of line - - :s - search/replace word or regex in buffer or on line - - :f - flood-fill entire buffer or line - :fi - indent entire buffer or line - :fd - de-indent entire buffer or line - - :echo - turn echoing of the input on/off (helpful for some clients) - - Legend: - - line numbers, or range lstart:lend, e.g. '3:7'. - - one word or several enclosed in quotes. - - longer string, usually not needed to be enclosed in quotes. -``` - -### The EvEditor to edit code - -The `EvEditor` is also used to edit some Python code in Evennia. The `@py` command supports an -`/edit` switch that will open the EvEditor in code mode. This mode isn't significantly different -from the standard one, except it handles automatic indentation of blocks and a few options to -control this behavior. - -- `:<` to remove a level of indentation for the future lines. -- `:+` to add a level of indentation for the future lines. -- `:=` to disable automatic indentation altogether. - -Automatic indentation is there to make code editing more simple. Python needs correct indentation, -not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The -EvEditor will try to guess the next level of indentation. If you type a block "if", for instance, -the EvEditor will propose you an additional level of indentation at the next line. This feature -cannot be perfect, however, and sometimes, you will have to use the above options to handle -indentation. - -`:=` can be used to turn automatic indentation off completely. This can be very useful when trying -to paste several lines of code that are already correctly indented, for instance. - -To see the EvEditor in code mode, you can use the `@py/edit` command. Type in your code (on one or -several lines). You can then use the `:w` option (save without quitting) and the code you have -typed will be executed. The `:!` will do the same thing. Executing code while not closing the -editor can be useful if you want to test the code you have typed but add new lines after your test. \ No newline at end of file diff --git a/docs/0.9.5/_sources/EvMenu.md.txt b/docs/0.9.5/_sources/EvMenu.md.txt deleted file mode 100644 index 90fa5c3e9e..0000000000 --- a/docs/0.9.5/_sources/EvMenu.md.txt +++ /dev/null @@ -1,1312 +0,0 @@ -# EvMenu - - -## Introduction - -The `EvMenu` utility class is located in -[evennia/utils/evmenu.py](https://github.com/evennia/evennia/blob/master/evennia/utils/evmenu.py). -It allows for easily adding interactive menus to the game; for example to implement Character -creation, building commands or similar. Below is an example of offering NPC conversation choices: - -``` -The guard looks at you suspiciously. -"No one is supposed to be in here ..." -he says, a hand on his weapon. -_______________________________________________ - 1. Try to bribe him [Cha + 10 gold] - 2. Convince him you work here [Int] - 3. Appeal to his vanity [Cha] - 4. Try to knock him out [Luck + Dex] - 5. Try to run away [Dex] - -``` - -This is an example of a menu *node*. Think of a node as a point where the menu stops printing text -and waits for user to give some input. By jumping to different nodes depending on the input, a menu -is constructed. - -## Ways to create the menu - -### node functions - -The native way to define an EvMenu is to define Python functions, one per node. It will load all -those -functions/nodes either from a module or by being passed a dictionary mapping the node's names to -said functions, like `{"nodename": , ...}`. Since you are dealing with raw code, this is -by -far the most powerful way - for example you could have dynamic nodes that change content depending -on game context, time and what you picked before. - -### menu templating - -For a simpler menu you often don't need the full flexibility you get from defining each node as a -Python function. For that, there is the _EvMenu templating_ language. This allows you to define the -menu -in a more human-readable string with a simple format. This is then parsed to produce the -`{"nodename": , ...}` mapping for you, for the EvMenu to use normally. The templating -language is described in the [Menu templating section](./EvMenu.md#evmenu-templating-language). - - -## Launching the menu - -Initializing the menu is done using a call to the `evennia.utils.evmenu.EvMenu` class. This is the -most common way to do so - from inside a [Command](./Commands.md): - -```python -# in, for example gamedir/commands/command.py - -from evennia.utils.evmenu import EvMenu - -class CmdTestMenu(Command): - - key = "testcommand" - - def func(self): - - EvMenu(caller, "world.mymenu") - -``` - -When running this command, the menu will start using the menu nodes loaded from -`mygame/world/mymenu.py` and use this to build the menu-tree - each function name becomes -the name of a node in the tree. See next section on how to define menu nodes. - -Alternatively, you could pass the menu-tree to EvMenu directly: - -```python - - menutree = {"start": nodestartfunc, - "node1": nodefunc1, - "node2": nodefunc2,, ...} - EvMenu(caller, menutree) - -``` -This menutree can also be generated from an *EvMenu template* - -```python - from evennia.utils.evmenu import parse_menu_template - - menutree = parse_menu_template(caller, template_string, goto_callables) - EvMenu(caller, menutree) -``` - -The `template_string` and `goto_callables` are described in [Template language -section](./EvMenu.md#evmenu-templating-language). - - -## The EvMenu class - -The `EvMenu` has the following optional callsign: - -```python -EvMenu(caller, menu_data, - startnode="start", - cmdset_mergetype="Replace", cmdset_priority=1, - auto_quit=True, auto_look=True, auto_help=True, - cmd_on_exit="look", - persistent=False, - startnode_input="", - session=None, - debug=False, - **kwargs) - -``` - - - `caller` (Object or Account): is a reference to the object using the menu. This object will get a - new [CmdSet](./Command-Sets.md) assigned to it, for handling the menu. - - `menu_data` (str, module or dict): is a module or python path to a module where the global-level - functions will each be considered to be a menu node. Their names in the module will be the names - by which they are referred to in the module. Importantly, function names starting with an -underscore - `_` will be ignored by the loader. Alternatively, this can be a direct mapping -`{"nodename":function, ...}`. - - `startnode` (str): is the name of the menu-node to start the menu at. Changing this means that - you can jump into a menu tree at different positions depending on circumstance and thus possibly - re-use menu entries. - - `cmdset_mergetype` (str): This is usually one of "Replace" or "Union" (see [CmdSets](Command- -Sets). - The first means that the menu is exclusive - the user has no access to any other commands while - in the menu. The Union mergetype means the menu co-exists with previous commands (and may -overload - them, so be careful as to what to name your menu entries in this case). - - `cmdset_priority` (int): The priority with which to merge in the menu cmdset. This allows for - advanced usage. - - `auto_quit`, `auto_look`, `auto_help` (bool): If either of these are `True`, the menu - automatically makes a `quit`, `look` or `help` command available to the user. The main reason why - you'd want to turn this off is if you want to use the aliases "q", "l" or "h" for something in -your - menu. Nevertheless, at least `quit` is highly recommend - if `False`, the menu *must* itself -supply - an "exit node" (a node without any options), or the user will be stuck in the menu until the -server - reloads (or eternally if the menu is `persistent`)! - - `cmd_on_exit` (str): This command string will be executed right *after* the menu has closed down. - From experience, it's useful to trigger a "look" command to make sure the user is aware of the - change of state; but any command can be used. If set to `None`, no command will be triggered -after - exiting the menu. - - `persistent` (bool) - if `True`, the menu will survive a reload (so the user will not be kicked - out by the reload - make sure they can exit on their own!) - - `startnode_input` (str or (str, dict) tuple): Pass an input text or a input text + kwargs to the - start node as if it was entered on a fictional previous node. This can be very useful in order to - start a menu differently depending on the Command's arguments in which it was initialized. - - `session` (Session): Useful when calling the menu from an [Account](./Accounts.md) in - `MULTISESSION_MODDE` higher than 2, to make sure only the right Session sees the menu output. - - `debug` (bool): If set, the `menudebug` command will be made available in the menu. Use it to - list the current state of the menu and use `menudebug ` to inspect a specific state - variable from the list. - - All other keyword arguments will be available as initial data for the nodes. They will be - available in all nodes as properties on `caller.ndb._menutree` (see below). These will also -survive a `@reload` if the menu is `persistent`. - -You don't need to store the EvMenu instance anywhere - the very act of initializing it will store it -as `caller.ndb._menutree` on the `caller`. This object will be deleted automatically when the menu -is exited and you can also use it to store your own temporary variables for access throughout the -menu. Temporary variables you store on a persistent `_menutree` as it runs will -*not* survive a `@reload`, only those you set as part of the original `EvMenu` call. - - -## The Menu nodes - -The EvMenu nodes consist of functions on one of these forms. - -```python -def menunodename1(caller): - # code - return text, options - -def menunodename2(caller, raw_string): - # code - return text, options - -def menunodename3(caller, raw_string, **kwargs): - # code - return text, options - -``` - -> While all of the above forms are okay, it's recommended to stick to the third and last form since -it -> gives the most flexibility. The previous forms are mainly there for backwards compatibility with -> existing menus from a time when EvMenu was less able. - - -### Input arguments to the node - - - `caller` (Object or Account): The object using the menu - usually a Character but could also be a - Session or Account depending on where the menu is used. - - `raw_string` (str): If this is given, it will be set to the exact text the user entered on the - *previous* node (that is, the command entered to get to this node). On the starting-node of the - menu, this will be an empty string, unless `startnode_input` was set. - - `kwargs` (dict): These extra keyword arguments are extra optional arguments passed to the node - when the user makes a choice on the *previous* node. This may include things like status flags - and details about which exact option was chosen (which can be impossible to determine from - `raw_string` alone). Just what is passed in `kwargs` is up to you when you create the previous - node. - -### Return values from the node - -Each function must return two variables, `text` and `options`. - - -#### text - -The `text` variable is a string or tuple. This text is what will be displayed when the user reaches -this node. If this is a tuple, then the first element of the tuple will be considered the displayed -text and the second the help-text to display when the user enters the `help` command on this node. - -```python - text = ("This is the text to display", "This is the help text for this node") -``` - -Returning a `None` text is allowed and simply leads to a node with no text and only options. If the -help text is not given, the menu will give a generic error message when using `help`. - - -#### options - -The `options` list describe all the choices available to the user when viewing this node. If -`options` is -returned as `None`, it means that this node is an *Exit node* - any text is displayed and then the -menu immediately exits, running the `exit_cmd` if given. - -Otherwise, `options` should be a list (or tuple) of dictionaries, one for each option. If only one -option is -available, a single dictionary can also be returned. This is how it could look: - - -```python -def node_test(caller, raw_string, **kwargs): - - text = "A goblin attacks you!" - - options = ( - {"key": ("Attack", "a", "att"), - "desc": "Strike the enemy with all your might", - "goto": "node_attack"}, - {"key": ("Defend", "d", "def"), - "desc": "Hold back and defend yourself", - "goto": (_defend, {"str": 10, "enemyname": "Goblin"})}) - - return text, options - -``` - -This will produce a menu node looking like this: - - -``` -A goblin attacks you! -________________________________ - -Attack: Strike the enemy with all your might -Defend: Hold back and defend yourself - -``` - -##### option-key 'key' - -The option's `key` is what the user should enter in order to choose that option. If given as a -tuple, the -first string of that tuple will be what is shown on-screen while the rest are aliases for picking -that option. In the above example, the user could enter "Attack" (or "attack", it's not -case-sensitive), "a" or "att" in order to attack the goblin. Aliasing is useful for adding custom -coloring to the choice. The first element of the aliasing tuple should then be the colored version, -followed by a version without color - since otherwise the user would have to enter the color codes -to select that choice. - -Note that the `key` is *optional*. If no key is given, it will instead automatically be replaced -with a running number starting from `1`. If removing the `key` part of each option, the resulting -menu node would look like this instead: - - -``` -A goblin attacks you! -________________________________ - -1: Strike the enemy with all your might -2: Hold back and defend yourself - -``` - -Whether you want to use a key or rely on numbers is mostly -a matter of style and the type of menu. - -EvMenu accepts one important special `key` given only as `"_default"`. This key is used when a user -enters something that does not match any other fixed keys. It is particularly useful for getting -user input: - -```python -def node_readuser(caller, raw_string, **kwargs): - text = "Please enter your name" - - options = {"key": "_default", - "goto": "node_parse_input"} - - return text, options - -``` - -A `"_default"` option does not show up in the menu, so the above will just be a node saying -`"Please enter your name"`. The name they entered will appear as `raw_string` in the next node. - - -#### option-key 'desc' - -This simply contains the description as to what happens when selecting the menu option. For -`"_default"` options or if the `key` is already long or descriptive, it is not strictly needed. But -usually it's better to keep the `key` short and put more detail in `desc`. - - -#### option-key 'goto' - -This is the operational part of the option and fires only when the user chooses said option. Here -are three ways to write it - -```python - -def _action_two(caller, raw_string, **kwargs): - # do things ... - return "calculated_node_to_go_to" - -def _action_three(caller, raw_string, **kwargs): - # do things ... - return "node_four", {"mode": 4} - -def node_select(caller, raw_string, **kwargs): - - text = ("select one", - "help - they all do different things ...") - - options = ({"desc": "Option one", - "goto": "node_one"}, - {"desc": "Option two", - "goto": _action_two}, - {"desc": "Option three", - "goto": (_action_three, {"key": 1, "key2": 2})} - ) - - return text, options - -``` - -As seen above, `goto` could just be pointing to a single `nodename` string - the name of the node to -go to. When given like this, EvMenu will look for a node named like this and call its associated -function as - -```python - nodename(caller, raw_string, **kwargs) -``` - -Here, `raw_string` is always the input the user entered to make that choice and `kwargs` are the -same as those `kwargs` that already entered the *current* node (they are passed on). - -Alternatively the `goto` could point to a "goto-callable". Such callables are usually defined in the -same -module as the menu nodes and given names starting with `_` (to avoid being parsed as nodes -themselves). These callables will be called the same as a node function - `callable(caller, -raw_string, **kwargs)`, where `raw_string` is what the user entered on this node and `**kwargs` is -forwarded from the node's own input. - -The `goto` option key could also point to a tuple `(callable, kwargs)` - this allows for customizing -the kwargs passed into the goto-callable, for example you could use the same callable but change the -kwargs passed into it depending on which option was actually chosen. - -The "goto callable" must either return a string `"nodename"` or a tuple `("nodename", mykwargs)`. -This will lead to the next node being called as either `nodename(caller, raw_string, **kwargs)` or -`nodename(caller, raw_string, **mykwargs)` - so this allows changing (or replacing) the options -going -into the next node depending on what option was chosen. - -There is one important case - if the goto-callable returns `None` for a `nodename`, *the current -node will run again*, possibly with different kwargs. This makes it very easy to re-use a node over -and over, for example allowing different options to update some text form being passed and -manipulated for every iteration. - - -> The EvMenu also supports the `exec` option key. This allows for running a callable *before* the -> goto-callable. This functionality comes from a time before goto could be a callable and is -> *deprecated* as of Evennia 0.8. Use `goto` for all functionality where you'd before use `exec`. - - -## Temporary storage - -When the menu starts, the EvMenu instance is stored on the caller as `caller.ndb._menutree`. Through -this object you can in principle reach the menu's internal state if you know what you are doing. -This is also a good place to store temporary, more global variables that may be cumbersome to keep -passing from node to node via the `**kwargs`. The `_menutree` will be deleted automatically when the -menu closes, meaning you don't need to worry about cleaning anything up. - -If you want *permanent* state storage, it's instead better to use an Attribute on `caller`. Remember -that this will remain after the menu closes though, so you need to handle any needed cleanup -yourself. - - -## Customizing Menu formatting - -The `EvMenu` display of nodes, options etc are controlled by a series of formatting methods on the -`EvMenu` class. To customize these, simply create a new child class of `EvMenu` and override as -needed. Here is an example: - -```python -from evennia.utils.evmenu import EvMenu - -class MyEvMenu(EvMenu): - - def nodetext_formatter(self, nodetext): - """ - Format the node text itself. - - Args: - nodetext (str): The full node text (the text describing the node). - - Returns: - nodetext (str): The formatted node text. - - """ - - def helptext_formatter(self, helptext): - """ - Format the node's help text - - Args: - helptext (str): The unformatted help text for the node. - - Returns: - helptext (str): The formatted help text. - - """ - - def options_formatter(self, optionlist): - """ - Formats the option block. - - Args: - optionlist (list): List of (key, description) tuples for every - option related to this node. - caller (Object, Account or None, optional): The caller of the node. - - Returns: - options (str): The formatted option display. - - """ - - def node_formatter(self, nodetext, optionstext): - """ - Formats the entirety of the node. - - Args: - nodetext (str): The node text as returned by `self.nodetext_formatter`. - optionstext (str): The options display as returned by `self.options_formatter`. - caller (Object, Account or None, optional): The caller of the node. - - Returns: - node (str): The formatted node to display. - - """ - -``` -See `evennia/utils/evmenu.py` for the details of their default implementations. - - - -## Evmenu templating language - -The EvMenu is very powerful and flexible. But often your menu is simple enough to -not require the full power of EvMenu. For this you can use the Evmenu templating language. - -This is how the templating is used: - -```python -from evennia.utils.evmenu import parse_menu_template, EvMenu - -template_string = "(will be described below)" -# this could be empty if you don't need to access any callables -# in your template -goto_callables = {"mycallable1": function, ...} - -# generate the menutree -menutree = parse_menu_template(caller, template_string, goto_callables) -# a normal EvMenu call -EvMenu(caller, menutree, ...) - -``` - -... So the `parse_menu_template` is just another way to generate the `menutree` dict needed by -EvMenu - after this EvMenu works normally. - -The good thing with this two-step procedude is that you can mix- and match - if you wanted -you could insert a normal, fully flexible function-based node-function in the `menutree` before -passing -the whole thing into `EvMenu` and get the best of both worlds. It also makes it -easy to substitute base EvMenu with a child class that changes the menu display. - -... But if you really don't need any such customization, you can also apply the template in one step -using -the `template2menu` helper: - - -```python -from evennia.utils.evmenu import template2menu - -template_string = "(will be described below)" -goto_callables = {"mycallable1": function, ...} - -template2menu(caller, template_string, goto_callables, startnode="start", ...) - -``` -In addition to the template-related arguments, `template2menu` takes all the same `**kwargs` -as `EvMenu` and will parse the template and start the menu for you in one go. - -### The templating string - -The template is a normal string with a very simple format. Each node begins -with a marker `## Node `, follwowed by a `## Options` separator (the `Node` and -`Options` are -case-insensitive). - -```python -template_string = """ - -## NODE start - - - -## OPTIONS - -# this is a comment. Only line-comments are allowed. - -key;alias;alias: description -> goto_str_or_callable -key;alias;alias: goto_str_or_callable ->pattern: goto_str_or_callable - -""" -``` - -- The text after `## NODE` defines the name of the node. This must be unique within the - menu because this is what you use for `goto` statements. The name could have spaces. -- The area between `## NODE` and `## OPTIONS` contains the text of the node. It can have - normal formatting and will retain intentation. -- The `## OPTIONS` section, until the next `## NODE` or the end of the string, - holds the options, one per line. -- Option-indenting is ignored but can be useful for readability. -- The options-section can also have line-comments, marked by starting the line with `#`. -- A node without a following `## OPTIONS` section indicates an end node, and reaching - it will print the text and immediately exit the menu (same as for regular EvMenu). - -### Templating options format - -The normal, full syntax is: - - key;alias;alias: description -> goto_str_or_callable - -An example would be - - next;n: Go to node Two -> node2 - -In the menu, this will become an option - - next: Go to node Two - -where you can enter `next` or `n` to go to the menu node named `node2`. - -To skip the description, just add the goto without the `->`: - - next;n: node2 - -This will create a menu option without any description: - - next - -A special key is `>`. This acts as a _pattern matcher_. Between `>` and the `:` one -can fit an optional _pattern_. This -pattern will first be parsed with [glob-style -parsing](https://docs.python.org/2/library/fnmatch.html) and then -with [regex](https://docs.python.org/3/library/re.html#module-re), and only if -the player's input matches either will the option be chosen. An input-matching -option cannot have a description. - -``` - # this matches the empty string (just pressing return) - >: node2 - - # this matches input starting with 'test' (regex match) - > ^test.+?: testnode - - # this matches any number input (regex match) - > [0-9]+?: countnode - - # this matches everything not covered by previous options - # (glob-matching, space is stripped without quotes) - > *: node3 -``` - -You can have multiple pattern-matchers for a node but remember that options are -checked in the order they are listed. So make sure to put your pattern-matchers -in decending order of generality; if you have a 'catch-all' pattern, -it should be put last or those behind it will never be tried. - -``` - next;n: node2 - back;b: node1 - >: node2 -``` - -The above would give you the option to write next/back but you can also just press return to move on -to the next node. - -### Templating goto-callables - -Instead of giving the name of a node to go to, you can also give the name -of a _goto_callable_, which in turn returns the name of the node to go to. You -tell the template it's a callable by simply adding `()` at the end. - - next: Go to node 2 -> goto_node2() - -You can also add keyword arguments: - - back: myfunction(from=foo) - -> Note: ONLY keyword-arguments are supported! Trying to pass a positional -> argument will lead to an error. - -The contents of the kwargs-values will be evaluated by `literal_eval` so -you don't need to add quotes to strings _unless they have spaces in them_. Numbers -will be converted correctly, but more complex input structures (like lists or dicts) will -_not_ - if you want more complex input you should use a full function-based EvMenu -node instead. - -The goto-callable is defined just like any Evmenu goto-func. You must always -use the full form (including `**kwargs`): - -```python -def mygotocallable(caller, raw_string, **kwargs): - # ... - return "nodename_to_goto" - -``` - -Return `None` to re-run the current node. Any keyword arguments you specify in -your template will be passed to your goto-callable in `**kwargs`. Unlike in -regular EvMenu nodes you _can't_ return kwargs to pass it between nodes and other dynamic -tricks. - -All goto-callables you use in your menu-template must be added to the -`goto_callable` mapping that you pass to `parse_menu_template` or -`template2menu`. - -### Templating example to show all possible options: - - -```python - -template_string = """ - -## NODE start - -This is the text of the start node. -Both ## NODE, ## node or ## Node works. The node-name can have -spaces. - -The text area can have multiple lines, line breaks etc. - -## OPTIONS - - # here starts the option-defition - # comments are only allowed from beginning of line. - # Indenting is not necessary, but good for readability - - 1: Option number 1 -> node1 - 2: Option number 2 -> node2 - next: This steps next -> go_back() - # the -> can be ignored if there is no desc - back: go_back(from_node=start) - abort: abort - -# ----------------------------------- this is ignored - -## NODE node1 - -Text for Node1. Enter a message! - to go back. - -## options - - # Starting the option-line with > - # allows to perform different actions depending on - # what is inserted. - - # this catches everything starting with foo - > foo*: handle_foo_message() - - # regex are also allowed (this catches number inputs) - > [0-9]+?: handle_numbers() - - # this catches the empty return - >: start - - # this catches everything else - > *: handle_message(from_node=node1) - -# ----------------------------------------- - -## NODE node2 - -Text for Node2. Just go back. - -## options - - >: start - -# node abort - -This exits the menu since there is no `## options` section. - - -""" - -# we assume the callables are defined earlier -goto_callables = {"go_back": go_back_func, - "handle_foo_message": handle_message, - "handle_numbers": my_number_handler, - "handle_message": handle_message2} - -# boom - a menu -template2menu(caller, template_string, goto_callables) - -``` - - -## Examples: - -- **[Simple branching menu](./EvMenu.md#example-simple-branching-menu)** - choose from options -- **[Dynamic goto](./EvMenu.md#example-dynamic-goto)** - jumping to different nodes based on response -- **[Set caller properties](./EvMenu.md#example-set-caller-properties)** - a menu that changes things -- **[Getting arbitrary input](./EvMenu.md#example-get-arbitrary-input)** - entering text -- **[Storing data between nodes](./EvMenu.md#example-storing-data-between-nodes)** - keeping states and -information while in the menu -- **[Repeating the same node](./EvMenu.md#example-repeating-the-same-node)** - validating within the node -before moving to the next -- **[Yes/No prompt](./EvMenu.md#example-yesno-prompt)** - entering text with limited possible responses -(this is *not* using EvMenu but the conceptually similar yet technically unrelated `get_input` -helper function accessed as `evennia.utils.evmenu.get_input`). - - -### Example: Simple branching menu - -Below is an example of a simple branching menu node leading to different other nodes depending on -choice: - -```python -# in mygame/world/mychargen.py - -def define_character(caller): - text = \ - """ - What aspect of your character do you want - to change next? - """ - options = ({"desc": "Change the name", - "goto": "set_name"}, - {"desc": "Change the description", - "goto": "set_description"}) - return text, options - -EvMenu(caller, "world.mychargen", startnode="define_character") - -``` - -This will result in the following node display: - -``` -What aspect of your character do you want -to change next? -_________________________ -1: Change the name -2: Change the description -``` - -Note that since we didn't specify the "name" key, EvMenu will let the user enter numbers instead. In -the following examples we will not include the `EvMenu` call but just show nodes running inside the -menu. Also, since `EvMenu` also takes a dictionary to describe the menu, we could have called it -like this instead in the example: - -```python -EvMenu(caller, {"define_character": define_character}, startnode="define_character") - -``` - -### Example: Dynamic goto - -```python - -def _is_in_mage_guild(caller, raw_string, **kwargs): - if caller.tags.get('mage', category="guild_member"): - return "mage_guild_welcome" - else: - return "mage_guild_blocked" - -def enter_guild: - text = 'You say to the mage guard:' - options ({'desc': 'I need to get in there.', - 'goto': _is_in_mage_guild}, - {'desc': 'Never mind', - 'goto': 'end_conversation'}) - return text, options -``` - -This simple callable goto will analyse what happens depending on who the `caller` is. The -`enter_guild` node will give you a choice of what to say to the guard. If you try to enter, you will -end up in different nodes depending on (in this example) if you have the right [Tag](./Tags.md) set on -yourself or not. Note that since we don't include any 'key's in the option dictionary, you will just -get to pick between numbers. - -### Example: Set caller properties - -Here is an example of passing arguments into the `goto` callable and use that to influence -which node it should go to next: - -```python - -def _set_attribute(caller, raw_string, **kwargs): - "Get which attribute to modify and set it" - - attrname, value = kwargs.get("attr", (None, None)) - next_node = kwargs.get("next_node") - - caller.attributes.add(attrname, attrvalue) - - return next_node - - -def node_background(caller): - text = \ - """ - {} experienced a traumatic event - in their childhood. What was it? - """.format(caller.key} - - options = ({"key": "death", - "desc": "A violent death in the family", - "goto": (_set_attribute, {"attr": ("experienced_violence", True), - "next_node": "node_violent_background"})}, - {"key": "betrayal", - "desc": "The betrayal of a trusted grown-up", - "goto": (_set_attribute, {"attr": ("experienced_betrayal", True), - "next_node": "node_betrayal_background"})}) - return text, options -``` - -This will give the following output: - -``` -Kovash the magnificent experienced a traumatic event -in their childhood. What was it? -____________________________________________________ -death: A violent death in the family -betrayal: The betrayal of a trusted grown-up - -``` - -Note above how we use the `_set_attribute` helper function to set the attribute depending on the -User's choice. In thie case the helper function doesn't know anything about what node called it - we -even tell it which nodename it should return, so the choices leads to different paths in the menu. -We could also imagine the helper function analyzing what other choices - - -### Example: Get arbitrary input - -An example of the menu asking the user for input - any input. - -```python - -def _set_name(caller, raw_string, **kwargs): - - inp = raw_string.strip() - - prev_entry = kwargs.get("prev_entry") - - if not inp: - # a blank input either means OK or Abort - if prev_entry: - caller.key = prev_entry - caller.msg("Set name to {}.".format(prev_entry)) - return "node_background" - else: - caller.msg("Aborted.") - return "node_exit" - else: - # re-run old node, but pass in the name given - return None, {"prev_entry": inp} - - -def enter_name(caller, raw_string, **kwargs): - - # check if we already entered a name before - prev_entry = kwargs.get("prev_entry") - - if prev_entry: - text = "Current name: {}.\nEnter another name or to accept." - else: - text = "Enter your character's name or to abort." - - options = {"key": "_default", - "goto": (_set_name, {"prev_entry": prev_entry})} - - return text, options - -``` - -This will display as - -``` -Enter your character's name or to abort. - -> Gandalf - -Current name: Gandalf -Enter another name or to accept. - -> - -Set name to Gandalf. - -``` - -Here we re-use the same node twice for reading the input data from the user. Whatever we enter will -be caught by the `_default` option and passed into the helper function. We also pass along whatever -name we have entered before. This allows us to react correctly on an "empty" input - continue to the -node named `"node_background"` if we accept the input or go to an exit node if we presses Return -without entering anything. By returning `None` from the helper function we automatically re-run the -previous node, but updating its ingoing kwargs to tell it to display a different text. - - - -### Example: Storing data between nodes - -A convenient way to store data is to store it on the `caller.ndb._menutree` which you can reach from -every node. The advantage of doing this is that the `_menutree` NAttribute will be deleted -automatically when you exit the menu. - -```python - -def _set_name(caller, raw_string, **kwargs): - - caller.ndb._menutree.charactersheet = {} - caller.ndb._menutree.charactersheet['name'] = raw_string - caller.msg("You set your name to {}".format(raw_string) - return "background" - -def node_set_name(caller): - text = 'Enter your name:' - options = {'key': '_default', - 'goto': _set_name} - - return text, options - -... - - -def node_view_sheet(caller): - text = "Character sheet:\n {}".format(self.ndb._menutree.charactersheet) - - options = ({"key": "Accept", - "goto": "finish_chargen"}, - {"key": "Decline", - "goto": "start_over"}) - - return text, options - -``` - -Instead of passing the character sheet along from node to node through the `kwargs` we instead -set it up temporarily on `caller.ndb._menutree.charactersheet`. This makes it easy to reach from -all nodes. At the end we look at it and, if we accept the character the menu will likely save the -result to permanent storage and exit. - -> One point to remember though is that storage on `caller.ndb._menutree` is not persistent across -> `@reloads`. If you are using a persistent menu (using `EvMenu(..., persistent=True)` you should -use -> `caller.db` to store in-menu data like this as well. You must then yourself make sure to clean it -> when the user exits the menu. - - -### Example: Repeating the same node - -Sometimes you want to make a chain of menu nodes one after another, but you don't want the user to -be able to continue to the next node until you have verified that what they input in the previous -node is ok. A common example is a login menu: - - -```python - -def _check_username(caller, raw_string, **kwargs): - # we assume lookup_username() exists - if not lookup_username(raw_string): - # re-run current node by returning `None` - caller.msg("|rUsername not found. Try again.") - return None - else: - # username ok - continue to next node - return "node_password" - - -def node_username(caller): - text = "Please enter your user name." - options = {"key": "_default", - "goto": _check_username} - return text, options - - -def _check_password(caller, raw_string, **kwargs): - - nattempts = kwargs.get("nattempts", 0) - if nattempts > 3: - caller.msg("Too many failed attempts. Logging out") - return "node_abort" - elif not validate_password(raw_string): - caller.msg("Password error. Try again.") - return None, {"nattempts", nattempts + 1} - else: - # password accepted - return "node_login" - -def node_password(caller, raw_string, **kwargs): - text = "Enter your password." - options = {"key": "_default", - "goto": _check_password} - return text, options - -``` - -This will display something like - - -``` ---------------------------- -Please enter your username. ---------------------------- - -> Fo - ------------------------------- -Username not found. Try again. -______________________________ -abort: (back to start) ------------------------------- - -> Foo - ---------------------------- -Please enter your password. ---------------------------- - -> Bar - --------------------------- -Password error. Try again. --------------------------- -``` - -And so on. - -Here the goto-callables will return to the previous node if there is an error. In the case of -password attempts, this will tick up the `nattempts` argument that will get passed on from iteration -to iteration until too many attempts have been made. - - -### Defining nodes in a dictionary - -You can also define your nodes directly in a dictionary to feed into the `EvMenu` creator. - -```python -def mynode(caller): - # a normal menu node function - return text, options - -menu_data = {"node1": mynode, - "node2": lambda caller: ( - "This is the node text", - ({"key": "lambda node 1", - "desc": "go to node 1 (mynode)", - "goto": "node1"}, - {"key": "lambda node 2", - "desc": "go to thirdnode", - "goto": "node3"})), - "node3": lambda caller, raw_string: ( - # ... etc ) } - -# start menu, assuming 'caller' is available from earlier -EvMenu(caller, menu_data, startnode="node1") - -``` - -The keys of the dictionary become the node identifiers. You can use any callable on the right form -to describe each node. If you use Python `lambda` expressions you can make nodes really on the fly. -If you do, the lambda expression must accept one or two arguments and always return a tuple with two -elements (the text of the node and its options), same as any menu node function. - -Creating menus like this is one way to present a menu that changes with the circumstances - you -could for example remove or add nodes before launching the menu depending on some criteria. The -drawback is that a `lambda` expression [is much more -limited](https://docs.python.org/2/tutorial/controlflow.html#lambda-expressions) than a full -function - for example you can't use other Python keywords like `if` inside the body of the -`lambda`. - -Unless you are dealing with a relatively simple dynamic menu, defining menus with lambda's is -probably more work than it's worth: You can create dynamic menus by instead making each node -function more clever. See the [NPC shop tutorial](./NPC-shop-Tutorial.md) for an example of this. - - -## Ask for simple input - -This describes two ways for asking for simple questions from the user. Using Python's `input` -will *not* work in Evennia. `input` will *block* the entire server for *everyone* until that one -player has entered their text, which is not what you want. - -### The `yield` way - -In the `func` method of your Commands (only) you can use Python's built-in `yield` command to -request input in a similar way to `input`. It looks like this: - -```python -result = yield("Please enter your answer:") -``` - -This will send "Please enter your answer" to the Command's `self.caller` and then pause at that -point. All other players at the server will be unaffected. Once caller enteres a reply, the code -execution will continue and you can do stuff with the `result`. Here is an example: - -```python -from evennia import Command -class CmdTestInput(Command): - key = "test" - def func(self): - result = yield("Please enter something:") - self.caller.msg(f"You entered {result}.") - result2 = yield("Now enter something else:") - self.caller.msg(f"You now entered {result2}.") -``` - -Using `yield` is simple and intuitive, but it will only access input from `self.caller` and you -cannot abort or time out the pause until the player has responded. Under the hood, it is actually -just a wrapper calling `get_input` described in the following section. - -> Important Note: In Python you *cannot mix `yield` and `return ` in the same method*. It has -> to do with `yield` turning the method into a -> [generator](https://www.learnpython.org/en/Generators). A `return` without an argument works, you -> can just not do `return `. This is usually not something you need to do in `func()` anyway, -> but worth keeping in mind. - -### The `get_input` way - -The evmenu module offers a helper function named `get_input`. This is wrapped by the `yield` -statement which is often easier and more intuitive to use. But `get_input` offers more flexibility -and power if you need it. While in the same module as `EvMenu`, `get_input` is technically unrelated -to it. The `get_input` allows you to ask and receive simple one-line input from the user without -launching the full power of a menu to do so. To use, call `get_input` like this: - -```python -get_input(caller, prompt, callback) -``` - -Here `caller` is the entity that should receive the prompt for input given as `prompt`. The -`callback` is a callable `function(caller, prompt, user_input)` that you define to handle the answer -from the user. When run, the caller will see `prompt` appear on their screens and *any* text they -enter will be sent into the callback for whatever processing you want. - -Below is a fully explained callback and example call: - -```python -from evennia import Command -from evennia.utils.evmenu import get_input - -def callback(caller, prompt, user_input): - """ - This is a callback you define yourself. - - Args: - caller (Account or Object): The one being asked - for input - prompt (str): A copy of the current prompt - user_input (str): The input from the account. - - Returns: - repeat (bool): If not set or False, exit the - input prompt and clean up. If returning anything - True, stay in the prompt, which means this callback - will be called again with the next user input. - """ - caller.msg(f"When asked '{prompt}', you answered '{user_input}'.") - -get_input(caller, "Write something! ", callback) -``` - -This will show as - -``` -Write something! -> Hello -When asked 'Write something!', you answered 'Hello'. - -``` - -Normally, the `get_input` function quits after any input, but as seen in the example docs, you could -return True from the callback to repeat the prompt until you pass whatever check you want. - -> Note: You *cannot* link consecutive questions by putting a new `get_input` call inside the -> callback. If you want that you should use an EvMenu instead (see the [Repeating the same -> node](./EvMenu.md#example-repeating-the-same-node) example above). Otherwise you can either peek at the -> implementation of `get_input` and implement your own mechanism (it's just using cmdset nesting) or -> you can look at [this extension suggested on the mailing -> list](https://groups.google.com/forum/#!category-topic/evennia/evennia-questions/16pi0SfMO5U). - - -#### Example: Yes/No prompt - -Below is an example of a Yes/No prompt using the `get_input` function: - -```python -def yesno(caller, prompt, result): - if result.lower() in ("y", "yes", "n", "no"): - # do stuff to handle the yes/no answer - # ... - # if we return None/False the prompt state - # will quit after this - else: - # the answer is not on the right yes/no form - caller.msg("Please answer Yes or No. \n{prompt}") -@ # returning True will make sure the prompt state is not exited - return True - -# ask the question -get_input(caller, "Is Evennia great (Yes/No)?", yesno) -``` - -## The `@list_node` decorator - -The `evennia.utils.evmenu.list_node` is an advanced decorator for use with `EvMenu` node functions. -It is used to quickly create menus for manipulating large numbers of items. - - -``` -text here -______________________________________________ - -1. option1 7. option7 13. option13 -2. option2 8. option8 14. option14 -3. option3 9. option9 [p]revius page -4. option4 10. option10 page 2 -5. option5 11. option11 [n]ext page -6. option6 12. option12 - -``` - -The menu will automatically create an multi-page option listing that one can flip through. One can -inpect each entry and then select them with prev/next. This is how it is used: - - -```python -from evennia.utils.evmenu import list_node - - -... - -_options(caller): - return ['option1', 'option2', ... 'option100'] - -_select(caller, menuchoice, available_choices): - # analyze choice - return node_matching_the_choice - -@list_node(_options, select=_select, pagesize=10) -def node_mylist(caller, raw_string, **kwargs): - ... - - # the decorator auto-creates the options; any options - # returned here would be appended to the auto-options - return node_text, {} -``` - -The `options` argument to `list_node` is either a list, a generator or a callable returning a list -of strings for each option that should be displayed in the node. - -The `select` is a callable in the example above but could also be the name of a menu node. If a -callable, the `menuchoice` argument holds the selection done and `available_choices` holds all the -options available. The callable should return the menu to go to depending on the selection (or -`None` to rerun the same node). If the name of a menu node, the selection will be passed as -`selection` kwarg to that node. - -The decorated node itself should return `text` to display in the node. It must return at least an -empty dictionary for its options. It returning options, those will supplement the options -auto-created by the `list_node` decorator. - - -## Assorted notes - -The EvMenu is implemented using [Commands](./Commands.md). When you start a new EvMenu, the user of the -menu will be assigned a [CmdSet](./Command-Sets.md) with the commands they need to navigate the menu. -This means that if you were to, from inside the menu, assign a new command set to the caller, *you -may override the Menu Cmdset and kill the menu*. If you want to assign cmdsets to the caller as part -of the menu, you should store the cmdset on `caller.ndb._menutree` and wait to actually assign it -until the exit node. diff --git a/docs/0.9.5/_sources/EvMore.md.txt b/docs/0.9.5/_sources/EvMore.md.txt deleted file mode 100644 index 452c4fb2b7..0000000000 --- a/docs/0.9.5/_sources/EvMore.md.txt +++ /dev/null @@ -1,38 +0,0 @@ -# EvMore - - -When sending a very long text to a user client, it might scroll beyond of the height of the client -window. The `evennia.utils.evmore.EvMore` class gives the user the in-game ability to only view one -page of text at a time. It is usually used via its access function, `evmore.msg`. - -The name comes from the famous unix pager utility *more* which performs just this function. - -### Using EvMore - -To use the pager, just pass the long text through it: - -```python -from evennia.utils import evmore - -evmore.msg(receiver, long_text) -``` -Where receiver is an [Object](./Objects.md) or a [Account](./Accounts.md). If the text is longer than the -client's screen height (as determined by the NAWS handshake or by `settings.CLIENT_DEFAULT_HEIGHT`) -the pager will show up, something like this: - ->[...] -aute irure dolor in reprehenderit in voluptate velit -esse cillum dolore eu fugiat nulla pariatur. Excepteur -sint occaecat cupidatat non proident, sunt in culpa qui -officia deserunt mollit anim id est laborum. - ->(**more** [1/6] retur**n**|**b**ack|**t**op|**e**nd|**a**bort) - - -where the user will be able to hit the return key to move to the next page, or use the suggested -commands to jump to previous pages, to the top or bottom of the document as well as abort the -paging. - -The pager takes several more keyword arguments for controlling the message output. See the -[evmore-API](github:evennia.utils.evmore) for more info. - diff --git a/docs/0.9.5/_sources/Evennia-API.md.txt b/docs/0.9.5/_sources/Evennia-API.md.txt deleted file mode 100644 index b4bbf14866..0000000000 --- a/docs/0.9.5/_sources/Evennia-API.md.txt +++ /dev/null @@ -1,97 +0,0 @@ -# API Summary - -[evennia](api/evennia.md) - library root -- [evennia.accounts](evennia.accounts) - the out-of-character entities representing players -- [evennia.commands](evennia.commands) - handle all inputs. Also includes default commands -- [evennia.comms](evennia.comms) - in-game channels and messaging -- [evennia.contrib](evennia.contrib) - game-specific tools and code contributed by the community -- [evennia.help](evennia.help) - in-game help system -- [evennia.locks](evennia.locks) - limiting access to various systems and resources -- [evennia.objects](evennia.objects) - all in-game entities, like Rooms, Characters, Exits etc -- [evennia.prototypes](evennia.prototypes) - customize entities using dicts -- [evennia.scripts](evennia.scripts) - all out-of-character game objects -- [evennia.server](evennia.server) - core Server and Portal programs, also network protocols -- [evennia.typeclasses](evennia.typeclasses) - core database-python bridge -- [evennia.utils](evennia.utils) - lots of useful coding tools and utilities -- [evennia.web](evennia.web) - webclient, website and other web resources - - -## Shortcuts - -Evennia's 'flat API' has shortcuts to common tools, available by only importing `evennia`. -The flat API is defined in `__init__.py` [viewable here](github:evennia/__init__.py) - - -### Main config - -- [evennia.settings_default](github:evennia/settings_default.py) - all settings (modify/override in `mygame/server/settings.py`) - -### Search functions - -- [evennia.search_account](evennia.utils.search.search_account) -- [evennia.search_object](evennia.utils.search.search_object) -- [evennia.search_object_by_tag](evennia.utils.search.search_tag) -- [evennia.search_script](evennia.utils.search.search_script) -- [evennia.search_channel](evennia.utils.search.search_channel) -- [evennia.search_message](evennia.utils.search.search_message) -- [evennia.search_help](evennia.utils.search.search_help_entry) - -### Create functions - -- [evennia.create_account](evennia.utils.create.create_account) -- [evennia.create_object](evennia.utils.create.create_object) -- [evennia.create_script](evennia.utils.create.create_script) -- [evennia.create_channel](evennia.utils.create.create_channel) -- [evennia.create_help_entry](evennia.utils.create.create_help_entry) -- [evennia.create_message](evennia.utils.create.create_message) - -### Typeclasses - -- [evennia.Defaultaccount](evennia.accounts.accounts.DefaultAccount) - player account class ([docs](./Accounts.md)) -- [evennia.DefaultGuest](evennia.accounts.accounts.DefaultGuest) - base guest account class -- [evennia.DefaultObject](evennia.objects.objects.DefaultObject) - base class for all objects ([docs](./Objects.md)) -- [evennia.DefaultCharacter](evennia.objects.objects.DefaultCharacter) - base class for in-game characters ([docs](./Objects.md#characters)) -- [evennia.DefaultRoom](evennia.objects.objects.DefaultRoom) - base class for rooms ([docs](./Objects.md#rooms)) -- [evennia.DefaultExit](evennia.objects.objects.DefaultExit) - base class for exits ([docs](./Objects.md#exits)) -- [evennia.DefaultScript](evennia.scripts.scripts.DefaultScript) - base class for OOC-objects ([docs](./Scripts.md)) -- [evennia.DefaultChannel](evennia.comms.comms.DefaultChannel) - base class for in-game channels ([docs](./Communications.md)) - -### Commands - -- [evennia.Command](evennia.commands.command.Command) - base [Command](./Commands.md) class. See also `evennia.default_cmds.MuxCommand` -- [evennia.CmdSet](evennia.commands.cmdset.CmdSet) - base [Cmdset](./Command-Sets.md) class -- evennia.default_cmds - access to all [default command classes](evennia.commands.default) as properties -- evennia.syscmdkeys - access to all [system command](./Commands.md#system-commands) names as properties - -### Utilities - -- [evennia.utils.utils](evennia.utils.utils) - mixed useful utilities -- [evennia.gametime](evennia.utils.gametime) - server run- and game time ([docs](./Coding-Utils.md#game-time)) -- [evennia.logger](evennia.utils.logger) - logging tools -- [evennia.ansi](evennia.utils.ansi) - ansi coloring tools -- [evennia.spawn](evennia.prototypes.spawner.spawn) - spawn/prototype system ([docs](./Spawner-and-Prototypes.md)) -- [evennia.lockfuncs](evennia.locks.lockfuncs) - default lock functions for access control ([docs](./Locks.md)) -- [evennia.EvMenu](evennia.utils.evmenu.EvMenu) - menu system ([docs](./EvMenu.md)) -- [evennia.EvTable](evennia.utils.evtable.EvTable) - text table creater -- [evennia.EvForm](evennia.utils.evform.EvForm) - text form creator -- [evennia.EvEditor](evennia.utils.eveditor.EvEditor) - in game text line editor ([docs](./EvEditor.md)) - -### Global singleton handlers - -- [evennia.TICKER_HANDLER](evennia.scripts.tickerhandler) - allow objects subscribe to tickers ([docs](./TickerHandler.md)) -- [evennia.MONITOR_HANDLER](evennia.scripts.monitorhandler) - monitor changes ([docs](./MonitorHandler.md)) -- [evennia.CHANNEL_HANDLER](evennia.comms.channelhandler) - maintains channels -- [evennia.SESSION_HANDLER](evennia.server.sessionhandler) - manages all sessions - -### Database core models (for more advanced lookups) - -- [evennia.ObjectDB](evennia.objects.models.ObjectDB) -- [evennia.accountDB](evennia.accounts.models.AccountDB) -- [evennia.ScriptDB](evennia.scripts.models.ScriptDB) -- [evennia.ChannelDB](evennia.comms.models.ChannelDB) -- [evennia.Msg](evennia.comms.models.Msg) -- evennia.managers - contains shortcuts to all database managers - -### Contributions - -- [evennia.contrib](evennia.contrib) - game-specific contributions and plugins ([README](github:evennia/contrib/README.md)) diff --git a/docs/0.9.5/_sources/Evennia-Game-Index.md.txt b/docs/0.9.5/_sources/Evennia-Game-Index.md.txt deleted file mode 100644 index 9f47bbc322..0000000000 --- a/docs/0.9.5/_sources/Evennia-Game-Index.md.txt +++ /dev/null @@ -1,71 +0,0 @@ -# Evennia Game Index - - -The [Evennia game index](http://games.evennia.com) is a list of games built or -being built with Evennia. Anyone is allowed to add their game to the index -- also if you have just started development and don't yet accept external -players. It's a chance for us to know you are out there and for you to make us -intrigued about or excited for your upcoming game! - -All we ask is that you check so your game-name does not collide with one -already in the list - be nice! - -## Connect with the wizard - -From your game dir, run - - evennia connections - -This will start the Evennia _Connection wizard_. From the menu, select to add -your game to the Evennia Game Index. Follow the prompts and don't forget to -save your new settings in the end. Use `quit` at any time if you change your -mind. - -> The wizard will create a new file `mygame/server/conf/connection_settings.py` -> with the settings you chose. This is imported from the end of your main -> settings file and will thus override it. You can edit this new file if you -> want, but remember that if you run the wizard again, your changes may get -> over-written. - -## Manual Settings - -If you don't want to use the wizard (maybe because you already have the client installed from an -earlier version), you can also configure your index entry in your settings file -(`mygame/server/conf/settings.py`). Add the following: - -```python -GAME_INDEX_ENABLED = True - -GAME_INDEX_LISTING = { - # required - 'game_status': 'pre-alpha', # pre-alpha, alpha, beta, launched - 'listing_contact': "dummy@dummy.com", # not publicly shown. - 'short_description': 'Short blurb', - - # optional - 'long_description': - "Longer description that can use Markdown like *bold*, _italic_" - "and [linkname](http://link.com). Use \n for line breaks." - 'telnet_hostname': 'dummy.com', - 'telnet_port': '1234', - 'web_client_url': 'dummy.com/webclient', - 'game_website': 'dummy.com', - # 'game_name': 'MyGame', # set only if different than settings.SERVERNAME -} -``` - -Of these, the `game_status`, `short_description` and `listing_contact` are -required. The `listing_contact` is not publicly visible and is only meant as a -last resort if we need to get in touch with you over any listing issue/bug (so -far this has never happened). - -If `game_name` is not set, the `settings.SERVERNAME` will be used. Use empty strings -(`''`) for optional fields you don't want to specify at this time. - -## Non-public games - -If you don't specify neither `telnet_hostname + port` nor -`web_client_url`, the Game index will list your game as _Not yet public_. -Non-public games are moved to the bottom of the index since there is no way -for people to try them out. But it's a good way to show you are out there, even -if you are not ready for players yet. diff --git a/docs/0.9.5/_sources/Evennia-Introduction.md.txt b/docs/0.9.5/_sources/Evennia-Introduction.md.txt deleted file mode 100644 index 96fd2a8f2d..0000000000 --- a/docs/0.9.5/_sources/Evennia-Introduction.md.txt +++ /dev/null @@ -1,178 +0,0 @@ -# Evennia Introduction - -> *A MUD (originally Multi-User Dungeon, with later variants Multi-User Dimension and Multi-User -Domain) is a multiplayer real-time virtual world described primarily in text. MUDs combine elements -of role-playing games, hack and slash, player versus player, interactive fiction and online chat. -Players can read or view descriptions of rooms, objects, other players, non-player characters, and -actions performed in the virtual world. Players typically interact with each other and the world by -typing commands that resemble a natural language.* - [Wikipedia](http://en.wikipedia.org/wiki/MUD) - -If you are reading this, it's quite likely you are dreaming of creating and running a text-based -massively-multiplayer game ([MUD/MUX/MUSH](http://tinyurl.com/c5sc4bm) etc) of your very own. You -might just be starting to think about it, or you might have lugged around that *perfect* game in -your mind for years ... you know *just* how good it would be, if you could only make it come to -reality. We know how you feel. That is, after all, why Evennia came to be. - -Evennia is in principle a MUD-building system: a bare-bones Python codebase and server intended to -be highly extendable for any style of game. "Bare-bones" in this context means that we try to impose -as few game-specific things on you as possible. So whereas we for convenience offer basic building -blocks like objects, characters, rooms, default commands for building and administration etc, we -don't prescribe any combat rules, mob AI, races, skills, character classes or other things that will -be different from game to game anyway. It is possible that we will offer some such systems as -contributions in the future, but these will in that case all be optional. - -What we *do* however, is to provide a solid foundation for all the boring database, networking, and -behind-the-scenes administration stuff that all online games need whether they like it or not. -Evennia is *fully persistent*, that means things you drop on the ground somewhere will still be -there a dozen server reboots later. Through Django we support a large variety of different database -systems (a database is created for you automatically if you use the defaults). - -Using the full power of Python throughout the server offers some distinct advantages. All your -coding, from object definitions and custom commands to AI scripts and economic systems is done in -normal Python modules rather than some ad-hoc scripting language. The fact that you script the game -in the same high-level language that you code it in allows for very powerful and custom game -implementations indeed. - -The server ships with a default set of player commands that are similar to the MUX command set. We -*do not* aim specifically to be a MUX server, but we had to pick some default to go with (see -[this](./Soft-Code.md) for more about our original motivations). It's easy to remove or add commands, or -to have the command syntax mimic other systems, like Diku, LP, MOO and so on. Or why not create a -new and better command system of your own design. - -## Can I test it somewhere? - -Evennia's demo server can be found at [demo.evennia.com](http://demo.evennia.com). If you prefer to -connect to the demo via your own telnet client you can do so at `silvren.com`, port `4280`. Here is -a [screenshot](./Screenshot.md). - -Once you installed Evennia yourself it comes with its own tutorial - this shows off some of the -possibilities _and_ gives you a small single-player quest to play. The tutorial takes only one -single in-game command to install as explained [here](./Tutorial-World-Introduction.md). - -## Brief summary of features - -### Technical - -- Game development is done by the server importing your normal Python modules. Specific server -features are implemented by overloading hooks that the engine calls appropriately. -- All game entities are simply Python classes that handle database negotiations behind the scenes -without you needing to worry. -- Command sets are stored on individual objects (including characters) to offer unique functionality -and object-specific commands. Sets can be updated and modified on the fly to expand/limit player -input options during play. -- Scripts are used to offer asynchronous/timed execution abilities. Scripts can also be persistent. -There are easy mechanisms to thread particularly long-running processes and built-in ways to start -"tickers" for games that wants them. -- In-game communication channels are modular and can be modified to any functionality, including -mailing systems and full logging of all messages. -- Server can be fully rebooted/reloaded without users disconnecting. -- An Account can freely connect/disconnect from game-objects, offering an easy way to implement -multi-character systems and puppeting. -- Each Account can optionally control multiple Characters/Objects at the same time using the same -login information. -- Spawning of individual objects via a prototypes-like system. -- Tagging can be used to implement zones and object groupings. -- All source code is extensively documented. -- Unit-testing suite, including tests of default commands and plugins. - -### Default content - -- Basic classes for Objects, Characters, Rooms and Exits -- Basic login system, using the Account's login name as their in-game Character's name for -simplicity -- "MUX-like" command set with administration, building, puppeting, channels and social commands -- In-game Tutorial -- Contributions folder with working, but optional, code such as alternative login, menus, character -generation and more - -### Standards/Protocols supported - -- TCP/websocket HTML5 browser web client, with ajax/comet fallback for older browsers -- Telnet and Telnet + SSL with mud-specific extensions ([MCCP](http://tintin.sourceforge.net/mccp/), -[MSSP](http://tintin.sourceforge.net/mssp/), [TTYPE](http://tintin.sourceforge.net/mtts/), -[MSDP](http://tintin.sourceforge.net/msdp/), -[GMCP](https://www.ironrealms.com/rapture/manual/files/FeatGMCP-txt.html), -[MXP](https://www.zuggsoft.com/zmud/mxp.htm) links) -- ANSI and xterm256 colours -- SSH -- HTTP - Website served by in-built webserver and connected to same database as game. -- IRC - external IRC channels can be connected to in-game chat channels -- RSS feeds can be echoed to in-game channels (things like Twitter can easily be added) -- Several different databases supported (SQLite3, MySQL, PostgreSQL, ...) - -For more extensive feature information, see the [Developer Central](./Developer-Central.md). - -## What you need to know to work with Evennia - -Assuming you have Evennia working (see the [quick start instructions](./Getting-Started.md)) and have -gotten as far as to start the server and connect to it with the client of your choice, here's what -you need to know depending on your skills and needs. - -### I don't know (or don't want to do) any programming - I just want to run a game! - -Evennia comes with a default set of commands for the Python newbies and for those who need to get a -game running *now*. Stock Evennia is enough for running a simple 'Talker'-type game - you can build -and describe rooms and basic objects, have chat channels, do emotes and other things suitable for a -social or free-form MU\*. Combat, mobs and other game elements are not included, so you'll have a -very basic game indeed if you are not willing to do at least *some* coding. - -### I know basic Python, or I am willing to learn - -Evennia's source code is extensively documented and is [viewable online](https://github.com/evennia/evennia). -We also have a comprehensive [online manual](https://www/evennia/com/docs) with lots of examples. -But while Python is -considered a very easy programming language to get into, you do have a learning curve to climb if -you are new to programming. You should probably sit down -with a Python beginner's [tutorial](http://docs.python.org/tutorial/) (there are plenty of them on -the web if you look around) so you at least know what you are seeing. See also our -[link page](./Links.md) for some reading suggestions. To efficiently code your dream game in -Evennia you don't need to be a Python guru, but you do need to be able to read example code -containing at least these basic Python features: - -- Importing and using python [modules](http://docs.python.org/3.7/tutorial/modules.html) -- Using [variables](http://www.tutorialspoint.com/python/python_variable_types.htm), -[conditional statements](http://docs.python.org/tutorial/controlflow.html#if-statements), -[loops](http://docs.python.org/tutorial/controlflow.html#for-statements) and -[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions) -- Using [lists, dictionaries and list comprehensions](http://docs.python.org/tutorial/datastructures.html) -- Doing [string handling and formatting](http://docs.python.org/tutorial/introduction.html#strings) -- Have a basic understanding of [object-oriented programming](http://www.tutorialspoint.com/python/python_classes_objects.htm), using -[Classes](http://docs.python.org/tutorial/classes.html), their methods and properties - -Obviously, the more things you feel comfortable with, the easier time you'll have to find your way. -With just basic knowledge you should be able to define your own [Commands](./Commands.md), create custom -[Objects](./Objects.md) as well as make your world come alive with basic [Scripts](./Scripts.md). You can -definitely build a whole advanced and customized game from extending Evennia's examples only. - -### I know my Python stuff and I am willing to use it! - -Even if you started out as a Python beginner, you will likely get to this point after working on -your game for a while. With more general knowledge in Python the full power of Evennia opens up for -you. Apart from modifying commands, objects and scripts, you can develop everything from advanced -mob AI and economic systems, through sophisticated combat and social mini games, to redefining how -commands, players, rooms or channels themselves work. Since you code your game by importing normal -Python modules, there are few limits to what you can accomplish. - -If you *also* happen to know some web programming (HTML, CSS, Javascript) there is also a web -presence (a website and a mud web client) to play around with ... - -### Where to from here? - -From here you can continue browsing the [online documentation](./index.md) to -find more info about Evennia. Or you can jump into the [Tutorials](./Tutorials.md) and get your hands -dirty with code right away. You can also read the developer's [dev blog](https://evennia.blogspot.com/) for many tidbits and snippets about Evennia's development and -structure. - -Some more hints: - -1. Get engaged in the community. Make an introductory post to our [mailing list/forum](https://groups.google.com/forum/#!forum/evennia) and get to know people. It's also -highly recommended you hop onto our [Developer chat](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) -on IRC. This allows you to chat directly with other developers new and old as well as with the devs -of Evennia itself. This chat is logged (you can find links on http://www.evennia.com) and can also -be searched from the same place for discussion topics you are interested in. -2. Read the [Game Planning](./Game-Planning.md) wiki page. It gives some ideas for your work flow and the -state of mind you should aim for - including cutting down the scope of your game for its first -release. -3. Do the [Tutorial for basic MUSH-like game](./Tutorial-for-basic-MUSH-like-game.md) carefully from -beginning to end and try to understand what does what. Even if you are not interested in a MUSH for -your own game, you will end up with a small (very small) game that you can build or learn from. diff --git a/docs/0.9.5/_sources/Evennia-for-Diku-Users.md.txt b/docs/0.9.5/_sources/Evennia-for-Diku-Users.md.txt deleted file mode 100644 index 5ce7b14352..0000000000 --- a/docs/0.9.5/_sources/Evennia-for-Diku-Users.md.txt +++ /dev/null @@ -1,200 +0,0 @@ -# Evennia for Diku Users - - -Evennia represents a learning curve for those who used to code on -[Diku](https://en.wikipedia.org/wiki/DikuMUD) type MUDs. While coding in Python is easy if you -already know C, the main effort is to get rid of old C programming habits. Trying to code Python the -way you code C will not only look ugly, it will lead to less optimal and harder to maintain code. -Reading Evennia example code is a good way to get a feel for how different problems are approached -in Python. - -Overall, Python offers an extensive library of resources, safe memory management and excellent -handling of errors. While Python code does not run as fast as raw C code does, the difference is not -all that important for a text-based game. The main advantage of Python is an extremely fast -development cycle with and easy ways to create game systems that would take many times more code and -be much harder to make stable and maintainable in C. - -### Core Differences - -- As mentioned, the main difference between Evennia and a Diku-derived codebase is that Evennia is -written purely in Python. Since Python is an interpreted language there is no compile stage. It is -modified and extended by the server loading Python modules at run-time. It also runs on all computer -platforms Python runs on (which is basically everywhere). -- Vanilla Diku type engines save their data in custom *flat file* type storage solutions. By -contrast, Evennia stores all game data in one of several supported SQL databases. Whereas flat files -have the advantage of being easier to implement, they (normally) lack many expected safety features -and ways to effectively extract subsets of the stored data. For example, if the server loses power -while writing to a flatfile it may become corrupt and the data lost. A proper database solution is -not susceptible to this - at no point is the data in a state where it cannot be recovered. Databases -are also highly optimized for querying large data sets efficiently. - -### Some Familiar Things - -Diku expresses the character object referenced normally by: - -`struct char ch*` then all character-related fields can be accessed by `ch->`. In Evennia, one must -pay attention to what object you are using, and when you are accessing another through back- -handling, that you are accessing the right object. In Diku C, accessing character object is normally -done by: - -```c -/* creating pointer of both character and room struct */ - -void(struct char ch*, struct room room*){ - int dam; - if (ROOM_FLAGGED(room, ROOM_LAVA)){ - dam = 100 - ch->damage_taken = dam - }; -}; -``` - -As an example for creating Commands in Evennia via the `from evennia import Command` the character -object that calls the command is denoted by a class property as `self.caller`. In this example -`self.caller` is essentially the 'object' that has called the Command, but most of the time it is an -Account object. For a more familiar Diku feel, create a variable that becomes the account object as: - -```python -#mygame/commands/command.py - -from evennia import Command - -class CmdMyCmd(Command): - """ - This is a Command Evennia Object - """ - - [...] - - def func(self): - ch = self.caller - # then you can access the account object directly by using the familiar ch. - ch.msg("...") - account_name = ch.name - race = ch.db.race - -``` - -As mentioned above, care must be taken what specific object you are working with. If focused on a -room object and you need to access the account object: - -```python -#mygame/typeclasses/room.py - -from evennia import DefaultRoom - -class MyRoom(DefaultRoom): - [...] - - def is_account_object(self, object): - # a test to see if object is an account - [...] - - def myMethod(self): - #self.caller would not make any sense, since self refers to the - # object of 'DefaultRoom', you must find the character obj first: - for ch in self.contents: - if self.is_account_object(ch): - # now you can access the account object with ch: - account_name = ch.name - race = ch.db.race -``` - - -## Emulating Evennia to Look and Feel Like A Diku/ROM - -To emulate a Diku Mud on Evennia some work has to be done before hand. If there is anything that all -coders and builders remember from Diku/Rom days is the presence of VNUMs. Essentially all data was -saved in flat files and indexed by VNUMs for easy access. Evennia has the ability to emulate VNUMS -to the extent of categorising rooms/mobs/objs/trigger/zones[...] into vnum ranges. - -Evennia has objects that are called Scripts. As defined, they are the 'out of game' instances that -exist within the mud, but never directly interacted with. Scripts can be used for timers, mob AI, -and even a stand alone databases. - -Because of their wonderful structure all mob, room, zone, triggers, etc.. data can be saved in -independently created global scripts. - -Here is a sample mob file from a Diku Derived flat file. - -```text -#0 -mob0~ -mob0~ -mob0 -~ - Mob0 -~ -10 0 0 0 0 0 0 0 0 E -1 20 9 0d0+10 1d2+0 -10 100 -8 8 0 -E -#1 -Puff dragon fractal~ -Puff~ -Puff the Fractal Dragon is here, contemplating a higher reality. -~ - Is that some type of differential curve involving some strange, and unknown -calculus that she seems to be made out of? -~ -516106 0 0 0 2128 0 0 0 1000 E -34 9 -10 6d6+340 5d5+5 -340 115600 -8 8 2 -BareHandAttack: 12 -E -T 95 -``` -Each line represents something that the MUD reads in and does something with it. This isn't easy to -read, but let's see if we can emulate this as a dictionary to be stored on a database script created -in Evennia. - -First, let's create a global script that does absolutely nothing and isn't attached to anything. You -can either create this directly in-game with the @py command or create it in another file to do some -checks and balances if for whatever reason the script needs to be created again. Progmatically it -can be done like so: - -```python -from evennia import create_script - -mob_db = create_script("typeclasses.scripts.DefaultScript", key="mobdb", - persistent=True, obj=None) -mob_db.db.vnums = {} -``` -Just by creating a simple script object and assigning it a 'vnums' attribute as a type dictionary. -Next we have to create the mob layout.. - -```python -# vnum : mob_data - -mob_vnum_1 = { - 'key' : 'puff', - 'sdesc' : 'puff the fractal dragon', - 'ldesc' : 'Puff the Fractal Dragon is here, ' \ - 'contemplating a higher reality.', - 'ddesc' : ' Is that some type of differential curve ' \ - 'involving some strange, and unknown calculus ' \ - 'that she seems to be made out of?', - [...] - } - -# Then saving it to the data, assuming you have the script obj stored in a variable. -mob_db.db.vnums[1] = mob_vnum_1 -``` - -This is a very 'caveman' example, but it gets the idea across. You can use the keys in the -`mob_db.vnums` to act as the mob vnum while the rest contains the data.. - -Much simpler to read and edit. If you plan on taking this route, you must keep in mind that by -default evennia 'looks' at different properties when using the `look` command for instance. If you -create an instance of this mob and make its `self.key = 1`, by default evennia will say - -`Here is : 1` - -You must restructure all default commands so that the mud looks at different properties defined on -your mob. - - - - diff --git a/docs/0.9.5/_sources/Evennia-for-MUSH-Users.md.txt b/docs/0.9.5/_sources/Evennia-for-MUSH-Users.md.txt deleted file mode 100644 index f0fe7d4670..0000000000 --- a/docs/0.9.5/_sources/Evennia-for-MUSH-Users.md.txt +++ /dev/null @@ -1,218 +0,0 @@ -# Evennia for MUSH Users - -*This page is adopted from an article originally posted for the MUSH community [here on -musoapbox.net](http://musoapbox.net/topic/1150/evennia-for-mushers).* - -[MUSH](https://en.wikipedia.org/wiki/MUSH)es are text multiplayer games traditionally used for -heavily roleplay-focused game styles. They are often (but not always) utilizing game masters and -human oversight over code automation. MUSHes are traditionally built on the TinyMUSH-family of game -servers, like PennMUSH, TinyMUSH, TinyMUX and RhostMUSH. Also their siblings -[MUCK](https://en.wikipedia.org/wiki/TinyMUCK) and [MOO](https://en.wikipedia.org/wiki/MOO) are -often mentioned together with MUSH since they all inherit from the same -[TinyMUD](https://en.wikipedia.org/wiki/MUD_trees#TinyMUD_family_tree) base. A major feature is the -ability to modify and program the game world from inside the game by using a custom scripting -language. We will refer to this online scripting as *softcode* here. - -Evennia works quite differently from a MUSH both in its overall design and under the hood. The same -things are achievable, just in a different way. Here are some fundamental differences to keep in -mind if you are coming from the MUSH world. - -## Developers vs Players - -In MUSH, users tend to code and expand all aspects of the game from inside it using softcode. A MUSH -can thus be said to be managed solely by *Players* with different levels of access. Evennia on the -other hand, differentiates between the role of the *Player* and the *Developer*. - -- An Evennia *Developer* works in Python from *outside* the game, in what MUSH would consider -“hardcode”. Developers implement larger-scale code changes and can fundamentally change how the game -works. They then load their changes into the running Evennia server. Such changes will usually not -drop any connected players. -- An Evennia *Player* operates from *inside* the game. Some staff-level players are likely to double -as developers. Depending on access level, players can modify and expand the game's world by digging -new rooms, creating new objects, alias commands, customize their experience and so on. Trusted staff -may get access to Python via the `@py` command, but this would be a security risk for normal Players -to use. So the *Player* usually operates by making use of the tools prepared for them by the -*Developer* - tools that can be as rigid or flexible as the developer desires. - -## Collaborating on a game - Python vs Softcode - -For a *Player*, collaborating on a game need not be too different between MUSH and Evennia. The -building and description of the game world can still happen mostly in-game using build commands, -using text tags and [inline functions](./TextTags.md#inline-functions) to prettify and customize the -experience. Evennia offers external ways to build a world but those are optional. There is also -nothing *in principle* stopping a Developer from offering a softcode-like language to Players if -that is deemed necessary. - -For *Developers* of the game, the difference is larger: Code is mainly written outside the game in -Python modules rather than in-game on the command line. Python is a very popular and well-supported -language with tons of documentation and help to be found. The Python standard library is also a -great help for not having to reinvent the wheel. But that said, while Python is considered one of -the easier languages to learn and use it is undoubtedly very different from MUSH softcode. - -While softcode allows collaboration in-game, Evennia's external coding instead opens up the -possibility for collaboration using professional version control tools and bug tracking using -websites like github (or bitbucket for a free private repo). Source code can be written in proper -text editors and IDEs with refactoring, syntax highlighting and all other conveniences. In short, -collaborative development of an Evennia game is done in the same way most professional collaborative -development is done in the world, meaning all the best tools can be used. - - -## `@parent` vs `@typeclass` and `@spawn` - -Inheritance works differently in Python than in softcode. Evennia has no concept of a "master -object" that other objects inherit from. There is in fact no reason at all to introduce "virtual -objects" in the game world - code and data are kept separate from one another. - -In Python (which is an [object oriented](https://en.wikipedia.org/wiki/Object-oriented_programming) -language) one instead creates *classes* - these are like blueprints from which you spawn any number -of *object instances*. Evennia also adds the extra feature that every instance is persistent in the -database (this means no SQL is ever needed). To take one example, a unique character in Evennia is -an instances of the class `Character`. - -One parallel to MUSH's `@parent` command may be Evennia's `@typeclass` command, which changes which -class an already existing object is an instance of. This way you can literally turn a `Character` -into a `Flowerpot` on the spot. - -if you are new to object oriented design it's important to note that all object instances of a class -does *not* have to be identical. If they did, all Characters would be named the same. Evennia allows -to customize individual objects in many different ways. One way is through *Attributes*, which are -database-bound properties that can be linked to any object. For example, you could have an `Orc` -class that defines all the stuff an Orc should be able to do (probably in turn inheriting from some -`Monster` class shared by all monsters). Setting different Attributes on different instances -(different strength, equipment, looks etc) would make each Orc unique despite all sharing the same -class. - - The `@spawn` command allows one to conveniently choose between different "sets" of Attributes to -put on each new Orc (like the "warrior" set or "shaman" set) . Such sets can even inherit one -another which is again somewhat remniscent at least of the *effect* of `@parent` and the object- -based inheritance of MUSH. - -There are other differences for sure, but that should give some feel for things. Enough with the -theory. Let's get down to more practical matters next. To install, see the -[Getting Started instructions](./Getting-Started.md). - -## A first step making things more familiar - -We will here give two examples of customizing Evennia to be more familiar to a MUSH *Player*. - -### Activating a multi-descer - -By default Evennia’s `desc` command updates your description and that’s it. There is a more feature- -rich optional “multi-descer” in `evennia/contrib/multidesc.py` though. This alternative allows for -managing and combining a multitude of keyed descriptions. - -To activate the multi-descer, `cd` to your game folder and into the `commands` sub-folder. There -you’ll find the file `default_cmdsets.py`. In Python lingo all `*.py` files are called *modules*. -Open the module in a text editor. We won’t go into Evennia in-game *Commands* and *Command sets* -further here, but suffice to say Evennia allows you to change which commands (or versions of -commands) are available to the player from moment to moment depending on circumstance. - -Add two new lines to the module as seen below: - -```python -# the file mygame/commands/default_cmdsets.py -# [...] - -from evennia.contrib import multidescer # <- added now - -class CharacterCmdSet(default_cmds.CharacterCmdSet): - """ - The CharacterCmdSet contains general in-game commands like look, - get etc available on in-game Character objects. It is merged with - the AccountCmdSet when an Account puppets a Character. - """ - key = "DefaultCharacter" - - def at_cmdset_creation(self): - """ - Populates the cmdset - """ - super().at_cmdset_creation() - # - # any commands you add below will overload the default ones. - # - self.add(multidescer.CmdMultiDesc()) # <- added now -# [...] -``` - -Note that Python cares about indentation, so make sure to indent with the same number of spaces as -shown above! - -So what happens above? We [import the module](http://www.linuxtopia.org/online_books/programming_books/python_programming/python_ch28s03.html) `evennia/contrib/multidescer.py` at the top. Once -imported we can access stuff inside that module using full stop (`.`). The multidescer is defined as -a class `CmdMultiDesc` (we could find this out by opening said module in a text editor). At the -bottom we create a new instance of this class and add it to the `CharacterCmdSet` class. For the -sake of this tutorial we only need to know that `CharacterCmdSet` contains all commands that should -be be available to the `Character` by default. - -This whole thing will be triggered when the command set is first created, which happens on server -start. So we need to reload Evennia with `@reload` - no one will be disconnected by doing this. If -all went well you should now be able to use `desc` (or `+desc`) and find that you have more -possibilities: - -```text -> help +desc # get help on the command -> +desc eyes = His eyes are blue. -> +desc basic = A big guy. -> +desc/set basic + + eyes # we add an extra space between -> look me -A big guy. His eyes are blue. -``` - -If there are errors, a *traceback* will show in the server log - several lines of text showing -where the error occurred. Find where the error is by locating the line number related to the -`default_cmdsets.py` file (it's the only one you've changed so far). Most likely you mis-spelled -something or missed the indentation. Fix it and either `@reload` again or run `evennia start` as -needed. - -### Customizing the multidescer syntax - -As seen above the multidescer uses syntax like this (where `|/` are Evennia's tags for line breaks) -: - -```text -> +desc/set basic + |/|/ + cape + footwear + |/|/ + attitude -``` - -This use of `+ ` was prescribed by the *Developer* that coded this `+desc` command. What if the -*Player* doesn’t like this syntax though? Do players need to pester the dev to change it? Not -necessarily. While Evennia does not allow the player to build their own multi-descer on the command -line, it does allow for *re-mapping* the command syntax to one they prefer. This is done using the -`nick` command. - -Here’s a nick that changes how to input the command above: - -```text -> nick setdesc $1 $2 $3 $4 = +desc/set $1 + |/|/ + $2 + $3 + |/|/ + $4 -``` - -The string on the left will be matched against your input and if matching, it will be replaced with -the string on the right. The `$`-type tags will store space-separated arguments and put them into -the replacement. The nick allows [shell-like wildcards](http://www.linfo.org/wildcard.html), so you -can use `*`, `?`, `[...]`, `[!...]` etc to match parts of the input. - -The same description as before can now be set as - -```text -> setdesc basic cape footwear attitude -``` - -With the `nick` functionality players can mitigate a lot of syntax dislikes even without the -developer changing the underlying Python code. - -## Next steps - -If you are a *Developer* and are interested in making a more MUSH-like Evennia game, a good start is -to look into the Evennia [Tutorial for a first MUSH-like game](./Tutorial-for-basic-MUSH-like-game.md). -That steps through building a simple little game from scratch and helps to acquaint you with the -various corners of Evennia. There is also the [Tutorial for running roleplaying sessions](./Evennia-for-roleplaying-sessions.md) that can be of interest. - -An important aspect of making things more familiar for *Players* is adding new and tweaking existing -commands. How this is done is covered by the [Tutorial on adding new commands](./Adding-Command-Tutorial.md). You may also find it useful to shop through the `evennia/contrib/` folder. The [Tutorial -world](./Tutorial-World-Introduction.md) is a small single-player quest you can try (it’s not very MUSH- -like but it does show many Evennia concepts in action). Beyond that there are [many more -tutorials](./Tutorials.md) to try out. If you feel you want a more visual overview you can also look at -[Evennia in pictures](https://evennia.blogspot.se/2016/05/evennia-in-pictures.html). - -… And of course, if you need further help you can always drop into the [Evennia chatroom](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) or post a -question in our [forum/mailing list](https://groups.google.com/forum/#%21forum/evennia)! diff --git a/docs/0.9.5/_sources/Evennia-for-roleplaying-sessions.md.txt b/docs/0.9.5/_sources/Evennia-for-roleplaying-sessions.md.txt deleted file mode 100644 index f3fa385321..0000000000 --- a/docs/0.9.5/_sources/Evennia-for-roleplaying-sessions.md.txt +++ /dev/null @@ -1,732 +0,0 @@ -# Evennia for roleplaying sessions - -This tutorial will explain how to set up a realtime or play-by-post tabletop style game using a -fresh Evennia server. - -The scenario is thus: You and a bunch of friends want to play a tabletop role playing game online. -One of you will be the game master and you are all okay with playing using written text. You want -both the ability to role play in real-time (when people happen to be online at the same time) as -well as the ability for people to post when they can and catch up on what happened since they were -last online. - -This is the functionality we will be needing and using: - -* The ability to make one of you the *GM* (game master), with special abilities. -* A *Character sheet* that players can create, view and fill in. It can also be locked so only the -GM can modify it. -* A *dice roller* mechanism, for whatever type of dice the RPG rules require. -* *Rooms*, to give a sense of location and to compartmentalize play going on- This means both -Character movements from location to location and GM explicitly moving them around. -* *Channels*, for easily sending text to all subscribing accounts, regardless of location. -* Account-to-Account *messaging* capability, including sending to multiple recipients -simultaneously, regardless of location. - -We will find most of these things are already part of vanilla Evennia, but that we can expand on the -defaults for our particular use-case. Below we will flesh out these components from start to finish. - -## Starting out - -We will assume you start from scratch. You need Evennia installed, as per the [Getting -Started](./Getting-Started.md) instructions. Initialize a new game directory with `evennia init -`. In this tutorial we assume your game dir is simply named `mygame`. You can use the -default database and keep all other settings to default for now. Familiarize yourself with the -`mygame` folder before continuing. You might want to browse the [First Steps Coding](First-Steps- -Coding) tutorial, just to see roughly where things are modified. - -## The Game Master role - -In brief: - -* Simplest way: Being an admin, just give one account `Admins` permission using the standard `@perm` -command. -* Better but more work: Make a custom command to set/unset the above, while tweaking the Character -to show your renewed GM status to the other accounts. - -### The permission hierarchy - -Evennia has the following [permission hierarchy](./Building-Permissions.md#assigning-permissions) out of -the box: *Players, Helpers, Builders, Admins* and finally *Developers*. We could change these but -then we'd need to update our Default commands to use the changes. We want to keep this simple, so -instead we map our different roles on top of this permission ladder. - -1. `Players` is the permission set on normal players. This is the default for anyone creating a new -account on the server. -2. `Helpers` are like `Players` except they also have the ability to create/edit new help entries. -This could be granted to players who are willing to help with writing lore or custom logs for -everyone. -3. `Builders` is not used in our case since the GM should be the only world-builder. -4. `Admins` is the permission level the GM should have. Admins can do everything builders can -(create/describe rooms etc) but also kick accounts, rename them and things like that. -5. `Developers`-level permission are the server administrators, the ones with the ability to -restart/shutdown the server as well as changing the permission levels. - -> The [superuser](./Building-Permissions.md#the-super-user) is not part of the hierarchy and actually -completely bypasses it. We'll assume server admin(s) will "just" be Developers. - -### How to grant permissions - -Only `Developers` can (by default) change permission level. Only they have access to the `@perm` -command: - -``` -> @perm Yvonne -Permissions on Yvonne: accounts - -> @perm Yvonne = Admins -> @perm Yvonne -Permissions on Yvonne: accounts, admins - -> @perm/del Yvonne = Admins -> @perm Yvonne -Permissions on Yvonne: accounts -``` - -There is no need to remove the basic `Players` permission when adding the higher permission: the -highest will be used. Permission level names are *not* case sensitive. You can also use both plural -and singular, so "Admins" gives the same powers as "Admin". - - -### Optional: Making a GM-granting command - -Use of `@perm` works out of the box, but it's really the bare minimum. Would it not be nice if other -accounts could tell at a glance who the GM is? Also, we shouldn't really need to remember that the -permission level is called "Admins". It would be easier if we could just do `@gm ` and -`@notgm ` and at the same time change something make the new GM status apparent. - -So let's make this possible. This is what we'll do: - -1. We'll customize the default Character class. If an object of this class has a particular flag, -its name will have the string`(GM)` added to the end. -2. We'll add a new command, for the server admin to assign the GM-flag properly. - -#### Character modification - -Let's first start by customizing the Character. We recommend you browse the beginning of the -[Account](./Accounts.md) page to make sure you know how Evennia differentiates between the OOC "Account -objects" (not to be confused with the `Accounts` permission, which is just a string specifying your -access) and the IC "Character objects". - -Open `mygame/typeclasses/characters.py` and modify the default `Character` class: - -```python -# in mygame/typeclasses/characters.py - -# [...] - -class Character(DefaultCharacter): - # [...] - def get_display_name(self, looker, **kwargs): - """ - This method customizes how character names are displayed. We assume - only permissions of types "Developers" and "Admins" require - special attention. - """ - name = self.key - selfaccount = self.account # will be None if we are not puppeted - lookaccount = looker.account # - " - - - if selfaccount and selfaccount.db.is_gm: - # A GM. Show name as name(GM) - name = "%s(GM)" % name - - if lookaccount and \ - (lookaccount.permissions.get("Developers") or lookaccount.db.is_gm): - # Developers/GMs see name(#dbref) or name(GM)(#dbref) - return "%s(#%s)" % (name, self.id) - else: - return name - -``` - -Above, we change how the Character's name is displayed: If the account controlling this Character is -a GM, we attach the string `(GM)` to the Character's name so everyone can tell who's the boss. If we -ourselves are Developers or GM's we will see database ids attached to Characters names, which can -help if doing database searches against Characters of exactly the same name. We base the "gm- -ingness" on having an flag (an [Attribute](./Attributes.md)) named `is_gm`. We'll make sure new GM's -actually get this flag below. - -> **Extra exercise:** This will only show the `(GM)` text on *Characters* puppeted by a GM account, -that is, it will show only to those in the same location. If we wanted it to also pop up in, say, -`who` listings and channels, we'd need to make a similar change to the `Account` typeclass in -`mygame/typeclasses/accounts.py`. We leave this as an exercise to the reader. - -#### New @gm/@ungm command - -We will describe in some detail how to create and add an Evennia [command](./Commands.md) here with the -hope that we don't need to be as detailed when adding commands in the future. We will build on -Evennia's default "mux-like" commands here. - -Open `mygame/commands/command.py` and add a new Command class at the bottom: - -```python -# in mygame/commands/command.py - -from evennia import default_cmds - -# [...] - -import evennia - -class CmdMakeGM(default_cmds.MuxCommand): - """ - Change an account's GM status - - Usage: - @gm - @ungm - - """ - # note using the key without @ means both @gm !gm etc will work - key = "gm" - aliases = "ungm" - locks = "cmd:perm(Developers)" - help_category = "RP" - - def func(self): - "Implement the command" - caller = self.caller - - if not self.args: - caller.msg("Usage: @gm account or @ungm account") - return - - accountlist = evennia.search_account(self.args) # returns a list - if not accountlist: - caller.msg("Could not find account '%s'" % self.args) - return - elif len(accountlist) > 1: - caller.msg("Multiple matches for '%s': %s" % (self.args, accountlist)) - return - else: - account = accountlist[0] - - if self.cmdstring == "gm": - # turn someone into a GM - if account.permissions.get("Admins"): - caller.msg("Account %s is already a GM." % account) - else: - account.permissions.add("Admins") - caller.msg("Account %s is now a GM." % account) - account.msg("You are now a GM (changed by %s)." % caller) - account.character.db.is_gm = True - else: - # @ungm was entered - revoke GM status from someone - if not account.permissions.get("Admins"): - caller.msg("Account %s is not a GM." % account) - else: - account.permissions.remove("Admins") - caller.msg("Account %s is no longer a GM." % account) - account.msg("You are no longer a GM (changed by %s)." % caller) - del account.character.db.is_gm - -``` - -All the command does is to locate the account target and assign it the `Admins` permission if we -used `@gm` or revoke it if using the `@ungm` alias. We also set/unset the `is_gm` Attribute that is -expected by our new `Character.get_display_name` method from earlier. - -> We could have made this into two separate commands or opted for a syntax like `@gm/revoke -`. Instead we examine how this command was called (stored in `self.cmdstring`) in order -to act accordingly. Either way works, practicality and coding style decides which to go with. - -To actually make this command available (only to Developers, due to the lock on it), we add it to -the default Account command set. Open the file `mygame/commands/default_cmdsets.py` and find the -`AccountCmdSet` class: - -```python -# mygame/commands/default_cmdsets.py - -# [...] -from commands.command import CmdMakeGM - -class AccountCmdSet(default_cmds.AccountCmdSet): - # [...] - def at_cmdset_creation(self): - # [...] - self.add(CmdMakeGM()) - -``` - -Finally, issue the `@reload` command to update the server to your changes. Developer-level players -(or the superuser) should now have the `@gm/@ungm` command available. - -## Character sheet - -In brief: - -* Use Evennia's EvTable/EvForm to build a Character sheet -* Tie individual sheets to a given Character. -* Add new commands to modify the Character sheet, both by Accounts and GMs. -* Make the Character sheet lockable by a GM, so the Player can no longer modify it. - -### Building a Character sheet - -There are many ways to build a Character sheet in text, from manually pasting strings together to -more automated ways. Exactly what is the best/easiest way depends on the sheet one tries to create. -We will here show two examples using the *EvTable* and *EvForm* utilities.Later we will create -Commands to edit and display the output from those utilities. - -> Note that due to the limitations of the wiki, no color is used in any of the examples. See [the -text tag documentation](./TextTags.md) for how to add color to the tables and forms. - -#### Making a sheet with EvTable - -[EvTable](github:evennia.utils.evtable) is a text-table generator. It helps with displaying text in -ordered rows and columns. This is an example of using it in code: - -````python -# this can be tried out in a Python shell like iPython - -from evennia.utils import evtable - -# we hardcode these for now, we'll get them as input later -STR, CON, DEX, INT, WIS, CHA = 12, 13, 8, 10, 9, 13 - -table = evtable.EvTable("Attr", "Value", - table = [ - ["STR", "CON", "DEX", "INT", "WIS", "CHA"], - [STR, CON, DEX, INT, WIS, CHA] - ], align='r', border="incols") -```` - -Above, we create a two-column table by supplying the two columns directly. We also tell the table to -be right-aligned and to use the "incols" border type (borders drawns only in between columns). The -`EvTable` class takes a lot of arguments for customizing its look, you can see [some of the possible -keyword arguments here](github:evennia.utils.evtable#evtable__init__). Once you have the `table` you -could also retroactively add new columns and rows to it with `table.add_row()` and -`table.add_column()`: if necessary the table will expand with empty rows/columns to always remain -rectangular. - -The result from printing the above table will be - -```python -table_string = str(table) - -print(table_string) - - Attr | Value -~~~~~~+~~~~~~~ - STR | 12 - CON | 13 - DEX | 8 - INT | 10 - WIS | 9 - CHA | 13 -``` - -This is a minimalistic but effective Character sheet. By combining the `table_string` with other -strings one could build up a reasonably full graphical representation of a Character. For more -advanced layouts we'll look into EvForm next. - -#### Making a sheet with EvForm - -[EvForm](github:evennia.utils.evform) allows the creation of a two-dimensional "graphic" made by -text characters. On this surface, one marks and tags rectangular regions ("cells") to be filled with -content. This content can be either normal strings or `EvTable` instances (see the previous section, -one such instance would be the `table` variable in that example). - -In the case of a Character sheet, these cells would be comparable to a line or box where you could -enter the name of your character or their strength score. EvMenu also easily allows to update the -content of those fields in code (it use EvTables so you rebuild the table first before re-sending it -to EvForm). - -The drawback of EvForm is that its shape is static; if you try to put more text in a region than it -was sized for, the text will be cropped. Similarly, if you try to put an EvTable instance in a field -too small for it, the EvTable will do its best to try to resize to fit, but will eventually resort -to cropping its data or even give an error if too small to fit any data. - -An EvForm is defined in a Python module. Create a new file `mygame/world/charsheetform.py` and -modify it thus: - -````python -#coding=utf-8 - -# in mygame/world/charsheetform.py - -FORMCHAR = "x" -TABLECHAR = "c" - -FORM = """ -.--------------------------------------. -| | -| Name: xxxxxxxxxxxxxx1xxxxxxxxxxxxxxx | -| xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | -| | - >------------------------------------< -| | -| ccccccccccc Advantages: | -| ccccccccccc xxxxxxxxxxxxxxxxxxxxxx | -| ccccccccccc xxxxxxxxxx3xxxxxxxxxxx | -| ccccccccccc xxxxxxxxxxxxxxxxxxxxxx | -| ccccc2ccccc Disadvantages: | -| ccccccccccc xxxxxxxxxxxxxxxxxxxxxx | -| ccccccccccc xxxxxxxxxx4xxxxxxxxxxx | -| ccccccccccc xxxxxxxxxxxxxxxxxxxxxx | -| | -+--------------------------------------+ -""" -```` -The `#coding` statement (which must be put on the very first line to work) tells Python to use the -utf-8 encoding for the file. Using the `FORMCHAR` and `TABLECHAR` we define what single-character we -want to use to "mark" the regions of the character sheet holding cells and tables respectively. -Within each block (which must be separated from one another by at least one non-marking character) -we embed identifiers 1-4 to identify each block. The identifier could be any single character except -for the `FORMCHAR` and `TABLECHAR` - -> You can still use `FORMCHAR` and `TABLECHAR` elsewhere in your sheet, but not in a way that it -would identify a cell/table. The smallest identifiable cell/table area is 3 characters wide -including the identifier (for example `x2x`). - -Now we will map content to this form. - -````python -# again, this can be tested in a Python shell - -# hard-code this info here, later we'll ask the -# account for this info. We will re-use the 'table' -# variable from the EvTable example. - -NAME = "John, the wise old admin with a chip on his shoulder" -ADVANTAGES = "Language-wiz, Intimidation, Firebreathing" -DISADVANTAGES = "Bad body odor, Poor eyesight, Troubled history" - -from evennia.utils import evform - -# load the form from the module -form = evform.EvForm("world/charsheetform.py") - -# map the data to the form -form.map(cells={"1":NAME, "3": ADVANTAGES, "4": DISADVANTAGES}, - tables={"2":table}) -```` - -We create some RP-sounding input and re-use the `table` variable from the previous `EvTable` -example. - -> Note, that if you didn't want to create the form in a separate module you *could* also load it -directly into the `EvForm` call like this: `EvForm(form={"FORMCHAR":"x", "TABLECHAR":"c", "FORM": -formstring})` where `FORM` specifies the form as a string in the same way as listed in the module -above. Note however that the very first line of the `FORM` string is ignored, so start with a `\n`. - -We then map those to the cells of the form: - -````python -print(form) -```` -```` -.--------------------------------------. -| | -| Name: John, the wise old admin with | -| a chip on his shoulder | -| | - >------------------------------------< -| | -| Attr|Value Advantages: | -| ~~~~~+~~~~~ Language-wiz, | -| STR| 12 Intimidation, | -| CON| 13 Firebreathing | -| DEX| 8 Disadvantages: | -| INT| 10 Bad body odor, Poor | -| WIS| 9 eyesight, Troubled | -| CHA| 13 history | -| | -+--------------------------------------+ -```` - -As seen, the texts and tables have been slotted into the text areas and line breaks have been added -where needed. We chose to just enter the Advantages/Disadvantages as plain strings here, meaning -long names ended up split between rows. If we wanted more control over the display we could have -inserted `\n` line breaks after each line or used a borderless `EvTable` to display those as well. - -### Tie a Character sheet to a Character - -We will assume we go with the `EvForm` example above. We now need to attach this to a Character so -it can be modified. For this we will modify our `Character` class a little more: - -```python -# mygame/typeclasses/character.py - -from evennia.utils import evform, evtable - -[...] - -class Character(DefaultCharacter): - [...] - def at_object_creation(self): - "called only once, when object is first created" - # we will use this to stop account from changing sheet - self.db.sheet_locked = False - # we store these so we can build these on demand - self.db.chardata = {"str": 0, - "con": 0, - "dex": 0, - "int": 0, - "wis": 0, - "cha": 0, - "advantages": "", - "disadvantages": ""} - self.db.charsheet = evform.EvForm("world/charsheetform.py") - self.update_charsheet() - - def update_charsheet(self): - """ - Call this to update the sheet after any of the ingoing data - has changed. - """ - data = self.db.chardata - table = evtable.EvTable("Attr", "Value", - table = [ - ["STR", "CON", "DEX", "INT", "WIS", "CHA"], - [data["str"], data["con"], data["dex"], - data["int"], data["wis"], data["cha"]]], - align='r', border="incols") - self.db.charsheet.map(tables={"2": table}, - cells={"1":self.key, - "3":data["advantages"], - "4":data["disadvantages"]}) - -``` - -Use `@reload` to make this change available to all *newly created* Characters. *Already existing* -Characters will *not* have the charsheet defined, since `at_object_creation` is only called once. -The easiest to force an existing Character to re-fire its `at_object_creation` is to use the -`@typeclass` command in-game: - -``` -@typeclass/force -``` - -### Command for Account to change Character sheet - -We will add a command to edit the sections of our Character sheet. Open -`mygame/commands/command.py`. - -```python -# at the end of mygame/commands/command.py - -ALLOWED_ATTRS = ("str", "con", "dex", "int", "wis", "cha") -ALLOWED_FIELDNAMES = ALLOWED_ATTRS + \ - ("name", "advantages", "disadvantages") - -def _validate_fieldname(caller, fieldname): - "Helper function to validate field names." - if fieldname not in ALLOWED_FIELDNAMES: - err = "Allowed field names: %s" % (", ".join(ALLOWED_FIELDNAMES)) - caller.msg(err) - return False - if fieldname in ALLOWED_ATTRS and not value.isdigit(): - caller.msg("%s must receive a number." % fieldname) - return False - return True - -class CmdSheet(MuxCommand): - """ - Edit a field on the character sheet - - Usage: - @sheet field value - - Examples: - @sheet name Ulrik the Warrior - @sheet dex 12 - @sheet advantages Super strength, Night vision - - If given without arguments, will view the current character sheet. - - Allowed field names are: - name, - str, con, dex, int, wis, cha, - advantages, disadvantages - - """ - - key = "sheet" - aliases = "editsheet" - locks = "cmd: perm(Players)" - help_category = "RP" - - def func(self): - caller = self.caller - if not self.args or len(self.args) < 2: - # not enough arguments. Display the sheet - if sheet: - caller.msg(caller.db.charsheet) - else: - caller.msg("You have no character sheet.") - return - - # if caller.db.sheet_locked: - caller.msg("Your character sheet is locked.") - return - - # split input by whitespace, once - fieldname, value = self.args.split(None, 1) - fieldname = fieldname.lower() # ignore case - - if not _validate_fieldnames(caller, fieldname): - return - if fieldname == "name": - self.key = value - else: - caller.chardata[fieldname] = value - caller.update_charsheet() - caller.msg("%s was set to %s." % (fieldname, value)) - -``` - -Most of this command is error-checking to make sure the right type of data was input. Note how the -`sheet_locked` Attribute is checked and will return if not set. - -This command you import into `mygame/commands/default_cmdsets.py` and add to the `CharacterCmdSet`, -in the same way the `@gm` command was added to the `AccountCmdSet` earlier. - -### Commands for GM to change Character sheet - -Game masters use basically the same input as Players do to edit a character sheet, except they can -do it on other players than themselves. They are also not stopped by any `sheet_locked` flags. - -```python -# continuing in mygame/commands/command.py - -class CmdGMsheet(MuxCommand): - """ - GM-modification of char sheets - - Usage: - @gmsheet character [= fieldname value] - - Switches: - lock - lock the character sheet so the account - can no longer edit it (GM's still can) - unlock - unlock character sheet for Account - editing. - - Examples: - @gmsheet Tom - @gmsheet Anna = str 12 - @gmsheet/lock Tom - - """ - key = "gmsheet" - locks = "cmd: perm(Admins)" - help_category = "RP" - - def func(self): - caller = self.caller - if not self.args: - caller.msg("Usage: @gmsheet character [= fieldname value]") - - if self.rhs: - # rhs (right-hand-side) is set only if a '=' - # was given. - if len(self.rhs) < 2: - caller.msg("You must specify both a fieldname and value.") - return - fieldname, value = self.rhs.split(None, 1) - fieldname = fieldname.lower() - if not _validate_fieldname(caller, fieldname): - return - charname = self.lhs - else: - # no '=', so we must be aiming to look at a charsheet - fieldname, value = None, None - charname = self.args.strip() - - character = caller.search(charname, global_search=True) - if not character: - return - - if "lock" in self.switches: - if character.db.sheet_locked: - caller.msg("The character sheet is already locked.") - else: - character.db.sheet_locked = True - caller.msg("%s can no longer edit their character sheet." % character.key) - elif "unlock" in self.switches: - if not character.db.sheet_locked: - caller.msg("The character sheet is already unlocked.") - else: - character.db.sheet_locked = False - caller.msg("%s can now edit their character sheet." % character.key) - - if fieldname: - if fieldname == "name": - character.key = value - else: - character.db.chardata[fieldname] = value - character.update_charsheet() - caller.msg("You set %s's %s to %s." % (character.key, fieldname, value) - else: - # just display - caller.msg(character.db.charsheet) -``` - -The `@gmsheet` command takes an additional argument to specify which Character's character sheet to -edit. It also takes `/lock` and `/unlock` switches to block the Player from tweaking their sheet. - -Before this can be used, it should be added to the default `CharacterCmdSet` in the same way as the -normal `@sheet`. Due to the lock set on it, this command will only be available to `Admins` (i.e. -GMs) or higher permission levels. - -## Dice roller - -Evennia's *contrib* folder already comes with a full dice roller. To add it to the game, simply -import `contrib.dice.CmdDice` into `mygame/commands/default_cmdsets.py` and add `CmdDice` to the -`CharacterCmdset` as done with other commands in this tutorial. After a `@reload` you will be able -to roll dice using normal RPG-style format: - -``` -roll 2d6 + 3 -7 -``` - -Use `help dice` to see what syntax is supported or look at `evennia/contrib/dice.py` to see how it's -implemented. - -## Rooms - -Evennia comes with rooms out of the box, so no extra work needed. A GM will automatically have all -needed building commands available. A fuller go-through is found in the [Building -tutorial](./Building-Quickstart.md). Here are some useful highlights: - -* `@dig roomname;alias = exit_there;alias, exit_back;alias` - this is the basic command for digging -a new room. You can specify any exit-names and just enter the name of that exit to go there. -* `@tunnel direction = roomname` - this is a specialized command that only accepts directions in the -cardinal directions (n,ne,e,se,s,sw,w,nw) as well as in/out and up/down. It also automatically -builds "matching" exits back in the opposite direction. -* `@create/drop objectname` - this creates and drops a new simple object in the current location. -* `@desc obj` - change the look-description of the object. -* `@tel object = location` - teleport an object to a named location. -* `@search objectname` - locate an object in the database. - -> TODO: Describe how to add a logging room, that logs says and poses to a log file that people can -access after the fact. - -## Channels - -Evennia comes with [Channels](./Communications.md#channels) in-built and they are described fully in the -documentation. For brevity, here are the relevant commands for normal use: - -* `@ccreate new_channel;alias;alias = short description` - Creates a new channel. -* `addcom channel` - join an existing channel. Use `addcom alias = channel` to add a new alias you -can use to talk to the channel, as many as desired. -* `delcom alias or channel` - remove an alias from a channel or, if the real channel name is given, -unsubscribe completely. -* `@channels` lists all available channels, including your subscriptions and any aliases you have -set up for them. - -You can read channel history: if you for example are chatting on the `public` channel you can do -`public/history` to see the 20 last posts to that channel or `public/history 32` to view twenty -posts backwards, starting with the 32nd from the end. - -## PMs - -To send PMs to one another, players can use the `@page` (or `tell`) command: - -``` -page recipient = message -page recipient, recipient, ... = message -``` - -Players can use `page` alone to see the latest messages. This also works if they were not online -when the message was sent. diff --git a/docs/0.9.5/_sources/Execute-Python-Code.md.txt b/docs/0.9.5/_sources/Execute-Python-Code.md.txt deleted file mode 100644 index a37c080022..0000000000 --- a/docs/0.9.5/_sources/Execute-Python-Code.md.txt +++ /dev/null @@ -1,120 +0,0 @@ -# Execute Python Code - - -The `@py` command supplied with the default command set of Evennia allows you to execute Python -commands directly from inside the game. An alias to `@py` is simply "`!`". *Access to the `@py` -command should be severely restricted*. This is no joke - being able to execute arbitrary Python -code on the server is not something you should entrust to just anybody. - - @py 1+2 - <<< 3 - -## Available variables - -A few local variables are made available when running `@py`. These offer entry into the running -system. - -- **self** / **me** - the calling object (i.e. you) -- **here** - the current caller's location -- **obj** - a dummy [Object](./Objects.md) instance -- **evennia** - Evennia's [flat API](./Evennia-API.md) - through this you can access all of Evennia. - -For accessing other objects in the same room you need to use `self.search(name)`. For objects in -other locations, use one of the `evennia.search_*` methods. See [below](./Execute-Python-Code.md#finding- -objects). - -## Returning output - -This is an example where we import and test one of Evennia's utilities found in -`src/utils/utils.py`, but also accessible through `ev.utils`: - - @py from ev import utils; utils.time_format(33333) - <<< Done. - -Note that we didn't get any return value, all we where told is that the code finished executing -without error. This is often the case in more complex pieces of code which has no single obvious -return value. To see the output from the `time_format()` function we need to tell the system to -echo it to us explicitly with `self.msg()`. - - @py from ev import utils; self.msg(str(utils.time_format(33333))) - 09:15 - <<< Done. - -> Warning: When using the `msg` function wrap our argument in `str()` to convert it into a string -above. This is not strictly necessary for most types of data (Evennia will usually convert to a -string behind the scenes for you). But for *lists* and *tuples* you will be confused by the output -if you don't wrap them in `str()`: only the first item of the iterable will be returned. This is -because doing `msg(text)` is actually just a convenience shortcut; the full argument that `msg` -accepts is something called an *outputfunc* on the form `(cmdname, (args), {kwargs})` (see [the -message path](./Messagepath.md) for more info). Sending a list/tuple confuses Evennia to think you are -sending such a structure. Converting it to a string however makes it clear it should just be -displayed as-is. - -If you were to use Python's standard `print`, you will see the result in your current `stdout` (your -terminal by default, otherwise your log file). - -## Finding objects - -A common use for `@py` is to explore objects in the database, for debugging and performing specific -operations that are not covered by a particular command. - -Locating an object is best done using `self.search()`: - - @py self.search("red_ball") - <<< Ball - - @py self.search("red_ball").db.color = "red" - <<< Done. - - @py self.search("red_ball").db.color - <<< red - -`self.search()` is by far the most used case, but you can also search other database tables for -other Evennia entities like scripts or configuration entities. To do this you can use the generic -search entries found in `ev.search_*`. - - @py evennia.search_script("sys_game_time") - <<< [] - -(Note that since this becomes a simple statement, we don't have to wrap it in `self.msg()` to get -the output). You can also use the database model managers directly (accessible through the `objects` -properties of database models or as `evennia.managers.*`). This is a bit more flexible since it -gives you access to the full range of database search methods defined in each manager. - - @py evennia.managers.scripts.script_search("sys_game_time") - <<< [] - -The managers are useful for all sorts of database studies. - - @py ev.managers.configvalues.all() - <<< [, , ...] - -## Testing code outside the game - -`@py` has the advantage of operating inside a running server (sharing the same process), where you -can test things in real time. Much of this *can* be done from the outside too though. - -In a terminal, cd to the top of your game directory (this bit is important since we need access to -your config file) and run - - evennia shell - -Your default Python interpreter will start up, configured to be able to work with and import all -modules of your Evennia installation. From here you can explore the database and test-run individual -modules as desired. - -It's recommended that you get a more fully featured Python interpreter like -[iPython](http://ipython.scipy.org/moin/). If you use a virtual environment, you can just get it -with `pip install ipython`. IPython allows you to better work over several lines, and also has a lot -of other editing features, such as tab-completion and `__doc__`-string reading. - - $ evennia shell - - IPython 0.10 -- An enhanced Interactive Python - ... - - In [1]: import evennia - In [2]: evennia.managers.objects.all() - Out[3]: [, , ...] - -See the page about the [Evennia-API](./Evennia-API.md) for more things to explore. diff --git a/docs/0.9.5/_sources/First-Steps-Coding.md.txt b/docs/0.9.5/_sources/First-Steps-Coding.md.txt deleted file mode 100644 index a29f3d0113..0000000000 --- a/docs/0.9.5/_sources/First-Steps-Coding.md.txt +++ /dev/null @@ -1,292 +0,0 @@ -# First Steps Coding - - -This section gives a brief step-by-step introduction on how to set up Evennia for the first time so -you can modify and overload the defaults easily. You should only need to do these steps once. It -also walks through you making your first few tweaks. - -Before continuing, make sure you have Evennia installed and running by following the [Getting -Started](./Getting-Started.md) instructions. You should have initialized a new game folder with the -`evennia --init foldername` command. We will in the following assume this folder is called -"mygame". - -It might be a good idea to eye through the brief [Coding Introduction](./Coding-Introduction.md) too -(especially the recommendations in the section about the evennia "flat" API and about using `evennia -shell` will help you here and in the future). - -To follow this tutorial you also need to know the basics of operating your computer's -terminal/command line. You also need to have a text editor to edit and create source text files. -There are plenty of online tutorials on how to use the terminal and plenty of good free text -editors. We will assume these things are already familiar to you henceforth. - - -## Your First Changes - -Below are some first things to try with your new custom modules. You can test these to get a feel -for the system. See also [Tutorials](./Tutorials.md) for more step-by-step help and special cases. - -### Tweak Default Character - -We will add some simple rpg attributes to our default Character. In the next section we will follow -up with a new command to view those attributes. - -1. Edit `mygame/typeclasses/characters.py` and modify the `Character` class. The -`at_object_creation` method also exists on the `DefaultCharacter` parent and will overload it. The -`get_abilities` method is unique to our version of `Character`. - - ```python - class Character(DefaultCharacter): - # [...] - def at_object_creation(self): - """ - Called only at initial creation. This is a rather silly - example since ability scores should vary from Character to - Character and is usually set during some character - generation step instead. - """ - #set persistent attributes - self.db.strength = 5 - self.db.agility = 4 - self.db.magic = 2 - - def get_abilities(self): - """ - Simple access method to return ability - scores as a tuple (str,agi,mag) - """ - return self.db.strength, self.db.agility, self.db.magic - ``` - -1. [Reload](./Start-Stop-Reload.md) the server (you will still be connected to the game after doing -this). Note that if you examine *yourself* you will *not* see any new Attributes appear yet. Read -the next section to understand why. - -#### Updating Yourself - -It's important to note that the new [Attributes](./Attributes.md) we added above will only be stored on -*newly* created characters. The reason for this is simple: The `at_object_creation` method, where we -added those Attributes, is per definition only called when the object is *first created*, then never -again. This is usually a good thing since those Attributes may change over time - calling that hook -would reset them back to start values. But it also means that your existing character doesn't have -them yet. You can see this by calling the `get_abilities` hook on yourself at this point: - -``` -# (you have to be superuser to use @py) -@py self.get_abilities() -<<< (None, None, None) -``` - -This is easily remedied. - -``` -@update self -``` - -This will (only) re-run `at_object_creation` on yourself. You should henceforth be able to get the -abilities successfully: - -``` -@py self.get_abilities() -<<< (5, 4, 2) -``` - -This is something to keep in mind if you start building your world before your code is stable - -startup-hooks will not (and should not) automatically run on *existing* objects - you have to update -your existing objects manually. Luckily this is a one-time thing and pretty simple to do. If the -typeclass you want to update is in `typeclasses.myclass.MyClass`, you can do the following (e.g. -from `evennia shell`): - -```python -from typeclasses.myclass import MyClass -# loop over all MyClass instances in the database -# and call .swap_typeclass on them -for obj in MyClass.objects.all(): - obj.swap_typeclass(MyClass, run_start_hooks="at_object_creation") -``` - -Using `swap_typeclass` to the same typeclass we already have will re-run the creation hooks (this is -what the `@update` command does under the hood). From in-game you can do the same with `@py`: - -``` -@py typeclasses.myclass import MyClass;[obj.swap_typeclass(MyClass) for obj in -MyClass.objects.all()] -``` - -See the [Object Typeclass tutorial](./Adding-Object-Typeclass-Tutorial.md) for more help and the -[Typeclasses](./Typeclasses.md) and [Attributes](./Attributes.md) page for detailed documentation about -Typeclasses and Attributes. - -#### Troubleshooting: Updating Yourself - -One may experience errors for a number of reasons. Common beginner errors are spelling mistakes, -wrong indentations or code omissions leading to a `SyntaxError`. Let's say you leave out a colon -from the end of a class function like so: ```def at_object_creation(self)```. The client will reload -without issue. *However*, if you look at the terminal/console (i.e. not in-game), you will see -Evennia complaining (this is called a *traceback*): - -``` -Traceback (most recent call last): -File "C:\mygame\typeclasses\characters.py", line 33 - def at_object_creation(self) - ^ -SyntaxError: invalid syntax -``` - -Evennia will still be restarting and following the tutorial, doing `@py self.get_abilities()` will -return the right response `(None, None, None)`. But when attempting to `@typeclass/force self` you -will get this response: - -```python - AttributeError: 'DefaultObject' object has no attribute 'get_abilities' -``` - -The full error will show in the terminal/console but this is confusing since you did add -`get_abilities` before. Note however what the error says - you (`self`) should be a `Character` but -the error talks about `DefaultObject`. What has happened is that due to your unhandled `SyntaxError` -earlier, Evennia could not load the `character.py` module at all (it's not valid Python). Rather -than crashing, Evennia handles this by temporarily falling back to a safe default - `DefaultObject` -- in order to keep your MUD running. Fix the original `SyntaxError` and reload the server. Evennia -will then be able to use your modified `Character` class again and things should work. - -> Note: Learning how to interpret an error traceback is a critical skill for anyone learning Python. -Full tracebacks will appear in the terminal/Console you started Evennia from. The traceback text can -sometimes be quite long, but you are usually just looking for the last few lines: The description of -the error and the filename + line number for where the error occurred. In the example above, we see -it's a `SyntaxError` happening at `line 33` of `mygame\typeclasses\characters.py`. In this case it -even points out *where* on the line it encountered the error (the missing colon). Learn to read -tracebacks and you'll be able to resolve the vast majority of common errors easily. - -### Add a New Default Command - -The `@py` command used above is only available to privileged users. We want any player to be able to -see their stats. Let's add a new [command](./Commands.md) to list the abilities we added in the previous -section. - -1. Open `mygame/commands/command.py`. You could in principle put your command anywhere but this -module has all the imports already set up along with some useful documentation. Make a new class at -the bottom of this file: - - ```python - class CmdAbilities(BaseCommand): - """ - List abilities - - Usage: - abilities - - Displays a list of your current ability values. - """ - key = "abilities" - aliases = ["abi"] - lock = "cmd:all()" - help_category = "General" - - def func(self): - """implements the actual functionality""" - - str, agi, mag = self.caller.get_abilities() - string = "STR: %s, AGI: %s, MAG: %s" % (str, agi, mag) - self.caller.msg(string) - ``` - -1. Next you edit `mygame/commands/default_cmdsets.py` and add a new import to it near the top: - - ```python - from commands.command import CmdAbilities - ``` - -1. In the `CharacterCmdSet` class, add the following near the bottom (it says where): - - ```python - self.add(CmdAbilities()) - ``` - -1. [Reload](./Start-Stop-Reload.md) the server (noone will be disconnected by doing this). - -You (and anyone else) should now be able to use `abilities` (or its alias `abi`) as part of your -normal commands in-game: - -``` -abilities -STR: 5, AGI: 4, MAG: 2 -``` - -See the [Adding a Command tutorial](./Adding-Command-Tutorial.md) for more examples and the -[Commands](./Commands.md) section for detailed documentation about the Command system. - -### Make a New Type of Object - -Let's test to make a new type of object. This example is an "wise stone" object that returns some -random comment when you look at it, like this: - - > look stone - - A very wise stone - - This is a very wise old stone. - It grumbles and says: 'The world is like a rock of chocolate.' - -1. Create a new module in `mygame/typeclasses/`. Name it `wiseobject.py` for this example. -1. In the module import the base `Object` (`typeclasses.objects.Object`). This is empty by default, -meaning it is just a proxy for the default `evennia.DefaultObject`. -1. Make a new class in your module inheriting from `Object`. Overload hooks on it to add new -functionality. Here is an example of how the file could look: - - ```python - from random import choice - from typeclasses.objects import Object - - class WiseObject(Object): - """ - An object speaking when someone looks at it. We - assume it looks like a stone in this example. - """ - def at_object_creation(self): - """Called when object is first created""" - self.db.wise_texts = \ - ["Stones have feelings too.", - "To live like a stone is to not have lived at all.", - "The world is like a rock of chocolate."] - - def return_appearance(self, looker): - """ - Called by the look command. We want to return - a wisdom when we get looked at. - """ - # first get the base string from the - # parent's return_appearance. - string = super().return_appearance(looker) - wisewords = "\n\nIt grumbles and says: '%s'" - wisewords = wisewords % choice(self.db.wise_texts) - return string + wisewords - ``` - -1. Check your code for bugs. Tracebacks will appear on your command line or log. If you have a grave -Syntax Error in your code, the source file itself will fail to load which can cause issues with the -entire cmdset. If so, fix your bug and [reload the server from the command line](./Start-Stop-Reload.md) -(noone will be disconnected by doing this). -1. Use `@create/drop stone:wiseobject.WiseObject` to create a talkative stone. If the `@create` -command spits out a warning or cannot find the typeclass (it will tell you which paths it searched), -re-check your code for bugs and that you gave the correct path. The `@create` command starts looking -for Typeclasses in `mygame/typeclasses/`. -1. Use `look stone` to test. You will see the default description ("You see nothing special") -followed by a random message of stony wisdom. Use `@desc stone = This is a wise old stone.` to make -it look nicer. See the [Builder Docs](./Builder-Docs.md) for more information. - -Note that `at_object_creation` is only called once, when the stone is first created. If you make -changes to this method later, already existing stones will not see those changes. As with the -`Character` example above you can use `@typeclass/force` to tell the stone to re-run its -initialization. - -The `at_object_creation` is a special case though. Changing most other aspects of the typeclass does -*not* require manual updating like this - you just need to `@reload` to have all changes applied -automatically to all existing objects. - -## Where to Go From Here? - -There are more [Tutorials](./Tutorials.md), including one for building a [whole little MUSH-like -game](./Tutorial-for-basic-MUSH-like-game.md) - that is instructive also if you have no interest in -MUSHes per se. A good idea is to also get onto the [IRC -chat](http://webchat.freenode.net/?channels=evennia) and the [mailing -list](https://groups.google.com/forum/#!forum/evennia) to get in touch with the community and other -developers. diff --git a/docs/0.9.5/_sources/Game-Planning.md.txt b/docs/0.9.5/_sources/Game-Planning.md.txt deleted file mode 100644 index d20ca900d1..0000000000 --- a/docs/0.9.5/_sources/Game-Planning.md.txt +++ /dev/null @@ -1,214 +0,0 @@ -# Game Planning - - -So you have Evennia up and running. You have a great game idea in mind. Now it's time to start -cracking! But where to start? Here are some ideas for a workflow. Note that the suggestions on this -page are just that - suggestions. Also, they are primarily aimed at a lone hobby designer or a small -team developing a game in their free time. - -Below are some minimal steps for getting the first version of a new game world going with players. -It's worth to at least make the attempt to do these steps in order even if you are itching to jump -ahead in the development cycle. On the other hand, you should also make sure to keep your work fun -for you, or motivation will falter. Making a full game is a lot of work as it is, you'll need all -your motivation to make it a reality. - -Remember that *99.99999% of all great game ideas never lead to a game*. Especially not to an online -game that people can actually play and enjoy. So our first all overshadowing goal is to beat those -odds and get *something* out the door! Even if it's a scaled-down version of your dream game, -lacking many "must-have" features! It's better to get it out there and expand on it later than to -code in isolation forever until you burn out, lose interest or your hard drive crashes. - -Like is common with online games, getting a game out the door does not mean you are going to be -"finished" with the game - most MUDs add features gradually over the course of years - it's often -part of the fun! - -## Planning (step 1) - -This is what you do before having coded a single line or built a single room. Many prospective game -developers are very good at *parts* of this process, namely in defining what their world is "about": -The theme, the world concept, cool monsters and so on. It is by all means very important to define -what is the unique appeal of your game. But it's unfortunately not enough to make your game a -reality. To do that you must also have an idea of how to actually map those great ideas onto -Evennia. - -A good start is to begin by planning out the basic primitives of the game and what they need to be -able to do. Below are a far-from-complete list of examples (and for your first version you should -definitely try for a much shorter list): - -### Systems - -These are the behind-the-scenes features that exist in your game, often without being represented by -a specific in-game object. - -- Should your game rules be enforced by coded systems or are you planning for human game masters to -run and arbitrate rules? -- What are the actual mechanical game rules? How do you decide if an action succeeds or fails? What -"rolls" does the game need to be able to do? Do you base your game off an existing system or make up -your own? -- Does the flow of time matter in your game - does night and day change? What about seasons? Maybe -your magic system is affected by the phase of the moon? -- Do you want changing, global weather? This might need to operate in tandem over a large number of -rooms. -- Do you want a game-wide economy or just a simple barter system? Or no formal economy at all? -- Should characters be able to send mail to each other in-game? -- Should players be able to post on Bulletin boards? -- What is the staff hierarchy in your game? What powers do you want your staff to have? -- What should a Builder be able to build and what commands do they need in order to do that? -- etc. - -### Rooms - -Consider the most basic room in your game. - - - Is a simple description enough or should the description be able to change (such as with time, by -light conditions, weather or season)? - - Should the room have different statuses? Can it have smells, sounds? Can it be affected by -dramatic weather, fire or magical effects? If so, how would this affect things in the room? Or are -these things something admins/game masters should handle manually? - - Can objects be hidden in the room? Can a person hide in the room? How does the room display this? - - etc. - -### Objects - -Consider the most basic (non-player-controlled) object in your game. - -- How numerous are your objects? Do you want large loot-lists or are objects just role playing props -created on demand? -- Does the game use money? If so, is each coin a separate object or do you just store a bank account -value? -- What about multiple identical objects? Do they form stacks and how are those stacks handled in -that case? -- Does an object have weight or volume (so you cannot carry an infinite amount of them)? -- Can objects be broken? If so, does it have a health value? Is burning it causing the same damage -as smashing it? Can it be repaired? -- Is a weapon a specific type of object or are you supposed to be able to fight with a chair too? -Can you fight with a flower or piece of paper as well? -- NPCs/mobs are also objects. Should they just stand around or should they have some sort of AI? -- Are NPCs/mobs differet entities? How is an Orc different from a Kobold, in code - are they the -same object with different names or completely different types of objects, with custom code? -- Should there be NPCs giving quests? If so, how would you track quest status and what happens when -multiple players try to do the same quest? Do you use instances or some other mechanism? -- etc. - -### Characters - -These are the objects controlled directly by Players. - -- Can players have more than one Character active at a time or are they allowed to multi-play? -- How does a Player create their Character? A Character-creation screen? Answering questions? -Filling in a form? -- Do you want to use classes (like "Thief", "Warrior" etc) or some other system, like Skill-based? -- How do you implement different "classes" or "races"? Are they separate types of objects or do you -simply load different stats on a basic object depending on what the Player wants? -- If a Character can hide in a room, what skill will decide if they are detected? -- What skill allows a Character to wield a weapon and hit? Do they need a special skill to wield a -chair rather than a sword? -- Does a Character need a Strength attribute to tell how much they can carry or which objects they -can smash? -- What does the skill tree look like? Can a Character gain experience to improve? By killing -enemies? Solving quests? By roleplaying? -- etc. - -A MUD's a lot more involved than you would think and these things hang together in a complex web. It -can easily become overwhelming and it's tempting to want *all* functionality right out of the door. -Try to identify the basic things that "make" your game and focus *only* on them for your first -release. Make a list. Keep future expansions in mind but limit yourself. - -## Coding (step 2) - -This is the actual work of creating the "game" part of your game. Many "game-designer" types tend to -gloss over this bit and jump directly to **World Building**. Vice versa, many "game-coder" types -tend to jump directly to this part without doing the **Planning** first. Neither way is good and -*will* lead to you having to redo all your hard work at least once, probably more. - -Evennia's [Developer Central](./Developer-Central.md) tries to help you with this bit of development. We -also have a slew of [Tutorials](./Tutorials.md) with worked examples. Evennia tries hard to make this -part easier for you, but there is no way around the fact that if you want anything but a very basic -Talker-type game you *will* have to bite the bullet and code your game (or find a coder willing to -do it for you). - -Even if you won't code anything yourself, as a designer you need to at least understand the basic -paradigms of Evennia, such as [Objects](./Objects.md), [Commands](./Commands.md) and [Scripts](./Scripts.md) and -how they hang together. We recommend you go through the [Tutorial World](Tutorial-World- -Introduction) in detail (as well as glancing at its code) to get at least a feel for what is -involved behind the scenes. You could also look through the tutorial for [building a game from -scratch](./Tutorial-for-basic-MUSH-like-game.md). - -During Coding you look back at the things you wanted during the **Planning** phase and try to -implement them. Don't be shy to update your plans if you find things easier/harder than you thought. -The earlier you revise problems, the easier they will be to fix. - -A good idea is to host your code online (publicly or privately) using version control. Not only will -this make it easy for multiple coders to collaborate (and have a bug-tracker etc), it also means -your work is backed up at all times. The [Version Control](./Version-Control.md) tutorial has -instructions for setting up a sane developer environment with proper version control. - -### "Tech Demo" Building - -This is an integral part of your Coding. It might seem obvious to experienced coders, but it cannot -be emphasized enough that you should *test things on a small scale* before putting your untested -code into a large game-world. The earlier you test, the easier and cheaper it will be to fix bugs -and even rework things that didn't work out the way you thought they would. You might even have to -go back to the **Planning** phase if your ideas can't handle their meet with reality. - -This means building singular in-game examples. Make one room and one object of each important type -and test so they work correctly in isolation. Then add more if they are supposed to interact with -each other in some way. Build a small series of rooms to test how mobs move around ... and so on. In -short, a test-bed for your growing code. It should be done gradually until you have a fully -functioning (if not guaranteed bug-free) miniature tech demo that shows *all* the features you want -in the first release of your game. There does not need to be any game play or even a theme to your -tests, this is only for you and your co-coders to see. The more testing you do on this small scale, -the less headaches you will have in the next phase. - -## World Building (step 3) - -Up until this point we've only had a few tech-demo objects in the database. This step is the act of -populating the database with a larger, thematic world. Too many would-be developers jump to this -stage too soon (skipping the **Coding** or even **Planning** stages). What if the rooms you build -now doesn't include all the nice weather messages the code grows to support? Or the way you store -data changes under the hood? Your building work would at best require some rework and at worst you -would have to redo the whole thing. And whereas Evennia's typeclass system does allow you to edit -the properties of existing objects, some hooks are only called at object creation ... Suffice to -say you are in for a *lot* of unnecessary work if you build stuff en masse without having the -underlying code systems in some reasonable shape first. - -So before starting to build, the "game" bit (**Coding** + **Testing**) should be more or less -**complete**, *at least to the level of your initial release*. - -Before starting to build, you should also plan ahead again. Make sure it is clear to yourself and -your eventual builders just which parts of the world you want for your initial release. Establish -for everyone which style, quality and level of detail you expect. Your goal should *not* be to -complete your entire world in one go. You want just enough to make the game's "feel" come across. -You want a minimal but functioning world where the intended game play can be tested and roughly -balanced. You can always add new areas later. - -During building you get free and extensive testing of whatever custom build commands and systems you -have made at this point. Since Building often involves different people than those Coding, you also -get a chance to hear if some things are hard to understand or non-intuitive. Make sure to respond -to this feedback. - - -## Alpha Release - -As mentioned, don't hold onto your world more than necessary. *Get it out there* with a huge *Alpha* -flag and let people try it! Call upon your alpha-players to try everything - they *will* find ways -to break your game in ways that you never could have imagined. In Alpha you might be best off to -focus on inviting friends and maybe other MUD developers, people who you can pester to give proper -feedback and bug reports (there *will* be bugs, there is no way around it). Follow the quick -instructions for [Online Setup](./Online-Setup.md) to make your game visible online. If you hadn't -already, make sure to put up your game on the [Evennia game index](http://games.evennia.com/) so -people know it's in the works (actually, even pre-alpha games are allowed in the index so don't be -shy)! - -## Beta Release/Perpetual Beta - -Once things stabilize in Alpha you can move to *Beta* and let more people in. Many MUDs are in -[perpetual beta](http://en.wikipedia.org/wiki/Perpetual_beta), meaning they are never considered -"finished", but just repeat the cycle of Planning, Coding, Testing and Building over and over as new -features get implemented or Players come with suggestions. As the game designer it is now up to you -to gradually perfect your vision. - -## Congratulate yourself! - -You are worthy of a celebration since at this point you have joined the small, exclusive crowd who -have made their dream game a reality! diff --git a/docs/0.9.5/_sources/Gametime-Tutorial.md.txt b/docs/0.9.5/_sources/Gametime-Tutorial.md.txt deleted file mode 100644 index eb10504650..0000000000 --- a/docs/0.9.5/_sources/Gametime-Tutorial.md.txt +++ /dev/null @@ -1,302 +0,0 @@ -# Gametime Tutorial - - -A lot of games use a separate time system we refer to as *game time*. This runs in parallel to what -we usually think of as *real time*. The game time might run at a different speed, use different -names for its time units or might even use a completely custom calendar. You don't need to rely on a -game time system at all. But if you do, Evennia offers basic tools to handle these various -situations. This tutorial will walk you through these features. - -### A game time with a standard calendar - -Many games let their in-game time run faster or slower than real time, but still use our normal -real-world calendar. This is common both for games set in present day as well as for games in -historical or futuristic settings. Using a standard calendar has some advantages: - -- Handling repetitive actions is much easier, since converting from the real time experience to the -in-game perceived one is easy. -- The intricacies of the real world calendar, with leap years and months of different length etc are -automatically handled by the system. - -Evennia's game time features assume a standard calendar (see the relevant section below for a custom -calendar). - -#### Setting up game time for a standard calendar - -All is done through the settings. Here are the settings you should use if you want a game time with -a standard calendar: - -```python -# in a file settings.py in mygame/server/conf -# The time factor dictates if the game world runs faster (timefactor>1) -# or slower (timefactor<1) than the real world. -TIME_FACTOR = 2.0 - -# The starting point of your game time (the epoch), in seconds. -# In Python a value of 0 means Jan 1 1970 (use negatives for earlier -# start date). This will affect the returns from the utils.gametime -# module. -TIME_GAME_EPOCH = None -``` - -By default, the game time runs twice as fast as the real time. You can set the time factor to be 1 -(the game time would run exactly at the same speed than the real time) or lower (the game time will -be slower than the real time). Most games choose to have the game time spinning faster (you will -find some games that have a time factor of 60, meaning the game time runs sixty times as fast as the -real time, a minute in real time would be an hour in game time). - -The epoch is a slightly more complex setting. It should contain a number of seconds that would -indicate the time your game started. As indicated, an epoch of 0 would mean January 1st, 1970. If -you want to set your time in the future, you just need to find the starting point in seconds. There -are several ways to do this in Python, this method will show you how to do it in local time: - -```python -# We're looking for the number of seconds representing -# January 1st, 2020 -from datetime import datetime -import time -start = datetime(2020, 1, 1) -time.mktime(start.timetuple()) -``` - -This should return a huge number - the number of seconds since Jan 1 1970. Copy that directly into -your settings (editing `server/conf/settings.py`): - -```python -# in a file settings.py in mygame/server/conf -TIME_GAME_EPOCH = 1577865600 -``` - -Reload the game with `@reload`, and then use the `@time` command. You should see something like -this: - -``` -+----------------------------+-------------------------------------+ -| Server time | | -+~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ -| Current uptime | 20 seconds | -| Total runtime | 1 day, 1 hour, 55 minutes | -| First start | 2017-02-12 15:47:50.565000 | -| Current time | 2017-02-13 17:43:10.760000 | -+----------------------------+-------------------------------------+ -| In-Game time | Real time x 2 | -+~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ -| Epoch (from settings) | 2020-01-01 00:00:00 | -| Total time passed: | 1 day, 17 hours, 34 minutes | -| Current time | 2020-01-02 17:34:55.430000 | -+----------------------------+-------------------------------------+ -``` - -The line that is most relevant here is the game time epoch. You see it shown at 2020-01-01. From -this point forward, the game time keeps increasing. If you keep typing `@time`, you'll see the game -time updated correctly... and going (by default) twice as fast as the real time. - -#### Time-related events - -The `gametime` utility also has a way to schedule game-related events, taking into account your game -time, and assuming a standard calendar (see below for the same feature with a custom calendar). For -instance, it can be used to have a specific message every (in-game) day at 6:00 AM showing how the -sun rises. - -The function `schedule()` should be used here. It will create a [script](./Scripts.md) with some -additional features to make sure the script is always executed when the game time matches the given -parameters. - -The `schedule` function takes the following arguments: - -- The *callback*, a function to be called when time is up. -- The keyword `repeat` (`False` by default) to indicate whether this function should be called -repeatedly. -- Additional keyword arguments `sec`, `min`, `hour`, `day`, `month` and `year` to describe the time -to schedule. If the parameter isn't given, it assumes the current time value of this specific unit. - -Here is a short example for making the sun rise every day: - -```python -# in a file ingame_time.py in mygame/world/ - -from evennia.utils import gametime -from typeclasses.rooms import Room - -def at_sunrise(): - """When the sun rises, display a message in every room.""" - # Browse all rooms - for room in Room.objects.all(): - room.msg_contents("The sun rises from the eastern horizon.") - -def start_sunrise_event(): - """Schedule an sunrise event to happen every day at 6 AM.""" - script = gametime.schedule(at_sunrise, repeat=True, hour=6, min=0, sec=0) - script.key = "at sunrise" -``` - -If you want to test this function, you can easily do something like: - -``` -@py from world import ingame_time; ingame_time.start_sunrise_event() -``` - -The script will be created silently. The `at_sunrise` function will now be called every in-game day -at 6 AM. You can use the `@scripts` command to see it. You could stop it using `@scripts/stop`. If -we hadn't set `repeat` the sun would only have risen once and then never again. - -We used the `@py` command here: nothing prevents you from adding the system into your game code. -Remember to be careful not to add each event at startup, however, otherwise there will be a lot of -overlapping events scheduled when the sun rises. - -The `schedule` function when using `repeat` set to `True` works with the higher, non-specified unit. -In our example, we have specified hour, minute and second. The higher unit we haven't specified is -day: `schedule` assumes we mean "run the callback every day at the specified time". Therefore, you -can have an event that runs every hour at HH:30, or every month on the 3rd day. - -> A word of caution for repeated scripts on a monthly or yearly basis: due to the variations in the -real-life calendar you need to be careful when scheduling events for the end of the month or year. -For example, if you set a script to run every month on the 31st it will run in January but find no -such day in February, April etc. Similarly, leap years may change the number of days in the year. - -### A game time with a custom calendar - -Using a custom calendar to handle game time is sometimes needed if you want to place your game in a -fictional universe. For instance you may want to create the Shire calendar which Tolkien described -having 12 months, each which 30 days. That would give only 360 days per year (presumably hobbits -weren't really fond of the hassle of following the astronomical calendar). Another example would be -creating a planet in a different solar system with, say, days 29 hours long and months of only 18 -days. - -Evennia handles custom calendars through an optional *contrib* module, called `custom_gametime`. -Contrary to the normal `gametime` module described above it is not active by default. - -#### Setting up the custom calendar - -In our first example of the Shire calendar, used by hobbits in books by Tolkien, we don't really -need the notion of weeks... but we need the notion of months having 30 days, not 28. - -The custom calendar is defined by adding the `TIME_UNITS` setting to your settings file. It's a -dictionary containing as keys the name of the units, and as value the number of seconds (the -smallest unit for us) in this unit. Its keys must be picked among the following: "sec", "min", -"hour", "day", "week", "month" and "year" but you don't have to include them all. Here is the -configuration for the Shire calendar: - -```python -# in a file settings.py in mygame/server/conf -TIME_UNITS = {"sec": 1, - "min": 60, - "hour": 60 * 60, - "day": 60 * 60 * 24, - "month": 60 * 60 * 24 * 30, - "year": 60 * 60 * 24 * 30 * 12 } -``` - -We give each unit we want as keys. Values represent the number of seconds in that unit. Hour is -set to 60 * 60 (that is, 3600 seconds per hour). Notice that we don't specify the week unit in this -configuration: instead, we skip from days to months directly. - -In order for this setting to work properly, remember all units have to be multiples of the previous -units. If you create "day", it needs to be multiple of hours, for instance. - -So for our example, our settings may look like this: - -```python -# in a file settings.py in mygame/server/conf -# Time factor -TIME_FACTOR = 4 - -# Game time epoch -TIME_GAME_EPOCH = 0 - -# Units -TIME_UNITS = { - "sec": 1, - "min": 60, - "hour": 60 * 60, - "day": 60 * 60 * 24, - "month": 60 * 60 * 24 * 30, - "year": 60 * 60 * 24 * 30 * 12, -} -``` - -Notice we have set a time epoch of 0. Using a custom calendar, we will come up with a nice display -of time on our own. In our case the game time starts at year 0, month 0, day 0, and at midnight. - -Note that while we use "month", "week" etc in the settings, your game may not use those terms in- -game, instead referring to them as "cycles", "moons", "sand falls" etc. This is just a matter of you -displaying them differently. See next section. - -#### A command to display the current game time - -As pointed out earlier, the `@time` command is meant to be used with a standard calendar, not a -custom one. We can easily create a new command though. We'll call it `time`, as is often the case -on other MU*. Here's an example of how we could write it (for the example, you can create a file -`showtime.py` in your `commands` directory and paste this code in it): - -```python -# in a file mygame/commands/gametime.py - -from evennia.contrib import custom_gametime - -from commands.command import Command - -class CmdTime(Command): - - """ - Display the time. - - Syntax: - time - - """ - - key = "time" - locks = "cmd:all()" - - def func(self): - """Execute the time command.""" - # Get the absolute game time - year, month, day, hour, min, sec = custom_gametime.custom_gametime(absolute=True) - string = "We are in year {year}, day {day}, month {month}." - string += "\nIt's {hour:02}:{min:02}:{sec:02}." - self.msg(string.format(year=year, month=month, day=day, - hour=hour, min=min, sec=sec)) -``` - -Don't forget to add it in your CharacterCmdSet to see this command: - -```python -# in mygame/commands/default_cmdset.py - -from commands.gametime import CmdTime # <-- Add - -# ... - -class CharacterCmdSet(default_cmds.CharacterCmdSet): - """ - The `CharacterCmdSet` contains general in-game commands like `look`, - `get`, etc available on in-game Character objects. It is merged with - the `AccountCmdSet` when an Account puppets a Character. - """ - key = "DefaultCharacter" - - def at_cmdset_creation(self): - """ - Populates the cmdset - """ - super().at_cmdset_creation() - # ... - self.add(CmdTime()) # <- Add -``` - -Reload your game with the `@reload` command. You should now see the `time` command. If you enter -it, you might see something like: - - We are in year 0, day 0, month 0. - It's 00:52:17. - -You could display it a bit more prettily with names for months and perhaps even days, if you want. -And if "months" are called "moons" in your game, this is where you'd add that. - -#### Time-related events in custom gametime - -The `custom_gametime` module also has a way to schedule game-related events, taking into account -your game time (and your custom calendar). It can be used to have a specific message every day at -6:00 AM, to show the sun rises, for instance. The `custom_gametime.schedule` function works in the -same way as described for the default one above. diff --git a/docs/0.9.5/_sources/Getting-Started.md.txt b/docs/0.9.5/_sources/Getting-Started.md.txt deleted file mode 100644 index bcbf49de24..0000000000 --- a/docs/0.9.5/_sources/Getting-Started.md.txt +++ /dev/null @@ -1,542 +0,0 @@ -# Getting Started - - -This will help you download, install and start Evennia for the first time. - -> Note: You don't need to make anything visible to the 'net in order to run and -> test out Evennia. Apart from downloading and updating you don't even need an -> internet connection until you feel ready to share your game with the world. - -- [Quick Start](./Getting-Started.md#quick-start) -- [Requirements](./Getting-Started.md#requirements) -- [Linux Install](./Getting-Started.md#linux-install) -- [Mac Install](./Getting-Started.md#mac-install) -- [Windows Install](./Getting-Started.md#windows-install) -- [Running in Docker](./Running-Evennia-in-Docker.md) -- [Where to Go Next](./Getting-Started.md#where-to-go-next) -- [Troubleshooting](./Getting-Started.md#troubleshooting) -- [Glossary of terms](./Glossary.md) - -## Quick Start - -For the impatient. If you have trouble with a step, you should jump on to the -more detailed instructions for your platform. - -1. Install Python, GIT and python-virtualenv. Start a Console/Terminal. -2. `cd` to some place you want to do your development (like a folder - `/home/anna/muddev/` on Linux or a folder in your personal user directory on Windows). -3. `git clone https://github.com/evennia/evennia.git` -4. `virtualenv evenv` -5. `source evenv/bin/activate` (Linux, Mac), `evenv\Scripts\activate` (Windows) -6. `pip install -e evennia` -7. `evennia --init mygame` -8. `cd mygame` -9. `evennia migrate` -10. `evennia start` (make sure to make a superuser when asked) -Evennia should now be running and you can connect to it by pointing a web browser to -`http://localhost:4001` or a MUD telnet client to `localhost:4000` (use `127.0.0.1` if your OS does -not recognize `localhost`). - -We also release [Docker images](./Running-Evennia-in-Docker.md) -based on `master` and `develop` branches. - -## Requirements - -Any system that supports Python3.7+ should work. We'll describe how to install -everything in the following sections. -- Linux/Unix -- Windows (Vista, Win7, Win8, Win10) -- Mac OSX (>=10.5 recommended) - -- [Python](http://www.python.org) (v3.7, 3.8 or 3.9) - - [virtualenv](http://pypi.python.org/pypi/virtualenv) for making isolated - Python environments. Installed with `pip install virtualenv`. - -- [GIT](http://git-scm.com/) - version control software for getting and -updating Evennia itself - Mac users can use the -[git-osx-installer](http://code.google.com/p/git-osx-installer/) or the -[MacPorts version](http://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac). -- [Twisted](http://twistedmatrix.com) (v21.0+) - - [ZopeInterface](http://www.zope.org/Products/ZopeInterface) (v3.0+) - usually included in -Twisted packages - - Linux/Mac users may need the `gcc` and `python-dev` packages or equivalent. - - Windows users need [MS Visual C++](https://aka.ms/vs/16/release/vs_buildtools.exe) and *maybe* -[pypiwin32](https://pypi.python.org/pypi/pypiwin32). -- [Django](http://www.djangoproject.com) (v3.2.x), be warned that latest dev - version is usually untested with Evennia) - -## Linux Install - -If you run into any issues during the installation and first start, please -check out [Linux Troubleshooting](./Getting-Started.md#linux-troubleshooting). - -For Debian-derived systems (like Ubuntu, Mint etc), start a terminal and -install the [dependencies](./Getting-Started.md#requirements): - -``` -sudo apt-get update -sudo apt-get install python3 python3-pip python3-dev python3-setuptools python3-git -python3-virtualenv gcc - -# If you are using an Ubuntu version that defaults to Python3, like 18.04+, use this instead: -sudo apt-get update -sudo apt-get install python3.7 python3-pip python3.7-dev python3-setuptools virtualenv gcc - -``` -Note that, the default Python version for your distribution may still not be Python3.7 after this. -This is ok - we'll specify exactly which Python to use later. -You should make sure to *not* be `root` after this step, running as `root` is a -security risk. Now create a folder where you want to do all your Evennia -development: - -``` -mkdir muddev -cd muddev -``` - -Next we fetch Evennia itself: - -``` -git clone https://github.com/evennia/evennia.git -``` -A new folder `evennia` will appear containing the Evennia library. This only -contains the source code though, it is not *installed* yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a _virtualenv_. If you are unsure about what a -virtualenv is and why it's useful, see the [Glossary entry on -virtualenv](./Glossary.md#virtualenv). - -Run `python -V` to see which version of Python your system defaults to. - -``` -# If your Linux defaults to Python3.7+: -virtualenv evenv - -# If your Linux defaults to Python2 or an older version -# of Python3, you must instead point to Python3.7+ explicitly: -virtualenv -p /usr/bin/python3.7 evenv -``` - -A new folder `evenv` will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system (or the Linux distro lagging behind -on Python package versions). It will also always use the right version of Python. -Activate the virtualenv: - -``` -source evenv/bin/activate -``` - -The text `(evenv)` should appear next to your prompt to show that the virtual -environment is active. - -> Remember that you need to activate the virtualenv like this *every time* you -> start a new terminal to get access to the Python packages (notably the -> important `evennia` program) we are about to install. - -Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the `evennia/` and `evenv/` -folders) and run - -``` -pip install -e evennia -``` - -For more info about `pip`, see the [Glossary entry on pip](./Glossary.md#pip). If -install failed with any issues, see [Linux Troubleshooting](./Getting-Started.md#linux-troubleshooting). - -Next we'll start our new game, here called "mygame". This will create yet -another new folder where you will be creating your new game: - -``` -evennia --init mygame -``` - -Your final folder structure should look like this: -``` -./muddev - evenv/ - evennia/ - mygame/ -``` - -You can [configure Evennia](./Server-Conf.md#settings-file) extensively, for example -to use a [different database](./Choosing-An-SQL-Server.md). For now we'll just stick -to the defaults though. - -``` -cd mygame -evennia migrate # (this creates the database) -evennia start # (create a superuser when asked. Email is optional.) -``` - -> Server logs are found in `mygame/server/logs/`. To easily view server logs -> live in the terminal, use `evennia -l` (exit the log-view with Ctrl-C). - -Your game should now be running! Open a web browser at `http://localhost:4001` -or point a telnet client to `localhost:4000` and log in with the user you -created. Check out [where to go next](./Getting-Started.md#where-to-go-next). - - -## Mac Install - -The Evennia server is a terminal program. Open the terminal e.g. from -*Applications->Utilities->Terminal*. [Here is an introduction to the Mac -terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) -if you are unsure how it works. If you run into any issues during the -installation, please check out [Mac Troubleshooting](./Getting-Started.md#mac-troubleshooting). - -* Python should already be installed but you must make sure it's a high enough version. -([This](http://docs.python-guide.org/en/latest/starting/install/osx/) discusses - how you may upgrade it). Remember that you need Python3.7, not Python2.7! -* GIT can be obtained with -[git-osx-installer](http://code.google.com/p/git-osx-installer/) or via -MacPorts [as described -here](http://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac). -* If you run into issues with installing `Twisted` later you may need to -install gcc and the Python headers. - -After this point you should not need `sudo` or any higher privileges to install anything. - -Now create a folder where you want to do all your Evennia development: - -``` -mkdir muddev -cd muddev -``` - -Next we fetch Evennia itself: - -``` -git clone https://github.com/evennia/evennia.git -``` - -A new folder `evennia` will appear containing the Evennia library. This only -contains the source code though, it is not *installed* yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a _virtualenv_. If you are unsure about what a -virtualenv is and why it's useful, see the [Glossary entry on virtualenv](./Glossary.md#virtualenv). - -Run `python -V` to check which Python your system defaults to. - - -``` -# If your Mac defaults to Python3: -virtualenv evenv - -# If your Mac defaults to Python2 you need to specify the Python3.7 binary explicitly: -virtualenv -p /path/to/your/python3.7 evenv -``` - -A new folder `evenv` will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system. Activate the virtualenv: - -``` -source evenv/bin/activate -``` - -The text `(evenv)` should appear next to your prompt to show the virtual -environment is active. - -> Remember that you need to activate the virtualenv like this *every time* you -> start a new terminal to get access to the Python packages (notably the -> important `evennia` program) we are about to install. - -Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the `evennia/` and `evenv/` -folders) and run - -``` -pip install --upgrade pip # Old pip versions may be an issue on Mac. -pip install --upgrade setuptools # Ditto concerning Mac issues. -pip install -e evennia -``` - -For more info about `pip`, see the [Glossary entry on pip](./Glossary.md#pip). If -install failed with any issues, see [Mac Troubleshooting](./Getting-Started.md#mac-troubleshooting). - -Next we'll start our new game. We'll call it "mygame" here. This creates a new -folder where you will be creating your new game: - -``` -evennia --init mygame -``` - -Your final folder structure should look like this: - -``` -./muddev - evenv/ - evennia/ - mygame/ -``` - -You can [configure Evennia](./Server-Conf.md#settings-file) extensively, for example -to use a [different database](./Choosing-An-SQL-Server.md). We'll go with the -defaults here. - -``` -cd mygame -evennia migrate # (this creates the database) -evennia start # (create a superuser when asked. Email is optional.) -``` - -> Server logs are found in `mygame/server/logs/`. To easily view server logs -> live in the terminal, use `evennia -l` (exit the log-view with Ctrl-C). - -Your game should now be running! Open a web browser at `http://localhost:4001` -or point a telnet client to `localhost:4000` and log in with the user you -created. Check out [where to go next](./Getting-Started.md#where-to-go-next). - - -## Windows Install - -If you run into any issues during the installation, please check out -[Windows Troubleshooting](./Getting-Started.md#windows-troubleshooting). - -> If you are running Windows10, consider using the Windows Subsystem for Linux -> ([WSL](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)) instead. -> You should then follow the Linux install instructions above. - -The Evennia server itself is a command line program. In the Windows launch -menu, start *All Programs -> Accessories -> command prompt* and you will get -the Windows command line interface. Here is [one of many tutorials on using the Windows command -line](http://www.bleepingcomputer.com/tutorials/windows-command-prompt-introduction/) -if you are unfamiliar with it. - -* Install Python [from the Python homepage](https://www.python.org/downloads/windows/). You will -need to be a -Windows Administrator to install packages. You want Python version **3.7.0** (latest verified -version), usually -the 64-bit version (although it doesn't matter too much). **When installing, make sure -to check-mark *all* install options, especially the one about making Python -available on the path (you may have to scroll to see it)**. This allows you to -just write `python` in any console without first finding where the `python` -program actually sits on your hard drive. -* You need to also get [GIT](http://git-scm.com/downloads) and install it. You -can use the default install options but when you get asked to "Adjust your PATH -environment", you should select the second option "Use Git from the Windows -Command Prompt", which gives you more freedom as to where you can use the -program. -* Finally you must install the [Microsoft Visual C++ compiler for -Python](https://aka.ms/vs/16/release/vs_buildtools.exe). Download and run the linked installer and -install the C++ tools. Keep all the defaults. Allow the install of the "Win10 SDK", even if you are -on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due -to a failure to build the "Twisted wheels", this is where you are missing things. -* You *may* need the [pypiwin32](https://pypi.python.org/pypi/pypiwin32) Python headers. Install -these only if you have issues. - -You can install Evennia wherever you want. `cd` to that location and create a -new folder for all your Evennia development (let's call it `muddev`). - -``` -mkdir muddev -cd muddev -``` - -> Hint: If `cd` isn't working you can use `pushd` instead to force the -> directory change. - -Next we fetch Evennia itself: - -``` -git clone https://github.com/evennia/evennia.git -``` - -A new folder `evennia` will appear containing the Evennia library. This only -contains the source code though, it is not *installed* yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a _virtualenv_. If you are unsure about what a -virtualenv is and why it's useful, see the [Glossary entry on virtualenv](./Glossary.md#virtualenv). - -In your console, try `python -V` to see which version of Python your system -defaults to. - - -``` -pip install virtualenv - -# If your setup defaults to Python3.7: -virtualenv evenv - -# If your setup defaults to Python2, specify path to python3.exe explicitly: -virtualenv -p C:\Python37\python.exe evenv - -# If you get an infinite spooling response, press CTRL + C to interrupt and try using: -python -m venv evenv - -``` -A new folder `evenv` will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system. Activate the virtualenv: - -``` -# If you are using a standard command prompt, you can use the following: -evenv\scripts\activate.bat - -# If you are using a PS Shell, Git Bash, or other, you can use the following: -.\evenv\scripts\activate - -``` -The text `(evenv)` should appear next to your prompt to show the virtual -environment is active. - -> Remember that you need to activate the virtualenv like this *every time* you -> start a new console window if you want to get access to the Python packages -> (notably the important `evennia` program) we are about to install. - -Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the `evennia` and `evenv` -folders when you use the `dir` command) and run - -``` -pip install -e evennia -``` -For more info about `pip`, see the [Glossary entry on pip](./Glossary.md#pip). If -the install failed with any issues, see [Windows Troubleshooting](./Getting-Started.md#windows- -troubleshooting). -Next we'll start our new game, we'll call it "mygame" here. This creates a new folder where you will -be -creating your new game: - -``` -evennia --init mygame -``` - -Your final folder structure should look like this: - -``` -path\to\muddev - evenv\ - evennia\ - mygame\ -``` - -You can [configure Evennia](./Server-Conf.md#settings-file) extensively, for example -to use a [different database](./Choosing-An-SQL-Server.md). We'll go with the -defaults here. - -``` -cd mygame -evennia migrate # (this creates the database) -evennia start # (create a superuser when asked. Email is optional.) -``` - -> Server logs are found in `mygame/server/logs/`. To easily view server logs -> live in the terminal, use `evennia -l` (exit the log-view with Ctrl-C). - -Your game should now be running! Open a web browser at `http://localhost:4001` -or point a telnet client to `localhost:4000` and log in with the user you -created. Check out [where to go next](./Getting-Started.md#where-to-go-next). - - -## Where to Go Next - -Welcome to Evennia! Your new game is fully functioning, but empty. If you just -logged in, stand in the `Limbo` room and run - - @batchcommand tutorial_world.build - -to build [Evennia's tutorial world](./Tutorial-World-Introduction.md) - it's a small solo quest to -explore. Only run the instructed `@batchcommand` once. You'll get a lot of text scrolling by as the -tutorial is built. Once done, the `tutorial` exit will have appeared out of Limbo - just write -`tutorial` to enter it. - -Once you get back to `Limbo` from the tutorial (if you get stuck in the tutorial quest you can do -`@tel #2` to jump to Limbo), a good idea is to learn how to [start, stop and reload](Start-Stop- -Reload) the Evennia server. You may also want to familiarize yourself with some [commonly used terms -in our Glossary](./Glossary.md). After that, why not experiment with [creating some new items and build -some new rooms](./Building-Quickstart.md) out from Limbo. - -From here on, you could move on to do one of our [introductory tutorials](./Tutorials.md) or simply dive -headlong into Evennia's comprehensive [manual](https://github.com/evennia/evennia/wiki). While -Evennia has no major game systems out of the box, we do supply a range of optional *contribs* that -you can use or borrow from. They range from dice rolling and alternative color schemes to barter and -combat systems. You can find the [growing list of contribs -here](https://github.com/evennia/evennia/blob/master/evennia/contrib/README.md). - -If you have any questions, you can always ask in [the developer -chat](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) -`#evennia` on `irc.freenode.net` or by posting to the [Evennia -forums](https://groups.google.com/forum/#%21forum/evennia). You can also join the [Discord -Server](https://discord.gg/NecFePw). - -Finally, if you are itching to help out or support Evennia (awesome!) have an -issue to report or a feature to request, [see here](./How-To-Get-And-Give-Help.md). - -Enjoy your stay! - - -## Troubleshooting - -If you have issues with installing or starting Evennia for the first time, -check the section for your operating system below. If you have an issue not -covered here, [please report it](https://github.com/evennia/evennia/issues) -so it can be fixed or a workaround found! - -Remember, the server logs are in `mygame/server/logs/`. To easily view server logs in the terminal, -you can run `evennia -l`, or (in the future) start the server with `evennia start -l`. - -### Linux Troubleshooting - -- If you get an error when installing Evennia (especially with lines mentioning - failing to include `Python.h`) then try `sudo apt-get install python3-setuptools python3-dev`. - Once installed, run `pip install -e evennia` again. -- Under some not-updated Linux distributions you may run into errors with a - too-old `setuptools` or missing `functools`. If so, update your environment - with `pip install --upgrade pip wheel setuptools`. Then try `pip install -e evennia` again. -- If you get an `setup.py not found` error message while trying to `pip install`, make sure you are - in the right directory. You should be at the same level of the `evenv` directory, and the - `evennia` git repository. Note that there is an `evennia` directory inside of the repository too. -- One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; `Command -"python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/` with errors -like `distutils.errors.DistutilsError: Could not find suitable distribution for -Requirement.parse('incremental>=16.10.1')`. This appears possible to solve by simply updating Ubuntu -with `sudo apt-get update && sudo apt-get dist-upgrade`. -- Users of Fedora (notably Fedora 24) has reported a `gcc` error saying the directory -`/usr/lib/rpm/redhat/redhat-hardened-cc1` is missing, despite `gcc` itself being installed. [The -confirmed work-around](https://gist.github.com/yograterol/99c8e123afecc828cb8c) seems to be to -install the `redhat-rpm-config` package with e.g. `sudo dnf install redhat-rpm-config`. -- Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues -with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to -yourself?) - -### Mac Troubleshooting - -- Mac users have reported a critical `MemoryError` when trying to start Evennia on Mac with a Python -version below `2.7.12`. If you get this error, update to the latest XCode and Python2 version. -- Some Mac users have reported not being able to connect to `localhost` (i.e. your own computer). If -so, try to connect to `127.0.0.1` instead, which is the same thing. Use port 4000 from mud clients -and port 4001 from the web browser as usual. - -### Windows Troubleshooting - -- If you installed Python but the `python` command is not available (even in a new console), then -you might have missed installing Python on the path. In the Windows Python installer you get a list -of options for what to install. Most or all options are pre-checked except this one, and you may -even have to scroll down to see it. Reinstall Python and make sure it's checked. -- If your MUD client cannot connect to `localhost:4000`, try the equivalent `127.0.0.1:4000` -instead. Some MUD clients on Windows does not appear to understand the alias `localhost`. -- If you run `virtualenv evenv` and get a `'virtualenv' is not recognized as an internal or external -command, -operable program or batch file.` error, you can `mkdir evenv`, `cd evenv` and then `python -m -virtualenv .` as a workaround. -- Some Windows users get an error installing the Twisted 'wheel'. A wheel is a pre-compiled binary -package for Python. A common reason for this error is that you are using a 32-bit version of Python, -but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a -slightly older Twisted version. So if, say, version `18.1` failed, install `18.0` manually with `pip -install twisted==18.0`. Alternatively you could try to get a 64-bit version of Python (uninstall the -32bit one). If so, you must then `deactivate` the virtualenv, delete the `evenv` folder and recreate -it anew (it will then use the new Python executable). -- If your server won't start, with no error messages (and no log files at all when starting from -scratch), try to start with `evennia ipstart` instead. If you then see an error about `system cannot -find the path specified`, it may be that the file `evennia/evennia/server/twistd.bat` has the wrong -path to the `twistd` executable. This file is auto-generated, so try to delete it and then run -`evennia start` to rebuild it and see if it works. If it still doesn't work you need to open it in a -text editor like Notepad. It's just one line containing the path to the `twistd.exe` executable as -determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and -you should update the line to the real location. -- Some users have reported issues with Windows WSL and anti-virus software during Evennia -development. Timeout errors and the inability to run `evennia connections` may be due to your anti- -virus software interfering. Try disabling or changing your anti-virus software settings. diff --git a/docs/0.9.5/_sources/Glossary.md.txt b/docs/0.9.5/_sources/Glossary.md.txt deleted file mode 100644 index 6b74b4ce01..0000000000 --- a/docs/0.9.5/_sources/Glossary.md.txt +++ /dev/null @@ -1,381 +0,0 @@ -# Glossary - - -This explains common recurring terms used in the Evennia docs. It will be expanded as needed. - -- _[account](./Glossary.md#account)_ - the player's account on the game -- _[admin-site](./Glossary.md#admin-site)_ - the Django web page for manipulating the database -- _[attribute](./Glossary.md#attribute)_ - persistent, custom data stored on typeclasses -- _[channel](./Glossary.md#channel)_ - game communication channels -- _[character](./Glossary.md#character)_ - the player's avatar in the game, controlled from -_[account](./Glossary.md#account)_ -- _[core](./Glossary.md#core)_ - a term used for the code distributed with Evennia proper -- _[django](./Glossary.md#django)_ - web framework Evennia uses for database access and web integration -- _[field](./Glossary.md#field)_ - a _[typeclass](./Glossary.md#typeclass)_ property representing a database -column -- _[git](./Glossary.md#git)_ - the version-control system we use -- _[github](./Glossary.md#github)_ - the online hosting of our source code -- _[migrate](./Glossary.md#migrate)_ - updating the database schema -- _[multisession mode`](#multisession-mode)_ - a setting defining how users connect to Evennia -- _[object](./Glossary.md#object)_ - Python instance, general term or in-game -_[typeclass](./Glossary.md#typeclass)_ -- _[pip](./Glossary.md#pip)_ - the Python installer -- _player_ - the human connecting to the game with their client -- _[puppet](./Glossary.md#puppet)_ - when an [account](./Glossary.md#account) controls an in-game -[object](./Glossary.md#object) -- _[property](./Glossary.md#property)_ - a python property -- _evenv_ - see _[virtualenv](./Glossary.md#virtualenv)_ -- _[repository](./Glossary.md#repository)_ - a store of source code + source history -- _[script](./Glossary.md#script)_ - a building block for custom storage, systems and time-keepint -- _[session](./Glossary.md#session)_ - represents one client connection -- _[ticker](./Glossary.md#ticker)_ - Allows to run events on a steady 'tick' -- _[twisted](./Glossary.md#twisted)_ - networking engine responsible for Evennia's event loop and -communications -- _[typeclass](./Glossary.md#typeclass)_ - Evennia's database-connected Python class -- _upstream_ - see _[github](./Glossary.md#github)_ -- _[virtualenv](./Glossary.md#virtualenv)_ - a Python program and way to make an isolated Python install - - ---- - -### _account_ - -The term 'account' refers to the player's unique account on the game. It is -represented by the `Account` [typeclass](./Glossary.md#typeclass) and holds things like email, password, -configuration etc. - -When a player connects to the game, they connect to their account. The account has *no* -representation in the game world. Through their Account they can instead choose to -[puppet](./Glossary.md#puppet) one (or more, depending on game mode) [Characters](./Glossary.md#character) in -the game. - -In the default [multisession mode](./Sessions.md#multisession-mode) of Evennia, you immediately start -puppeting a Character with the same name as your Account when you log in - mimicking how older -servers used to work. - -### _admin-site_ - -This usually refers to [Django's](./Glossary.md#django) *Admin site* or database-administration web page -([link to Django docs](https://docs.djangoproject.com/en/2.1/ref/contrib/admin/)). The admin site is -an automatically generated web interface to the database (it can be customized extensively). It's -reachable from the `admin` link on the default Evennia website you get with your server. - -### _attribute_ - -The term _Attribute_ should not be confused with ([properties](./Glossary.md#property) or -[fields](./Glossary.md#field). The `Attribute` represents arbitrary pieces of data that can be attached -to any [typeclassed](./Glossary.md#typeclass) entity in Evennia. Attributes allows storing new persistent -data on typeclasses without changing their underlying database schemas. [Read more about Attributes -here](./Attributes.md). - -### _channel_ - -A _Channel_ refers to an in-game communication channel. It's an entity that people subscribe to and -which re-distributes messages between all subscribers. Such subscribers default to being -[Accounts](./Glossary.md#account), for out-of-game communication but could also be [Objects (usually -Characters)](./Glossary.md#character) if one wanted to adopt Channels for things like in-game walkie- -talkies or phone systems. It is represented by the `Channel` typeclass. [You can read more about the -comm system here](./Communications.md#channels). - -### _character_ - -The _Character_ is the term we use for the default avatar being [puppeted](./Glossary.md#puppet) by the -[account](./Glossary.md#account) in the game world. It is represented by the `Character` typeclass (which -is a child of [Object](./Glossary.md#object)). Many developers use children of this class to represent -monsters and other NPCs. You can [read more about it here](./Objects.md#subclasses-of-object). - -### _django_ - -[Django](https://www.djangoproject.com/) is a professional and very popular Python web framework, -similar to Rails for the Ruby language. It is one of Evennia's central library dependencies (the -other one is [Twisted](./Glossary.md#twisted)). Evennia uses Django for two main things - to map all -database operations to Python and for structuring our web site. - -Through Django, we can work with any supported database (SQlite3, Postgres, MySQL ...) using generic -Python instead of database-specific SQL: A database table is represented in Django as a Python class -(called a *model*). An Python instance of such a class represents a row in that table. - -There is usually no need to know the details of Django's database handling in order to use Evennia - -it will handle most of the complexity for you under the hood using what we call -[typeclasses](./Glossary.md#typeclass). But should you need the power of Django you can always get it. -Most commonly people want to use "raw" Django when doing more advanced/custom database queries than -offered by Evennia's [default search functions](./Tutorial-Searching-For-Objects.md). One will then need -to read about Django's _querysets_. Querysets are Python method calls on a special form that lets -you build complex queries. They get converted into optimized SQL queries under the hood, suitable -for your current database. [Here is our tutorial/explanation of Django queries](Tutorial-Searching- -For-Objects#queries-in-django). - -> By the way, Django (and Evennia) does allow you to fall through and send raw SQL if you really -want to. It's highly unlikely to be needed though; the Django database abstraction is very, very -powerful. - -The other aspect where Evennia uses Django is for web integration. On one end Django gives an -infrastructure for wiring Python functions (called *views*) to URLs: the view/function is called -when a user goes that URL in their browser, enters data into a form etc. The return is the web page -to show. Django also offers templating with features such as being able to add special markers in -HTML where it will insert the values of Python variables on the fly (like showing the current player -count on the web page). [Here is one of our tutorials on wiring up such a web page](Add-a-simple- -new-web-page). Django also comes with the [admin site](./Glossary.md#admin-site), which automatically -maps the database into a form accessible from a web browser. - -### _contrib_ - -Contribs are optional and often more game-specific code-snippets contributed by the Evennia community. -They are distributed with Evennia in the `contrib/` folder. - -### _core_ - -This term is sometimes used to represent the main Evennia library code suite, *excluding* its -[contrib](./Glossary.md#contrib) directory. It can sometimes come up in code reviews, such as - -> Evennia is game-agnostic but this feature is for a particular game genre. So it does not belong in -core. Better make it a contrib. - -### _field_ - -A _field_ or _database field_ in Evennia refers to a [property](./Glossary.md#property) on a -[typeclass](./Glossary.md#typeclass) directly linked to an underlying database column. Only a few fixed -properties per typeclass are database fields but they are often tied to the core functionality of -that base typeclass (for example [Objects](./Glossary.md#object) store its location as a field). In all -other cases, [attributes](./Glossary.md#attribute) are used to add new persistent data to the typeclass. -[Read more about typeclass properties here](./Typeclasses.md#about-typeclass-properties). - -### _git_ - -[Git](https://git-scm.com/) is a [version control](https://en.wikipedia.org/wiki/Version_control) -tool. It allows us to track the development of the Evennia code by dividing it into units called -*commits*. A 'commit' is sort of a save-spot - you save the current state of your code and can then -come back to it later if later changes caused problems. By tracking commits we know what 'version' -of the code we are currently using. - -Evennia's source code + its source history is jointly called a [repository](./Glossary.md#repository). -This is centrally stored at our online home on [GitHub](./Glossary.md#github). Everyone using or -developing Evennia makes a 'clone' of this repository to their own computer - everyone -automatically gets everything that is online, including all the code history. - -> Don't confuse Git and [GitHub](./Glossary.md#github). The former is the version control system. The -latter is a website (run by a company) that allows you to upload source code controlled by Git for -others to see (among other things). - -Git allows multiple users from around the world to efficiently collaborate on Evennia's code: People -can make local commits on their cloned code. The commits they do can then be uploaded to GitHub and -reviewed by the Evennia lead devs - and if the changes look ok they can be safely *merged* into the -central Evennia code - and everyone can *pull* those changes to update their local copies. - -Developers using Evennia often uses Git on their own games in the same way - to track their changes -and to help collaboration with team mates. This is done completely independently of Evennia's Git -usage. - -Common usage (for non-Evennia developers): -- `git clone ` - clone an online repository to your computer. This is what you do when -you 'download' Evennia. You only need to do this once. -- `git pull` (inside local copy of repository) - sync your local repository with what is online. - -> Full usage of Git is way beyond the scope of this glossary. See [Tutorial - version -control](./Version-Control.md) for more info and links to the Git documentation. - -### _migrate_ - -This term is used for upgrading the database structure (it's _schema_ )to a new version. Most often -this is due to Evennia's [upstream](./Glossary.md#github) schema changing. When that happens you need to -migrate that schema to the new version as well. Once you have used [git](./Glossary.md#git) to pull the -latest changes, just `cd` into your game dir and run - - evennia migrate - -That should be it (see [virtualenv](./Glossary.md#virtualenv) if you get a warning that the `evennia` -command is not available). See also [Updating your game](./Updating-Your-Game.md) for more details. - -> Technically, migrations are shipped as little Python snippets of code that explains which database -actions must be taken to upgrade from one version of the schema to the next. When you run the -command above, those snippets are run in sequence. - -### _multisession mode_ - -This term refers to the `MULTISESSION_MODE` setting, which has a value of 0 to 3. The mode alters -how players can connect to the game, such as how many Sessions a player can start with one account -and how many Characters they can control at the same time. It is [described in detail -here](./Sessions.md#multisession-mode). - -### _github_ - -[Github](https://github.com/evennia) is where Evennia's source code and documentation is hosted. -This online [repository](./Glossary.md#repository) of code we also sometimes refer to as _upstream_. - -GitHub is a business, offering free hosting to Open-source projects like Evennia. Despite the -similarity in name, don't confuse GitHub the website with [Git](./Glossary.md#git), the versioning -system. Github hosts Git [repositories](./Glossary.md#repository) online and helps with collaboration and -infrastructure. Git itself is a separate project. - -### _object_ - -In general Python (and other [object-oriented languages](https://en.wikipedia.org/wiki/Object-oriented_programming)), an `object` is what we call the instance of a *class*. But one of Evennia's -core [typeclasses](./Glossary.md#typeclass) is also called "Object". To separate these in the docs we -try to use `object` to refer to the general term and capitalized `Object` when we refer to the -typeclass. - -The `Object` is a typeclass that represents all *in-game* entities, including -[Characters](./Glossary.md#character), rooms, trees, weapons etc. [Read more about Objects -here](./Objects.md). - -### _pip_ - -_[pip](https://pypi.org/project/pip/)_ comes with Python and is the main tool for installing third- -party Python packages from the web. Once a python package is installed you can do `import -` in your Python code. - -Common usage: -- `pip install ` - install the given package along with all its dependencies. -- `pip search ` - search Python's central package repository [PyPi](https://pypi.org/) for a -package of that name. -- `pip install --upgrade ` - upgrade a package you already have to the latest version. -- `pip install ==1.5` - install exactly a specific package version. -- `pip install ` - install a Python package you have downloaded earlier (or cloned using -git). -- `pip install -e ` - install a local package by just making a soft link to the folder. This -means that if the code in `` changes, the installed Python package is immediately updated. -If not using `-e`, one would need to run `pip install --upgrade ` every time to make the -changes available when you import this package into your code. Evennia is installed this way. - -For development, `pip` is usually used together with a [virtualenv](./Glossary.md#virtualenv) to install -all packages and dependencies needed for a project in one, isolated location on the hard drive. - -### _puppet_ - -An [account](./Glossary.md#account) can take control and "play as" any [Object](./Glossary.md#object). When -doing so, we call this _puppeting_, (like [puppeteering](https://en.wikipedia.org/wiki/Puppeteer)). -Normally the entity being puppeted is of the [Character](./Glossary.md#character) subclass but it does -not have to be. - -### _property_ - -A _property_ is a general term used for properties on any Python object. The term also sometimes -refers to the `property` built-in function of Python ([read more here](https://www.python-course.eu/python3_properties.php)). Note the distinction between properties, -[fields](./Glossary.md#field) and [Attributes](./Glossary.md#attribute). - -### _repository_ - -A _repository_ is a version control/[git](./Glossary.md#git) term. It represents a folder containing -source code plus its versioning history. - -> In Git's case, that history is stored in a hidden folder `.git`. If you ever feel the need to look -into this folder you probably already know enough Git to know why. - -The `evennia` folder you download from us with `git clone` is a repository. The code on -[GitHub](./Glossary.md#github) is often referred to as the 'online repository' (or the _upstream_ -repository). If you put your game dir under version control, that of course becomes a repository as -well. - -### _script_ - -When we refer to _Scripts_, we generally refer to the `Script` [typeclass](./Typeclasses.md). Scripts are -the mavericks of Evennia - they are like [Objects](./Glossary.md#object) but without any in-game -existence. They are useful as custom places to store data but also as building blocks in persistent -game systems. Since the can be initialized with timing capabilities they can also be used for long- -time persistent time keeping (for fast updates other types of timers may be better though). [Read -more about Scripts here](./Scripts.md) - -### _session_ - -A [Session](./Sessions.md) is a Python object representing a single client connection to the server. A -given human player could connect to the game from different clients and each would get a Session -(even if you did not allow them to actually log in and get access to an -[account](./Glossary.md#account)). - -Sessions are _not_ [typeclassed](./Glossary.md#typeclass) and has no database persistence. But since they -always exist (also when not logged in), they share some common functionality with typeclasses that -can be useful for certain game states. - -### _tag_ - -A [Tag](./Tags.md) is a simple label one can attach to one or more objects in the game. Tagging is a -powerful way to group entities and can also be used to indicate they have particular shared abilities. -Tags are shared between objects (unlike [Attributes](#attribute)). - -### _ticker_ - -The [Ticker handler](./TickerHandler.md) runs Evennia's optional 'ticker' system. In other engines, such -as [DIKU](https://en.wikipedia.org/wiki/DikuMUD), all game events are processed only at specific -intervals called 'ticks'. Evennia has no such technical limitation (events are processed whenever -needed) but using a fixed tick can still be useful for certain types of game systems, like combat. -Ticker Handler allows you to emulate any number of tick rates (not just one) and subscribe actions -to be called when those ticks come around. - -### _typeclass_ - -The [typeclass](./Typeclasses.md) is an Evennia-specific term. A typeclass allows developers to work with -database-persistent objects as if they were normal Python objects. It makes use of specific -[Django](./Glossary.md#django) features to link a Python class to a database table. Sometimes we refer to -such code entities as _being typeclassed_. - -Evennia's main typeclasses are [Account](./Glossary.md#account), [Object](./Glossary.md#object), -[Script](./Glossary.md#script) and [Channel](./Glossary.md#channel). Children of the base class (such as -[Character](./Glossary.md#character)) will use the same database table as the parent, but can have vastly -different Python capabilities (and persistent features through [Attributes](./Glossary.md#attribute) and -[Tags](./Glossary.md#tag). A typeclass can be coded and treated pretty much like any other Python class -except it must inherit (at any distance) from one of the base typeclasses. Also, creating a new -instance of a typeclass will add a new row to the database table to which it is linked. - -The [core](./Glossary.md#core) typeclasses in the Evennia library are all named `DefaultAccount`, -`DefaultObject` etc. When you initialize your [game dir] you automatically get empty children of -these, called `Account`, `Object` etc that you can start working with. - -### _twisted_ - -[Twisted](https://twistedmatrix.com/trac/) is a heavy-duty asynchronous networking engine. It is one -of Evennia's two major library dependencies (the other one is [Django](./Glossary.md#django)). Twisted is -what "runs" Evennia - it handles Evennia's event loop. Twisted also has the building blocks we need -to construct network protocols and communicate with the outside world; such as our MUD-custom -version of Telnet, Telnet+SSL, SSH, webclient-websockets etc. Twisted also runs our integrated web -server, serving the Django-based website for your game. - -### _virtualenv_ - -The standard [virtualenv](https://virtualenv.pypa.io/en/stable/) program comes with Python. It is -used to isolate all Python packages needed by a given Python project into one folder (we call that -folder `evenv` but it could be called anything). A package environment created this way is usually -referred to as "a virtualenv". If you ever try to run the `evennia` program and get an error saying -something like "the command 'evennia' is not available" - it's probably because your virtualenv is -not 'active' yet (see below). - -Usage: -- `virtualenv ` - initialize a new virtualenv `` in a new folder `` in the current -location. Called `evenv` in these docs. -- `virtualenv -p path/to/alternate/python_executable ` - create a virtualenv using another -Python version than default. -- `source /bin/activate`(linux/mac) - activate the virtualenv in ``. -- `\Scripts\activate` (windows) -- `deactivate` - turn off the currently activated virtualenv. - -A virtualenv is 'activated' only for the console/terminal it was started in, but it's safe to -activate the same virtualenv many times in different windows if you want. Once activated, all Python -packages now installed with [pip](./Glossary.md#pip) will install to `evenv` rather than to a global -location like `/usr/local/bin` or `C:\Program Files`. - -> Note that if you have root/admin access you *could* install Evennia globally just fine, without -using a virtualenv. It's strongly discouraged and considered bad practice though. Experienced Python -developers tend to rather create one new virtualenv per project they are working on, to keep the -varying installs cleanly separated from one another. - -When you execute Python code within this activated virtualenv, *only* those packages installed -within will be possible to `import` into your code. So if you installed a Python package globally on -your computer, you'll need to install it again in your virtualenv. - -> Virtualenvs *only* deal with Python programs/packages. Other programs on your computer couldn't -care less if your virtualenv is active or not. So you could use `git` without the virtualenv being -active, for example. - -When your virtualenv is active you should see your console/terminal prompt change to - - (evenv) ... - -... or whatever name you gave the virtualenv when you initialized it. - -> We sometimes say that we are "in" the virtualenv when it's active. But just to be clear - you -never have to actually `cd` into the `evenv` folder. You can activate it from anywhere and will -still be considered "in" the virtualenv wherever you go until you `deactivate` or close the -console/terminal. - -So, when do I *need* to activate my virtualenv? If the virtualenv is not active, none of the Python -packages/programs you installed in it will be available to you. So at a minimum, *it needs to be -activated whenever you want to use the `evennia` command* for any reason. diff --git a/docs/0.9.5/_sources/Grapevine.md.txt b/docs/0.9.5/_sources/Grapevine.md.txt deleted file mode 100644 index fb8ede4372..0000000000 --- a/docs/0.9.5/_sources/Grapevine.md.txt +++ /dev/null @@ -1,71 +0,0 @@ -# Grapevine - - -[Grapevine](http://grapevine.haus) is a new chat network for `MU*`*** games. By -connecting an in-game channel to the grapevine network, players on your game -can chat with players in other games, also non-Evennia ones. - -## Configuring Grapevine - -To use Grapevine, you first need the `pyopenssl` module. Install it into your -Evennia python environment with - - pip install pyopenssl - -To configure Grapevine, you'll need to activate it in your settings file. - -```python - GRAPEVINE_ENABLED = True -``` - -Next, register an account at https://grapevine.haus. When you have logged in, -go to your Settings/Profile and to the `Games` sub menu. Here you register your -new game by filling in its information. At the end of registration you are going -to get a `Client ID` and a `Client Secret`. These should not be shared. - -Open/create the file `mygame/server/conf/secret_settings.py` and add the following: - -```python - GRAPEVINE_CLIENT_ID = "" - GRAPEVINE_CLIENT_SECRET = "" -``` - -You can also customize the Grapevine channels you are allowed to connect to. This -is added to the `GRAPEVINE_CHANNELS` setting. You can see which channels are available -by going to the Grapevine online chat here: https://grapevine.haus/chat. - -Start/reload Evennia and log in as a privileged user. You should now have a new -command available: `@grapevine2chan`. This command is called like this: - - @grapevine2chan[/switches] = - -Here, the `evennia_channel` must be the name of an existing Evennia channel and -`grapevine_channel` one of the supported channels in `GRAPEVINE_CHANNELS`. - -> At the time of writing, the Grapevine network only has two channels: -> `testing` and `gossip`. Evennia defaults to allowing connecting to both. Use -> `testing` for trying your connection. - -## Setting up Grapevine, step by step - -You can connect Grapevine to any Evennia channel (so you could connect it to -the default *public* channel if you like), but for testing, let's set up a -new channel `gw`. - - @ccreate gw = This is connected to an gw channel! - -You will automatically join the new channel. - -Next we will create a connection to the Grapevine network. - - @grapevine2chan gw = gossip - -Evennia will now create a new connection and connect it to Grapevine. Connect -to https://grapevine.haus/chat to check. - - -Write something in the Evennia channel *gw* and check so a message appears in -the Grapevine chat. Write a reply in the chat and the grapevine bot should echo -it to your channel in-game. - -Your Evennia gamers can now chat with users on external Grapevine channels! diff --git a/docs/0.9.5/_sources/Guest-Logins.md.txt b/docs/0.9.5/_sources/Guest-Logins.md.txt deleted file mode 100644 index d1cf8cae21..0000000000 --- a/docs/0.9.5/_sources/Guest-Logins.md.txt +++ /dev/null @@ -1,29 +0,0 @@ -# Guest Logins - - -Evennia supports *guest logins* out of the box. A guest login is an anonymous, low-access account -and can be useful if you want users to have a chance to try out your game without committing to -creating a real account. - -Guest accounts are turned off by default. To activate, add this to your `game/settings.py` file: - - GUEST_ENABLED = True - -Henceforth users can use `connect guest` (in the default command set) to login with a guest account. -You may need to change your [Connection Screen](./Connection-Screen.md) to inform them of this -possibility. Guest accounts work differently from normal accounts - they are automatically *deleted* -whenever the user logs off or the server resets (but not during a reload). They are literally re- -usable throw-away accounts. - -You can add a few more variables to your `settings.py` file to customize your guests: - -- `BASE_GUEST_TYPECLASS` - the python-path to the default [typeclass](./Typeclasses.md) for guests. -Defaults to `"typeclasses.accounts.Guest"`. -- `PERMISSION_GUEST_DEFAULT` - [permission level](./Locks.md) for guest accounts. Defaults to `"Guests"`, -which is the lowest permission level in the hierarchy. -- `GUEST_START_LOCATION` - the `#dbref` to the starting location newly logged-in guests should -appear at. Defaults to `"#2` (Limbo). -- `GUEST_HOME` - guest home locations. Defaults to Limbo as well. -- `GUEST_LIST` - this is a list holding the possible guest names to use when entering the game. The -length of this list also sets how many guests may log in at the same time. By default this is a list -of nine names from `"Guest1"` to `"Guest9"`. diff --git a/docs/0.9.5/_sources/HAProxy-Config.md.txt b/docs/0.9.5/_sources/HAProxy-Config.md.txt deleted file mode 100644 index 7bdf1f99a5..0000000000 --- a/docs/0.9.5/_sources/HAProxy-Config.md.txt +++ /dev/null @@ -1,176 +0,0 @@ -# HAProxy Config (Optional) - -## Making Evennia, HTTPS and Secure Websockets play nicely together - -This we can do by installing a _proxy_ between Evennia and the outgoing ports of your server. -Essentially, -Evennia will think it's only running locally (on localhost, IP 127.0.0.1) - the proxy will -transparently -map that to the "real" outgoing ports and handle HTTPS/WSS for us. - -``` -Evennia <-> (inside-visible IP/ports) <-> Proxy <-> (outside-visible IP/ports) <-> Internet -``` - - -Here we will use [HAProxy](https://www.haproxy.org/), an open-source proxy that is easy to set up -and use. We will -also be using [LetsEncrypt](https://letsencrypt.org/getting-started/), especially the excellent -helper-program [Certbot](https://certbot.eff.org/instructions) which pretty much automates the whole -certificate setup process for us. - -Before starting you also need the following: - -- (optional) The host name of your game (like `myawesomegame.com`). This is something you must -previously have purchased from a _domain registrar_ and set up with DNS to point to the IP of your -server. -- If you don't have a domain name or haven't set it up yet, you must at least know the IP of your -server. Find this with `ifconfig` or similar from inside the server. If you use a hosting service -like DigitalOcean you can also find the droplet's IP address in the control panel. -- You must open port 80 in your firewall. This is used by Certbot below to auto-renew certificates. -So you can't really run another webserver alongside this setup without tweaking. -- You must open port 443 (HTTPS) in your firewall. -- You must open port 4002 (the default Websocket port) in your firewall. - - -## Getting certificates - -Certificates guarantee that you are you. Easiest is to get this with -[Letsencrypt](https://letsencrypt.org/getting-started/) and the -[Certbot](https://certbot.eff.org/instructions) program. Certbot has a lot of install instructions -for various operating systems. Here's for Debian/Ubuntu: - -``` -sudo apt install certbot -``` - -Make sure to stop Evennia and that no port-80 using service is running, then - -``` -sudo certbot certonly --standalone -``` - -You will get some questions you need to answer, such as an email to send certificate errors to and -the host name (or IP, supposedly) to use with this certificate. After this, the certificates will -end up in `/etc/letsencrypt/live//*pem` (example from Ubuntu). The critical files -for our purposes are `fullchain.pem` and `privkey.pem`. - -Certbot sets up a cron-job/systemd job to regularly renew the certificate. To check this works, try - -``` -sudo certbot renew --dry-run -``` - -The certificate is only valid for 3 months at a time, so make sure this test works (it requires port -80 to be open). Look up Certbot's page for more help. - -We are not quite done. HAProxy expects these two files to be _one_ file. - -``` -sudo cp /etc/letsencrypt/live//privkey.pem /etc/letsencrypt/live//.pem -sudo bash -c "cat /etc/letsencrypt/live//fullchain.pem >> -/etc/letsencrypt/live//.pem" -``` - -This will create a new `.pem` file by concatenating the two files together. The `yourhostname.pem` -file (or whatever you named it) is what we will use when the the HAProxy config file (below) asks -for "your-certificate.pem". - -## Installing and configuring HAProxy - -Installing HaProxy is usually as simple as: -``` -# Debian derivatives (Ubuntu, Mint etc) -sudo apt install haproxy - -# Redhat derivatives (dnf instead of yum for very recent Fedora distros) -sudo yum install haproxy - -``` - -Configuration of HAProxy is done in a single file. Put this wherever you like, for example in -your game dir; name it something like haproxy.conf. - -Here is an example tested on Centos7 and Ubuntu. Make sure to change the file to put in your own -values. - -``` -# base stuff to set up haproxy -global - log /dev/log local0 - chroot /var/lib/haproxy - maxconn 4000 - user haproxy - tune.ssl.default-dh-param 2048 - ## uncomment this when everything works - # daemon -defaults - mode http - option forwardfor - -# Evennia Specifics -listen evennia-https-website - bind : ssl no-sslv3 no-tlsv10 crt -/etc/letsencrypt/live//.pem - server localhost 127.0.0.1: - timeout client 10m - timeout server 10m - timeout connect 5m - -listen evennia-secure-websocket - bind : ssl no-sslv3 no-tlsv10 crt -/etc/letsencrypt/live//.pem - server localhost 127.0.0.1: - timeout client 10m - timeout server 10m - timeout connect 5m -``` - -## Putting it all together - -Get back to the Evennia game dir and edit mygame/server/conf/settings.py. Add: - -``` -WEBSERVER_INTERFACES = ['127.0.0.1'] -WEBSOCKET_CLIENT_INTERFACE = '127.0.0.1' -``` -and -``` -WEBSOCKET_CLIENT_URL="wss://fullhost.domain.name:4002/" -``` - -Make sure to reboot (stop + start) evennia completely: - -``` -evennia reboot -``` - - -Finally you start the proxy: - -``` -sudo haproxy -f /path/to/the/above/config_file.cfg -``` - -Make sure you can connect to your game from your browser and that you end up with an `https://` page -and can use the websocket webclient. - -Once everything works you may want to start the proxy automatically and in the background. Stop the -proxy with `Ctrl-C` and uncomment the line `# daemon` in the config file, then start the proxy again -- it will now start in the bacground. - -You may also want to have the proxy start automatically; this you can do with `cron`, the inbuilt -Linux mechanism for running things at specific times. - -``` -sudo crontab -e -``` - -Choose your editor and add a new line at the end of the crontab file that opens: - -``` -@reboot haproxy -f /path/to/the/above/config_file.cfg -``` - -Save the file and haproxy should start up automatically when you reboot the server. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Help-System-Tutorial.md.txt b/docs/0.9.5/_sources/Help-System-Tutorial.md.txt deleted file mode 100644 index 585168f629..0000000000 --- a/docs/0.9.5/_sources/Help-System-Tutorial.md.txt +++ /dev/null @@ -1,465 +0,0 @@ -# Help System Tutorial - - -**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](./Web-Tutorial.md).** Reading the three first parts of the [Django tutorial](https://docs.djangoproject.com/en/2.2/) might help as well. - -This tutorial will show you how to access the help system through your website. Both help commands -and regular help entries will be visible, depending on the logged-in user or an anonymous character. - -This tutorial will show you how to: - -- Create a new page to add to your website. -- Take advantage of a basic view and basic templates. -- Access the help system on your website. -- Identify whether the viewer of this page is logged-in and, if so, to what account. - -## Creating our app - -The first step is to create our new Django *app*. An app in Django can contain pages and -mechanisms: your website may contain different apps. Actually, the website provided out-of-the-box -by Evennia has already three apps: a "webclient" app, to handle the entire webclient, a "website" -app to contain your basic pages, and a third app provided by Django to create a simple admin -interface. So we'll create another app in parallel, giving it a clear name to represent our help -system. - -From your game directory, use the following command: - - evennia startapp help_system - -> Note: calling the app "help" would have been more explicit, but this name is already used by -Django. - -This will create a directory named `help_system` at the root of your game directory. It's a good -idea to keep things organized and move this directory in the "web" directory of your game. Your -game directory should look like: - - mygame/ - ... - web/ - help_system/ - ... - -The "web/help_system" directory contains files created by Django. We'll use some of them, but if -you want to learn more about them all, you should read [the Django -tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/). - -There is a last thing to be done: your folder has been added, but Django doesn't know about it, it -doesn't know it's a new app. We need to tell it, and we do so by editing a simple setting. Open -your "server/conf/settings.py" file and add, or edit, these lines: - -```python -# Web configuration -INSTALLED_APPS += ( - "web.help_system", -) -``` - -You can start Evennia if you want, and go to your website, probably at -[http://localhost:4001](http://localhost:4001) . You won't see anything different though: we added -the app but it's fairly empty. - -## Our new page - -At this point, our new *app* contains mostly empty files that you can explore. In order to create -a page for our help system, we need to add: - -- A *view*, dealing with the logic of our page. -- A *template* to display our new page. -- A new *URL* pointing to our page. - -> We could get away by creating just a view and a new URL, but that's not a recommended way to work -with your website. Building on templates is so much more convenient. - -### Create a view - -A *view* in Django is a simple Python function placed in the "views.py" file in your app. It will -handle the behavior that is triggered when a user asks for this information by entering a *URL* (the -connection between *views* and *URLs* will be discussed later). - -So let's create our view. You can open the "web/help_system/views.py" file and paste the following -lines: - -```python -from django.shortcuts import render - -def index(request): - """The 'index' view.""" - return render(request, "help_system/index.html") -``` - -Our view handles all code logic. This time, there's not much: when this function is called, it will -render the template we will now create. But that's where we will do most of our work afterward. - -### Create a template - -The `render` function called into our *view* asks the *template* `help_system/index.html`. The -*templates* of our apps are stored in the app directory, "templates" sub-directory. Django may have -created the "templates" folder already. If not, create it yourself. In it, create another folder -"help_system", and inside of this folder, create a file named "index.html". Wow, that's some -hierarchy. Your directory structure (starting from `web`) should look like this: - - web/ - help_system/ - ... - templates/ - help_system/ - index.html - -Open the "index.html" file and paste in the following lines: - -``` -{% extends "base.html" %} -{% block titleblock %}Help index{% endblock %} -{% block content %} -

Help index

-{% endblock %} -``` - -Here's a little explanation line by line of what this template does: - -1. It loads the "base.html" *template*. This describes the basic structure of all your pages, with -a menu at the top and a footer, and perhaps other information like images and things to be present -on each page. You can create templates that do not inherit from "base.html", but you should have a -good reason for doing so. -2. The "base.html" *template* defines all the structure of the page. What is left is to override -some sections of our pages. These sections are called *blocks*. On line 2, we override the block -named "blocktitle", which contains the title of our page. -3. Same thing here, we override the *block* named "content", which contains the main content of our -web page. This block is bigger, so we define it on several lines. -4. This is perfectly normal HTML code to display a level-2 heading. -5. And finally we close the *block* named "content". - -### Create a new URL - -Last step to add our page: we need to add a *URL* leading to it... otherwise users won't be able to -access it. The URLs of our apps are stored in the app's directory "urls.py" file. - -Open the "web/help_system/urls.py" file (you might have to create it) and write in it: - -```python -# URL patterns for the help_system app - -from django.urls import include, path -from web.help_system.views import index - -urlpatterns = [ - path(r'', index, name="help-index") -] -``` - -We also need to add our app as a namespace holder for URLS. Edit the file "web/urls.py" (you might -have to create this one too). In it you will find the `custom_patterns` variable. Replace it with: - -```python -custom_patterns = [ - path(r'help_system/', include('web.help_system.urls')), -] -``` - -When a user will ask for a specific *URL* on your site, Django will: - -1. Read the list of custom patterns defined in "web/urls.py". There's one pattern here, which -describes to Django that all URLs beginning by 'help/' should be sent to the 'help_system' app. The -'help/' part is removed. -2. Then Django will check the "web.help_system/urls.py" file. It contains only one URL, which is -empty. - -In other words, if the URL is '/help/', then Django will execute our defined view. - -### Let's see it work - -You can now reload or start Evennia. Open a tab in your browser and go to -[http://localhost:4001/help_system/](http://localhost:4001/help_system/) . If everything goes well, -you should see your new page... which isn't empty since Evennia uses our "base.html" *template*. In -the content of our page, there's only a heading that reads "help index". Notice that the title of -our page is "mygame - Help index" ("mygame" is replaced by the name of your game). - -From now on, it will be easier to move forward and add features. - -### A brief reminder - -We'll be trying the following things: - -- Have the help of commands and help entries accessed online. -- Have various commands and help entries depending on whether the user is logged in or not. - -In terms of pages, we'll have: - -- One to display the list of help topics. -- One to display the content of a help topic. - -The first one would link to the second. - -> Should we create two URLs? - -The answer is... maybe. It depends on what you want to do. We have our help index accessible -through the "/help_system/" URL. We could have the detail of a help entry accessible through -"/help_system/desc" (to see the detail of the "desc" command). The problem is that our commands or -help topics may contain special characters that aren't to be present in URLs. There are different -ways around this problem. I have decided to use a *GET variable* here, which would create URLs like -this: - - /help_system?name=desc - -If you use this system, you don't have to add a new URL: GET and POST variables are accessible -through our requests and we'll see how soon enough. - -## Handling logged-in users - -One of our requirements is to have a help system tailored to our accounts. If an account with admin -access logs in, the page should display a lot of commands that aren't accessible to common users. -And perhaps even some additional help topics. - -Fortunately, it's fairly easy to get the logged in account in our view (remember that we'll do most -of our coding there). The *request* object, passed to our function, contains a `user` attribute. -This attribute will always be there: we cannot test whether it's `None` or not, for instance. But -when the request comes from a user that isn't logged in, the `user` attribute will contain an -anonymous Django user. We then can use the `is_anonymous` method to see whether the user is logged- -in or not. Last gift by Evennia, if the user is logged in, `request.user` contains a reference to -an account object, which will help us a lot in coupling the game and online system. - -So we might end up with something like: - -```python -def index(request): - """The 'index' view.""" - user = request.user - if not user.is_anonymous() and user.character: - character = user.character -``` - -> Note: this code works when your MULTISESSION_MODE is set to 0 or 1. When it's above, you would -have something like: - -```python -def index(request): - """The 'index' view.""" - user = request.user - if not user.is_anonymous() and user.db._playable_characters: - character = user.db._playable_characters[0] -``` - -In this second case, it will select the first character of the account. - -But what if the user's not logged in? Again, we have different solutions. One of the most simple -is to create a character that will behave as our default character for the help system. You can -create it through your game: connect to it and enter: - - @charcreate anonymous - -The system should answer: - - Created new character anonymous. Use @ic anonymous to enter the game as this character. - -So in our view, we could have something like this: - -```python -from typeclasses.characters import Character - -def index(request): - """The 'index' view.""" - user = request.user - if not user.is_anonymous() and user.character: - character = user.character - else: - character = Character.objects.get(db_key="anonymous") -``` - -This time, we have a valid character no matter what: remember to adapt this code if you're running -in multisession mode above 1. - -## The full system - -What we're going to do is to browse through all commands and help entries, and list all the commands -that can be seen by this character (either our 'anonymous' character, or our logged-in character). - -The code is longer, but it presents the entire concept in our view. Edit the -"web/help_system/views.py" file and paste into it: - -```python -from django.http import Http404 -from django.shortcuts import render -from evennia.help.models import HelpEntry - -from typeclasses.characters import Character - -def index(request): - """The 'index' view.""" - user = request.user - if not user.is_anonymous() and user.character: - character = user.character - else: - character = Character.objects.get(db_key="anonymous") - - # Get the categories and topics accessible to this character - categories, topics = _get_topics(character) - - # If we have the 'name' in our GET variable - topic = request.GET.get("name") - if topic: - if topic not in topics: - raise Http404("This help topic doesn't exist.") - - topic = topics[topic] - context = { - "character": character, - "topic": topic, - } - return render(request, "help_system/detail.html", context) - else: - context = { - "character": character, - "categories": categories, - } - return render(request, "help_system/index.html", context) - -def _get_topics(character): - """Return the categories and topics for this character.""" - cmdset = character.cmdset.all()[0] - commands = cmdset.commands - entries = [entry for entry in HelpEntry.objects.all()] - categories = {} - topics = {} - - # Browse commands - for command in commands: - if not command.auto_help or not command.access(character): - continue - - # Create the template for a command - template = { - "name": command.key, - "category": command.help_category, - "content": command.get_help(character, cmdset), - } - - category = command.help_category - if category not in categories: - categories[category] = [] - categories[category].append(template) - topics[command.key] = template - - # Browse through the help entries - for entry in entries: - if not entry.access(character, 'view', default=True): - continue - - # Create the template for an entry - template = { - "name": entry.key, - "category": entry.help_category, - "content": entry.entrytext, - } - - category = entry.help_category - if category not in categories: - categories[category] = [] - categories[category].append(template) - topics[entry.key] = template - - # Sort categories - for entries in categories.values(): - entries.sort(key=lambda c: c["name"]) - - categories = list(sorted(categories.items())) - return categories, topics -``` - -That's a bit more complicated here, but all in all, it can be divided in small chunks: - -- The `index` function is our view: - - It begins by getting the character as we saw in the previous section. - - It gets the help topics (commands and help entries) accessible to this character. It's another -function that handles that part. - - If there's a *GET variable* "name" in our URL (like "/help?name=drop"), it will retrieve it. If -it's not a valid topic's name, it returns a *404*. Otherwise, it renders the template called -"detail.html", to display the detail of our topic. - - If there's no *GET variable* "name", render "index.html", to display the list of topics. -- The `_get_topics` is a private function. Its sole mission is to retrieve the commands a character -can execute, and the help entries this same character can see. This code is more Evennia-specific -than Django-specific, it will not be detailed in this tutorial. Just notice that all help topics -are stored in a dictionary. This is to simplify our job when displaying them in our templates. - -Notice that, in both cases when we asked to render a *template*, we passed to `render` a third -argument which is the dictionary of variables used in our templates. We can pass variables this -way, and we will use them in our templates. - -### The index template - -Let's look at our full "index" *template*. You can open the -"web/help_system/templates/help_system/index.html" file and paste the following into it: - -``` -{% extends "base.html" %} -{% block titleblock %}Help index{% endblock %} -{% block content %} -

Help index

-{% if categories %} - {% for category, topics in categories %} -

{{ category|capfirst }}

- - - {% for topic in topics %} - {% if forloop.counter|divisibleby:"5" %} - - - {% endif %} - - {% endfor %} - -
- {{ topic.name }}
- {% endfor %} -{% endif %} -{% endblock %} -``` - -This template is definitely more detailed. What it does is: - -1. Browse through all categories. -2. For all categories, display a level-2 heading with the name of the category. -3. All topics in a category (remember, they can be either commands or help entries) are displayed in -a table. The trickier part may be that, when the loop is above 5, it will create a new line. The -table will have 5 columns at the most per row. -4. For every cell in the table, we create a link redirecting to the detail page (see below). The -URL would look something like "help?name=say". We use `urlencode` to ensure special characters are -properly escaped. - -### The detail template - -It's now time to show the detail of a topic (command or help entry). You can create the file -"web/help_system/templates/help_system/detail.html". You can paste into it the following code: - -``` -{% extends "base.html" %} -{% block titleblock %}Help for {{ topic.name }}{% endblock %} -{% block content %} -

{{ topic.name|capfirst }} help topic

-

Category: {{ topic.category|capfirst }}

-{{ topic.content|linebreaks }} -{% endblock %} -``` - -This template is much easier to read. Some *filters* might be unknown to you, but they are just -used to format here. - -### Put it all together - -Remember to reload or start Evennia, and then go to -[http://localhost:4001/help_system](http://localhost:4001/help_system/). You should see the list of -commands and topics accessible by all characters. Try to login (click the "login" link in the menu -of your website) and go to the same page again. You should now see a more detailed list of commands -and help entries. Click on one to see its detail. - -## To improve this feature - -As always, a tutorial is here to help you feel comfortable adding new features and code by yourself. -Here are some ideas of things to improve this little feature: - -- Links at the bottom of the detail template to go back to the index might be useful. -- A link in the main menu to link to this page would be great... for the time being you have to -enter the URL, users won't guess it's there. -- Colors aren't handled at this point, which isn't exactly surprising. You could add it though. -- Linking help entries between one another won't be simple, but it would be great. For instance, if -you see a help entry about how to use several commands, it would be great if these commands were -themselves links to display their details. diff --git a/docs/0.9.5/_sources/Help-System.md.txt b/docs/0.9.5/_sources/Help-System.md.txt deleted file mode 100644 index 5dda8132ac..0000000000 --- a/docs/0.9.5/_sources/Help-System.md.txt +++ /dev/null @@ -1,122 +0,0 @@ -# Help System - - -An important part of Evennia is the online help system. This allows the players and staff alike to -learn how to use the game's commands as well as other information pertinent to the game. The help -system has many different aspects, from the normal editing of help entries from inside the game, to -auto-generated help entries during code development using the *auto-help system*. - -## Viewing the help database - -The main command is `help`: - - help [searchstring] - -This will show a list of help entries, ordered after categories. You will find two sections, -*Command help entries* and *Other help entries* (initially you will only have the first one). You -can use help to get more info about an entry; you can also give partial matches to get suggestions. -If you give category names you will only be shown the topics in that category. - - -## Command Auto-help system - -A common item that requires help entries are in-game commands. Keeping these entries up-to-date with -the actual source code functionality can be a chore. Evennia's commands are therefore auto- -documenting straight from the sources through its *auto-help system*. Only commands that you and -your character can actually currently use are picked up by the auto-help system. That means an admin -will see a considerably larger amount of help topics than a normal player when using the default -`help` command. - -The auto-help system uses the `__doc__` strings of your command classes and formats this to a nice- -looking help entry. This makes for a very easy way to keep the help updated - just document your -commands well and updating the help file is just a `@reload` away. There is no need to manually -create and maintain help database entries for commands; as long as you keep the docstrings updated -your help will be dynamically updated for you as well. - -Example (from a module with command definitions): - -```python - class CmdMyCmd(Command): - """ - mycmd - my very own command - - Usage: - mycmd[/switches] - - Switches: - test - test the command - run - do something else - - This is my own command that does this and that. - - """ - # [...] - - help_category = "General" # default - auto_help = True # default - - # [...] -``` - -The text at the very top of the command class definition is the class' `__doc__`-string and will be -shown to users looking for help. Try to use a consistent format - all default commands are using the -structure shown above. - -You should also supply the `help_category` class property if you can; this helps to group help -entries together for people to more easily find them. See the `help` command in-game to see the -default categories. If you don't specify the category, "General" is assumed. - -If you don't want your command to be picked up by the auto-help system at all (like if you want to -write its docs manually using the info in the next section or you use a [cmdset](./Command-Sets.md) that -has its own help functionality) you can explicitly set `auto_help` class property to `False` in your -command definition. - -Alternatively, you can keep the advantages of *auto-help* in commands, but control the display of -command helps. You can do so by overriding the command's `get_help()` method. By default, this -method will return the class docstring. You could modify it to add custom behavior: the text -returned by this method will be displayed to the character asking for help in this command. - -## Database help entries - -These are all help entries not involving commands (this is handled automatically by the [Command -Auto-help system](./Help-System.md#command-auto-help-system)). Non-automatic help entries describe how -your particular game is played - its rules, world descriptions and so on. - -A help entry consists of four parts: - -- The *topic*. This is the name of the help entry. This is what players search for when they are -looking for help. The topic can contain spaces and also partial matches will be found. -- The *help category*. Examples are *Administration*, *Building*, *Comms* or *General*. This is an -overall grouping of similar help topics, used by the engine to give a better overview. -- The *text* - the help text itself, of any length. -- locks - a [lock definition](./Locks.md). This can be used to limit access to this help entry, maybe -because it's staff-only or otherwise meant to be restricted. Help commands check for `access_type`s -`view` and `edit`. An example of a lock string would be `view:perm(Builders)`. - -You can create new help entries in code by using `evennia.create_help_entry()`. - -```python -from evennia import create_help_entry -entry = create_help_entry("emote", - "Emoting is important because ...", - category="Roleplaying", locks="view:all()") -``` - -From inside the game those with the right permissions can use the `@sethelp` command to add and -modify help entries. - - > @sethelp/add emote = The emote command is ... - -Using `@sethelp` you can add, delete and append text to existing entries. By default new entries -will go in the *General* help category. You can change this using a different form of the `@sethelp` -command: - - > @sethelp/add emote, Roleplaying = Emoting is important because ... - -If the category *Roleplaying* did not already exist, it is created and will appear in the help -index. - -You can, finally, define a lock for the help entry by following the category with a [lock -definition](./Locks.md): - - > @sethelp/add emote, Roleplaying, view:all() = Emoting is ... diff --git a/docs/0.9.5/_sources/How-To-Get-And-Give-Help.md.txt b/docs/0.9.5/_sources/How-To-Get-And-Give-Help.md.txt deleted file mode 100644 index 85ef9fb8a7..0000000000 --- a/docs/0.9.5/_sources/How-To-Get-And-Give-Help.md.txt +++ /dev/null @@ -1,71 +0,0 @@ -# How To Get And Give Help - - -### How to *get* Help - -If you cannot find what you are looking for in the [online documentation](./index.md), here's what to do: - -- If you think the documentation is not clear enough and are short on time, fill in our quick little -[online form][form] and let us know - no login required. Maybe the docs need to be improved or a new -tutorial added! Note that while this form is useful as a suggestion box we cannot answer questions -or reply to you. Use the discussion group or chat (linked below) if you want feedback. -- If you have trouble with a missing feature or a problem you think is a bug, go to the -[issue tracker][issues] and search to see if has been reported/suggested already. If you can't find an -existing entry create a new one. -- If you need help, want to start a discussion or get some input on something you are working on, -make a post to the [discussions group][group] This is technically a 'mailing list', but you don't -need to use e-mail; you can post and read all messages just as easily from your browser via the -online interface. -- If you want more direct discussions with developers and other users, consider dropping into our -IRC chat channel [#evennia][chat] on the *Freenode* network. Please note however that you have to be -patient if you don't get any response immediately; we are all in very different time zones and many -have busy personal lives. So you might have to hang around for a while - you'll get noticed -eventually! - - -### How to *give* Help - -Evennia is a completely non-funded project. It relies on the time donated by its users and -developers in order to progress. - -The first and easiest way you as a user can help us out is by taking part in -[community discussions][group] and by giving feedback on what is good or bad. Report bugs you find and features -you lack to our [issue tracker][issues]. Just the simple act of letting developers know you are out -there using their program is worth a lot. Generally mentioning and reviewing Evennia elsewhere is -also a nice way to spread the word. - -If you'd like to help develop Evennia more hands-on, here are some ways to get going: - -- Look through our [online documentation wiki](./index.md) and see if you -can help improve or expand the documentation (even small things like fixing typos!). You don't need -any particular permissions to edit the wiki. -- Send a message to our [discussion group][group] and/or our [IRC chat][chat] asking about what -needs doing, along with what your interests and skills are. -- Take a look at our [issue tracker][issues] and see if there's something you feel like taking on. -[here are bugs][issues-master] that need fixes. At any given time there may also be some -[bounties][issues-bounties] open - these are issues members of the community has put up money to see -fixed (if you want to put up a bounty yourself you can do so via our page on -[bountysource][bountysource]). -- Check out the [Contributing](./Contributing.md) page on how to practically contribute with code using -github. - -... And finally, if you want to help motivate and support development you can also drop some coins -in the developer's cup. You can [make a donation via PayPal][paypal] or, even better, -[become an Evennia patron on Patreon][patreon]! This is a great way to tip your hat and show that you -appreciate the work done with the server! Finally, if you want to encourage the community to resolve -a particular - -[form]: https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0 -[group]: http://groups.google.com/group/evennia/ -[issues]: https://github.com/evennia/evennia/issues -[issues-master]: https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Abug%20label%3Amaster-branch -[chat]: http://webchat.freenode.net/?channels=evennia -[paypal]: https://www.paypal.com/se/cgi-bin/webscr?cmd=_flow&SESSION=Z-VlOvfGjYq2qvCDOUGpb6C8Due7skT0qOklQEy5EbaD1f0eyEQaYlmCc8O&dispatch=5885d80a13c0db1f8e263663d3faee8d64ad11bbf4d2a5a1a0d303a50933f9 -b2 -[donate-img]: http://images-focus-opensocial.googleusercontent.com/gadgets/proxy?url=https://www.paypalobjects.com/en%255fUS/SE/i/btn/btn%255fdonateCC%255fLG.gif&container=focus&gadget=a&rewriteMime=image/* -[patreon]: https://www.patreon.com/griatch -[patreon-img]: http://www.evennia.com/_/rsrc/1424724909023/home/evennia_patreon_100x100.png -[issues-bounties]: https://github.com/evennia/evennia/labels/bounty -[bountysource]: https://www.bountysource.com/teams/evennia - - diff --git a/docs/0.9.5/_sources/How-to-connect-Evennia-to-Twitter.md.txt b/docs/0.9.5/_sources/How-to-connect-Evennia-to-Twitter.md.txt deleted file mode 100644 index 36dd7bdf78..0000000000 --- a/docs/0.9.5/_sources/How-to-connect-Evennia-to-Twitter.md.txt +++ /dev/null @@ -1,110 +0,0 @@ -# How to connect Evennia to Twitter - - -[Twitter](http://en.wikipedia.org/wiki/twitter) is an online social networking service that enables -users to send and read short 280-character messages called "tweets". Following is a short tutorial -explaining how to enable users to send tweets from inside Evennia. - -## Configuring Twitter - -You must first have a Twitter account. Log in and register an App at the [Twitter Dev -Site](https://apps.twitter.com/). Make sure you enable access to "write" tweets! - -To tweet from Evennia you will need both the "API Token" and the "API secret" strings as well as the -"Access Token" and "Access Secret" strings. - -Twitter changed their requirements to require a Mobile number on the Twitter account to register new -apps with write access. If you're unable to do this, please see [this Dev -post](https://dev.twitter.com/notifications/new-apps-registration) which describes how to get around -it. - -## Install the twitter python module - -To use Twitter you must install the [Twitter](https://pypi.python.org/pypi/twitter) Python module: - -``` -pip install python-twitter -``` - -## A basic tweet command - -Evennia doesn't have a `tweet` command out of the box so you need to write your own little -[Command](./Commands.md) in order to tweet. If you are unsure about how commands work and how to add -them, it can be an idea to go through the [Adding a Command Tutorial](./Adding-Command-Tutorial.md) -before continuing. - -You can create the command in a separate command module (something like `mygame/commands/tweet.py`) -or together with your other custom commands, as you prefer. - -This is how it can look: - -```python -import twitter -from evennia import Command - -# here you insert your unique App tokens -# from the Twitter dev site -TWITTER_API = twitter.Api(consumer_key='api_key', - consumer_secret='api_secret', - access_token_key='access_token_key', - access_token_secret='access_token_secret') - -class CmdTweet(Command): - """ - Tweet a message - - Usage: - tweet - - This will send a Twitter tweet to a pre-configured Twitter account. - A tweet has a maximum length of 280 characters. - """ - - key = "tweet" - locks = "cmd:pperm(tweet) or pperm(Developers)" - help_category = "Comms" - - def func(self): - "This performs the tweet" - - caller = self.caller - tweet = self.args - - if not tweet: - caller.msg("Usage: tweet ") - return - - tlen = len(tweet) - if tlen > 280: - caller.msg("Your tweet was %i chars long (max 280)." % tlen) - return - - # post the tweet - TWITTER_API.PostUpdate(tweet) - - caller.msg("You tweeted:\n%s" % tweet) -``` - -Be sure to substitute your own actual API/Access keys and secrets in the appropriate places. - -We default to limiting tweet access to players with `Developers`-level access *or* to those players -that have the permission "tweet" (allow individual characters to tweet with `@perm/player playername -= tweet`). You may change the [lock](./Locks.md) as you feel is appropriate. Change the overall -permission to `Players` if you want everyone to be able to tweet. - -Now add this command to your default command set (e.g in `mygame/commands/defalt_cmdsets.py`") and -reload the server. From now on those with access can simply use `tweet ` to see the tweet -posted from the game's Twitter account. - -## Next Steps - -This shows only a basic tweet setup, other things to do could be: - -* Auto-Adding the character name to the tweet -* More error-checking of postings -* Changing locks to make tweeting open to more people -* Echo your tweets to an in-game channel - -Rather than using an explicit command you can set up a Script to send automatic tweets, for example -to post updated game stats. See the [Tweeting Game Stats tutorial](./Tutorial-Tweeting-Game-Stats.md) for -help. diff --git a/docs/0.9.5/_sources/IRC.md.txt b/docs/0.9.5/_sources/IRC.md.txt deleted file mode 100644 index 9b3684dee7..0000000000 --- a/docs/0.9.5/_sources/IRC.md.txt +++ /dev/null @@ -1,90 +0,0 @@ -# IRC - - -_Disambiguation: This page is related to using IRC inside an Evennia game. To join the official -Evennia IRC chat, connect to irc.freenode.net and join #evennia. Alternatively, you can [join our -Discord](https://discord.gg/NecFePw), which is mirrored to IRC._ - -[IRC (Internet Relay Chat)](http://en.wikipedia.org/wiki/Internet_Relay_Chat) is a long standing -chat protocol used by many open-source projects for communicating in real time. By connecting one of -Evennia's [Channels](./Communications.md) to an IRC channel you can communicate also with people not on -an mud themselves. You can also use IRC if you are only running your Evennia MUD locally on your -computer (your game doesn't need to be open to the public)! All you need is an internet connection. -For IRC operation you also need [twisted.words](http://twistedmatrix.com/trac/wiki/TwistedWords). -This is available simply as a package *python-twisted-words* in many Linux distros, or directly -downloadable from the link. - -## Configuring IRC - -To configure IRC, you'll need to activate it in your settings file. - -```python - IRC_ENABLED = True -``` - -Start Evennia and log in as a privileged user. You should now have a new command available: -`@irc2chan`. This command is called like this: - - @irc2chan[/switches] = <#irchannel> - -If you already know how IRC works, this should be pretty self-evident to use. Read the help entry -for more features. - -## Setting up IRC, step by step - -You can connect IRC to any Evennia channel (so you could connect it to the default *public* channel -if you like), but for testing, let's set up a new channel `irc`. - - @ccreate irc = This is connected to an irc channel! - -You will automatically join the new channel. - -Next we will create a connection to an external IRC network and channel. There are many, many IRC -nets. [Here is a list](http://www.irchelp.org/irchelp/networks/popular.html) of some of the biggest -ones, the one you choose is not really very important unless you want to connect to a particular -channel (also make sure that the network allows for "bots" to connect). - -For testing, we choose the *Freenode* network, `irc.freenode.net`. We will connect to a test -channel, let's call it *#myevennia-test* (an IRC channel always begins with `#`). It's best if you -pick an obscure channel name that didn't exist previously - if it didn't exist it will be created -for you. - -> *Don't* connect to `#evennia` for testing and debugging, that is Evennia's official chat channel! -You *are* welcome to connect your game to `#evennia` once you have everything working though - it -can be a good way to get help and ideas. But if you do, please do so with an in-game channel open -only to your game admins and developers). - -The *port* needed depends on the network. For Freenode this is `6667`. - -What will happen is that your Evennia server will connect to this IRC channel as a normal user. This -"user" (or "bot") needs a name, which you must also supply. Let's call it "mud-bot". - -To test that the bot connects correctly you also want to log onto this channel with a separate, -third-party IRC client. There are hundreds of such clients available. If you use Firefox, the -*Chatzilla* plugin is good and easy. Freenode also offers its own web-based chat page. Once you -have connected to a network, the command to join is usually `/join #channelname` (don't forget the -#). - -Next we connect Evennia with the IRC channel. - - @irc2chan irc = irc.freenode.net 6667 #myevennia-test mud-bot - -Evennia will now create a new IRC bot `mud-bot` and connect it to the IRC network and the channel -#myevennia. If you are connected to the IRC channel you will soon see the user *mud-bot* connect. - -Write something in the Evennia channel *irc*. - - irc Hello, World! - [irc] Anna: Hello, World! - -If you are viewing your IRC channel with a separate IRC client you should see your text appearing -there, spoken by the bot: - - mud-bot> [irc] Anna: Hello, World! - -Write `Hello!` in your IRC client window and it will appear in your normal channel, marked with the -name of the IRC channel you used (#evennia here). - - [irc] Anna@#myevennia-test: Hello! - -Your Evennia gamers can now chat with users on external IRC channels! diff --git a/docs/0.9.5/_sources/Implementing-a-game-rule-system.md.txt b/docs/0.9.5/_sources/Implementing-a-game-rule-system.md.txt deleted file mode 100644 index 26fd0471c6..0000000000 --- a/docs/0.9.5/_sources/Implementing-a-game-rule-system.md.txt +++ /dev/null @@ -1,302 +0,0 @@ -# Implementing a game rule system - - -The simplest way to create an online roleplaying game (at least from a code perspective) is to -simply grab a paperback RPG rule book, get a staff of game masters together and start to run scenes -with whomever logs in. Game masters can roll their dice in front of their computers and tell the -players the results. This is only one step away from a traditional tabletop game and puts heavy -demands on the staff - it is unlikely staff will be able to keep up around the clock even if they -are very dedicated. - -Many games, even the most roleplay-dedicated, thus tend to allow for players to mediate themselves -to some extent. A common way to do this is to introduce *coded systems* - that is, to let the -computer do some of the heavy lifting. A basic thing is to add an online dice-roller so everyone can -make rolls and make sure noone is cheating. Somewhere at this level you find the most bare-bones -roleplaying MUSHes. - -The advantage of a coded system is that as long as the rules are fair the computer is too - it makes -no judgement calls and holds no personal grudges (and cannot be accused of holding any). Also, the -computer doesn't need to sleep and can always be online regardless of when a player logs on. The -drawback is that a coded system is not flexible and won't adapt to the unprogrammed actions human -players may come up with in role play. For this reason many roleplay-heavy MUDs do a hybrid -variation - they use coded systems for things like combat and skill progression but leave role play -to be mostly freeform, overseen by staff game masters. - -Finally, on the other end of the scale are less- or no-roleplay games, where game mechanics (and -thus player fairness) is the most important aspect. In such games the only events with in-game value -are those resulting from code. Such games are very common and include everything from hack-and-slash -MUDs to various tactical simulations. - -So your first decision needs to be just what type of system you are aiming for. This page will try -to give some ideas for how to organize the "coded" part of your system, however big that may be. - -## Overall system infrastructure - -We strongly recommend that you code your rule system as stand-alone as possible. That is, don't -spread your skill check code, race bonus calculation, die modifiers or what have you all over your -game. - -- Put everything you would need to look up in a rule book into a module in `mygame/world`. Hide away -as much as you can. Think of it as a black box (or maybe the code representation of an all-knowing -game master). The rest of your game will ask this black box questions and get answers back. Exactly -how it arrives at those results should not need to be known outside the box. Doing it this way -makes it easier to change and update things in one place later. -- Store only the minimum stuff you need with each game object. That is, if your Characters need -values for Health, a list of skills etc, store those things on the Character - don't store how to -roll or change them. -- Next is to determine just how you want to store things on your Objects and Characters. You can -choose to either store things as individual [Attributes](./Attributes.md), like `character.db.STR=34` and -`character.db.Hunting_skill=20`. But you could also use some custom storage method, like a -dictionary `character.db.skills = {"Hunting":34, "Fishing":20, ...}`. A much more fancy solution is -to look at the Ainneve [Trait -handler](https://github.com/evennia/ainneve/blob/master/world/traits.py). Finally you could even go -with a [custom django model](./New-Models.md). Which is the better depends on your game and the -complexity of your system. -- Make a clear [API](http://en.wikipedia.org/wiki/Application_programming_interface) into your -rules. That is, make methods/functions that you feed with, say, your Character and which skill you -want to check. That is, you want something similar to this: - - ```python - from world import rules - result = rules.roll_skill(character, "hunting") - result = rules.roll_challenge(character1, character2, "swords") - ``` - -You might need to make these functions more or less complex depending on your game. For example the -properties of the room might matter to the outcome of a roll (if the room is dark, burning etc). -Establishing just what you need to send into your game mechanic module is a great way to also get a -feel for what you need to add to your engine. - -## Coded systems - -Inspired by tabletop role playing games, most game systems mimic some sort of die mechanic. To this -end Evennia offers a full [dice -roller](https://github.com/evennia/evennia/blob/master/evennia/contrib/dice.py) in its `contrib` -folder. For custom implementations, Python offers many ways to randomize a result using its in-built -`random` module. No matter how it's implemented, we will in this text refer to the action of -determining an outcome as a "roll". - -In a freeform system, the result of the roll is just compared with values and people (or the game -master) just agree on what it means. In a coded system the result now needs to be processed somehow. -There are many things that may happen as a result of rule enforcement: - -- Health may be added or deducted. This can effect the character in various ways. -- Experience may need to be added, and if a level-based system is used, the player might need to be -informed they have increased a level. -- Room-wide effects need to be reported to the room, possibly affecting everyone in the room. - -There are also a slew of other things that fall under "Coded systems", including things like -weather, NPC artificial intelligence and game economy. Basically everything about the world that a -Game master would control in a tabletop role playing game can be mimicked to some level by coded -systems. - - -## Example of Rule module - -Here is a simple example of a rule module. This is what we assume about our simple example game: -- Characters have only four numerical values: - - Their `level`, which starts at 1. - - A skill `combat`, which determines how good they are at hitting things. Starts between 5 and -10. - - Their Strength, `STR`, which determine how much damage they do. Starts between 1 and 10. - - Their Health points, `HP`, which starts at 100. -- When a Character reaches `HP = 0`, they are presumed "defeated". Their HP is reset and they get a -failure message (as a stand-in for death code). -- Abilities are stored as simple Attributes on the Character. -- "Rolls" are done by rolling a 100-sided die. If the result plus the `combat`value is greater than -the other character, it's a success and damage is rolled. Damage is rolled as a six-sided die + the -value of `STR` (for this example we ignore weapons and assume `STR` is all that matters). -- Every successful `attack` roll gives 1-3 experience points (`XP`). Every time the number of `XP` -reaches `(level + 1) ** 2`, the Character levels up. When leveling up, the Character's `combat` -value goes up by 2 points and `STR` by one (this is a stand-in for a real progression system). -- Characters with the name `dummy` will gain no XP. Allowing us to make a dummy to train with. - -### Character - -The Character typeclass is simple. It goes in `mygame/typeclasses/characters.py`. There is already -an empty `Character` class there that Evennia will look to and use. - -```python -from random import randint -from evennia import DefaultCharacter - -class Character(DefaultCharacter): - """ - Custom rule-restricted character. We randomize - the initial skill and ability values bettween 1-10. - """ - def at_object_creation(self): - "Called only when first created" - self.db.level = 1 - self.db.HP = 100 - self.db.XP = 0 - self.db.STR = randint(1, 10) - self.db.combat = randint(5, 10) -``` - -`@reload` the server to load up the new code. Doing `examine self` will however *not* show the new -Attributes on yourself. This is because the `at_object_creation` hook is only called on *new* -Characters. Your Character was already created and will thus not have them. To force a reload, use -the following command: - -``` -@typeclass/force/reset self -``` - -The `examine self` command will now show the new Attributes. - -### Rule module - -This is a module `mygame/world/rules.py`. - -```python -""" -mygame/world/rules.py -""" -from random import randint - - -def roll_hit(): - "Roll 1d100" - return randint(1, 100) - - -def roll_dmg(): - "Roll 1d6" - return randint(1, 6) - - -def check_defeat(character): - "Checks if a character is 'defeated'." - if character.db.HP <= 0: - character.msg("You fall down, defeated!") - character.db.HP = 100 # reset - - -def add_XP(character, amount): - "Add XP to character, tracking level increases." - if "training_dummy" in character.tags.all(): # don't allow the training dummy to level - character.location.msg_contents("Training Dummies can not gain XP.") - return - else: - character.db.XP += amount - if character.db.XP >= (character.db.level + 1) ** 2: - character.db.level += 1 - character.db.STR += 1 - character.db.combat += 2 - character.msg("You are now level %i!" % character.db.level) - - -def skill_combat(*args): - """ - This determines outcome of combat. The one who - rolls under their combat skill AND higher than - their opponent's roll hits. - """ - char1, char2 = args - roll1, roll2 = roll_hit(), roll_hit() - failtext = "You are hit by %s for %i damage!" - wintext = "You hit %s for %i damage!" - xp_gain = randint(1, 3) - - # display messages showing attack numbers - attack_message = f"{char1.name} rolls {roll1} + combat {char1.db.combat} " \ - f"= {char1.db.combat+roll1} | {char2.name} rolls {roll2} + combat " \ - f"{char2.db.combat} = {char2.db.combat+roll2}" - char1.location.msg_contents(attack_message) - attack_summary = f"{char1.name} {char1.db.combat+roll1} " \ - f"vs {char2.name} {char2.db.combat+roll2}" - char1.location.msg_contents(attack_summary) - - if char1.db.combat+roll1 > char2.db.combat+roll2: - # char 1 hits - dmg = roll_dmg() + char1.db.STR - char1.msg(wintext % (char2, dmg)) - add_XP(char1, xp_gain) - char2.msg(failtext % (char1, dmg)) - char2.db.HP -= dmg - check_defeat(char2) - elif char2.db.combat+roll2 > char1.db.combat+roll1: - # char 2 hits - dmg = roll_dmg() + char2.db.STR - char1.msg(failtext % (char2, dmg)) - char1.db.HP -= dmg - check_defeat(char1) - char2.msg(wintext % (char1, dmg)) - add_XP(char2, xp_gain) - else: - # a draw - drawtext = "Neither of you can find an opening." - char1.msg(drawtext) - char2.msg(drawtext) - - -SKILLS = {"combat": skill_combat} - - -def roll_challenge(character1, character2, skillname): - """ - Determine the outcome of a skill challenge between - two characters based on the skillname given. - """ - if skillname in SKILLS: - SKILLS[skillname](character1, character2) - else: - raise RunTimeError("Skillname %s not found." % skillname) -``` - -These few functions implement the entirety of our simple rule system. We have a function to check -the "defeat" condition and reset the `HP` back to 100 again. We define a generic "skill" function. -Multiple skills could all be added with the same signature; our `SKILLS` dictionary makes it easy to -look up the skills regardless of what their actual functions are called. Finally, the access -function `roll_challenge` just picks the skill and gets the result. - -In this example, the skill function actually does a lot - it not only rolls results, it also informs -everyone of their results via `character.msg()` calls. - -### Attack Command - -Here is an example of usage in a game command: - -```python -from evennia import Command -from world import rules - -class CmdAttack(Command): - """ - attack an opponent - - Usage: - attack - - This will attack a target in the same room, dealing - damage with your bare hands. - """ - def func(self): - "Implementing combat" - - caller = self.caller - if not self.args: - caller.msg("You need to pick a target to attack.") - return - - target = caller.search(self.args) - if target: - rules.roll_challenge(caller, target, "combat") -``` - -Note how simple the command becomes and how generic you can make it. It becomes simple to offer any -number of Combat commands by just extending this functionality - you can easily roll challenges and -pick different skills to check. And if you ever decided to, say, change how to determine hit chance, -you don't have to change every command, but need only change the single `roll_hit` function inside -your `rules` module. - -### Training dummy -Create a dummy to test the attack command. Give it a `training_dummy` tag anything with the tag -`training_dummy` will not gain xp.
-> create/drop dummy:characters.Character
-tag dummy=training_dummy - -Attack it with your new command - -> attack dummy \ No newline at end of file diff --git a/docs/0.9.5/_sources/Inputfuncs.md.txt b/docs/0.9.5/_sources/Inputfuncs.md.txt deleted file mode 100644 index 7a708021a8..0000000000 --- a/docs/0.9.5/_sources/Inputfuncs.md.txt +++ /dev/null @@ -1,174 +0,0 @@ -# Inputfuncs - - -An *inputfunc* is an Evennia function that handles a particular input (an [inputcommand](./OOB.md)) from -the client. The inputfunc is the last destination for the inputcommand along the [ingoing message -path](./Messagepath.md#the-ingoing-message-path). The inputcommand always has the form `(commandname, -(args), {kwargs})` and Evennia will use this to try to find and call an inputfunc on the form - -```python - def commandname(session, *args, **kwargs): - # ... - -``` -Or, if no match was found, it will call an inputfunc named "default" on this form - -```python - def default(session, cmdname, *args, **kwargs): - # cmdname is the name of the mismatched inputcommand - -``` - -## Adding your own inputfuncs - -This is simple. Add a function on the above form to `mygame/server/conf/inputfuncs.py`. Your -function must be in the global, outermost scope of that module and not start with an underscore -(`_`) to be recognized as an inputfunc. Reload the server. That's it. To overload a default -inputfunc (see below), just add a function with the same name. - -The modules Evennia looks into for inputfuncs are defined in the list `settings.INPUT_FUNC_MODULES`. -This list will be imported from left to right and later imported functions will replace earlier -ones. - -## Default inputfuncs - -Evennia defines a few default inputfuncs to handle the common cases. These are defined in -`evennia/server/inputfuncs.py`. - -### text - - - Input: `("text", (textstring,), {})` - - Output: Depends on Command triggered - -This is the most common of inputcommands, and the only one supported by every traditional mud. The -argument is usually what the user sent from their command line. Since all text input from the user -like this is considered a [Command](./Commands.md), this inputfunc will do things like nick-replacement -and then pass on the input to the central Commandhandler. - -### echo - - - Input: `("echo", (args), {})` - - Output: `("text", ("Echo returns: %s" % args), {})` - -This is a test input, which just echoes the argument back to the session as text. Can be used for -testing custom client input. - -### default - -The default function, as mentioned above, absorbs all non-recognized inputcommands. The default one -will just log an error. - -### client_options - - - Input: `("client_options, (), {key:value, ...})` - - Output: - - normal: None - - get: `("client_options", (), {key:value, ...})` - -This is a direct command for setting protocol options. These are settable with the `@option` -command, but this offers a client-side way to set them. Not all connection protocols makes use of -all flags, but here are the possible keywords: - - - get (bool): If this is true, ignore all other kwargs and immediately return the current settings -as an outputcommand `("client_options", (), {key=value, ...})`- - - client (str): A client identifier, like "mushclient". - - version (str): A client version - - ansi (bool): Supports ansi colors - - xterm256 (bool): Supports xterm256 colors or not - - mxp (bool): Supports MXP or not - - utf-8 (bool): Supports UTF-8 or not - - screenreader (bool): Screen-reader mode on/off - - mccp (bool): MCCP compression on/off - - screenheight (int): Screen height in lines - - screenwidth (int): Screen width in characters - - inputdebug (bool): Debug input functions - - nomarkup (bool): Strip all text tags - - raw (bool): Leave text tags unparsed - -> Note that there are two GMCP aliases to this inputfunc - `hello` and `supports_set`, which means -it will be accessed via the GMCP `Hello` and `Supports.Set` instructions assumed by some clients. - -### get_client_options - - - Input: `("get_client_options, (), {key:value, ...})` - - Output: `("client_options, (), {key:value, ...})` - -This is a convenience wrapper that retrieves the current options by sending "get" to -`client_options` above. - -### get_inputfuncs - -- Input: `("get_inputfuncs", (), {})` -- Output: `("get_inputfuncs", (), {funcname:docstring, ...})` - -Returns an outputcommand on the form `("get_inputfuncs", (), {funcname:docstring, ...})` - a list of -all the available inputfunctions along with their docstrings. - -### login - -> Note: this is currently experimental and not very well tested. - - - Input: `("login", (username, password), {})` - - Output: Depends on login hooks - -This performs the inputfunc version of a login operation on the current Session. - -### get_value - -Input: `("get_value", (name, ), {})` -Output: `("get_value", (value, ), {})` - -Retrieves a value from the Character or Account currently controlled by this Session. Takes one -argument, This will only accept particular white-listed names, you'll need to overload the function -to expand. By default the following values can be retrieved: - - - "name" or "key": The key of the Account or puppeted Character. - - "location": Name of the current location, or "None". - - "servername": Name of the Evennia server connected to. - -### repeat - - - Input: `("repeat", (), {"callback":funcname, - "interval": secs, "stop": False})` - - Output: Depends on the repeated function. Will return `("text", (repeatlist),{}` with a list of -accepted names if given an unfamiliar callback name. - -This will tell evennia to repeatedly call a named function at a given interval. Behind the scenes -this will set up a [Ticker](./TickerHandler.md). Only previously acceptable functions are possible to -repeat-call in this way, you'll need to overload this inputfunc to add the ones you want to offer. -By default only two example functions are allowed, "test1" and "test2", which will just echo a text -back at the given interval. Stop the repeat by sending `"stop": True` (note that you must include -both the callback name and interval for Evennia to know what to stop). - -### unrepeat - - - Input: `("unrepeat", (), ("callback":funcname, - "interval": secs)` - - Output: None - -This is a convenience wrapper for sending "stop" to the `repeat` inputfunc. - -### monitor - - - Input: `("monitor", (), ("name":field_or_argname, stop=False)` - - Output (on change): `("monitor", (), {"name":name, "value":value})` - -This sets up on-object monitoring of Attributes or database fields. Whenever the field or Attribute -changes in any way, the outputcommand will be sent. This is using the -[MonitorHandler](./MonitorHandler.md) behind the scenes. Pass the "stop" key to stop monitoring. Note -that you must supply the name also when stopping to let the system know which monitor should be -cancelled. - -Only fields/attributes in a whitelist are allowed to be used, you have to overload this function to -add more. By default the following fields/attributes can be monitored: - - - "name": The current character name - - "location": The current location - - "desc": The description Argument - -## unmonitor - - - Input: `("unmonitor", (), {"name":name})` - - Output: None - -A convenience wrapper that sends "stop" to the `monitor` function. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Installing-on-Android.md.txt b/docs/0.9.5/_sources/Installing-on-Android.md.txt deleted file mode 100644 index 0a41c3ee04..0000000000 --- a/docs/0.9.5/_sources/Installing-on-Android.md.txt +++ /dev/null @@ -1,143 +0,0 @@ -# Installing on Android - - -This page describes how to install and run the Evennia server on an Android phone. This will involve -installing a slew of third-party programs from the Google Play store, so make sure you are okay with -this before starting. - -## Install Termux - -The first thing to do is install a terminal emulator that allows a "full" version of linux to be -run. Note that Android is essentially running on top of linux so if you have a rooted phone, you may -be able to skip this step. You *don't* require a rooted phone to install Evennia though. - -Assuming we do not have root, we will install -[Termux](https://play.google.com/store/apps/details?id=com.termux&hl=en). -Termux provides a base installation of Linux essentials, including apt and Python, and makes them -available under a writeable directory. It also gives us a terminal where we can enter commands. By -default, Android doesn't give you permissions to the root folder, so Termux pretends that its own -installation directory is the root directory. - -Termux will set up a base system for us on first launch, but we will need to install some -prerequisites for Evennia. Commands you should run in Termux will look like this: - -``` -$ cat file.txt -``` -The `$` symbol is your prompt - do not include it when running commands. - -## Prerequisites - -To install some of the libraries Evennia requires, namely Pillow and Twisted, we have to first -install some packages they depend on. In Termux, run the following -``` -$ pkg install -y clang git zlib ndk-sysroot libjpeg-turbo libcrypt python -``` - -Termux ships with Python 3, perfect. Python 3 has venv (virtualenv) and pip (Python's module -installer) built-in. - -So, let's set up our virtualenv. This keeps the Python packages we install separate from the system -versions. - -``` -$ cd -$ python3 -m venv evenv -``` - -This will create a new folder, called `evenv`, containing the new python executable. -Next, let's activate our new virtualenv. Every time you want to work on Evennia, you need to run the -following command: - -``` -$ source evenv/bin/activate -``` - -Your prompt will change to look like this: -``` -(evenv) $ -``` -Update the updaters and installers in the venv: pip, setuptools and wheel. -``` -python3 -m pip install --upgrade pip setuptools wheel -``` - -### Installing Evennia - -Now that we have everything in place, we're ready to download and install Evennia itself. - -Mysterious incantations -``` -export LDFLAGS="-L/data/data/com.termux/files/usr/lib/" -export CFLAGS="-I/data/data/com.termux/files/usr/include/" -``` -(these tell clang, the C compiler, where to find the bits for zlib when building Pillow) - -Install the latest Evennia in a way that lets you edit the source -``` -(evenv) $ pip install --upgrade -e 'git+https://github.com/evennia/evennia#egg=evennia' -``` - -This step will possibly take quite a while - we are downloading Evennia and are then installing it, -building all of the requirements for Evennia to run. If you run into trouble on this step, please -see [Troubleshooting](./Installing-on-Android.md#troubleshooting). - -You can go to the dir where Evennia is installed with `cd $VIRTUAL_ENV/src/evennia`. `git grep -(something)` can be handy, as can `git diff` - -### Final steps - -At this point, Evennia is installed on your phone! You can now continue with the original [Getting -Started](./Getting-Started.md) instruction, we repeat them here for clarity. - -To start a new game: - -``` -(evenv) $ evennia --init mygame -(evenv) $ ls -mygame evenv -``` - -To start the game for the first time: - -``` -(evenv) $ cd mygame -(evenv) $ evennia migrate -(evenv) $ evennia start -``` - -Your game should now be running! Open a web browser at http://localhost:4001 or point a telnet -client to localhost:4000 and log in with the user you created. - -## Running Evennia - -When you wish to run Evennia, get into your Termux console and make sure you have activated your -virtualenv as well as are in your game's directory. You can then run evennia start as normal. - -``` -$ cd ~ && source evenv/bin/activate -(evenv) $ cd mygame -(evenv) $ evennia start -``` - -You may wish to look at the [Linux Instructions](./Getting-Started.md#linux-install) for more. - -## Caveats - -- Android's os module doesn't support certain functions - in particular getloadavg. Thusly, running -the command @server in-game will throw an exception. So far, there is no fix for this problem. -- As you might expect, performance is not amazing. -- Android is fairly aggressive about memory handling, and you may find that your server process is -killed if your phone is heavily taxed. Termux seems to keep a notification up to discourage this. - -## Troubleshooting - -As time goes by and errors are reported, this section will be added to. - -Some steps to try anyway: -* Make sure your packages are up-to-date, try running `pkg update && pkg upgrade -y` -* Make sure you've installed the clang package. If not, try `pkg install clang -y` -* Make sure you're in the right directory. `cd ~/mygame -* Make sure you've sourced your virtualenv. type `cd && source evenv/bin/activate` -* See if a shell will start: `cd ~/mygame ; evennia shell` -* Look at the log files in ~/mygame/server/logs/ \ No newline at end of file diff --git a/docs/0.9.5/_sources/Internationalization.md.txt b/docs/0.9.5/_sources/Internationalization.md.txt deleted file mode 100644 index f521221b41..0000000000 --- a/docs/0.9.5/_sources/Internationalization.md.txt +++ /dev/null @@ -1,88 +0,0 @@ -# Internationalization - - -*Internationalization* (often abbreviated *i18n* since there are 18 characters between the first "i" -and the last "n" in that word) allows Evennia's core server to return texts in other languages than -English - without anyone having to edit the source code. Take a look at the `locale` directory of -the Evennia installation, there you will find which languages are currently supported. - -## Changing server language - -Change language by adding the following to your `mygame/server/conf/settings.py` file: - -```python - USE_I18N = True - LANGUAGE_CODE = 'en' -``` - -Here `'en'` should be changed to the abbreviation for one of the supported languages found in -`locale/`. Restart the server to activate i18n. The two-character international language codes are -found [here](http://www.science.co.il/Language/Codes.asp). - -> Windows Note: If you get errors concerning `gettext` or `xgettext` on Windows, see the [Django -documentation](https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#gettext-on-windows). A -self-installing and up-to-date version of gettext for Windows (32/64-bit) is available on -[Github](https://github.com/mlocati/gettext-iconv-windows). - -## Translating Evennia - -> **Important Note:** Evennia offers translations of hard-coded strings in the server, things like -"Connection closed" or "Server restarted", strings that end users will see and which game devs are -not supposed to change on their own. Text you see in the log file or on the command line (like error -messages) are generally *not* translated (this is a part of Python). - -> In addition, text in default Commands and in default Typeclasses will *not* be translated by -switching *i18n* language. To translate Commands and Typeclass hooks you must overload them in your -game directory and translate their returns to the language you want. This is because from Evennia's -perspective, adding *i18n* code to commands tend to add complexity to code that is *meant* to be -changed anyway. One of the goals of Evennia is to keep the user-changeable code as clean and easy- -to-read as possible. - -If you cannot find your language in `evennia/locale/` it's because noone has translated it yet. -Alternatively you might have the language but find the translation bad ... You are welcome to help -improve the situation! - -To start a new translation you need to first have cloned the Evennia repositry with GIT and -activated a python virtualenv as described on the [Getting Started](./Getting-Started.md) page. You now -need to `cd` to the `evennia/` directory. This is *not* your created game folder but the main -Evennia library folder. If you see a folder `locale/` then you are in the right place. From here you -run: - - evennia makemessages - -where `` is the [two-letter locale code](http://www.science.co.il/Language/Codes.asp) -for the language you want, like 'sv' for Swedish or 'es' for Spanish. After a moment it will tell -you the language has been processed. For instance: - - evennia makemessages sv - -If you started a new language a new folder for that language will have emerged in the `locale/` -folder. Otherwise the system will just have updated the existing translation with eventual new -strings found in the server. Running this command will not overwrite any existing strings so you can -run it as much as you want. - -> Note: in Django, the `makemessages` command prefixes the locale name by the `-l` option (`... -makemessages -l sv` for instance). This syntax is not allowed in Evennia, due to the fact that `-l` -is the option to tail log files. Hence, `makemessages` doesn't use the `-l` flag. - -Next head to `locale//LC_MESSAGES` and edit the `**.po` file you find there. You can -edit this with a normal text editor but it is easiest if you use a special po-file editor from the -web (search the web for "po editor" for many free alternatives). - -The concept of translating is simple, it's just a matter of taking the english strings you find in -the `**.po` file and add your language's translation best you can. The `**.po` format (and many -supporting editors) allow you to mark translations as "fuzzy". This tells the system (and future -translators) that you are unsure about the translation, or that you couldn't find a translation that -exactly matched the intention of the original text. Other translators will see this and might be -able to improve it later. -Finally, you need to compile your translation into a more efficient form. Do so from the `evennia` -folder -again: - - evennia compilemessages - -This will go through all languages and create/update compiled files (`**.mo`) for them. This needs -to be done whenever a `**.po` file is updated. - -When you are done, send the `**.po` and `*.mo` file to the Evennia developer list (or push it into -your own repository clone) so we can integrate your translation into Evennia! diff --git a/docs/0.9.5/_sources/Learn-Python-for-Evennia-The-Hard-Way.md.txt b/docs/0.9.5/_sources/Learn-Python-for-Evennia-The-Hard-Way.md.txt deleted file mode 100644 index fd7e22a3c6..0000000000 --- a/docs/0.9.5/_sources/Learn-Python-for-Evennia-The-Hard-Way.md.txt +++ /dev/null @@ -1,70 +0,0 @@ -# Learn Python for Evennia The Hard Way - -# WORK IN PROGRESS - DO NOT USE - -Evennia provides a great foundation to build your very own MU* whether you have programming -experience or none at all. Whilst Evennia has a number of in-game building commands and tutorials -available to get you started, when approaching game systems of any complexity it is advisable to -have the basics of Python under your belt before jumping into the code. There are many Python -tutorials freely available online however this page focuses on Learn Python the Hard Way (LPTHW) by -Zed Shaw. This tutorial takes you through the basics of Python and progresses you to creating your -very own online text based game. Whilst completing the course feel free to install Evennia and try -out some of our beginner tutorials. On completion you can return to this page, which will act as an -overview to the concepts separating your online text based game and the inner-workings of Evennia. --The latter portion of the tutorial focuses working your engine into a webpage and is not strictly -required for development in Evennia. - -## Exercise 23 -You may have returned here when you were invited to read some code. If you haven’t already, you -should now have the knowledge necessary to install Evennia. Head over to the Getting Started page -for install instructions. You can also try some of our tutorials to get you started on working with -Evennia. - -## Bridging the gap. -If you have successfully completed the Learn Python the Hard Way tutorial you should now have a -simple browser based Interactive Fiction engine which looks similar to this. -This engine is built using a single interactive object type, the Room class. The Room class holds a -description of itself that is presented to the user and a list of hardcoded commands which if -selected correctly will present you with the next rooms’ description and commands. Whilst your game -only has one interactive object, MU* have many more: Swords and shields, potions and scrolls or even -laser guns and robots. Even the player has an in-game representation in the form of your character. -Each of these examples are represented by their own object with their own description that can be -presented to the user. - -A basic object in Evennia has a number of default functions but perhaps most important is the idea -of location. In your text engine you receive a description of a room but you are not really in the -room because you have no in-game representation. However, in Evennia when you enter a Dungeon you -ARE in the dungeon. That is to say your character.location = Dungeon whilst the Dungeon.contents now -has a spunky young adventurer added to it. In turn, your character.contents may have amongst it a -number of swords and potions to help you on your adventure and their location would be you. - -In reality each of these “objects” are just an entry in your Evennia projects database which keeps -track of all these attributes, such as location and contents. Making changes to those attributes and -the rules in which they are changed is the most fundamental perspective of how your game works. We -define those rules in the objects Typeclass. The Typeclass is a Python class with a special -connection to the games database which changes values for us through various class methods. Let’s -look at your characters Typeclass rules for changing location. - - 1. `self.at_before_move(destination)` (if this returns False, move is aborted) - 2. `self.announce_move_from(destination)` - 3. (move happens here) - 4. `self.announce_move_to(source_location)` - 5. `self.at_after_move(source_location)` - -First we check if it’s okay to leave our current location, then we tell everyone there that we’re -leaving. We move locations and tell everyone at our new location that we’ve arrived before checking -we’re okay to be there. By default stages 1 and 5 are empty ready for us to add some rules. We’ll -leave an explanation as to how to make those changes for the tutorial section, but imagine if you -were an astronaut. A smart astronaut might stop at step 1 to remember to put his helmet on whilst a -slower astronaut might realise he’s forgotten in step 5 before shortly after ceasing to be an -astronaut. - -With all these objects and all this moving around it raises another problem. In your text engine the -commands available to the player were hard-coded to the room. That means if we have commands we -always want available to the player we’ll need to have those commands hard-coded on every single -room. What about an armoury? When all the swords are gone the command to take a sword would still -remain causing confusion. Evennia solves this problem by giving each object the ability to hold -commands. Rooms can have commands attached to them specific to that location, like climbing a tree; -Players can have commands which are always available to them, like ‘look’, ‘get’ and ‘say’; and -objects can have commands attached to them which unlock when taking possession of it, like attack -commands when obtaining a weapon. diff --git a/docs/0.9.5/_sources/Licensing.md.txt b/docs/0.9.5/_sources/Licensing.md.txt deleted file mode 100644 index 04520213d0..0000000000 --- a/docs/0.9.5/_sources/Licensing.md.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Licensing - - -Evennia is licensed under the very friendly [BSD](http://en.wikipedia.org/wiki/BSD_license) -(3-clause) license. You can find the license as -[LICENSE.txt](https://github.com/evennia/evennia/blob/master/LICENSE.txt) in the Evennia -repository's root. - -**Q: When creating a game using Evennia, what does the license permit me to do with it?** - -**A:** It's your own game world to do with as you please! Keep it to yourself or re-distribute it -under another license of your choice - or sell it and become filthy rich for all we care. - -**Q: I have modified the Evennia library itself, what does the license say about that?** - -**A:** Our license allows you to do whatever you want with your modified Evennia, including -re-distributing or selling it, as long as you include our license and copyright info found in -`LICENSE.txt` along with your distribution. - -... Of course, if you fix bugs or add some new snazzy feature we *softly nudge* you to make those -changes available so they can be added to the core Evennia package for everyone's benefit. The -license doesn't *require* you to do it, but that doesn't mean we won't still greatly appreciate it -if you do! - -**Q: Can I re-distribute the Evennia server package along with my custom game implementation?** - -**A:** Sure. As long as the text in `LICENSE.txt` is included. - -**Q: What about Contributions?** - -The contributions in `evennia/evennia/contrib` are considered to be released under the same license -as Evennia itself, unless the individual contributor has specifically defined otherwise. diff --git a/docs/0.9.5/_sources/Links.md.txt b/docs/0.9.5/_sources/Links.md.txt deleted file mode 100644 index 21a73992ab..0000000000 --- a/docs/0.9.5/_sources/Links.md.txt +++ /dev/null @@ -1,176 +0,0 @@ -# Links - -*A list of resources that may be useful for Evennia users and developers.* - -### Official Evennia links - -- [evennia.com](http://www.evennia.com) - Main Evennia portal page. Links to all corners of Evennia. -- [Evennia github page](https://github.com/evennia/evennia) - Download code and read documentation. -- [Evennia official chat channel](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) - Our official IRC chat #evennia at irc.freenode.net:6667. -- [Evennia forums/mailing list](http://groups.google.com/group/evennia) - Web interface to our -google group. -- [Evennia development blog](http://evennia.blogspot.se/) - Musings from the lead developer. -- [Evennia's manual on ReadTheDocs](http://readthedocs.org/projects/evennia/) - Read and download -offline in html, PDF or epub formats. -- [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games. ----- -- [Evennia on Open Hub](https://www.openhub.net/p/6906) -- [Evennia on OpenHatch](https://openhatch.org/projects/Evennia) -- [Evennia on PyPi](https://pypi.python.org/pypi/Evennia-MUD-Server/) -- [Evennia subreddit](http://www.reddit.com/r/Evennia/) (not much there yet though) - -### Third-party Evennia utilities and resources - -*For publicly available games running on Evennia, add and find those in the [Evennia game -index](http://games.evennia.com) instead!* - -- [Discord Evennia channel](https://discord.gg/NecFePw) - This is a fan-driven Discord channel with -a bridge to the official Evennia IRC channel. - ---- - -- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the -_Blackbirds_ Evennia game project. -- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin) -- an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine. -- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON -27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an -Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and -challenges [used during the conference](https://dcdark.net/home#). -- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular -[Arx](http://play.arxmush.org/) Evennia game. [Here are instructions for installing](Arxcode- -installing-help) -- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your -website. -- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional -coloration for Evennia unit-test output. -- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for -telnet/web). -- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for -Evennia with things like races, combat etc. [Summary -here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/). -- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source -turn-based battle system for Evennia. It also has a [live demo](http://wcb.battlestudio.com/). -- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people -to make [RPI](http://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games. -- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an -older fork of Evennia. It has some specific design goals for building and extending the game based -on input files. -- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`) -files in the [vim](http://www.vim.org/) text editor (Emacs users can use [evennia- -mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)). -- [Other Evennia-related repos on github](https://github.com/search?p=1&q=evennia) ----- -- [EvCast video series](https://www.youtube.com/playlist?list=PLyYMNttpc-SX1hvaqlUNmcxrhmM64pQXl) - -Tutorial videos explaining installing Evennia, basic Python etc. -- [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker -container](https://www.docker.com/) for quick install and deployment in just a few commands. -- [Evennia's docs in Chinese](http://www.evenniacn.com/) - A translated mirror of a slightly older -Evennia version. Announcement [here](https://groups.google.com/forum/#!topic/evennia/3AXS8ZTzJaA). -- [Evennia for MUSHers](http://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing -Evennia for those used to the MUSH way of doing things. -- *[Language Understanding for Text games using Deep reinforcement -learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)* -([PDF](http://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia -to train AIs. - -### Other useful mud development resources - -- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to -Python objects. -- [Gossip MUD chat network](https://gossip.haus/) - -### General MUD forums and discussions - -- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack -channel](https://slack.mudcoders.com/) with discussions on MUD development. -- [MuSoapbox](http://www.musoapbox.net/) - Very active Mu* game community mainly focused on MUSH- -type gaming. -- [Imaginary Realities](http://journal.imaginary-realities.com/) - An e-magazine on game and MUD -design that has several articles about Evennia. There is also an [archive of older -issues](http://disinterest.org/resource/imaginary-realities/) from 1998-2001 that are still very -relevant. -- [Optional Realities](http://optionalrealities.com/) - Mud development discussion forums that has -regular articles on MUD development focused on roleplay-intensive games. After a HD crash it's not -as content-rich as it once was. -- [MudLab](http://mudlab.org/) - Mud design discussion forum -- [MudConnector](http://www.mudconnect.com/) - Mud listing and forums -- [MudBytes](http://www.mudbytes.net/) - Mud listing and forums -- [Top Mud Sites](http://www.topmudsites.com/) - Mud listing and forums -- [Planet Mud-Dev](http://planet-muddev.disinterest.org/) - A blog aggregator following blogs of -current MUD development (including Evennia) around the 'net. Worth to put among your RSS -subscriptions. -- Mud Dev mailing list archive ([mirror](http://www.disinterest.org/resource/MUD-Dev/)) - -Influential mailing list active 1996-2004. Advanced game design discussions. -- [Mud-dev wiki](http://mud-dev.wikidot.com/) - A (very) slowly growing resource on MUD creation. -- [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD -telnet protocols. -- [Mud Tech's fun/cool but ...](http://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-ship-damned-thing/) - Greg Taylor gives good advice on mud design. -- [Lost Library of MOO](http://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in -particular moo). -- [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) - -Contains a very useful list of things to think about when starting your new MUD. -- [Lost Garden](http://www.lostgarden.com/) - A game development blog with long and interesting -articles (not MUD-specific) -- [What Games Are](http://whatgamesare.com/) - A blog about general game design (not MUD-specific) -- [The Alexandrian](http://thealexandrian.net/) - A blog about tabletop roleplaying and board games, -but with lots of general discussion about rule systems and game balance that could be applicable -also for MUDs. -- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world-design/the-laws-of-online-world-design/) - thought-provoking guidelines and things to think about -when designing a virtual multiplayer world (Raph is known for *Ultima Online* among other things). - -### Literature - -- Richard Bartle *Designing Virtual Worlds* ([amazon page](http://www.amazon.com/Designing-Virtual-Worlds-Richard-Bartle/dp/0131018167)) - Essential reading for the design of any persistent game -world, written by the co-creator of the original game *MUD*. Published in 2003 but it's still as -relevant now as when it came out. Covers everything you need to know and then some. -- Zed A. Shaw *Learn Python the Hard way* ([homepage](https://learnpythonthehardway.org/)) - Despite -the imposing name this book is for the absolute Python/programming beginner. One learns the language -by gradually creating a small text game! It has been used by multiple users before moving on to -Evennia. *Update: This used to be free to read online, this is no longer the case.* -- David M. Beazley *Python Essential Reference (4th ed)* ([amazon -page](http://www.amazon.com/Python-Essential-Reference-David-Beazley/dp/0672329786/)) - Our -recommended book on Python; it not only efficiently summarizes the language but is also an excellent -reference to the standard library for more experienced Python coders. -- Luciano Ramalho, *Fluent Python* ([o'reilly -page](http://shop.oreilly.com/product/0636920032519.do)) - This is an excellent book for experienced -Python coders willing to take their code to the next level. A great read with a lot of useful info -also for veteran Pythonistas. -- Richard Cantillon *An Essay on Economic Theory* ([free -pdf](http://mises.org/books/essay_on_economic_theory_cantillon.pdf)) - A very good English -translation of *Essai sur la Nature du Commerce en Général*, one of the foundations of modern -economic theory. Written in 1730 but the translation is annotated and the essay is actually very -easy to follow also for a modern reader. Required reading if you think of implementing a sane game -economic system. - -### Frameworks - -- [Django's homepage](http://www.djangoproject.com/) - - [Documentation](http://docs.djangoproject.com/en) - - [Code](http://code.djangoproject.com/) -- [Twisted homepage](http://twistedmatrix.com/) - - [Documentation](http://twistedmatrix.com/documents/current/core/howto/index.html) - - [Code](http://twistedmatrix.com/trac/browser) - -### Tools - -- [GIT](http://git-scm.com/) - - [Documentation](http://git-scm.com/documentation) - - [Learn GIT in 15 minutes](http://try.github.io/levels/1/challenges/1) (interactive tutorial) - -### Python Info - -- [Python Website](http://www.python.org/) - - [Documentation](http://www.python.org/doc/) - - [Tutorial](http://docs.python.org/tut/tut.html) - - [Library Reference](http://docs.python.org/lib/lib.html) - - [Language Reference](http://docs.python.org/ref/ref.html) - - [Python tips and tricks](http://www.siafoo.net/article/52) - - [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) - free online -programming curriculum for different skill levels - -### Credits - - - Wiki [Home](./index.md) Icons made by [Freepik](http://www.freepik.com"-title="Freepik">Freepik) from -[flaticon.com](http://www.flaticon.com), licensed under [Creative Commons BY -3.0](http://creativecommons.org/licenses/by/3.0). diff --git a/docs/0.9.5/_sources/Locks.md.txt b/docs/0.9.5/_sources/Locks.md.txt deleted file mode 100644 index dd1801f81d..0000000000 --- a/docs/0.9.5/_sources/Locks.md.txt +++ /dev/null @@ -1,495 +0,0 @@ -# Locks - - -For most games it is a good idea to restrict what people can do. In Evennia such restrictions are -applied and checked by something called *locks*. All Evennia entities ([Commands](./Commands.md), -[Objects](./Objects.md), [Scripts](./Scripts.md), [Accounts](./Accounts.md), [Help System](./Help-System.md), -[messages](./Communications.md#msg) and [channels](./Communications.md#channels)) are accessed through locks. - -A lock can be thought of as an "access rule" restricting a particular use of an Evennia entity. -Whenever another entity wants that kind of access the lock will analyze that entity in different -ways to determine if access should be granted or not. Evennia implements a "lockdown" philosophy - -all entities are inaccessible unless you explicitly define a lock that allows some or full access. - -Let's take an example: An object has a lock on itself that restricts how people may "delete" that -object. Apart from knowing that it restricts deletion, the lock also knows that only players with -the specific ID of, say, `34` are allowed to delete it. So whenever a player tries to run `delete` -on the object, the `delete` command makes sure to check if this player is really allowed to do so. -It calls the lock, which in turn checks if the player's id is `34`. Only then will it allow `delete` -to go on with its job. - -## Setting and checking a lock - -The in-game command for setting locks on objects is `lock`: - - > lock obj = - -The `` is a string of a certain form that defines the behaviour of the lock. We will go -into more detail on how `` should look in the next section. - -Code-wise, Evennia handles locks through what is usually called `locks` on all relevant entities. -This is a handler that allows you to add, delete and check locks. - -```python - myobj.locks.add() -``` - -One can call `locks.check()` to perform a lock check, but to hide the underlying implementation all -objects also have a convenience function called `access`. This should preferably be used. In the -example below, `accessing_obj` is the object requesting the 'delete' access whereas `obj` is the -object that might get deleted. This is how it would look (and does look) from inside the `delete` -command: - -```python - if not obj.access(accessing_obj, 'delete'): - accessing_obj.msg("Sorry, you may not delete that.") - return -``` - -## Defining locks - -Defining a lock (i.e. an access restriction) in Evennia is done by adding simple strings of lock -definitions to the object's `locks` property using `obj.locks.add()`. - -Here are some examples of lock strings (not including the quotes): - -```python - delete:id(34) # only allow obj #34 to delete - edit:all() # let everyone edit - # only those who are not "very_weak" or are Admins may pick this up - get: not attr(very_weak) or perm(Admin) -``` - -Formally, a lockstring has the following syntax: - -```python - access_type: [NOT] lockfunc1([arg1,..]) [AND|OR] [NOT] lockfunc2([arg1,...]) [...] -``` - -where `[]` marks optional parts. `AND`, `OR` and `NOT` are not case sensitive and excess spaces are -ignored. `lockfunc1, lockfunc2` etc are special _lock functions_ available to the lock system. - -So, a lockstring consists of the type of restriction (the `access_type`), a colon (`:`) and then an -expression involving function calls that determine what is needed to pass the lock. Each function -returns either `True` or `False`. `AND`, `OR` and `NOT` work as they do normally in Python. If the -total result is `True`, the lock is passed. - -You can create several lock types one after the other by separating them with a semicolon (`;`) in -the lockstring. The string below yields the same result as the previous example: - - delete:id(34);edit:all();get: not attr(very_weak) or perm(Admin) - - -### Valid access_types - -An `access_type`, the first part of a lockstring, defines what kind of capability a lock controls, -such as "delete" or "edit". You may in principle name your `access_type` anything as long as it is -unique for the particular object. The name of the access types is not case-sensitive. - -If you want to make sure the lock is used however, you should pick `access_type` names that you (or -the default command set) actually checks for, as in the example of `delete` above that uses the -'delete' `access_type`. - -Below are the access_types checked by the default commandset. - -- [Commands](./Commands.md) - - `cmd` - this defines who may call this command at all. -- [Objects](./Objects.md): - - `control` - who is the "owner" of the object. Can set locks, delete it etc. Defaults to the -creator of the object. - - `call` - who may call Object-commands stored on this Object except for the Object itself. By -default, Objects share their Commands with anyone in the same location (e.g. so you can 'press' a -`Button` object in the room). For Characters and Mobs (who likely only use those Commands for -themselves and don't want to share them) this should usually be turned off completely, using -something like `call:false()`. - - `examine` - who may examine this object's properties. - - `delete` - who may delete the object. - - `edit` - who may edit properties and attributes of the object. - - `view` - if the `look` command will display/list this object - - `get`- who may pick up the object and carry it around. - - `puppet` - who may "become" this object and control it as their "character". - - `attrcreate` - who may create new attributes on the object (default True) -- [Characters](./Objects.md#characters): - - Same as for Objects -- [Exits](./Objects.md#exits): - - Same as for Objects - - `traverse` - who may pass the exit. -- [Accounts](./Accounts.md): - - `examine` - who may examine the account's properties. - - `delete` - who may delete the account. - - `edit` - who may edit the account's attributes and properties. - - `msg` - who may send messages to the account. - - `boot` - who may boot the account. -- [Attributes](./Attributes.md): (only checked by `obj.secure_attr`) - - `attrread` - see/access attribute - - `attredit` - change/delete attribute -- [Channels](./Communications.md#channels): - - `control` - who is administrating the channel. This means the ability to delete the channel, -boot listeners etc. - - `send` - who may send to the channel. - - `listen` - who may subscribe and listen to the channel. -- [HelpEntry](./Help-System.md): - - `examine` - who may view this help entry (usually everyone) - - `edit` - who may edit this help entry. - -So to take an example, whenever an exit is to be traversed, a lock of the type *traverse* will be -checked. Defining a suitable lock type for an exit object would thus involve a lockstring `traverse: -`. - -### Custom access_types - -As stated above, the `access_type` part of the lock is simply the 'name' or 'type' of the lock. The -text is an arbitrary string that must be unique for an object. If adding a lock with the same -`access_type` as one that already exists on the object, the new one override the old one. - -For example, if you wanted to create a bulletin board system and wanted to restrict who can either -read a board or post to a board. You could then define locks such as: - -```python - obj.locks.add("read:perm(Player);post:perm(Admin)") -``` - -This will create a 'read' access type for Characters having the `Player` permission or above and a -'post' access type for those with `Admin` permissions or above (see below how the `perm()` lock -function works). When it comes time to test these permissions, simply check like this (in this -example, the `obj` may be a board on the bulletin board system and `accessing_obj` is the player -trying to read the board): - -```python - if not obj.access(accessing_obj, 'read'): - accessing_obj.msg("Sorry, you may not read that.") - return -``` - -### Lock functions - -A lock function is a normal Python function put in a place Evennia looks for such functions. The -modules Evennia looks at is the list `settings.LOCK_FUNC_MODULES`. *All functions* in any of those -modules will automatically be considered a valid lock function. The default ones are found in -`evennia/locks/lockfuncs.py` and you can start adding your own in `mygame/server/conf/lockfuncs.py`. -You can append the setting to add more module paths. To replace a default lock function, just add -your own with the same name. - -A lock function must always accept at least two arguments - the *accessing object* (this is the -object wanting to get access) and the *accessed object* (this is the object with the lock). Those -two are fed automatically as the first two arguments to the function when the lock is checked. Any -arguments explicitly given in the lock definition will appear as extra arguments. - -```python - # A simple example lock function. Called with e.g. `id(34)`. This is - # defined in, say mygame/server/conf/lockfuncs.py - - def id(accessing_obj, accessed_obj, *args, **kwargs): - if args: - wanted_id = args[0] - return accessing_obj.id == wanted_id - return False -``` - -The above could for example be used in a lock function like this: - -```python - # we have `obj` and `owner_object` from before - obj.locks.add("edit: id(%i)" % owner_object.id) -``` - -We could check if the "edit" lock is passed with something like this: - -```python - # as part of a Command's func() method, for example - if not obj.access(caller, "edit"): - caller.msg("You don't have access to edit this!") - return -``` - -In this example, everyone except the `caller` with the right `id` will get the error. - -> (Using the `*` and `**` syntax causes Python to magically put all extra arguments into a list -`args` and all keyword arguments into a dictionary `kwargs` respectively. If you are unfamiliar with -how `*args` and `**kwargs` work, see the Python manuals). - -Some useful default lockfuncs (see `src/locks/lockfuncs.py` for more): - -- `true()/all()` - give access to everyone -- `false()/none()/superuser()` - give access to none. Superusers bypass the check entirely and are -thus the only ones who will pass this check. -- `perm(perm)` - this tries to match a given `permission` property, on an Account firsthand, on a -Character second. See [below](./Locks.md#permissions). -- `perm_above(perm)` - like `perm` but requires a "higher" permission level than the one given. -- `id(num)/dbref(num)` - checks so the access_object has a certain dbref/id. -- `attr(attrname)` - checks if a certain [Attribute](./Attributes.md) exists on accessing_object. -- `attr(attrname, value)` - checks so an attribute exists on accessing_object *and* has the given -value. -- `attr_gt(attrname, value)` - checks so accessing_object has a value larger (`>`) than the given -value. -- `attr_ge, attr_lt, attr_le, attr_ne` - corresponding for `>=`, `<`, `<=` and `!=`. -- `holds(objid)` - checks so the accessing objects contains an object of given name or dbref. -- `inside()` - checks so the accessing object is inside the accessed object (the inverse of -`holds()`). -- `pperm(perm)`, `pid(num)/pdbref(num)` - same as `perm`, `id/dbref` but always looks for -permissions and dbrefs of *Accounts*, not on Characters. -- `serversetting(settingname, value)` - Only returns True if Evennia has a given setting or a -setting set to a given value. - -## Checking simple strings - -Sometimes you don't really need to look up a certain lock, you just want to check a lockstring. A -common use is inside Commands, in order to check if a user has a certain permission. The lockhandler -has a method `check_lockstring(accessing_obj, lockstring, bypass_superuser=False)` that allows this. - -```python - # inside command definition - if not self.caller.locks.check_lockstring(self.caller, "dummy:perm(Admin)"): - self.caller.msg("You must be an Admin or higher to do this!") - return -``` - -Note here that the `access_type` can be left to a dummy value since this method does not actually do -a Lock lookup. - -## Default locks - -Evennia sets up a few basic locks on all new objects and accounts (if we didn't, noone would have -any access to anything from the start). This is all defined in the root [Typeclasses](./Typeclasses.md) -of the respective entity, in the hook method `basetype_setup()` (which you usually don't want to -edit unless you want to change how basic stuff like rooms and exits store their internal variables). -This is called once, before `at_object_creation`, so just put them in the latter method on your -child object to change the default. Also creation commands like `create` changes the locks of -objects you create - for example it sets the `control` lock_type so as to allow you, its creator, to -control and delete the object. - -# Permissions - -> This section covers the underlying code use of permissions. If you just want to learn how to -practically assign permissions in-game, refer to the [Building Permissions](./Building-Permissions.md) -page, which details how you use the `perm` command. - -A *permission* is simply a list of text strings stored in the handler `permissions` on `Objects` -and `Accounts`. Permissions can be used as a convenient way to structure access levels and -hierarchies. It is set by the `perm` command. Permissions are especially handled by the `perm()` and -`pperm()` lock functions listed above. - -Let's say we have a `red_key` object. We also have red chests that we want to unlock with this key. - - perm red_key = unlocks_red_chests - -This gives the `red_key` object the permission "unlocks_red_chests". Next we lock our red chests: - - lock red chest = unlock:perm(unlocks_red_chests) - -What this lock will expect is to the fed the actual key object. The `perm()` lock function will -check the permissions set on the key and only return true if the permission is the one given. - -Finally we need to actually check this lock somehow. Let's say the chest has an command `open ` -sitting on itself. Somewhere in its code the command needs to figure out which key you are using and -test if this key has the correct permission: - -```python - # self.obj is the chest - # and used_key is the key we used as argument to - # the command. The self.caller is the one trying - # to unlock the chest - if not self.obj.access(used_key, "unlock"): - self.caller.msg("The key does not fit!") - return -``` - -All new accounts are given a default set of permissions defined by -`settings.PERMISSION_ACCOUNT_DEFAULT`. - -Selected permission strings can be organized in a *permission hierarchy* by editing the tuple -`settings.PERMISSION_HIERARCHY`. Evennia's default permission hierarchy is as follows: - - Developer # like superuser but affected by locks - Admin # can administrate accounts - Builder # can edit the world - Helper # can edit help files - Player # can chat and send tells (default level) - -(Also the plural form works, so you could use `Developers` etc too). - -> There is also a `Guest` level below `Player` that is only active if `settings.GUEST_ENABLED` is -set. This is never part of `settings.PERMISSION_HIERARCHY`. - -The main use of this is that if you use the lock function `perm()` mentioned above, a lock check for -a particular permission in the hierarchy will *also* grant access to those with *higher* hierarchy -access. So if you have the permission "Admin" you will also pass a lock defined as `perm(Builder)` -or any of those levels below "Admin". - -When doing an access check from an [Object](./Objects.md) or Character, the `perm()` lock function will -always first use the permissions of any Account connected to that Object before checking for -permissions on the Object. In the case of hierarchical permissions (Admins, Builders etc), the -Account permission will always be used (this stops an Account from escalating their permission by -puppeting a high-level Character). If the permission looked for is not in the hierarchy, an exact -match is required, first on the Account and if not found there (or if no Account is connected), then -on the Object itself. - -Here is how you use `perm` to give an account more permissions: - - perm/account Tommy = Builders - perm/account/del Tommy = Builders # remove it again - -Note the use of the `/account` switch. It means you assign the permission to the -[Accounts](./Accounts.md) Tommy instead of any [Character](./Objects.md) that also happens to be named -"Tommy". - -Putting permissions on the *Account* guarantees that they are kept, *regardless* of which Character -they are currently puppeting. This is especially important to remember when assigning permissions -from the *hierarchy tree* - as mentioned above, an Account's permissions will overrule that of its -character. So to be sure to avoid confusion you should generally put hierarchy permissions on the -Account, not on their Characters (but see also [quelling](./Locks.md#quelling)). - -Below is an example of an object without any connected account - -```python - obj1.permissions = ["Builders", "cool_guy"] - obj2.locks.add("enter:perm_above(Accounts) and perm(cool_guy)") - - obj2.access(obj1, "enter") # this returns True! -``` - -And one example of a puppet with a connected account: - -```python - account.permissions.add("Accounts") - puppet.permissions.add("Builders", "cool_guy") - obj2.locks.add("enter:perm_above(Accounts) and perm(cool_guy)") - - obj2.access(puppet, "enter") # this returns False! -``` - -## Superusers - -There is normally only one *superuser* account and that is the one first created when starting -Evennia (User #1). This is sometimes known as the "Owner" or "God" user. A superuser has more than -full access - it completely *bypasses* all locks so no checks are even run. This allows for the -superuser to always have access to everything in an emergency. But it also hides any eventual errors -you might have made in your lock definitions. So when trying out game systems you should either use -quelling (see below) or make a second Developer-level character so your locks get tested correctly. - -## Quelling - -The `quell` command can be used to enforce the `perm()` lockfunc to ignore permissions on the -Account and instead use the permissions on the Character only. This can be used e.g. by staff to -test out things with a lower permission level. Return to the normal operation with `unquell`. Note -that quelling will use the smallest of any hierarchical permission on the Account or Character, so -one cannot escalate one's Account permission by quelling to a high-permission Character. Also the -superuser can quell their powers this way, making them affectable by locks. - -## More Lock definition examples - - examine: attr(eyesight, excellent) or perm(Builders) - -You are only allowed to do *examine* on this object if you have 'excellent' eyesight (that is, has -an Attribute `eyesight` with the value `excellent` defined on yourself) or if you have the -"Builders" permission string assigned to you. - - open: holds('the green key') or perm(Builder) - -This could be called by the `open` command on a "door" object. The check is passed if you are a -Builder or has the right key in your inventory. - - cmd: perm(Builders) - -Evennia's command handler looks for a lock of type `cmd` to determine if a user is allowed to even -call upon a particular command or not. When you define a command, this is the kind of lock you must -set. See the default command set for lots of examples. If a character/account don't pass the `cmd` -lock type the command will not even appear in their `help` list. - - cmd: not perm(no_tell) - -"Permissions" can also be used to block users or implement highly specific bans. The above example -would be be added as a lock string to the `tell` command. This will allow everyone *not* having the -"permission" `no_tell` to use the `tell` command. You could easily give an account the "permission" -`no_tell` to disable their use of this particular command henceforth. - - -```python - dbref = caller.id - lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Admin);get:all()" % -(dbref, dbref) - new_obj.locks.add(lockstring) -``` - -This is how the `create` command sets up new objects. In sequence, this permission string sets the -owner of this object be the creator (the one running `create`). Builders may examine the object -whereas only Admins and the creator may delete it. Everyone can pick it up. - -## A complete example of setting locks on an object - -Assume we have two objects - one is ourselves (not superuser) and the other is an [Object](./Objects.md) -called `box`. - - > create/drop box - > desc box = "This is a very big and heavy box." - -We want to limit which objects can pick up this heavy box. Let's say that to do that we require the -would-be lifter to to have an attribute *strength* on themselves, with a value greater than 50. We -assign it to ourselves to begin with. - - > set self/strength = 45 - -Ok, so for testing we made ourselves strong, but not strong enough. Now we need to look at what -happens when someone tries to pick up the the box - they use the `get` command (in the default set). -This is defined in `evennia/commands/default/general.py`. In its code we find this snippet: - -```python - if not obj.access(caller, 'get'): - if obj.db.get_err_msg: - caller.msg(obj.db.get_err_msg) - else: - caller.msg("You can't get that.") - return -``` - -So the `get` command looks for a lock with the type *get* (not so surprising). It also looks for an -[Attribute](./Attributes.md) on the checked object called _get_err_msg_ in order to return a customized -error message. Sounds good! Let's start by setting that on the box: - - > set box/get_err_msg = You are not strong enough to lift this box. - -Next we need to craft a Lock of type *get* on our box. We want it to only be passed if the accessing -object has the attribute *strength* of the right value. For this we would need to create a lock -function that checks if attributes have a value greater than a given value. Luckily there is already -such a one included in evennia (see `evennia/locks/lockfuncs.py`), called `attr_gt`. - -So the lock string will look like this: `get:attr_gt(strength, 50)`. We put this on the box now: - - lock box = get:attr_gt(strength, 50) - -Try to `get` the object and you should get the message that we are not strong enough. Increase your -strength above 50 however and you'll pick it up no problem. Done! A very heavy box! - -If you wanted to set this up in python code, it would look something like this: - -```python - - from evennia import create_object - - # create, then set the lock - box = create_object(None, key="box") - box.locks.add("get:attr_gt(strength, 50)") - - # or we can assign locks in one go right away - box = create_object(None, key="box", locks="get:attr_gt(strength, 50)") - - # set the attributes - box.db.desc = "This is a very big and heavy box." - box.db.get_err_msg = "You are not strong enough to lift this box." - - # one heavy box, ready to withstand all but the strongest... -``` - -## On Django's permission system - -Django also implements a comprehensive permission/security system of its own. The reason we don't -use that is because it is app-centric (app in the Django sense). Its permission strings are of the -form `appname.permstring` and it automatically adds three of them for each database model in the app -- for the app evennia/object this would be for example 'object.create', 'object.admin' and -'object.edit'. This makes a lot of sense for a web application, not so much for a MUD, especially -when we try to hide away as much of the underlying architecture as possible. - -The django permissions are not completely gone however. We use it for validating passwords during -login. It is also used exclusively for managing Evennia's web-based admin site, which is a graphical -front-end for the database of Evennia. You edit and assign such permissions directly from the web -interface. It's stand-alone from the permissions described above. diff --git a/docs/0.9.5/_sources/Manually-Configuring-Color.md.txt b/docs/0.9.5/_sources/Manually-Configuring-Color.md.txt deleted file mode 100644 index 8b4aa17b79..0000000000 --- a/docs/0.9.5/_sources/Manually-Configuring-Color.md.txt +++ /dev/null @@ -1,169 +0,0 @@ -# Manually Configuring Color - - -This is a small tutorial for customizing your character objects, using the example of letting users -turn on and off ANSI color parsing as an example. `@options NOCOLOR=True` will now do what this -tutorial shows, but the tutorial subject can be applied to other toggles you may want, as well. - -In the Building guide's [Colors](./TextTags.md#coloured-text) page you can learn how to add color to your -game by using special markup. Colors enhance the gaming experience, but not all users want color. -Examples would be users working from clients that don't support color, or people with various seeing -disabilities that rely on screen readers to play your game. Also, whereas Evennia normally -automatically detects if a client supports color, it may get it wrong. Being able to turn it on -manually if you know it **should** work could be a nice feature. - -So here's how to allow those users to remove color. It basically means you implementing a simple -configuration system for your characters. This is the basic sequence: - -1. Define your own default character typeclass, inheriting from Evennia's default. -1. Set an attribute on the character to control markup on/off. -1. Set your custom character class to be the default for new accounts. -1. Overload the `msg()` method on the typeclass and change how it uses markup. -1. Create a custom command to allow users to change their setting. - -## Setting up a custom Typeclass - -Create a new module in `mygame/typeclasses` named, for example, `mycharacter.py`. Alternatively you -can simply add a new class to 'mygamegame/typeclasses/characters.py'. - -In your new module(or characters.py), create a new [Typeclass](./Typeclasses.md) inheriting from -`evennia.DefaultCharacter`. We will also import `evennia.utils.ansi`, which we will use later. - -```python - from evennia import Character - from evennia.utils import ansi - - class ColorableCharacter(Character): - at_object_creation(self): - # set a color config value - self.db.config_color = True -``` - -Above we set a simple config value as an [Attribute](./Attributes.md). - -Let's make sure that new characters are created of this type. Edit your -`mygame/server/conf/settings.py` file and add/change `BASE_CHARACTER_TYPECLASS` to point to your new -character class. Observe that this will only affect *new* characters, not those already created. You -have to convert already created characters to the new typeclass by using the `@typeclass` command -(try on a secondary character first though, to test that everything works - you don't want to render -your root user unusable!). - - @typeclass/reset/force Bob = mycharacter.ColorableCharacter - -`@typeclass` changes Bob's typeclass and runs all its creation hooks all over again. The `/reset` -switch clears all attributes and properties back to the default for the new typeclass - this is -useful in this case to avoid ending up with an object having a "mixture" of properties from the old -typeclass and the new one. `/force` might be needed if you edit the typeclass and want to update the -object despite the actual typeclass name not having changed. - -## Overload the `msg()` method - -Next we need to overload the `msg()` method. What we want is to check the configuration value before -calling the main function. The original `msg` method call is seen in `evennia/objects/objects.py` -and is called like this: - -```python - msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): -``` - -As long as we define a method on our custom object with the same name and keep the same number of -arguments/keywords we will overload the original. Here's how it could look: - -```python - class ColorableCharacter(Character): - # [...] - msg(self, text=None, from_obj=None, session=None, options=None, - **kwargs): - "our custom msg()" - if self.db.config_color is not None: # this would mean it was not set - if not self.db.config_color: - # remove the ANSI from the text - text = ansi.strip_ansi(text) - super().msg(text=text, from_obj=from_obj, - session=session, **kwargs) -``` - -Above we create a custom version of the `msg()` method. If the configuration Attribute is set, it -strips the ANSI from the text it is about to send, and then calls the parent `msg()` as usual. You -need to `@reload` before your changes become visible. - -There we go! Just flip the attribute `config_color` to False and your users will not see any color. -As superuser (assuming you use the Typeclass `ColorableCharacter`) you can test this with the `@py` -command: - - @py self.db.config_color = False - -## Custom color config command - -For completeness, let's add a custom command so users can turn off their color display themselves if -they want. - -In `mygame/commands`, create a new file, call it for example `configcmds.py` (it's likely that -you'll want to add other commands for configuration down the line). You can also copy/rename the -command template. - -```python - from evennia import Command - - class CmdConfigColor(Command): - """ - Configures your color - - Usage: - @togglecolor on|off - - This turns ANSI-colors on/off. - Default is on. - """ - - key = "@togglecolor" - aliases = ["@setcolor"] - - def func(self): - "implements the command" - # first we must remove whitespace from the argument - self.args = self.args.strip() - if not self.args or not self.args in ("on", "off"): - self.caller.msg("Usage: @setcolor on|off") - return - if self.args == "on": - self.caller.db.config_color = True - # send a message with a tiny bit of formatting, just for fun - self.caller.msg("Color was turned |won|W.") - else: - self.caller.db.config_color = False - self.caller.msg("Color was turned off.") -``` - -Lastly, we make this command available to the user by adding it to the default `CharacterCmdSet` in -`mygame/commands/default_cmdsets.py` and reloading the server. Make sure you also import the -command: - -```python -from mygame.commands import configcmds -class CharacterCmdSet(default_cmds.CharacterCmdSet): - # [...] - def at_cmdset_creation(self): - """ - Populates the cmdset - """ - super().at_cmdset_creation() - # - # any commands you add below will overload the default ones. - # - - # here is the only line that we edit - self.add(configcmds.CmdConfigColor()) -``` - -## More colors - -Apart from ANSI colors, Evennia also supports **Xterm256** colors (See [Colors](./TextTags.md#colored- -text)). The `msg()` method supports the `xterm256` keyword for manually activating/deactiving -xterm256. It should be easy to expand the above example to allow players to customize xterm256 -regardless of if Evennia thinks their client supports it or not. - -To get a better understanding of how `msg()` works with keywords, you can try this as superuser: - - @py self.msg("|123Dark blue with xterm256, bright blue with ANSI", xterm256=True) - @py self.msg("|gThis should be uncolored", nomarkup=True) diff --git a/docs/0.9.5/_sources/Mass-and-weight-for-objects.md.txt b/docs/0.9.5/_sources/Mass-and-weight-for-objects.md.txt deleted file mode 100644 index d8a43571c0..0000000000 --- a/docs/0.9.5/_sources/Mass-and-weight-for-objects.md.txt +++ /dev/null @@ -1,95 +0,0 @@ -# Mass and weight for objects - - -An easy addition to add dynamic variety to your world objects is to give them some mass. Why mass -and not weight? Weight varies in setting; for example things on the Moon weigh 1/6 as much. On -Earth's surface and in most environments, no relative weight factor is needed. - -In most settings, mass can be used as weight to spring a pressure plate trap or a floor giving way, -determine a character's burden weight for travel speed... The total mass of an object can -contribute to the force of a weapon swing, or a speeding meteor to give it a potential striking -force. - -#### Objects - -Now that we have reasons for keeping track of object mass, let's look at the default object class -inside your mygame/typeclasses/objects.py and see how easy it is to total up mass from an object and -its contents. - -```python -# inside your mygame/typeclasses/objects.py - -class Object(DefaultObject): -# [...] - def get_mass(self): - mass = self.attributes.get('mass', 1) # Default objects have 1 unit mass. - return mass + sum(obj.get_mass() for obj in self.contents) -``` - -Adding the `get_mass` definition to the objects you want to sum up the masses for is done with -Python's "sum" function which operates on all the contents, in this case by summing them to -return a total mass value. - -If you only wanted specific object types to have mass or have the new object type in a different -module, see [[Adding-Object-Typeclass-Tutorial]] with its Heavy class object. You could set the -default for Heavy types to something much larger than 1 gram or whatever unit you want to use. Any -non-default mass would be stored on the `mass` [[Attributes]] of the objects. - - -#### Characters and rooms - -You can add a `get_mass` definition to characters and rooms, also. - -If you were in a one metric-ton elevator with four other friends also wearing armor and carrying -gold bricks, you might wonder if this elevator's going to move, and how fast. - -Assuming the unit is grams and the elevator itself weights 1,000 kilograms, it would already be -`@set elevator/mass=1000000`, we're `@set me/mass=85000` and our armor is `@set armor/mass=50000`. -We're each carrying 20 gold bars each `@set gold bar/mass=12400` then step into the elevator and see -the following message in the elevator's appearance: `Elevator weight and contents should not exceed -3 metric tons.` Are we safe? Maybe not if you consider dynamic loading. But at rest: - -```python -# Elevator object knows when it checks itself: -if self.get_mass() < 3000000: - pass # Elevator functions as normal. -else: - pass # Danger! Alarm sounds, cable snaps, elevator stops... -``` - -#### Inventory -Example of listing mass of items in your inventory, don't forget to add it to your -default_cmdsets.py file: - -```python -from evennia import utils - -class CmdInventory(Command): - """ - view inventory - Usage: - inventory - inv - Switches: - /weight to display all available channels. - Shows your inventory: carrying, wielding, wearing, obscuring. - """ - - key = "inventory" - aliases = ["inv", "i"] - locks = "cmd:all()" - - def func(self): - "check inventory" - items = self.caller.contents - if not items: - string = "You are not carrying anything." - else: - table = utils.evtable.EvTable("name", "weight") - for item in items: - mass = item.get_mass() - table.add_row(item.name, mass) - string = f"|wYou are carrying:|n\n{table}" - self.caller.msg(string) - -``` diff --git a/docs/0.9.5/_sources/Messagepath.md.txt b/docs/0.9.5/_sources/Messagepath.md.txt deleted file mode 100644 index bf974b89fc..0000000000 --- a/docs/0.9.5/_sources/Messagepath.md.txt +++ /dev/null @@ -1,292 +0,0 @@ -# Messagepath - - -The main functionality of Evennia is to communicate with clients connected to it; a player enters -commands or their client queries for a gui update (ingoing data). The server responds or sends data -on its own as the game changes (outgoing data). It's important to understand how this flow of -information works in Evennia. - -## The ingoing message path - -We'll start by tracing data from the client to the server. Here it is in short: - - Client -> - PortalSession -> - PortalSessionhandler -> - (AMP) -> - ServerSessionHandler -> - ServerSession -> - Inputfunc - -### Client (ingoing) - -The client sends data to Evennia in two ways. - - - When first connecting, the client can send data to the server about its - capabilities. This is things like "I support xterm256 but not unicode" and is - mainly used when a Telnet client connects. This is called a "handshake" and - will generally set some flags on the [Portal Session](./Portal-And-Server.md) that - are later synced to the Server Session. Since this is not something the player - controls, we'll not explore this further here. - - The client can send an *inputcommand* to the server. Traditionally this only - happens when the player enters text on the command line. But with a custom - client GUI, a command could also come from the pressing of a button. Finally - the client may send commands based on a timer or some trigger. - -Exactly how the inputcommand looks when it travels from the client to Evennia -depends on the [Protocol](./Custom-Protocols.md) used: - - Telnet: A string. If GMCP or MSDP OOB protocols are used, this string will - be formatted in a special way, but it's still a raw string. If Telnet SSL is - active, the string will be encrypted. - - SSH: An encrypted string - - Webclient: A JSON-serialized string. - -### Portal Session (ingoing) - -Each client is connected to the game via a *Portal Session*, one per connection. This Session is -different depending on the type of connection (telnet, webclient etc) and thus know how to handle -that particular data type. So regardless of how the data arrives, the Session will identify the type -of the instruction and any arguments it should have. For example, the telnet protocol will figure -that anything arriving normally over the wire should be passed on as a "text" type. - -### PortalSessionHandler (ingoing) - -The *PortalSessionhandler* manages all connected Sessions in the Portal. Its `data_in` method -(called by each Portal Session) will parse the command names and arguments from the protocols and -convert them to a standardized form we call the *inputcommand*: - -```python - (commandname, (args), {kwargs}) -``` - -All inputcommands must have a name, but they may or may not have arguments and keyword arguments - -in fact no default inputcommands use kwargs at all. The most common inputcommand is "text", which -has the argument the player input on the command line: - -```python - ("text", ("look",), {}) -``` - -This inputcommand-structure is pickled together with the unique session-id of the Session to which -it belongs. This is then sent over the AMP connection. - -### ServerSessionHandler (ingoing) - -On the Server side, the AMP unpickles the data and associates the session id with the server-side -[Session](./Sessions.md). Data and Session are passed to the server-side `SessionHandler.data_in`. This -in turn calls `ServerSession.data_in()` - -### ServerSession (ingoing) - -The method `ServerSession.data_in` is meant to offer a single place to override if they want to -examine *all* data passing into the server from the client. It is meant to call the -`ssessionhandler.call_inputfuncs` with the (potentially processed) data (so this is technically a -sort of detour back to the sessionhandler). - -In `call_inputfuncs`, the inputcommand's name is compared against the names of all the *inputfuncs* -registered with the server. The inputfuncs are named the same as the inputcommand they are supposed -to handle, so the (default) inputfunc for handling our "look" command is called "text". These are -just normal functions and one can plugin new ones by simply putting them in a module where Evennia -looks for such functions. - -If a matching inputfunc is found, it will be called with the Session and the inputcommand's -arguments: - -```python - text(session, *("look",), **{}) -``` - - If no matching inputfunc is found, an inputfunc named "default" will be tried and if that is also -not found, an error will be raised. - -### Inputfunc - -The [Inputfunc](./Inputfuncs.md) must be on the form `func(session, *args, **kwargs)`. An exception is -the `default` inputfunc which has form `default(session, cmdname, *args, **kwargs)`, where `cmdname` -is the un-matched inputcommand string. - -This is where the message's path diverges, since just what happens next depends on the type of -inputfunc was triggered. In the example of sending "look", the inputfunc is named "text". It will -pass the argument to the `cmdhandler` which will eventually lead to the `look` command being -executed. - - -## The outgoing message path - -Next let's trace the passage from server to client. - - msg -> - ServerSession -> - ServerSessionHandler -> - (AMP) -> - PortalSessionHandler -> - PortalSession -> - Client - -### msg - -All outgoing messages start in the `msg` method. This is accessible from three places: - - - `Object.msg` - - `Account.msg` - - `Session.msg` - -The call sign of the `msg` method looks like this: - -```python - msg(text=None, from_obj=None, session=None, options=None, **kwargs) -``` - -For our purposes, what is important to know is that with the exception of `from_obj`, `session` and -`options`, all keywords given to the `msg` method is the name of an *outputcommand* and its -arguments. So `text` is actually such a command, taking a string as its argument. The reason `text` -sits as the first keyword argument is that it's so commonly used (`caller.msg("Text")` for example). -Here are some examples - -```python - msg("Hello!") # using the 'text' outputfunc - msg(prompt="HP:%i, SP: %i, MP: %i" % (HP, SP, MP)) - msg(mycommand=((1,2,3,4), {"foo": "bar"}) - -``` -Note the form of the `mycommand` outputfunction. This explicitly defines the arguments and keyword -arguments for the function. In the case of the `text` and `prompt` calls we just specify a string - -this works too: The system will convert this into a single argument for us later in the message -path. - -> Note: The `msg` method sits on your Object- and Account typeclasses. It means you can easily -override `msg` and make custom- or per-object modifications to the flow of data as it passes -through. - -### ServerSession (outgoing) - -Nothing is processed on the Session, it just serves as a gathering points for all different `msg`. -It immediately passes the data on to ... - -### ServerSessionHandler (outgoing) - -In the *ServerSessionhandler*, the keywords from the `msg` method are collated into one or more -*outputcommands* on a standardized form (identical to inputcommands): - -``` - (commandname, (args), {kwargs}) -``` - -This will intelligently convert different input to the same form. So `msg("Hello")` will end up as -an outputcommand `("text", ("Hello",), {})`. - -This is also the point where [Inlinefuncs](./TextTags.md#inline-functions) are parsed, depending on the -session to receive the data. Said data is pickled together with the Session id then sent over the -AMP bridge. - -### PortalSessionHandler (outgoing) - -After the AMP connection has unpickled the data and paired the session id to the matching -PortalSession, the handler next determines if this Session has a suitable method for handling the -outputcommand. - -The situation is analogous to how inputfuncs work, except that protocols are fixed things that don't -need a plugin infrastructure like the inputfuncs are handled. So instead of an "outputfunc", the -handler looks for methods on the PortalSession with names of the form `send_`. - -For example, the common sending of text expects a PortalSession method `send_text`. This will be -called as `send_text(*("Hello",), **{})`. If the "prompt" outputfunction was used, send_prompt is -called. In all other cases the `send_default(cmdname, *args, **kwargs)` will be called - this is the -case for all client-custom outputcommands, like when wanting to tell the client to update a graphic -or play a sound. - -### PortalSession (outgoing) - -At this point it is up to the session to convert the command into a form understood by this -particular protocol. For telnet, `send_text` will just send the argument as a string (since that is -what telnet clients expect when "text" is coming). If `send_default` was called (basically -everything that is not traditional text or a prompt), it will pack the data as an GMCP or MSDP -command packet if the telnet client supports either (otherwise it won't send at all). If sending to -the webclient, the data will get packed into a JSON structure at all times. - -### Client (outgoing) - -Once arrived at the client, the outputcommand is handled in the way supported by the client (or it -may be quietly ignored if not). "text" commands will be displayed in the main window while others -may trigger changes in the GUI or play a sound etc. - -### Full example of Outgoing Message - -For a full outgoing message, you need to have the outgoing function defined in the javascript. See -https://evennia.readthedocs.io/en/latest/Web-Client-Webclient.html for getting set up with custom -webclient code. Once you have a custom plugin defined and loaded, create a new function in the -plugin, onCustomFunc() for example: - -```javascript - var onCustomFunc = function(args, kwargs) { - var = args.var - console.log(var) - } -``` - -You'll also need to add the function to what the main plugin function returns: - -```javascript - return { - init: init, - onCustomFunc, - } -``` - -This defines the function and looks for "var" as a variable that is passed to it. Once you have this -in place in your custom plugin, you also need to update the static/webclient/js/webclient_gui.js -file to recognize the new function when it's called. First you should add a new function inside the -plugin_handler function to recognize the new function: - -```javascript - var onCustomFunc = function (cmdname, args, kwargs) { - for( let n=0; n < ordered_plugins.length; n++ ) { - let plugin = ordered_plugins[n]; - if( 'onCustomFunc' in plugin ) { - if( plugin.onCustomFunc(args, kwargs) ) { - // True -- means this plugin claims this command exclusively. - return; - } - } - } - } -``` - -This looks through all the plugins for a function that corresponds to the custom function being -called. Next, add the custom function to the return statement of the plugin handler: - -```javascript - return { - add: add, - onKeydown: onKeydown, - onBeforeUnload: onBeforeUnload, - onLoggedIn: onLoggedIn, - onText: onText, - onGotOptions: onGotOptions, - onPrompt: onPrompt, - onDefault: onDefault, - onSilence: onSilence, - onConnectionClose: onConnectionClose, - onSend: onSend, - init: init, - postInit: postInit, - onCustomFunc: onCustomFunc, - } -``` -Lastly, you will also need to need to add an entry to the Evennia emitter to tie the python function -call to this new javascript function (this is in the $(document).ready function): - -```javascript - Evennia.emitter.on("customFunc", plugin_handler.onCustomFunc); -``` - -Now you can make a call from your python code to the new custom function to pass information from -the server to the client: - -```python - character.msg(customFunc=({"var": "blarg"})) -``` - -When this code in your python is run, you should be able to see the "blarg" string printed in the -web client console. You should now be able to update the function call and definition to pass any -information needed between server and client. \ No newline at end of file diff --git a/docs/0.9.5/_sources/MonitorHandler.md.txt b/docs/0.9.5/_sources/MonitorHandler.md.txt deleted file mode 100644 index 8b49bd1684..0000000000 --- a/docs/0.9.5/_sources/MonitorHandler.md.txt +++ /dev/null @@ -1,79 +0,0 @@ -# MonitorHandler - - -The *MonitorHandler* is a system for watching changes in properties or Attributes on objects. A -monitor can be thought of as a sort of trigger that responds to change. - -The main use for the MonitorHandler is to report changes to the client; for example the client -Session may ask Evennia to monitor the value of the Characer's `health` attribute and report -whenever it changes. This way the client could for example update its health bar graphic as needed. - -## Using the MonitorHandler - -The MontorHandler is accessed from the singleton `evennia.MONITOR_HANDLER`. The code for the handler -is in `evennia.scripts.monitorhandler`. - -Here's how to add a new monitor: - -```python -from evennia import MONITOR_HANDLER - -MONITOR_HANDLER.add(obj, fieldname, callback, - idstring="", persistent=False, **kwargs) - -``` - - - `obj` ([Typeclassed](./Typeclasses.md) entity) - the object to monitor. Since this must be -typeclassed, it means you can't monitor changes on [Sessions](./Sessions.md) with the monitorhandler, for -example. - - `fieldname` (str) - the name of a field or [Attribute](./Attributes.md) on `obj`. If you want to -monitor a database field you must specify its full name, including the starting `db_` (like -`db_key`, `db_location` etc). Any names not starting with `db_` are instead assumed to be the names -of Attributes. This difference matters, since the MonitorHandler will automatically know to watch -the `db_value` field of the Attribute. - - `callback`(callable) - This will be called as `callback(fieldname=fieldname, obj=obj, **kwargs)` -when the field updates. - - `idstring` (str) - this is used to separate multiple monitors on the same object and fieldname. -This is required in order to properly identify and remove the monitor later. It's also used for -saving it. - - `persistent` (bool) - if True, the monitor will survive a server reboot. - -Example: - -```python -from evennia import MONITOR_HANDLER as monitorhandler - -def _monitor_callback(fieldname="", obj=None, **kwargs): - # reporting callback that works both - # for db-fields and Attributes - if fieldname.startswith("db_"): - new_value = getattr(obj, fieldname) - else: # an attribute - new_value = obj.attributes.get(fieldname) - - obj.msg("%s.%s changed to '%s'." % \ - (obj.key, fieldname, new_value)) - -# (we could add _some_other_monitor_callback here too) - -# monitor Attribute (assume we have obj from before) -monitorhandler.add(obj, "desc", _monitor_callback) - -# monitor same db-field with two different callbacks (must separate by id_string) -monitorhandler.add(obj, "db_key", _monitor_callback, id_string="foo") -monitorhandler.add(obj, "db_key", _some_other_monitor_callback, id_string="bar") - -``` - -A monitor is uniquely identified by the combination of the *object instance* it is monitoring, the -*name* of the field/attribute to monitor on that object and its `idstring` (`obj` + `fieldname` + -`idstring`). The `idstring` will be the empty string unless given explicitly. - -So to "un-monitor" the above you need to supply enough information for the system to uniquely find -the monitor to remove: - -``` -monitorhandler.remove(obj, "desc") -monitorhandler.remove(obj, "db_key", idstring="foo") -monitorhandler.remove(obj, "db_key", idstring="bar") -``` diff --git a/docs/0.9.5/_sources/NPC-shop-Tutorial.md.txt b/docs/0.9.5/_sources/NPC-shop-Tutorial.md.txt deleted file mode 100644 index e070750585..0000000000 --- a/docs/0.9.5/_sources/NPC-shop-Tutorial.md.txt +++ /dev/null @@ -1,334 +0,0 @@ -# NPC shop Tutorial - -This tutorial will describe how to make an NPC-run shop. We will make use of the [EvMenu](./EvMenu.md) -system to present shoppers with a menu where they can buy things from the store's stock. - -Our shop extends over two rooms - a "front" room open to the shop's customers and a locked "store -room" holding the wares the shop should be able to sell. We aim for the following features: - - - The front room should have an Attribute `storeroom` that points to the store room. - - Inside the front room, the customer should have a command `buy` or `browse`. This will open a -menu listing all items available to buy from the store room. - - A customer should be able to look at individual items before buying. - - We use "gold" as an example currency. To determine cost, the system will look for an Attribute -`gold_value` on the items in the store room. If not found, a fixed base value of 1 will be assumed. -The wealth of the customer should be set as an Attribute `gold` on the Character. If not set, they -have no gold and can't buy anything. - - When the customer makes a purchase, the system will check the `gold_value` of the goods and -compare it to the `gold` Attribute of the customer. If enough gold is available, this will be -deducted and the goods transferred from the store room to the inventory of the customer. - - We will lock the store room so that only people with the right key can get in there. - -### The shop menu - -We want to show a menu to the customer where they can list, examine and buy items in the store. This -menu should change depending on what is currently for sale. Evennia's *EvMenu* utility will manage -the menu for us. It's a good idea to [read up on EvMenu](./EvMenu.md) if you are not familiar with it. - -#### Designing the menu - -The shopping menu's design is straightforward. First we want the main screen. You get this when you -enter a shop and use the `browse` or `buy` command: - -``` -*** Welcome to ye Old Sword shop! *** - Things for sale (choose 1-3 to inspect, quit to exit): -_________________________________________________________ -1. A rusty sword (5 gold) -2. A sword with a leather handle (10 gold) -3. Excalibur (100 gold) -``` - -There are only three items to buy in this example but the menu should expand to however many items -are needed. When you make a selection you will get a new screen showing the options for that -particular item: - -``` -You inspect A rusty sword: - -This is an old weapon maybe once used by soldiers in some -long forgotten army. It is rusty and in bad condition. -__________________________________________________________ -1. Buy A rusty sword (5 gold) -2. Look for something else. -``` - -Finally, when you buy something, a brief message should pop up: - -``` -You pay 5 gold and purchase A rusty sword! -``` -or -``` -You cannot afford 5 gold for A rusty sword! -``` -After this you should be back to the top level of the shopping menu again and can continue browsing. - -#### Coding the menu - -EvMenu defines the *nodes* (each menu screen with options) as normal Python functions. Each node -must be able to change on the fly depending on what items are currently for sale. EvMenu will -automatically make the `quit` command available to us so we won't add that manually. For compactness -we will put everything needed for our shop in one module, `mygame/typeclasses/npcshop.py`. - -```python -# mygame/typeclasses/npcshop.py - -from evennia.utils import evmenu - -def menunode_shopfront(caller): - "This is the top-menu screen." - - shopname = caller.location.key - wares = caller.location.db.storeroom.contents - - # Wares includes all items inside the storeroom, including the - # door! Let's remove that from our for sale list. - wares = [ware for ware in wares if ware.key.lower() != "door"] - - text = "*** Welcome to %s! ***\n" % shopname - if wares: - text += " Things for sale (choose 1-%i to inspect);" \ - " quit to exit:" % len(wares) - else: - text += " There is nothing for sale; quit to exit." - - options = [] - for ware in wares: - # add an option for every ware in store - options.append({"desc": "%s (%s gold)" % - (ware.key, ware.db.gold_value or 1), - "goto": "menunode_inspect_and_buy"}) - return text, options -``` - -In this code we assume the caller to be *inside* the shop when accessing the menu. This means we can -access the shop room via `caller.location` and get its `key` to display as the shop's name. We also -assume the shop has an Attribute `storeroom` we can use to get to our stock. We loop over our goods -to build up the menu's options. - -Note that *all options point to the same menu node* called `menunode_inspect_and_buy`! We can't know -which goods will be available to sale so we rely on this node to modify itself depending on the -circumstances. Let's create it now. - -```python -# further down in mygame/typeclasses/npcshop.py - -def menunode_inspect_and_buy(caller, raw_string): - "Sets up the buy menu screen." - - wares = caller.location.db.storeroom.contents - # Don't forget, we will need to remove that pesky door again! - wares = [ware for ware in wares if ware.key.lower() != "door"] - iware = int(raw_string) - 1 - ware = wares[iware] - value = ware.db.gold_value or 1 - wealth = caller.db.gold or 0 - text = "You inspect %s:\n\n%s" % (ware.key, ware.db.desc) - - def buy_ware_result(caller): - "This will be executed first when choosing to buy." - if wealth >= value: - rtext = "You pay %i gold and purchase %s!" % \ - (value, ware.key) - caller.db.gold -= value - ware.move_to(caller, quiet=True) - else: - rtext = "You cannot afford %i gold for %s!" % \ - (value, ware.key) - caller.msg(rtext) - - options = ({"desc": "Buy %s for %s gold" % \ - (ware.key, ware.db.gold_value or 1), - "goto": "menunode_shopfront", - "exec": buy_ware_result}, - {"desc": "Look for something else", - "goto": "menunode_shopfront"}) - - return text, options -``` - -In this menu node we make use of the `raw_string` argument to the node. This is the text the menu -user entered on the *previous* node to get here. Since we only allow numbered options in our menu, -`raw_input` must be an number for the player to get to this point. So we convert it to an integer -index (menu lists start from 1, whereas Python indices always starts at 0, so we need to subtract -1). We then use the index to get the corresponding item from storage. - -We just show the customer the `desc` of the item. In a more elaborate setup you might want to show -things like weapon damage and special stats here as well. - -When the user choose the "buy" option, EvMenu will execute the `exec` instruction *before* we go -back to the top node (the `goto` instruction). For this we make a little inline function -`buy_ware_result`. EvMenu will call the function given to `exec` like any menu node but it does not -need to return anything. In `buy_ware_result` we determine if the customer can afford the cost and -give proper return messages. This is also where we actually move the bought item into the inventory -of the customer. - -#### The command to start the menu - -We could *in principle* launch the shopping menu the moment a customer steps into our shop room, but -this would probably be considered pretty annoying. It's better to create a [Command](./Commands.md) for -customers to explicitly wanting to shop around. - -```python -# mygame/typeclasses/npcshop.py - -from evennia import Command - -class CmdBuy(Command): - """ - Start to do some shopping - - Usage: - buy - shop - browse - - This will allow you to browse the wares of the - current shop and buy items you want. - """ - key = "buy" - aliases = ("shop", "browse") - - def func(self): - "Starts the shop EvMenu instance" - evmenu.EvMenu(self.caller, - "typeclasses.npcshop", - startnode="menunode_shopfront") -``` - -This will launch the menu. The `EvMenu` instance is initialized with the path to this very module - -since the only global functions available in this module are our menu nodes, this will work fine -(you could also have put those in a separate module). We now just need to put this command in a -[CmdSet](./Command-Sets.md) so we can add it correctly to the game: - -```python -from evennia import CmdSet - -class ShopCmdSet(CmdSet): - def at_cmdset_creation(self): - self.add(CmdBuy()) -``` - -### Building the shop - -There are really only two things that separate our shop from any other Room: - -- The shop has the `storeroom` Attribute set on it, pointing to a second (completely normal) room. -- It has the `ShopCmdSet` stored on itself. This makes the `buy` command available to users entering -the shop. - -For testing we could easily add these features manually to a room using `@py` or other admin -commands. Just to show how it can be done we'll instead make a custom [Typeclass](./Typeclasses.md) for -the shop room and make a small command that builders can use to build both the shop and the -storeroom at once. - -```python -# bottom of mygame/typeclasses/npcshop.py - -from evennia import DefaultRoom, DefaultExit, DefaultObject -from evennia.utils.create import create_object - -# class for our front shop room -class NPCShop(DefaultRoom): - def at_object_creation(self): - # we could also use add(ShopCmdSet, permanent=True) - self.cmdset.add_default(ShopCmdSet) - self.db.storeroom = None - -# command to build a complete shop (the Command base class -# should already have been imported earlier in this file) -class CmdBuildShop(Command): - """ - Build a new shop - - Usage: - @buildshop shopname - - This will create a new NPCshop room - as well as a linked store room (named - simply -storage) for the - wares on sale. The store room will be - accessed through a locked door in - the shop. - """ - key = "@buildshop" - locks = "cmd:perm(Builders)" - help_category = "Builders" - - def func(self): - "Create the shop rooms" - if not self.args: - self.msg("Usage: @buildshop ") - return - # create the shop and storeroom - shopname = self.args.strip() - shop = create_object(NPCShop, - key=shopname, - location=None) - storeroom = create_object(DefaultRoom, - key="%s-storage" % shopname, - location=None) - shop.db.storeroom = storeroom - # create a door between the two - shop_exit = create_object(DefaultExit, - key="back door", - aliases=["storage", "store room"], - location=shop, - destination=storeroom) - storeroom_exit = create_object(DefaultExit, - key="door", - location=storeroom, - destination=shop) - # make a key for accessing the store room - storeroom_key_name = "%s-storekey" % shopname - storeroom_key = create_object(DefaultObject, - key=storeroom_key_name, - location=shop) - # only allow chars with this key to enter the store room - shop_exit.locks.add("traverse:holds(%s)" % storeroom_key_name) - - # inform the builder about progress - self.caller.msg("The shop %s was created!" % shop) -``` - -Our typeclass is simple and so is our `buildshop` command. The command (which is for Builders only) -just takes the name of the shop and builds the front room and a store room to go with it (always -named `"-storage"`. It connects the rooms with a two-way exit. You need to add -`CmdBuildShop` [to the default cmdset](./Adding-Command-Tutorial.md#step-2-adding-the-command-to-a- -default-cmdset) before you can use it. Once having created the shop you can now `@teleport` to it or -`@open` a new exit to it. You could also easily expand the above command to automatically create -exits to and from the new shop from your current location. - -To avoid customers walking in and stealing everything, we create a [Lock](./Locks.md) on the storage -door. It's a simple lock that requires the one entering to carry an object named -`-storekey`. We even create such a key object and drop it in the shop for the new shop -keeper to pick up. - -> If players are given the right to name their own objects, this simple lock is not very secure and -you need to come up with a more robust lock-key solution. - -> We don't add any descriptions to all these objects so looking "at" them will not be too thrilling. -You could add better default descriptions as part of the `@buildshop` command or leave descriptions -this up to the Builder. - -### The shop is open for business! - -We now have a functioning shop and an easy way for Builders to create it. All you need now is to -`@open` a new exit from the rest of the game into the shop and put some sell-able items in the store -room. Our shop does have some shortcomings: - -- For Characters to be able to buy stuff they need to also have the `gold` Attribute set on -themselves. -- We manually remove the "door" exit from our items for sale. But what if there are other unsellable -items in the store room? What if the shop owner walks in there for example - anyone in the store -could then buy them for 1 gold. -- What if someone else were to buy the item we're looking at just before we decide to buy it? It -would then be gone and the counter be wrong - the shop would pass us the next item in the list. - -Fixing these issues are left as an exercise. - -If you want to keep the shop fully NPC-run you could add a [Script](./Scripts.md) to restock the shop's -store room regularly. This shop example could also easily be owned by a human Player (run for them -by a hired NPC) - the shop owner would get the key to the store room and be responsible for keeping -it well stocked. diff --git a/docs/0.9.5/_sources/New-Models.md.txt b/docs/0.9.5/_sources/New-Models.md.txt deleted file mode 100644 index 4d73365d86..0000000000 --- a/docs/0.9.5/_sources/New-Models.md.txt +++ /dev/null @@ -1,264 +0,0 @@ -# New Models - -*Note: This is considered an advanced topic.* - -Evennia offers many convenient ways to store object data, such as via Attributes or Scripts. This is -sufficient for most use cases. But if you aim to build a large stand-alone system, trying to squeeze -your storage requirements into those may be more complex than you bargain for. Examples may be to -store guild data for guild members to be able to change, tracking the flow of money across a game- -wide economic system or implement other custom game systems that requires the storage of custom data -in a quickly accessible way. Whereas [Tags](./Tags.md) or [Scripts](./Scripts.md) can handle many situations, -sometimes things may be easier to handle by adding your own database model. - -## Overview of database tables - -SQL-type databases (which is what Evennia supports) are basically highly optimized systems for -retrieving text stored in tables. A table may look like this - -``` - id | db_key | db_typeclass_path | db_permissions ... - ------------------------------------------------------------------ - 1 | Griatch | evennia.DefaultCharacter | Developers ... - 2 | Rock | evennia.DefaultObject | None ... -``` - -Each line is considerably longer in your database. Each column is referred to as a "field" and every -row is a separate object. You can check this out for yourself. If you use the default sqlite3 -database, go to your game folder and run - - evennia dbshell - -You will drop into the database shell. While there, try: - - sqlite> .help # view help - - sqlite> .tables # view all tables - - # show the table field names for objects_objectdb - sqlite> .schema objects_objectdb - - # show the first row from the objects_objectdb table - sqlite> select * from objects_objectdb limit 1; - - sqlite> .exit - -Evennia uses [Django](https://docs.djangoproject.com), which abstracts away the database SQL -manipulation and allows you to search and manipulate your database entirely in Python. Each database -table is in Django represented by a class commonly called a *model* since it describes the look of -the table. In Evennia, Objects, Scripts, Channels etc are examples of Django models that we then -extend and build on. - -## Adding a new database table - -Here is how you add your own database table/models: - -1. In Django lingo, we will create a new "application" - a subsystem under the main Evennia program. -For this example we'll call it "myapp". Run the following (you need to have a working Evennia -running before you do this, so make sure you have run the steps in [Getting Started](Getting- -Started) first): - - cd mygame/world - evennia startapp myapp - -1. A new folder `myapp` is created. "myapp" will also be the name (the "app label") from now on. We -chose to put it in the `world/` subfolder here, but you could put it in the root of your `mygame` if -that makes more sense. -1. The `myapp` folder contains a few empty default files. What we are -interested in for now is `models.py`. In `models.py` you define your model(s). Each model will be a -table in the database. See the next section and don't continue until you have added the models you -want. -1. You now need to tell Evennia that the models of your app should be a part of your database -scheme. Add this line to your `mygame/server/conf/settings.py`file (make sure to use the path where -you put `myapp` and don't forget the comma at the end of the tuple): - - ``` - INSTALLED_APPS = INSTALLED_APPS + ("world.myapp", ) - ``` - -1. From `mygame/`, run - - evennia makemigrations myapp - evennia migrate - -This will add your new database table to the database. If you have put your game under version -control (if not, [you should](./Version-Control.md)), don't forget to `git add myapp/*` to add all items -to version control. - -## Defining your models - -A Django *model* is the Python representation of a database table. It can be handled like any other -Python class. It defines *fields* on itself, objects of a special type. These become the "columns" -of the database table. Finally, you create new instances of the model to add new rows to the -database. - -We won't describe all aspects of Django models here, for that we refer to the vast [Django -documentation](https://docs.djangoproject.com/en/2.2/topics/db/models/) on the subject. Here is a -(very) brief example: - -```python -from django.db import models - -class MyDataStore(models.Model): - "A simple model for storing some data" - db_key = models.CharField(max_length=80, db_index=True) - db_category = models.CharField(max_length=80, null=True, blank=True) - db_text = models.TextField(null=True, blank=True) - # we need this one if we want to be - # able to store this in an Evennia Attribute! - db_date_created = models.DateTimeField('date created', editable=False, - auto_now_add=True, db_index=True) -``` - -We create four fields: two character fields of limited length and one text field which has no -maximum length. Finally we create a field containing the current time of us creating this object. - -> The `db_date_created` field, with exactly this name, is *required* if you want to be able to store -instances of your custom model in an Evennia [Attribute](./Attributes.md). It will automatically be set -upon creation and can after that not be changed. Having this field will allow you to do e.g. -`obj.db.myinstance = mydatastore`. If you know you'll never store your model instances in Attributes -the `db_date_created` field is optional. - -You don't *have* to start field names with `db_`, this is an Evennia convention. It's nevertheless -recommended that you do use `db_`, partly for clarity and consistency with Evennia (if you ever want -to share your code) and partly for the case of you later deciding to use Evennia's -`SharedMemoryModel` parent down the line. - -The field keyword `db_index` creates a *database index* for this field, which allows quicker -lookups, so it's recommended to put it on fields you know you'll often use in queries. The -`null=True` and `blank=True` keywords means that these fields may be left empty or set to the empty -string without the database complaining. There are many other field types and keywords to define -them, see django docs for more info. - -Similar to using [django-admin](https://docs.djangoproject.com/en/2.2/howto/legacy-databases/) you -are able to do `evennia inspectdb` to get an automated listing of model information for an existing -database. As is the case with any model generating tool you should only use this as a starting -point for your models. - -## Creating a new model instance - -To create a new row in your table, you instantiate the model and then call its `save()` method: - -```python - from evennia.myapp import MyDataStore - - new_datastore = MyDataStore(db_key="LargeSword", - db_category="weapons", - db_text="This is a huge weapon!") - # this is required to actually create the row in the database! - new_datastore.save() - -``` - -Note that the `db_date_created` field of the model is not specified. Its flag `at_now_add=True` -makes sure to set it to the current date when the object is created (it can also not be changed -further after creation). - -When you update an existing object with some new field value, remember that you have to save the -object afterwards, otherwise the database will not update: - -```python - my_datastore.db_key = "Larger Sword" - my_datastore.save() -``` - -Evennia's normal models don't need to explicitly save, since they are based on `SharedMemoryModel` -rather than the raw django model. This is covered in the next section. - -## Using the `SharedMemoryModel` parent - -Evennia doesn't base most of its models on the raw `django.db.models` but on the Evennia base model -`evennia.utils.idmapper.models.SharedMemoryModel`. There are two main reasons for this: - -1. Ease of updating fields without having to explicitly call `save()` -2. On-object memory persistence and database caching - -The first (and least important) point means that as long as you named your fields `db_*`, Evennia -will automatically create field wrappers for them. This happens in the model's -[Metaclass](http://en.wikibooks.org/wiki/Python_Programming/Metaclasses) so there is no speed -penalty for this. The name of the wrapper will be the same name as the field, minus the `db_` -prefix. So the `db_key` field will have a wrapper property named `key`. You can then do: - -```python - my_datastore.key = "Larger Sword" -``` - -and don't have to explicitly call `save()` afterwards. The saving also happens in a more efficient -way under the hood, updating only the field rather than the entire model using django optimizations. -Note that if you were to manually add the property or method `key` to your model, this will be used -instead of the automatic wrapper and allows you to fully customize access as needed. - -To explain the second and more important point, consider the following example using the default -Django model parent: - -```python - shield = MyDataStore.objects.get(db_key="SmallShield") - shield.cracked = True # where cracked is not a database field -``` - -And then later: - -```python - shield = MyDataStore.objects.get(db_key="SmallShield") - print(shield.cracked) # error! -``` - -The outcome of that last print statement is *undefined*! It could *maybe* randomly work but most -likely you will get an `AttributeError` for not finding the `cracked` property. The reason is that -`cracked` doesn't represent an actual field in the database. It was just added at run-time and thus -Django don't care about it. When you retrieve your shield-match later there is *no* guarantee you -will get back the *same Python instance* of the model where you defined `cracked`, even if you -search for the same database object. - -Evennia relies heavily on on-model handlers and other dynamically created properties. So rather than -using the vanilla Django models, Evennia uses `SharedMemoryModel`, which levies something called -*idmapper*. The idmapper caches model instances so that we will always get the *same* instance back -after the first lookup of a given object. Using idmapper, the above example would work fine and you -could retrieve your `cracked` property at any time - until you rebooted when all non-persistent data -goes. - -Using the idmapper is both more intuitive and more efficient *per object*; it leads to a lot less -reading from disk. The drawback is that this system tends to be more memory hungry *overall*. So if -you know that you'll *never* need to add new properties to running instances or know that you will -create new objects all the time yet rarely access them again (like for a log system), you are -probably better off making "plain" Django models rather than using `SharedMemoryModel` and its -idmapper. - -To use the idmapper and the field-wrapper functionality you just have to have your model classes -inherit from `evennia.utils.idmapper.models.SharedMemoryModel` instead of from the default -`django.db.models.Model`: - -```python -from evennia.utils.idmapper.models import SharedMemoryModel - -class MyDataStore(SharedMemoryModel): - # the rest is the same as before, but db_* is important; these will - # later be settable as .key, .category, .text ... - db_key = models.CharField(max_length=80, db_index=True) - db_category = models.CharField(max_length=80, null=True, blank=True) - db_text = models.TextField(null=True, blank=True) - db_date_created = models.DateTimeField('date created', editable=False, - auto_now_add=True, db_index=True) -``` - -## Searching for your models - -To search your new custom database table you need to use its database *manager* to build a *query*. -Note that even if you use `SharedMemoryModel` as described in the previous section, you have to use -the actual *field names* in the query, not the wrapper name (so `db_key` and not just `key`). - -```python - from world.myapp import MyDataStore - - # get all datastore objects exactly matching a given key - matches = MyDataStore.objects.filter(db_key="Larger Sword") - # get all datastore objects with a key containing "sword" - # and having the category "weapons" (both ignoring upper/lower case) - matches2 = MyDataStore.objects.filter(db_key__icontains="sword", - db_category__iequals="weapons") - # show the matching data (e.g. inside a command) - for match in matches2: - self.caller.msg(match.db_text) -``` - -See the [Django query documentation](https://docs.djangoproject.com/en/2.2/topics/db/queries/) for a -lot more information about querying the database. diff --git a/docs/0.9.5/_sources/Nicks.md.txt b/docs/0.9.5/_sources/Nicks.md.txt deleted file mode 100644 index a49406c0de..0000000000 --- a/docs/0.9.5/_sources/Nicks.md.txt +++ /dev/null @@ -1,124 +0,0 @@ -# Nicks - - -*Nicks*, short for *Nicknames* is a system allowing an object (usually a [Account](./Accounts.md)) to -assign custom replacement names for other game entities. - -Nicks are not to be confused with *Aliases*. Setting an Alias on a game entity actually changes an -inherent attribute on that entity, and everyone in the game will be able to use that alias to -address the entity thereafter. A *Nick* on the other hand, is used to map a different way *you -alone* can refer to that entity. Nicks are also commonly used to replace your input text which means -you can create your own aliases to default commands. - -Default Evennia use Nicks in three flavours that determine when Evennia actually tries to do the -substitution. - -- inputline - replacement is attempted whenever you write anything on the command line. This is the -default. -- objects - replacement is only attempted when referring to an object -- accounts - replacement is only attempted when referring an account - -Here's how to use it in the default command set (using the `nick` command): - - nick ls = look - -This is a good one for unix/linux users who are accustomed to using the `ls` command in their daily -life. It is equivalent to `nick/inputline ls = look`. - - nick/object mycar2 = The red sports car - -With this example, substitutions will only be done specifically for commands expecting an object -reference, such as - - look mycar2 - -becomes equivalent to "`look The red sports car`". - - nick/accounts tom = Thomas Johnsson - -This is useful for commands searching for accounts explicitly: - - @find *tom - -One can use nicks to speed up input. Below we add ourselves a quicker way to build red buttons. In -the future just writing *rb* will be enough to execute that whole long string. - - nick rb = @create button:examples.red_button.RedButton - -Nicks could also be used as the start for building a "recog" system suitable for an RP mud. - - nick/account Arnold = The mysterious hooded man - -The nick replacer also supports unix-style *templating*: - - nick build $1 $2 = @create/drop $1;$2 - -This will catch space separated arguments and store them in the the tags `$1` and `$2`, to be -inserted in the replacement string. This example allows you to do `build box crate` and have Evennia -see `@create/drop box;crate`. You may use any `$` numbers between 1 and 99, but the markers must -match between the nick pattern and the replacement. - -> If you want to catch "the rest" of a command argument, make sure to put a `$` tag *with no spaces -to the right of it* - it will then receive everything up until the end of the line. - -You can also use [shell-type wildcards](http://www.linfo.org/wildcard.html): - -- \* - matches everything. -- ? - matches a single character. -- [seq] - matches everything in the sequence, e.g. [xyz] will match both x, y and z -- [!seq] - matches everything *not* in the sequence. e.g. [!xyz] will match all but x,y z. - - - - - -## Coding with nicks - -Nicks are stored as the `Nick` database model and are referred from the normal Evennia -[object](./Objects.md) through the `nicks` property - this is known as the *NickHandler*. The NickHandler -offers effective error checking, searches and conversion. - -```python - # A command/channel nick: - obj.nicks.add("greetjack", "tell Jack = Hello pal!") - - # An object nick: - obj.nicks.add("rose", "The red flower", nick_type="object") - - # An account nick: - obj.nicks.add("tom", "Tommy Hill", nick_type="account") - - # My own custom nick type (handled by my own game code somehow): - obj.nicks.add("hood", "The hooded man", nick_type="my_identsystem") - - # get back the translated nick: - full_name = obj.nicks.get("rose", nick_type="object") - - # delete a previous set nick - object.nicks.remove("rose", nick_type="object") -``` - -In a command definition you can reach the nick handler through `self.caller.nicks`. See the `nick` -command in `evennia/commands/default/general.py` for more examples. - -As a last note, The Evennia [channel](./Communications.md) alias systems are using nicks with the -`nick_type="channel"` in order to allow users to create their own custom aliases to channels. - -# Advanced note - -Internally, nicks are [Attributes](./Attributes.md) saved with the `db_attrype` set to "nick" (normal -Attributes has this set to `None`). - -The nick stores the replacement data in the Attribute.db_value field as a tuple with four fields -`(regex_nick, template_string, raw_nick, raw_template)`. Here `regex_nick` is the converted regex -representation of the `raw_nick` and the `template-string` is a version of the `raw_template` -prepared for efficient replacement of any `$`- type markers. The `raw_nick` and `raw_template` are -basically the unchanged strings you enter to the `nick` command (with unparsed `$` etc). - -If you need to access the tuple for some reason, here's how: - -```python -tuple = obj.nicks.get("nickname", return_tuple=True) -# or, alternatively -tuple = obj.nicks.get("nickname", return_obj=True).value -``` \ No newline at end of file diff --git a/docs/0.9.5/_sources/OOB.md.txt b/docs/0.9.5/_sources/OOB.md.txt deleted file mode 100644 index efaa472c55..0000000000 --- a/docs/0.9.5/_sources/OOB.md.txt +++ /dev/null @@ -1,170 +0,0 @@ -# OOB - -OOB, or Out-Of-Band, means sending data between Evennia and the user's client without the user -prompting it or necessarily being aware that it's being passed. Common uses would be to update -client health-bars, handle client button-presses or to display certain tagged text in a different -window pane. - -## Briefly on input/outputcommands - -Inside Evennia, all server-client communication happens in the same way (so plain text is also an -'OOB message' as far as Evennia is concerned). The message follows the [Message Path](./Messagepath.md). -You should read up on that if you are unfamiliar with it. As the message travels along the path it -has a standardized internal form: a tuple with a string, a tuple and a dict: - - ("cmdname", (args), {kwargs}) - -This is often referred to as an *inputcommand* or *outputcommand*, depending on the direction it's -traveling. The end point for an inputcommand, (the 'Evennia-end' of the message path) is a matching -[Inputfunc](./Inputfuncs.md). This function is called as `cmdname(session, *args, **kwargs)` where -`session` is the Session-source of the command. Inputfuncs can easily be added by the developer to -support/map client commands to actions inside Evennia (see the [inputfunc](./Inputfuncs.md) page for more -details). - -When a message is outgoing (at the 'Client-end' of the message path) the outputcommand is handled by -a matching *Outputfunc*. This is responsible for converting the internal Evennia representation to a -form suitable to send over the wire to the Client. Outputfuncs are hard-coded. Which is chosen and -how it processes the outgoing data depends on the nature of the client it's connected to. The only -time one would want to add new outputfuncs is as part of developing support for a new Evennia -[Protocol](./Custom-Protocols.md). - -## Sending and receiving an OOB message - -Sending is simple. You just use the normal `msg` method of the object whose session you want to send -to. For example in a Command: - -```python - caller.msg(cmdname=((args, ...), {key:value, ...})) -``` - -A special case is the `text` input/outputfunc. It's so common that it's the default of the `msg` -method. So these are equivalent: - -```python - caller.msg("Hello") - caller.msg(text="Hello") -``` - -You don't have to specify the full output/input definition. So for example, if your particular -command only needs kwargs, you can skip the `(args)` part. Like in the `text` case you can skip -writing the tuple if there is only one arg ... and so on - the input is pretty flexible. If there -are no args at all you need to give the empty tuple `msg(cmdname=(,)` (giving `None` would mean a -single argument `None`). - -Which commands you can send depends on the client. If the client does not support an explicit OOB -protocol (like many old/legacy MUD clients) Evennia can only send `text` to them and will quietly -drop any other types of outputfuncs. - -> Remember that a given message may go to multiple clients with different capabilities. So unless -you turn off telnet completely and only rely on the webclient, you should never rely on non-`text` -OOB messages always reaching all targets. - -[Inputfuncs](./Inputfuncs.md) lists the default inputfuncs available to handle incoming OOB messages. To -accept more you need to add more inputfuncs (see that page for more info). - -## Supported OOB protocols - -Evennia supports clients using one of the following protocols: - -### Telnet - -By default telnet (and telnet+SSL) supports only the plain `text` outputcommand. Evennia however -detects if the Client supports one of two MUD-specific OOB *extensions* to the standard telnet -protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the -client uses. If the client supports both, GMCP will be used. - -> Note that for Telnet, `text` has a special status as the "in-band" operation. So the `text` -outputcommand sends the `text` argument directly over the wire, without going through the OOB -translations described below. - -#### Telnet + GMCP - -[GMCP](http://www.gammon.com.au/gmcp), the *Generic Mud Communication Protocol* sends data on the -form `cmdname + JSONdata`. Here the cmdname is expected to be on the form "Package.Subpackage". -There could also be additional Sub-sub packages etc. The names of these 'packages' and 'subpackages' -are not that well standardized beyond what individual MUDs or companies have chosen to go with over -the years. You can decide on your own package names, but here are what others are using: - -- [Aardwolf GMCP](http://www.aardwolf.com/wiki/index.php/Clients/GMCP) -- [Discworld GMCP](http://discworld.starturtle.net/lpc/playing/documentation.c?path=/concepts/gmcp) -- [Avatar GMCP](http://www.outland.org/infusions/wiclear/index.php?title=MUD%20Protocols&lang=en) -- [IRE games GMCP](http://nexus.ironrealms.com/GMCP) - -Evennia will translate underscores to `.` and capitalize to fit the specification. So the -outputcommand `foo_bar` will become a GMCP command-name `Foo.Bar`. A GMCP command "Foo.Bar" will be -come `foo_bar`. To send a GMCP command that turns into an Evennia inputcommand without an -underscore, use the `Core` package. So `Core.Cmdname` becomes just `cmdname` in Evennia and vice -versa. - -On the wire, a GMCP instruction for `("cmdname", ("arg",), {})` will look like this: - - IAC SB GMCP "cmdname" "arg" IAC SE - -where all the capitalized words are telnet character constants specified in -`evennia/server/portal/telnet_oob.py`. These are parsed/added by the protocol and we don't include -these in the listings below. - -Input/Outputfunc | GMCP-Command ------------------- -`[cmd_name, [], {}]` | Cmd.Name -`[cmd_name, [arg], {}]` | Cmd.Name arg -`[cmd_na_me, [args],{}]` | Cmd.Na.Me [args] -`[cmd_name, [], {kwargs}]` | Cmd.Name {kwargs} -`[cmdname, [args, {kwargs}]` | Core.Cmdname [[args],{kwargs}] - -Since Evennia already supplies default inputfuncs that don't match the names expected by the most -common GMCP implementations we have a few hard-coded mappings for those: - -GMCP command name | Input/Outputfunc name ------------------ -"Core.Hello" | "client_options" -"Core.Supports.Get" | "client_options" -"Core.Commands.Get" | "get_inputfuncs" -"Char.Value.Get" | "get_value" -"Char.Repeat.Update" | "repeat" -"Char.Monitor.Update" | "monitor" - -#### Telnet + MSDP - -[MSDP](http://tintin.sourceforge.net/msdp/), the *Mud Server Data Protocol*, is a competing standard -to GMCP. The MSDP protocol page specifies a range of "recommended" available MSDP command names. -Evennia does *not* support those - since MSDP doesn't specify a special format for its command names -(like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual -name. - -MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports -strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs -needed. When sending MSDP for `("cmdname", ("arg",), {})` the resulting MSDP instruction will look -like this: - - IAC SB MSDP VAR cmdname VAL arg IAC SE - -The various available MSDP constants like `VAR` (variable), `VAL` (value), `ARRAYOPEN`/`ARRAYCLOSE` -and `TABLEOPEN`/`TABLECLOSE` are specified in `evennia/server/portal/telnet_oob`. - -Outputfunc/Inputfunc | MSDP instruction -------------------------- -`[cmdname, [], {}]` | VAR cmdname VAL -`[cmdname, [arg], {}]` | VAR cmdname VAL arg -`[cmdname, [args],{}]` | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE -`[cmdname, [], {kwargs}]` | VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE -`[cmdname, [args], {kwargs}]` | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE VAR cmdname -VAL TABLEOPEN VAR key VAL val ... TABLECLOSE - -Observe that `VAR ... VAL` always identifies cmdnames, so if there are multiple arrays/dicts tagged -with the same cmdname they will be appended to the args, kwargs of that inputfunc. Vice-versa, a -different `VAR ... VAL` (outside a table) will come out as a second, different command input. - -### SSH - -SSH only supports the `text` input/outputcommand. - -### Web client - -Our web client uses pure JSON structures for all its communication, including `text`. This maps -directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs. So the -same example `("cmdname", ("arg",), {})` will be sent/received as a valid JSON structure - - ["cmdname, ["arg"], {}] - -Since JSON is native to Javascript, this becomes very easy for the webclient to handle. diff --git a/docs/0.9.5/_sources/Objects.md.txt b/docs/0.9.5/_sources/Objects.md.txt deleted file mode 100644 index f0e8010df5..0000000000 --- a/docs/0.9.5/_sources/Objects.md.txt +++ /dev/null @@ -1,185 +0,0 @@ -# Objects - - -All in-game objects in Evennia, be it characters, chairs, monsters, rooms or hand grenades are -represented by an Evennia *Object*. Objects form the core of Evennia and is probably what you'll -spend most time working with. Objects are [Typeclassed](./Typeclasses.md) entities. - -## How to create your own object types - -An Evennia Object is, per definition, a Python class that includes `evennia.DefaultObject` among its -parents. In `mygame/typeclasses/objects.py` there is already a class `Object` that inherits from -`DefaultObject` and that you can inherit from. You can put your new typeclass directly in that -module or you could organize your code in some other way. Here we assume we make a new module -`mygame/typeclasses/flowers.py`: - -```python - # mygame/typeclasses/flowers.py - - from typeclasses.objects import Object - - class Rose(Object): - """ - This creates a simple rose object - """ - def at_object_creation(self): - "this is called only once, when object is first created" - # add a persistent attribute 'desc' - # to object (silly example). - self.db.desc = "This is a pretty rose with thorns." -``` - -You could save this in the `mygame/typeclasses/objects.py` (then you'd not need to import `Object`) -or you can put it in a new module. Let's say we do the latter, making a module -`typeclasses/flowers.py`. Now you just need to point to the class *Rose* with the `@create` command -to make a new rose: - - @create/drop MyRose:flowers.Rose - -What the `@create` command actually *does* is to use `evennia.create_object`. You can do the same -thing yourself in code: - -```python - from evennia import create_object - new_rose = create_object("typeclasses.flowers.Rose", key="MyRose") -``` - -(The `@create` command will auto-append the most likely path to your typeclass, if you enter the -call manually you have to give the full path to the class. The `create.create_object` function is -powerful and should be used for all coded object creating (so this is what you use when defining -your own building commands). Check out the `ev.create_*` functions for how to build other entities -like [Scripts](./Scripts.md)). - -This particular Rose class doesn't really do much, all it does it make sure the attribute -`desc`(which is what the `look` command looks for) is pre-set, which is pretty pointless since you -will usually want to change this at build time (using the `@desc` command or using the -[Spawner](./Spawner-and-Prototypes.md)). The `Object` typeclass offers many more hooks that is available -to use though - see next section. - -## Properties and functions on Objects - -Beyond the properties assigned to all [typeclassed](./Typeclasses.md) objects (see that page for a list -of those), the Object also has the following custom properties: - -- `aliases` - a handler that allows you to add and remove aliases from this object. Use -`aliases.add()` to add a new alias and `aliases.remove()` to remove one. -- `location` - a reference to the object currently containing this object. -- `home` is a backup location. The main motivation is to have a safe place to move the object to if -its `location` is destroyed. All objects should usually have a home location for safety. -- `destination` - this holds a reference to another object this object links to in some way. Its -main use is for [Exits](./Objects.md#exits), it's otherwise usually unset. -- `nicks` - as opposed to aliases, a [Nick](./Nicks.md) holds a convenient nickname replacement for a -real name, word or sequence, only valid for this object. This mainly makes sense if the Object is -used as a game character - it can then store briefer shorts, example so as to quickly reference game -commands or other characters. Use nicks.add(alias, realname) to add a new one. -- `account` - this holds a reference to a connected [Account](./Accounts.md) controlling this object (if -any). Note that this is set also if the controlling account is *not* currently online - to test if -an account is online, use the `has_account` property instead. -- `sessions` - if `account` field is set *and the account is online*, this is a list of all active -sessions (server connections) to contact them through (it may be more than one if multiple -connections are allowed in settings). -- `has_account` - a shorthand for checking if an *online* account is currently connected to this -object. -- `contents` - this returns a list referencing all objects 'inside' this object (i,e. which has this -object set as their `location`). -- `exits` - this returns all objects inside this object that are *Exits*, that is, has the -`destination` property set. - -The last two properties are special: - -- `cmdset` - this is a handler that stores all [command sets](./Command-Sets.md) defined on the -object (if any). -- `scripts` - this is a handler that manages [Scripts](./Scripts.md) attached to the object (if any). - -The Object also has a host of useful utility functions. See the function headers in -`src/objects/objects.py` for their arguments and more details. - -- `msg()` - this function is used to send messages from the server to an account connected to this -object. -- `msg_contents()` - calls `msg` on all objects inside this object. -- `search()` - this is a convenient shorthand to search for a specific object, at a given location -or globally. It's mainly useful when defining commands (in which case the object executing the -command is named `caller` and one can do `caller.search()` to find objects in the room to operate -on). -- `execute_cmd()` - Lets the object execute the given string as if it was given on the command line. -- `move_to` - perform a full move of this object to a new location. This is the main move method -and will call all relevant hooks, do all checks etc. -- `clear_exits()` - will delete all [Exits](./Objects.md#exits) to *and* from this object. -- `clear_contents()` - this will not delete anything, but rather move all contents (except Exits) to -their designated `Home` locations. -- `delete()` - deletes this object, first calling `clear_exits()` and - `clear_contents()`. - -The Object Typeclass defines many more *hook methods* beyond `at_object_creation`. Evennia calls -these hooks at various points. When implementing your custom objects, you will inherit from the -base parent and overload these hooks with your own custom code. See `evennia.objects.objects` for an -updated list of all the available hooks or the [API for DefaultObject -here](evennia.objects.objects.DefaultObject). - -## Subclasses of `Object` - -There are three special subclasses of *Object* in default Evennia - *Characters*, *Rooms* and -*Exits*. The reason they are separated is because these particular object types are fundamental, -something you will always need and in some cases requires some extra attention in order to be -recognized by the game engine (there is nothing stopping you from redefining them though). In -practice they are all pretty similar to the base Object. - -### Characters - -Characters are objects controlled by [Accounts](./Accounts.md). When a new Account -logs in to Evennia for the first time, a new `Character` object is created and -the Account object is assigned to the `account` attribute. A `Character` object -must have a [Default Commandset](./Command-Sets.md) set on itself at -creation, or the account will not be able to issue any commands! If you just -inherit your own class from `evennia.DefaultCharacter` and make sure to use -`super()` to call the parent methods you should be fine. In -`mygame/typeclasses/characters.py` is an empty `Character` class ready for you -to modify. - -### Rooms - -*Rooms* are the root containers of all other objects. The only thing really separating a room from -any other object is that they have no `location` of their own and that default commands like `@dig` -creates objects of this class - so if you want to expand your rooms with more functionality, just -inherit from `ev.DefaultRoom`. In `mygame/typeclasses/rooms.py` is an empty `Room` class ready for -you to modify. - -### Exits - -*Exits* are objects connecting other objects (usually *Rooms*) together. An object named *North* or -*in* might be an exit, as well as *door*, *portal* or *jump out the window*. An exit has two things -that separate them from other objects. Firstly, their *destination* property is set and points to a -valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits -define a special [Transit Command](./Commands.md) on themselves when they are created. This command is -named the same as the exit object and will, when called, handle the practicalities of moving the -character to the Exits's *destination* - this allows you to just enter the name of the exit on its -own to move around, just as you would expect. - -The exit functionality is all defined on the Exit typeclass, so you could in principle completely -change how exits work in your game (it's not recommended though, unless you really know what you are -doing). Exits are [locked](./Locks.md) using an access_type called *traverse* and also make use of a few -hook methods for giving feedback if the traversal fails. See `evennia.DefaultExit` for more info. -In `mygame/typeclasses/exits.py` there is an empty `Exit` class for you to modify. - -The process of traversing an exit is as follows: - -1. The traversing `obj` sends a command that matches the Exit-command name on the Exit object. The -[cmdhandler](./Commands.md) detects this and triggers the command defined on the Exit. Traversal always -involves the "source" (the current location) and the `destination` (this is stored on the Exit -object). -1. The Exit command checks the `traverse` lock on the Exit object -1. The Exit command triggers `at_traverse(obj, destination)` on the Exit object. -1. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks, -in order: - 1. `obj.at_before_move(destination)` - if this returns False, move is aborted. - 1. `origin.at_object_leave(obj, destination)` - 1. `obj.announce_move_from(destination)` - 1. Move is performed by changing `obj.location` from source location to `destination`. - 1. `obj.announce_move_to(source)` - 1. `destination.at_object_receive(obj, source)` - 1. `obj.at_after_move(source)` -1. On the Exit object, `at_after_traverse(obj, source)` is triggered. - -If the move fails for whatever reason, the Exit will look for an Attribute `err_traverse` on itself -and display this as an error message. If this is not found, the Exit will instead call -`at_failed_traverse(obj)` on itself. diff --git a/docs/0.9.5/_sources/Online-Setup.md.txt b/docs/0.9.5/_sources/Online-Setup.md.txt deleted file mode 100644 index 9a27941eab..0000000000 --- a/docs/0.9.5/_sources/Online-Setup.md.txt +++ /dev/null @@ -1,426 +0,0 @@ -# Online Setup - - -Evennia development can be made without any Internet connection beyond fetching updates. At some -point however, you are likely to want to make your game visible online, either as part opening it to -the public or to allow other developers or beta testers access to it. - -## Connecting from the outside - -Accessing your Evennia server from the outside is not hard on its own. Any issues are usually due to -the various security measures your computer, network or hosting service has. These will generally -(and correctly) block outside access to servers on your machine unless you tell them otherwise. - -We will start by showing how to host your server on your own local computer. Even if you plan to -host your "real" game on a remote host later, setting it up locally is useful practice. We cover -remote hosting later in this document. - -Out of the box, Evennia uses three ports for outward communication. If your computer has a firewall, -these should be open for in/out communication (and only these, other ports used by Evennia are -internal to your computer only). - - `4000`, telnet, for traditional mud clients - - `4001`, HTTP for the website) - - `4002`, websocket, for the web client - -Evennia will by default accept incoming connections on all interfaces (`0.0.0.0`) so in principle -anyone knowing the ports to use and has the IP address to your machine should be able to connect to -your game. - - - Make sure Evennia is installed and that you have activated the virtualenv. Start the server with -`evennia start --log`. The `--log` (or `-l`) will make sure that the logs are echoed to the -terminal. -> Note: If you need to close the log-view, use `Ctrl-C`. Use just `evennia --log` on its own to -start tailing the logs again. - - Make sure you can connect with your web browser to `http://localhost:4001` or, alternatively, -`http://127.0.0.1:4001` which is the same thing. You should get your Evennia web site and be able to -play the game in the web client. Also check so that you can connect with a mud client to host -`localhost`, port `4000` or host `127.0.0.1`, port `4000`. -- [Google for "my ip"](https://www.google.se/search?q=my+ip) or use any online service to figure out -what your "outward-facing" IP address is. For our purposes, let's say your outward-facing IP is -`203.0.113.0`. - - Next try your outward-facing IP by opening `http://203.0.113.0:4001` in a browser. If this works, -that's it! Also try telnet, with the server set to `203.0.113.0` and port `4000`. However, most -likely it will *not* work. If so, read on. - - If your computer has a firewall, it may be blocking the ports we need (it may also block telnet -overall). If so, you need to open the outward-facing ports to in/out communication. See the -manual/instructions for your firewall software on how to do this. To test you could also temporarily -turn off your firewall entirely to see if that was indeed the problem. - - Another common problem for not being able to connect is that you are using a hardware router -(like a wifi router). The router sits 'between' your computer and the Internet. So the IP you find -with Google is the *router's* IP, not that of your computer. To resolve this you need to configure -your router to *forward* data it gets on its ports to the IP and ports of your computer sitting in -your private network. How to do this depends on the make of your router; you usually configure it -using a normal web browser. In the router interface, look for "Port forwarding" or maybe "Virtual -server". If that doesn't work, try to temporarily wire your computer directly to the Internet outlet -(assuming your computer has the ports for it). You'll need to check for your IP again. If that -works, you know the problem is the router. - -> Note: If you need to reconfigure a router, the router's Internet-facing ports do *not* have to -have to have the same numbers as your computer's (and Evennia's) ports! For example, you might want -to connect Evennia's outgoing port 4001 to an outgoing router port 80 - this is the port HTTP -requests use and web browsers automatically look for - if you do that you could go to -`http://203.0.113.0` without having to add the port at the end. This would collide with any other -web services you are running through this router though. - -### Settings example - -You can connect Evennia to the Internet without any changes to your settings. The default settings -are easy to use but are not necessarily the safest. You can customize your online presence in your -[settings file](./Server-Conf.md#settings-file). To have Evennia recognize changed port settings you have -to do a full `evennia reboot` to also restart the Portal and not just the Server component. - -Below is an example of a simple set of settings, mostly using the defaults. Evennia will require -access to five computer ports, of which three (only) should be open to the outside world. Below we -continue to assume that our server address is `203.0.113.0`. - -```python -# in mygame/server/conf/settings.py - -SERVERNAME = "MyGame" - -# open to the internet: 4000, 4001, 4002 -# closed to the internet (internal use): 4005, 4006 -TELNET_PORTS = [4000] -WEBSOCKET_CLIENT_PORT = 4002 -WEBSERVER_PORTS = [(4001, 4005)] -AMP_PORT = 4006 - -# Optional - security measures limiting interface access -# (don't set these before you know things work without them) -TELNET_INTERFACES = ['203.0.113.0'] -WEBSOCKET_CLIENT_INTERFACE = '203.0.113.0' -ALLOWED_HOSTS = [".mymudgame.com"] - -# uncomment if you want to lock the server down for maintenance. -# LOCKDOWN_MODE = True - -``` - -Read on for a description of the individual settings. - -### Telnet - -```python -# Required. Change to whichever outgoing Telnet port(s) -# you are allowed to use on your host. -TELNET_PORTS = [4000] -# Optional for security. Restrict which telnet -# interfaces we should accept. Should be set to your -# outward-facing IP address(es). Default is ´0.0.0.0´ -# which accepts all interfaces. -TELNET_INTERFACES = ['0.0.0.0'] -``` - -The `TELNET_*` settings are the most important ones for getting a traditional base game going. Which -IP addresses you have available depends on your server hosting solution (see the next sections). -Some hosts will restrict which ports you are allowed you use so make sure to check. - -### Web server - -```python -# Required. This is a list of tuples -# (outgoing_port, internal_port). Only the outgoing -# port should be open to the world! -# set outgoing port to 80 if you want to run Evennia -# as the only web server on your machine (if available). -WEBSERVER_PORTS = [(4001, 4005)] -# Optional for security. Change this to the IP your -# server can be reached at (normally the same -# as TELNET_INTERFACES) -WEBSERVER_INTERFACES = ['0.0.0.0'] -# Optional for security. Protects against -# man-in-the-middle attacks. Change it to your server's -# IP address or URL when you run a production server. -ALLOWED_HOSTS = ['*'] -``` - -The web server is always configured with two ports at a time. The *outgoing* port (`4001` by -default) is the port external connections can use. If you don't want users to have to specify the -port when they connect, you should set this to `80` - this however only works if you are not running -any other web server on the machine. -The *internal* port (`4005` by default) is used internally by Evennia to communicate between the -Server and the Portal. It should not be available to the outside world. You usually only need to -change the outgoing port unless the default internal port is clashing with some other program. - -### Web client - -```python -# Required. Change this to the main IP address of your server. -WEBSOCKET_CLIENT_INTERFACE = '0.0.0.0' -# Optional and needed only if using a proxy or similar. Change -# to the IP or address where the client can reach -# your server. The ws:// part is then required. If not given, the client -# will use its host location. -WEBSOCKET_CLIENT_URL = "" -# Required. Change to a free port for the websocket client to reach -# the server on. This will be automatically appended -# to WEBSOCKET_CLIENT_URL by the web client. -WEBSOCKET_CLIENT_PORT = 4002 -``` - -The websocket-based web client needs to be able to call back to the server, and these settings must -be changed for it to find where to look. If it cannot find the server you will get an warning in -your browser's Console (in the dev tools of the browser), and the client will revert to the AJAX- -based of the client instead, which tends to be slower. - -### Other ports - -```python -# Optional public facing. Only allows SSL connections (off by default). -SSL_PORTS = [4003] -SSL_INTERFACES = ['0.0.0.0'] -# Optional public facing. Only if you allow SSH connections (off by default). -SSH_PORTS = [4004] -SSH_INTERFACES = ['0.0.0.0'] -# Required private. You should only change this if there is a clash -# with other services on your host. Should NOT be open to the -# outside world. -AMP_PORT = 4006 -``` - -The `AMP_PORT` is required to work, since this is the internal port linking Evennia's -[Server and Portal](./Portal-And-Server.md) components together. The other ports are encrypted ports that may be -useful for custom protocols but are otherwise not used. - -### Lockdown mode - -When you test things out and check configurations you may not want players to drop in on you. -Similarly, if you are doing maintenance on a live game you may want to take it offline for a while -to fix eventual problems without risking people connecting. To do this, stop the server with -`evennia stop` and add `LOCKDOWN_MODE = True` to your settings file. When you start the server -again, your game will only be accessible from localhost. - -### Registering with the Evennia game directory - -Once your game is online you should make sure to register it with the [Evennia Game -Index](http://games.evennia.com/). Registering with the index will help people find your server, -drum up interest for your game and also shows people that Evennia is being used. You can do this -even if you are just starting development - if you don't give any telnet/web address it will appear -as _Not yet public_ and just be a teaser. If so, pick _pre-alpha_ as the development status. - -To register, stand in your game dir, run - - evennia connections - -and follow the instructions. See the [Game index page](./Evennia-Game-Index.md) for more details. - -## SSL - -SSL can be very useful for web clients. It will protect the credentials and gameplay of your users -over a web client if they are in a public place, and your websocket can also be switched to WSS for -the same benefit. SSL certificates used to cost money on a yearly basis, but there is now a program -that issues them for free with assisted setup to make the entire process less painful. - -Options that may be useful in combination with an SSL proxy: - -``` -# See above for the section on Lockdown Mode. -# Useful for a proxy on the public interface connecting to Evennia on localhost. -LOCKDOWN_MODE = True - -# Have clients communicate via wss after connecting with https to port 4001. -# Without this, you may get DOMException errors when the browser tries -# to create an insecure websocket from a secure webpage. -WEBSOCKET_CLIENT_URL = "wss://fqdn:4002" -``` - -### Let's Encrypt - -[Let's Encrypt](https://letsencrypt.org) is a certificate authority offering free certificates to -secure a website with HTTPS. To get started issuing a certificate for your web server using Let's -Encrypt, see these links: - - - [Let's Encrypt - Getting Started](https://letsencrypt.org/getting-started/) - - The [CertBot Client](https://certbot.eff.org/) is a program for automatically obtaining a -certificate, use it and maintain it with your website. - -Also, on Freenode visit the #letsencrypt channel for assistance from the community. For an -additional resource, Let's Encrypt has a very active [community -forum](https://community.letsencrypt.org/). - -[A blog where someone sets up Let's Encrypt](https://www.digitalocean.com/community/tutorials/how- -to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04) - -The only process missing from all of the above documentation is how to pass verification. This is -how Let's Encrypt verifies that you have control over your domain (not necessarily ownership, it's -Domain Validation (DV)). This can be done either with configuring a certain path on your web server -or through a TXT record in your DNS. Which one you will want to do is a personal preference, but can -also be based on your hosting choice. In a controlled/cPanel environment, you will most likely have -to use DNS verification. - -## Relevant SSL Proxy Setup Information -- [Apache webserver configuration](./Apache-Config.md) (optional) -- [HAProxy Config](./HAProxy-Config.md) - -## Hosting locally or remotely? - -### Using your own computer as a server - -What we showed above is by far the simplest and probably cheapest option: Run Evennia on your own -home computer. Moreover, since Evennia is its own web server, you don't need to install anything -extra to have a website. - -**Advantages** -- Free (except for internet costs and the electrical bill). -- Full control over the server and hardware (it sits right there!). -- Easy to set up. -- Suitable for quick setups - e.g. to briefly show off results to your collaborators. - -**Disadvantages** -- You need a good internet connection, ideally without any upload/download limits/costs. -- If you want to run a full game this way, your computer needs to always be on. It could be noisy, -and as mentioned, the electrical bill must be considered. -- No support or safety - if your house burns down, so will your game. Also, you are yourself -responsible for doing regular backups. -- Potentially not as easy if you don't know how to open ports in your firewall or router. -- Home IP numbers are often dynamically allocated, so for permanent online time you need to set up a -DNS to always re-point to the right place (see below). -- You are personally responsible for any use/misuse of your internet connection-- though unlikely -(but not impossible) if running your server somehow causes issues for other customers on the -network, goes against your ISP's terms of service (many ISPs insist on upselling you to a business- -tier connection) or you are the subject of legal action by a copyright holder, you may find your -main internet connection terminated as a consequence. - -#### Setting up your own machine as a server - -[The first section](./Online-Setup.md#connecting-from-the-outside) of this page describes how to do this -and allow users to connect to the IP address of your machine/router. - -A complication with using a specific IP address like this is that your home IP might not remain the -same. Many ISPs (Internet Service Providers) allocates a *dynamic* IP to you which could change at -any time. When that happens, that IP you told people to go to will be worthless. Also, that long -string of numbers is not very pretty, is it? It's hard to remember and not easy to use in marketing -your game. What you need is to alias it to a more sensible domain name - an alias that follows you -around also when the IP changes. - -1. To set up a domain name alias, we recommend starting with a free domain name from -[FreeDNS](https://freedns.afraid.org/). Once you register there (it's free) you have access to tens -of thousands domain names that people have "donated" to allow you to use for your own sub domain. -For example, `strangled.net` is one of those available domains. So tying our IP address to -`strangled.net` using the subdomain `evennia` would mean that one could henceforth direct people to -`http://evennia.strangled.net:4001` for their gaming needs - far easier to remember! -1. So how do we make this new, nice domain name follow us also if our IP changes? For this we need -to set up a little program on our computer. It will check whenever our ISP decides to change our IP -and tell FreeDNS that. There are many alternatives to be found from FreeDNS:s homepage, one that -works on multiple platforms is [inadyn](http://www.inatech.eu/inadyn/). Get it from their page or, -in Linux, through something like `apt-get install inadyn`. -1. Next, you login to your account on FreeDNS and go to the -[Dynamic](https://freedns.afraid.org/dynamic/) page. You should have a list of your subdomains. Click -the `Direct URL` link and you'll get a page with a text message. Ignore that and look at the URL of -the page. It should be ending in a lot of random letters. Everything after the question mark is your -unique "hash". Copy this string. -1. You now start inadyn with the following command (Linux): - - `inadyn --dyndns_system default@freedns.afraid.org -a , &` - - where `` would be `evennia.strangled.net` and `` the string of numbers we copied -from FreeDNS. The `&` means we run in the background (might not be valid in other operating -systems). `inadyn` will henceforth check for changes every 60 seconds. You should put the `inadyn` -command string in a startup script somewhere so it kicks into gear whenever your computer starts. - -### Remote hosting - -Your normal "web hotel" will probably not be enough to run Evennia. A web hotel is normally aimed at -a very specific usage - delivering web pages, at the most with some dynamic content. The "Python -scripts" they refer to on their home pages are usually only intended to be CGI-like scripts launched -by their webserver. Even if they allow you shell access (so you can install the Evennia dependencies -in the first place), resource usage will likely be very restricted. Running a full-fledged game -server like Evennia will probably be shunned upon or be outright impossible. If you are unsure, -contact your web hotel and ask about their policy on you running third-party servers that will want -to open custom ports. - -The options you probably need to look for are *shell account services*, *VPS:es* or *Cloud -services*. A "Shell account" service means that you get a shell account on a server and can log in -like any normal user. By contrast, a *VPS* (Virtual Private Server) service usually means that you -get `root` access, but in a virtual machine. There are also *Cloud*-type services which allows for -starting up multiple virtual machines and pay for what resources you use. - -**Advantages** -- Shell accounts/VPS/clouds offer more flexibility than your average web hotel - it's the ability to -log onto a shared computer away from home. -- Usually runs a Linux flavor, making it easy to install Evennia. -- Support. You don't need to maintain the server hardware. If your house burns down, at least your -game stays online. Many services guarantee a certain level of up-time and also do regular backups -for you. Make sure to check, some offer lower rates in exchange for you yourself being fully -responsible for your data/backups. -- Usually offers a fixed domain name, so no need to mess with IP addresses. -- May have the ability to easily deploy [docker](./Running-Evennia-in-Docker.md) versions of evennia -and/or your game. - -**Disadvantages** -- Might be pretty expensive (more so than a web hotel). Note that Evennia will normally need at -least 100MB RAM and likely much more for a large production game. -- Linux flavors might feel unfamiliar to users not used to ssh/PuTTy and the Linux command line. -- You are probably sharing the server with many others, so you are not completely in charge. CPU -usage might be limited. Also, if the server people decides to take the server down for maintenance, -you have no choice but to sit it out (but you'll hopefully be warned ahead of time). - -#### Installing Evennia on a remote server - -Firstly, if you are familiar with server infrastructure, consider using [Docker](Running-Evennia-in- -Docker) to deploy your game to the remote server; it will likely ease installation and deployment. -Docker images may be a little confusing if you are completely new to them though. - -If not using docker, and assuming you know how to connect to your account over ssh/PuTTy, you should -be able to follow the [Setup Quickstart](./Setup-Quickstart.md) instructions normally. You only need Python -and GIT pre-installed; these should both be available on any servers (if not you should be able to -easily ask for them to be installed). On a VPS or Cloud service you can install them yourself as -needed. - -If `virtualenv` is not available and you can't get it, you can download it (it's just a single file) -from [the virtualenv pypi](https://pypi.python.org/pypi/virtualenv). Using `virtualenv` you can -install everything without actually needing to have further `root` access. Ports might be an issue, -so make sure you know which ports are available to use and reconfigure Evennia accordingly. - -### Hosting options - -To find commercial solutions, browse the web for "shell access", "VPS" or "Cloud services" in your -region. You may find useful offers for "low cost" VPS hosting on [Low End Box][7]. The associated -[Low End Talk][8] forum can be useful for health checking the many small businesses that offer -"value" hosting, and occasionally for technical suggestions. - -There are all sorts of services available. Below are some international suggestions offered by -Evennia users: - -| Hosting name | Type | Lowest price | Comments | -|---|---| ---| --- | -| [silvren.com][1] | Shell account | Free for MU* | Private hobby provider so don't assume backups or expect immediate support. To ask for an account,connect with a MUD client to rostdev.mushpark.com, port 4201 and ask for "Jarin". | -| [Digital Ocean][2] | VPS | $5/month | You can get a $50 credit if you use the referral link https://m.do.co/c/8f64fec2670c - if you do, once you've had it long enough to have paid $25 we will get that as a referral bonus to help Evennia development.| -| [Amazon Web services][3] | Cloud | ~$5/month / on-demand | Free Tier first 12 months. Regions available around the globe.| -| [Amazon Lightsail][9] | Cloud | $5/month | Free first month. AWS's new "fixed cost" offering.| -| [Azure App Services][12] | Cloud | Free | Provides a free tier for hobbyist. Limited regions to be deployed to under the free tier| -| [Huawei Cloud][13] | Cloud | on demand | Similar to Amazon. Free Tier first 12 months. Limited regions to be deployed to| -| [Genesis MUD hosting][4] | Shell account | $8/month | Dedicated MUD host with very limited memory offerings. As for 2017, runs a 13 years old Python version (2.4) so you'd need to either convince them to update or compile yourself. Note that Evennia needs *at least* the "Deluxe" package (50MB RAM) and probably *a lot* higher for a production game. This host is *not* recommended for Evennia.| -| [Host1Plus][5] | VPS & Cloud | $4/month | $4-$8/month depending on length of sign-up period. -| [Scaleway][6] | Cloud | €3/month / on-demand | EU based (Paris, Amsterdam). Smallest option provides 2GB RAM. | -| [Prgmr][10] | VPS | $5/month | 1 month free with a year prepay. You likely want some experience with servers with this option as they don't have a lot of support.| -| [Linode][11] | Cloud | $5/month / on-demand | Multiple regions. Smallest option provides 1GB RAM| - -*Please help us expand this list.* - -[1]: https://silvren.com -[2]: https://www.digitalocean.com/pricing -[3]: https://aws.amazon.com/pricing/ -[4]: https://www.genesismuds.com/ -[5]: https://www.host1plus.com/ -[6]: https://www.scaleway.com/ -[7]: https://lowendbox.com/ -[8]: https://www.lowendtalk.com -[9]: https://amazonlightsail.com -[10]: https://prgmr.com/ -[11]: https://www.linode.com/ -[12]: https://azure.microsoft.com/en-us/pricing/details/app-service/windows/ -[13]: https://activity.huaweicloud.com/intl/en-us/free_packages/index.html - -## Cloud9 - -If you are interested in running Evennia in the online dev environment [Cloud9](https://c9.io/), you -can spin it up through their normal online setup using the Evennia Linux install instructions. The -one extra thing you will have to do is update `mygame/server/conf/settings.py` and add -`WEBSERVER_PORTS = [(8080, 4001)]`. This will then let you access the web server and do everything -else as normal. - -Note that, as of December 2017, Cloud9 was re-released by Amazon as a service within their AWS cloud -service offering. New customers entitled to the 1 year AWS "free tier" may find it provides -sufficient resources to operate a Cloud9 development environment without charge. -https://aws.amazon.com/cloud9/ diff --git a/docs/0.9.5/_sources/Parsing-command-arguments,-theory-and-best-practices.md.txt b/docs/0.9.5/_sources/Parsing-command-arguments,-theory-and-best-practices.md.txt deleted file mode 100644 index 025f43a95e..0000000000 --- a/docs/0.9.5/_sources/Parsing-command-arguments,-theory-and-best-practices.md.txt +++ /dev/null @@ -1,748 +0,0 @@ -# Parsing command arguments, theory and best practices - - -This tutorial will elaborate on the many ways one can parse command arguments. The first step after -[adding a command](./Adding-Command-Tutorial.md) usually is to parse its arguments. There are lots of -ways to do it, but some are indeed better than others and this tutorial will try to present them. - -If you're a Python beginner, this tutorial might help you a lot. If you're already familiar with -Python syntax, this tutorial might still contain useful information. There are still a lot of -things I find in the standard library that come as a surprise, though they were there all along. -This might be true for others. - -In this tutorial we will: - -- Parse arguments with numbers. -- Parse arguments with delimiters. -- Take a look at optional arguments. -- Parse argument containing object names. - -## What are command arguments? - -I'm going to talk about command arguments and parsing a lot in this tutorial. So let's be sure we -talk about the same thing before going any further: - -> A command is an Evennia object that handles specific user input. - -For instance, the default `look` is a command. After having created your Evennia game, and -connected to it, you should be able to type `look` to see what's around. In this context, `look` is -a command. - -> Command arguments are additional text passed after the command. - -Following the same example, you can type `look self` to look at yourself. In this context, `self` -is the text specified after `look`. `" self"` is the argument to the `look` command. - -Part of our task as a game developer is to connect user inputs (mostly commands) with actions in the -game. And most of the time, entering commands is not enough, we have to rely on arguments for -specifying actions with more accuracy. - -Take the `say` command. If you couldn't specify what to say as a command argument (`say hello!`), -you would have trouble communicating with others in the game. One would need to create a different -command for every kind of word or sentence, which is, of course, not practical. - -Last thing: what is parsing? - -> In our case, parsing is the process by which we convert command arguments into something we can -work with. - -We don't usually use the command argument as is (which is just text, of type `str` in Python). We -need to extract useful information. We might want to ask the user for a number, or the name of -another character present in the same room. We're going to see how to do all that now. - -## Working with strings - -In object terms, when you write a command in Evennia (when you write the Python class), the -arguments are stored in the `args` attribute. Which is to say, inside your `func` method, you can -access the command arguments in `self.args`. - -### self.args - -To begin with, look at this example: - -```python -class CmdTest(Command): - - """ - Test command. - - Syntax: - test [argument] - - Enter any argument after test. - - """ - - key = "test" - - def func(self): - self.msg(f"You have entered: {self.args}.") -``` - -If you add this command and test it, you will receive exactly what you have entered without any -parsing: - -``` -> test Whatever -You have entered: Whatever. -> test -You have entered: . -``` - -> The lines starting with `>` indicate what you enter into your client. The other lines are what -you receive from the game server. - -Notice two things here: - -1. The left space between our command key ("test", here) and our command argument is not removed. -That's why there are two spaces in our output at line 2. Try entering something like "testok". -2. Even if you don't enter command arguments, the command will still be called with an empty string -in `self.args`. - -Perhaps a slight modification to our code would be appropriate to see what's happening. We will -force Python to display the command arguments as a debug string using a little shortcut. - -```python -class CmdTest(Command): - - """ - Test command. - - Syntax: - test [argument] - - Enter any argument after test. - - """ - - key = "test" - - def func(self): - self.msg(f"You have entered: {self.args!r}.") -``` - -The only line we have changed is the last one, and we have added `!r` between our braces to tell -Python to print the debug version of the argument (the repr-ed version). Let's see the result: - -``` -> test Whatever -You have entered: ' Whatever'. -> test -You have entered: ''. -> test And something with '? -You have entered: " And something with '?". -``` - -This displays the string in a way you could see in the Python interpreter. It might be easier to -read... to debug, anyway. - -I insist so much on that point because it's crucial: the command argument is just a string (of type -`str`) and we will use this to parse it. What you will see is mostly not Evennia-specific, it's -Python-specific and could be used in any other project where you have the same need. - -### Stripping - -As you've seen, our command arguments are stored with the space. And the space between the command -and the arguments is often of no importance. - -> Why is it ever there? - -Evennia will try its best to find a matching command. If the user enters your command key with -arguments (but omits the space), Evennia will still be able to find and call the command. You might -have seen what happened if the user entered `testok`. In this case, `testok` could very well be a -command (Evennia checks for that) but seeing none, and because there's a `test` command, Evennia -calls it with the arguments `"ok"`. - -But most of the time, we don't really care about this left space, so you will often see code to -remove it. There are different ways to do it in Python, but a command use case is the `strip` -method on `str` and its cousins, `lstrip` and `rstrip`. - -- `strip`: removes one or more characters (either spaces or other characters) from both ends of the -string. -- `lstrip`: same thing but only removes from the left end (left strip) of the string. -- `rstrip`: same thing but only removes from the right end (right strip) of the string. - -Some Python examples might help: - -```python ->>> ' this is '.strip() # remove spaces by default -'this is' ->>> " What if I'm right? ".lstrip() # strip spaces from the left -"What if I'm right? " ->>> 'Looks good to me...'.strip('.') # removes '.' -'Looks good to me' ->>> '"Now, what is it?"'.strip('"?') # removes '"' and '?' from both ends -'Now, what is it' -``` - -Usually, since we don't need the space separator, but still want our command to work if there's no -separator, we call `lstrip` on the command arguments: - -```python -class CmdTest(Command): - - """ - Test command. - - Syntax: - test [argument] - - Enter any argument after test. - - """ - - key = "test" - - def parse(self): - """Parse arguments, just strip them.""" - self.args = self.args.lstrip() - - def func(self): - self.msg(f"You have entered: {self.args!r}.") -``` - -> We are now beginning to override the command's `parse` method, which is typically useful just for -argument parsing. This method is executed before `func` and so `self.args` in `func()` will contain -our `self.args.lstrip()`. - -Let's try it: - -``` -> test Whatever -You have entered: 'Whatever'. -> test -You have entered: ''. -> test And something with '? -You have entered: "And something with '?". -> test And something with lots of spaces -You have entered: 'And something with lots of spaces'. -``` - -Spaces at the end of the string are kept, but all spaces at the beginning are removed: - -> `strip`, `lstrip` and `rstrip` without arguments will strip spaces, line breaks and other common -separators. You can specify one or more characters as a parameter. If you specify more than one -character, all of them will be stripped from your original string. - -### Convert arguments to numbers - -As pointed out, `self.args` is a string (of type `str`). What if we want the user to enter a -number? - -Let's take a very simple example: creating a command, `roll`, that allows to roll a six-sided die. -The player has to guess the number, specifying the number as argument. To win, the player has to -match the number with the die. Let's see an example: - -``` -> roll 3 -You roll a die. It lands on the number 4. -You played 3, you have lost. -> dice 1 -You roll a die. It lands on the number 2. -You played 1, you have lost. -> dice 1 -You roll a die. It lands on the number 1. -You played 1, you have won! -``` - -If that's your first command, it's a good opportunity to try to write it. A command with a simple -and finite role always is a good starting choice. Here's how we could (first) write it... but it -won't work as is, I warn you: - -```python -from random import randint - -from evennia import Command - -class CmdRoll(Command): - - """ - Play random, enter a number and try your luck. - - Usage: - roll - - Enter a valid number as argument. A random die will be rolled and you - will win if you have specified the correct number. - - Example: - roll 3 - - """ - - key = "roll" - - def parse(self): - """Convert the argument to a number.""" - self.args = self.args.lstrip() - - def func(self): - # Roll a random die - figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both - self.msg(f"You roll a die. It lands on the number {figure}.") - - if self.args == figure: # THAT WILL BREAK! - self.msg(f"You played {self.args}, you have won!") - else: - self.msg(f"You played {self.args}, you have lost.") -``` - -If you try this code, Python will complain that you try to compare a number with a string: `figure` -is a number and `self.args` is a string and can't be compared as-is in Python. Python doesn't do -"implicit converting" as some languages do. By the way, this might be annoying sometimes, and other -times you will be glad it tries to encourage you to be explicit rather than implicit about what to -do. This is an ongoing debate between programmers. Let's move on! - -So we need to convert the command argument from a `str` into an `int`. There are a few ways to do -it. But the proper way is to try to convert and deal with the `ValueError` Python exception. - -Converting a `str` into an `int` in Python is extremely simple: just use the `int` function, give it -the string and it returns an integer, if it could. If it can't, it will raise `ValueError`. So -we'll need to catch that. However, we also have to indicate to Evennia that, should the number be -invalid, no further parsing should be done. Here's a new attempt at our command with this -converting: - -```python -from random import randint - -from evennia import Command, InterruptCommand - -class CmdRoll(Command): - - """ - Play random, enter a number and try your luck. - - Usage: - roll - - Enter a valid number as argument. A random die will be rolled and you - will win if you have specified the correct number. - - Example: - roll 3 - - """ - - key = "roll" - - def parse(self): - """Convert the argument to number if possible.""" - args = self.args.lstrip() - - # Convert to int if possible - # If not, raise InterruptCommand. Evennia will catch this - # exception and not call the 'func' method. - try: - self.entered = int(args) - except ValueError: - self.msg(f"{args} is not a valid number.") - raise InterruptCommand - - def func(self): - # Roll a random die - figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both - self.msg(f"You roll a die. It lands on the number {figure}.") - - if self.entered == figure: - self.msg(f"You played {self.entered}, you have won!") - else: - self.msg(f"You played {self.entered}, you have lost.") -``` - -Before enjoying the result, let's examine the `parse` method a little more: what it does is try to -convert the entered argument from a `str` to an `int`. This might fail (if a user enters `roll -something`). In such a case, Python raises a `ValueError` exception. We catch it in our -`try/except` block, send a message to the user and raise the `InterruptCommand` exception in -response to tell Evennia to not run `func()`, since we have no valid number to give it. - -In the `func` method, instead of using `self.args`, we use `self.entered` which we have defined in -our `parse` method. You can expect that, if `func()` is run, then `self.entered` contains a valid -number. - -If you try this command, it will work as expected this time: the number is converted as it should -and compared to the die roll. You might spend some minutes playing this game. Time out! - -Something else we could want to address: in our small example, we only want the user to enter a -positive number between 1 and 6. And the user can enter `roll 0` or `roll -8` or `roll 208` for -that matter, the game still works. It might be worth addressing. Again, you could write a -condition to do that, but since we're catching an exception, we might end up with something cleaner -by grouping: - -```python -from random import randint - -from evennia import Command, InterruptCommand - -class CmdRoll(Command): - - """ - Play random, enter a number and try your luck. - - Usage: - roll - - Enter a valid number as argument. A random die will be rolled and you - will win if you have specified the correct number. - - Example: - roll 3 - - """ - - key = "roll" - - def parse(self): - """Convert the argument to number if possible.""" - args = self.args.lstrip() - - # Convert to int if possible - try: - self.entered = int(args) - if not 1 <= self.entered <= 6: - # self.entered is not between 1 and 6 (including both) - raise ValueError - except ValueError: - self.msg(f"{args} is not a valid number.") - raise InterruptCommand - - def func(self): - # Roll a random die - figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both - self.msg(f"You roll a die. It lands on the number {figure}.") - - if self.entered == figure: - self.msg(f"You played {self.entered}, you have won!") - else: - self.msg(f"You played {self.entered}, you have lost.") -``` - -Using grouped exceptions like that makes our code easier to read, but if you feel more comfortable -checking, afterward, that the number the user entered is in the right range, you can do so in a -latter condition. - -> Notice that we have updated our `parse` method only in this last attempt, not our `func()` method -which remains the same. This is one goal of separating argument parsing from command processing, -these two actions are best kept isolated. - -### Working with several arguments - -Often a command expects several arguments. So far, in our example with the "roll" command, we only -expect one argument: a number and just a number. What if we want the user to specify several -numbers? First the number of dice to roll, then the guess? - -> You won't win often if you roll 5 dice but that's for the example. - -So we would like to interpret a command like this: - - > roll 3 12 - -(To be understood: roll 3 dice, my guess is the total number will be 12.) - -What we need is to cut our command argument, which is a `str`, break it at the space (we use the -space as a delimiter). Python provides the `str.split` method which we'll use. Again, here are -some examples from the Python interpreter: - - >>> args = "3 12" - >>> args.split(" ") - ['3', '12'] - >>> args = "a command with several arguments" - >>> args.split(" ") - ['a', 'command', 'with', 'several', 'arguments'] - >>> - -As you can see, `str.split` will "convert" our strings into a list of strings. The specified -argument (`" "` in our case) is used as delimiter. So Python browses our original string. When it -sees a delimiter, it takes whatever is before this delimiter and append it to a list. - -The point here is that `str.split` will be used to split our argument. But, as you can see from the -above output, we can never be sure of the length of the list at this point: - - >>> args = "something" - >>> args.split(" ") - ['something'] - >>> args = "" - >>> args.split(" ") - [''] - >>> - -Again we could use a condition to check the number of split arguments, but Python offers a better -approach, making use of its exception mechanism. We'll give a second argument to `str.split`, the -maximum number of splits to do. Let's see an example, this feature might be confusing at first -glance: - - >>> args = "that is something great" - >>> args.split(" ", 1) # one split, that is a list with two elements (before, after) - ['that', 'is something great'] - >>> - -Read this example as many times as needed to understand it. The second argument we give to -`str.split` is not the length of the list that should be returned, but the number of times we have -to split. Therefore, we specify 1 here, but we get a list of two elements (before the separator, -after the separator). - -> What will happen if Python can't split the number of times we ask? - -It won't: - - >>> args = "whatever" - >>> args.split(" ", 1) # there isn't even a space here... - ['whatever'] - >>> - -This is one moment I would have hoped for an exception and didn't get one. But there's another way -which will raise an exception if there is an error: variable unpacking. - -We won't talk about this feature in details here. It would be complicated. But the code is really -straightforward to use. Let's take our example of the roll command but let's add a first argument: -the number of dice to roll. - -```python -from random import randint - -from evennia import Command, InterruptCommand - -class CmdRoll(Command): - - """ - Play random, enter a number and try your luck. - - Specify two numbers separated by a space. The first number is the - number of dice to roll (1, 2, 3) and the second is the expected sum - of the roll. - - Usage: - roll - - For instance, to roll two 6-figure dice, enter 2 as first argument. - If you think the sum of these two dice roll will be 10, you could enter: - - roll 2 10 - - """ - - key = "roll" - - def parse(self): - """Split the arguments and convert them.""" - args = self.args.lstrip() - - # Split: we expect two arguments separated by a space - try: - number, guess = args.split(" ", 1) - except ValueError: - self.msg("Invalid usage. Enter two numbers separated by a space.") - raise InterruptCommand - - # Convert the entered number (first argument) - try: - self.number = int(number) - if self.number <= 0: - raise ValueError - except ValueError: - self.msg(f"{number} is not a valid number of dice.") - raise InterruptCommand - - # Convert the entered guess (second argument) - try: - self.guess = int(guess) - if not 1 <= self.guess <= self.number * 6: - raise ValueError - except ValueError: - self.msg(f"{self.guess} is not a valid guess.") - raise InterruptCommand - - def func(self): - # Roll a random die X times (X being self.number) - figure = 0 - for _ in range(self.number): - figure += randint(1, 6) - - self.msg(f"You roll {self.number} dice and obtain the sum {figure}.") - - if self.guess == figure: - self.msg(f"You played {self.guess}, you have won!") - else: - self.msg(f"You played {self.guess}, you have lost.") -``` - -The beginning of the `parse()` method is what interests us most: - -```python -try: - number, guess = args.split(" ", 1) -except ValueError: - self.msg("Invalid usage. Enter two numbers separated by a space.") - raise InterruptCommand -``` - -We split the argument using `str.split` but we capture the result in two variables. Python is smart -enough to know that we want what's left of the space in the first variable, what's right of the -space in the second variable. If there is not even a space in the string, Python will raise a -`ValueError` exception. - -This code is much easier to read than browsing through the returned strings of `str.split`. We can -convert both variables the way we did previously. Actually there are not so many changes in this -version and the previous one, most of it is due to name changes for clarity. - -> Splitting a string with a maximum of splits is a common occurrence while parsing command -arguments. You can also see the `str.rspli8t` method that does the same thing but from the right of -the string. Therefore, it will attempt to find delimiters at the end of the string and work toward -the beginning of it. - -We have used a space as a delimiter. This is absolutely not necessary. You might remember that -most default Evennia commands can take an `=` sign as a delimiter. Now you know how to parse them -as well: - - >>> cmd_key = "tel" - >>> cmd_args = "book = chest" - >>> left, right = cmd_args.split("=") # mighht raise ValueError! - >>> left - 'book ' - >>> right - ' chest' - >>> - -### Optional arguments - -Sometimes, you'll come across commands that have optional arguments. These arguments are not -necessary but they can be set if more information is needed. I will not provide the entire command -code here but just enough code to show the mechanism in Python: - -Again, we'll use `str.split`, knowing that we might not have any delimiter at all. For instance, -the player could enter the "tel" command like this: - - > tel book - > tell book = chest - -The equal sign is optional along with whatever is specified after it. A possible solution in our -`parse` method would be: - -```python - def parse(self): - args = self.args.lstrip() - - # = is optional - try: - obj, destination = args.split("=", 1) - except ValueError: - obj = args - destination = None -``` - -This code would place everything the user entered in `obj` if she didn't specify any equal sign. -Otherwise, what's before the equal sign will go in `obj`, what's after the equal sign will go in -`destination`. This makes for quick testing after that, more robust code with less conditions that -might too easily break your code if you're not careful. - -> Again, here we specified a maximum numbers of splits. If the users enters: - - > tel book = chest = chair - -Then `destination` will contain: `" chest = chair"`. This is often desired, but it's up to you to -set parsing however you like. - -## Evennia searches - -After this quick tour of some `str` methods, we'll take a look at some Evennia-specific features -that you won't find in standard Python. - -One very common task is to convert a `str` into an Evennia object. Take the previous example: -having `"book"` in a variable is great, but we would prefer to know what the user is talking -about... what is this `"book"`? - -To get an object from a string, we perform an Evennia search. Evennia provides a `search` method on -all typeclassed objects (you will most likely use the one on characters or accounts). This method -supports a very wide array of arguments and has [its own tutorial](./Tutorial-Searching-For-Objects.md). -Some examples of useful cases follow: - -### Local searches - -When an account or a character enters a command, the account or character is found in the `caller` -attribute. Therefore, `self.caller` will contain an account or a character (or a session if that's -a session command, though that's not as frequent). The `search` method will be available on this -caller. - -Let's take the same example of our little "tel" command. The user can specify an object as -argument: - -```python - def parse(self): - name = self.args.lstrip() -``` - -We then need to "convert" this string into an Evennia object. The Evennia object will be searched -in the caller's location and its contents by default (that is to say, if the command has been -entered by a character, it will search the object in the character's room and the character's -inventory). - -```python - def parse(self): - name = self.args.lstrip() - - self.obj = self.caller.search(name) -``` - -We specify only one argument to the `search` method here: the string to search. If Evennia finds a -match, it will return it and we keep it in the `obj` attribute. If it can't find anything, it will -return `None` so we need to check for that: - -```python - def parse(self): - name = self.args.lstrip() - - self.obj = self.caller.search(name) - if self.obj is None: - # A proper error message has already been sent to the caller - raise InterruptCommand -``` - -That's it. After this condition, you know that whatever is in `self.obj` is a valid Evennia object -(another character, an object, an exit...). - -### Quiet searches - -By default, Evennia will handle the case when more than one match is found in the search. The user -will be asked to narrow down and re-enter the command. You can, however, ask to be returned the -list of matches and handle this list yourself: - -```python - def parse(self): - name = self.args.lstrip() - - objs = self.caller.search(name, quiet=True) - if not objs: - # This is an empty list, so no match - self.msg(f"No {name!r} was found.") - raise InterruptCommand - - self.obj = objs[0] # Take the first match even if there are several -``` - -All we have changed to obtain a list is a keyword argument in the `search` method: `quiet`. If set -to `True`, then errors are ignored and a list is always returned, so we need to handle it as such. -Notice in this example, `self.obj` will contain a valid object too, but if several matches are -found, `self.obj` will contain the first one, even if more matches are available. - -### Global searches - -By default, Evennia will perform a local search, that is, a search limited by the location in which -the caller is. If you want to perform a global search (search in the entire database), just set the -`global_search` keyword argument to `True`: - -```python - def parse(self): - name = self.args.lstrip() - self.obj = self.caller.search(name, global_search=True) -``` - -## Conclusion - -Parsing command arguments is vital for most game designers. If you design "intelligent" commands, -users should be able to guess how to use them without reading the help, or with a very quick peek at -said help. Good commands are intuitive to users. Better commands do what they're told to do. For -game designers working on MUDs, commands are the main entry point for users into your game. This is -no trivial. If commands execute correctly (if their argument is parsed, if they don't behave in -unexpected ways and report back the right errors), you will have happier players that might stay -longer on your game. I hope this tutorial gave you some pointers on ways to improve your command -parsing. There are, of course, other ways you will discover, or ways you are already using in your -code. diff --git a/docs/0.9.5/_sources/Portal-And-Server.md.txt b/docs/0.9.5/_sources/Portal-And-Server.md.txt deleted file mode 100644 index d51f152a9c..0000000000 --- a/docs/0.9.5/_sources/Portal-And-Server.md.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Portal And Server - - -Evennia consists of two processes, known as *Portal* and *Server*. They can be controlled from -inside the game or from the command line as described [here](./Start-Stop-Reload.md). - -If you are new to the concept, the main purpose of separating the two is to have accounts connect to -the Portal but keep the MUD running on the Server. This way one can restart/reload the game (the -Server part) without Accounts getting disconnected. - -![portal and server layout](https://474a3b9f-a-62cb3a1a-s-sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia_server_portal.png) - -The Server and Portal are glued together via an AMP (Asynchronous Messaging Protocol) connection. -This allows the two programs to communicate seamlessly. diff --git a/docs/0.9.5/_sources/Profiling.md.txt b/docs/0.9.5/_sources/Profiling.md.txt deleted file mode 100644 index d9442e4d34..0000000000 --- a/docs/0.9.5/_sources/Profiling.md.txt +++ /dev/null @@ -1,125 +0,0 @@ -# Profiling - -*This is considered an advanced topic mainly of interest to server developers.* - -## Introduction - -Sometimes it can be useful to try to determine just how efficient a particular piece of code is, or -to figure out if one could speed up things more than they are. There are many ways to test the -performance of Python and the running server. - -Before digging into this section, remember Donald Knuth's [words of -wisdom](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize): - -> *[...]about 97% of the time: Premature optimization is the root of all evil*. - -That is, don't start to try to optimize your code until you have actually identified a need to do -so. This means your code must actually be working before you start to consider optimization. -Optimization will also often make your code more complex and harder to read. Consider readability -and maintainability and you may find that a small gain in speed is just not worth it. - -## Simple timer tests - -Python's `timeit` module is very good for testing small things. For example, in order to test if it -is faster to use a `for` loop or a list comprehension you could use the following code: - -```python - import timeit - # Time to do 1000000 for loops - timeit.timeit("for i in range(100):\n a.append(i)", setup="a = []") - <<< 10.70982813835144 - # Time to do 1000000 list comprehensions - timeit.timeit("a = [i for i in range(100)]") - <<< 5.358283996582031 -``` - -The `setup` keyword is used to set up things that should not be included in the time measurement, -like `a = []` in the first call. - -By default the `timeit` function will re-run the given test 1000000 times and returns the *total -time* to do so (so *not* the average per test). A hint is to not use this default for testing -something that includes database writes - for that you may want to use a lower number of repeats -(say 100 or 1000) using the `number=100` keyword. - -## Using cProfile - -Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done -with `pypy` at this point). Due to the way Evennia's processes are handled, there is no point in -using the normal way to start the profiler (`python -m cProfile evennia.py`). Instead you start the -profiler through the launcher: - - evennia --profiler start - -This will start Evennia with the Server component running (in daemon mode) under cProfile. You could -instead try `--profile` with the `portal` argument to profile the Portal (you would then need to -[start the Server separately](./Start-Stop-Reload.md)). - -Please note that while the profiler is running, your process will use a lot more memory than usual. -Memory usage is even likely to climb over time. So don't leave it running perpetually but monitor it -carefully (for example using the `top` command on Linux or the Task Manager's memory display on -Windows). - -Once you have run the server for a while, you need to stop it so the profiler can give its report. -Do *not* kill the program from your task manager or by sending it a kill signal - this will most -likely also mess with the profiler. Instead either use `evennia.py stop` or (which may be even -better), use `@shutdown` from inside the game. - -Once the server has fully shut down (this may be a lot slower than usual) you will find that -profiler has created a new file `mygame/server/logs/server.prof`. - -## Analyzing the profile - -The `server.prof` file is a binary file. There are many ways to analyze and display its contents, -all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works). - -We recommend the -[Runsnake](http://www.vrplumber.com/programming/runsnakerun/) visualizer to see the processor usage -of different processes in a graphical form. For more detailed listing of usage time, you can use -[KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html). To make KCachegrind work with -Python profiles you also need the wrapper script -[pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/). You can get pyprof2calltree via -`pip` whereas KCacheGrind is something you need to get via your package manager or their homepage. - -How to analyze and interpret profiling data is not a trivial issue and depends on what you are -profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing -list if you need help and be ready to be able to supply your `server.prof` file for comparison, -along with the exact conditions under which it was obtained. - -## The Dummyrunner - -It is difficult to test "actual" game performance without having players in your game. For this -reason Evennia comes with the *Dummyrunner* system. The Dummyrunner is a stress-testing system: a -separate program that logs into your game with simulated players (aka "bots" or "dummies"). Once -connected these dummies will semi-randomly perform various tasks from a list of possible actions. -Use `Ctrl-C` to stop the Dummyrunner. - -> Warning: You should not run the Dummyrunner on a production database. It will spawn many objects -and also needs to run with general permissions. - -To launch the Dummyrunner, first start your server normally (with or without profiling, as above). -Then start a new terminal/console window and active your virtualenv there too. In the new terminal, -try to connect 10 dummy players: - - evennia --dummyrunner 10 - -The first time you do this you will most likely get a warning from Dummyrunner. It will tell you to -copy an import string to the end of your settings file. Quit the Dummyrunner (`Ctrl-C`) and follow -the instructions. Restart Evennia and try `evennia --dummyrunner 10` again. Make sure to remove that -extra settings line when running a public server. - -The actions perform by the dummies is controlled by a settings file. The default Dummyrunner -settings file is `evennia/server/server/profiling/dummyrunner_settings.py` but you shouldn't modify -this directly. Rather create/copy the default file to `mygame/server/conf/` and modify it there. To -make sure to use your file over the default, add the following line to your settings file: - -```python -DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py" -``` - -> Hint: Don't start with too many dummies. The Dummyrunner defaults to taxing the server much more -intensely than an equal number of human players. A good dummy number to start with is 10-100. - -Once you have the dummyrunner running, stop it with `Ctrl-C`. - -Generally, the dummyrunner system makes for a decent test of general performance; but it is of -course hard to actually mimic human user behavior. For this, actual real-game testing is required. diff --git a/docs/0.9.5/_sources/Python-3.md.txt b/docs/0.9.5/_sources/Python-3.md.txt deleted file mode 100644 index 96b3479a83..0000000000 --- a/docs/0.9.5/_sources/Python-3.md.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Python 3 - -Evennia supports Python 3+ since v0.8. This page is deprecated. - diff --git a/docs/0.9.5/_sources/Python-basic-introduction.md.txt b/docs/0.9.5/_sources/Python-basic-introduction.md.txt deleted file mode 100644 index 66512e06fd..0000000000 --- a/docs/0.9.5/_sources/Python-basic-introduction.md.txt +++ /dev/null @@ -1,266 +0,0 @@ -# Python basic introduction - -This is the first part of our beginner's guide to the basics of using Python with Evennia. It's -aimed at you with limited or no programming/Python experience. But also if you are an experienced -programmer new to Evennia or Python you might still pick up a thing or two. It is by necessity brief -and low on detail. There are countless Python guides and tutorials, books and videos out there for -learning more in-depth - use them! - -**Contents:** -- [Evennia Hello world](./Python-basic-introduction.md#evennia-hello-world) -- [Importing modules](./Python-basic-introduction.md#importing-modules) -- [Parsing Python errors](./Python-basic-introduction.md#parsing-python-errors) -- [Our first function](./Python-basic-introduction.md#our-first-function) -- [Looking at the log](./Python-basic-introduction.md#looking-at-the-log) -- (continued in [part 2](./Python-basic-tutorial-part-two.md)) - -This quickstart assumes you have [gotten Evennia started](./Getting-Started.md). You should make sure -that you are able to see the output from the server in the console from which you started it. Log -into the game either with a mud client on `localhost:4000` or by pointing a web browser to -`localhost:4001/webclient`. Log in as your superuser (the user you created during install). - -Below, lines starting with a single `>` means command input. - -### Evennia Hello world - -The `py` (or `!` which is an alias) command allows you as a superuser to run raw Python from in- -game. From the game's input line, enter the following: - - > py print("Hello World!") - -You will see - - > print("Hello world!") - Hello World - -To understand what is going on: some extra info: The `print(...)` *function* is the basic, in-built -way to output text in Python. The quotes `"..."` means you are inputing a *string* (i.e. text). You -could also have used single-quotes `'...'`, Python accepts both. - -The first return line (with `>>>`) is just `py` echoing what you input (we won't include that in the -examples henceforth). - -> Note: You may sometimes see people/docs refer to `@py` or other commands starting with `@`. -Evennia ignores `@` by default, so `@py` is the exact same thing as `py`. - -The `print` command is a standard Python structure. We can use that here in the `py` command, and -it's great for debugging and quick testing. But if you need to send a text to an actual player, -`print` won't do, because it doesn't know _who_ to send to. Try this: - - > py me.msg("Hello world!") - Hello world! - -This looks the same as the `print` result, but we are now actually messaging a specific *object*, -`me`. The `me` is something uniquely available in the `py` command (we could also use `self`, it's -an alias). It represents "us", the ones calling the `py` command. The `me` is an example of an -*Object instance*. Objects are fundamental in Python and Evennia. The `me` object not only -represents the character we play in the game, it also contains a lot of useful resources for doing -things with that Object. One such resource is `msg`. `msg` works like `print` except it sends the -text to the object it is attached to. So if we, for example, had an object `you`, doing -`you.msg(...)` would send a message to the object `you`. - -You access an Object's resources by using the full-stop character `.`. So `self.msg` accesses the -`msg` resource and then we call it like we did print, with our "Hello World!" greeting in -parentheses. - -> Important: something like `print(...)` we refer to as a *function*, while `msg(...)` which sits on -an object is called a *method*. - -For now, `print` and `me.msg` behaves the same, just remember that you're going to mostly be using -the latter in the future. Try printing other things. Also try to include `|r` at the start of your -string to make the output red in-game. Use `color` to learn more color tags. - -### Importing modules - -Keep your game running, then open a text editor of your choice. If your game folder is called -`mygame`, create a new text file `test.py` in the subfolder `mygame/world`. This is how the file -structure should look: - -``` -mygame/ - world/ - test.py -``` - -For now, only add one line to `test.py`: - -```python -print("Hello World!") -``` - -Don't forget to save the file. A file with the ending `.py` is referred to as a Python *module*. To -use this in-game we have to *import* it. Try this: - -```python -> @py import world.test -Hello World -``` -If you make some error (we'll cover how to handle errors below) you may need to run the `@reload` -command for your changes to take effect. - -So importing `world.test` actually means importing `world/test.py`. Think of the period `.` as -replacing `/` (or `\` for Windows) in your path. The `.py` ending of `test.py` is also never -included in this "Python-path", but _only_ files with that ending can be imported this way. Where is -`mygame` in that Python-path? The answer is that Evennia has already told Python that your `mygame` -folder is a good place to look for imports. So we don't include `mygame` in the path - Evennia -handles this for us. - -When you import the module, the top "level" of it will execute. In this case, it will immediately -print "Hello World". - -> If you look in the folder you'll also often find new files ending with `.pyc`. These are compiled -Python binaries that Python auto-creates when running code. Just ignore them, you should never edit -those anyway. - -Now try to run this a second time: - -```python -> py import world.test -``` -You will *not* see any output this second time or any subsequent times! This is not a bug. Rather -it is because Python is being clever - it stores all imported modules and to be efficient it will -avoid importing them more than once. So your `print` will only run the first time, when the module -is first imported. To see it again you need to `@reload` first, so Python forgets about the module -and has to import it again. - -We'll get back to importing code in the second part of this tutorial. For now, let's press on. - -### Parsing Python errors - -Next, erase the single `print` statement you had in `test.py` and replace it with this instead: - -```python -me.msg("Hello World!") -``` - -As you recall we used this from `py` earlier - it echoed "Hello World!" in-game. -Save your file and `reload` your server - this makes sure Evennia sees the new version of your code. -Try to import it from `py` in the same way as earlier: - -```python -> py import world.test -``` - -No go - this time you get an error! - -```python -File "./world/test.py", line 1, in - me.msg("Hello world!") -NameError: name 'me' is not defined -``` - -This is called a *traceback*. Python's errors are very friendly and will most of the time tell you -exactly what and where things are wrong. It's important that you learn to parse tracebacks so you -can fix your code. Let's look at this one. A traceback is to be read from the _bottom up_. The last -line is the error Python balked at, while the two lines above it details exactly where that error -was encountered. - -1. An error of type `NameError` is the problem ... -2. ... more specifically it is due to the variable `me` not being defined. -3. This happened on the line `me.msg("Hello world!")` ... -4. ... which is on line `1` of the file `./world/test.py`. - -In our case the traceback is short. There may be many more lines above it, tracking just how -different modules called each other until it got to the faulty line. That can sometimes be useful -information, but reading from the bottom is always a good start. - -The `NameError` we see here is due to a module being its own isolated thing. It knows nothing about -the environment into which it is imported. It knew what `print` is because that is a special -[reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html). But `me` is *not* such a -reserved word. As far as the module is concerned `me` is just there out of nowhere. Hence the -`NameError`. - -### Our first function - -Let's see if we can resolve that `NameError` from the previous section. We know that `me` is defined -at the time we use the `@py` command because if we do `py me.msg("Hello World!")` directly in-game -it works fine. What if we could *send* that `me` to the `test.py` module so it knows what it is? One -way to do this is with a *function*. - -Change your `mygame/world/test.py` file to look like this: - -```python -def hello_world(who): - who.msg("Hello World!") -``` - -Now that we are moving onto multi-line Python code, there are some important things to remember: - -- Capitalization matters in Python. It must be `def` and not `DEF`, `who` is not the same as `Who` -etc. -- Indentation matters in Python. The second line must be indented or it's not valid code. You should -also use a consistent indentation length. We *strongly* recommend that you set up your editor to -always indent *4 spaces* (**not** a single tab-character) when you press the TAB key - it will make -your life a lot easier. -- `def` is short for "define" and defines a *function* (or a *method*, if sitting on an object). -This is a [reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html); try not to use -these words anywhere else. -- A function name can not have spaces but otherwise we could have called it almost anything. We call -it `hello_world`. Evennia follows [Python's standard naming style](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#a-quick-list-of-code-style- -points) with lowercase letters and underscores. Use this style for now. -- `who` is what we call the *argument* to our function. Arguments are variables we pass to the -function. We could have named it anything and we could also have multiple arguments separated by -commas. What `who` is depends on what we pass to this function when we *call* it later (hint: we'll -pass `me` to it). -- The colon (`:`) at the end of the first line indicates that the header of the function is -complete. -- The indentation marks the beginning of the actual operating code of the function (the function's -*body*). If we wanted more lines to belong to this function those lines would all have to have to -start at this indentation level. -- In the function body we take the `who` argument and treat it as we would have treated `me` earlier -- we expect it to have a `.msg` method we can use to send "Hello World" to. - -First, `reload` your game to make it aware of the updated Python module. Now we have defined our -first function, let's use it. - - > reload - > py import world.test - -Nothing happened! That is because the function in our module won't do anything just by importing it. -It will only act when we *call* it. We will need to enter the module we just imported and do so. - - > py import world.test ; world.test.hello_world(me) - Hello world! - -There is our "Hello World"! The `;` is the way to put multiple Python-statements on one line. - -> Some MUD clients use `;` for their own purposes to separate client-inputs. If so you'll get a -`NameError` stating that `world` is not defined. Check so you understand why this is! Change the use -of `;` in your client or use the Evennia web client if this is a problem. - -In the second statement we access the module path we imported (`world.test`) and reach for the -`hello_world` function within. We *call* the function with `me`, which becomes the `who` variable we -use inside the `hello_function`. - -> As an exercise, try to pass something else into `hello_world`. Try for example to pass _who_ as -the number `5` or the simple string `"foo"`. You'll get errors that they don't have the attribute -`msg`. As we've seen, `me` *does* make `msg` available which is why it works (you'll learn more -about Objects like `me` in the next part of this tutorial). If you are familiar with other -programming languages you may be tempted to start *validating* `who` to make sure it works as -expected. This is usually not recommended in Python which suggests it's better to -[handle](https://docs.python.org/2/tutorial/errors.html) the error if it happens rather than to make -a lot of code to prevent it from happening. See also [duck -typing](https://en.wikipedia.org/wiki/Duck_typing). - -# Looking at the log - -As you start to explore Evennia, it's important that you know where to look when things go wrong. -While using the friendly `py` command you'll see errors directly in-game. But if something goes -wrong in your code while the game runs, you must know where to find the _log_. - -Open a terminal (or go back to the terminal you started Evennia in), make sure your `virtualenv` is -active and that you are standing in your game directory (the one created with `evennia --init` -during installation). Enter - -``` -evennia --log -``` -(or `evennia -l`) - -This will show the log. New entries will show up in real time. Whenever you want to leave the log, -enter `Ctrl-C` or `Cmd-C` depending on your system. As a game dev it is important to look at the -log output when working in Evennia - many errors will only appear with full details here. You may -sometimes have to scroll up in the history if you miss it. - -This tutorial is continued in [Part 2](./Python-basic-tutorial-part-two.md), where we'll start learning -about objects and to explore the Evennia library. diff --git a/docs/0.9.5/_sources/Python-basic-tutorial-part-two.md.txt b/docs/0.9.5/_sources/Python-basic-tutorial-part-two.md.txt deleted file mode 100644 index 10dd60b38f..0000000000 --- a/docs/0.9.5/_sources/Python-basic-tutorial-part-two.md.txt +++ /dev/null @@ -1,492 +0,0 @@ -# Python basic tutorial part two - -[In the first part](./Python-basic-introduction.md) of this Python-for-Evennia basic tutorial we learned -how to run some simple Python code from inside the game. We also made our first new *module* -containing a *function* that we called. Now we're going to start exploring the very important -subject of *objects*. - -**Contents:** -- [On the subject of objects](./Python-basic-tutorial-part-two.md#on-the-subject-of-objects) -- [Exploring the Evennia library](./Python-basic-tutorial-part-two.md#exploring-the-evennia-library) -- [Tweaking our Character class](./Python-basic-tutorial-part-two.md#tweaking-our-character-class) -- [The Evennia shell](./Python-basic-tutorial-part-two.md#the-evennia-shell) -- [Where to go from here](./Python-basic-tutorial-part-two.md#where-to-go-from-here) - -### On the subject of objects - -In the first part of the tutorial we did things like - - > py me.msg("Hello World!") - -To learn about functions and imports we also passed that `me` on to a function `hello_world` in -another module. - -Let's learn some more about this `me` thing we are passing around all over the place. In the -following we assume that we named our superuser Character "Christine". - - > py me - Christine - > py me.key - Christine - -These returns look the same at first glance, but not if we examine them more closely: - - > py type(me) - - > py type(me.key) - - -> Note: In some MU clients, such as Mudlet and MUSHclient simply returning `type(me)`, you may not -see the proper return from the above commands. This is likely due to the HTML-like tags `<...>`, -being swallowed by the client. - -The `type` function is, like `print`, another in-built function in Python. It -tells us that we (`me`) are of the *class* `typeclasses.characters.Character`. -Meanwhile `me.key` is a *property* on us, a string. It holds the name of this -object. - -> When you do `py me`, the `me` is defined in such a way that it will use its `.key` property to -represent itself. That is why the result is the same as when doing `py me.key`. Also, remember that -as noted in the first part of the tutorial, the `me` is *not* a reserved Python word; it was just -defined by the Evennia developers as a convenient short-hand when creating the `py` command. So -don't expect `me` to be available elsewhere. - -A *class* is like a "factory" or blueprint. From a class you then create individual *instances*. So -if class is`Dog`, an instance of `Dog` might be `fido`. Our in-game persona is of a class -`Character`. The superuser `christine` is an *instance* of the `Character` class (an instance is -also often referred to as an *object*). This is an important concept in *object oriented -programming*. You are wise to [familiarize yourself with it](https://en.wikipedia.org/wiki/Class-based_programming) a little. - -> In other terms: -> * class: A description of a thing, all the methods (code) and data (information) -> * object: A thing, defined as an *instance* of a class. -> -> So in "Fido is a Dog", "Fido" is an object--a unique thing--and "Dog" is a class. Coders would -also say, "Fido is an instance of Dog". There can be other dogs too, such as Butch and Fifi. They, -too, would be instances of Dog. -> -> As another example: "Christine is a Character", or "Christine is an instance of -typeclasses.characters.Character". To start, all characters will be instances of -typeclass.characters.Character. -> -> You'll be writing your own class soon! The important thing to know here is how classes and objects -relate. - -The string `'typeclasses.characters.Character'` we got from the `type()` function is not arbitrary. -You'll recognize this from when we _imported_ `world.test` in part one. This is a _path_ exactly -describing where to find the python code describing this class. Python treats source code files on -your hard drive (known as *modules*) as well as folders (known as *packages*) as objects that you -access with the `.` operator. It starts looking at a place that Evennia has set up for you - namely -the root of your own game directory. - -Open and look at your game folder (named `mygame` if you exactly followed the Getting Started -instructions) in a file editor or in a new terminal/console. Locate the file -`mygame/typeclasses/characters.py` - -``` -mygame/ - typeclasses - characters.py -``` - -This represents the first part of the python path - `typeclasses.characters` (the `.py` file ending -is never included in the python path). The last bit, `.Character` is the actual class name inside -the `characters.py` module. Open that file in a text editor and you will see something like this: - -```python -""" -(Doc string for module) -""" - -from evennia import DefaultCharacter - -class Character(DefaultCharacter): - """ - (Doc string for class) - """ - pass - -``` - -There is `Character`, the last part of the path. Note how empty this file is. At first glance one -would think a Character had no functionality at all. But from what we have used already we know it -has at least the `key` property and the method `msg`! Where is the code? The answer is that this -'emptiness' is an illusion caused by something called *inheritance*. Read on. - -Firstly, in the same way as the little `hello.py` we did in the first part of the tutorial, this is -an example of full, multi-line Python code. Those triple-quoted strings are used for strings that -have line breaks in them. When they appear on their own like this, at the top of a python module, -class or similar they are called *doc strings*. Doc strings are read by Python and is used for -producing online help about the function/method/class/module. By contrast, a line starting with `#` -is a *comment*. It is ignored completely by Python and is only useful to help guide a human to -understand the code. - -The line - -```python - class Character(DefaultCharacter): -``` - -means that the class `Character` is a *child* of the class `DefaultCharacter`. This is called -*inheritance* and is another fundamental concept. The answer to the question "where is the code?" is -that the code is *inherited* from its parent, `DefaultCharacter`. And that in turn may inherit code -from *its* parent(s) and so on. Since our child, `Character` is empty, its functionality is *exactly -identical* to that of its parent. The moment we add new things to Character, these will take -precedence. And if we add something that already existed in the parent, our child-version will -*override* the version in the parent. This is very practical: It means that we can let the parent do -the heavy lifting and only tweak the things we want to change. It also means that we could easily -have many different Character classes, all inheriting from `DefaultCharacter` but changing different -things. And those can in turn also have children ... - -Let's go on an expedition up the inheritance tree. - -### Exploring the Evennia library - -Let's figure out how to tweak `Character`. Right now we don't know much about `DefaultCharacter` -though. Without knowing that we won't know what to override. At the top of the file you find - -```python -from evennia import DefaultCharacter -``` - -This is an `import` statement again, but on a different form to what we've seen before. `from ... -import ...` is very commonly used and allows you to precisely dip into a module to extract just the -component you need to use. In this case we head into the `evennia` package to get -`DefaultCharacter`. - -Where is `evennia`? To find it you need to go to the `evennia` folder (repository) you originally -cloned from us. If you open it, this is how it looks: - -``` -evennia/ - __init__.py - bin/ - CHANGELOG.txt etc. - ... - evennia/ - ... -``` -There are lots of things in there. There are some docs but most of those have to do with the -distribution of Evennia and does not concern us right now. The `evennia` subfolder is what we are -looking for. *This* is what you are accessing when you do `from evennia import ...`. It's set up by -Evennia as a good place to find modules when the server starts. The exact layout of the Evennia -library [is covered by our directory overview](./Directory-Overview.md#evennia-library-layout). You can -also explore it [online on github](https://github.com/evennia/evennia/tree/master/evennia). - -The structure of the library directly reflects how you import from it. - -- To, for example, import [the text justify function](https://github.com/evennia/evennia/blob/master/evennia/utils/utils.py#L201) from -`evennia/utils/utils.py` you would do `from evennia.utils.utils import justify`. In your code you -could then just call `justify(...)` to access its functionality. -- You could also do `from evennia.utils import utils`. In code you would then have to write -`utils.justify(...)`. This is practical if want a lot of stuff from that `utils.py` module and don't -want to import each component separately. -- You could also do `import evennia`. You would then have to enter the full -`evennia.utils.utils.justify(...)` every time you use it. Using `from` to only import the things you -need is usually easier and more readable. -- See [this overview](http://effbot.org/zone/import-confusion.htm) about the different ways to -import in Python. - -Now, remember that our `characters.py` module did `from evennia import DefaultCharacter`. But if we -look at the contents of the `evennia` folder, there is no `DefaultCharacter` anywhere! This is -because Evennia gives a large number of optional "shortcuts", known as [the "flat" API](./Evennia-API.md). The intention is to make it easier to remember where to find stuff. The flat API is defined in -that weirdly named `__init__.py` file. This file just basically imports useful things from all over -Evennia so you can more easily find them in one place. - -We could [just look at the documenation](github:evennia#typeclasses) to find out where we can look -at our `DefaultCharacter` parent. But for practice, let's figure it out. Here is where -`DefaultCharacter` [is imported from](https://github.com/evennia/evennia/blob/master/evennia/__init__.py#L188) inside `__init__.py`: - -```python -from .objects.objects import DefaultCharacter -``` - -The period at the start means that it imports beginning from the same location this module sits(i.e. -the `evennia` folder). The full python-path accessible from the outside is thus -`evennia.objects.objects.DefaultCharacter`. So to import this into our game it'd be perfectly valid -to do - -```python -from evennia.objects.objects import DefaultCharacter -``` - -Using - -```python -from evennia import DefaultCharacter -``` - -is the same thing, just a little easier to remember. - -> To access the shortcuts of the flat API you *must* use `from evennia import -> ...`. Using something like `import evennia.DefaultCharacter` will not work. -> See [more about the Flat API here](./Evennia-API.md). - - -### Tweaking our Character class - -In the previous section we traced the parent of our `Character` class to be -`DefaultCharacter` in -[evennia/objects/objects.py](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py). -Open that file and locate the `DefaultCharacter` class. It's quite a bit down -in this module so you might want to search using your editor's (or browser's) -search function. Once you find it, you'll find that the class starts like this: - -```python - -class DefaultCharacter(DefaultObject): - """ - This implements an Object puppeted by a Session - that is, a character - avatar controlled by an account. - """ - - def basetype_setup(self): - """ - Setup character-specific security. - You should normally not need to overload this, but if you do, - make sure to reproduce at least the two last commands in this - method (unless you want to fundamentally change how a - Character object works). - """ - super().basetype_setup() - self.locks.add(";".join(["get:false()", # noone can pick up the character - "call:false()"])) # no commands can be called on character from -outside - # add the default cmdset - self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True) - - def at_after_move(self, source_location, **kwargs): - """ - We make sure to look around after a move. - """ - if self.location.access(self, "view"): - self.msg(self.at_look(self.location)) - - def at_pre_puppet(self, account, session=None, **kwargs): - """ - Return the character from storage in None location in `at_post_unpuppet`. - """ - - # ... - -``` - -... And so on (you can see the full [class online here](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L1915)). Here we -have functional code! These methods may not be directly visible in `Character` back in our game dir, -but they are still available since `Character` is a child of `DefaultCharacter` above. Here is a -brief summary of the methods we find in `DefaultCharacter` (follow in the code to see if you can see -roughly where things happen):: - -- `basetype_setup` is called by Evennia only once, when a Character is first created. In the -`DefaultCharacter` class it sets some particular [Locks](./Locks.md) so that people can't pick up and -puppet Characters just like that. It also adds the [Character Cmdset](./Command-Sets.md) so that -Characters always can accept command-input (this should usually not be modified - the normal hook to -override is `at_object_creation`, which is called after `basetype_setup` (it's in the parent)). -- `at_after_move` makes it so that every time the Character moves, the `look` command is -automatically fired (this would not make sense for just any regular Object). -- `at_pre_puppet` is called when an Account begins to puppet this Character. When not puppeted, the -Character is hidden away to a `None` location. This brings it back to the location it was in before. -Without this, "headless" Characters would remain in the game world just standing around. -- `at_post_puppet` is called when puppeting is complete. It echoes a message to the room that his -Character has now connected. -- `at_post_unpuppet` is called once stopping puppeting of the Character. This hides away the -Character to a `None` location again. -- There are also some utility properties which makes it easier to get some time stamps from the -Character. - -Reading the class we notice another thing: - -```python -class DefaultCharacter(DefaultObject): - # ... -``` - -This means that `DefaultCharacter` is in *itself* a child of something called `DefaultObject`! Let's -see what this parent class provides. It's in the same module as `DefaultCharacter`, you just need to -[scroll up near the top](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L193): - -```python -class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): - # ... -``` - -This is a really big class where the bulk of code defining an in-game object resides. It consists of -a large number of methods, all of which thus also becomes available on the `DefaultCharacter` class -below *and* by extension in your `Character` class over in your game dir. In this class you can for -example find the `msg` method we have been using before. - -> You should probably not expect to understand all details yet, but as an exercise, find and read -the doc string of `msg`. - -> As seen, `DefaultObject` actually has multiple parents. In one of those the basic `key` property -is defined, but we won't travel further up the inheritance tree in this tutorial. If you are -interested to see them, you can find `TypeclassBase` in [evennia/typeclasses/models.py](https://github.com/evennia/evennia/blob/master/evennia/typeclasses/models.py#L93) and `ObjectDB` in [evennia/obj -ects/models.py](https://github.com/evennia/evennia/blob/master/evennia/objects/models.py#L121). We -will also not go into the details of [Multiple Inheritance](https://docs.python.org/2/tutorial/classes.html#multiple-inheritance) or -[Metaclasses](http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html) here. The general rule -is that if you realize that you need these features, you already know enough to use them. - -Remember the `at_pre_puppet` method we looked at in `DefaultCharacter`? If you look at the -`at_pre_puppet` hook as defined in `DefaultObject` you'll find it to be completely empty (just a -`pass`). So if you puppet a regular object it won't be hiding/retrieving the object when you -unpuppet it. The `DefaultCharacter` class *overrides* its parent's functionality with a version of -its own. And since it's `DefaultCharacter` that our `Character` class inherits back in our game dir, -it's *that* version of `at_pre_puppet` we'll get. Anything not explicitly overridden will be passed -down as-is. - -While it's useful to read the code, we should never actually modify anything inside the `evennia` -folder. Only time you would want that is if you are planning to release a bug fix or new feature for -Evennia itself. Instead you *override* the default functionality inside your game dir. - -So to conclude our little foray into classes, objects and inheritance, locate the simple little -`at_before_say` method in the `DefaultObject` class: - -```python - def at_before_say(self, message, **kwargs): - """ - (doc string here) - """ - return message -``` - -If you read the doc string you'll find that this can be used to modify the output of `say` before it -goes out. You can think of it like this: Evennia knows the name of this method, and when someone -speaks, Evennia will make sure to redirect the outgoing message through this method. It makes it -ripe for us to replace with a version of our own. - -> In the Evennia documentation you may sometimes see the term *hook* used for a method explicitly -meant to be overridden like this. - -As you can see, the first argument to `at_before_say` is `self`. In Python, the first argument of a -method is *always a back-reference to the object instance on which the method is defined*. By -convention this argument is always called `self` but it could in principle be named anything. The -`self` is very useful. If you wanted to, say, send a message to the same object from inside -`at_before_say`, you would do `self.msg(...)`. - -What can trip up newcomers is that you *don't* include `self` when you *call* the method. Try: - - > @py me.at_before_say("Hello World!") - Hello World! - -Note that we don't send `self` but only the `message` argument. Python will automatically add `self` -for us. In this case, `self` will become equal to the Character instance `me`. - -By default the `at_before_say` method doesn't do anything. It just takes the `message` input and -`return`s it just the way it was (the `return` is another reserved Python word). - -> We won't go into `**kwargs` here, but it (and its sibling `*args`) is also important to -understand, extra reading is [here for `**kwargs`](https://stackoverflow.com/questions/1769403/understanding-kwargs-in-python). - -Now, open your game folder and edit `mygame/typeclasses/characters.py`. Locate your `Character` -class and modify it as such: - -```python -class Character(DefaultCharacter): - """ - (docstring here) - """ - def at_before_say(self, message, **kwargs): - "Called before say, allows for tweaking message" - return f"{message} ..." -``` - -So we add our own version of `at_before_say`, duplicating the `def` line from the parent but putting -new code in it. All we do in this tutorial is to add an ellipsis (`...`) to the message as it passes -through the method. - -Note that `f` in front of the string, it means we turned the string into a 'formatted string'. We -can now easily inject stuff directly into the string by wrapping them in curly brackets `{ }`. In -this example, we put the incoming `message` into the string, followed by an ellipsis. This is only -one way to format a string. Python has very powerful [string formatting](https://docs.python.org/2/library/string.html#format-specification-mini-language) and -you are wise to learn it well, considering your game will be mainly text-based. - -> You could also copy & paste the relevant method from `DefaultObject` here to get the full doc -string. For more complex methods, or if you only want to change some small part of the default -behavior, copy & pasting will eliminate the need to constantly look up the original method and keep -you sane. - -In-game, now try - - > @reload - > say Hello - You say, "Hello ..." - -An ellipsis `...` is added to what you said! This is a silly example but you have just made your -first code change to core functionality - without touching any of Evennia's original code! We just -plugged in our own version of the `at_before_say` method and it replaced the default one. Evennia -happily redirected the message through our version and we got a different output. - -> For sane overriding of parent methods you should also be aware of Python's -[super](https://docs.python.org/3/library/functions.html#super), which allows you to call the -methods defined on a parent in your child class. - -### The Evennia shell - -Now on to some generally useful tools as you continue learning Python and Evennia. We have so far -explored using `py` and have inserted Python code directly in-game. We have also modified Evennia's -behavior by overriding default functionality with our own. There is a third way to conveniently -explore Evennia and Python - the Evennia shell. - -Outside of your game, `cd` to your mygame folder and make sure any needed virtualenv is running. -Next: - - > pip install ipython # only needed once - -The [`IPython`](https://en.wikipedia.org/wiki/IPython) program is just a nicer interface to the -Python interpreter - you only need to install it once, after which Evennia will use it -automatically. - - > evennia shell - -If you did this call from your game dir you will now be in a Python prompt managed by the IPython -program. - - IPython ... - ... - In [1]: IPython has some very nice ways to explore what Evennia has to offer. - - > import evennia - > evennia. - -That is, write `evennia.` and press the Tab key. You will be presented with a list of all available -resources in the Evennia Flat API. We looked at the `__init__.py` file in the `evennia` folder -earlier, so some of what you see should be familiar. From the IPython prompt, do: - - > from evennia import DefaultCharacter - > DefaultCharacter.at_before_say? - -Don't forget that you can use `` to auto-complete code as you write. Appending a single `?` to -the end will show you the doc-string for `at_before_say` we looked at earlier. Use `??` to get the -whole source code. - -Let's look at our over-ridden version instead. Since we started the `evennia shell` from our game -dir we can easily get to our code too: - - > from typeclasses.characters import Character - > Character.at_before_say?? - -This will show us the changed code we just did. Having a window with IPython running is very -convenient for quickly exploring code without having to go digging through the file structure! - -### Where to go from here - -This should give you a running start using Python with Evennia. If you are completely new to -programming or Python you might want to look at a more formal Python tutorial. You can find links -and resources [on our link page](./Links.md). - -We have touched upon many of the concepts here but to use Evennia and to be able to follow along in -the code, you will need basic understanding of Python -[modules](http://docs.python.org/2/tutorial/modules.html), -[variables](http://www.tutorialspoint.com/python/python_variable_types.htm), -[conditional statements](http://docs.python.org/tutorial/controlflow.html#if-statements), -[loops](http://docs.python.org/tutorial/controlflow.html#for-statements), -[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions), -[lists, dictionaries, list comprehensions](http://docs.python.org/tutorial/datastructures.html) -and [string formatting](http://docs.python.org/tutorial/introduction.html#strings). You should also have a basic -understanding of [object-oriented programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) -and what Python [Classes](http://docs.python.org/tutorial/classes.html) are. - -Once you have familiarized yourself, or if you prefer to pick Python up as you go, continue to one -of the beginning-level [Evennia tutorials](./Tutorials.md) to gradually build up your understanding. - -Good luck! diff --git a/docs/0.9.5/_sources/Quirks.md.txt b/docs/0.9.5/_sources/Quirks.md.txt deleted file mode 100644 index c23b4a41fc..0000000000 --- a/docs/0.9.5/_sources/Quirks.md.txt +++ /dev/null @@ -1,120 +0,0 @@ -# Quirks - - -This is a list of various quirks or common stumbling blocks that people often ask about or report -when using (or trying to use) Evennia. They are not bugs. - -### Forgetting to use @reload to see changes to your typeclasses - -Firstly: Reloading the server is a safe and usually quick operation which will *not* disconnect any -accounts. - -New users tend to forget this step. When editing source code (such as when tweaking typeclasses and -commands or adding new commands to command sets) you need to either use the in-game `@reload` -command or, from the command line do `python evennia.py reload` before you see your changes. - -### Web admin to create new Account - -If you use the default login system and are trying to use the Web admin to create a new Player -account, you need to consider which `MULTIACCOUNT_MODE` you are in. If you are in -`MULTIACCOUNT_MODE` `0` or `1`, the login system expects each Account to also have a Character -object named the same as the Account - there is no character creation screen by default. If using -the normal mud login screen, a Character with the same name is automatically created and connected -to your Account. From the web interface you must do this manually. - -So, when creating the Account, make sure to also create the Character *from the same form* as you -create the Account from. This should set everything up for you. Otherwise you need to manually set -the "account" property on the Character and the "character" property on the Account to point to each -other. You must also set the lockstring of the Character to allow the Account to "puppet" this -particular character. - -### Mutable attributes and their connection to the database - -When storing a mutable object (usually a list or a dictionary) in an Attribute - -```python - object.db.mylist = [1,2,3] -``` - -you should know that the connection to the database is retained also if you later extract that -Attribute into another variable (what is stored and retrieved is actually a `PackedList` or a -`PackedDict` that works just like their namesakes except they save themselves to the database when -changed). So if you do - -```python - alist = object.db.mylist - alist.append(4) -``` - -this updates the database behind the scenes, so both `alist` and `object.db.mylist` are now -`[1,2,3,4]` - -If you don't want this, Evennia provides a way to stably disconnect the mutable from the database by -use of `evennia.utils.dbserialize.deserialize`: - -```python - from evennia.utils.dbserialize import deserialize - - blist = deserialize(object.db.mylist) - blist.append(4) -``` - -The property `blist` is now `[1,2,3,4]` whereas `object.db.mylist` remains unchanged. If you want to -update the database you'd need to explicitly re-assign the updated data to the `mylist` Attribute. - -### Commands are matched by name *or* alias - -When merging [command sets](./Commands.md) it's important to remember that command objects are identified -*both* by key *or* alias. So if you have a command with a key `look` and an alias `ls`, introducing -another command with a key `ls` will be assumed by the system to be *identical* to the first one. -This usually means merging cmdsets will overload one of them depending on priority. Whereas this is -logical once you know how command objects are handled, it may be confusing if you are just looking -at the command strings thinking they are parsed as-is. - -### Objects turning to `DefaultObject` - -A common confusing error for new developers is finding that one or more objects in-game are suddenly -of the type `DefaultObject` rather than the typeclass you wanted it to be. This happens when you -introduce a critical Syntax error to the module holding your custom class. Since such a module is -not valid Python, Evennia can't load it at all to get to the typeclasses within. To keep on running, -Evennia will solve this by printing the full traceback to the terminal/console and temporarily fall -back to the safe `DefaultObject` until you fix the problem and reload. Most errors of this kind will -be caught by any good text editors. Keep an eye on the terminal/console during a reload to catch -such errors - you may have to scroll up if your window is small. - -### Overriding of magic methods - -Python implements a system of [magic -methods](https://docs.python.org/3/reference/datamodel.html#emulating-container-types), usually -prefixed and suffixed by double-underscores (`__example__`) that allow object instances to have -certain operations performed on them without needing to do things like turn them into strings or -numbers first-- for example, is `obj1` greater than or equal to `obj2`? - -Neither object is a number, but given `obj1.size == "small"` and `obj2.size == "large"`, how might -one compare these two arbitrary English adjective strings to figure out which is greater than the -other? By defining the `__ge__` (greater than or equal to) magic method on the object class in which -you figure out which word has greater significance, perhaps through use of a mapping table -(`{'small':0, 'large':10}`) or other lookup and comparing the numeric values of each. - -Evennia extensively makes use of magic methods on typeclasses to do things like initialize objects, -check object existence or iterate over objects in an inventory or container. If you override or -interfere with the return values from the methods Evennia expects to be both present and working, it -can result in very inconsistent and hard-to-diagnose errors. - -The moral of the story-- it can be dangerous to tinker with magic methods on typeclassed objects. -Try to avoid doing so. - -### Known upstream bugs - -- There is currently (Autumn 2017) a bug in the `zope.interface` installer on some Linux Ubuntu -distributions (notably Ubuntu 16.04 LTS). Zope is a dependency of Twisted. The error manifests in -the server not starting with an error that `zope.interface` is not found even though `pip list` -shows it's installed. The reason is a missing empty `__init__.py` file at the root of the zope -package. If the virtualenv is named "evenv" as suggested in the [Getting Started](./Getting-Started.md) -instructions, use the following command to fix it: - - ```shell - touch evenv/local/lib/python2.7/site-packages/zope/__init__.py - ``` - - This will create the missing file and things should henceforth work correctly. \ No newline at end of file diff --git a/docs/0.9.5/_sources/RSS.md.txt b/docs/0.9.5/_sources/RSS.md.txt deleted file mode 100644 index 73c35b0f2f..0000000000 --- a/docs/0.9.5/_sources/RSS.md.txt +++ /dev/null @@ -1,47 +0,0 @@ -# RSS - - -[RSS](http://en.wikipedia.org/wiki/RSS) is a format for easily tracking updates on websites. The -principle is simple - whenever a site is updated, a small text file is updated. An RSS reader can -then regularly go online, check this file for updates and let the user know what's new. - -Evennia allows for connecting any number of RSS feeds to any number of in-game channels. Updates to -the feed will be conveniently echoed to the channel. There are many potential uses for this: For -example the MUD might use a separate website to host its forums. Through RSS, the players can then -be notified when new posts are made. Another example is to let everyone know you updated your dev -blog. Admins might also want to track the latest Evennia updates through our own RSS feed -[here](http://code.google.com/feeds/p/evennia/updates/basic). - -## Configuring RSS - -To use RSS, you first need to install the [feedparser](http://code.google.com/p/feedparser/) python -module. - - pip install feedparser - -Next you activate RSS support in your config file by settting `RSS_ENABLED=True`. - -Start/reload Evennia as a privileged user. You should now have a new command available, `@rss2chan`: - - @rss2chan = - -### Setting up RSS, step by step - -You can connect RSS to any Evennia channel, but for testing, let's set up a new channel "rss". - - @ccreate rss = RSS feeds are echoed to this channel! - -Let's connect Evennia's code-update feed to this channel. The RSS url for evennia updates is -`https://github.com/evennia/evennia/commits/master.atom`, so let's add that: - - @rss2chan rss = https://github.com/evennia/evennia/commits/master.atom - -That's it, really. New Evennia updates will now show up as a one-line title and link in the channel. -Give the `@rss2chan` command on its own to show all connections. To remove a feed from a channel, -you specify the connection again (use the command to see it in the list) but add the `/delete` -switch: - - @rss2chan/delete rss = https://github.com/evennia/evennia/commits/master.atom - -You can connect any number of RSS feeds to a channel this way. You could also connect them to the -same channels as [IRC](./IRC.md) to have the feed echo to external chat channels as well. diff --git a/docs/0.9.5/_sources/Roadmap.md.txt b/docs/0.9.5/_sources/Roadmap.md.txt deleted file mode 100644 index 5dde75a599..0000000000 --- a/docs/0.9.5/_sources/Roadmap.md.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Roadmap - - -*As of Autumn 2016, Evennia's development roadmap is tracked through the [Evennia Projects -Page](https://github.com/evennia/evennia/projects).* \ No newline at end of file diff --git a/docs/0.9.5/_sources/Running-Evennia-in-Docker.md.txt b/docs/0.9.5/_sources/Running-Evennia-in-Docker.md.txt deleted file mode 100644 index ca546dc7a1..0000000000 --- a/docs/0.9.5/_sources/Running-Evennia-in-Docker.md.txt +++ /dev/null @@ -1,299 +0,0 @@ -# Running Evennia in Docker - -Evennia has an [official docker image](https://hub.docker.com/r/evennia/evennia/) which makes -running an Evennia-based game in a Docker container easy. - -## Install Evennia through docker - -First, install the `docker` program so you can run the Evennia container. You can get it freely from -[docker.com](https://www.docker.com/). Linux users can likely also get it through their normal -package manager. - -To fetch the latest evennia docker image, run: - - docker pull evennia/evennia - -This is a good command to know, it is also how you update to the latest version when we make updates -in the future. This tracks the `master` branch of Evennia. - -> Note: If you want to experiment with the (unstable) `develop` branch, use `docker pull -evennia/evennia:develop`. - -Next `cd` to a place where your game dir is, or where you want to create it. Then run: - - docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 --rm -v $PWD:/usr/src/game --user -$UID:$GID evennia/evennia - -Having run this (see next section for a description of what's what), you will be at a prompt inside -the docker container: - -```bash -evennia|docker /usr/src/game $ -``` - -This is a normal shell prompt. We are in the `/usr/src/game` location inside the docker container. -If you had anything in the folder you started from, you should see it here (with `ls`) since we -mounted the current directory to `/usr/src/game` (with `-v` above). You have the `evennia` command -available and can now proceed to create a new game as per the [Getting Started](./Getting-Started.md) -instructions (you can skip the virtualenv and install 'globally' in the container though). - -You can run Evennia from inside this container if you want to, it's like you are root in a little -isolated Linux environment. To exit the container and all processes in there, press `Ctrl-D`. If you -created a new game folder, you will find that it has appeared on-disk. - -> The game folder or any new files that you created from inside the container will appear as owned -by `root`. If you want to edit the files outside of the container you should change the ownership. -On Linux/Mac you do this with `sudo chown myname:myname -R mygame`, where you replace `myname` with -your username and `mygame` with whatever your game folder is named. - -### Description of the `docker run` command - -```bash - docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 --rm -v $PWD:/usr/src/game --user -$UID:$GID evennia/evennia -``` - -This is what it does: - -- `docker run ... evennia/evennia` tells us that we want to run a new container based on the -`evennia/evennia` docker image. Everything in between are options for this. The `evennia/evennia` is -the name of our [official docker image on the dockerhub -repository](https://hub.docker.com/r/evennia/evennia/). If you didn't do `docker pull -evennia/evennia` first, the image will be downloaded when running this, otherwise your already -downloaded version will be used. It contains everything needed to run Evennia. -- `-it` has to do with creating an interactive session inside the container we start. -- `--rm` will make sure to delete the container when it shuts down. This is nice to keep things tidy -on your drive. -- `-p 4000:4000 -p 4001:4001 -p 4002:4002` means that we *map* ports `4000`, `4001` and `4002` from -inside the docker container to same-numbered ports on our host machine. These are ports for telnet, -webserver and websockets. This is what allows your Evennia server to be accessed from outside the -container (such as by your MUD client)! -- `-v $PWD:/usr/src/game` mounts the current directory (*outside* the container) to the path -`/usr/src/game` *inside* the container. This means that when you edit that path in the container you -will actually be modifying the "real" place on your hard drive. If you didn't do this, any changes -would only exist inside the container and be gone if we create a new one. Note that in linux a -shortcut for the current directory is `$PWD`. If you don't have this for your OS, you can replace it -with the full path to the current on-disk directory (like `C:/Development/evennia/game` or wherever -you want your evennia files to appear). -- `--user $UID:$GID` ensures the container's modifications to `$PWD` are done with you user and -group IDs instead of root's IDs (root is the user running evennia inside the container). This avoids -having stale `.pid` files in your filesystem between container reboots which you have to force -delete with `sudo rm server/*.pid` before each boot. - -## Running your game as a docker image - -If you run the `docker` command given in the previous section from your game dir you can then -easily start Evennia and have a running server without any further fuss. - -But apart from ease of install, the primary benefit to running an Evennia-based game in a container -is to simplify its deployment into a public production environment. Most cloud-based hosting -providers these days support the ability to run container-based applications. This makes deploying -or updating your game as simple as building a new container image locally, pushing it to your Docker -Hub account, and then pulling from Docker Hub into your AWS/Azure/other docker-enabled hosting -account. The container eliminates the need to install Python, set up a virtualenv, or run pip to -install dependencies. - -### Start Evennia and run through docker - -For remote or automated deployment you may want to start Evennia immediately as soon as the docker -container comes up. If you already have a game folder with a database set up you can also start the -docker container and pass commands directly to it. The command you pass will be the main process to -run in the container. From your game dir, run for example this command: - - docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 --rm -v $PWD:/usr/src/game -evennia/evennia evennia start --log - -This will start Evennia as the foreground process, echoing the log to the terminal. Closing the -terminal will kill the server. Note that you *must* use a foreground command like `evennia start ---log` or `evennia ipstart` to start the server - otherwise the foreground process will finish -immediately and the container go down. - -### Create your own game image - -You may want to create your own image in order to bake in your gamedir directly into the docker -container for easy upload and deployment. -These steps assume that you have created or otherwise obtained a game directory already. First, `cd` -to your game dir and create a new empty text file named `Dockerfile`. Save the following two lines -into it: - -``` -FROM evennia/evennia:latest - -ENTRYPOINT evennia start --log -``` - -These are instructions for building a new docker image. This one is based on the official -`evennia/evennia` image. We add the second line to -make make sure evennia starts immediately along with the container (so we don't need to enter it and -run commands). - -To build the image: - -```bash - docker build -t mydhaccount/mygame . -``` - -(don't forget the period `.` at the end, it tells docker to use use the `Dockerfile` from the -current location). Here `mydhaccount` is the name of your `dockerhub` account. If you don't have a -dockerhub account you can build the image locally only (name the container whatever you like in that -case, like just `mygame`). - -Docker images are stored centrally on your computer. You can see which ones you have available -locally with `docker images`. Once built, you have a couple of options to run your game. - -If you have a docker-hub account, you can push your (new or updated) image there: - -```bash - docker push myhdaccount/mygame -``` - -### Run container from your game image for development - -To run the container based on your game image locally for development, mount the local game -directory as before: - -``` -docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 -v $PWD:/usr/src/game --user $UID:$GID -mydhaccount/mygame -``` - -Evennia will start and you'll get output in the terminal, perfect for development. You should be -able to connect to the game with your clients normally. - -### Deploy game image for production - -Each time you rebuild the docker image as per the above instructions, the latest copy of your game -directory is actually copied inside the image (at `/usr/src/game/`). If you don't mount your on-disk -folder there, the internal one will be used. So for deploying evennia on a server, omit the `-v` -option and just give the following command: - -``` -docker run -it --rm -d -p 4000:4000 -p 4001:4001 -p 4002:4002 --user $UID:$GID mydhaccount/mygame -``` - -Your game will be downloaded from your docker-hub account and a new container will be built using -the image and started on the server! If your server environment forces you to use different ports, -you can just map the normal ports differently in the command above. - -Above we added the `-d` option, which starts the container in *daemon* mode - you won't see any -return in the console. You can see it running with `docker ps`: - -```bash -$ docker ps - -CONTAINER ID IMAGE COMMAND CREATED ... -f6d4ca9b2b22 mygame "/bin/sh -c 'evenn..." About a minute ago ... -``` - -Note the container ID, this is how you manage the container as it runs. - -``` - docker logs f6d4ca9b2b22 -``` -Looks at the STDOUT output of the container (i.e. the normal server log) -``` - docker logs -f f6d4ca9b2b22 -``` -Tail the log (so it updates to your screen 'live'). -``` - docker pause f6d4ca9b2b22 -``` -Suspend the state of the container. -``` - docker unpause f6d4ca9b2b22 -``` -Un-suspend it again after a pause. It will pick up exactly where it were. -``` - docker stop f6d4ca9b2b22 -``` -Stop the container. To get it up again you need to use `docker run`, specifying ports etc. A new -container will get a new container id to reference. - -## How it Works - -The `evennia/evennia` docker image holds the evennia library and all of its dependencies. It also -has an `ONBUILD` directive which is triggered during builds of images derived from it. This -`ONBUILD` directive handles setting up a volume and copying your game directory code into the proper -location within the container. - -In most cases, the Dockerfile for an Evennia-based game will only need the `FROM -evennia/evennia:latest` directive, and optionally a `MAINTAINER` directive if you plan to publish -your image on Docker Hub and would like to provide contact info. - -For more information on Dockerfile directives, see the [Dockerfile -Reference](https://docs.docker.com/engine/reference/builder/). - -For more information on volumes and Docker containers, see the Docker site's [Manage data in -containers](https://docs.docker.com/engine/tutorials/dockervolumes/) page. - -### What if I Don't Want "LATEST"? - -A new `evennia/evennia` image is built automatically whenever there is a new commit to the `master` -branch of Evennia. It is possible to create your own custom evennia base docker image based on any -arbitrary commit. - -1. Use git tools to checkout the commit that you want to base your image upon. (In the example -below, we're checking out commit a8oc3d5b.) -``` -git checkout -b my-stable-branch a8oc3d5b -``` -2. Change your working directory to the `evennia` directory containing `Dockerfile`. Note that -`Dockerfile` has changed over time, so if you are going far back in the commit history you might -want to bring a copy of the latest `Dockerfile` with you and use that instead of whatever version -was used at the time. -3. Use the `docker build` command to build the image based off of the currently checked out commit. -The example below assumes your docker account is **mydhaccount**. -``` -docker build -t mydhaccount/evennia . -``` -4. Now you have a base evennia docker image built off of a specific commit. To use this image to -build your game, you would modify **FROM** directive in the **Dockerfile** for your game directory -to be: - -``` -FROM mydhacct/evennia:latest -``` - -Note: From this point, you can also use the `docker tag` command to set a specific tag on your image -and/or upload it into Docker Hub under your account. - -5. At this point, build your game using the same `docker build` command as usual. Change your -working directory to be your game directory and run - -``` -docker build -t mydhaccountt/mygame . -``` - -## Additional Creature Comforts - -The Docker ecosystem includes a tool called `docker-compose`, which can orchestrate complex multi- -container applications, or in our case, store the default port and terminal parameters that we want -specified every time we run our container. A sample `docker-compose.yml` file to run a containerized -Evennia game in development might look like this: -``` -version: '2' - -services: - evennia: - image: mydhacct/mygame - stdin_open: true - tty: true - ports: - - "4001-4002:4001-4002" - - "4000:4000" - volumes: - - .:/usr/src/game -``` -With this file in the game directory next to the `Dockerfile`, starting the container is as simple -as -``` -docker-compose up -``` -For more information about `docker-compose`, see [Getting Started with docker- -compose](https://docs.docker.com/compose/gettingstarted/). - -> Note that with this setup you lose the `--user $UID` option. The problem is that the variable -`UID` is not available inside the configuration file `docker-compose.yml`. A workaround is to -hardcode your user and group id. In a terminal run `echo $UID:$GID` and if for example you get -`1000:1000` you can add to `docker-compose.yml` a line `user: 1000:1000` just below the `image: ...` -line. diff --git a/docs/0.9.5/_sources/Screenshot.md.txt b/docs/0.9.5/_sources/Screenshot.md.txt deleted file mode 100644 index 322dda2349..0000000000 --- a/docs/0.9.5/_sources/Screenshot.md.txt +++ /dev/null @@ -1,19 +0,0 @@ -# Screenshot - - -![evennia_screenshot2017](https://user-images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png) -*(right-click and choose your browser's equivalent of "view image" to see it full size)* - -This screenshot shows a vanilla [install](./Getting-Started.md) of the just started Evennia MUD server. -In the bottom window we can see the log messages from the running server. - -In the top left window we see the default website of our new game displayed in a web browser (it has -shrunk to accomodate the smaller screen). Evennia contains its own webserver to serve this page. The -default site shows some brief info about the database. From here you can also reach Django's *admin -interface* for editing the database online. - -To the upper right is the included web-browser client showing a connection to the server on port -4001. This allows users to access the game without downloading a mud client separately. - -Bottom right we see a login into the stock game using a third-party MUD client -([Mudlet](http://www.mudlet.org)). This connects to the server via the telnet protocol on port 4000. diff --git a/docs/0.9.5/_sources/Scripts.md.txt b/docs/0.9.5/_sources/Scripts.md.txt deleted file mode 100644 index dc1c5a8d98..0000000000 --- a/docs/0.9.5/_sources/Scripts.md.txt +++ /dev/null @@ -1,384 +0,0 @@ -# Scripts - - -*Scripts* are the out-of-character siblings to the in-character -[Objects](./Objects.md). Scripts are so flexible that the "Script" is a bit limiting -- we had to pick something to name them after all. Other possible names -(depending on what you'd use them for) would be `OOBObjects`, -`StorageContainers` or `TimerObjects`. - -Scripts can be used for many different things in Evennia: - -- They can attach to Objects to influence them in various ways - or exist - independently of any one in-game entity (so-called *Global Scripts*). -- They can work as timers and tickers - anything that may change with Time. But - they can also have no time dependence at all. Note though that if all you want - is just to have an object method called repeatedly, you should consider using - the [TickerHandler](./TickerHandler.md) which is more limited but is specialized on - just this task. -- They can describe State changes. A Script is an excellent platform for -hosting a persistent, but unique system handler. For example, a Script could be -used as the base to track the state of a turn-based combat system. Since -Scripts can also operate on a timer they can also update themselves regularly -to perform various actions. -- They can act as data stores for storing game data persistently in the database -(thanks to its ability to have [Attributes](./Attributes.md)). -- They can be used as OOC stores for sharing data between groups of objects, for -example for tracking the turns in a turn-based combat system or barter exchange. - -Scripts are [Typeclassed](./Typeclasses.md) entities and are manipulated in a similar -way to how it works for other such Evennia entities: - -```python -# create a new script -new_script = evennia.create_script(key="myscript", typeclass=...) - -# search (this is always a list, also if there is only one match) -list_of_myscript = evennia.search_script("myscript") - -``` - -## Defining new Scripts - -A Script is defined as a class and is created in the same way as other -[typeclassed](./Typeclasses.md) entities. The class has several properties -to control the timer-component of the scripts. These are all _optional_ - -leaving them out will just create a Script with no timer components (useful to act as -a database store or to hold a persistent game system, for example). - -This you can do for example in the module -`evennia/typeclasses/scripts.py`. Below is an example Script - Typeclass. - -```python -from evennia import DefaultScript - -class MyScript(DefaultScript): - - def at_script_creation(self): - self.key = "myscript" - self.interval = 60 # 1 min repeat - - def at_repeat(self): - # do stuff every minute -``` - -In `mygame/typeclasses/scripts.py` is the `Script` class which inherits from `DefaultScript` -already. This is provided as your own base class to do with what you like: You can tweak `Script` if -you want to change the default behavior and it is usually convenient to inherit from this instead. -Here's an example: - -```python - # for example in mygame/typeclasses/scripts.py - # Script class is defined at the top of this module - - import random - - class Weather(Script): - """ - A timer script that displays weather info. Meant to - be attached to a room. - - """ - def at_script_creation(self): - self.key = "weather_script" - self.desc = "Gives random weather messages." - self.interval = 60 * 5 # every 5 minutes - self.persistent = True # will survive reload - - def at_repeat(self): - "called every self.interval seconds." - rand = random.random() - if rand < 0.5: - weather = "A faint breeze is felt." - elif rand < 0.7: - weather = "Clouds sweep across the sky." - else: - weather = "There is a light drizzle of rain." - # send this message to everyone inside the object this - # script is attached to (likely a room) - self.obj.msg_contents(weather) -``` - -If we put this script on a room, it will randomly report some weather -to everyone in the room every 5 minutes. - -To activate it, just add it to the script handler (`scripts`) on an -[Room](./Objects.md). That object becomes `self.obj` in the example above. Here we -put it on a room called `myroom`: - -``` - myroom.scripts.add(scripts.Weather) -``` - -> Note that `typeclasses` in your game dir is added to the setting `TYPECLASS_PATHS`. -> Therefore we don't need to give the full path (`typeclasses.scripts.Weather` -> but only `scripts.Weather` above. - -If you wanted to stop and delete that script on the [Room](./Objects.md). You could do that -with the script handler by passing the `delete` method with the script key (`self.key`) as: - -``` - myroom.scripts.delete('weather_script') -``` - -> Note that If no key is given, this will delete *all* scripts on the object! - -You can also create scripts using the `evennia.create_script` function: - -```python - from evennia import create_script - create_script('typeclasses.weather.Weather', obj=myroom) -``` - -Note that if you were to give a keyword argument to `create_script`, that would -override the default value in your Typeclass. So for example, here is an instance -of the weather script that runs every 10 minutes instead (and also not survive -a server reload): - -```python - create_script('typeclasses.weather.Weather', obj=myroom, - persistent=False, interval=10*60) -``` - -From in-game you can use the `@script` command to launch the Script on things: - -``` - @script here = typeclasses.scripts.Weather -``` - -You can conveniently view and kill running Scripts by using the `@scripts` -command in-game. - -## Properties and functions defined on Scripts - -A Script has all the properties of a typeclassed object, such as `db` and `ndb`(see -[Typeclasses](./Typeclasses.md)). Setting `key` is useful in order to manage scripts (delete them by name -etc). These are usually set up in the Script's typeclass, but can also be assigned on the fly as -keyword arguments to `evennia.create_script`. - -- `desc` - an optional description of the script's function. Seen in script listings. -- `interval` - how often the script should run. If `interval == 0` (default), this script has no -timing component, will not repeat and will exist forever. This is useful for Scripts used for -storage or acting as bases for various non-time dependent game systems. -- `start_delay` - (bool), if we should wait `interval` seconds before firing for the first time or -not. -- `repeats` - How many times we should repeat, assuming `interval > 0`. If repeats is set to `<= 0`, -the script will repeat indefinitely. Note that *each* firing of the script (including the first one) -counts towards this value. So a `Script` with `start_delay=False` and `repeats=1` will start, -immediately fire and shut down right away. -- `persistent`- if this script should survive a server *reset* or server *shutdown*. (You don't need -to set this for it to survive a normal reload - the script will be paused and seamlessly restart -after the reload is complete). - -There is one special property: - -- `obj` - the [Object](./Objects.md) this script is attached to (if any). You should not need to set -this manually. If you add the script to the Object with `myobj.scripts.add(myscriptpath)` or give -`myobj` as an argument to the `utils.create.create_script` function, the `obj` property will be set -to `myobj` for you. - -It's also imperative to know the hook functions. Normally, overriding -these are all the customization you'll need to do in Scripts. You can -find longer descriptions of these in `src/scripts/scripts.py`. - -- `at_script_creation()` - this is usually where the script class sets things like `interval` and -`repeats`; things that control how the script runs. It is only called once - when the script is -first created. -- `is_valid()` - determines if the script should still be running or not. This is called when -running `obj.scripts.validate()`, which you can run manually, but which is also called by Evennia -during certain situations such as reloads. This is also useful for using scripts as state managers. -If the method returns `False`, the script is stopped and cleanly removed. -- `at_start()` - this is called when the script starts or is unpaused. For persistent scripts this -is at least once ever server startup. Note that this will *always* be called right away, also if -`start_delay` is `True`. -- `at_repeat()` - this is called every `interval` seconds, or not at all. It is called right away at -startup, unless `start_delay` is `True`, in which case the system will wait `interval` seconds -before calling. -- `at_stop()` - this is called when the script stops for whatever reason. It's a good place to do -custom cleanup. -- `at_server_reload()` - this is called whenever the server is warm-rebooted (e.g. with the -`@reload` command). It's a good place to save non-persistent data you might want to survive a -reload. -- `at_server_shutdown()` - this is called when a system reset or systems shutdown is invoked. - -Running methods (usually called automatically by the engine, but possible to also invoke manually) - -- `start()` - this will start the script. This is called automatically whenever you add a new script -to a handler. `at_start()` will be called. -- `stop()` - this will stop the script and delete it. Removing a script from a handler will stop it -automatically. `at_stop()` will be called. -- `pause()` - this pauses a running script, rendering it inactive, but not deleting it. All -properties are saved and timers can be resumed. This is called automatically when the server reloads -and will *not* lead to the *at_stop()* hook being called. This is a suspension of the script, not a -change of state. -- `unpause()` - resumes a previously paused script. The `at_start()` hook *will* be called to allow -it to reclaim its internal state. Timers etc are restored to what they were before pause. The server -automatically unpauses all paused scripts after a server reload. -- `force_repeat()` - this will forcibly step the script, regardless of when it would otherwise have -fired. The timer will reset and the `at_repeat()` hook is called as normal. This also counts towards -the total number of repeats, if limited. -- `time_until_next_repeat()` - for timed scripts, this returns the time in seconds until it next -fires. Returns `None` if `interval==0`. -- `remaining_repeats()` - if the Script should run a limited amount of times, this tells us how many -are currently left. -- `reset_callcount(value=0)` - this allows you to reset the number of times the Script has fired. It -only makes sense if `repeats > 0`. -- `restart(interval=None, repeats=None, start_delay=None)` - this method allows you to restart the -Script in-place with different run settings. If you do, the `at_stop` hook will be called and the -Script brought to a halt, then the `at_start` hook will be called as the Script starts up with your -(possibly changed) settings. Any keyword left at `None` means to not change the original setting. - - -## Global Scripts - -A script does not have to be connected to an in-game object. If not it is -called a *Global script*. You can create global scripts by simply not supplying an object to store -it on: - -```python - # adding a global script - from evennia import create_script - create_script("typeclasses.globals.MyGlobalEconomy", - key="economy", persistent=True, obj=None) -``` -Henceforth you can then get it back by searching for its key or other identifier with -`evennia.search_script`. In-game, the `scripts` command will show all scripts. - -Evennia supplies a convenient "container" called `GLOBAL_SCRIPTS` that can offer an easy -way to access global scripts. If you know the name (key) of the script you can get it like so: - -```python -from evennia import GLOBAL_SCRIPTS - -my_script = GLOBAL_SCRIPTS.my_script -# needed if there are spaces in name or name determined on the fly -another_script = GLOBAL_SCRIPTS.get("another script") -# get all global scripts (this returns a Queryset) -all_scripts = GLOBAL_SCRIPTS.all() -# you can operate directly on the script -GLOBAL_SCRIPTS.weather.db.current_weather = "Cloudy" - -``` - -> Note that global scripts appear as properties on `GLOBAL_SCRIPTS` based on their `key`. -If you were to create two global scripts with the same `key` (even with different typeclasses), -the `GLOBAL_SCRIPTS` container will only return one of them (which one depends on order in -the database). Best is to organize your scripts so that this does not happen. Otherwise, use -`evennia.search_script` to get exactly the script you want. - -There are two ways to make a script appear as a property on `GLOBAL_SCRIPTS`. The first is -to manually create a new global script with `create_script` as mentioned above. Often you want this -to happen automatically when the server starts though. For this you add a python global dictionary -named `GLOBAL_SCRIPTS` to your `settings.py` file. The `settings.py` fie is located in -`mygame/conf/settings.py`: - -```python -GLOBAL_SCRIPTS = { - "my_script": { - "typeclass": "typeclasses.scripts.Weather", - "repeats": -1, - "interval": 50, - "desc": "Weather script", - "persistent": True - }, - "storagescript": { - "typeclass": "typeclasses.scripts.Storage", - "persistent": True - }, - { - "another_script": { - "typeclass": "typeclasses.another_script.AnotherScript" - } -} -``` -Here the key (`myscript` and `storagescript` above) is required, all other fields are optional. If -`typeclass` is not given, a script of type `settings.BASE_SCRIPT_TYPECLASS` is assumed. The keys -related to timing and intervals are only needed if the script is timed. - -> Note: Provide the full path to the scripts module in GLOBAL_SCRIPTS. In the example with -`another_script`, you can also create separate script modules that exist beyond scrypt.py for -further organizational requirements if needed. - -Evennia will use the information in `settings.GLOBAL_SCRIPTS` to automatically create and start -these -scripts when the server starts (unless they already exist, based on their `key`). You need to reload -the server before the setting is read and new scripts become available. You can then find the `key` -you gave as properties on `evennia.GLOBAL_SCRIPTS` -(such as `evennia.GLOBAL_SCRIPTS.storagescript`). - -> Note: Make sure that your Script typeclass does not have any critical errors. If so, you'll see -errors in your log and your Script will temporarily fall back to being a `DefaultScript` type. - -Moreover, a script defined this way is *guaranteed* to exist when you try to access it: -```python -from evennia import GLOBAL_SCRIPTS -# first stop the script -GLOBAL_SCRIPTS.storagescript.stop() -# running the `scripts` command now will show no storagescript -# but below now it's recreated again! -storage = GLOBAL_SCRIPTS.storagescript -``` -That is, if the script is deleted, next time you get it from `GLOBAL_SCRIPTS`, it will use the -information -in settings to recreate it for you. - -> Note that if your goal with the Script is to store persistent data, you should set it as -`persistent=True`, either in `settings.GLOBAL_SCRIPTS` or in the Scripts typeclass. Otherwise any -data you wanted to store on it will be gone (since a new script of the same name is restarted -instead). - -## Dealing with Errors - -Errors inside an timed, executing script can sometimes be rather terse or point to -parts of the execution mechanism that is hard to interpret. One way to make it -easier to debug scripts is to import Evennia's native logger and wrap your -functions in a try/catch block. Evennia's logger can show you where the -traceback occurred in your script. - -```python - -from evennia.utils import logger - -class Weather(DefaultScript): - - # [...] - - def at_repeat(self): - - try: - # [...] code as above - except Exception: - # logs the error - logger.log_trace() - -``` - -## Example of a timed script - -In-game you can try out scripts using the `@script` command. In the -`evennia/contrib/tutorial_examples/bodyfunctions.py` is a little example script -that makes you do little 'sounds' at random intervals. Try the following to apply an -example time-based script to your character. - - > @script self = bodyfunctions.BodyFunctions - -> Note: Since `evennia/contrib/tutorial_examples` is in the default setting -> `TYPECLASS_PATHS`, we only need to specify the final part of the path, -> that is, `bodyfunctions.BodyFunctions`. - -If you want to inflict your flatulence script on another person, place or -thing, try something like the following: - - > @py self.location.search('matt').scripts.add('bodyfunctions.BodyFunctions') - -Here's how you stop it on yourself. - - > @script/stop self = bodyfunctions.BodyFunctions - -This will kill the script again. You can use the `@scripts` command to list all -active scripts in the game, if any (there are none by default). - - -For another example of a Script in use, check out the [Turn Based Combat System -tutorial](https://github.com/evennia/evennia/wiki/Turn%20based%20Combat%20System). diff --git a/docs/0.9.5/_sources/Security.md.txt b/docs/0.9.5/_sources/Security.md.txt deleted file mode 100644 index e3a5ca60eb..0000000000 --- a/docs/0.9.5/_sources/Security.md.txt +++ /dev/null @@ -1,152 +0,0 @@ -# Security - -Hackers these days aren't discriminating, and their backgrounds range from bored teenagers to -international intelligence agencies. Their scripts and bots endlessly crawl the web, looking for -vulnerable systems they can break into. Who owns the system is irrelevant-- it doesn't matter if it -belongs to you or the Pentagon, the goal is to take advantage of poorly-secured systems and see what -resources can be controlled or stolen from them. - -If you're considering deploying to a cloud-based host, you have a vested interest in securing your -applications-- you likely have a credit card on file that your host can freely bill. Hackers pegging -your CPU to mine cryptocurrency or saturating your network connection to participate in a botnet or -send spam can run up your hosting bill, get your service suspended or get your address/site -blacklisted by ISPs. It can be a difficult legal or political battle to undo this damage after the -fact. - -As a developer about to expose a web application to the threat landscape of the modern internet, -here are a few tips to consider to increase the security of your Evennia install. - -### Know your logs -In case of emergency, check your logs! By default they are located in the `server/logs/` folder. -Here are some of the more important ones and why you should care: - -* `http_requests.log` will show you what HTTP requests have been made against Evennia's built-in -webserver (TwistedWeb). This is a good way to see if people are innocuously browsing your site or -trying to break it through code injection. -* `portal.log` will show you various networking-related information. This is a good place to check -for odd or unusual types or amounts of connections to your game, or other networking-related -issues-- like when users are reporting an inability to connect. -* `server.log` is the MUX administrator's best friend. Here is where you'll find information -pertaining to who's trying to break into your system by guessing at passwords, who created what -objects, and more. If your game fails to start or crashes and you can't tell why, this is the first -place you should look for answers. Security-related events are prefixed with an `[SS]` so when -there's a problem you might want to pay special attention to those. - -### Disable development/debugging options -There are a few Evennia/Django options that are set when you first create your game to make it more -obvious to you where problems arise. These options should be disabled before you push your game into -production-- leaving them on can expose variables or code someone with malicious intent can easily -abuse to compromise your environment. - -In `server/conf/settings.py`: - - # Disable Django's debug mode - DEBUG = False - # Disable the in-game equivalent - IN_GAME_ERRORS = False - # If you've registered a domain name, force Django to check host headers. Otherwise leave this -as-is. - # Note the leading period-- it is not a typo! - ALLOWED_HOSTS = ['.example.com'] - -### Handle user-uploaded images with care -If you decide to allow users to upload their own images to be served from your site, special care -must be taken. Django will read the file headers to confirm it's an image (as opposed to a document -or zip archive), but [code can be injected into an image -file](https://insinuator.net/2014/05/django-image-validation-vulnerability/) *after* the headers -that can be interpreted as HTML and/or give an attacker a web shell through which they can access -other filesystem resources. - -[Django has a more comprehensive overview of how to handle user-uploaded -files](https://docs.djangoproject.com/en/dev/topics/security/#user-uploaded-content-security), but -in short you should take care to do one of two things-- - -* Serve all user-uploaded assets from a *separate* domain or CDN (*not* a subdomain of the one you -already have!). For example, you may be browsing `reddit.com` but note that all the user-submitted -images are being served from the `redd.it` domain. There are both security and performance benefits -to this (webservers tend to load local resources one-by-one, whereas they will request external -resources in bulk). -* If you don't want to pay for a second domain, don't understand what any of this means or can't be -bothered with additional infrastructure, then simply reprocess user images upon receipt using an -image library. Convert them to a different format, for example. *Destroy the originals!* - -### Disable the web interface -The web interface allows visitors to see an informational page as well as log into a browser-based -telnet client with which to access Evennia. It also provides authentication endpoints against which -an attacker can attempt to validate stolen lists of credentials to see which ones might be shared by -your users. Django's security is robust, but if you don't want/need these features and fully intend -to force your users to use traditional clients to access your game, you might consider disabling -either/both to minimize your attack surface. - -In `server/conf/settings.py`: - - # Disable the Javascript webclient - WEBCLIENT_ENABLED = False - # Disable the website altogether - WEBSERVER_ENABLED = False - -### Change your ssh port -Automated attacks will often target port 22 seeing as how it's the standard port for SSH traffic. -Also, -many public wifi hotspots block ssh traffic over port 22 so you might not be able to access your -server from these locations if you like to work remotely or don't have a home internet connection. - -If you don't intend on running a website or securing it with TLS, you can mitigate both problems by -changing the port used for ssh to 443, which most/all hotspot providers assume is HTTPS traffic and -allows through. - -(Ubuntu) In /etc/ssh/sshd_config, change the following variable: - - # What ports, IPs and protocols we listen for - Port 443 - -Save, close, then run the following command: - - sudo service ssh restart - -### Set up a firewall -Ubuntu users can make use of the simple ufw utility. Anybody else can use iptables. - - # Install ufw (if not already) - sudo apt-get install ufw - -UFW's default policy is to deny everything. We must specify what we want to allow through our -firewall. - - # Allow terminal connections to your game - sudo ufw allow 4000/tcp - # Allow browser connections to your website - sudo ufw allow 4001/tcp - -Use ONE of the next two commands depending on which port your ssh daemon is listening on: - - sudo ufw allow 22/tcp - sudo ufw allow 443/tcp - -Finally: - - sudo ufw enable - -Now the only ports open will be your administrative ssh port (whichever you chose), and Evennia on -4000-4001. - -### Use an external webserver -Though not officially supported, there are some benefits to [deploying a webserver](./Apache-Config.md) -to handle/proxy traffic to your Evennia instance. - -For example, Evennia's game engine and webservice are tightly integrated. If you bring your game -down for maintenance (or if it simply crashes) your website will go down with it. In these cases a -standalone webserver can still be used to display a maintenance page or otherwise communicate to -your users the reason for the downtime, instead of disappearing off the face of the earth and -returning opaque `SERVER NOT FOUND` error messages. - -Proper webservers are also written in more efficient programming languages than Python, and while -Twisted can handle its own, putting a webserver in front of it is like hiring a bouncer to deal with -nuisances and crowds before they even get in the door. - -Many of the popular webservers also let you plug in additional modules (like -[mod_security](https://en.wikipedia.org/wiki/ModSecurity) for Apache) that can be used to detect -(and block!) malicious users or requests before they even touch your game or site. There are also -automated solutions for installing and configuring TLS (via [Certbot/Let's -Encrypt](https://en.wikipedia.org/wiki/Let%27s_Encrypt)) to secure your website against hotspot and -ISP snooping. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Server-Conf.md.txt b/docs/0.9.5/_sources/Server-Conf.md.txt deleted file mode 100644 index 4d72e5b1f1..0000000000 --- a/docs/0.9.5/_sources/Server-Conf.md.txt +++ /dev/null @@ -1,104 +0,0 @@ -# Server Conf - - -Evennia runs out of the box without any changes to its settings. But there are several important -ways to customize the server and expand it with your own plugins. - -## Settings file - -The "Settings" file referenced throughout the documentation is the file -`mygame/server/conf/settings.py`. This is automatically created on the first run of `evennia --init` -(see the [Getting Started](./Getting-Started.md) page). - -Your new `settings.py` is relatively bare out of the box. Evennia's core settings file is actually -[evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default -.py) and is considerably more extensive (it is also heavily documented so you should refer to this -file directly for the available settings). - -Since `mygame/server/conf/settings.py` is a normal Python module, it simply imports -`evennia/settings_default.py` into itself at the top. - -This means that if any setting you want to change were to depend on some *other* default setting, -you might need to copy & paste both in order to change them and get the effect you want (for most -commonly changed settings, this is not something you need to worry about). - -You should never edit `evennia/settings_default.py`. Rather you should copy&paste the select -variables you want to change into your `settings.py` and edit them there. This will overload the -previously imported defaults. - -> Warning: It may be tempting to copy everything from `settings_default.py` into your own settings -file. There is a reason we don't do this out of the box though: it makes it directly clear what -changes you did. Also, if you limit your copying to the things you really need you will directly be -able to take advantage of upstream changes and additions to Evennia for anything you didn't -customize. - -In code, the settings is accessed through - -```python - from django.conf import settings - # or (shorter): - from evennia import settings - # example: - servername = settings.SERVER_NAME -``` - -Each setting appears as a property on the imported `settings` object. You can also explore all -possible options with `evennia.settings_full` (this also includes advanced Django defaults that are -not touched in default Evennia). - -> It should be pointed out that when importing `settings` into your code like this, it will be *read -only*. You *cannot* edit your settings from your code! The only way to change an Evennia setting is -to edit `mygame/server/conf/settings.py` directly. You also generally need to restart the server -(possibly also the Portal) before a changed setting becomes available. - -## Other files in the `server/conf` directory - -Apart from the main `settings.py` file, - -- `at_initial_setup.py` - this allows you to add a custom startup method to be called (only) the -very first time Evennia starts (at the same time as user #1 and Limbo is created). It can be made to -start your own global scripts or set up other system/world-related things your game needs to have -running from the start. -- `at_server_startstop.py` - this module contains two functions that Evennia will call every time -the Server starts and stops respectively - this includes stopping due to reloading and resetting as -well as shutting down completely. It's a useful place to put custom startup code for handlers and -other things that must run in your game but which has no database persistence. -- `connection_screens.py` - all global string variables in this module are interpreted by Evennia as -a greeting screen to show when an Account first connects. If more than one string variable is -present in the module a random one will be picked. -- `inlinefuncs.py` - this is where you can define custom [Inline functions](./TextTags.md#inline-functions). -- `inputfuncs.py` - this is where you define custom [Input functions](./Inputfuncs.md) to handle data -from the client. -- `lockfuncs.py` - this is one of many possible modules to hold your own "safe" *lock functions* to -make available to Evennia's [Locks](./Locks.md). -- `mssp.py` - this holds meta information about your game. It is used by MUD search engines (which -you often have to register with) in order to display what kind of game you are running along with - statistics such as number of online accounts and online status. -- `oobfuncs.py` - in here you can define custom [OOB functions](./OOB.md). -- `portal_services_plugin.py` - this allows for adding your own custom services/protocols to the -Portal. It must define one particular function that will be called by Evennia at startup. There can -be any number of service plugin modules, all will be imported and used if defined. More info can be -found [here](http://code.google.com/p/evennia/wiki/SessionProtocols#Adding_custom_Protocols). -- `server_services_plugin.py` - this is equivalent to the previous one, but used for adding new -services to the Server instead. More info can be found -[here](http://code.google.com/p/evennia/wiki/SessionProtocols#Adding_custom_Protocols). - -Some other Evennia systems can be customized by plugin modules but has no explicit template in -`conf/`: - -- *cmdparser.py* - a custom module can be used to totally replace Evennia's default command parser. -All this does is to split the incoming string into "command name" and "the rest". It also handles -things like error messages for no-matches and multiple-matches among other things that makes this -more complex than it sounds. The default parser is *very* generic, so you are most often best served -by modifying things further down the line (on the command parse level) than here. -- *at_search.py* - this allows for replacing the way Evennia handles search results. It allows to -change how errors are echoed and how multi-matches are resolved and reported (like how the default -understands that "2-ball" should match the second "ball" object if there are two of them in the -room). - -## ServerConf - -There is a special database model called `ServerConf` that stores server internal data and settings -such as current account count (for interfacing with the webserver), startup status and many other -things. It's rarely of use outside the server core itself but may be good to -know about if you are an Evennia developer. diff --git a/docs/0.9.5/_sources/Sessions.md.txt b/docs/0.9.5/_sources/Sessions.md.txt deleted file mode 100644 index aac20214a6..0000000000 --- a/docs/0.9.5/_sources/Sessions.md.txt +++ /dev/null @@ -1,188 +0,0 @@ -# Sessions - - -An Evennia *Session* represents one single established connection to the server. Depending on the -Evennia session, it is possible for a person to connect multiple times, for example using different -clients in multiple windows. Each such connection is represented by a session object. - -A session object has its own [cmdset](./Command-Sets.md), usually the "unloggedin" cmdset. This is what -is used to show the login screen and to handle commands to create a new account (or -[Account](./Accounts.md) in evennia lingo) read initial help and to log into the game with an existing -account. A session object can either be "logged in" or not. Logged in means that the user has -authenticated. When this happens the session is associated with an Account object (which is what -holds account-centric stuff). The account can then in turn puppet any number of objects/characters. - -> Warning: A Session is not *persistent* - it is not a [Typeclass](./Typeclasses.md) and has no -connection to the database. The Session will go away when a user disconnects and you will lose any -custom data on it if the server reloads. The `.db` handler on Sessions is there to present a uniform -API (so you can assume `.db` exists even if you don't know if you receive an Object or a Session), -but this is just an alias to `.ndb`. So don't store any data on Sessions that you can't afford to -lose in a reload. You have been warned. - -## Properties on Sessions - -Here are some important properties available on (Server-)Sessions - -- `sessid` - The unique session-id. This is an integer starting from 1. -- `address` - The connected client's address. Different protocols give different information here. -- `logged_in` - `True` if the user authenticated to this session. -- `account` - The [Account](./Accounts.md) this Session is attached to. If not logged in yet, this is -`None`. -- `puppet` - The [Character/Object](./Objects.md) currently puppeted by this Account/Session combo. If -not logged in or in OOC mode, this is `None`. -- `ndb` - The [Non-persistent Attribute](./Attributes.md) handler. -- `db` - As noted above, Sessions don't have regular Attributes. This is an alias to `ndb`. -- `cmdset` - The Session's [CmdSetHandler](./Command-Sets.md) - -Session statistics are mainly used internally by Evennia. - -- `conn_time` - How long this Session has been connected -- `cmd_last` - Last active time stamp. This will be reset by sending `idle` keepalives. -- `cmd_last_visible` - last active time stamp. This ignores `idle` keepalives and representes the -last time this session was truly visibly active. -- `cmd_total` - Total number of Commands passed through this Session. - - -## Multisession mode - -The number of sessions possible to connect to a given account at the same time and how it works is -given by the `MULTISESSION_MODE` setting: - -* `MULTISESSION_MODE=0`: One session per account. When connecting with a new session the old one is -disconnected. This is the default mode and emulates many classic mud code bases. In default Evennia, -this mode also changes how the `create account` Command works - it will automatically create a -Character with the *same name* as the Account. When logging in, the login command is also modified -to have the player automatically puppet that Character. This makes the distinction between Account -and Character minimal from the player's perspective. -* `MULTISESSION_MODE=1`: Many sessions per account, input/output from/to each session is treated the -same. For the player this means they can connect to the game from multiple clients and see the same -output in all of them. The result of a command given in one client (that is, through one Session) -will be returned to *all* connected Sessions/clients with no distinction. This mode will have the -Session(s) auto-create and puppet a Character in the same way as mode 0. -* `MULTISESSION_MODE=2`: Many sessions per account, one character per session. In this mode, -puppeting an Object/Character will link the puppet back only to the particular Session doing the -puppeting. That is, input from that Session will make use of the CmdSet of that Object/Character and -outgoing messages (such as the result of a `look`) will be passed back only to that puppeting -Session. If another Session tries to puppet the same Character, the old Session will automatically -un-puppet it. From the player's perspective, this will mean that they can open separate game clients -and play a different Character in each using one game account. -This mode will *not* auto-create a Character and *not* auto-puppet on login like in modes 0 and 1. -Instead it changes how the account-cmdsets's `OOCLook` command works so as to show a simple -'character select' menu. -* `MULTISESSION_MODE=3`: Many sessions per account *and* character. This is the full multi-puppeting -mode, where multiple sessions may not only connect to the player account but multiple sessions may -also puppet a single character at the same time. From the user's perspective it means one can open -multiple client windows, some for controlling different Characters and some that share a Character's -input/output like in mode 1. This mode otherwise works the same as mode 2. - -> Note that even if multiple Sessions puppet one Character, there is only ever one instance of that -Character. - -## Returning data to the session - -When you use `msg()` to return data to a user, the object on which you call the `msg()` matters. The -`MULTISESSION_MODE` also matters, especially if greater than 1. - -For example, if you use `account.msg("hello")` there is no way for evennia to know which session it -should send the greeting to. In this case it will send it to all sessions. If you want a specific -session you need to supply its session to the `msg` call (`account.msg("hello", -session=mysession)`). - -On the other hand, if you call the `msg()` message on a puppeted object, like -`character.msg("hello")`, the character already knows the session that controls it - it will -cleverly auto-add this for you (you can specify a different session if you specifically want to send -stuff to another session). - -Finally, there is a wrapper for `msg()` on all command classes: `command.msg()`. This will -transparently detect which session was triggering the command (if any) and redirects to that session -(this is most often what you want). If you are having trouble redirecting to a given session, -`command.msg()` is often the safest bet. - -You can get the `session` in two main ways: -* [Accounts](./Accounts.md) and [Objects](./Objects.md) (including Characters) have a `sessions` property. -This is a *handler* that tracks all Sessions attached to or puppeting them. Use e.g. -`accounts.sessions.get()` to get a list of Sessions attached to that entity. -* A Command instance has a `session` property that always points back to the Session that triggered -it (it's always a single one). It will be `None` if no session is involved, like when a mob or -script triggers the Command. - -## Customizing the Session object - -When would one want to customize the Session object? Consider for example a character creation -system: You might decide to keep this on the out-of-character level. This would mean that you create -the character at the end of some sort of menu choice. The actual char-create cmdset would then -normally be put on the account. This works fine as long as you are `MULTISESSION_MODE` below 2. -For higher modes, replacing the Account cmdset will affect *all* your connected sessions, also those -not involved in character creation. In this case you want to instead put the char-create cmdset on -the Session level - then all other sessions will keep working normally despite you creating a new -character in one of them. - -By default, the session object gets the `commands.default_cmdsets.UnloggedinCmdSet` when the user -first connects. Once the session is authenticated it has *no* default sets. To add a "logged-in" -cmdset to the Session, give the path to the cmdset class with `settings.CMDSET_SESSION`. This set -will then henceforth always be present as soon as the account logs in. - -To customize further you can completely override the Session with your own subclass. To replace the -default Session class, change `settings.SERVER_SESSION_CLASS` to point to your custom class. This is -a dangerous practice and errors can easily make your game unplayable. Make sure to take heed of the -[original](https://github.com/evennia/evennia/blob/master/evennia/server/session.py) and make your -changes carefully. - -## Portal and Server Sessions - -*Note: This is considered an advanced topic. You don't need to know this on a first read-through.* - -Evennia is split into two parts, the [Portal and the Server](./Portal-And-Server.md). Each side tracks -its own Sessions, syncing them to each other. - -The "Session" we normally refer to is actually the `ServerSession`. Its counter-part on the Portal -side is the `PortalSession`. Whereas the server sessions deal with game states, the portal session -deals with details of the connection-protocol itself. The two are also acting as backups of critical -data such as when the server reboots. - -New Account connections are listened for and handled by the Portal using the [protocols](Portal-And- -Server) it understands (such as telnet, ssh, webclient etc). When a new connection is established, a -`PortalSession` is created on the Portal side. This session object looks different depending on -which protocol is used to connect, but all still have a minimum set of attributes that are generic -to all -sessions. - -These common properties are piped from the Portal, through the AMP connection, to the Server, which -is now informed a new connection has been established. On the Server side, a `ServerSession` object -is created to represent this. There is only one type of `ServerSession`; It looks the same -regardless of how the Account connects. - -From now on, there is a one-to-one match between the `ServerSession` on one side of the AMP -connection and the `PortalSession` on the other. Data arriving to the Portal Session is sent on to -its mirror Server session and vice versa. - -During certain situations, the portal- and server-side sessions are -"synced" with each other: -- The Player closes their client, killing the Portal Session. The Portal syncs with the Server to -make sure the corresponding Server Session is also deleted. -- The Player quits from inside the game, killing the Server Session. The Server then syncs with the -Portal to make sure to close the Portal connection cleanly. -- The Server is rebooted/reset/shutdown - The Server Sessions are copied over ("saved") to the -Portal side. When the Server comes back up, this data is returned by the Portal so the two are again -in sync. This way an Account's login status and other connection-critical things can survive a -server reboot (assuming the Portal is not stopped at the same time, obviously). - -## Sessionhandlers - -Both the Portal and Server each have a *sessionhandler* to manage the connections. These handlers -are global entities contain all methods for relaying data across the AMP bridge. All types of -Sessions hold a reference to their respective Sessionhandler (the property is called -`sessionhandler`) so they can relay data. See [protocols](./Custom-Protocols.md) for more info -on building new protocols. - -To get all Sessions in the game (i.e. all currently connected clients), you access the server-side -Session handler, which you get by -``` -from evennia.server.sessionhandler import SESSION_HANDLER -``` -> Note: The `SESSION_HANDLER` singleton has an older alias `SESSIONS` that is commonly seen in -various places as well. - -See the -[sessionhandler.py](https://github.com/evennia/evennia/blob/master/evennia/server/sessionhandler.py) -module for details on the capabilities of the `ServerSessionHandler`. diff --git a/docs/0.9.5/_sources/Setting-up-PyCharm.md.txt b/docs/0.9.5/_sources/Setting-up-PyCharm.md.txt deleted file mode 100644 index cacc71b6be..0000000000 --- a/docs/0.9.5/_sources/Setting-up-PyCharm.md.txt +++ /dev/null @@ -1,116 +0,0 @@ -# Setting up PyCharm - -# Directions for 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. - -> This page was originally tested on Windows (so use Windows-style path examples), but should work -the same for all platforms. - -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. - -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: -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" - -## Setting up the project interpreter - -It's a good idea to do this before attempting anything further. The rest of this page assumes your -project is already configured in PyCharm. - -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 - -Enjoy seeing all your imports checked properly, setting breakpoints, and live variable watching! - -## Attaching PyCharm debugger to Evennia - -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`) - -Of course 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) - -## Setting up an Evennia run configuration - -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. - -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). - -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). - -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. - -## Alternative run configuration - 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 diff --git a/docs/0.9.5/_sources/Signals.md.txt b/docs/0.9.5/_sources/Signals.md.txt deleted file mode 100644 index 5e172b09ff..0000000000 --- a/docs/0.9.5/_sources/Signals.md.txt +++ /dev/null @@ -1,122 +0,0 @@ -# Signals - - -_This is feature available from evennia 0.9 and onward_. - -There are multiple ways for you to plug in your own functionality into Evennia. -The most common way to do so is through *hooks* - methods on typeclasses that -gets called at particular events. Hooks are great when you want a game entity -to behave a certain way when something happens to it. _Signals_ complements -hooks for cases when you want to easily attach new functionality without -overriding things on the typeclass. - -When certain events happen in Evennia, a _Signal_ is fired. The idea is that -you can "attach" any number of event-handlers to these signals. You can attach -any number of handlers and they'll all fire whenever any entity triggers the -signal. - -Evennia uses the [Django Signal system](https://docs.djangoproject.com/en/2.2/topics/signals/). - - -## Attaching a handler to a signal - -First you create your handler - -```python - -def myhandler(sender, **kwargs): - # do stuff - -``` - -The `**kwargs` is mandatory. Then you attach it to the signal of your choice: - -```python -from evennia.server import signals - -signals.SIGNAL_OBJECT_POST_CREATE.connect(myhandler) - -``` - -This particular signal fires after (post) an Account has connected to the game. -When that happens, `myhandler` will fire with the `sender` being the Account that just connected. - -If you want to respond only to the effects of a specific entity you can do so -like this: - -```python -from evennia import search_account -from evennia import signals - -account = search_account("foo")[0] -signals.SIGNAL_ACCOUNT_POST_CONNECT.connect(myhandler, account) -``` - -## Available signals - -All signals (including some django-specific defaults) are available in the module -`evennia.server.signals` -(with a shortcut `evennia.signals`). Signals are named by the sender type. So `SIGNAL_ACCOUNT_*` -returns -`Account` instances as senders, `SIGNAL_OBJECT_*` returns `Object`s etc. Extra keywords (kwargs) -should -be extracted from the `**kwargs` dict in the signal handler. - -- `SIGNAL_ACCOUNT_POST_CREATE` - this is triggered at the very end of `Account.create()`. Note that - calling `evennia.create.create_account` (which is called internally by `Account.create`) will -*not* - trigger this signal. This is because using `Account.create()` is expected to be the most commonly - used way for users to themselves create accounts during login. It passes and extra kwarg `ip` with - the client IP of the connecting account. -- `SIGNAL_ACCOUNT_POST_LOGIN` - this will always fire when the account has authenticated. Sends - extra kwarg `session` with the new [Session](./Sessions.md) object involved. -- `SIGNAL_ACCCOUNT_POST_FIRST_LOGIN` - this fires just before `SIGNAL_ACCOUNT_POST_LOGIN` but only -if - this is the *first* connection done (that is, if there are no previous sessions connected). Also - passes the `session` along as a kwarg. -- `SIGNAL_ACCOUNT_POST_LOGIN_FAIL` - sent when someone tried to log into an account by failed. -Passes - the `session` as an extra kwarg. -- `SIGNAL_ACCOUNT_POST_LOGOUT` - always fires when an account logs off, no matter if other sessions - remain or not. Passes the disconnecting `session` along as a kwarg. -- `SIGNAL_ACCOUNT_POST_LAST_LOGOUT` - fires before `SIGNAL_ACCOUNT_POST_LOGOUT`, but only if this is - the *last* Session to disconnect for that account. Passes the `session` as a kwarg. -- `SIGNAL_OBJECT_POST_PUPPET` - fires when an account puppets this object. Extra kwargs `session` - and `account` represent the puppeting entities. - `SIGNAL_OBJECT_POST_UNPUPPET` - fires when the sending object is unpuppeted. Extra kwargs are - `session` and `account`. -- `SIGNAL_ACCOUNT_POST_RENAME` - triggered by the setting of `Account.username`. Passes extra - kwargs `old_name`, `new_name`. -- `SIGNAL_TYPED_OBJECT_POST_RENAME` - triggered when any Typeclassed entity's `key` is changed. -Extra - kwargs passed are `old_key` and `new_key`. -- `SIGNAL_SCRIPT_POST_CREATE` - fires when a script is first created, after any hooks. -- `SIGNAL_CHANNEL_POST_CREATE` - fires when a Channel is first created, after any hooks. -- `SIGNAL_HELPENTRY_POST_CREATE` - fires when a help entry is first created. - -The `evennia.signals` module also gives you conveneient access to the default Django signals (these -use a -different naming convention). - -- `pre_save` - fired when any database entitiy's `.save` method fires, before any saving has -happened. -- `post_save` - fires after saving a database entity. -- `pre_delete` - fires just before a database entity is deleted. -- `post_delete` - fires after a database entity was deleted. -- `pre_init` - fires before a typeclass' `__init__` method (which in turn - happens before the `at_init` hook fires). -- `post_init` - triggers at the end of `__init__` (still before the `at_init` hook). - -These are highly specialized Django signals that are unlikely to be useful to most users. But -they are included here for completeness. - -- `m2m_changed` - fires after a Many-to-Many field (like `db_attributes`) changes. -- `pre_migrate` - fires before database migration starts with `evennia migrate`. -- `post_migrate` - fires after database migration finished. -- `request_started` - sent when HTTP request begins. -- `request_finished` - sent when HTTP request ends. -- `settings_changed` - sent when changing settings due to `@override_settings` - decorator (only relevant for unit testing) -- `template_rendered` - sent when test system renders http template (only useful for unit tests). -- `connection_creation` - sent when making initial connection to database. - diff --git a/docs/0.9.5/_sources/Soft-Code.md.txt b/docs/0.9.5/_sources/Soft-Code.md.txt deleted file mode 100644 index 3f49bdb99c..0000000000 --- a/docs/0.9.5/_sources/Soft-Code.md.txt +++ /dev/null @@ -1,94 +0,0 @@ -# Soft Code - - -Softcode is a very simple programming language that was created for in-game development on TinyMUD -derivatives such as MUX, PennMUSH, TinyMUSH, and RhostMUSH. The idea is that by providing a stripped -down, minimalistic language for in-game use, you can allow quick and easy building and game -development to happen without having to learn C/C++. There is an added benefit of not having to have -to hand out shell access to all developers, and permissions can be used to alleviate many security -problems. - -Writing and installing softcode is done through a MUD client. Thus it is not a formatted language. -Each softcode function is a single line of varying size. Some functions can be a half of a page long -or more which is obviously not very readable nor (easily) maintainable over time. - -## Examples of Softcode - -Here is a simple 'Hello World!' command: - -```bash - @set me=HELLO_WORLD.C:$hello:@pemit %#=Hello World! -``` - -Pasting this into a MUX/MUSH and typing 'hello' will theoretically yield 'Hello World!', assuming -certain flags are not set on your account object. - -Setting attributes is done via `@set`. Softcode also allows the use of the ampersand (`&`) symbol. -This shorter version looks like this: - -```bash - &HELLO_WORLD.C me=$hello:@pemit %#=Hello World! -``` - -Perhaps I want to break the Hello World into an attribute which is retrieved when emitting: - -```bash - &HELLO_VALUE.D me=Hello World - &HELLO_WORLD.C me=$hello:@pemit %#=[v(HELLO_VALUE.D)] -``` - -The `v()` function returns the `HELLO_VALUE.D` attribute on the object that the command resides -(`me`, which is yourself in this case). This should yield the same output as the first example. - -If you are still curious about how Softcode works, take a look at some external resources: - -- http://www.tinymux.com/wiki/index.php/Softcode -- http://www.duh.com/discordia/mushman/man2x1 - -## Problems with Softcode - -Softcode is excellent at what it was intended for: *simple things*. It is a great tool for making an -interactive object, a room with ambiance, simple global commands, simple economies and coded -systems. However, once you start to try to write something like a complex combat system or a higher -end economy, you're likely to find yourself buried under a mountain of functions that span multiple -objects across your entire code. - -Not to mention, softcode is not an inherently fast language. It is not compiled, it is parsed with -each calling of a function. While MUX and MUSH parsers have jumped light years ahead of where they -once were they can still stutter under the weight of more complex systems if not designed properly. - -## Changing Times - -Now that starting text-based games is easy and an option for even the most technically inarticulate, -new projects are a dime a dozen. People are starting new MUDs every day with varying levels of -commitment and ability. Because of this shift from fewer, larger, well-staffed games to a bunch of -small, one or two developer games, some of the benefit of softcode fades. - -Softcode is great in that it allows a mid to large sized staff all work on the same game without -stepping on one another's toes. As mentioned before, shell access is not necessary to develop a MUX -or a MUSH. However, now that we are seeing a lot more small, one or two-man shops, the issue of -shell access and stepping on each other's toes is a lot less. - -## Our Solution - -Evennia shuns in-game softcode for on-disk Python modules. Python is a popular, mature and -professional programming language. You code it using the conveniences of modern text editors. -Evennia developers have access to the entire library of Python modules out there in the wild - not -to mention the vast online help resources available. Python code is not bound to one-line functions -on objects but complex systems may be organized neatly into real source code modules, sub-modules, -or even broken out into entire Python packages as desired. - -So what is *not* included in Evennia is a MUX/MOO-like online player-coding system. Advanced coding -in Evennia is primarily intended to be done outside the game, in full-fledged Python modules. -Advanced building is best handled by extending Evennia's command system with your own sophisticated -building commands. We feel that with a small development team you are better off using a -professional source-control system (svn, git, bazaar, mercurial etc) anyway. - -## Your Solution - -Adding advanced and flexible building commands to your game is easy and will probably be enough to -satisfy most creative builders. However, if you really, *really* want to offer online coding, there -is of course nothing stopping you from adding that to Evennia, no matter our recommendations. You -could even re-implement MUX' softcode in Python should you be very ambitious. The -[in-game-python](./Dialogues-in-events.md) is an optional -pseudo-softcode plugin aimed at developers wanting to script their game from inside it. diff --git a/docs/0.9.5/_sources/Spawner-and-Prototypes.md.txt b/docs/0.9.5/_sources/Spawner-and-Prototypes.md.txt deleted file mode 100644 index caaf12a192..0000000000 --- a/docs/0.9.5/_sources/Spawner-and-Prototypes.md.txt +++ /dev/null @@ -1,332 +0,0 @@ -# Spawner and Prototypes - - -The *spawner* is a system for defining and creating individual objects from a base template called a -*prototype*. It is only designed for use with in-game [Objects](./Objects.md), not any other type of -entity. - -The normal way to create a custom object in Evennia is to make a [Typeclass](./Typeclasses.md). If you -haven't read up on Typeclasses yet, think of them as normal Python classes that save to the database -behind the scenes. Say you wanted to create a "Goblin" enemy. A common way to do this would be to -first create a `Mobile` typeclass that holds everything common to mobiles in the game, like generic -AI, combat code and various movement methods. A `Goblin` subclass is then made to inherit from -`Mobile`. The `Goblin` class adds stuff unique to goblins, like group-based AI (because goblins are -smarter in a group), the ability to panic, dig for gold etc. - -But now it's time to actually start to create some goblins and put them in the world. What if we -wanted those goblins to not all look the same? Maybe we want grey-skinned and green-skinned goblins -or some goblins that can cast spells or which wield different weapons? We *could* make subclasses of -`Goblin`, like `GreySkinnedGoblin` and `GoblinWieldingClub`. But that seems a bit excessive (and a -lot of Python code for every little thing). Using classes can also become impractical when wanting -to combine them - what if we want a grey-skinned goblin shaman wielding a spear - setting up a web -of classes inheriting each other with multiple inheritance can be tricky. - -This is what the *prototype* is for. It is a Python dictionary that describes these per-instance -changes to an object. The prototype also has the advantage of allowing an in-game builder to -customize an object without access to the Python backend. Evennia also allows for saving and -searching prototypes so other builders can find and use (and tweak) them later. Having a library of -interesting prototypes is a good reasource for builders. The OLC system allows for creating, saving, -loading and manipulating prototypes using a menu system. - -The *spawner* takes a prototype and uses it to create (spawn) new, custom objects. - -## Using the OLC - -Enter the `olc` command or `@spawn/olc` to enter the prototype wizard. This is a menu system for -creating, loading, saving and manipulating prototypes. It's intended to be used by in-game builders -and will give a better understanding of prototypes in general. Use `help` on each node of the menu -for more information. Below are further details about how prototypes work and how they are used. - -## The prototype - -The prototype dictionary can either be created for you by the OLC (see above), be written manually -in a Python module (and then referenced by the `@spawn` command/OLC), or created on-the-fly and -manually loaded into the spawner function or `@spawn` command. - -The dictionary defines all possible database-properties of an Object. It has a fixed set of allowed -keys. When preparing to store the prototype in the database (or when using the OLC), some -of these keys are mandatory. When just passing a one-time prototype-dict to the spawner the system -is -more lenient and will use defaults for keys not explicitly provided. - -In dictionary form, a prototype can look something like this: - -```python -{ - "prototype_key": "house" - "key": "Large house" - "typeclass": "typeclasses.rooms.house.House" - } -``` -If you wanted to load it into the spawner in-game you could just put all on one line: - - @spawn {"prototype_key="house", "key": "Large house", ...} - -> Note that the prototype dict as given on the command line must be a valid Python structure - -so you need to put quotes around strings etc. For security reasons, a dict inserted from-in game -cannot have any -other advanced Python functionality, such as executable code, `lambda` etc. If builders are supposed -to be able to use such features, you need to offer them through [$protfuncs](Spawner-and- -Prototypes#protfuncs), embedded runnable functions that you have full control to check and vet -before running. - -### Prototype keys - -All keys starting with `prototype_` are for book keeping. - - - `prototype_key` - the 'name' of the prototype. While this can sometimes be skipped (such as when - defining a prototype in a module or feeding a prototype-dict manually to the spawner function), -it's good - practice to try to include this. It is used for book-keeping and storing of the prototype so you - can find it later. - - `prototype_parent` - If given, this should be the `prototype_key` of another prototype stored in - the system or available in a module. This makes this prototype *inherit* the keys from the - parent and only override what is needed. Give a tuple `(parent1, parent2, ...)` for multiple - left-right inheritance. If this is not given, a `typeclass` should usually be defined (below). - - `prototype_desc` - this is optional and used when listing the prototype in in-game listings. - - `protototype_tags` - this is optional and allows for tagging the prototype in order to find it - easier later. - - `prototype_locks` - two lock types are supported: `edit` and `spawn`. The first lock restricts - the copying and editing of the prototype when loaded through the OLC. The second determines who - may use the prototype to create new objects. - -The remaining keys determine actual aspects of the objects to spawn from this prototype: - - - `key` - the main object identifier. Defaults to "Spawned Object *X*", where *X* is a random -integer. - - `typeclass` - A full python-path (from your gamedir) to the typeclass you want to use. If not -set, the `prototype_parent` should be - defined, with `typeclass` defined somewhere in the parent chain. When creating a one-time -prototype - dict just for spawning, one could omit this - `settings.BASE_OBJECT_TYPECLASS` will be used -instead. - - `location` - this should be a `#dbref`. - - `home` - a valid `#dbref`. Defaults to `location` or `settings.DEFAULT_HOME` if location does not -exist. - - `destination` - a valid `#dbref`. Only used by exits. - - `permissions` - list of permission strings, like `["Accounts", "may_use_red_door"]` - - `locks` - a [lock-string](./Locks.md) like `"edit:all();control:perm(Builder)"` - - `aliases` - list of strings for use as aliases - - `tags` - list [Tags](./Tags.md). These are given as tuples `(tag, category, data)`. - - `attrs` - list of [Attributes](./Attributes.md). These are given as tuples `(attrname, value, -category, lockstring)` - - Any other keywords are interpreted as non-category [Attributes](./Attributes.md) and their values. -This is - convenient for simple Attributes - use `attrs` for full control of Attributes. - -Deprecated as of Evennia 0.8: - - - `ndb_` - sets the value of a non-persistent attribute (`"ndb_"` is stripped from the name). - This is simply not useful in a prototype and is deprecated. - - `exec` - This accepts a code snippet or a list of code snippets to run. This should not be used - - use callables or [$protfuncs](./Spawner-and-Prototypes.md#protfuncs) instead (see below). - -### Prototype values - -The prototype supports values of several different types. - -It can be a hard-coded value: - -```python - {"key": "An ugly goblin", ...} - -``` - -It can also be a *callable*. This callable is called without arguments whenever the prototype is -used to -spawn a new object: - -```python - {"key": _get_a_random_goblin_name, ...} - -``` - -By use of Python `lambda` one can wrap the callable so as to make immediate settings in the -prototype: - -```python - {"key": lambda: random.choice(("Urfgar", "Rick the smelly", "Blargh the foul", ...)), ...} - -``` - -#### Protfuncs - -Finally, the value can be a *prototype function* (*Protfunc*). These look like simple function calls -that you embed in strings and that has a `$` in front, like - -```python - {"key": "$choice(Urfgar, Rick the smelly, Blargh the foul)", - "attrs": {"desc": "This is a large $red(and very red) demon. " - "He has $randint(2,5) skulls in a chain around his neck."} -``` -At execution time, the place of the protfunc will be replaced with the result of that protfunc being -called (this is always a string). A protfunc works in much the same way as an -[InlineFunc](./TextTags.md#inline-functions) - they are actually -parsed using the same parser - except protfuncs are run every time the prototype is used to spawn a -new object (whereas an inlinefunc is called when a text is returned to the user). - -Here is how a protfunc is defined (same as an inlinefunc). - -```python -# this is a silly example, you can just color the text red with |r directly! -def red(*args, **kwargs): - """ - Usage: $red() - Returns the same text you entered, but red. - """ - if not args or len(args) > 1: - raise ValueError("Must have one argument, the text to color red!") - return "|r{}|n".format(args[0]) -``` - -> Note that we must make sure to validate input and raise `ValueError` if that fails. Also, it is -*not* possible to use keywords in the call to the protfunc (so something like `$echo(text, -align=left)` is invalid). The `kwargs` requred is for internal evennia use and not used at all for -protfuncs (only by inlinefuncs). - -To make this protfunc available to builders in-game, add it to a new module and add the path to that -module to `settings.PROT_FUNC_MODULES`: - -```python -# in mygame/server/conf/settings.py - -PROT_FUNC_MODULES += ["world.myprotfuncs"] - -``` -All *global callables* in your added module will be considered a new protfunc. To avoid this (e.g. -to have helper functions that are not protfuncs on their own), name your function something starting -with `_`. - -The default protfuncs available out of the box are defined in `evennia/prototypes/profuncs.py`. To -override the ones available, just add the same-named function in your own protfunc module. - -| Protfunc | Description | - -| `$random()` | Returns random value in range [0, 1) | -| `$randint(start, end)` | Returns random value in range [start, end] | -| `$left_justify()` | Left-justify text | -| `$right_justify()` | Right-justify text to screen width | -| `$center_justify()` | Center-justify text to screen width | -| `$full_justify()` | Spread text across screen width by adding spaces | -| `$protkey()` | Returns value of another key in this prototype (self-reference) | -| `$add(, )` | Returns value1 + value2. Can also be lists, dicts etc | -| `$sub(, )` | Returns value1 - value2 | -| `$mult(, )` | Returns value1 * value2 | -| `$div(, )` | Returns value2 / value1 | -| `$toint()` | Returns value converted to integer (or value if not possible) | -| `$eval()` | Returns result of [literal- -eval](https://docs.python.org/2/library/ast.html#ast.literal_eval) of code string. Only simple -python expressions. | -| `$obj()` | Returns object #dbref searched globally by key, tag or #dbref. Error if more -than one found." | -| `$objlist()` | Like `$obj`, except always returns a list of zero, one or more results. | -| `$dbref(dbref)` | Returns argument if it is formed as a #dbref (e.g. #1234), otherwise error. - -For developers with access to Python, using protfuncs in prototypes is generally not useful. Passing -real Python functions is a lot more powerful and flexible. Their main use is to allow in-game -builders to -do limited coding/scripting for their prototypes without giving them direct access to raw Python. - -## Storing prototypes - -A prototype can be defined and stored in two ways, either in the database or as a dict in a module. - -### Database prototypes - -Stored as [Scripts](./Scripts.md) in the database. These are sometimes referred to as *database- -prototypes* This is the only way for in-game builders to modify and add prototypes. They have the -advantage of being easily modifiable and sharable between builders but you need to work with them -using in-game tools. - -### Module-based prototypes - -These prototypes are defined as dictionaries assigned to global variables in one of the modules -defined in `settings.PROTOTYPE_MODULES`. They can only be modified from outside the game so they are -are necessarily "read-only" from in-game and cannot be modified (but copies of them could be made -into database-prototypes). These were the only prototypes available before Evennia 0.8. Module based -prototypes can be useful in order for developers to provide read-only "starting" or "base" -prototypes to build from or if they just prefer to work offline in an external code editor. - -By default `mygame/world/prototypes.py` is set up for you to add your own prototypes. *All global -dicts* in this module will be considered by Evennia to be a prototype. You could also tell Evennia -to look for prototypes in more modules if you want: - -```python -# in mygame/server/conf/settings.py - -PROTOTYPE_MODULES += ["world.myownprototypes", "combat.prototypes"] - -``` -> Note the += operator in the above example. This will extend the already defined `world.prototypes` -definition in the settings_default.py file in Evennia. If you would like to completely override the -location of your `PROTOTYPE_MODULES` then set this to just = without the addition operator. - -Here is an example of a prototype defined in a module: - -```python - # in a module Evennia looks at for prototypes, - # (like mygame/world/prototypes.py) - - ORC_SHAMAN = {"key":"Orc shaman", - "typeclass": "typeclasses.monsters.Orc", - "weapon": "wooden staff", - "health": 20} -``` - -> Note that in the example above, `"ORC_SHAMAN"` will become the `prototype_key` of this prototype. -> It's the only case when `prototype_key` can be skipped in a prototype. However, if `prototype_key` -> was given explicitly, that would take precedence. This is a legacy behavior and it's recommended -> that you always add `prototype_key` to be consistent. - - -## Using @spawn - -The spawner can be used from inside the game through the Builder-only `@spawn` command. Assuming the -"goblin" typeclass is available to the system (either as a database-prototype or read from module), -you can spawn a new goblin with - - @spawn goblin - -You can also specify the prototype directly as a valid Python dictionary: - - @spawn {"prototype_key": "shaman", \ - "key":"Orc shaman", \ - "prototype_parent": "goblin", \ - "weapon": "wooden staff", \ - "health": 20} - -> Note: The `@spawn` command is more lenient about the prototype dictionary than shown here. So you -can for example skip the `prototype_key` if you are just testing a throw-away prototype. A random -hash will be used to please the validation. You could also skip `prototype_parent/typeclass` - then -the typeclass given by `settings.BASE_OBJECT_TYPECLASS` will be used. - -## Using evennia.prototypes.spawner() - -In code you access the spawner mechanism directly via the call - -```python - new_objects = evennia.prototypes.spawner.spawn(*prototypes) -``` - -All arguments are prototype dictionaries or the unique `prototype_key`s of prototypes -known to the system (either database- or module-based). The function will return a matching list of -created objects. Example: - -```python - obj1, obj2, obj3 = evennia.prototypes.spawner.spawn({"key": "Obj1", "desc": "A test"}, - {"key": "Obj2", "desc": "Another test"}, - "GOBLIN_SHAMAN") -``` -> Hint: Same as when using `@spawn`, when spawning from a one-time prototype dict like this, you can -skip otherwise required keys, like `prototype_key` or `typeclass`/`prototype_parent`. Defaults will -be used. - -Note that no `location` will be set automatically when using `evennia.prototypes.spawner.spawn()`, -you -have to specify `location` explicitly in the prototype dict. - -If the prototypes you supply are using `prototype_parent` keywords, the spawner will read prototypes -from modules -in `settings.PROTOTYPE_MODULES` as well as those saved to the database to determine the body of -available parents. The `spawn` command takes many optional keywords, you can find its definition [in -the api docs](github:evennia.prototypes.spawner#spawn). \ No newline at end of file diff --git a/docs/0.9.5/_sources/Start-Stop-Reload.md.txt b/docs/0.9.5/_sources/Start-Stop-Reload.md.txt deleted file mode 100644 index 36e2a827d9..0000000000 --- a/docs/0.9.5/_sources/Start-Stop-Reload.md.txt +++ /dev/null @@ -1,196 +0,0 @@ -# Start Stop Reload - - -You control Evennia from your game folder (we refer to it as `mygame/` here), using the `evennia` -program. If the `evennia` program is not available on the command line you must first install -Evennia as described in the [Getting Started](./Getting-Started.md) page. - -> Hint: If you ever try the `evennia` command and get an error complaining that the command is not -available, make sure your [virtualenv](./Glossary.md#virtualenv) is active. - -Below are described the various management options. Run - - evennia -h - -to give you a brief help and - - evennia menu - -to give you a menu with options. - -## Starting Evennia - -Evennia consists of two components, the Evennia [Server and Portal](./Portal-And-Server.md). Briefly, -the *Server* is what is running the mud. It handles all game-specific things but doesn't care -exactly how players connect, only that they have. The *Portal* is a gateway to which players -connect. It knows everything about telnet, ssh, webclient protocols etc but very little about the -game. Both are required for a functioning mud. - - evennia start - -The above command will start the Portal, which in turn will boot up the Server. The command will -print a summary of the process and unless there is an error you will see no further output. Both -components will instead log to log files in `mygame/server/logs/`. For convenience you can follow -those logs directly in your terminal by attaching `-l` to commands: - - evennia -l - -Will start following the logs of an already running server. When starting Evennia you can also do - - evennia start -l - -> To stop viewing the log files, press `Ctrl-C`. - -## Foreground mode - -Normally, Evennia runs as a 'daemon', in the background. If you want you can start either of the -processes (but not both) as foreground processes in *interactive* mode. This means they will log -directly to the terminal (rather than to log files that we then echo to the terminal) and you can -kill the process (not just the log-file view) with `Ctrl-C`. - - evennia istart - -will start/restart the *Server* in interactive mode. This is required if you want to run a -*debugger*. Next time you reload the server, it will return to normal mode. - - evennia ipstart - -will start the *Portal* in interactive mode. This is usually only necessary if you want to run -Evennia under the control of some other type of process. - -## Reloading - -The act of *reloading* means the Portal will tell the Server to shut down and then boot it back up -again. Everyone will get a message and the game will be briefly paused for all accounts as the -server -reboots. Since they are connected to the *Portal*, their connections are not lost. - - -Reloading is as close to a "warm reboot" you can get. It reinitializes all code of Evennia, but -doesn't kill "persistent" [Scripts](./Scripts.md). It also calls `at_server_reload()` hooks on all -objects so you -can save eventual temporary properties you want. - -From in-game the `@reload` command is used. You can also reload the server from outside the game: - - evennia reload - -Sometimes reloading from "the outside" is necessary in case you have added some sort of bug that -blocks in-game input. - -## Resetting - -*Resetting* is the equivalent of a "cold reboot" - the Server will shut down and then restarted -again, but will behave as if it was fully shut down. As opposed to a "real" shutdown, no accounts -will be disconnected during a -reset. A reset will however purge all non-persistent scripts and will call `at_server_shutdown()` -hooks. It can be a good way to clean unsafe scripts during development, for example. - -From in-game the `@reset` command is used. From the terminal: - - evennia reset - - -## Rebooting - -This will shut down *both* Server and Portal, which means all connected players will lose their -connection. It can only be initiated from the terminal: - - evennia reboot - -This is identical to doing these two commands: - - evennia stop - evennia start - - -## Shutting down - -A full shutdown closes Evennia completely, both Server and Portal. All accounts will be booted and -systems saved and turned off cleanly. - -From inside the game you initiate a shutdown with the `@shutdown` command. From command line you do - - evennia stop - -You will see messages of both Server and Portal closing down. All accounts will see the shutdown -message and then be disconnected. The same effect happens if you press `Ctrl+C` while the server -runs in interactive mode. - -## Status and info - -To check basic Evennia settings, such as which ports and services are active, this will repeat the -initial return given when starting the server: - - evennia info - -You can also get a briefer run-status from both components with this command - - evennia status - -This can be useful for automating checks to make sure the game is running and is responding. - - -## Killing (Linux/Mac only) - -In the extreme case that neither of the server processes locks up and does not respond to commands, -you can send them kill-signals to force them to shut down. To kill only the Server: - - evennia skill - -To kill both Server and Portal: - - evennia kill - -Note that this functionality is not supported on Windows. - - -## Django options - -The `evennia` program will also pass-through options used by the `django-admin`. These operate on -the database in various ways. - -```bash - - evennia migrate # migrate the database - evennia shell # launch an interactive, django-aware python shell - evennia dbshell # launch database shell - -``` - -For (many) more options, see [the django-admin -docs](https://docs.djangoproject.com/en/1.7/ref/django-admin/#usage). - -## Advanced handling of Evennia processes - -If you should need to manually manage Evennia's processors (or view them in a task manager program -such as Linux' `top` or the more advanced `htop`), you will find the following processes to be -related to Evennia: - -* 1 x `twistd ... evennia/server/portal/portal.py` - this is the Portal process. -* 3 x `twistd ... server.py` - One of these processes manages Evennia's Server component, the main - game. The other processes (with the same name but different process id) handle's Evennia's - internal web server threads. You can look at `mygame/server/server.pid` to determine which is the - main process. - -### Syntax errors during live development - -During development, you will usually modify code and then reload the server to see your changes. -This is done by Evennia re-importing your custom modules from disk. Usually bugs in a module will -just have you see a traceback in the game, in the log or on the command line. For some really -serious syntax errors though, your module might not even be recognized as valid Python. Evennia may -then fail to restart correctly. - -From inside the game you see a text about the Server restarting followed by an ever growing list of -"...". Usually this only lasts a very short time (up to a few seconds). If it seems to go on, it -means the Portal is still running (you are still connected to the game) but the Server-component of -Evennia failed to restart (that is, it remains in a shut-down state). Look at your log files or -terminal to see what the problem is - you will usually see a clear traceback showing what went -wrong. - -Fix your bug then run - - evennia start - -Assuming the bug was fixed, this will start the Server manually (while not restarting the Portal). -In-game you should now get the message that the Server has successfully restarted. diff --git a/docs/0.9.5/_sources/Static-In-Game-Map.md.txt b/docs/0.9.5/_sources/Static-In-Game-Map.md.txt deleted file mode 100644 index d40fea7ed6..0000000000 --- a/docs/0.9.5/_sources/Static-In-Game-Map.md.txt +++ /dev/null @@ -1,412 +0,0 @@ -# Static In Game Map - - -## Introduction - -This tutorial describes the creation of an in-game map display based on a pre-drawn map. It also -details how to use the [Batch code processor](./Batch-Code-Processor.md) for advanced building. There is -also the [Dynamic in-game map tutorial](./Dynamic-In-Game-Map.md) that works in the opposite direction, -by generating a map from an existing grid of rooms. - -Evennia does not require its rooms to be positioned in a "logical" way. Your exits could be named -anything. You could make an exit "west" that leads to a room described to be in the far north. You -could have rooms inside one another, exits leading back to the same room or describing spatial -geometries impossible in the real world. - -That said, most games *do* organize their rooms in a logical fashion, if nothing else to retain the -sanity of their players. And when they do, the game becomes possible to map. This tutorial will give -an example of a simple but flexible in-game map system to further help player's to navigate. We will - -To simplify development and error-checking we'll break down the work into bite-size chunks, each -building on what came before. For this we'll make extensive use of the [Batch code processor](./Batch-Code-Processor.md), so you may want to familiarize yourself with that. - -1. **Planning the map** - Here we'll come up with a small example map to use for the rest of the -tutorial. -2. **Making a map object** - This will showcase how to make a static in-game "map" object a -Character could pick up and look at. -3. **Building the map areas** - Here we'll actually create the small example area according to the -map we designed before. -4. **Map code** - This will link the map to the location so our output looks something like this: - - ``` - crossroads(#3) - ↑╚∞╝↑ - ≈↑│↑∩ The merger of two roads. To the north looms a mighty castle. - O─O─O To the south, the glow of a campfire can be seen. To the east lie - ≈↑│↑∩ the vast mountains and to the west is heard the waves of the sea. - ↑▲O▲↑ - - Exits: north(#8), east(#9), south(#10), west(#11) - ``` - -We will henceforth assume your game folder is name named `mygame` and that you haven't modified the -default commands. We will also not be using [Colors](./TextTags.md#coloured-text) for our map since they -don't show in the documentation wiki. - -## Planning the Map - -Let's begin with the fun part! Maps in MUDs come in many different [shapes and sizes](http://journal.imaginary-realities.com/volume-05/issue-01/modern-interface-modern-mud/index.html). Some appear as just boxes connected by lines. Others have complex graphics that are -external to the game itself. - -Our map will be in-game text but that doesn't mean we're restricted to the normal alphabet! If -you've ever selected the [Wingdings font](https://en.wikipedia.org/wiki/Wingdings) in Microsoft Word -you will know there are a multitude of other characters around to use. When creating your game with -Evennia you have access to the [UTF-8 character encoding](https://en.wikipedia.org/wiki/UTF-8) which -put at your disposal [thousands of letters, number and geometric shapes](http://mcdlr.com/utf-8/#1). - -For this exercise, we've copy-and-pasted from the pallet of special characters used over at [Dwarf -Fortress](http://dwarffortresswiki.org/index.php/Character_table) to create what is hopefully a -pleasing and easy to understood landscape: - -``` -≈≈↑↑↑↑↑∩∩ -≈≈↑╔═╗↑∩∩ Places the account can visit are indicated by "O". -≈≈↑║O║↑∩∩ Up the top is a castle visitable by the account. -≈≈↑╚∞╝↑∩∩ To the right is a cottage and to the left the beach. -≈≈≈↑│↑∩∩∩ And down the bottom is a camp site with tents. -≈≈O─O─O⌂∩ In the center is the starting location, a crossroads -≈≈≈↑│↑∩∩∩ which connect the four other areas. -≈≈↑▲O▲↑∩∩ -≈≈↑↑▲↑↑∩∩ -≈≈↑↑↑↑↑∩∩ -``` -There are many considerations when making a game map depending on the play style and requirements -you intend to implement. Here we will display a 5x5 character map of the area surrounding the -account. This means making sure to account for 2 characters around every visitable location. Good -planning at this stage can solve many problems before they happen. - -## Creating a Map Object - -In this section we will try to create an actual "map" object that an account can pick up and look -at. - -Evennia offers a range of [default commands](./Default-Commands.md) for [creating objects and rooms -in-game](./Building-Quickstart.md). While readily accessible, these commands are made to do very -specific, restricted things and will thus not offer as much flexibility to experiment (for an -advanced exception see [in-line functions](./TextTags.md#new-inlinefuncs)). Additionally, entering long -descriptions and properties over and over in the game client can become tedious; especially when -testing and you may want to delete and recreate things over and over. - -To overcome this, Evennia offers [batch processors](./Batch-Processors.md) that work as input-files -created out-of-game. In this tutorial we'll be using the more powerful of the two available batch -processors, the [Batch Code Processor ](./Batch-Code-Processor.md), called with the `@batchcode` command. -This is a very powerful tool. It allows you to craft Python files to act as blueprints of your -entire game world. These files have access to use Evennia's Python API directly. Batchcode allows -for easy editing and creation in whatever text editor you prefer, avoiding having to manually build -the world line-by-line inside the game. - -> Important warning: `@batchcode`'s power is only rivaled by the `@py` command. Batchcode is so -powerful it should be reserved only for the [superuser](./Building-Permissions.md). Think carefully -before you let others (such as `Developer`- level staff) run `@batchcode` on their own - make sure -you are okay with them running *arbitrary Python code* on your server. - -While a simple example, the map object it serves as good way to try out `@batchcode`. Go to -`mygame/world` and create a new file there named `batchcode_map.py`: - -```Python -# mygame/world/batchcode_map.py - -from evennia import create_object -from evennia import DefaultObject - -# We use the create_object function to call into existence a -# DefaultObject named "Map" wherever you are standing. - -map = create_object(DefaultObject, key="Map", location=caller.location) - -# We then access its description directly to make it our map. - -map.db.desc = """ -≈≈↑↑↑↑↑∩∩ -≈≈↑╔═╗↑∩∩ -≈≈↑║O║↑∩∩ -≈≈↑╚∞╝↑∩∩ -≈≈≈↑│↑∩∩∩ -≈≈O─O─O⌂∩ -≈≈≈↑│↑∩∩∩ -≈≈↑▲O▲↑∩∩ -≈≈↑↑▲↑↑∩∩ -≈≈↑↑↑↑↑∩∩ -""" - -# This message lets us know our map was created successfully. -caller.msg("A map appears out of thin air and falls to the ground.") -``` - -Log into your game project as the superuser and run the command - -``` -@batchcode batchcode_map -``` - -This will load your `batchcode_map.py` file and execute the code (Evennia will look in your `world/` -folder automatically so you don't need to specify it). - -A new map object should have appeared on the ground. You can view the map by using `look map`. Let's -take it with the `get map` command. We'll need it in case we get lost! - -## Building the map areas - -We've just used batchcode to create an object useful for our adventures. But the locations on that -map does not actually exist yet - we're all mapped up with nowhere to go! Let's use batchcode to -build a game area based on our map. We have five areas outlined: a castle, a cottage, a campsite, a -coastal beach and the crossroads which connects them. Create a new batchcode file for this in -`mygame/world`, named `batchcode_world.py`. - -```Python -# mygame/world/batchcode_world.py - -from evennia import create_object, search_object -from typeclasses import rooms, exits - -# We begin by creating our rooms so we can detail them later. - -centre = create_object(rooms.Room, key="crossroads") -north = create_object(rooms.Room, key="castle") -east = create_object(rooms.Room, key="cottage") -south = create_object(rooms.Room, key="camp") -west = create_object(rooms.Room, key="coast") - -# This is where we set up the cross roads. -# The rooms description is what we see with the 'look' command. - -centre.db.desc = """ -The merger of two roads. A single lamp post dimly illuminates the lonely crossroads. -To the north looms a mighty castle. To the south the glow of a campfire can be seen. -To the east lie a wall of mountains and to the west the dull roar of the open sea. -""" - -# Here we are creating exits from the centre "crossroads" location to -# destinations to the north, east, south, and west. We will be able -# to use the exit by typing it's key e.g. "north" or an alias e.g. "n". - -centre_north = create_object(exits.Exit, key="north", - aliases=["n"], location=centre, destination=north) -centre_east = create_object(exits.Exit, key="east", - aliases=["e"], location=centre, destination=east) -centre_south = create_object(exits.Exit, key="south", - aliases=["s"], location=centre, destination=south) -centre_west = create_object(exits.Exit, key="west", - aliases=["w"], location=centre, destination=west) - -# Now we repeat this for the other rooms we'll be implementing. -# This is where we set up the northern castle. - -north.db.desc = "An impressive castle surrounds you. " \ - "There might be a princess in one of these towers." -north_south = create_object(exits.Exit, key="south", - aliases=["s"], location=north, destination=centre) - -# This is where we set up the eastern cottage. - -east.db.desc = "A cosy cottage nestled among mountains " \ - "stretching east as far as the eye can see." -east_west = create_object(exits.Exit, key="west", - aliases=["w"], location=east, destination=centre) - -# This is where we set up the southern camp. - -south.db.desc = "Surrounding a clearing are a number of " \ - "tribal tents and at their centre a roaring fire." -south_north = create_object(exits.Exit, key="north", - aliases=["n"], location=south, destination=centre) - -# This is where we set up the western coast. - -west.db.desc = "The dark forest halts to a sandy beach. " \ - "The sound of crashing waves calms the soul." -west_east = create_object(exits.Exit, key="east", - aliases=["e"], location=west, destination=centre) - -# Lastly, lets make an entrance to our world from the default Limbo room. - -limbo = search_object('Limbo')[0] -limbo_exit = create_object(exits.Exit, key="enter world", - aliases=["enter"], location=limbo, destination=centre) - -``` - -Apply this new batch code with `@batchcode batchcode_world`. If there are no errors in the code we -now have a nice mini-world to explore. Remember that if you get lost you can look at the map we -created! - -## In-game minimap - -Now we have a landscape and matching map, but what we really want is a mini-map that displays -whenever we move to a room or use the `look` command. - -We *could* manually enter a part of the map into the description of every room like we did our map -object description. But some MUDs have tens of thousands of rooms! Besides, if we ever changed our -map we would have to potentially alter a lot of those room descriptions manually to match the -change. So instead we will make one central module to hold our map. Rooms will reference this -central location on creation and the map changes will thus come into effect when next running our -batchcode. - -To make our mini-map we need to be able to cut our full map into parts. To do this we need to put it -in a format which allows us to do that easily. Luckily, python allows us to treat strings as lists -of characters allowing us to pick out the characters we need. - -`mygame/world/map_module.py` -```Python -# We place our map into a sting here. -world_map = """\ -≈≈↑↑↑↑↑∩∩ -≈≈↑╔═╗↑∩∩ -≈≈↑║O║↑∩∩ -≈≈↑╚∞╝↑∩∩ -≈≈≈↑│↑∩∩∩ -≈≈O─O─O⌂∩ -≈≈≈↑│↑∩∩∩ -≈≈↑▲O▲↑∩∩ -≈≈↑↑▲↑↑∩∩ -≈≈↑↑↑↑↑∩∩ -""" - -# This turns our map string into a list of rows. Because python -# allows us to treat strings as a list of characters, we can access -# those characters with world_map[5][5] where world_map[row][column]. -world_map = world_map.split('\n') - -def return_map(): - """ - This function returns the whole map - """ - map = "" - - #For each row in our map, add it to map - for valuey in world_map: - map += valuey - map += "\n" - - return map - -def return_minimap(x, y, radius = 2): - """ - This function returns only part of the map. - Returning all chars in a 2 char radius from (x,y) - """ - map = "" - - #For each row we need, add the characters we need. - for valuey in world_map[y-radius:y+radius+1]: for valuex in valuey[x-radius:x+radius+1]: - map += valuex - map += "\n" - - return map -``` - -With our map_module set up, let's replace our hardcoded map in `mygame/world/batchcode_map.py` with -a reference to our map module. Make sure to import our map_module! - -```python -# mygame/world/batchcode_map.py - -from evennia import create_object -from evennia import DefaultObject -from world import map_module - -map = create_object(DefaultObject, key="Map", location=caller.location) - -map.db.desc = map_module.return_map() - -caller.msg("A map appears out of thin air and falls to the ground.") -``` - -Log into Evennia as the superuser and run this batchcode. If everything worked our new map should -look exactly the same as the old map - you can use `@delete` to delete the old one (use a number to -pick which to delete). - -Now, lets turn our attention towards our game's rooms. Let's use the `return_minimap` method we -created above in order to include a minimap in our room descriptions. This is a little more -complicated. - -By itself we would have to settle for either the map being *above* the description with -`room.db.desc = map_string + description_string`, or the map going *below* by reversing their order. -Both options are rather unsatisfactory - we would like to have the map next to the text! For this -solution we'll explore the utilities that ship with Evennia. Tucked away in `evennia\evennia\utils` -is a little module called [EvTable](github:evennia.utils.evtable) . This is an advanced ASCII table -creator for you to utilize in your game. We'll use it by creating a basic table with 1 row and two -columns (one for our map and one for our text) whilst also hiding the borders. Open the batchfile -again - -```python -# mygame\world\batchcode_world.py - -# Add to imports -from evennia.utils import evtable -from world import map_module - -# [...] - -# Replace the descriptions with the below code. - -# The cross roads. -# We pass what we want in our table and EvTable does the rest. -# Passing two arguments will create two columns but we could add more. -# We also specify no border. -centre.db.desc = evtable.EvTable(map_module.return_minimap(4,5), - "The merger of two roads. A single lamp post dimly " \ - "illuminates the lonely crossroads. To the north " \ - "looms a mighty castle. To the south the glow of " \ - "a campfire can be seen. To the east lie a wall of " \ - "mountains and to the west the dull roar of the open sea.", - border=None) -# EvTable allows formatting individual columns and cells. We use that here -# to set a maximum width for our description, but letting the map fill -# whatever space it needs. -centre.db.desc.reformat_column(1, width=70) - -# [...] - -# The northern castle. -north.db.desc = evtable.EvTable(map_module.return_minimap(4,2), - "An impressive castle surrounds you. There might be " \ - "a princess in one of these towers.", - border=None) -north.db.desc.reformat_column(1, width=70) - -# [...] - -# The eastern cottage. -east.db.desc = evtable.EvTable(map_module.return_minimap(6,5), - "A cosy cottage nestled among mountains stretching " \ - "east as far as the eye can see.", - border=None) -east.db.desc.reformat_column(1, width=70) - -# [...] - -# The southern camp. -south.db.desc = evtable.EvTable(map_module.return_minimap(4,7), - "Surrounding a clearing are a number of tribal tents " \ - "and at their centre a roaring fire.", - border=None) -south.db.desc.reformat_column(1, width=70) - -# [...] - -# The western coast. -west.db.desc = evtable.EvTable(map_module.return_minimap(2,5), - "The dark forest halts to a sandy beach. The sound of " \ - "crashing waves calms the soul.", - border=None) -west.db.desc.reformat_column(1, width=70) -``` - -Before we run our new batchcode, if you are anything like me you would have something like 100 maps -lying around and 3-4 different versions of our rooms extending from limbo. Let's wipe it all and -start with a clean slate. In Command Prompt you can run `evennia flush` to clear the database and -start anew. It won't reset dbref values however, so if you are at #100 it will start from there. -Alternatively you can navigate to `mygame/server` and delete the `evennia.db3` file. Now in Command -Prompt use `evennia migrate` to have a completely freshly made database. - -Log in to evennia and run `@batchcode batchcode_world` and you'll have a little world to explore. - -## Conclusions - -You should now have a mapped little world and a basic understanding of batchcode, EvTable and how -easily new game defining features can be added to Evennia. - -You can easily build from this tutorial by expanding the map and creating more rooms to explore. Why -not add more features to your game by trying other tutorials: [Add weather to your world](./Weather-Tutorial.md), -[fill your world with NPC's](./Tutorial-Aggressive-NPCs.md) or [implement a combat system](./Turn-based-Combat-System.md). diff --git a/docs/0.9.5/_sources/Tags.md.txt b/docs/0.9.5/_sources/Tags.md.txt deleted file mode 100644 index 8b34e09021..0000000000 --- a/docs/0.9.5/_sources/Tags.md.txt +++ /dev/null @@ -1,170 +0,0 @@ -# Tags - - -A common task of a game designer is to organize and find groups of objects and do operations on -them. A classic example is to have a weather script affect all "outside" rooms. Another would be for -a player casting a magic spell that affects every location "in the dungeon", but not those -"outside". Another would be to quickly find everyone joined with a particular guild or everyone -currently dead. - -*Tags* are short text labels that you attach to objects so as to easily be able to retrieve and -group them. An Evennia entity can be tagged with any number of Tags. On the database side, Tag -entities are *shared* between all objects with that tag. This makes them very efficient but also -fundamentally different from [Attributes](./Attributes.md), each of which always belongs to one *single* -object. - -In Evennia, Tags are technically also used to implement `Aliases` (alternative names for objects) -and `Permissions` (simple strings for [Locks](./Locks.md) to check for). - - -## Properties of Tags (and Aliases and Permissions) - -Tags are *unique* per object model. This means that for each object model (`Objects`, `Scripts`, -`Msgs`, etc.) there is only ever one Tag object with a given key and category. - -> Not specifying a category (default) gives the tag a category of `None`, which is also considered a -unique key + category combination. - -When Tags are assigned to game entities, these entities are actually sharing the same Tag. This -means that Tags are not suitable for storing information about a single object - use an -[Attribute](./Attributes.md) for this instead. Tags are a lot more limited than Attributes but this also -makes them very quick to lookup in the database - this is the whole point. - -Tags have the following properties, stored in the database: - -- **key** - the name of the Tag. This is the main property to search for when looking up a Tag. -- **category** - this category allows for retrieving only specific subsets of tags used for -different purposes. You could have one category of tags for "zones", another for "outdoor -locations", for example. If not given, the category will be `None`, which is also considered a -separate, default, category. -- **data** - this is an optional text field with information about the tag. Remember that Tags are -shared between entities, so this field cannot hold any object-specific information. Usually it would -be used to hold info about the group of entities the Tag is tagging - possibly used for contextual -help like a tool tip. It is not used by default. - -There are also two special properties. These should usually not need to be changed or set, it is -used internally by Evennia to implement various other uses it makes of the `Tag` object: -- **model** - this holds a *natural-key* description of the model object that this tag deals with, -on the form *application.modelclass*, for example `objects.objectdb`. It used by the TagHandler of -each entity type for correctly storing the data behind the scenes. -- **tagtype** - this is a "top-level category" of sorts for the inbuilt children of Tags, namely -*Aliases* and *Permissions*. The Taghandlers using this special field are especially intended to -free up the *category* property for any use you desire. - -## Adding/Removing Tags - -You can tag any *typeclassed* object, namely [Objects](./Objects.md), [Accounts](./Accounts.md), -[Scripts](./Scripts.md) and [Channels](./Communications.md). General tags are added by the *Taghandler*. The -tag handler is accessed as a property `tags` on the relevant entity: - -```python - mychair.tags.add("furniture") - mychair.tags.add("furniture", category="luxurious") - myroom.tags.add("dungeon#01") - myscript.tags.add("weather", category="climate") - myaccount.tags.add("guestaccount") - - mychair.tags.all() # returns a list of Tags - mychair.tags.remove("furniture") - mychair.tags.clear() -``` - -Adding a new tag will either create a new Tag or re-use an already existing one. Note that there are -_two_ "furniture" tags, one with a `None` category, and one with the "luxurious" category. - -When using `remove`, the `Tag` is not deleted but are just disconnected from the tagged object. This -makes for very quick operations. The `clear` method removes (disconnects) all Tags from the object. -You can also use the default `@tag` command: - - @tag mychair = furniture - -This tags the chair with a 'furniture' Tag (the one with a `None` category). - -## Searching for objects with a given tag - -Usually tags are used as a quick way to find tagged database entities. You can retrieve all objects -with a given Tag like this in code: - -```python - import evennia - - # all methods return Querysets - - # search for objects - objs = evennia.search_tag("furniture") - objs2 = evennia.search_tag("furniture", category="luxurious") - dungeon = evennia.search_tag("dungeon#01") - forest_rooms = evennia.search_tag(category="forest") - forest_meadows = evennia.search_tag("meadow", category="forest") - magic_meadows = evennia.search_tag("meadow", category="magical") - - # search for scripts - weather = evennia.search_tag_script("weather") - climates = evennia.search_tag_script(category="climate") - - # search for accounts - accounts = evennia.search_tag_account("guestaccount") -``` - -> Note that searching for just "furniture" will only return the objects tagged with the "furniture" -tag that -has a category of `None`. We must explicitly give the category to get the "luxurious" furniture. - -Using any of the `search_tag` variants will all return [Django -Querysets](https://docs.djangoproject.com/en/2.1/ref/models/querysets/), including if you only have -one match. You can treat querysets as lists and iterate over them, or continue building search -queries with them. - -Remember when searching that not setting a category means setting it to `None` - this does *not* -mean that category is undefined, rather `None` is considered the default, unnamed category. - -```python -import evennia - -myobj1.tags.add("foo") # implies category=None -myobj2.tags.add("foo", category="bar") - -# this returns a queryset with *only* myobj1 -objs = evennia.search_tag("foo") - -# these return a queryset with *only* myobj2 -objs = evennia.search_tag("foo", category="bar") -# or -objs = evennia.search_tag(category="bar") - -``` - - - -There is also an in-game command that deals with assigning and using ([Object-](./Objects.md)) tags: - - @tag/search furniture - -## Using Aliases and Permissions - -Aliases and Permissions are implemented using normal TagHandlers that simply save Tags with a -different `tagtype`. These handlers are named `aliases` and `permissions` on all Objects. They are -used in the same way as Tags above: - -```python - boy.aliases.add("rascal") - boy.permissions.add("Builders") - boy.permissions.remove("Builders") - - all_aliases = boy.aliases.all() -``` - -and so on. Similarly to how `@tag` works in-game, there is also the `@perm` command for assigning -permissions and `@alias` command for aliases. - -## Assorted notes - -Generally, tags are enough on their own for grouping objects. Having no tag `category` is perfectly -fine and the normal operation. Simply adding a new Tag for grouping objects is often better than -making a new category. So think hard before deciding you really need to categorize your Tags. - -That said, tag categories can be useful if you build some game system that uses tags. You can then -use tag categories to make sure to separate tags created with this system from any other tags -created elsewhere. You can then supply custom search methods that *only* find objects tagged with -tags of that category. An example of this -is found in the [Zone tutorial](./Zones.md). diff --git a/docs/0.9.5/_sources/Text-Encodings.md.txt b/docs/0.9.5/_sources/Text-Encodings.md.txt deleted file mode 100644 index 1dfdf5a4e4..0000000000 --- a/docs/0.9.5/_sources/Text-Encodings.md.txt +++ /dev/null @@ -1,66 +0,0 @@ -# Text Encodings - - -Evennia is a text-based game server. This makes it important to understand how -it actually deals with data in the form of text. - -Text *byte encodings* describe how a string of text is actually stored in the -computer - that is, the particular sequence of bytes used to represent the -letters of your particular alphabet. A common encoding used in English-speaking -languages is the *ASCII* encoding. This describes the letters in the English -alphabet (Aa-Zz) as well as a bunch of special characters. For describing other -character sets (such as that of other languages with other letters than - English), sets with names such as *Latin-1*, *ISO-8859-3* and *ARMSCII-8* -are used. There are hundreds of different byte encodings in use around the -world. - -A string of letters in a byte encoding is represented with the `bytes` type. -In contrast to the byte encoding is the *unicode representation*. In Python -this is the `str` type. The unicode is an internationally agreed-upon table -describing essentially all available letters you could ever want to print. -Everything from English to Chinese alphabets and all in between. So what -Evennia (as well as Python and Django) does is to store everything in Unicode -internally, but then converts the data to one of the encodings whenever -outputting data to the user. - -An easy memory aid is that `bytes` are what are sent over the network wire. At -all other times, `str` (unicode) is used. This means that we must convert -between the two at the points where we send/receive network data. - -The problem is that when receiving a string of bytes over the network it's -impossible for Evennia to guess which encoding was used - it's just a bunch of -bytes! Evennia must know the encoding in order to convert back and from the -correct unicode representation. - -## How to customize encodings - -As long as you stick to the standard ASCII character set (which means the -normal English characters, basically) you should not have to worry much -about this section. - -If you want to build your game in another language however, or expect your -users to want to use special characters not in ASCII, you need to consider -which encodings you want to support. - -As mentioned, there are many, many byte-encodings used around the world. It -should be clear at this point that Evennia can't guess but has to assume or -somehow be told which encoding you want to use to communicate with the server. -Basically the encoding used by your client must be the same encoding used by -the server. This can be customized in two complementary ways. - -1. Point users to the default `@encoding` command or the `@options` command. - This allows them to themselves set which encoding they (and their client of - choice) uses. Whereas data will remain stored as unicode strings internally in - Evennia, all data received from and sent to this particular player will be - converted to the given format before transmitting. -1. As a back-up, in case the user-set encoding translation is erroneous or - fails in some other way, Evennia will fall back to trying with the names - defined in the settings variable `ENCODINGS`. This is a list of encoding - names Evennia will try, in order, before giving up and giving an encoding - error message. - -Note that having to try several different encodings every input/output adds -unneccesary overhead. Try to guess the most common encodings you players will -use and make sure these are tried first. The International *UTF-8* encoding is -what Evennia assumes by default (and also what Python/Django use normally). See -the Wikipedia article [here](http://en.wikipedia.org/wiki/Text_encodings) for more help. diff --git a/docs/0.9.5/_sources/TextTags.md.txt b/docs/0.9.5/_sources/TextTags.md.txt deleted file mode 100644 index 4c326b4a83..0000000000 --- a/docs/0.9.5/_sources/TextTags.md.txt +++ /dev/null @@ -1,345 +0,0 @@ -# TextTags - - -This documentation details the various text tags supported by Evennia, namely *colours*, *command -links* and *inline functions*. - -There is also an [Understanding Color Tags](./Understanding-Color-Tags.md) tutorial which expands on the -use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context. - -## Coloured text - -*Note that the Documentation does not display colour the way it would look on the screen.* - -Color can be a very useful tool for your game. It can be used to increase readability and make your -game more appealing visually. - -Remember however that, with the exception of the webclient, you generally don't control the client -used to connect to the game. There is, for example, one special tag meaning "yellow". But exactly -*which* hue of yellow is actually displayed on the user's screen depends on the settings of their -particular mud client. They could even swap the colours around or turn them off altogether if so -desired. Some clients don't even support color - text games are also played with special reading -equipment by people who are blind or have otherwise diminished eyesight. - -So a good rule of thumb is to use colour to enhance your game but don't *rely* on it to display -critical information. If you are coding the game, you can add functionality to let users disable -colours as they please, as described [here](./Manually-Configuring-Color.md). - -To see which colours your client support, use the default `@color` command. This will list all -available colours for ANSI and Xterm256 along with the codes you use for them. You can find a list -of all the parsed `ANSI`-colour codes in `evennia/utils/ansi.py`. - -### ANSI colours - -Evennia supports the `ANSI` standard for text. This is by far the most supported MUD-color standard, -available in all but the most ancient mud clients. The ANSI colours are **r**ed, **g**reen, -**y**ellow, **b**lue, **m**agenta, **c**yan, **w**hite and black. They are abbreviated by their -first letter except for black which is abbreviated with the letter **x**. In ANSI there are "bright" -and "normal" (darker) versions of each color, adding up to a total of 16 colours to use for -foreground text. There are also 8 "background" colours. These have no bright alternative in ANSI -(but Evennia uses the [Xterm256](./TextTags.md#xterm256-colours) extension behind the scenes to offer -them anyway). - -To colour your text you put special tags in it. Evennia will parse these and convert them to the -correct markup for the client used. If the user's client/console/display supports ANSI colour, they -will see the text in the specified colour, otherwise the tags will be stripped (uncolored text). -This works also for non-terminal clients, such as the webclient. For the webclient, Evennia will -translate the codes to HTML RGB colors. - -Here is an example of the tags in action: - - |rThis text is bright red.|n This is normal text. - |RThis is a dark red text.|n This is normal text. - |[rThis text has red background.|n This is normal text. - |b|[yThis is bright blue text on yellow background.|n This is normal text. - -- `|n` - this tag will turn off all color formatting, including background colors. -- `|#`- markup marks the start of foreground color. The case defines if the text is "bright" or -"normal". So `|g` is a bright green and `|G` is "normal" (darker) green. -- `|[#` is used to add a background colour to the text. The case again specifies if it is "bright" -or "normal", so `|[c` starts a bright cyan background and `|[C` a darker cyan background. -- `|!#` is used to add foreground color without any enforced brightness/normal information. - These are normal-intensity and are thus always given as uppercase, such as - `|!R` for red. The difference between e.g. `|!R` and `|R` is that - `|!R` will "inherit" the brightness setting from previously set color tags, whereas `|R` will -always reset to the normal-intensity red. The `|#` format contains an implicit `|h`/`|H` tag in it: -disabling highlighting when switching to a normal color, and enabling it for bright ones. So `|btest -|!Rtest2` will result in a bright red `test2` since the brightness setting from `|b` "bleeds over". -You could use this to for example quickly switch the intensity of a multitude of color tags. There -is no background-color equivalent to `|!` style tags. -- `|h` is used to make any following foreground ANSI colors bright (it has no effect on Xterm -colors). This is only relevant to use with `|!` type tags and will be valid until the next `|n`, -`|H` or normal (upper-case) `|#` tag. This tag will never affect background colors, those have to be -set bright/normal explicitly. Technically, `|h|!G` is identical to `|g`. -- `|H` negates the effects `|h` and returns all ANSI foreground colors (`|!` and `|` types) to -'normal' intensity. It has no effect on background and Xterm colors. - -> Note: The ANSI standard does not actually support bright backgrounds like `|[r` - the standard -only supports "normal" intensity backgrounds. To get around this Evennia instead implements these -as [Xterm256 colours](./TextTags.md#xterm256-colours) behind the scenes. If the client does not support -Xterm256 the ANSI colors will be used instead and there will be no visible difference between using -upper- and lower-case background tags. - -If you want to display an ANSI marker as output text (without having any effect), you need to escape -it by preceding its `|` with another `|`: - -``` -say The ||r ANSI marker changes text color to bright red. -``` - -This will output the raw `|r` without any color change. This can also be necessary if you are doing -ansi art that uses `|` with a letter directly following it. - -Use the command - - @color ansi - -to get a list of all supported ANSI colours and the tags used to produce them. - -A few additional ANSI codes are supported: - -- `|/` A line break. You cannot put the normal Python `\n` line breaks in text entered inside the -game (Evennia will filter this for security reasons). This is what you use instead: use the `|/` -marker to format text with line breaks from the game command line. -- `` This will translate into a `TAB` character. This will not always show (or show differently) to -the client since it depends on their local settings. It's often better to use multiple spaces. -- `|_` This is a space. You can usually use the normal space character, but if the space is *at the -end of the line*, Evennia will likely crop it. This tag will not be cropped but always result in a -space. -- `|*` This will invert the current text/background colours. Can be useful to mark things (but see -below). - -##### Caveats of `|*` - -The `|*` tag (inverse video) is an old ANSI standard and should usually not be used for more than to -mark short snippets of text. If combined with other tags it comes with a series of potentially -confusing behaviors: - -* The `|*` tag will only work once in a row:, ie: after using it once it won't have an effect again -until you declare another tag. This is an example: - - ``` - Normal text, |*reversed text|*, still reversed text. - ``` - - that is, it will not reverse to normal at the second `|*`. You need to reset it manually: - - ``` - Normal text, |*reversed text|n, normal again. - ``` - -* The `|*` tag does not take "bright" colors into account: - - ``` - |RNormal red, |hnow brightened. |*BG is normal red. - ``` - - So `|*` only considers the 'true' foreground color, ignoring any highlighting. Think of the bright -state (`|h`) as something like like `` in HTML: it modifies the _appearance_ of a normal -foreground color to match its bright counterpart, without changing its normal color. -* Finally, after a `|*`, if the previous background was set to a dark color (via `|[`), `|!#`) will -actually change the background color instead of the foreground: - - ``` - |*reversed text |!R now BG is red. - ``` -For a detailed explanation of these caveats, see the [Understanding Color Tags](Understanding-Color- -Tags) tutorial. But most of the time you might be better off to simply avoid `|*` and mark your text -manually instead. - -### Xterm256 Colours - -The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background. -While this offers many more possibilities than traditional ANSI colours, be wary that too many text -colors will be confusing to the eye. Also, not all clients support Xterm256 - these will instead see -the closest equivalent ANSI color. You can mix Xterm256 tags with ANSI tags as you please. - - |555 This is pure white text.|n This is normal text. - |230 This is olive green text. - |[300 This text has a dark red background. - |005|[054 This is dark blue text on a bright cyan background. - |=a This is a greyscale value, equal to black. - |=m This is a greyscale value, midway between white and black. - |=z This is a greyscale value, equal to white. - |[=m This is a background greyscale value. - -- `|###` - markup consists of three digits, each an integer from 0 to 5. The three digits describe -the amount of **r**ed, **g**reen and **b**lue (RGB) components used in the colour. So `|500` means -maximum red and none of the other colours - the result is a bright red. `|520` is red with a touch -of green - the result is orange. As opposed to ANSI colors, Xterm256 syntax does not worry about -bright/normal intensity, a brighter (lighter) color is just achieved by upping all RGB values with -the same amount. -- `|[###` - this works the same way but produces a coloured background. -- `|=#` - markup produces the xterm256 gray scale tones, where `#` is a letter from `a` (black) to -`z` (white). This offers many more nuances of gray than the normal `|###` markup (which only has -four gray tones between solid black and white (`|000`, `|111`, `|222`, `|333` and `|444`)). -- `|[=#` - this works in the same way but produces background gray scale tones. - -If you have a client that supports Xterm256, you can use - - @color xterm256 - -to get a table of all the 256 colours and the codes that produce them. If the table looks broken up -into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement. -You can use the `@options` command to see if xterm256 is active for you. This depends on if your -client told Evennia what it supports - if not, and you know what your client supports, you may have -to activate some features manually. - -## Clickable links - -Evennia supports clickable links for clients that supports it. This marks certain text so it can be -clicked by a mouse and trigger a given Evennia command. To support clickable links, Evennia requires -the webclient or an third-party telnet client with [MXP](http://www.zuggsoft.com/zmud/mxp.htm) -support (*Note: Evennia only supports clickable links, no other MXP features*). - - - `|lc` to start the link, by defining the command to execute. - - `|lt` to continue with the text to show to the user (the link text). - - `|le` to end the link text and the link definition. - -All elements must appear in exactly this order to make a valid link. For example, - -``` -"If you go |lcnorth|ltto the north|le you will find a cottage." -``` - -This will display as "If you go __to the north__ you will find a cottage." where clicking the link -will execute the command `north`. If the client does not support clickable links, only the link text -will be shown. - -## Inline functions - -> Note: Inlinefuncs are **not** activated by default. To use them you need to add -`INLINEFUNC_ENABLED=True` to your settings file. - -Evennia has its own inline text formatting language, known as *inlinefuncs*. It allows the builder -to include special function calls in code. They are executed dynamically by each session that -receives them. - -To add an inlinefunc, you embed it in a text string like this: - -``` -"A normal string with $funcname(arg, arg, ...) embedded inside it." -``` - -When this string is sent to a session (with the `msg()` method), these embedded inlinefuncs will be -parsed. Their return value (which always is a string) replace their call location in the finalized -string. The interesting thing with this is that the function called will have access to which -session is seeing the string, meaning the string can end up looking different depending on who is -looking. It could of course also vary depending on other factors like game time. - -Any number of comma-separated arguments can be given (or none). No keywords are supported. You can -also nest inlinefuncs by letting an argument itself also be another `$funcname(arg, arg, ...)` call -(down to a depth of nesting given by `settings.INLINEFUNC_STACK_MAXSIZE`). Function call resolution -happens as in all programming languages inside-out, with the nested calls replacing the argument -with their return strings before calling he parent. - -``` - > say "This is $pad(a center-padded text, 30,c,-) of width 30." - You say, "This is ---- a center-padded text----- of width 30." - > say "I roll a die and the result is ... a $random(1,6)!" - You say, "I roll a die and the result is ... a 5!" -``` - -A special case happens if wanting to use an inlinefunc argument that itself includes a comma - this -would be parsed as an argument separator. To escape commas you can either escape each comma manually -with a backslash `\,`, or you can embed the entire string in python triple-quotes `"""` or `'''` - -this will escape the entire argument, including commas and any nested inlinefunc calls within. - -Only certain functions are available to use as inlinefuncs and the game developer may add their own -functions as needed. - -### New inlinefuncs - -To add new inlinefuncs, edit the file `mygame/server/conf/inlinefuncs.py`. - -*All globally defined functions in this module* are considered inline functions by the system. The -only exception is functions whose name starts with an underscore `_`. An inlinefunc must be of the -following form: - -```python -def funcname(*args, **kwargs): - # ... - return modified_text -``` - -where `*args` denotes all the arguments this function will accept as an `$inlinefunc`. The inline -function is expected to clean arguments and check that they are valid. If needed arguments are not -given, default values should be used. The function should always return a string (even if it's -empty). An inlinefunc should never cause a traceback regardless of the input (but it could log -errors if desired). - -Note that whereas the function should accept `**kwargs`, keyword inputs are *not* usable in the call -to the inlinefunction. The `kwargs` part is instead intended for Evennia to be able to supply extra -information. Currently Evennia sends a single keyword to every inline function and that is -`session`, which holds the [serversession](./Sessions.md) this text is targeted at. Through the session -object, a lot of dynamic possibilities are opened up for your inline functions. - -The `settings.INLINEFUNC_MODULES` configuration option is a list that decides which modules should -be parsed for inline function definitions. This will include `mygame/server/conf/inlinefuncs.py` but -more could be added. The list is read from left to right so if you want to overload default -functions you just have to put your custom module-paths later in the list and name your functions -the same as default ones. - -Here is an example, the `crop` default inlinefunction: - -```python -from evennia.utils import utils - -def crop(*args, **kwargs): - """ - Inlinefunc. Crops ingoing text to given widths. - Args: - text (str, optional): Text to crop. - width (str, optional): Will be converted to an integer. Width of - crop in characters. - suffix (str, optional): End string to mark the fact that a part - of the string was cropped. Defaults to `[...]`. - Kwargs: - session (Session): Session performing the crop. - Example: - `$crop(text, 50, [...])` - - """ - text, width, suffix = "", 78, "[...]" - nargs = len(args) - if nargs > 0: - text = args[0] - if nargs > 1: - width = int(args[1]) if args[1].strip().isdigit() else 78 - if nargs > 2: - suffix = args[2] - return utils.crop(text, width=width, suffix=suffix) -``` -Another example, making use of the Session: - -```python -def charactername(*args, **kwargs): - """ - Inserts the character name of whomever sees the string - (so everyone will see their own name). Uses the account - name for OOC communications. - - Example: - say "This means YOU, $charactername()!" - - """ - session = kwargs["session"] - if session.puppet: - return kwargs["session"].puppet.key - else: - return session.account.key -``` - -Evennia itself offers the following default inline functions (mostly as examples): - -* `crop(text, width, suffix)` - See above. -* `pad(text, width, align, fillchar)` - this pads the text to `width` (default 78), alignment ("c", -"l" or "r", defaulting to "c") and fill-in character (defaults to space). Example: `$pad(40,l,-)` -* `clr(startclr, text, endclr)` - A programmatic way to enter colored text for those who don't want -to use the normal `|c` type color markers for some reason. The `color` argument is the same as the -color markers except without the actual pre-marker, so `|r` would be just `r`. If `endclr` is not -given, it defaults to resetting the color (`n`). Example: `$clr(b, A blue text)` -* `space(number)` - Inserts the given number of spaces. If no argument is given, use 4 spaces. -* `random()`, `random(max)`, `random(min, max)` - gives a random value between min and max. With no -arguments, give 0 or 1 (on/off). If one argument, returns 0...max. If values are floats, random -value will be a float (so `random(1.0)` replicates the normal Python `random` function). \ No newline at end of file diff --git a/docs/0.9.5/_sources/TickerHandler.md.txt b/docs/0.9.5/_sources/TickerHandler.md.txt deleted file mode 100644 index 9b9a5878b6..0000000000 --- a/docs/0.9.5/_sources/TickerHandler.md.txt +++ /dev/null @@ -1,136 +0,0 @@ -# TickerHandler - - -One way to implement a dynamic MUD is by using "tickers", also known as "heartbeats". A ticker is a -timer that fires ("ticks") at a given interval. The tick triggers updates in various game systems. - -## About Tickers - -Tickers are very common or even unavoidable in other mud code bases. Certain code bases are even -hard-coded to rely on the concept of the global 'tick'. Evennia has no such notion - the decision to -use tickers is very much up to the need of your game and which requirements you have. The "ticker -recipe" is just one way of cranking the wheels. - -The most fine-grained way to manage the flow of time is of course to use [Scripts](./Scripts.md). Many -types of operations (weather being the classic example) are however done on multiple objects in the -same way at regular intervals, and for this, storing separate Scripts on each object is inefficient. -The way to do this is to use a ticker with a "subscription model" - let objects sign up to be -triggered at the same interval, unsubscribing when the updating is no longer desired. - -Evennia offers an optimized implementation of the subscription model - the *TickerHandler*. This is -a singleton global handler reachable from `evennia.TICKER_HANDLER`. You can assign any *callable* (a -function or, more commonly, a method on a database object) to this handler. The TickerHandler will -then call this callable at an interval you specify, and with the arguments you supply when adding -it. This continues until the callable un-subscribes from the ticker. The handler survives a reboot -and is highly optimized in resource usage. - -Here is an example of importing `TICKER_HANDLER` and using it: - -```python - # we assume that obj has a hook "at_tick" defined on itself - from evennia import TICKER_HANDLER as tickerhandler - - tickerhandler.add(20, obj.at_tick) -``` - -That's it - from now on, `obj.at_tick()` will be called every 20 seconds. - -You can also import function and tick that: - -```python - from evennia import TICKER_HANDLER as tickerhandler - from mymodule import myfunc - - tickerhandler.add(30, myfunc) -``` - -Removing (stopping) the ticker works as expected: - -```python - tickerhandler.remove(20, obj.at_tick) - tickerhandler.remove(30, myfunc) -``` - -Note that you have to also supply `interval` to identify which subscription to remove. This is -because the TickerHandler maintains a pool of tickers and a given callable can subscribe to be -ticked at any number of different intervals. - -The full definition of the `tickerhandler.add` method is - -```python - tickerhandler.add(interval, callback, - idstring="", persistent=True, *args, **kwargs) -``` - -Here `*args` and `**kwargs` will be passed to `callback` every `interval` seconds. If `persistent` -is `False`, this subscription will not survive a server reload. - -Tickers are identified and stored by making a key of the callable itself, the ticker-interval, the -`persistent` flag and the `idstring` (the latter being an empty string when not given explicitly). - -Since the arguments are not included in the ticker's identification, the `idstring` must be used to -have a specific callback triggered multiple times on the same interval but with different arguments: - -```python - tickerhandler.add(10, obj.update, "ticker1", True, 1, 2, 3) - tickerhandler.add(10, obj.update, "ticker2", True, 4, 5) -``` - -> Note that, when we want to send arguments to our callback within a ticker handler, we need to -specify `idstring` and `persistent` before, unless we call our arguments as keywords, which would -often be more readable: - -```python - tickerhandler.add(10, obj.update, caller=self, value=118) -``` - -If you add a ticker with exactly the same combination of callback, interval and idstring, it will -overload the existing ticker. This identification is also crucial for later removing (stopping) the -subscription: - -```python - tickerhandler.remove(10, obj.update, idstring="ticker1") - tickerhandler.remove(10, obj.update, idstring="ticker2") -``` - -The `callable` can be on any form as long as it accepts the arguments you give to send to it in -`TickerHandler.add`. - -> Note that everything you supply to the TickerHandler will need to be pickled at some point to be -saved into the database. Most of the time the handler will correctly store things like database -objects, but the same restrictions as for [Attributes](./Attributes.md) apply to what the TickerHandler -may store. - -When testing, you can stop all tickers in the entire game with `tickerhandler.clear()`. You can also -view the currently subscribed objects with `tickerhandler.all()`. - -See the [Weather Tutorial](./Weather-Tutorial.md) for an example of using the TickerHandler. - -### When *not* to use TickerHandler - -Using the TickerHandler may sound very useful but it is important to consider when not to use it. -Even if you are used to habitually relying on tickers for everything in other code bases, stop and -think about what you really need it for. This is the main point: - -> You should *never* use a ticker to catch *changes*. - -Think about it - you might have to run the ticker every second to react to the change fast enough. -Most likely nothing will have changed at a given moment. So you are doing pointless calls (since -skipping the call gives the same result as doing it). Making sure nothing's changed might even be -computationally expensive depending on the complexity of your system. Not to mention that you might -need to run the check *on every object in the database*. Every second. Just to maintain status quo -... - -Rather than checking over and over on the off-chance that something changed, consider a more -proactive approach. Could you implement your rarely changing system to *itself* report when its -status changes? It's almost always much cheaper/efficient if you can do things "on demand". Evennia -itself uses hook methods for this very reason. - -So, if you consider a ticker that will fire very often but which you expect to have no effect 99% of -the time, consider handling things things some other way. A self-reporting on-demand solution is -usually cheaper also for fast-updating properties. Also remember that some things may not need to be -updated until someone actually is examining or using them - any interim changes happening up to that -moment are pointless waste of computing time. - -The main reason for needing a ticker is when you want things to happen to multiple objects at the -same time without input from something else. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Turn-based-Combat-System.md.txt b/docs/0.9.5/_sources/Turn-based-Combat-System.md.txt deleted file mode 100644 index a4577185df..0000000000 --- a/docs/0.9.5/_sources/Turn-based-Combat-System.md.txt +++ /dev/null @@ -1,516 +0,0 @@ -# Turn based Combat System - - -This tutorial gives an example of a full, if simplified, combat system for Evennia. It was inspired -by the discussions held on the [mailing list](https://groups.google.com/forum/#!msg/evennia/wnJNM2sXSfs/-dbLRrgWnYMJ). - -## Overview of combat system concepts - -Most MUDs will use some sort of combat system. There are several main variations: - -- _Freeform_ - the simplest form of combat to implement, common to MUSH-style roleplaying games. -This means the system only supplies dice rollers or maybe commands to compare skills and spit out -the result. Dice rolls are done to resolve combat according to the rules of the game and to direct -the scene. A game master may be required to resolve rule disputes. -- _Twitch_ - This is the traditional MUD hack&slash style combat. In a twitch system there is often -no difference between your normal "move-around-and-explore mode" and the "combat mode". You enter an -attack command and the system will calculate if the attack hits and how much damage was caused. -Normally attack commands have some sort of timeout or notion of recovery/balance to reduce the -advantage of spamming or client scripting. Whereas the simplest systems just means entering `kill -` over and over, more sophisticated twitch systems include anything from defensive stances -to tactical positioning. -- _Turn-based_ - a turn based system means that the system pauses to make sure all combatants can -choose their actions before continuing. In some systems, such entered actions happen immediately -(like twitch-based) whereas in others the resolution happens simultaneously at the end of the turn. -The disadvantage of a turn-based system is that the game must switch to a "combat mode" and one also -needs to take special care of how to handle new combatants and the passage of time. The advantage is -that success is not dependent on typing speed or of setting up quick client macros. This potentially -allows for emoting as part of combat which is an advantage for roleplay-heavy games. - -To implement a freeform combat system all you need is a dice roller and a roleplaying rulebook. See -[contrib/dice.py](https://github.com/evennia/evennia/blob/master/evennia/contrib/dice.py) for an -example dice roller. To implement at twitch-based system you basically need a few combat -[commands](./Commands.md), possibly ones with a [cooldown](./Command-Cooldown.md). You also need a [game rule -module](./Implementing-a-game-rule-system.md) that makes use of it. We will focus on the turn-based -variety here. - -## Tutorial overview - -This tutorial will implement the slightly more complex turn-based combat system. Our example has the -following properties: - -- Combat is initiated with `attack `, this initiates the combat mode. -- Characters may join an ongoing battle using `attack ` against a character already in -combat. -- Each turn every combating character will get to enter two commands, their internal order matters -and they are compared one-to-one in the order given by each combatant. Use of `say` and `pose` is -free. -- The commands are (in our example) simple; they can either `hit `, `feint ` or -`parry `. They can also `defend`, a generic passive defense. Finally they may choose to -`disengage/flee`. -- When attacking we use a classic [rock-paper-scissors](https://en.wikipedia.org/wiki/Rock-paper-scissors) mechanic to determine success: `hit` defeats `feint`, which defeats `parry` which defeats -`hit`. `defend` is a general passive action that has a percentage chance to win against `hit` -(only). -- `disengage/flee` must be entered two times in a row and will only succeed if there is no `hit` -against them in that time. If so they will leave combat mode. -- Once every player has entered two commands, all commands are resolved in order and the result is -reported. A new turn then begins. -- If players are too slow the turn will time out and any unset commands will be set to `defend`. - -For creating the combat system we will need the following components: - -- A combat handler. This is the main mechanic of the system. This is a [Script](./Scripts.md) object -created for each combat. It is not assigned to a specific object but is shared by the combating -characters and handles all the combat information. Since Scripts are database entities it also means -that the combat will not be affected by a server reload. -- A combat [command set](./Command-Sets.md) with the relevant commands needed for combat, such as the -various attack/defend options and the `flee/disengage` command to leave the combat mode. -- A rule resolution system. The basics of making such a module is described in the [rule system tutorial](./Implementing-a-game-rule-system.md). We will only sketch such a module here for our end-turn -combat resolution. -- An `attack` [command](./Commands.md) for initiating the combat mode. This is added to the default -command set. It will create the combat handler and add the character(s) to it. It will also assign -the combat command set to the characters. - -## The combat handler - -The _combat handler_ is implemented as a stand-alone [Script](./Scripts.md). This Script is created when -the first Character decides to attack another and is deleted when no one is fighting any more. Each -handler represents one instance of combat and one combat only. Each instance of combat can hold any -number of characters but each character can only be part of one combat at a time (a player would -need to disengage from the first combat before they could join another). - -The reason we don't store this Script "on" any specific character is because any character may leave -the combat at any time. Instead the script holds references to all characters involved in the -combat. Vice-versa, all characters holds a back-reference to the current combat handler. While we -don't use this very much here this might allow the combat commands on the characters to access and -update the combat handler state directly. - -_Note: Another way to implement a combat handler would be to use a normal Python object and handle -time-keeping with the [TickerHandler](./TickerHandler.md). This would require either adding custom hook -methods on the character or to implement a custom child of the TickerHandler class to track turns. -Whereas the TickerHandler is easy to use, a Script offers more power in this case._ - -Here is a basic combat handler. Assuming our game folder is named `mygame`, we store it in -`mygame/typeclasses/combat_handler.py`: - -```python -# mygame/typeclasses/combat_handler.py - -import random -from evennia import DefaultScript -from world.rules import resolve_combat - -class CombatHandler(DefaultScript): - """ - This implements the combat handler. - """ - - # standard Script hooks - - def at_script_creation(self): - "Called when script is first created" - - self.key = "combat_handler_%i" % random.randint(1, 1000) - self.desc = "handles combat" - self.interval = 60 * 2 # two minute timeout - self.start_delay = True - self.persistent = True - - # store all combatants - self.db.characters = {} - # store all actions for each turn - self.db.turn_actions = {} - # number of actions entered per combatant - self.db.action_count = {} - - def _init_character(self, character): - """ - This initializes handler back-reference - and combat cmdset on a character - """ - character.ndb.combat_handler = self - character.cmdset.add("commands.combat.CombatCmdSet") - - def _cleanup_character(self, character): - """ - Remove character from handler and clean - it of the back-reference and cmdset - """ - dbref = character.id - del self.db.characters[dbref] - del self.db.turn_actions[dbref] - del self.db.action_count[dbref] - del character.ndb.combat_handler - character.cmdset.delete("commands.combat.CombatCmdSet") - - def at_start(self): - """ - This is called on first start but also when the script is restarted - after a server reboot. We need to re-assign this combat handler to - all characters as well as re-assign the cmdset. - """ - for character in self.db.characters.values(): - self._init_character(character) - - def at_stop(self): - "Called just before the script is stopped/destroyed." - for character in list(self.db.characters.values()): - # note: the list() call above disconnects list from database - self._cleanup_character(character) - - def at_repeat(self): - """ - This is called every self.interval seconds (turn timeout) or - when force_repeat is called (because everyone has entered their - commands). We know this by checking the existence of the - `normal_turn_end` NAttribute, set just before calling - force_repeat. - - """ - if self.ndb.normal_turn_end: - # we get here because the turn ended normally - # (force_repeat was called) - no msg output - del self.ndb.normal_turn_end - else: - # turn timeout - self.msg_all("Turn timer timed out. Continuing.") - self.end_turn() - - # Combat-handler methods - - def add_character(self, character): - "Add combatant to handler" - dbref = character.id - self.db.characters[dbref] = character - self.db.action_count[dbref] = 0 - self.db.turn_actions[dbref] = [("defend", character, None), - ("defend", character, None)] - # set up back-reference - self._init_character(character) - - def remove_character(self, character): - "Remove combatant from handler" - if character.id in self.db.characters: - self._cleanup_character(character) - if not self.db.characters: - # if no more characters in battle, kill this handler - self.stop() - - def msg_all(self, message): - "Send message to all combatants" - for character in self.db.characters.values(): - character.msg(message) - - def add_action(self, action, character, target): - """ - Called by combat commands to register an action with the handler. - - action - string identifying the action, like "hit" or "parry" - character - the character performing the action - target - the target character or None - - actions are stored in a dictionary keyed to each character, each - of which holds a list of max 2 actions. An action is stored as - a tuple (character, action, target). - """ - dbref = character.id - count = self.db.action_count[dbref] - if 0 <= count <= 1: # only allow 2 actions - self.db.turn_actions[dbref][count] = (action, character, target) - else: - # report if we already used too many actions - return False - self.db.action_count[dbref] += 1 - return True - - def check_end_turn(self): - """ - Called by the command to eventually trigger - the resolution of the turn. We check if everyone - has added all their actions; if so we call force the - script to repeat immediately (which will call - `self.at_repeat()` while resetting all timers). - """ - if all(count > 1 for count in self.db.action_count.values()): - self.ndb.normal_turn_end = True - self.force_repeat() - - def end_turn(self): - """ - This resolves all actions by calling the rules module. - It then resets everything and starts the next turn. It - is called by at_repeat(). - """ - resolve_combat(self, self.db.turn_actions) - - if len(self.db.characters) < 2: - # less than 2 characters in battle, kill this handler - self.msg_all("Combat has ended") - self.stop() - else: - # reset counters before next turn - for character in self.db.characters.values(): - self.db.characters[character.id] = character - self.db.action_count[character.id] = 0 - self.db.turn_actions[character.id] = [("defend", character, None), - ("defend", character, None)] - self.msg_all("Next turn begins ...") -``` - -This implements all the useful properties of our combat handler. This Script will survive a reboot -and will automatically re-assert itself when it comes back online. Even the current state of the -combat should be unaffected since it is saved in Attributes at every turn. An important part to note -is the use of the Script's standard `at_repeat` hook and the `force_repeat` method to end each turn. -This allows for everything to go through the same mechanisms with minimal repetition of code. - -What is not present in this handler is a way for players to view the actions they set or to change -their actions once they have been added (but before the last one has added theirs). We leave this as -an exercise. - -## Combat commands - -Our combat commands - the commands that are to be available to us during the combat - are (in our -example) very simple. In a full implementation the commands available might be determined by the -weapon(s) held by the player or by which skills they know. - -We create them in `mygame/commands/combat.py`. - -```python -# mygame/commands/combat.py - -from evennia import Command - -class CmdHit(Command): - """ - hit an enemy - - Usage: - hit - - Strikes the given enemy with your current weapon. - """ - key = "hit" - aliases = ["strike", "slash"] - help_category = "combat" - - def func(self): - "Implements the command" - if not self.args: - self.caller.msg("Usage: hit ") - return - target = self.caller.search(self.args) - if not target: - return - ok = self.caller.ndb.combat_handler.add_action("hit", - self.caller, - target) - if ok: - self.caller.msg("You add 'hit' to the combat queue") - else: - self.caller.msg("You can only queue two actions per turn!") - - # tell the handler to check if turn is over - self.caller.ndb.combat_handler.check_end_turn() -``` - -The other commands `CmdParry`, `CmdFeint`, `CmdDefend` and `CmdDisengage` look basically the same. -We should also add a custom `help` command to list all the available combat commands and what they -do. - -We just need to put them all in a cmdset. We do this at the end of the same module: - -```python -# mygame/commands/combat.py - -from evennia import CmdSet -from evennia import default_cmds - -class CombatCmdSet(CmdSet): - key = "combat_cmdset" - mergetype = "Replace" - priority = 10 - no_exits = True - - def at_cmdset_creation(self): - self.add(CmdHit()) - self.add(CmdParry()) - self.add(CmdFeint()) - self.add(CmdDefend()) - self.add(CmdDisengage()) - self.add(CmdHelp()) - self.add(default_cmds.CmdPose()) - self.add(default_cmds.CmdSay()) -``` - -## Rules module - -A general way to implement a rule module is found in the [rule system tutorial](./Implementing-a-game-rule-system.md). Proper resolution would likely require us to change our Characters to store things -like strength, weapon skills and so on. So for this example we will settle for a very simplistic -rock-paper-scissors kind of setup with some randomness thrown in. We will not deal with damage here -but just announce the results of each turn. In a real system the Character objects would hold stats -to affect their skills, their chosen weapon affect the choices, they would be able to lose health -etc. - -Within each turn, there are "sub-turns", each consisting of one action per character. The actions -within each sub-turn happens simultaneously and only once they have all been resolved we move on to -the next sub-turn (or end the full turn). - -*Note: In our simple example the sub-turns don't affect each other (except for `disengage/flee`), -nor do any effects carry over between turns. The real power of a turn-based system would be to add -real tactical possibilities here though; For example if your hit got parried you could be out of -balance and your next action would be at a disadvantage. A successful feint would open up for a -subsequent attack and so on ...* - -Our rock-paper-scissor setup works like this: - -- `hit` beats `feint` and `flee/disengage`. It has a random chance to fail against `defend`. -- `parry` beats `hit`. -- `feint` beats `parry` and is then counted as a `hit`. -- `defend` does nothing but has a chance to beat `hit`. -- `flee/disengage` must succeed two times in a row (i.e. not beaten by a `hit` once during the -turn). If so the character leaves combat. - - -```python -# mygame/world/rules.py - -import random - -# messages - -def resolve_combat(combat_handler, actiondict): - """ - This is called by the combat handler - actiondict is a dictionary with a list of two actions - for each character: - {char.id:[(action1, char, target), (action2, char, target)], ...} - """ - flee = {} # track number of flee commands per character - for isub in range(2): - # loop over sub-turns - messages = [] - for subturn in (sub[isub] for sub in actiondict.values()): - # for each character, resolve the sub-turn - action, char, target = subturn - if target: - taction, tchar, ttarget = actiondict[target.id][isub] - if action == "hit": - if taction == "parry" and ttarget == char: - msg = "%s tries to hit %s, but %s parries the attack!" - messages.append(msg % (char, tchar, tchar)) - elif taction == "defend" and random.random() < 0.5: - msg = "%s defends against the attack by %s." - messages.append(msg % (tchar, char)) - elif taction == "flee": - msg = "%s stops %s from disengaging, with a hit!" - flee[tchar] = -2 - messages.append(msg % (char, tchar)) - else: - msg = "%s hits %s, bypassing their %s!" - messages.append(msg % (char, tchar, taction)) - elif action == "parry": - if taction == "hit": - msg = "%s parries the attack by %s." - messages.append(msg % (char, tchar)) - elif taction == "feint": - msg = "%s tries to parry, but %s feints and hits!" - messages.append(msg % (char, tchar)) - else: - msg = "%s parries to no avail." - messages.append(msg % char) - elif action == "feint": - if taction == "parry": - msg = "%s feints past %s's parry, landing a hit!" - messages.append(msg % (char, tchar)) - elif taction == "hit": - msg = "%s feints but is defeated by %s hit!" - messages.append(msg % (char, tchar)) - else: - msg = "%s feints to no avail." - messages.append(msg % char) - elif action == "defend": - msg = "%s defends." - messages.append(msg % char) - elif action == "flee": - if char in flee: - flee[char] += 1 - else: - flee[char] = 1 - msg = "%s tries to disengage (two subsequent turns needed)" - messages.append(msg % char) - - # echo results of each subturn - combat_handler.msg_all("\n".join(messages)) - - # at the end of both sub-turns, test if anyone fled - msg = "%s withdraws from combat." - for (char, fleevalue) in flee.items(): - if fleevalue == 2: - combat_handler.msg_all(msg % char) - combat_handler.remove_character(char) -``` - -To make it simple (and to save space), this example rule module actually resolves each interchange -twice - first when it gets to each character and then again when handling the target. Also, since we -use the combat handler's `msg_all` method here, the system will get pretty spammy. To clean it up, -one could imagine tracking all the possible interactions to make sure each pair is only handled and -reported once. - -## Combat initiator command - -This is the last component we need, a command to initiate combat. This will tie everything together. -We store this with the other combat commands. - -```python -# mygame/commands/combat.py - -from evennia import create_script - -class CmdAttack(Command): - """ - initiates combat - - Usage: - attack - - This will initiate combat with . If ") - return - target = self.caller.search(self.args) - if not target: - return - # set up combat - if target.ndb.combat_handler: - # target is already in combat - join it - target.ndb.combat_handler.add_character(self.caller) - target.ndb.combat_handler.msg_all("%s joins combat!" % self.caller) - else: - # create a new combat handler - chandler = create_script("combat_handler.CombatHandler") - chandler.add_character(self.caller) - chandler.add_character(target) - self.caller.msg("You attack %s! You are in combat." % target) - target.msg("%s attacks you! You are in combat." % self.caller) -``` - -The `attack` command will not go into the combat cmdset but rather into the default cmdset. See e.g. -the [Adding Command Tutorial](./Adding-Command-Tutorial.md) if you are unsure about how to do this. - -## Expanding the example - -At this point you should have a simple but flexible turn-based combat system. We have taken several -shortcuts and simplifications in this example. The output to the players is likely too verbose -during combat and too limited when it comes to informing about things surrounding it. Methods for -changing your commands or list them, view who is in combat etc is likely needed - this will require -play testing for each game and style. There is also currently no information displayed for other -people happening to be in the same room as the combat - some less detailed information should -probably be echoed to the room to -show others what's going on. diff --git a/docs/0.9.5/_sources/Tutorial-Aggressive-NPCs.md.txt b/docs/0.9.5/_sources/Tutorial-Aggressive-NPCs.md.txt deleted file mode 100644 index 9af3bfcf92..0000000000 --- a/docs/0.9.5/_sources/Tutorial-Aggressive-NPCs.md.txt +++ /dev/null @@ -1,127 +0,0 @@ -# Tutorial Aggressive NPCs - - -This tutorial shows the implementation of an NPC object that responds to characters entering their -location. In this example the NPC has the option to respond aggressively or not, but any actions -could be triggered this way. - -One could imagine using a [Script](./Scripts.md) that is constantly checking for newcomers. This would be -highly inefficient (most of the time its check would fail). Instead we handle this on-demand by -using a couple of existing object hooks to inform the NPC that a Character has entered. - -It is assumed that you already know how to create custom room and character typeclasses, please see -the [Basic Game tutorial](./Tutorial-for-basic-MUSH-like-game.md) if you haven't already done this. - -What we will need is the following: - -- An NPC typeclass that can react when someone enters. -- A custom [Room](./Objects.md#rooms) typeclass that can tell the NPC that someone entered. -- We will also tweak our default `Character` typeclass a little. - -To begin with, we need to create an NPC typeclass. Create a new file inside of your typeclasses -folder and name it `npcs.py` and then add the following code: - -```python -from typeclasses.characters import Character - -class NPC(Character): - """ - A NPC typeclass which extends the character class. - """ - def at_char_entered(self, character): - """ - A simple is_aggressive check. - Can be expanded upon later. - """ - if self.db.is_aggressive: - self.execute_cmd(f"say Graaah, die {character}!") - else: - self.execute_cmd(f"say Greetings, {character}!") -``` - -We will define our custom `Character` typeclass below. As for the new `at_char_entered` method we've -just defined, we'll ensure that it will be called by the room where the NPC is located, when a -player enters that room. You'll notice that right now, the NPC merely speaks. You can expand this -part as you like and trigger all sorts of effects here (like combat code, fleeing, bartering or -quest-giving) as your game design dictates. - -Now your `typeclasses.rooms` module needs to have the following added: - -```python -# Add this import to the top of your file. -from evennia import utils - - # Add this hook in any empty area within your Room class. - def at_object_receive(self, obj, source_location): - if utils.inherits_from(obj, 'typeclasses.npcs.NPC'): # An NPC has entered - return - elif utils.inherits_from(obj, 'typeclasses.characters.Character'): - # A PC has entered. - # Cause the player's character to look around. - obj.execute_cmd('look') - for item in self.contents: - if utils.inherits_from(item, 'typeclasses.npcs.NPC'): - # An NPC is in the room - item.at_char_entered(obj) -``` - -`inherits_from` must be given the full path of the class. If the object inherited a class from your -`world.races` module, then you would check inheritance with `world.races.Human`, for example. There -is no need to import these prior, as we are passing in the full path. As a matter of a fact, -`inherits_from` does not properly work if you import the class and only pass in the name of the -class. - -> Note: -[at_object_receive](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L1529) -is a default hook of the `DefaultObject` typeclass (and its children). Here we are overriding this -hook in our customized room typeclass to suit our needs. - -This room checks the typeclass of objects entering it (using `utils.inherits_from` and responds to -`Characters`, ignoring other NPCs or objects. When triggered the room will look through its -contents and inform any `NPCs inside by calling their `at_char_entered` method. - -You'll also see that we have added a 'look' into this code. This is because, by default, the -`at_object_receive` is carried out *before* the character's `at_after_move` which, we will now -overload. This means that a character entering would see the NPC perform its actions before the -'look' command. Deactivate the look command in the default `Character` class within the -`typeclasses.characters` module: - -```python - # Add this hook in any blank area within your Character class. - def at_after_move(self, source_location): - """ - Default is to look around after a move - Note: This has been moved to Room.at_object_receive - """ - #self.execute_cmd('look') - pass -``` - -Now let's create an NPC and make it aggressive. Type the following commands into your MUD client: -``` -reload -create/drop Orc:npcs.NPC -``` - -> Note: You could also give the path as `typeclasses.npcs.NPC`, but Evennia will look into the -`typeclasses` folder automatically, so this is a little shorter. - -When you enter the aggressive NPC's location, it will default to using its peaceful action (say your -name is Anna): - -``` -Orc says, "Greetings, Anna!" -``` - -Now we turn on the aggressive mode (we do it manually but it could also be triggered by some sort of -AI code). - -``` -set orc/is_aggressive = True -``` - -Now it will perform its aggressive action whenever a character enters. - -``` -Orc says, "Graaah, die, Anna!" -``` diff --git a/docs/0.9.5/_sources/Tutorial-NPCs-listening.md.txt b/docs/0.9.5/_sources/Tutorial-NPCs-listening.md.txt deleted file mode 100644 index 77238e705d..0000000000 --- a/docs/0.9.5/_sources/Tutorial-NPCs-listening.md.txt +++ /dev/null @@ -1,112 +0,0 @@ -# Tutorial NPCs listening - - -This tutorial shows the implementation of an NPC object that responds to characters speaking in -their location. In this example the NPC parrots what is said, but any actions could be triggered -this way. - -It is assumed that you already know how to create custom room and character typeclasses, please see -the [Basic Game tutorial](./Tutorial-for-basic-MUSH-like-game.md) if you haven't already done this. - -What we will need is simply a new NPC typeclass that can react when someone speaks. - -```python -# mygame/typeclasses/npc.py - -from characters import Character -class Npc(Character): - """ - A NPC typeclass which extends the character class. - """ - def at_heard_say(self, message, from_obj): - """ - A simple listener and response. This makes it easy to change for - subclasses of NPCs reacting differently to says. - - """ - # message will be on the form ` says, "say_text"` - # we want to get only say_text without the quotes and any spaces - message = message.split('says, ')[1].strip(' "') - - # we'll make use of this in .msg() below - return "%s said: '%s'" % (from_obj, message) -``` - -When someone in the room speaks to this NPC, its `msg` method will be called. We will modify the -NPCs `.msg` method to catch says so the NPC can respond. - - -```python -# mygame/typeclasses/npc.py - -from characters import Character -class Npc(Character): - - # [at_heard_say() goes here] - - def msg(self, text=None, from_obj=None, **kwargs): - "Custom msg() method reacting to say." - - if from_obj != self: - # make sure to not repeat what we ourselves said or we'll create a loop - try: - # if text comes from a say, `text` is `('say_text', {'type': 'say'})` - say_text, is_say = text[0], text[1]['type'] == 'say' - except Exception: - is_say = False - if is_say: - # First get the response (if any) - response = self.at_heard_say(say_text, from_obj) - # If there is a response - if response != None: - # speak ourselves, using the return - self.execute_cmd("say %s" % response) - - # this is needed if anyone ever puppets this NPC - without it you would never - # get any feedback from the server (not even the results of look) - super().msg(text=text, from_obj=from_obj, **kwargs) -``` - -So if the NPC gets a say and that say is not coming from the NPC itself, it will echo it using the -`at_heard_say` hook. Some things of note in the above example: - -- The `text` input can be on many different forms depending on where this `msg` is called from. -Instead of trying to analyze `text` in detail with a range of `if` statements we just assume the -form we want and catch the error if it does not match. This simplifies the code considerably. It's -called 'leap before you look' and is a Python paradigm that may feel unfamiliar if you are used to -other languages. Here we 'swallow' the error silently, which is fine when the code checked is -simple. If not we may want to import `evennia.logger.log_trace` and add `log_trace()` in the -`except` clause.
-If you would like to learn more about the `text` list used above refer to the [Out-Of-Band](./OOB.md) -documentation. -- We use `execute_cmd` to fire the `say` command back. We could also have called -`self.location.msg_contents` directly but using the Command makes sure all hooks are called (so -those seeing the NPC's `say` can in turn react if they want). -- Note the comments about `super` at the end. This will trigger the 'default' `msg` (in the parent -class) as well. It's not really necessary as long as no one puppets the NPC (by `@ic `) but -it's wise to keep in there since the puppeting player will be totally blind if `msg()` is never -returning anything to them! - -Now that's done, let's create an NPC and see what it has to say for itself. - -``` -@reload -@create/drop Guild Master:npc.Npc -``` - -(you could also give the path as `typeclasses.npc.Npc`, but Evennia will look into the `typeclasses` -folder automatically so this is a little shorter). - - > say hi - You say, "hi" - Guild Master says, "Anna said: 'hi'" - -## Assorted notes - -There are many ways to implement this kind of functionality. An alternative example to overriding -`msg` would be to modify the `at_say` hook on the *Character* instead. It could detect that it's -sending to an NPC and call the `at_heard_say` hook directly. - -While the tutorial solution has the advantage of being contained only within the NPC class, -combining this with using the Character class gives more direct control over how the NPC will react. -Which way to go depends on the design requirements of your particular game. diff --git a/docs/0.9.5/_sources/Tutorial-Searching-For-Objects.md.txt b/docs/0.9.5/_sources/Tutorial-Searching-For-Objects.md.txt deleted file mode 100644 index 3bf7076ed5..0000000000 --- a/docs/0.9.5/_sources/Tutorial-Searching-For-Objects.md.txt +++ /dev/null @@ -1,430 +0,0 @@ -# Tutorial Searching For Objects - - -You will often want to operate on a specific object in the database. For example when a player -attacks a named target you'll need to find that target so it can be attacked. Or when a rain storm -draws in you need to find all outdoor-rooms so you can show it raining in them. This tutorial -explains Evennia's tools for searching. - -## Things to search for - -The first thing to consider is the base type of the thing you are searching for. Evennia organizes -its database into a few main tables: [Objects](./Objects.md), [Accounts](./Accounts.md), [Scripts](./Scripts.md), -[Channels](./Communications.md#channels), [Messages](./Communications.md#msg) and [Help Entries](./Help-System.md). -Most of the time you'll likely spend your time searching for Objects and the occasional Accounts. - -So to find an entity, what can be searched for? - - - The `key` is the name of the entity. While you can get this from `obj.key` the *database field* -is actually named `obj.db_key` - this is useful to know only when you do [direct database -queries](./Tutorial-Searching-For-Objects.md#queries-in-django). The one exception is `Accounts`, where -the database field for `.key` is instead named `username` (this is a Django requirement). When you -don't specify search-type, you'll usually search based on key. *Aliases* are extra names given to -Objects using something like `@alias` or `obj.aliases.add('name')`. The main search functions (see -below) will automatically search for aliases whenever you search by-key. - - [Tags](./Tags.md) are the main way to group and identify objects in Evennia. Tags can most often be -used (sometimes together with keys) to uniquely identify an object. For example, even though you -have two locations with the same name, you can separate them by their tagging (this is how Evennia -implements 'zones' seen in other systems). Tags can also have categories, to further organize your -data for quick lookups. - - An object's [Attributes](./Attributes.md) can also used to find an object. This can be very useful but -since Attributes can store almost any data they are far less optimized to search for than Tags or -keys. -- The object's [Typeclass](./Typeclasses.md) indicate the sub-type of entity. A Character, Flower or -Sword are all types of Objects. A Bot is a kind of Account. The database field is called -`typeclass_path` and holds the full Python-path to the class. You can usually specify the -`typeclass` as an argument to Evennia's search functions as well as use the class directly to limit -queries. -- The `location` is only relevant for [Objects](./Objects.md) but is a very common way to weed down the -number of candidates before starting to search. The reason is that most in-game commands tend to -operate on things nearby (in the same room) so the choices can be limited from the start. -- The database id or the '#dbref' is unique (and never re-used) within each database table. So while -there is one and only one Object with dbref `#42` there could also be an Account or Script with the -dbref `#42` at the same time. In almost all search methods you can replace the "key" search -criterion with `"#dbref"` to search for that id. This can occasionally be practical and may be what -you are used to from other code bases. But it is considered *bad practice* in Evennia to rely on -hard-coded #dbrefs to do your searches. It makes your code tied to the exact layout of the database. -It's also not very maintainable to have to remember abstract numbers. Passing the actual objects -around and searching by Tags and/or keys will usually get you what you need. - - -## Getting objects inside another - -All in-game [Objects](./Objects.md) have a `.contents` property that returns all objects 'inside' them -(that is, all objects which has its `.location` property set to that object. This is a simple way to -get everything in a room and is also faster since this lookup is cached and won't hit the database. - -- `roomobj.contents` returns a list of all objects inside `roomobj`. -- `obj.contents` same as for a room, except this usually represents the object's inventory -- `obj.location.contents` gets everything in `obj`'s location (including `obj` itself). -- `roomobj.exits` returns all exits starting from `roomobj` (Exits are here defined as Objects with -their `destination` field set). -- `obj.location.contents_get(exclude=obj)` - this helper method returns all objects in `obj`'s -location except `obj`. - -## Searching using `Object.search` - -Say you have a [command](./Commands.md), and you want it to do something to a target. You might be -wondering how you retrieve that target in code, and that's where Evennia's search utilities come in. -In the most common case, you'll often use the `search` method of the `Object` or `Account` -typeclasses. In a command, the `.caller` property will refer back to the object using the command -(usually a `Character`, which is a type of `Object`) while `.args` will contain Command's arguments: - -```python -# e.g. in file mygame/commands/command.py - -from evennia import default_cmds - -class CmdPoke(Command): - """ - Pokes someone. - - Usage: poke - """ - key = "poke" - - def func(self): - """Executes poke command""" - target = self.caller.search(self.args.lstrip()) - if not target: - # we didn't find anyone, but search has already let the - # caller know. We'll just return, since we're done - return - # we found a target! we'll do stuff to them. - target.msg(f"{self.caller} pokes you.") - self.caller.msg(f"You poke {target}.") -``` -By default, the search method of a Character will attempt to find a unique object match for the -string sent to it (`self.args`, in this case, which is the arguments passed to the command by the -player) in the surroundings of the Character - the room or their inventory. If there is no match -found, the return value (which is assigned to `target`) will be `None`, and an appropriate failure -message will be sent to the Character. If there's not a unique match, `None` will again be returned, -and a different error message will be sent asking them to disambiguate the multi-match. By default, -the user can then pick out a specific match using with a number and dash preceding the name of the -object: `character.search("2-pink unicorn")` will try to find the second pink unicorn in the room. - -The search method has many [arguments](github:evennia.objects.objects#defaultcharactersearch) that -allow you to refine the search, such as by designating the location to search in or only matching -specific typeclasses. - -## Searching using `utils.search` - -Sometimes you will want to find something that isn't tied to the search methods of a character or -account. In these cases, Evennia provides a [utility module with a number of search -functions](github:evennia.utils.search). For example, suppose you want a command that will find and -display all the rooms that are tagged as a 'hangout', for people to gather by. Here's a simple -Command to do this: - -```python -# e.g. in file mygame/commands/command.py - -from evennia import default_cmds -from evennia.utils.search import search_tag - -class CmdListHangouts(default_cmds.MuxCommand): - """Lists hangouts""" - key = "hangouts" - - def func(self): - """Executes 'hangouts' command""" - hangouts = search_tag(key="hangout", category="location tags") - self.caller.msg(f"Hangouts available: {', '.join(str(ob) for ob in hangouts)}") -``` - -This uses the `search_tag` function to find all objects previously tagged with [Tags](./Tags.md) -"hangout" and with category "location tags". - -Other important search methods in `utils.search` are - -- `search_object` -- `search_account` -- `search_scripts` -- `search_channel` -- `search_message` -- `search_help` -- `search_tag` - find Objects with a given Tag. [See also here for how to search by -tag](./Tags.md#searching-for-objects-with-a-given-tag). -- `search_account_tag` - find Accounts with a given Tag. -- `search_script_tag` - find Scripts with a given Tag. -- `search_channel_tag` - find Channels with a given Tag. -- `search_object_attribute` - find Objects with a given Attribute. -- `search_account_attribute` - find Accounts with a given Attribute. -- `search_attribute_object` - this returns the actual Attribute, not the object it sits on. - -> Note: All search functions return a Django `queryset` which is technically a list-like -representation of the database-query it's about to do. Only when you convert it to a real list, loop -over it or try to slice or access any of its contents will the datbase-lookup happen. This means you -could yourself customize the query further if you know what you are doing (see the next section). - -## Queries in Django - -*This is an advanced topic.* - -Evennia's search methods should be sufficient for the vast majority of situations. But eventually -you might find yourself trying to figure out how to get searches for unusual circumstances: Maybe -you want to find all characters who are *not* in rooms tagged as hangouts *and* have the lycanthrope -tag *and* whose names start with a vowel, but *not* with 'Ab', and *only if* they have 3 or more -objects in their inventory ... You could in principle use one of the earlier search methods to find -all candidates and then loop over them with a lot of if statements in raw Python. But you can do -this much more efficiently by querying the database directly. - -Enter [django's querysets](https://docs.djangoproject.com/en/1.11/ref/models/querysets/). A QuerySet -is the representation of a database query and can be modified as desired. Only once one tries to -retrieve the data of that query is it *evaluated* and does an actual database request. This is -useful because it means you can modify a query as much as you want (even pass it around) and only -hit the database once you are happy with it. -Evennia's search functions are themselves an even higher level wrapper around Django's queries, and -many search methods return querysets. That means that you could get the result from a search -function and modify the resulting query to your own ends to further tweak what you search for. - -Evaluated querysets can either contain objects such as Character objects, or lists of values derived -from the objects. Queries usually use the 'manager' object of a class, which by convention is the -`.objects` attribute of a class. For example, a query of Accounts that contain the letter 'a' could -be: - -```python - from typeclasses.accounts import Account - -queryset = Account.objects.filter(username__contains='a') - -``` - -The `filter` method of a manager takes arguments that allow you to define the query, and you can -continue to refine the query by calling additional methods until you evaluate the queryset, causing -the query to be executed and return a result. For example, if you have the result above, you could, -without causing the queryset to be evaluated yet, get rid of matches that contain the letter 'e by -doing this: - -```python -queryset = result.exclude(username__contains='e') - -``` - -> You could also have chained `.exclude` directly to the end of the previous line. - -Once you try to access the result, the queryset will be evaluated automatically under the hood: - -```python -accounts = list(queryset) # this fills list with matches - -for account in queryset: - # do something with account - -accounts = queryset[:4] # get first four matches -account = queryset[0] # get first match -# etc - -``` - -### Limiting by typeclass - -Although `Character`s, `Exit`s, `Room`s, and other children of `DefaultObject` all shares the same -underlying database table, Evennia provides a shortcut to do more specific queries only for those -typeclasses. For example, to find only `Character`s whose names start with 'A', you might do: - -```python -Character.objects.filter(db_key__startswith="A") - -``` - -If Character has a subclass `Npc` and you wanted to find only Npc's you'd instead do - -```python -Npc.objects.filter(db_key__startswith="A") - -``` - -If you wanted to search both Characters and all its subclasses (like Npc) you use the `*_family` -method which is added by Evennia: - - -```python -Character.objects.filter_family(db_key__startswith="A") -``` - -The higher up in the inheritance hierarchy you go the more objects will be included in these -searches. There is one special case, if you really want to include *everything* from a given -database table. You do that by searching on the database model itself. These are named `ObjectDB`, -`AccountDB`, `ScriptDB` etc. - -```python -from evennia import AccountDB - -# all Accounts in the database, regardless of typeclass -all = AccountDB.objects.all() - -``` - -Here are the most commonly used methods to use with the `objects` managers: - -- `filter` - query for a listing of objects based on search criteria. Gives empty queryset if none -were found. -- `get` - query for a single match - raises exception if none were found, or more than one was -found. -- `all` - get all instances of the particular type. -- `filter_family` - like `filter`, but search all sub classes as well. -- `get_family` - like `get`, but search all sub classes as well. -- `all_family` - like `all`, but return entities of all subclasses as well. - -## Multiple conditions - -If you pass more than one keyword argument to a query method, the query becomes an `AND` -relationship. For example, if we want to find characters whose names start with "A" *and* are also -werewolves (have the `lycanthrope` tag), we might do: - -```python -queryset = Character.objects.filter(db_key__startswith="A", db_tags__db_key="lycanthrope") -``` - -To exclude lycanthropes currently in rooms tagged as hangouts, we might tack on an `.exclude` as -before: - -```python -queryset = quersyet.exclude(db_location__db_tags__db_key="hangout") -``` - -Note the syntax of the keywords in building the queryset. For example, `db_location` is the name of -the database field sitting on (in this case) the `Character` (Object). Double underscore `__` works -like dot-notation in normal Python (it's used since dots are not allowed in keyword names). So the -instruction `db_location__db_tags__db_key="hangout"` should be read as such: - -1. "On the `Character` object ... (this comes from us building this queryset using the -`Character.objects` manager) -2. ... get the value of the `db_location` field ... (this references a Room object, normally) -3. ... on that location, get the value of the `db_tags` field ... (this is a many-to-many field that -can be treated like an object for this purpose. It references all tags on the location) -4. ... through the `db_tag` manager, find all Tags having a field `db_key` set to the value -"hangout"." - -This may seem a little complex at first, but this syntax will work the same for all queries. Just -remember that all *database-fields* in Evennia are prefaced with `db_`. So even though Evennia is -nice enough to alias the `db_key` field so you can normally just do `char.key` to get a character's -name, the database field is actually called `db_key` and the real name must be used for the purpose -of building a query. - -> Don't confuse database fields with [Attributes](./Attributes.md) you set via `obj.db.attr = 'foo'` or -`obj.attributes.add()`. Attributes are custom database entities *linked* to an object. They are not -separate fields *on* that object like `db_key` or `db_location` are. You can get attached Attributes -manually through the `db_attributes` many-to-many field in the same way as `db_tags` above. - -### Complex queries - -What if you want to have a query with with `OR` conditions or negated requirements (`NOT`)? Enter -Django's Complex Query object, -[Q](https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q-objects). `Q()` -objects take a normal django keyword query as its arguments. The special thing is that these Q -objects can then be chained together with set operations: `|` for OR, `&` for AND, and preceded with -`~` for NOT to build a combined, complex query. - -In our original Lycanthrope example we wanted our werewolves to have names that could start with any -vowel except for the specific beginning "ab". - -```python -from django.db.models import Q -from typeclasses.characters import Character - -query = Q() -for letter in ("aeiouy"): - query |= Q(db_key__istartswith=letter) -query &= ~Q(db_key__istartswith="ab") -query = Character.objects.filter(query) - -list_of_lycanthropes = list(query) -``` - -In the above example, we construct our query our of several Q objects that each represent one part -of the query. We iterate over the list of vowels, and add an `OR` condition to the query using `|=` -(this is the same idea as using `+=` which may be more familiar). Each `OR` condition checks that -the name starts with one of the valid vowels. Afterwards, we add (using `&=`) an `AND` condition -that is negated with the `~` symbol. In other words we require that any match should *not* start -with the string "ab". Note that we don't actually hit the database until we convert the query to a -list at the end (we didn't need to do that either, but could just have kept the query until we -needed to do something with the matches). - -### Annotations and `F` objects - -What if we wanted to filter on some condition that isn't represented easily by a field on the -object? Maybe we want to find rooms only containing five or more objects? - -We *could* retrieve all interesting candidates and run them through a for-loop to get and count -their `.content` properties. We'd then just return a list of only those objects with enough -contents. It would look something like this (note: don't actually do this!): - -```python -# probably not a good idea to do it this way - -from typeclasses.rooms import Room - -queryset = Room.objects.all() # get all Rooms -rooms = [room for room in queryset if len(room.contents) >= 5] - -``` - -Once the number of rooms in your game increases, this could become quite expensive. Additionally, in -some particular contexts, like when using the web features of Evennia, you must have the result as a -queryset in order to use it in operations, such as in Django's admin interface when creating list -filters. - -Enter [F objects](https://docs.djangoproject.com/en/1.11/ref/models/expressions/#f-expressions) and -*annotations*. So-called F expressions allow you to do a query that looks at a value of each object -in the database, while annotations allow you to calculate and attach a value to a query. So, let's -do the same example as before directly in the database: - -```python -from typeclasses.rooms import Room -from django.db.models import Count - -room_count = Room.objects.annotate(num_objects=Count('locations_set')) -queryset = room_count.filter(num_objects__gte=5) - -rooms = (Room.objects.annotate(num_objects=Count('locations_set')) - .filter(num_objects__gte=5)) - -rooms = list(rooms) - -``` -Here we first create an annotation `num_objects` of type `Count`, which is a Django class. Note that -use of `location_set` in that `Count`. The `*_set` is a back-reference automatically created by -Django. In this case it allows you to find all objects that *has the current object as location*. -Once we have those, they are counted. -Next we filter on this annotation, using the name `num_objects` as something we can filter for. We -use `num_objects__gte=5` which means that `num_objects` should be greater than 5. This is a little -harder to get one's head around but much more efficient than lopping over all objects in Python. - -What if we wanted to compare two parameters against one another in a query? For example, what if -instead of having 5 or more objects, we only wanted objects that had a bigger inventory than they -had tags? Here an F-object comes in handy: - -```python -from django.db.models import Count, F -from typeclasses.rooms import Room - -result = (Room.objects.annotate(num_objects=Count('locations_set'), - num_tags=Count('db_tags')) - .filter(num_objects__gt=F('num_tags'))) -``` - -F-objects allows for wrapping an annotated structure on the right-hand-side of the expression. It -will be evaluated on-the-fly as needed. - -### Grouping By and Values - -Suppose you used tags to mark someone belonging an organization. Now you want to make a list and -need to get the membership count of every organization all at once. That's where annotations and the -`.values_list` queryset method come in. Values/Values Lists are an alternate way of returning a -queryset - instead of objects, you get a list of dicts or tuples that hold selected properties from -the the matches. It also allows you a way to 'group up' queries for returning information. For -example, to get a display about each tag per Character and the names of the tag: - -```python -result = (Character.objects.filter(db_tags__db_category="organization") - .values_list('db_tags__db_key') - .annotate(cnt=Count('id')) - .order_by('-cnt')) -``` -The result queryset will be a list of tuples ordered in descending order by the number of matches, -in a format like the following: -``` -[('Griatch Fanclub', 3872), ("Chainsol's Ainneve Testers", 2076), ("Blaufeuer's Whitespace Fixers", -1903), - ("Volund's Bikeshed Design Crew", 1764), ("Tehom's Misanthropes", 1)] diff --git a/docs/0.9.5/_sources/Tutorial-Tweeting-Game-Stats.md.txt b/docs/0.9.5/_sources/Tutorial-Tweeting-Game-Stats.md.txt deleted file mode 100644 index e17197af8c..0000000000 --- a/docs/0.9.5/_sources/Tutorial-Tweeting-Game-Stats.md.txt +++ /dev/null @@ -1,96 +0,0 @@ -# Tutorial Tweeting Game Stats - - -This tutorial will create a simple script that will send a tweet to your already configured twitter -account. Please see: [How to connect Evennia to Twitter](./How-to-connect-Evennia-to-Twitter.md) if you -haven't already done so. - -The script could be expanded to cover a variety of statistics you might wish to tweet about -regularly, from player deaths to how much currency is in the economy etc. - -```python -# evennia/typeclasses/tweet_stats.py - -import twitter -from random import randint -from django.conf import settings -from evennia import ObjectDB -from evennia import spawner -from evennia import logger -from evennia import DefaultScript - -class TweetStats(DefaultScript): - """ - This implements the tweeting of stats to a registered twitter account - """ - - # standard Script hooks - - def at_script_creation(self): - "Called when script is first created" - - self.key = "tweet_stats" - self.desc = "Tweets interesting stats about the game" - self.interval = 86400 # 1 day timeout - self.start_delay = False - - def at_repeat(self): - """ - This is called every self.interval seconds to tweet interesting stats about the game. - """ - - api = twitter.Api(consumer_key='consumer_key', - consumer_secret='consumer_secret', - access_token_key='access_token_key', - access_token_secret='access_token_secret') - - number_tweet_outputs = 2 - - tweet_output = randint(1, number_tweet_outputs) - - if tweet_output == 1: - ##Game Chars, Rooms, Objects taken from @stats command - nobjs = ObjectDB.objects.count() - base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS - nchars = ObjectDB.objects.filter(db_typeclass_path=base_char_typeclass).count() - nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=bas -e_char_typeclass).count() - nexits = ObjectDB.objects.filter(db_location__isnull=False, -db_destination__isnull=False).count() - nother = nobjs - nchars - nrooms - nexits - tweet = "Chars: %s, Rooms: %s, Objects: %s" %(nchars, nrooms, nother) - else: - if tweet_output == 2: ##Number of prototypes and 3 random keys - taken from @spawn -command - prototypes = spawner.spawn(return_prototypes=True) - - keys = prototypes.keys() - nprots = len(prototypes) - tweet = "Prototype Count: %s Random Keys: " % nprots - - tweet += " %s" % keys[randint(0,len(keys)-1)] - for x in range(0,2): ##tweet 3 - tweet += ", %s" % keys[randint(0,len(keys)-1)] - # post the tweet - try: - response = api.PostUpdate(tweet) - except: - logger.log_trace("Tweet Error: When attempting to tweet %s" % tweet) -``` - -In the `at_script_creation` method, we configure the script to fire immediately (useful for testing) -and setup the delay (1 day) as well as script information seen when you use `@scripts` - -In the `at_repeat` method (which is called immediately and then at interval seconds later) we setup -the Twitter API (just like in the initial configuration of twitter). numberTweetOutputs is used to -show how many different types of outputs we have (in this case 2). We then build the tweet based on -randomly choosing between these outputs. - -1. Shows the number of Player Characters, Rooms and Other/Objects -2. Shows the number of prototypes currently in the game and then selects 3 random keys to show - -[Scripts Information](./Scripts.md) will show you how to add it as a Global script, however, for testing -it may be useful to start/stop it quickly from within the game. Assuming that you create the file -as `mygame/typeclasses/tweet_stats.py` it can be started by using the following command - - @script Here = tweet_stats.TweetStats diff --git a/docs/0.9.5/_sources/Tutorial-Vehicles.md.txt b/docs/0.9.5/_sources/Tutorial-Vehicles.md.txt deleted file mode 100644 index cd48177f67..0000000000 --- a/docs/0.9.5/_sources/Tutorial-Vehicles.md.txt +++ /dev/null @@ -1,422 +0,0 @@ -# Tutorial Vehicles - - -This tutorial explains how you can create vehicles that can move around in your world. The tutorial -will explain how to create a train, but this can be equally applied to create other kind of vehicles -(cars, planes, boats, spaceships, submarines, ...). - -## How it works - -Objects in Evennia have an interesting property: you can put any object inside another object. This -is most obvious in rooms: a room in Evennia is just like any other game object (except rooms tend to -not themselves be inside anything else). - -Our train will be similar: it will be an object that other objects can get inside. We then simply -move the Train, which brings along everyone inside it. - -## Creating our train object - -The first step we need to do is create our train object, including a new typeclass. To do this, -create a new file, for instance in `mygame/typeclasses/train.py` with the following content: - -```python -# file mygame/typeclasses/train.py - -from evennia import DefaultObject - -class TrainObject(DefaultObject): - - def at_object_creation(self): - # We'll add in code here later. - pass - -``` - -Now we can create our train in our game: - -``` -@create/drop train:train.TrainObject -``` - -Now this is just an object that doesn't do much yet... but we can already force our way inside it -and back (assuming we created it in limbo). - -``` -@tel train -@tel limbo -``` - -## Entering and leaving the train - -Using the `@tel`command like shown above is obviously not what we want. `@tel` is an admin command -and normal players will thus never be able to enter the train! It is also not really a good idea to -use [Exits](./Objects.md#exits) to get in and out of the train - Exits are (at least by default) objects -too. They point to a specific destination. If we put an Exit in this room leading inside the train -it would stay here when the train moved away (still leading into the train like a magic portal!). In -the same way, if we put an Exit object inside the train, it would always point back to this room, -regardless of where the Train has moved. Now, one *could* define custom Exit types that move with -the train or change their destination in the right way - but this seems to be a pretty cumbersome -solution. - -What we will do instead is to create some new [commands](./Commands.md): one for entering the train and -another for leaving it again. These will be stored *on the train object* and will thus be made -available to whomever is either inside it or in the same room as the train. - -Let's create a new command module as `mygame/commands/train.py`: - -```python -# mygame/commands/train.py - -from evennia import Command, CmdSet - -class CmdEnterTrain(Command): - """ - entering the train - - Usage: - enter train - - This will be available to players in the same location - as the train and allows them to embark. - """ - - key = "enter train" - locks = "cmd:all()" - - def func(self): - train = self.obj - self.caller.msg("You board the train.") - self.caller.move_to(train) - - -class CmdLeaveTrain(Command): - """ - leaving the train - - Usage: - leave train - - This will be available to everyone inside the - train. It allows them to exit to the train's - current location. - """ - - key = "leave train" - locks = "cmd:all()" - - def func(self): - train = self.obj - parent = train.location - self.caller.move_to(parent) - - -class CmdSetTrain(CmdSet): - - def at_cmdset_creation(self): - self.add(CmdEnterTrain()) - self.add(CmdLeaveTrain()) -``` -Note that while this seems like a lot of text, the majority of lines here are taken up by -documentation. - -These commands are work in a pretty straightforward way: `CmdEnterTrain` moves the location of the -player to inside the train and `CmdLeaveTrain` does the opposite: it moves the player back to the -current location of the train (back outside to its current location). We stacked them in a -[cmdset](./Command-Sets.md) `CmdSetTrain` so they can be used. - -To make the commands work we need to add this cmdset to our train typeclass: - -```python -# file mygame/typeclasses/train.py - -from evennia import DefaultObject -from commands.train import CmdSetTrain - -class TrainObject(DefaultObject): - - def at_object_creation(self): - self.cmdset.add_default(CmdSetTrain) - -``` - -If we now `@reload` our game and reset our train, those commands should work and we can now enter -and leave the train: - -``` -@reload -@typeclass/force/reset train = train.TrainObject -enter train -leave train -``` - -Note the switches used with the `@typeclass` command: The `/force` switch is necessary to assign our -object the same typeclass we already have. The `/reset` re-triggers the typeclass' -`at_object_creation()` hook (which is otherwise only called the very first an instance is created). -As seen above, when this hook is called on our train, our new cmdset will be loaded. - -## Locking down the commands - -If you have played around a bit, you've probably figured out that you can use `leave train` when -outside the train and `enter train` when inside. This doesn't make any sense ... so let's go ahead -and fix that. We need to tell Evennia that you can not enter the train when you're already inside -or leave the train when you're outside. One solution to this is [locks](./Locks.md): we will lock down -the commands so that they can only be called if the player is at the correct location. - -Right now commands defaults to the lock `cmd:all()`. The `cmd` lock type in combination with the -`all()` lock function means that everyone can run those commands as long as they are in the same -room as the train *or* inside the train. We're going to change this to check the location of the -player and *only* allow access if they are inside the train. - -First of all we need to create a new lock function. Evennia comes with many lock functions built-in -already, but none that we can use for locking a command in this particular case. Create a new entry -in `mygame/server/conf/lockfuncs.py`: - -```python - -# file mygame/server/conf/lockfuncs.py - -def cmdinside(accessing_obj, accessed_obj, *args, **kwargs): - """ - Usage: cmdinside() - Used to lock commands and only allows access if the command - is defined on an object which accessing_obj is inside of. - """ - return accessed_obj.obj == accessing_obj.location - -``` -If you didn't know, Evennia is by default set up to use all functions in this module as lock -functions (there is a setting variable that points to it). - -Our new lock function, `cmdinside`, is to be used by Commands. The `accessed_obj` is the Command -object (in our case this will be `CmdEnterTrain` and `CmdLeaveTrain`) — Every command has an `obj` -property: this is the the object on which the command "sits". Since we added those commands to our -train object, the `.obj` property will be set to the train object. Conversely, `accessing_obj` is -the object that called the command: in our case it's the Character trying to enter or leave the -train. - -What this function does is to check that the player's location is the same as the train object. If -it is, it means the player is inside the train. Otherwise it means the player is somewhere else and -the check will fail. - -The next step is to actually use this new lock function to create a lock of type `cmd`: - -```python -# file commands/train.py -... -class CmdEnterTrain(Command): - key = "enter train" - locks = "cmd:not cmdinside()" - # ... - -class CmdLeaveTrain(Command): - key = "leave train" - locks = "cmd:cmdinside()" - # ... -``` - -Notice how we use the `not` here so that we can use the same `cmdinside` to check if we are inside -and outside, without having to create two separate lock functions. After a `@reload` our commands -should be locked down appropriately and you should only be able to use them at the right places. - -> Note: If you're logged in as the super user (user `#1`) then this lock will not work: the super -user ignores lock functions. In order to use this functionality you need to `@quell` first. - -## Making our train move - -Now that we can enter and leave the train correctly, it's time to make it move. There are different -things we need to consider for this: - -* Who can control your vehicle? The first player to enter it, only players that have a certain -"drive" skill, automatically? -* Where should it go? Can the player steer the vehicle to go somewhere else or will it always follow -the same route? - -For our example train we're going to go with automatic movement through a predefined route (its -track). The train will stop for a bit at the start and end of the route to allow players to enter -and leave it. - -Go ahead and create some rooms for our train. Make a list of the room ids along the route (using the -`@ex` command). - -``` -@dig/tel South station -@ex # note the id of the station -@tunnel/tel n = Following a railroad -@ex # note the id of the track -@tunnel/tel n = Following a railroad -... -@tunnel/tel n = North Station -``` - -Put the train onto the tracks: - -``` -@tel south station -@tel train = here -``` - -Next we will tell the train how to move and which route to take. - -```python -# file typeclasses/train.py - -from evennia import DefaultObject, search_object - -from commands.train import CmdSetTrain - -class TrainObject(DefaultObject): - - def at_object_creation(self): - self.cmdset.add_default(CmdSetTrain) - self.db.driving = False - # The direction our train is driving (1 for forward, -1 for backwards) - self.db.direction = 1 - # The rooms our train will pass through (change to fit your game) - self.db.rooms = ["#2", "#47", "#50", "#53", "#56", "#59"] - - def start_driving(self): - self.db.driving = True - - def stop_driving(self): - self.db.driving = False - - def goto_next_room(self): - currentroom = self.location.dbref - idx = self.db.rooms.index(currentroom) + self.db.direction - - if idx < 0 or idx >= len(self.db.rooms): - # We reached the end of our path - self.stop_driving() - # Reverse the direction of the train - self.db.direction *= -1 - else: - roomref = self.db.rooms[idx] - room = search_object(roomref)[0] - self.move_to(room) - self.msg_contents("The train is moving forward to %s." % (room.name, )) -``` - -We added a lot of code here. Since we changed the `at_object_creation` to add in variables we will -have to reset our train object like earlier (using the `@typeclass/force/reset` command). - -We are keeping track of a few different things now: whether the train is moving or standing still, -which direction the train is heading to and what rooms the train will pass through. - -We also added some methods: one to start moving the train, another to stop and a third that actually -moves the train to the next room in the list. Or makes it stop driving if it reaches the last stop. - -Let's try it out, using `@py` to call the new train functionality: - -``` -@reload -@typeclass/force/reset train = train.TrainObject -enter train -@py here.goto_next_room() -``` - -You should see the train moving forward one step along the rail road. - -## Adding in scripts - -If we wanted full control of the train we could now just add a command to step it along the track -when desired. We want the train to move on its own though, without us having to force it by manually -calling the `goto_next_room` method. - -To do this we will create two [scripts](./Scripts.md): one script that runs when the train has stopped at -a station and is responsible for starting the train again after a while. The other script will take -care of the driving. - -Let's make a new file in `mygame/typeclasses/trainscript.py` - -```python -# file mygame/typeclasses/trainscript.py - -from evennia import DefaultScript - -class TrainStoppedScript(DefaultScript): - - def at_script_creation(self): - self.key = "trainstopped" - self.interval = 30 - self.persistent = True - self.repeats = 1 - self.start_delay = True - - def at_repeat(self): - self.obj.start_driving() - - def at_stop(self): - self.obj.scripts.add(TrainDrivingScript) - - -class TrainDrivingScript(DefaultScript): - - def at_script_creation(self): - self.key = "traindriving" - self.interval = 1 - self.persistent = True - - def is_valid(self): - return self.obj.db.driving - - def at_repeat(self): - if not self.obj.db.driving: - self.stop() - else: - self.obj.goto_next_room() - - def at_stop(self): - self.obj.scripts.add(TrainStoppedScript) -``` - -Those scripts work as a state system: when the train is stopped, it waits for 30 seconds and then -starts again. When the train is driving, it moves to the next room every second. The train is always -in one of those two states - both scripts take care of adding the other one once they are done. - -As a last step we need to link the stopped-state script to our train, reload the game and reset our -train again., and we're ready to ride it around! - -```python -# file typeclasses/train.py - -from typeclasses.trainscript import TrainStoppedScript - -class TrainObject(DefaultObject): - - def at_object_creation(self): - # ... - self.scripts.add(TrainStoppedScript) -``` - -``` -@reload -@typeclass/force/reset train = train.TrainObject -enter train - -# output: -< The train is moving forward to Following a railroad. -< The train is moving forward to Following a railroad. -< The train is moving forward to Following a railroad. -... -< The train is moving forward to Following a railroad. -< The train is moving forward to North station. - -leave train -``` - -Our train will stop 30 seconds at each end station and then turn around to go back to the other end. - -## Expanding - -This train is very basic and still has some flaws. Some more things to do: - -* Make it look like a train. -* Make it impossible to exit and enter the train mid-ride. This could be made by having the -enter/exit commands check so the train is not moving before allowing the caller to proceed. -* Have train conductor commands that can override the automatic start/stop. -* Allow for in-between stops between the start- and end station -* Have a rail road track instead of hard-coding the rooms in the train object. This could for -example be a custom [Exit](./Objects.md#exits) only traversable by trains. The train will follow the -track. Some track segments can split to lead to two different rooms and a player can switch the -direction to which room it goes. -* Create another kind of vehicle! diff --git a/docs/0.9.5/_sources/Tutorial-World-Introduction.md.txt b/docs/0.9.5/_sources/Tutorial-World-Introduction.md.txt deleted file mode 100644 index df295858b9..0000000000 --- a/docs/0.9.5/_sources/Tutorial-World-Introduction.md.txt +++ /dev/null @@ -1,109 +0,0 @@ -# Tutorial World Introduction - - -The *Tutorial World* is a small and functioning MUD-style game world. It is intended to be -deconstructed and used as a way to learn Evennia. The game consists of a single-player quest and -has some 20 rooms that you can explore as you seek to discover the whereabouts of a mythical weapon. - -The source code is fully documented. You can find the whole thing in -`evennia/contrib/tutorial_world/`. - -Some features exemplified by the tutorial world: - -- Tutorial command, giving "behind-the-scenes" help for every room and some of the special objects -- Rooms with custom `return_appearance` to show details. -- Hidden exits -- Objects with multiple custom interactions -- Large-area rooms -- Outdoor weather rooms -- Dark room, needing light source -- Puzzle object -- Multi-room puzzle -- Aggressive mobile with roam, pursue and battle state-engine AI -- Weapons, also used by mobs -- Simple combat system with attack/defend commands -- Object spawning -- Teleporter trap rooms - - -## Install - -The tutorial world consists of a few modules in `evennia/contrib/tutorial_world/` containing custom -[Typeclasses](./Typeclasses.md) for [rooms and objects](./Objects.md) and associated [Commands](./Commands.md). - -These reusable bits and pieces are then put together into a functioning game area ("world" is maybe -too big a word for such a small zone) using a [batch script](./Batch-Processors.md) called `build.ev`. To -install, log into the server as the superuser (user #1) and run: - - @batchcommand tutorial_world.build - -The world will be built (this might take a while, so don't rerun the command even if it seems the -system has frozen). After finishing you will end up back in Limbo with a new exit called `tutorial`. - -An alternative is - - @batchcommand/interactive tutorial_world.build - -with the /interactive switch you are able to step through the building process at your own pace to -see what happens in detail. - -## Quelling and permissions in the tutorial-world - -Non-superusers entering the tutorial will be auto-`quelled` so they play with their Character's -permission. As superuser you will not be auto-quelled, but it's recommended that you still `quell` -manually to play the tutorial "correctly". The reason for this is that many game systems ignore the -presence of a superuser and will thus not work as normal. - -Use `unquell` if you want to get back your main account-level permissions to examine things under -the hood. When you exit the tutorial (either by winning or using the `abort/give up` command) you -will automatically be unquelled. - -## Gameplay - -![the castle off the moor](https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/22916c25-6299-453d-a221-446ec839f567/da2pmzu-46d63c6d-9cdc-41dd-87d6-1106db5a5e1a.jpg/v1/fill/w_600,h_849,q_75,strp/the_castle_off_the_moor_by_griatch_art_da2pmzu-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD04NDkiLCJwYXRoIjoiXC9mXC8yMjkxNmMyNS02Mjk5LTQ1M2QtYTIyMS00NDZlYzgzOWY1NjdcL2RhMnBtenUtNDZkNjNjNmQtOWNkYy00MWRkLTg3ZDYtMTEwNmRiNWE1ZTFhLmpwZyIsIndpZHRoIjoiPD02MDAifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.omuS3D1RmFiZCy9OSXiIita-HxVGrBok3_7asq0rflw) - - -*To get into the mood of this miniature quest, imagine you are an adventurer out to find fame and -fortune. You have heard rumours of an old castle ruin by the coast. In its depth a warrior princess -was buried together with her powerful magical weapon - a valuable prize, if it's true. Of course -this is a chance to adventure that you cannot turn down!* - -*You reach the ocean in the midst of a raging thunderstorm. With wind and rain screaming in your -face you stand where the moor meets the sea along a high, rocky coast ...* - -- Look at everything. -- Some objects are interactive in more than one way. Use the normal `help` command to get a feel for -which commands are available at any given time. (use the command `tutorial` to get insight behind -the scenes of the tutorial). - -- In order to fight, you need to first find some type of weapon. -- *slash* is a normal attack -- *stab* launches an attack that makes more damage but has a lower chance to hit. -- *defend* will lower the chance to taking damage on your enemy's next attack. -- You *can* run from a fight that feels too deadly. Expect to be chased though. -- Being defeated is a part of the experience ... - -## Uninstall - -Uninstalling the tutorial world basically means deleting all the rooms and objects it consists of. -First, move out of the tutorial area. - - @find tut#01 - @find tut#16 - -This should locate the first and last rooms created by `build.ev` - *Intro* and *Outro*. If you -installed normally, everything created between these two numbers should be part of the tutorial. -Note their dbref numbers, for example 5 and 80. Next we just delete all objects in that range: - - @del 5-80 - -You will see some errors since some objects are auto-deleted and so cannot be found when the delete -mechanism gets to them. That's fine. You should have removed the tutorial completely once the -command finishes. - -## Notes - -When reading and learning from the code, keep in mind that *Tutorial World* was created with a very -specific goal: to install easily and to not permanently modify the rest of the server. It therefore -goes to some length to use only temporary solutions and to clean up after -itself. diff --git a/docs/0.9.5/_sources/Tutorial-for-basic-MUSH-like-game.md.txt b/docs/0.9.5/_sources/Tutorial-for-basic-MUSH-like-game.md.txt deleted file mode 100644 index 3f925151be..0000000000 --- a/docs/0.9.5/_sources/Tutorial-for-basic-MUSH-like-game.md.txt +++ /dev/null @@ -1,651 +0,0 @@ -# Tutorial for basic MUSH like game - - -This tutorial lets you code a small but complete and functioning MUSH-like game in Evennia. A -[MUSH](http://en.wikipedia.org/wiki/MUSH) is, for our purposes, a class of roleplay-centric games -focused on free form storytelling. Even if you are not interested in MUSH:es, this is still a good -first game-type to try since it's not so code heavy. You will be able to use the same principles for -building other types of games. - -The tutorial starts from scratch. If you did the [First Steps Coding](./First-Steps-Coding.md) tutorial -already you should have some ideas about how to do some of the steps already. - -The following are the (very simplistic and cut-down) features we will implement (this was taken from -a feature request from a MUSH user new to Evennia). A Character in this system should: - -- Have a “Power” score from 1 to 10 that measures how strong they are (stand-in for the stat -system). -- Have a command (e.g. `+setpower 4`) that sets their power (stand-in for character generation -code). -- Have a command (e.g. `+attack`) that lets them roll their power and produce a "Combat Score" -between `1` and `10*Power`, displaying the result and editing their object to record this number -(stand-in for `+actions` in the command code). -- Have a command that displays everyone in the room and what their most recent "Combat Score" roll -was (stand-in for the combat code). -- Have a command (e.g. `+createNPC Jenkins`) that creates an NPC with full abilities. -- Have a command to control NPCs, such as `+npc/cmd (name)=(command)` (stand-in for the NPC -controlling code). - -In this tutorial we will assume you are starting from an empty database without any previous -modifications. - -## Server Settings - -To emulate a MUSH, the default `MULTISESSION_MODE=0` is enough (one unique session per -account/character). This is the default so you don't need to change anything. You will still be able -to puppet/unpuppet objects you have permission to, but there is no character selection out of the -box in this mode. - -We will assume our game folder is called `mygame` henceforth. You should be fine with the default -SQLite3 database. - -## Creating the Character - -First thing is to choose how our Character class works. We don't need to define a special NPC object --- an NPC is after all just a Character without an Account currently controlling them. - -Make your changes in the `mygame/typeclasses/characters.py` file: - -```python -# mygame/typeclasses/characters.py - -from evennia import DefaultCharacter - -class Character(DefaultCharacter): - """ - [...] - """ - def at_object_creation(self): - "This is called when object is first created, only." - self.db.power = 1 - self.db.combat_score = 1 -``` - -We defined two new [Attributes](./Attributes.md) `power` and `combat_score` and set them to default -values. Make sure to `@reload` the server if you had it already running (you need to reload every -time you update your python code, don't worry, no accounts will be disconnected by the reload). - -Note that only *new* characters will see your new Attributes (since the `at_object_creation` hook is -called when the object is first created, existing Characters won't have it). To update yourself, -run - - @typeclass/force self - -This resets your own typeclass (the `/force` switch is a safety measure to not do this -accidentally), this means that `at_object_creation` is re-run. - - examine self - -Under the "Persistent attributes" heading you should now find the new Attributes `power` and `score` -set on yourself by `at_object_creation`. If you don't, first make sure you `@reload`ed into the new -code, next look at your server log (in the terminal/console) to see if there were any syntax errors -in your code that may have stopped your new code from loading correctly. - -## Character Generation - -We assume in this example that Accounts first connect into a "character generation area". Evennia -also supports full OOC menu-driven character generation, but for this example, a simple start room -is enough. When in this room (or rooms) we allow character generation commands. In fact, character -generation commands will *only* be available in such rooms. - -Note that this again is made so as to be easy to expand to a full-fledged game. With our simple -example, we could simply set an `is_in_chargen` flag on the account and have the `+setpower` command -check it. Using this method however will make it easy to add more functionality later. - -What we need are the following: - -- One character generation [Command](./Commands.md) to set the "Power" on the `Character`. -- A chargen [CmdSet](./Command-Sets.md) to hold this command. Lets call it `ChargenCmdset`. -- A custom `ChargenRoom` type that makes this set of commands available to players in such rooms. -- One such room to test things in. - -### The +setpower command - -For this tutorial we will add all our new commands to `mygame/commands/command.py` but you could -split your commands into multiple module if you prefered. - -For this tutorial character generation will only consist of one [Command](./Commands.md) to set the -Character s "power" stat. It will be called on the following MUSH-like form: - - +setpower 4 - -Open `command.py` file. It contains documented empty templates for the base command and the -"MuxCommand" type used by default in Evennia. We will use the plain `Command` type here, the -`MuxCommand` class offers some extra features like stripping whitespace that may be useful - if so, -just import from that instead. - -Add the following to the end of the `command.py` file: - -```python -# end of command.py -from evennia import Command # just for clarity; already imported above - -class CmdSetPower(Command): - """ - set the power of a character - - Usage: - +setpower <1-10> - - This sets the power of the current character. This can only be - used during character generation. - """ - - key = "+setpower" - help_category = "mush" - - def func(self): - "This performs the actual command" - errmsg = "You must supply a number between 1 and 10." - if not self.args: - self.caller.msg(errmsg) - return - try: - power = int(self.args) - except ValueError: - self.caller.msg(errmsg) - return - if not (1 <= power <= 10): - self.caller.msg(errmsg) - return - # at this point the argument is tested as valid. Let's set it. - self.caller.db.power = power - self.caller.msg("Your Power was set to %i." % power) -``` -This is a pretty straightforward command. We do some error checking, then set the power on ourself. -We use a `help_category` of "mush" for all our commands, just so they are easy to find and separate -in the help list. - -Save the file. We will now add it to a new [CmdSet](./Command-Sets.md) so it can be accessed (in a full -chargen system you would of course have more than one command here). - -Open `mygame/commands/default_cmdsets.py` and import your `command.py` module at the top. We also -import the default `CmdSet` class for the next step: - -```python -from evennia import CmdSet -from commands import command -``` - -Next scroll down and define a new command set (based on the base `CmdSet` class we just imported at -the end of this file, to hold only our chargen-specific command(s): - -```python -# end of default_cmdsets.py - -class ChargenCmdset(CmdSet): - """ - This cmdset it used in character generation areas. - """ - key = "Chargen" - def at_cmdset_creation(self): - "This is called at initialization" - self.add(command.CmdSetPower()) -``` - -In the future you can add any number of commands to this cmdset, to expand your character generation -system as you desire. Now we need to actually put that cmdset on something so it's made available to -users. We could put it directly on the Character, but that would make it available all the time. -It's cleaner to put it on a room, so it's only available when players are in that room. - -### Chargen areas - -We will create a simple Room typeclass to act as a template for all our Chargen areas. Edit -`mygame/typeclasses/rooms.py` next: - -```python -from commands.default_cmdsets import ChargenCmdset - -# ... -# down at the end of rooms.py - -class ChargenRoom(Room): - """ - This room class is used by character-generation rooms. It makes - the ChargenCmdset available. - """ - def at_object_creation(self): - "this is called only at first creation" - self.cmdset.add(ChargenCmdset, permanent=True) -``` -Note how new rooms created with this typeclass will always start with `ChargenCmdset` on themselves. -Don't forget the `permanent=True` keyword or you will lose the cmdset after a server reload. For -more information about [Command Sets](./Command-Sets.md) and [Commands](./Commands.md), see the respective -links. - -### Testing chargen - -First, make sure you have `@reload`ed the server (or use `evennia reload` from the terminal) to have -your new python code added to the game. Check your terminal and fix any errors you see - the error -traceback lists exactly where the error is found - look line numbers in files you have changed. - -We can't test things unless we have some chargen areas to test. Log into the game (you should at -this point be using the new, custom Character class). Let's dig a chargen area to test. - - @dig chargen:rooms.ChargenRoom = chargen,finish - -If you read the help for `@dig` you will find that this will create a new room named `chargen`. The -part after the `:` is the python-path to the Typeclass you want to use. Since Evennia will -automatically try the `typeclasses` folder of our game directory, we just specify -`rooms.ChargenRoom`, meaning it will look inside the module `rooms.py` for a class named -`ChargenRoom` (which is what we created above). The names given after `=` are the names of exits to -and from the room from your current location. You could also append aliases to each one name, such -as `chargen;character generation`. - -So in summary, this will create a new room of type ChargenRoom and open an exit `chargen` to it and -an exit back here named `finish`. If you see errors at this stage, you must fix them in your code. -`@reload` -between fixes. Don't continue until the creation seems to have worked okay. - - chargen - -This should bring you to the chargen room. Being in there you should now have the `+setpower` -command available, so test it out. When you leave (via the `finish` exit), the command will go away -and trying `+setpower` should now give you a command-not-found error. Use `ex me` (as a privileged -user) to check so the `Power` [Attribute](./Attributes.md) has been set correctly. - -If things are not working, make sure your typeclasses and commands are free of bugs and that you -have entered the paths to the various command sets and commands correctly. Check the logs or command -line for tracebacks and errors. - -## Combat System - -We will add our combat command to the default command set, meaning it will be available to everyone -at all times. The combat system consists of a `+attack` command to get how successful our attack is. -We also change the default `look` command to display the current combat score. - - -### Attacking with the +attack command - -Attacking in this simple system means rolling a random "combat score" influenced by the `power` stat -set during Character generation: - - > +attack - You +attack with a combat score of 12! - -Go back to `mygame/commands/command.py` and add the command to the end like this: - -```python -import random - -# ... - -class CmdAttack(Command): - """ - issues an attack - - Usage: - +attack - - This will calculate a new combat score based on your Power. - Your combat score is visible to everyone in the same location. - """ - key = "+attack" - help_category = "mush" - - def func(self): - "Calculate the random score between 1-10*Power" - caller = self.caller - power = caller.db.power - if not power: - # this can happen if caller is not of - # our custom Character typeclass - power = 1 - combat_score = random.randint(1, 10 * power) - caller.db.combat_score = combat_score - - # announce - message = "%s +attack%s with a combat score of %s!" - caller.msg(message % ("You", "", combat_score)) - caller.location.msg_contents(message % - (caller.key, "s", combat_score), - exclude=caller) -``` - -What we do here is simply to generate a "combat score" using Python's inbuilt `random.randint()` -function. We then store that and echo the result to everyone involved. - -To make the `+attack` command available to you in game, go back to -`mygame/commands/default_cmdsets.py` and scroll down to the `CharacterCmdSet` class. At the correct -place add this line: - -```python -self.add(command.CmdAttack()) -``` - -`@reload` Evennia and the `+attack` command should be available to you. Run it and use e.g. `@ex` to -make sure the `combat_score` attribute is saved correctly. - -### Have "look" show combat scores - -Players should be able to view all current combat scores in the room. We could do this by simply -adding a second command named something like `+combatscores`, but we will instead let the default -`look` command do the heavy lifting for us and display our scores as part of its normal output, like -this: - - > look Tom - Tom (combat score: 3) - This is a great warrior. - -We don't actually have to modify the `look` command itself however. To understand why, take a look -at how the default `look` is actually defined. It sits in `evennia/commands/default/general.py` (or -browse it online -[here](https://github.com/evennia/evennia/blob/master/evennia/commands/default/general.py#L44)). -You will find that the actual return text is done by the `look` command calling a *hook method* -named `return_appearance` on the object looked at. All the `look` does is to echo whatever this hook -returns. So what we need to do is to edit our custom Character typeclass and overload its -`return_appearance` to return what we want (this is where the advantage of having a custom typeclass -comes into play for real). - -Go back to your custom Character typeclass in `mygame/typeclasses/characters.py`. The default -implementation of `return appearance` is found in `evennia.DefaultCharacter` (or online -[here](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L1438)). If you -want to make bigger changes you could copy & paste the whole default thing into our overloading -method. In our case the change is small though: - -```python -class Character(DefaultCharacter): - """ - [...] - """ - def at_object_creation(self): - "This is called when object is first created, only." - self.db.power = 1 - self.db.combat_score = 1 - - def return_appearance(self, looker): - """ - The return from this method is what - looker sees when looking at this object. - """ - text = super().return_appearance(looker) - cscore = " (combat score: %s)" % self.db.combat_score - if "\n" in text: - # text is multi-line, add score after first line - first_line, rest = text.split("\n", 1) - text = first_line + cscore + "\n" + rest - else: - # text is only one line; add score to end - text += cscore - return text -``` - -What we do is to simply let the default `return_appearance` do its thing (`super` will call the -parent's version of the same method). We then split out the first line of this text, append our -`combat_score` and put it back together again. - -`@reload` the server and you should be able to look at other Characters and see their current combat -scores. - -> Note: A potentially more useful way to do this would be to overload the entire `return_appearance` -of the `Room`s of your mush and change how they list their contents; in that way one could see all -combat scores of all present Characters at the same time as looking at the room. We leave this as an -exercise. - -## NPC system - -Here we will re-use the Character class by introducing a command that can create NPC objects. We -should also be able to set its Power and order it around. - -There are a few ways to define the NPC class. We could in theory create a custom typeclass for it -and put a custom NPC-specific cmdset on all NPCs. This cmdset could hold all manipulation commands. -Since we expect NPC manipulation to be a common occurrence among the user base however, we will -instead put all relevant NPC commands in the default command set and limit eventual access with -[Permissions and Locks](./Locks.md#permissions). - -### Creating an NPC with +createNPC - -We need a command for creating the NPC, this is a very straightforward command: - - > +createnpc Anna - You created the NPC 'Anna'. - -At the end of `command.py`, create our new command: - -```python -from evennia import create_object - -class CmdCreateNPC(Command): - """ - create a new npc - - Usage: - +createNPC - - Creates a new, named NPC. The NPC will start with a Power of 1. - """ - key = "+createnpc" - aliases = ["+createNPC"] - locks = "call:not perm(nonpcs)" - help_category = "mush" - - def func(self): - "creates the object and names it" - caller = self.caller - if not self.args: - caller.msg("Usage: +createNPC ") - return - if not caller.location: - # may not create npc when OOC - caller.msg("You must have a location to create an npc.") - return - # make name always start with capital letter - name = self.args.strip().capitalize() - # create npc in caller's location - npc = create_object("characters.Character", - key=name, - location=caller.location, - locks="edit:id(%i) and perm(Builders);call:false()" % caller.id) - # announce - message = "%s created the NPC '%s'." - caller.msg(message % ("You", name)) - caller.location.msg_contents(message % (caller.key, name), - exclude=caller) -``` -Here we define a `+createnpc` (`+createNPC` works too) that is callable by everyone *not* having the -`nonpcs` "[permission](./Locks.md#permissions)" (in Evennia, a "permission" can just as well be used to -block access, it depends on the lock we define). We create the NPC object in the caller's current -location, using our custom `Character` typeclass to do so. - -We set an extra lock condition on the NPC, which we will use to check who may edit the NPC later -- -we allow the creator to do so, and anyone with the Builders permission (or higher). See -[Locks](./Locks.md) for more information about the lock system. - -Note that we just give the object default permissions (by not specifying the `permissions` keyword -to the `create_object()` call). In some games one might want to give the NPC the same permissions -as the Character creating them, this might be a security risk though. - -Add this command to your default cmdset the same way you did the `+attack` command earlier. -`@reload` and it will be available to test. - -### Editing the NPC with +editNPC - -Since we re-used our custom character typeclass, our new NPC already has a *Power* value - it -defaults to 1. How do we change this? - -There are a few ways we can do this. The easiest is to remember that the `power` attribute is just a -simple [Attribute](./Attributes.md) stored on the NPC object. So as a Builder or Admin we could set this -right away with the default `@set` command: - - @set mynpc/power = 6 - -The `@set` command is too generally powerful though, and thus only available to staff. We will add a -custom command that only changes the things we want players to be allowed to change. We could in -principle re-work our old `+setpower` command, but let's try something more useful. Let's make a -`+editNPC` command. - - > +editNPC Anna/power = 10 - Set Anna's property 'power' to 10. - -This is a slightly more complex command. It goes at the end of your `command.py` file as before. - -```python -class CmdEditNPC(Command): - """ - edit an existing NPC - - Usage: - +editnpc [/ [= value]] - - Examples: - +editnpc mynpc/power = 5 - +editnpc mynpc/power - displays power value - +editnpc mynpc - shows all editable - attributes and values - - This command edits an existing NPC. You must have - permission to edit the NPC to use this. - """ - key = "+editnpc" - aliases = ["+editNPC"] - locks = "cmd:not perm(nonpcs)" - help_category = "mush" - - def parse(self): - "We need to do some parsing here" - args = self.args - propname, propval = None, None - if "=" in args: - args, propval = [part.strip() for part in args.rsplit("=", 1)] - if "/" in args: - args, propname = [part.strip() for part in args.rsplit("/", 1)] - # store, so we can access it below in func() - self.name = args - self.propname = propname - # a propval without a propname is meaningless - self.propval = propval if propname else None - - def func(self): - "do the editing" - - allowed_propnames = ("power", "attribute1", "attribute2") - - caller = self.caller - if not self.args or not self.name: - caller.msg("Usage: +editnpc name[/propname][=propval]") - return - npc = caller.search(self.name) - if not npc: - return - if not npc.access(caller, "edit"): - caller.msg("You cannot change this NPC.") - return - if not self.propname: - # this means we just list the values - output = f"Properties of {npc.key}:" - for propname in allowed_propnames: - output += f"\n {propname} = {npc.attributes.get(propname, default='N/A')}" - caller.msg(output) - elif self.propname not in allowed_propnames: - caller.msg(f"You may only change {', '.join(allowed_propnames)}.") - elif self.propval: - # assigning a new propvalue - # in this example, the properties are all integers... - intpropval = int(self.propval) - npc.attributes.add(self.propname, intpropval) - caller.msg(f"Set {npc.key}'s property {self.propname} to {self.propval}") - else: - # propname set, but not propval - show current value - caller.msg(f"{npc.key} has property {self.propname} = {npc.attributes.get(self.propname, -default='N/A')}") -``` - -This command example shows off the use of more advanced parsing but otherwise it's mostly error -checking. It searches for the given npc in the same room, and checks so the caller actually has -permission to "edit" it before continuing. An account without the proper permission won't even be -able to view the properties on the given NPC. It's up to each game if this is the way it should be. - -Add this to the default command set like before and you should be able to try it out. - -_Note: If you wanted a player to use this command to change an on-object property like the NPC's -name (the `key` property), you'd need to modify the command since "key" is not an Attribute (it is -not retrievable via `npc.attributes.get` but directly via `npc.key`). We leave this as an optional -exercise._ - -### Making the NPC do stuff - the +npc command - -Finally, we will make a command to order our NPC around. For now, we will limit this command to only -be usable by those having the "edit" permission on the NPC. This can be changed if it's possible for -anyone to use the NPC. - -The NPC, since it inherited our Character typeclass has access to most commands a player does. What -it doesn't have access to are Session and Player-based cmdsets (which means, among other things that -they cannot chat on channels, but they could do that if you just added those commands). This makes -the `+npc` command simple: - - +npc Anna = say Hello! - Anna says, 'Hello!' - -Again, add to the end of your `command.py` module: - -```python -class CmdNPC(Command): - """ - controls an NPC - - Usage: - +npc = - - This causes the npc to perform a command as itself. It will do so - with its own permissions and accesses. - """ - key = "+npc" - locks = "call:not perm(nonpcs)" - help_category = "mush" - - def parse(self): - "Simple split of the = sign" - name, cmdname = None, None - if "=" in self.args: - name, cmdname = self.args.rsplit("=", 1) - name = name.strip() - cmdname = cmdname.strip() - self.name, self.cmdname = name, cmdname - - def func(self): - "Run the command" - caller = self.caller - if not self.cmdname: - caller.msg("Usage: +npc = ") - return - npc = caller.search(self.name) - if not npc: - return - if not npc.access(caller, "edit"): - caller.msg("You may not order this NPC to do anything.") - return - # send the command order - npc.execute_cmd(self.cmdname) - caller.msg(f"You told {npc.key} to do '{self.cmdname}'.") -``` - -Note that if you give an erroneous command, you will not see any error message, since that error -will be returned to the npc object, not to you. If you want players to see this, you can give the -caller's session ID to the `execute_cmd` call, like this: - -```python -npc.execute_cmd(self.cmdname, sessid=self.caller.sessid) -``` - -Another thing to remember is however that this is a very simplistic way to control NPCs. Evennia -supports full puppeting very easily. An Account (assuming the "puppet" permission was set correctly) -could simply do `@ic mynpc` and be able to play the game "as" that NPC. This is in fact just what -happens when an Account takes control of their normal Character as well. - -## Concluding remarks - -This ends the tutorial. It looks like a lot of text but the amount of code you have to write is -actually relatively short. At this point you should have a basic skeleton of a game and a feel for -what is involved in coding your game. - -From here on you could build a few more ChargenRooms and link that to a bigger grid. The `+setpower` -command can either be built upon or accompanied by many more to get a more elaborate character -generation. - -The simple "Power" game mechanic should be easily expandable to something more full-fledged and -useful, same is true for the combat score principle. The `+attack` could be made to target a -specific player (or npc) and automatically compare their relevant attributes to determine a result. - -To continue from here, you can take a look at the [Tutorial World](./Tutorial-World-Introduction.md). For -more specific ideas, see the [other tutorials and hints](./Tutorials.md) as well -as the [Developer Central](./Developer-Central.md). diff --git a/docs/0.9.5/_sources/Tutorials.md.txt b/docs/0.9.5/_sources/Tutorials.md.txt deleted file mode 100644 index 681539c33e..0000000000 --- a/docs/0.9.5/_sources/Tutorials.md.txt +++ /dev/null @@ -1,207 +0,0 @@ -# Tutorials - - -Before continuing to read these tutorials (and especially before you start to code or build your -game in earnest) it's strongly recommended that you read the -[Evennia coding introduction](./Coding-Introduction.md) as well as the [Planning your own game](./Game-Planning.md) pages first. - -Please note that it's not within the scope of our tutorials to teach you basic Python. If you are -new to the language, expect to have to look up concepts you are unfamiliar with. Usually a quick -internet search will give you all info you need. Furthermore, our tutorials tend to focus on -implementation and concepts. As such they give only brief explanations to use Evennia features while -providing ample links to the relevant detailed documentation. - -The main information resource for builders is the [Builder Documentation](./Builder-Docs.md). Coders -should refer to the [Developer Central](./Developer-Central.md) for further information. - -### Building - -_Help with populating your game world._ - -- [Tutorial: Building Quick-start](./Building-Quickstart.md) - helps you build your first rocks and -crates using Evennia's defaults. -- [Tutorial: Understanding Color Tags](./Understanding-Color-Tags.md)- explains how you color your game's -text. -- [Introduction: The Tutorial World](./Tutorial-World-Introduction.md) - this introduces the full (if -small) solo-adventure game that comes with the Evennia distribution. It is useful both as an example -of building and of coding. -- [Tutorial: Building a Giant Mech](./Building-a-mech-tutorial.md) - this starts as a building tutorial -and transitions into writing code. - -### General Development tutorials - -_General code practices for newbie game developers._ - -To use Evennia, you will need basic understanding of Python -[modules](http://docs.python.org/3.7/tutorial/modules.html), -[variables](http://www.tutorialspoint.com/python/python_variable_types.htm), [conditional statements](http://docs.python.org/tutorial/controlflow.html#if-statements), -[loops](http://docs.python.org/tutorial/controlflow.html#for-statements), -[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions), [lists, dictionaries, list comprehensions](http://docs.python.org/tutorial/datastructures.html) and [string formatting](http://docs.python.org/tutorial/introduction.html#strings). You should also have a basic -understanding of [object-oriented programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) and what Python -[Classes](http://docs.python.org/tutorial/classes.html) are. - -- [Python tutorials for beginners](https://wiki.python.org/moin/BeginnersGuide/NonProgrammers) - -external link with tutorials for those not familiar with coding in general or Python in particular. -- [Tutorial: Version Control](./Version-Control.md) - use GIT to organize your code both for your own -game project and for contributing to Evennia. -- MIT offers free courses in many subjects. Their [Introduction to Computer Science and Programming](https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-00sc- -introduction-to-computer-science-and-programming-spring-2011/) uses Python as its language of -choice. Longer path, but more in-depth. Definitely worth a look. - -### Coding - First Step tutorials - -_Starting tutorials for you who are new to developing with Evennia._ - -- [Python basic introduction](./Python-basic-introduction.md) (part 1) - Python intro using Evennia. -- [Python basic introduction](./Python-basic-tutorial-part-two.md) (part 2) - More on objects, classes -and finding where things are. -- [Tutorial: First Steps Coding](./First-Steps-Coding.md) - learn each basic feature on their own through -step-by-step instruction. -- [Tutorial: A small first game](./Tutorial-for-basic-MUSH-like-game.md) - learn basic features as part -of building a small but working game from scratch. -- [Tutorial: Adding new commands](./Adding-Command-Tutorial.md) - focuses specifically on how to add new -commands. -- [Tutorial: Parsing command argument](./Parsing-command-arguments,-theory-and-best-practices.md). -- [Tutorial: Adding new objects](./Adding-Object-Typeclass-Tutorial.md) - focuses specifically on how to -add new objects. -- [Tutorial: Searching objects in the database](./Tutorial-Searching-For-Objects.md) - how to find -existing objects so you can operate on them. - - -### Custom objects and typeclasses - -_Examples of designing new objects for your game world_ - -- [Tutorial: Rooms with Weather](./Weather-Tutorial.md) -- [Tutorial: Aggressive NPC's](./Tutorial-Aggressive-NPCs.md) -- [Tutorial: Listening NPC's](./Tutorial-NPCs-listening.md) -- [Tutorial: Creating a vehicle](./Tutorial-Vehicles.md) -- [Tutorial: Making an NPC shop](./NPC-shop-Tutorial.md) (also advanced [EvMenu](./EvMenu.md) usage) -- [Tutorial: Implementing a Static In Game Map](./Static-In-Game-Map.md) (also [Batch Code](Batch-Code- -Processor) usage) -- [Tutorial: Implementing a Dynamic In Game Map](./Dynamic-In-Game-Map.md) -- [Tutorial: Writing your own unit tests](./Unit-Testing.md#testing-for-game-development-mini-tutorial) - -### Game mechanics tutorials - -_Creating the underlying game mechanics of game play._ - -- [Hints: Implementing a game rule system](./Implementing-a-game-rule-system.md) -- [Tutorial: Implementing a Combat system](./Turn-based-Combat-System.md) -- [Tutorial: Evennia for running tabletop rpgs](./Evennia-for-roleplaying-sessions.md) - -### Miscellaneous system tutorials - -_Design various game systems and achieve particular effects._ - -- [FAQ](./Coding-FAQ.md): A place for users to enter their own hints on achieving various goals in -Evennia. -- [Tutorial: Adding a Command prompt](./Command-Prompt.md) -- [Tutorial: Creating a Zoning system](./Zones.md) -- [Tutorial: Letting players manually configure color settings](./Manually-Configuring-Color.md) -- [Hints: Asking the user a question and dealing with the result](./EvMenu.md#ask-for-simple-input) -- [Hints: Designing commands that take time to finish](./Command-Duration.md) -- [Hints: Adding cooldowns to commands](./Command-Cooldown.md) -- [Tutorial: Mass and weight for objects](./Mass-and-weight-for-objects.md) -- [Hints: Show a different message when trying a non-existent exit](./Default-Exit-Errors.md) -- [Tutorial: Make automatic tweets of game statistics](./Tutorial-Tweeting-Game-Stats.md) -- [Tutorial: Handling virtual time in your game](./Gametime-Tutorial.md) -- [Tutorial: Setting up a coordinate system for rooms](./Coordinates.md) -- [Tutorial: customize the way channels and channel commands work in your game](./Customize-channels.md) -- [Tutorial: Adding unit tests to your game project](./Unit-Testing.md#testing-for-game-development-mini- tutorial) - -### Contrib - -_This section contains tutorials linked with contribs. These contribs can be used in your game, but -you'll need to install them explicitly. They add common features that can earn you time in -implementation._ - -- [list of contribs](https://github.com/evennia/evennia/blob/master/evennia/contrib/README.md) - -- [In-game Python: dialogues with characters](./Dialogues-in-events.md). -- [In-game Python: a voice-operated elevator](./A-voice-operated-elevator-using-events.md). - -### Web tutorials - -_Expanding Evennia's web presence._ - -- [Tutorial: Add a new web page](./Add-a-simple-new-web-page.md) - simple example to see how Django pages -hang together. -- [Tutorial: Website customization](./Web-Tutorial.md) - learn how to start customizing your game's web -presence. -- [Tutorial: Bootstrap & Evennia](./Bootstrap-&-Evennia.md) - Learn more about Bootstrap, the current CSS -framework Evennia is using -- [Tutorial: Build a web page displaying a game character](./Web-Character-View-Tutorial.md) - make a way -to view your character on the web page. -- [Tutorial: access your help system from your website](./Help-System-Tutorial.md) -- [Tutorial: add a wiki on your website](./Add-a-wiki-on-your-website.md) -- [Tutorial: Web Character Generation](Web-Character-Generation/) - make a web-based character -application form. -- [Tutorial: Bootstrap Components and Utilities](./Bootstrap-Components-and-Utilities.md) - Describes -some common Bootstrap Components and Utilities that might help in designing for Evennia - -### Evennia for [Engine]-Users - -_Hints for new users more familiar with other game engines._ - -- [Evennia for Diku Users](./Evennia-for-Diku-Users.md) - read up on the differences between Diku style -muds and Evennia. -- [Evennia for MUSH Users](./Evennia-for-MUSH-Users.md) - an introduction to Evennia for those accustomed -to MUSH-style servers. - - -```{toctree} - :hidden: - - Game-Planning - Building-Quickstart - Understanding-Color-Tags - Tutorial-World-Introduction - Building-a-mech-tutorial - Version-Control - Python-basic-introduction - Python-basic-tutorial-part-two - First-Steps-Coding - Tutorial-for-basic-MUSH-like-game - Adding-Command-Tutorial - Parsing-command-arguments,-theory-and-best-practices - Adding-Object-Typeclass-Tutorial - Tutorial-Searching-For-Objects - Weather-Tutorial - Tutorial-Aggressive-NPCs - Tutorial-NPCs-listening - Tutorial-Vehicles - NPC-shop-Tutorial - Static-In-Game-Map - Dynamic-In-Game-Map - Unit-Testing - Implementing-a-game-rule-system - Turn-based-Combat-System - Evennia-for-roleplaying-sessions - Coding-FAQ - Command-Prompt - Zones - Manually-Configuring-Color - EvMenu - Command-Duration - Command-Cooldown - Mass-and-weight-for-objects - Default-Exit-Errors - Tutorial-Tweeting-Game-Stats - Gametime-Tutorial - Coordinates - Customize-channels - Dialogues-in-events - A-voice-operated-elevator-using-events - Add-a-simple-new-web-page - Web-Tutorial - Bootstrap-&-Evennia - Web-Character-View-Tutorial - Help-System-Tutorial - Add-a-wiki-on-your-website - Web-Character-Generation - Bootstrap-Components-and-Utilities - Evennia-for-Diku-Users - Evennia-for-MUSH-Users - -``` diff --git a/docs/0.9.5/_sources/Typeclasses.md.txt b/docs/0.9.5/_sources/Typeclasses.md.txt deleted file mode 100644 index 456611f520..0000000000 --- a/docs/0.9.5/_sources/Typeclasses.md.txt +++ /dev/null @@ -1,354 +0,0 @@ -# Typeclasses - - -*Typeclasses* form the core of Evennia data storage. It allows Evennia to represent any number of -different game entities as Python classes, without having to modify the database schema for every -new type. - -In Evennia the most important game entities, [Accounts](./Accounts.md), [Objects](./Objects.md), -[Scripts](./Scripts.md) and [Channels](./Communications.md#channels) are all Python classes inheriting, at -varying distance, from `evennia.typeclasses.models.TypedObject`. In the documentation we refer to -these objects as being "typeclassed" or even "being a typeclass". - -This is how the inheritance looks for the typeclasses in Evennia: - -``` - TypedObject - _________________|_________________________________ - | | | | -1: AccountDB ObjectDB ScriptDB ChannelDB - | | | | -2: DefaultAccount DefaultObject DefaultScript DefaultChannel - | DefaultCharacter | | - | DefaultRoom | | - | DefaultExit | | - | | | | -3: Account Object Script Channel - Character - Room - Exit -``` - -- **Level 1** above is the "database model" level. This describes the database tables and fields -(this is technically a [Django model](https://docs.djangoproject.com/en/2.2/topics/db/models/)). -- **Level 2** is where we find Evennia's default implementations of the various game entities, on -top of the database. These classes define all the hook methods that Evennia calls in various -situations. `DefaultObject` is a little special since it's the parent for `DefaultCharacter`, -`DefaultRoom` and `DefaultExit`. They are all grouped under level 2 because they all represents -defaults to build from. -- **Level 3**, finally, holds empty template classes created in your game directory. This is the -level you are meant to modify and tweak as you please, overloading the defaults as befits your game. -The templates inherit directly from their defaults, so `Object` inherits from `DefaultObject` and -`Room` inherits from `DefaultRoom`. - -The `typeclass/list` command will provide a list of all typeclasses known to -Evennia. This can be useful for getting a feel for what is available. Note -however that if you add a new module with a class in it but do not import that -module from anywhere, the `typeclass/list` will not find it. To make it known -to Evennia you must import that module from somewhere. - - -### Difference between typeclasses and classes - -All Evennia classes inheriting from class in the table above share one important feature and two -important limitations. This is why we don't simply call them "classes" but "typeclasses". - - 1. A typeclass can save itself to the database. This means that some properties (actually not that -many) on the class actually represents database fields and can only hold very specific data types. -This is detailed [below](./Typeclasses.md#about-typeclass-properties). - 1. Due to its connection to the database, the typeclass' name must be *unique* across the _entire_ -server namespace. That is, there must never be two same-named classes defined anywhere. So the below -code would give an error (since `DefaultObject` is now globally found both in this module and in the -default library): - - ```python - from evennia import DefaultObject as BaseObject - class DefaultObject(BaseObject): - pass - ``` - - 1. A typeclass' `__init__` method should normally not be overloaded. This has mostly to do with the -fact that the `__init__` method is not called in a predictable way. Instead Evennia suggest you use -the `at_*_creation` hooks (like `at_object_creation` for Objects) for setting things the very first -time the typeclass is saved to the database or the `at_init` hook which is called every time the -object is cached to memory. If you know what you are doing and want to use `__init__`, it *must* -both accept arbitrary keyword arguments and use `super` to call its parent:: - - ```python - def __init__(self, **kwargs): - # my content - super().__init__(**kwargs) - # my content - ``` - -Apart from this, a typeclass works like any normal Python class and you can -treat it as such. - - -## Creating a new typeclass - -It's easy to work with Typeclasses. Either you use an existing typeclass or you create a new Python -class inheriting from an existing typeclass. Here is an example of creating a new type of Object: - -```python - from evennia import DefaultObject - - class Furniture(DefaultObject): - # this defines what 'furniture' is, like - # storing who sits on it or something. - pass - -``` - -You can now create a new `Furniture` object in two ways. First (and usually not the most -convenient) way is to create an instance of the class and then save it manually to the database: - -```python -chair = Furniture(db_key="Chair") -chair.save() - -``` - -To use this you must give the database field names as keywords to the call. Which are available -depends on the entity you are creating, but all start with `db_*` in Evennia. This is a method you -may be familiar with if you know Django from before. - -It is recommended that you instead use the `create_*` functions to create typeclassed entities: - - -```python -from evennia import create_object - -chair = create_object(Furniture, key="Chair") -# or (if your typeclass is in a module furniture.py) -chair = create_object("furniture.Furniture", key="Chair") -``` - -The `create_object` (`create_account`, `create_script` etc) takes the typeclass as its first -argument; this can both be the actual class or the python path to the typeclass as found under your -game directory. So if your `Furniture` typeclass sits in `mygame/typeclasses/furniture.py`, you -could point to it as `typeclasses.furniture.Furniture`. Since Evennia will itself look in -`mygame/typeclasses`, you can shorten this even further to just `furniture.Furniture`. The create- -functions take a lot of extra keywords allowing you to set things like [Attributes](./Attributes.md) and -[Tags](./Tags.md) all in one go. These keywords don't use the `db_*` prefix. This will also automatically -save the new instance to the database, so you don't need to call `save()` explicitly. - -### About typeclass properties - -An example of a database field is `db_key`. This stores the "name" of the entity you are modifying -and can thus only hold a string. This is one way of making sure to update the `db_key`: - -```python -chair.db_key = "Table" -chair.save() - -print(chair.db_key) -<<< Table -``` - -That is, we change the chair object to have the `db_key` "Table", then save this to the database. -However, you almost never do things this way; Evennia defines property wrappers for all the database -fields. These are named the same as the field, but without the `db_` part: - -```python -chair.key = "Table" - -print(chair.key) -<<< Table - -``` - -The `key` wrapper is not only shorter to write, it will make sure to save the field for you, and -does so more efficiently by levering sql update mechanics under the hood. So whereas it is good to -be aware that the field is named `db_key` you should use `key` as much as you can. - -Each typeclass entity has some unique fields relevant to that type. But all also share the -following fields (the wrapper name without `db_` is given): - - - `key` (str): The main identifier for the entity, like "Rose", "myscript" or "Paul". `name` is an -alias. - - `date_created` (datetime): Time stamp when this object was created. - - `typeclass_path` (str): A python path pointing to the location of this (type)class - -There is one special field that doesn't use the `db_` prefix (it's defined by Django): - - - `id` (int): the database id (database ref) of the object. This is an ever-increasing, unique -integer. It can also be accessed as `dbid` (database ID) or `pk` (primary key). The `dbref` property -returns the string form "#id". - -The typeclassed entity has several common handlers: - - - `tags` - the [TagHandler](./Tags.md) that handles tagging. Use `tags.add()` , `tags.get()` etc. - - `locks` - the [LockHandler](./Locks.md) that manages access restrictions. Use `locks.add()`, -`locks.get()` etc. - - `attributes` - the [AttributeHandler](./Attributes.md) that manages Attributes on the object. Use -`attributes.add()` -etc. - - `db` (DataBase) - a shortcut property to the AttributeHandler; allowing `obj.db.attrname = value` - - `nattributes` - the [Non-persistent AttributeHandler](./Attributes.md) for attributes not saved in the -database. - - `ndb` (NotDataBase) - a shortcut property to the Non-peristent AttributeHandler. Allows -`obj.ndb.attrname = value` - - -Each of the typeclassed entities then extend this list with their own properties. Go to the -respective pages for [Objects](./Objects.md), [Scripts](./Scripts.md), [Accounts](./Accounts.md) and -[Channels](./Communications.md) for more info. It's also recommended that you explore the available -entities using [Evennia's flat API](./Evennia-API.md) to explore which properties and methods they have -available. - -### Overloading hooks - -The way to customize typeclasses is usually to overload *hook methods* on them. Hooks are methods -that Evennia call in various situations. An example is the `at_object_creation` hook on `Objects`, -which is only called once, the very first time this object is saved to the database. Other examples -are the `at_login` hook of Accounts and the `at_repeat` hook of Scripts. - -### Querying for typeclasses - -Most of the time you search for objects in the database by using convenience methods like the -`caller.search()` of [Commands](./Commands.md) or the search functions like `evennia.search_objects`. - -You can however also query for them directly using [Django's query -language](https://docs.djangoproject.com/en/1.7/topics/db/queries/). This makes use of a _database -manager_ that sits on all typeclasses, named `objects`. This manager holds methods that allow -database searches against that particular type of object (this is the way Django normally works -too). When using Django queries, you need to use the full field names (like `db_key`) to search: - -```python -matches = Furniture.objects.get(db_key="Chair") - -``` - -It is important that this will *only* find objects inheriting directly from `Furniture` in your -database. If there was a subclass of `Furniture` named `Sitables` you would not find any chairs -derived from `Sitables` with this query (this is not a Django feature but special to Evennia). To -find objects from subclasses Evennia instead makes the `get_family` and `filter_family` query -methods available: - -```python -# search for all furnitures and subclasses of furnitures -# whose names starts with "Chair" -matches = Furniture.objects.filter_family(db_key__startswith="Chair") - -``` - -To make sure to search, say, all `Scripts` *regardless* of typeclass, you need to query from the -database model itself. So for Objects, this would be `ObjectDB` in the diagram above. Here's an -example for Scripts: - -```python -from evennia import ScriptDB -matches = ScriptDB.objects.filter(db_key__contains="Combat") -``` - -When querying from the database model parent you don't need to use `filter_family` or `get_family` - -you will always query all children on the database model. - -## Updating existing typeclass instances - -If you already have created instances of Typeclasses, you can modify the *Python code* at any time - -due to how Python inheritance works your changes will automatically be applied to all children once -you have reloaded the server. - -However, database-saved data, like `db_*` fields, [Attributes](./Attributes.md), [Tags](./Tags.md) etc, are -not themselves embedded into the class and will *not* be updated automatically. This you need to -manage yourself, by searching for all relevant objects and updating or adding the data: - -```python -# add a worth Attribute to all existing Furniture -for obj in Furniture.objects.all(): - # this will loop over all Furniture instances - obj.db.worth = 100 -``` - -A common use case is putting all Attributes in the `at_*_creation` hook of the entity, such as -`at_object_creation` for `Objects`. This is called every time an object is created - and only then. -This is usually what you want but it does mean already existing objects won't get updated if you -change the contents of `at_object_creation` later. You can fix this in a similar way as above -(manually setting each Attribute) or with something like this: - -```python -# Re-run at_object_creation only on those objects not having the new Attribute -for obj in Furniture.objects.all(): - if not obj.db.worth: - obj.at_object_creation() -``` - -The above examples can be run in the command prompt created by `evennia shell`. You could also run -it all in-game using `@py`. That however requires you to put the code (including imports) as one -single line using `;` and [list -comprehensions](http://www.secnetix.de/olli/Python/list_comprehensions.hawk), like this (ignore the -line break, that's only for readability in the wiki): - -``` -@py from typeclasses.furniture import Furniture; -[obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth] -``` - -It is recommended that you plan your game properly before starting to build, to avoid having to -retroactively update objects more than necessary. - -## Swap typeclass - -If you want to swap an already existing typeclass, there are two ways to do so: From in-game and via -code. From inside the game you can use the default `@typeclass` command: - -``` -@typeclass objname = path.to.new.typeclass -``` - -There are two important switches to this command: -- `/reset` - This will purge all existing Attributes on the object and re-run the creation hook -(like `at_object_creation` for Objects). This assures you get an object which is purely of this new -class. -- `/force` - This is required if you are changing the class to be *the same* class the object -already has - it's a safety check to avoid user errors. This is usually used together with `/reset` -to re-run the creation hook on an existing class. - -In code you instead use the `swap_typeclass` method which you can find on all typeclassed entities: - -```python -obj_to_change.swap_typeclass(new_typeclass_path, clean_attributes=False, - run_start_hooks="all", no_default=True, clean_cmdsets=False) -``` - -The arguments to this method are described [in the API docs -here](github:evennia.typeclasses.models#typedobjectswap_typeclass). - - -## How typeclasses actually work - -*This is considered an advanced section.* - -Technically, typeclasses are [Django proxy -models](https://docs.djangoproject.com/en/1.7/topics/db/models/#proxy-models). The only database -models that are "real" in the typeclass system (that is, are represented by actual tables in the -database) are `AccountDB`, `ObjectDB`, `ScriptDB` and `ChannelDB` (there are also -[Attributes](./Attributes.md) and [Tags](./Tags.md) but they are not typeclasses themselves). All the -subclasses of them are "proxies", extending them with Python code without actually modifying the -database layout. - -Evennia modifies Django's proxy model in various ways to allow them to work without any boiler plate -(for example you don't need to set the Django "proxy" property in the model `Meta` subclass, Evennia -handles this for you using metaclasses). Evennia also makes sure you can query subclasses as well as -patches django to allow multiple inheritance from the same base class. - -### Caveats - -Evennia uses the *idmapper* to cache its typeclasses (Django proxy models) in memory. The idmapper -allows things like on-object handlers and properties to be stored on typeclass instances and to not -get lost as long as the server is running (they will only be cleared on a Server reload). Django -does not work like this by default; by default every time you search for an object in the database -you'll get a *different* instance of that object back and anything you stored on it that was not in -the database would be lost. The bottom line is that Evennia's Typeclass instances subside in memory -a lot longer than vanilla Django model instance do. - -There is one caveat to consider with this, and that relates to [making your own models](New- -Models): Foreign relationships to typeclasses are cached by Django and that means that if you were -to change an object in a foreign relationship via some other means than via that relationship, the -object seeing the relationship may not reliably update but will still see its old cached version. -Due to typeclasses staying so long in memory, stale caches of such relationships could be more -visible than common in Django. See the [closed issue #1098 and its -comments](https://github.com/evennia/evennia/issues/1098) for examples and solutions. - diff --git a/docs/0.9.5/_sources/Understanding-Color-Tags.md.txt b/docs/0.9.5/_sources/Understanding-Color-Tags.md.txt deleted file mode 100644 index 0d23924be5..0000000000 --- a/docs/0.9.5/_sources/Understanding-Color-Tags.md.txt +++ /dev/null @@ -1,198 +0,0 @@ -# Understanding Color Tags - -This tutorial aims at dispelling confusions regarding the use of color tags within Evennia. - -Correct understanding of this topic requires having read the [TextTags](./TextTags.md) page and learned -Evennia's color tags. Here we'll explain by examples the reasons behind the unexpected (or -apparently incoherent) behaviors of some color tags, as mentioned _en passant_ in the -[TextTags](./TextTags.md) page. - - -All you'll need for this tutorial is access to a running instance of Evennia via a color-enabled -client. The examples provided are just commands that you can type in your client. - -Evennia, ANSI and Xterm256 -========================== - -All modern MUD clients support colors; nevertheless, the standards to which all clients abide dates -back to old day of terminals, and when it comes to colors we are dealing with ANSI and Xterm256 -standards. - -Evennia handles transparently, behind the scenes, all the code required to enforce these -standards—so, if a user connects with a client which doesn't support colors, or supports only ANSI -(16 colors), Evennia will take all due steps to ensure that the output will be adjusted to look -right at the client side. - -As for you, the developer, all you need to care about is knowing how to correctly use the color tags -within your MUD. Most likely, you'll be adding colors to help pages, descriptions, automatically -generated text, etc. - -You are free to mix together ANSI and Xterm256 color tags, but you should be aware of a few -pitfalls. ANSI and Xterm256 coexist without conflicts in Evennia, but in many ways they don't «see» -each other: ANSI-specific color tags will have no effect on Xterm-defined colors, as we shall see -here. - -ANSI -==== - -ANSI has a set of 16 colors, to be more precise: ANSI has 8 basic colors which come in _dark_ and -_bright_ flavours—with _dark_ being _normal_. The colors are: red, green, yellow, blue, magenta, -cyan, white and black. White in its dark version is usually referred to as gray, and black in its -bright version as darkgray. Here, for sake of simplicity they'll be referred to as dark and bright: -bright/dark black, bright/dark white. - -The default colors of MUD clients is normal (dark) white on normal black (ie: gray on black). - -It's important to grasp that in the ANSI standard bright colors apply only to text (foreground), not -to background. Evennia allows to bypass this limitation via Xterm256, but doing so will impact the -behavior of ANSI tags, as we shall see. - -Also, it's important to remember that the 16 ANSI colors are a convention, and the final user can -always customize their appearance—he might decide to have green show as red, and dark green as blue, -etc. - -Xterm256 -======== - -The 16 colors of ANSI should be more than enough to handle simple coloring of text. But when an -author wants to be sure that a given color will show as he intended it, she might choose to rely on -Xterm256 colors. - -Xterm256 doesn't rely on a palette of named colors, it instead represent colors by their values. So, -a red color could be `|[500` (bright and pure red), or `|[300` (darker red), and so on. - -ANSI Color Tags in Evennia -========================== - -> NOTE: for ease of reading, the examples contain extra white spaces after the -> color tags (eg: `|g green |b blue` ). This is done only so that it's easier -> to see the tags separated from their context; it wouldn't be good practice -> in real-life coding. - -Let's proceed by examples. In your MUD client type: - - - say Normal |* Negative - -Evennia should output the word "Normal" normally (ie: gray on black) and "Negative" in reversed -colors (ie: black on gray). - -This is pretty straight forward, the `|*` ANSI *invert* tag switches between foreground and -background—from now on, **FG** and **BG** shorthands will be used to refer to foreground and -background. - -But take mental note of this: `|*` has switched *dark white* and *dark black*. - -Now try this: - - say |w Bright white FG |* Negative - -You'll notice that the word "Negative" is not black on white, it's darkgray on gray. Why is this? -Shouldn't it be black text on a white BG? Two things are happening here. - -As mentioned, ANSI has 8 base colors, the dark ones. The bright ones are achieved by means of -*highlighting* the base/dark/normal colors, and they only apply to FG. - -What happened here is that when we set the bright white FG with `|w`, Evennia translated this into -the ANSI sequence of Highlight On + White FG. In terms of Evennia's color tags, it's as if we typed: - - - say |h|!W Bright white FG |* Negative - -Furthermore, the Highlight-On property (which only works for BG!) is preserved after the FG/BG -switch, this being the reason why we see black as darkgray: highlighting makes it *bright black* -(ie: darkgray). - -As for the BG being also grey, that is normal—ie: you are seeing *normal white* (ie: dark white = -gray). Remember that since there are no bright BG colors, the ANSI `|*` tag will transpose any FG -color in its normal/dark version. So here the FG's bright white became dark white in the BG! In -reality, it was always normal/dark white, except that in the FG is seen as bright because of the -highlight tag behind the scenes. - -Let's try the same thing with some color: - - say |m |[G Bright Magenta on Dark Green |* Negative - -Again, the BG stays dark because of ANSI rules, and the FG stays bright because of the implicit `|h` -in `|m`. - -Now, let's see what happens if we set a bright BG and then invert—yes, Evennia kindly allows us to -do it, even if it's not within ANSI expectations. - - say |[b Dark White on Bright Blue |* Negative - -Before color inversion, the BG does show in bright blue, and after inversion (as expected) it's -*dark white* (gray). The bright blue of the BG survived the inversion and gave us a bright blue FG. -This behavior is tricky though, and not as simple as it might look. - -If the inversion were to be pure ANSI, the bright blue would have been accounted just as normal -blue, and should have converted to normal blue in the FG (after all, there was no highlighting on). -The fact is that in reality this color is not bright blue at all, it just an Xterm version of it! - -To demonstrate this, type: - - say |[b Dark White on Bright Blue |* Negative |H un-bright - -The `|H` Highlight-Off tag should have turned *dark blue* the last word; but it didn't because it -couldn't: in order to enforce the non-ANSI bright BG Evennia turned to Xterm, and Xterm entities are -not affected by ANSI tags! - -So, we are getting at the heart of all confusions and possible odd-behaviors pertaining color tags -in Evennia: apart from Evennia's translations from- and to- ANSI/Xterm, the two systems are -independent and transparent to each other. - -The bright blue of the previous example was just an Xterm representation of the ANSI standard blue. -Try to change the default settings of your client, so that blue shows as some other color, you'll -then realize the difference when Evennia is sending a true ANSI color (which will show up according -to your settings) and when instead it's sending an Xterm representation of that color (which will -show up always as defined by Evennia). - -You'll have to keep in mind that the presence of an Xterm BG or FG color might affect the way your -tags work on the text. For example: - - say |[b Bright Blue BG |* Negative |!Y Dark Yellow |h not bright - -Here the `|h` tag no longer affects the FG color. Even though it was changed via the `|!` tag, the -ANSI system is out-of-tune because of the intrusion of an Xterm color (bright blue BG, then moved to -FG with `|*`). - -All unexpected ANSI behaviours are the result of mixing Xterm colors (either on purpose or either -via bright BG colors). The `|n` tag will restore things in place and ANSI tags will respond properly -again. So, at the end is just an issue of being mindful when using Xterm colors or bright BGs, and -avoid wild mixing them with ANSI tags without normalizing (`|n`) things again. - -Try this: - - say |[b Bright Blue BG |* Negative |!R Red FG - -And then: - - say |[B Dark Blue BG |* Negative |!R Red BG?? - -In this second example the `|!` changes the BG color instead of the FG! In fact, the odd behavior is -the one from the former example, non the latter. When you invert FG and BG with `|*` you actually -inverting their references. This is why the last example (which has a normal/dark BG!) allows `|!` -to change the BG color. In the first example, it's again the presence of an Xterm color (bright blue -BG) which changes the default behavior. - -Try this: - -`say Normal |* Negative |!R Red BG` - -This is the normal behavior, and as you can see it allows `|!` to change BG color after the -inversion of FG and BG. - -As long as you have an understanding of how ANSI works, it should be easy to handle color tags -avoiding the pitfalls of Xterm-ANSI promisquity. - -One last example: - -`say Normal |* Negative |* still Negative` - -Shows that `|*` only works once in a row and will not (and should not!) revert back if used again. -Nor it will have any effect until the `|n` tag is called to "reset" ANSI back to normal. This is how -it is meant to work. - -ANSI operates according to a simple states-based mechanism, and it's important to understand the -positive effect of resetting with the `|n` tag, and not try to -push it over the limit, so to speak. diff --git a/docs/0.9.5/_sources/Unit-Testing.md.txt b/docs/0.9.5/_sources/Unit-Testing.md.txt deleted file mode 100644 index 6a828e982f..0000000000 --- a/docs/0.9.5/_sources/Unit-Testing.md.txt +++ /dev/null @@ -1,435 +0,0 @@ -# Unit Testing - - -*Unit testing* means testing components of a program in isolation from each other to make sure every -part works on its own before using it with others. Extensive testing helps avoid new updates causing -unexpected side effects as well as alleviates general code rot (a more comprehensive wikipedia -article on unit testing can be found [here](http://en.wikipedia.org/wiki/Unit_test)). - -A typical unit test set calls some function or method with a given input, looks at the result and -makes sure that this result looks as expected. Rather than having lots of stand-alone test programs, -Evennia makes use of a central *test runner*. This is a program that gathers all available tests all -over the Evennia source code (called *test suites*) and runs them all in one go. Errors and -tracebacks are reported. - -By default Evennia only tests itself. But you can also add your own tests to your game code and have -Evennia run those for you. - -## Running the Evennia test suite - -To run the full Evennia test suite, go to your game folder and issue the command - - evennia test evennia - -This will run all the evennia tests using the default settings. You could also run only a subset of -all tests by specifying a subpackage of the library: - - evennia test evennia.commands.default - -A temporary database will be instantiated to manage the tests. If everything works out you will see -how many tests were run and how long it took. If something went wrong you will get error messages. -If you contribute to Evennia, this is a useful sanity check to see you haven't introduced an -unexpected bug. - -## Running tests with custom settings file - -If you have implemented your own tests for your game (see below) you can run them from your game dir -with - - evennia test . - -The period (`.`) means to run all tests found in the current directory and all subdirectories. You -could also specify, say, `typeclasses` or `world` if you wanted to just run tests in those subdirs. - -Those tests will all be run using the default settings. To run the tests with your own settings file -you must use the `--settings` option: - - evennia test --settings settings.py . - -The `--settings` option of Evennia takes a file name in the `mygame/server/conf` folder. It is -normally used to swap settings files for testing and development. In combination with `test`, it -forces Evennia to use this settings file over the default one. - -## Writing new tests - -Evennia's test suite makes use of Django unit test system, which in turn relies on Python's -*unittest* module. - -> If you want to help out writing unittests for Evennia, take a look at Evennia's [coveralls.io page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of -test coverage and which does not. - -To make the test runner find the tests, they must be put in a module named `test*.py` (so `test.py`, -`tests.py` etc). Such a test module will be found wherever it is in the package. It can be a good -idea to look at some of Evennia's `tests.py` modules to see how they look. - -Inside a testing file, a `unittest.TestCase` class is used to test a single aspect or component in -various ways. Each test case contains one or more *test methods* - these define the actual tests to -run. You can name the test methods anything you want as long as the name starts with "`test_`". -Your `TestCase` class can also have a method `setUp(self):`. This is run before each test, setting -up and storing whatever preparations the test methods need. Conversely, a `tearDown(self):` method -can optionally do cleanup after each test. - -To test the results, you use special methods of the `TestCase` class. Many of those start with -"`assert`", such as `assertEqual` or `assertTrue`. - -Example of a `TestCase` class: - -```python - import unittest - - # the function we want to test - from mypath import myfunc - - class TestObj(unittest.TestCase): - "This tests a function myfunc." - - def test_return_value(self): - "test method. Makes sure return value is as expected." - expected_return = "This is me being nice." - actual_return = myfunc() - # test - self.assertEqual(expected_return, actual_return) - def test_alternative_call(self): - "test method. Calls with a keyword argument." - expected_return = "This is me being baaaad." - actual_return = myfunc(bad=True) - # test - self.assertEqual(expected_return, actual_return) -``` - -You might also want to read the [documentation for the unittest module](http://docs.python.org/library/unittest.html). - -### Using the EvenniaTest class - -Evennia offers a custom TestCase, the `evennia.utils.test_resources.EvenniaTest` class. This class -initiates a range of useful properties on themselves for testing Evennia systems. Examples are -`.account` and `.session` representing a mock connected Account and its Session and `.char1` and -`.char2` representing Characters complete with a location in the test database. These are all useful -when testing Evennia system requiring any of the default Evennia typeclasses as inputs. See the full -definition of the `EvenniaTest` class in [evennia/utils/test_resources.py](https://github.com/evennia/evennia/blob/master/evennia/utils/test_resources.py). - -```python -# in a test module - -from evennia.utils.test_resources import EvenniaTest - -class TestObject(EvenniaTest): - def test_object_search(self): - # char1 and char2 are both created in room1 - self.assertEqual(self.char1.search(self.char2.key), self.char2) - self.assertEqual(self.char1.search(self.char1.location.key), self.char1.location) - # ... -``` - -### Testing in-game Commands - -In-game Commands are a special case. Tests for the default commands are put in -`evennia/commands/default/tests.py`. This uses a custom `CommandTest` class that inherits from -`evennia.utils.test_resources.EvenniaTest` described above. `CommandTest` supplies extra convenience -functions for executing commands and check that their return values (calls of `msg()` returns -expected values. It uses Characters and Sessions generated on the `EvenniaTest` class to call each -class). - -Each command tested should have its own `TestCase` class. Inherit this class from the `CommandTest` -class in the same module to get access to the command-specific utilities mentioned. - -```python - from evennia.commands.default.tests import CommandTest - from evennia.commands.default import general - class TestSet(CommandTest): - "tests the look command by simple call, using Char2 as a target" - def test_mycmd_char(self): - self.call(general.CmdLook(), "Char2", "Char2(#7)") - "tests the look command by simple call, with target as room" - def test_mycmd_room(self): - self.call(general.CmdLook(), "Room", - "Room(#1)\nroom_desc\nExits: out(#3)\n" - "You see: Obj(#4), Obj2(#5), Char2(#7)") -``` - -### Unit testing contribs with custom models - -A special case is if you were to create a contribution to go to the `evennia/contrib` folder that -uses its [own database models](./New-Models.md). The problem with this is that Evennia (and Django) will -only recognize models in `settings.INSTALLED_APPS`. If a user wants to use your contrib, they will -be required to add your models to their settings file. But since contribs are optional you cannot -add the model to Evennia's central `settings_default.py` file - this would always create your -optional models regardless of if the user wants them. But at the same time a contribution is a part -of the Evennia distribution and its unit tests should be run with all other Evennia tests using -`evennia test evennia`. - -The way to do this is to only temporarily add your models to the `INSTALLED_APPS` directory when the -test runs. here is an example of how to do it. - -> Note that this solution, derived from this [stackexchange answer](http://stackoverflow.com/questions/502916/django-how-to-create-a-model-dynamically-just-for-testing#503435) is currently untested! Please report your findings. - -```python -# a file contrib/mycontrib/tests.py - -from django.conf import settings -import django -from evennia.utils.test_resources import EvenniaTest - -OLD_DEFAULT_SETTINGS = settings.INSTALLED_APPS -DEFAULT_SETTINGS = dict( - INSTALLED_APPS=( - 'contrib.mycontrib.tests', - ), - DATABASES={ - "default": { - "ENGINE": "django.db.backends.sqlite3" - } - }, - SILENCED_SYSTEM_CHECKS=["1_7.W001"], - ) - -class TestMyModel(EvenniaTest): - def setUp(self): - - if not settings.configured: - settings.configure(**DEFAULT_SETTINGS) - django.setup() - - from django.core.management import call_command - from django.db.models import loading - loading.cache.loaded = False - call_command('syncdb', verbosity=0) - - def tearDown(self): - settings.configure(**OLD_DEFAULT_SETTINGS) - django.setup() - - from django.core.management import call_command - from django.db.models import loading - loading.cache.loaded = False - call_command('syncdb', verbosity=0) - - # test cases below ... - - def test_case(self): - # test case here -``` - -### A note on adding new tests - -Having an extensive tests suite is very important for avoiding code degradation as Evennia is -developed. Only a small fraction of the Evennia codebase is covered by test suites at this point. -Writing new tests is not hard, it's more a matter of finding the time to do so. So adding new tests -is really an area where everyone can contribute, also with only limited Python skills. - -### A note on making the test runner faster - -If you have custom models with a large number of migrations, creating the test database can take a -very long time. If you don't require migrations to run for your tests, you can disable them with the -django-test-without-migrations package. To install it, simply: - -``` -$ pip install django-test-without-migrations -``` - -Then add it to your `INSTALLED_APPS` in your `server.conf.settings.py`: - -```python -INSTALLED_APPS = ( - # ... - 'test_without_migrations', -) -``` - -After doing so, you can then run tests without migrations by adding the `--nomigrations` argument: - -``` -evennia test --settings settings.py --nomigrations . -``` - -## Testing for Game development (mini-tutorial) - -Unit testing can be of paramount importance to game developers. When starting with a new game, it is -recommended to look into unit testing as soon as possible; an already huge game is much harder to -write tests for. The benefits of testing a game aren't different from the ones regarding library -testing. For example it is easy to introduce bugs that affect previously working code. Testing is -there to ensure your project behaves the way it should and continue to do so. - -If you have never used unit testing (with Python or another language), you might want to check the -[official Python documentation about unit testing](https://docs.python.org/2/library/unittest.html), -particularly the first section dedicated to a basic example. - -### Basic testing using Evennia - -Evennia's test runner can be used to launch tests in your game directory (let's call it 'mygame'). -Evennia's test runner does a few useful things beyond the normal Python unittest module: - -* It creates and sets up an empty database, with some useful objects (accounts, characters and -rooms, among others). -* It provides simple ways to test commands, which can be somewhat tricky at times, if not tested -properly. - -Therefore, you should use the command-line to execute the test runner, while specifying your own -game directories (not the one containing evennia). Go to your game directory (referred as 'mygame' -in this section) and execute the test runner: - - evennia test --settings settings.py commands - -This command will execute Evennia's test runner using your own settings file. It will set up a dummy -database of your choice and look into the 'commands' package defined in your game directory -(`mygame/commands` in this example) to find tests. The test module's name should begin with 'test' -and contain one or more `TestCase`. A full example can be found below. - -### A simple example - -In your game directory, go to `commands` and create a new file `tests.py` inside (it could be named -anything starting with `test`). We will start by making a test that has nothing to do with Commands, -just to show how unit testing works: - -```python - # mygame/commands/tests.py - - import unittest - - class TestString(unittest.TestCase): - - """Unittest for strings (just a basic example).""" - - def test_upper(self): - """Test the upper() str method.""" - self.assertEqual('foo'.upper(), 'FOO') -``` - -This example, inspired from the Python documentation, is used to test the 'upper()' method of the -'str' class. Not very useful, but it should give you a basic idea of how tests are used. - -Let's execute that test to see if it works. - - > evennia test --settings settings.py commands - - TESTING: Using specified settings file 'server.conf.settings'. - - (Obs: Evennia's full test suite may not pass if the settings are very - different from the default. Use 'test .' as arguments to run only tests - on the game dir.) - - Creating test database for alias 'default'... - . - ---------------------------------------------------------------------- - Ran 1 test in 0.001s - - OK - Destroying test database for alias 'default'... - -We specified the `commands` package to the evennia test command since that's where we put our test -file. In this case we could just as well just said `.` to search all of `mygame` for testing files. -If we have a lot of tests it may be useful to test only a single set at a time though. We get an -information text telling us we are using our custom settings file (instead of Evennia's default -file) and then the test runs. The test passes! Change the "FOO" string to something else in the test -to see how it looks when it fails. - -### Testing commands - -This section will test the proper execution of the 'abilities' command, as described in the -[First Steps Coding](./First-Steps-Coding.md) page. Follow this tutorial to create the 'abilities' command, we -will need it to test it. - -Testing commands in Evennia is a bit more complex than the simple testing example we have seen. -Luckily, Evennia supplies a special test class to do just that ... we just need to inherit from it -and use it properly. This class is called 'CommandTest' and is defined in the -'evennia.commands.default.tests' package. To create a test for our 'abilities' command, we just -need to create a class that inherits from 'CommandTest' and add methods. - -We could create a new test file for this but for now we just append to the `tests.py` file we -already have in `commands` from before. - -```python - # bottom of mygame/commands/tests.py - - from evennia.commands.default.tests import CommandTest - - from commands.command import CmdAbilities - from typeclasses.characters import Character - - class TestAbilities(CommandTest): - - character_typeclass = Character - - def test_simple(self): - self.call(CmdAbilities(), "", "STR: 5, AGI: 4, MAG: 2") -``` - -* Line 1-4: we do some importing. 'CommandTest' is going to be our base class for our test, so we -need it. We also import our command ('CmdAbilities' in this case). Finally we import the -'Character' typeclass. We need it, since 'CommandTest' doesn't use 'Character', but -'DefaultCharacter', which means the character calling the command won't have the abilities we have -written in the 'Character' typeclass. -* Line 6-8: that's the body of our test. Here, a single command is tested in an entire class. -Default commands are usually grouped by category in a single class. There is no rule, as long as -you know where you put your tests. Note that we set the 'character_typeclass' class attribute to -Character. As explained above, if you didn't do that, the system would create a 'DefaultCharacter' -object, not a 'Character'. You can try to remove line 4 and 8 to see what happens when running the -test. -* Line 10-11: our unique testing method. Note its name: it should begin by 'test_'. Apart from -that, the method is quite simple: it's an instance method (so it takes the 'self' argument) but no -other arguments are needed. Line 11 uses the 'call' method, which is defined in 'CommandTest'. -It's a useful method that compares a command against an expected result. It would be like comparing -two strings with 'assertEqual', but the 'call' method does more things, including testing the -command in a realistic way (calling its hooks in the right order, so you don't have to worry about -that). - -Line 11 can be understood as: test the 'abilities' command (first parameter), with no argument -(second parameter), and check that the character using it receives his/her abilities (third -parameter). - -Let's run our new test: - - > evennia test --settings settings.py commands - [...] - Creating test database for alias 'default'... - .. - ---------------------------------------------------------------------- - Ran 2 tests in 0.156s - - OK - Destroying test database for alias 'default'... - -Two tests were executed, since we have kept 'TestString' from last time. In case of failure, you -will get much more information to help you fix the bug. - -### Testing Dynamic Output - -Having read the unit test tutorial on [Testing commands](./Unit-Testing.md#testing-commands) we can see -the code expects static unchanging numbers. While very good for learning it is unlikely a project -will have nothing but static output to test. Here we are going to learn how to test against dynamic -output.
- -This tutorial assumes you have a basic understanding of what regular expressions are. If you do not -I recommend reading the `Introduction` and `Simple Pattern` sections at -[Python regular expressions tutorial](https://docs.python.org/3/howto/regex.html). If you do plan on making a complete Evennia -project learning regular expressions will save a great deal of time.
- -Append the code below to your `tests.py` file.
- -```python - # bottom of mygame/commands/tests.py - - class TestDynamicAbilities(CommandTest): - - character_typeclass = Character - - def test_simple(self): - cmd_abil_result = self.call(CmdAbilities(), "") - self.assertRegex(cmd_abil_result, "STR: \d+, AGI: \d+, MAG: \d+") -``` - -Noticed that we removed the test string from `self.call`. That method always returns the string it -found from the commands output. If we remove the string to test against, all `self.call` will do is -return the screen output from the command object passed to it.
- -We are instead using the next line to test our command's output. -[assertRegex](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRegex) is a -method of `unittest.TestCase` this is inherited to `TestDynamicAbilities` from `CommandTest` who -inherited it from `EvenniaTest`.
- -What we are doing is testing the result of the `CmdAbilities` method or command against a regular -expression pattern. In this case, `"STR: \d+, AGI: \d+, MAG: \d+"`. `\d` in regular expressions or -regex are digits (numbers), the `+` is to state we want 1 or more of that pattern. Together `\d+` is -telling [assertRegex](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRegex) -that in that position of the string we expect 1 or more digits (numbers) to appear in the -string.
diff --git a/docs/0.9.5/_sources/Updating-Your-Game.md.txt b/docs/0.9.5/_sources/Updating-Your-Game.md.txt deleted file mode 100644 index ba85cf9d50..0000000000 --- a/docs/0.9.5/_sources/Updating-Your-Game.md.txt +++ /dev/null @@ -1,132 +0,0 @@ -# Updating Your Game - - -Fortunately, it's extremely easy to keep your Evennia server up-to-date. If you haven't already, see -the [Getting Started guide](./Getting-Started.md) and get everything running. - -### Updating with the latest Evennia code changes - -Very commonly we make changes to the Evennia code to improve things. There are many ways to get told -when to update: You can subscribe to the RSS feed or manually check up on the feeds from -http://www.evennia.com. You can also simply fetch the latest regularly. - -When you're wanting to apply updates, simply `cd` to your cloned `evennia` root directory and type: - - git pull - -assuming you've got the command line client. If you're using a graphical client, you will probably -want to navigate to the `evennia` directory and either right click and find your client's pull -function, or use one of the menus (if applicable). - -You can review the latest changes with - - git log - -or the equivalent in the graphical client. You can also see the latest changes online -[here](https://github.com/evennia/evennia/blob/master/CHANGELOG.md). - -You will always need to do `evennia reload` (or `reload` from -in-game) from your game-dir to have -the new code affect your game. If you want to be really sure you should run a full `evennia reboot` -so that both Server and Portal can restart (this will disconnect everyone though, so if you know the -Portal has had no updates you don't have to do that). - -### Upgrading Evennia dependencies - -On occasion we update the versions of third-party libraries Evennia depend on (or we may add a new -dependency). This will be announced on the mailing list/forum. If you run into errors when starting -Evennia, always make sure you have the latest versions of everything. In some cases, like for -Django, starting the server may also give warning saying that you are using a working, but too-old -version that should not be used in production. - -Upgrading `evennia` will automatically fetch all the latest packages that it now need. First `cd` to -your cloned `evennia` folder. Make sure your `virtualenv` is active and use - - pip install --upgrade -e . - -Remember the period (`.`) at the end - that applies the upgrade to the current location (your -`evennia` dir). - -> The `-e` means that we are _linking_ the evennia sources rather than copying them into the -environment. This means we can most of the time just update the sources (with `git pull`) and see -those changes directly applied to our installed `evennia` package. Without installing/upgrading the -package with `-e`, we would have to remember to upgrade the package every time we downloaded any new -source-code changes. - -Follow the upgrade output to make sure it finishes without errors. To check what packages are -currently available in your python environment after the upgrade, use - - pip list - -This will show you the version of all installed packages. The `evennia` package will also show the -location of its source code. - -## Migrating the Database Schema - -Whenever we change the database layout of Evennia upstream (such as when we add new features) you -will need to *migrate* your existing database. When this happens it will be clearly noted in the -`git log` (it will say something to the effect of "Run migrations"). Database changes will also be -announced on the Evennia [mailing list](https://groups.google.com/forum/#!forum/evennia). - -When the database schema changes, you just go to your game folder and run - - evennia migrate - -> Hint: If the `evennia` command is not found, you most likely need to activate your -[virtualenv](./Glossary.md#virtualenv). - -## Resetting your database - -Should you ever want to start over completely from scratch, there is no need to re-download Evennia -or anything like that. You just need to clear your database. Once you are done, you just rebuild it -from scratch as described in the second step of the [Getting Started guide](./Getting-Started.md). - -First stop a running server with - - evennia stop - -If you run the default `SQlite3` database (to change this you need to edit your `settings.py` file), -the database is actually just a normal file in `mygame/server/` called `evennia.db3`. *Simply delete -that file* - that's it. Now run `evennia migrate` to recreate a new, fresh one. - -If you run some other database system you can instead flush the database: - - evennia flush - -This will empty the database. However, it will not reset the internal counters of the database, so -you will start with higher dbref values. If this is okay, this is all you need. - -Django also offers an easy way to start the database's own management should we want more direct -control: - - evennia dbshell - -In e.g. MySQL you can then do something like this (assuming your MySQL database is named "Evennia": - - mysql> DROP DATABASE Evennia; - mysql> exit - -> NOTE: Under Windows OS, in order to access SQLite dbshell you need to [download the SQLite -command-line shell program](https://www.sqlite.org/download.html). It's a single executable file -(sqlite3.exe) that you should place in the root of either your MUD folder or Evennia's (it's the -same, in both cases Django will find it). - -## More about schema migrations - -If and when an Evennia update modifies the database *schema* (that is, the under-the-hood details as -to how data is stored in the database), you must update your existing database correspondingly to -match the change. If you don't, the updated Evennia will complain that it cannot read the database -properly. Whereas schema changes should become more and more rare as Evennia matures, it may still -happen from time to time. - -One way one could handle this is to apply the changes manually to your database using the database's -command line. This often means adding/removing new tables or fields as well as possibly convert -existing data to match what the new Evennia version expects. It should be quite obvious that this -quickly becomes cumbersome and error-prone. If your database doesn't contain anything critical yet -it's probably easiest to simply reset it and start over rather than to bother converting. - -Enter *migrations*. Migrations keeps track of changes in the database schema and applies them -automatically for you. Basically, whenever the schema changes we distribute small files called -"migrations" with the source. Those tell the system exactly how to implement the change so you don't -have to do so manually. When a migration has been added we will tell you so on Evennia's mailing -lists and in commit messages - -you then just run `evennia migrate` to be up-to-date again. diff --git a/docs/0.9.5/_sources/Using-MUX-as-a-Standard.md.txt b/docs/0.9.5/_sources/Using-MUX-as-a-Standard.md.txt deleted file mode 100644 index fb27e619b7..0000000000 --- a/docs/0.9.5/_sources/Using-MUX-as-a-Standard.md.txt +++ /dev/null @@ -1,85 +0,0 @@ -# Using MUX as a Standard - - -Evennia allows for any command syntax. If you like the way DikuMUDs, LPMuds or MOOs handle things, -you could emulate that with Evennia. If you are ambitious you could even design a whole new style, -perfectly fitting your own dreams of the ideal game. - -We do offer a default however. The default Evennia setup tends to *resemble* -[MUX2](http://www.tinymux.org/), and its cousins [PennMUSH](http://www.pennmush.org), -[TinyMUSH](http://tinymush.sourceforge.net/), and [RhostMUSH](http://www.rhostmush.org/). While the -reason for this similarity is partly historical, these codebases offer very mature feature sets for -administration and building. - -Evennia is *not* a MUX system though. It works very differently in many ways. For example, Evennia -deliberately lacks an online softcode language (a policy explained on our [softcode policy -page](./Soft-Code.md)). Evennia also does not shy from using its own syntax when deemed appropriate: the -MUX syntax has grown organically over a long time and is, frankly, rather arcane in places. All in -all the default command syntax should at most be referred to as "MUX-like" or "MUX-inspired". - -## Documentation policy - -All the commands in the default command sets should have their doc-strings formatted on a similar -form: - -```python - """ - Short header - - Usage: - key[/switches, if any] [optional] choice1||choice2||choice3 - - Switches: - switch1 - description - switch2 - description - - Examples: - usage example and output - - Longer documentation detailing the command. - - """ -``` - -- Two spaces are used for *indentation* in all default commands. -- Square brackets `[ ]` surround *optional, skippable arguments*. -- Angled brackets `< >` surround a _description_ of what to write rather than the exact syntax. -- *Explicit choices are separated by `|`. To avoid this being parsed as a color code, use `||` (this -will come out as a single `|`) or put spaces around the character ("` | `") if there's plenty of -room. -- The `Switches` and `Examples` blocks are optional based on the Command. - -Here is the `nick` command as an example: - -```python - """ - Define a personal alias/nick - - Usage: - nick[/switches] = [] - alias '' - - Switches: - object - alias an object - account - alias an account - clearall - clear all your aliases - list - show all defined aliases (also "nicks" works) - - Examples: - nick hi = say Hello, I'm Sarah! - nick/object tom = the tall man - - A 'nick' is a personal shortcut you create for your own use [...] - - """ -``` - -For commands that *require arguments*, the policy is for it to return a `Usage:` string if the -command is entered without any arguments. So for such commands, the Command body should contain -something to the effect of - -```python - if not self.args: - self.caller.msg("Usage: nick[/switches] = []") - return -``` diff --git a/docs/0.9.5/_sources/Using-Travis.md.txt b/docs/0.9.5/_sources/Using-Travis.md.txt deleted file mode 100644 index c867933ab6..0000000000 --- a/docs/0.9.5/_sources/Using-Travis.md.txt +++ /dev/null @@ -1,37 +0,0 @@ -# Using Travis - -Evennia uses [Travis CI](http://travis-ci.org/) to check that it's building successfully after every -commit to its Github repository (you can for example see the `build: passing` badge at the top of -Evennia's [Readme file](https://github.com/evennia/evennia)). If your game is open source on Github -you may use Travis for free. See [the Travis docs](http://docs.travis-ci.com/user/getting-started/) -for how to get started. - -After logging in you need to point Travis to your repository on github. One further thing you need -to set up yourself is a Travis config file named `.travis.yml` (note the initial period `.`). This -should be created in the _root_ of your game directory. - -``` yaml -dist: xenial -language: python -cache: pip - -python: - - "3.7" - - "3.8" - -install: - - git clone https://github.com/evennia/evennia.git ../evennia - - pip install -e ../evennia - -script: - - evennia test --settings settings.py - -``` - -Here we tell Travis how to download and install Evennia into a folder a level up from your game dir. -It will then install the server (so the `evennia` command is available) and run the tests only for -your game dir (based on your `settings.py` file in `server/conf/`). - -Running this will not actually do anything though, because there are no unit tests in your game dir -yet. [We have a page](./Unit-Testing.md) on how we set those up for Evennia, you should be able to refer -to that for making tests fitting your game. \ No newline at end of file diff --git a/docs/0.9.5/_sources/Version-Control.md.txt b/docs/0.9.5/_sources/Version-Control.md.txt deleted file mode 100644 index 1e8ddb32e4..0000000000 --- a/docs/0.9.5/_sources/Version-Control.md.txt +++ /dev/null @@ -1,475 +0,0 @@ -# Version Control - - -Version control software allows you to track the changes you make to your code, as well as being -able to easily backtrack these changes, share your development efforts and more. Even if you are not -contributing to Evennia itself, and only wish to develop your own MU* using Evennia, having a -version control system in place is a good idea (and standard coding practice). For an introduction -to the concept, start with the Wikipedia article -[here](http://en.wikipedia.org/wiki/Version_control). Evennia uses the version control system -[Git](https://git-scm.com/) and this is what will be covered henceforth. Note that this page also -deals with commands for Linux operating systems, and the steps below may vary for other systems, -however where possible links will be provided for alternative instructions. - -For more help on using Git, please refer to the [Official GitHub -documentation](https://help.github.com/articles/set-up-git#platform-all). - -## Setting up Git - -If you have gotten Evennia installed, you will have Git already and can skip to **Step 2** below. -Otherwise you will need to install Git on your platform. You can find expanded instructions for -installation [here](http://git-scm.com/book/en/Getting-Started-Installing-Git). - -### Step 1: Install Git - -- **Fedora Linux** - - yum install git-core - -- **Debian Linux** _(Ubuntu, Linux Mint, etc.)_ - - apt-get install git - -- **Windows**: It is recommended to use [Git for Windows](http://msysgit.github.io/). -- **Mac**: Mac platforms offer two methods for installation, one via MacPorts, which you can find -out about [here](http://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac), or -you can use the [Git OSX Installer](https://sourceforge.net/projects/git-osx-installer/). - -### Step 2: Define user/e-mail Settings for Git - -To avoid a common issue later, you will need to set a couple of settings; first you will need to -tell Git your username, followed by your e-mail address, so that when you commit code later you will -be properly credited. - -> Note that your commit information will be visible to everyone if you ever contribute to Evennia or -use an online service like github to host your code. So if you are not comfortable with using your -real, full name online, put a nickname here. - -1. Set the default name for git to use when you commit: - - git config --global user.name "Your Name Here" - -2. Set the default email for git to use when you commit: - - git config --global user.email "your_email@example.com" - - -## Putting your game folder under version control - -> Note: The game folder's version control is completely separate from Evennia's repository. - -After you have set up your game you will have created a new folder to host your particular game -(let's call this folder `mygame` for now). - -This folder is *not* under version control at this point. - - git init mygame - -Your mygame folder is now ready for version control! Now add all the content and make a first -commit: - - cd mygame - git add * - git commit -m "Initial commit" - -Read on for help on what these commands do. - - -### Tracking files - -When working on your code or fix bugs in your local branches you may end up creating new files. If -you do you must tell Git to track them by using the add command: - -``` -git add -``` - -You can check the current status of version control with `git status`. This will show if you have -any modified, added or otherwise changed files. Some files, like database files, logs and temporary -PID files are usually *not* tracked in version control. These should either not show up or have a -question mark in front of them. - -### Controlling tracking - -You will notice that some files are not covered by your git version control, notably your settings -file (`mygame/server/conf/settings.py`) and your sqlite3 database file `mygame/server/evennia.db3`. -This is controlled by the hidden file `mygame/.gitignore`. Evennia creates this file as part of the -creation of your game directory. Everything matched in this file will be ignored by GIT. If you want -to, for example, include your settings file for collaborators to access, remove that entry in -`.gitignore`. - -> Note: You should *never* put your sqlite3 database file into git by removing its entry in -`.gitignore`. GIT is for backing up your code, not your database. That way lies madness and a good -chance you'll confuse yourself so that after a few commits and reverts don't know what is in your -database or not. If you want to backup your database, do so by simply copying the file on your hard -drive to a backup-name. - -### Committing your Code - -> Committing means storing the current snapshot of your code within git. This creates a "save point" -or "history" of your development process. You can later jump back and forth in your history, for -example to figure out just when a bug was introduced or see what results the code used to produce -compared to now. - -It's usually a good idea to commit your changes often. Committing is fast and local only - you will -never commit anything online at this point. To commit your changes, use - -``` -git commit --all -``` - -This will save all changes you made since last commit. The command will open a text editor where you -can add a message detailing the changes you've made. Make it brief but informative. You can see the -history of commits with `git log`. If you don't want to use the editor you can set the message -directly by using the `-m` flag: - -``` -git commit --all -m "This fixes a bug in the combat code." -``` - -### Changing your mind - -If you have non-committed changes that you realize you want to throw away, you can do the following: - -``` -git checkout -``` - -This will revert the file to the state it was in at your last `commit`, throwing away the changes -you did to it since. It's a good way to make wild experiments without having to remember just what -you changed. If you do ` git checkout .` you will throw away _all_ changes since the last commit. - -### Pushing your code online - -So far your code is only located on your private machine. A good idea is to back it up online. The -easiest way to do this is to push it to your own remote repository on GitHub. - -1. Make sure you have your game directory setup under git version control as described above. Make -sure to commit any changes. -2. Create a new, empty repository on Github. Github explains how -[here](https://help.github.com/articles/create-a-repo/) (do *not* "Initialize the repository with a -README" or else you'll create unrelated histories). -3. From your local game dir, do `git remote add origin ` where `` is the URL -to your online repo. This tells your game dir that it should be pushing to the remote online dir. -4. `git remote -v` to verify the online dir. -5. `git push origin master` now pushes your game dir online so you can see it on github.com. - -You can commit your work locally (`git commit --all -m "Make a change that ..."`) as many times as -you want. When you want to push those changes to your online repo, you do `git push`. You can also -`git clone ` from your online repo to somewhere else (like your production -server) and henceforth do `git pull` to update that to the latest thing you pushed. - -Note that GitHub's repos are, by default publicly visible by all. Creating a publicly visible online -clone might not be what you want for all parts of your development process - you may prefer a more -private venue when sharing your revolutionary work with your team. If that's the case you can change -your repository to "Private" in the github settings. Then your code will only be visible to those -you specifically grant access. - - -## Forking Evennia - -This helps you set up an online *fork* of Evennia so you can easily commit fixes and help with -upstream development. - -### Step 1: Fork the evennia/master repository - -> Before proceeding with the following step, make sure you have registered and created an account on -[GitHub.com](https://github.com/). This is necessary in order to create a fork of Evennia's master -repository, and to push your commits to your fork either for yourself or for contributing to -Evennia. - -A _fork_ is a clone of the master repository that you can make your own commits and changes to. At -the top of [this page](https://github.com/evennia/evennia), click the "Fork" button, as it appears -below. ![](https://github-images.s3.amazonaws.com/help/bootcamp/Bootcamp-Fork.png) - -### Step 2: Clone your fork - -The fork only exists online as of yet. In a terminal, change your directory to the folder you wish -to develop in. From this directory run the following command: - -``` -git clone https://github.com/yourusername/evennia.git -``` - -This will download your fork to your computer. It creates a new folder `evennia/` at your current -location. - -### Step 3: Configure remotes - -A _remote_ is a repository stored on another computer, in this case on GitHub's server. When a -repository is cloned, it has a default remote called `origin`. This points to your fork on GitHub, -not the original repository it was forked from. To easily keep track of the original repository -(that is, Evennia's official repository), you need to add another remote. The standard name for this -remote is "upstream". - -Below we change the active directory to the newly cloned "evennia" directory and then assign the -original Evennia repository to a remote called "upstream": - -``` -cd evennia -git remote add upstream https://github.com/evennia/evennia.git -``` - -If you also want to access Evennia's `develop` branch (the bleeding edge development branch) do the -following: - -``` -git fetch upstream develop -git checkout develop -``` - -You should now have the upstream branch available locally. You can use this instead of `master` -below if you are contributing new features rather than bug fixes. - - -## Working with your fork - -> A _branch_ is a separate instance of your code. Changes you do to code in a branch does not affect -that in other branches (so if you for example add/commit a file to one branch and then switches to -another branch, that file will be gone until you switch back to the first branch again). One can -switch between branches at will and create as many branches as one needs for a given project. The -content of branches can also be merged together or deleted without affecting other branches. This is -not only a common way to organize development but also to test features without messing with -existing code. - -The default _branch_ of git is called the "master" branch. As a rule of thumb, you should *never* -make modifications directly to your local copy of the master branch. Rather keep the master clean -and only update it by pulling our latest changes to it. Any work you do should instead happen in a -local, other branches. - -### Making a work branch - -``` -git checkout -b myfixes -``` - -This command will checkout and automatically create the new branch `myfixes` on your machine. If you -stared out in the master branch, *myfixes* will be a perfect copy of the master branch. You can see -which branch you are on with `git branch` and change between different branches with `git checkout -`. - -Branches are fast and cheap to create and manage. It is common practice to create a new branch for -every bug you want to work on or feature you want to create, then create a *pull request* for that -branch to be merged upstream (see below). Not only will this organize your work, it will also make -sure that *your* master branch version of Evennia is always exactly in sync with the upstream -version's master branch. - -### Updating with upstream changes - -When Evennia's official repository updates, first make sure to commit all your changes to your -branch and then checkout the "clean" master branch: - -``` -git commit --all -git checkout master -``` - -Pull the latest changes from upstream: - -``` -git pull upstream master -``` - -This should sync your local master branch with upstream Evennia's master branch. Now we go back to -our own work-branch (let's say it's still called "myfixes") and _merge_ the updated master into our -branch. - -``` -git checkout myfixes -git merge master -``` - -If everything went well, your `myfixes` branch will now have the latest version of Evennia merged -with whatever changes you have done. Use `git log` to see what has changed. You may need to restart -the server or run `manage.py migrate` if the database schema changed (this will be seen in the -commit log and on the mailing list). See the [Git manuals](http://git-scm.com/documentation) for -learning more about useful day-to-day commands, and special situations such as dealing with merge -collisions. - -## Sharing your Code Publicly - -Up to this point your `myfixes` branch only exists on your local computer. No one else can see it. -If you want a copy of this branch to also appear in your online fork on GitHub, make sure to have -checked out your "myfixes" branch and then run the following: - -``` -git push -u origin myfixes -``` - -This will create a new _remote branch_ named "myfixes" in your online repository (which is refered -to as "origin" by default); the `-u` flag makes sure to set this to the default push location. -Henceforth you can just use `git push` from your myfixes branch to push your changes online. This is -a great way to keep your source backed-up and accessible. Remember though that by default your -repository will be public so everyone will be able to browse and download your code (same way as you -can with Evennia itself). If you want secrecy you can change your repository to "Private" in the -Github settings. Note though that if you do, you might have trouble contributing to Evennia (since -we can't see the code you want to share). - -*Note: If you hadn't setup a public key on GitHub or aren't asked for a username/password, you might -get an error `403: Forbidden Access` at this stage. In that case, some users have reported that the -workaround is to create a file `.netrc` under your home directory and add your credentials there:* - -```bash -machine github.com -login -password -``` - -## Committing fixes to Evennia - -_Contributing_ can mean both bug-fixes or adding new features to Evennia. Please note that if your -change is not already listed and accepted in the [Issue -Tracker](https://github.com/evennia/evennia/issues), it is recommended that you first hit the -developer mailing list or IRC chat to see beforehand if your feature is deemed suitable to include -as a core feature in the engine. When it comes to bug-fixes, other developers may also have good -input on how to go about resolving the issue. - -To contribute you need to have [forked Evennia](./Version-Control.md#forking-evennia) first. As described -above you should do your modification in a separate local branch (not in the master branch). This -branch is what you then present to us (as a *Pull request*, PR, see below). We can then merge your -change into the upstream master and you then do `git pull` to update master usual. Now that the -master is updated with your fixes, you can safely delete your local work branch. Below we describe -this work flow. - -First update the Evennia master branch to the latest Evennia version: - -``` -git checkout master -git pull upstream master -``` - -Next, create a new branch to hold your contribution. Let's call it the "fixing_strange_bug" branch: - -``` -git checkout -b fixing_strange_bug -``` - -It is wise to make separate branches for every fix or series of fixes you want to contribute. You -are now in your new `fixing_strange_bug` branch. You can list all branches with `git branch` and -jump between branches with `git checkout `. Code and test things in here, committing as -you go: - -``` -git commit --all -m "Fix strange bug in look command. Resolves #123." -``` - -You can make multiple commits if you want, depending on your work flow and progress. Make sure to -always make clear and descriptive commit messages so it's easy to see what you intended. To refer -to, say, issue number 123, write `#123`, it will turn to a link on GitHub. If you include the text -"Resolves #123", that issue will be auto-closed on GitHub if your commit gets merged into main -Evennia. - ->If you refer to in-game commands that start with `@`(such as `@examine`), please put them in -backticks \`, for example \`@examine\`. The reason for this is that GitHub uses `@username` to refer -to GitHub users, so if you forget the ticks, any user happening to be named `examine` will get a -notification .... - -If you implement multiple separate features/bug-fixes, split them into different branches if they -are very different and should be handled as separate PRs. You can do any number of commits to your -branch as you work. Once you are at a stage where you want to show the world what you did you might -want to consider making it clean for merging into Evennia's master branch by using [git -rebase](https://www.git-scm.com/book/en/v2/Git-Branching-Rebasing) (this is not always necessary, -and if it sounds too hard, say so and we'll handle it on our end). - -Once you are ready, push your work to your online Evennia fork on github, in a new remote branch: - -``` -git push -u origin fixing_strange_bug -``` - -The `-u` flag is only needed the first time - this tells GIT to create a remote branch. If you -already created the remote branch earlier, just stand in your `fixing_strange_bug` branch and do -`git push`. - -Now you should tell the Evennia developers that they should consider merging your brilliant changes -into Evennia proper. [Create a pull request](https://github.com/evennia/evennia/pulls) and follow -the instructions. Make sure to specifically select your `fixing_strange_bug` branch to be the source -of the merge. Evennia developers will then be able to examine your request and merge it if it's -deemed suitable. - -Once your changes have been merged into Evennia your local `fixing_strange_bug` can be deleted -(since your changes are now available in the "clean" Evennia repository). Do - -``` -git branch -D fixing_strange_bug -``` - -to delete your work branch. Update your master branch (`checkout master` and then `git pull`) and -you should get your fix back, now as a part of official Evennia! - - -## GIT tips and tricks - -Some of the GIT commands can feel a little long and clunky if you need to do them often. Luckily you -can create aliases for those. Here are some useful commands to run: - - -``` -# git st -# - view brief status info -git config --global alias.st 'status -s' -``` - -Above, you only need to ever enter the `git config ...` command once - you have then added the new -alias. Afterwards, just do `git st` to get status info. All the examples below follow the same -template. - -``` -# git cl -# - clone a repository -git config --global alias.cl clone -``` - -``` -# git cma "commit message" -# - commit all changes without opening editor for message -git config --global alias.cma 'commit -a -m' -``` - -``` -# git ca -# - amend text to your latest commit message -git config --global alias.ca 'commit --amend' -``` - -``` -# git fl -# - file log; shows diffs of files in latest commits -git config --global alias.fl 'log -u' -``` - -``` -# git co [branchname] -# - checkout -git config --global alias.co checkout -``` - -``` -# git br -# - create branch -git config --global alias.br branch -``` - -``` -# git ls -# - view log tree -git config --global alias.ls 'log --pretty=format:"%C(green)%h\ %C(yellow)[%ad]%Cred%d\ -%Creset%s%Cblue\ [%cn]" --decorate --date=relative --graph' -``` - -``` -# git diff -# - show current uncommitted changes -git config --global alias.diff 'diff --word-diff' -``` - -``` -# git grep -# - search (grep) codebase for a search criterion -git config --global alias.grep 'grep -Ii' -``` - -To get a further feel for GIT there is also [a good YouTube talk about -it](https://www.youtube.com/watch?v=1ffBJ4sVUb4#t=1m58s) - it's a bit long but it will help you -understand the underlying ideas behind GIT -(which in turn makes it a lot more intuitive to use). diff --git a/docs/0.9.5/_sources/Weather-Tutorial.md.txt b/docs/0.9.5/_sources/Weather-Tutorial.md.txt deleted file mode 100644 index b8da86932c..0000000000 --- a/docs/0.9.5/_sources/Weather-Tutorial.md.txt +++ /dev/null @@ -1,54 +0,0 @@ -# Weather Tutorial - - -This tutorial will have us create a simple weather system for our MUD. The way we want to use this -is to have all outdoor rooms echo weather-related messages to the room at regular and semi-random -intervals. Things like "Clouds gather above", "It starts to rain" and so on. - -One could imagine every outdoor room in the game having a script running on themselves that fires -regularly. For this particular example it is however more efficient to do it another way, namely by -using a "ticker-subscription" model. The principle is simple: Instead of having each Object -individually track the time, they instead subscribe to be called by a global ticker who handles time -keeping. Not only does this centralize and organize much of the code in one place, it also has less -computing overhead. - -Evennia offers the [TickerHandler](./TickerHandler.md) specifically for using the subscription model. We -will use it for our weather system. - -We will assume you know how to make your own Typeclasses. If not see one of the beginning tutorials. -We will create a new WeatherRoom typeclass that is aware of the day-night cycle. - -```python - - import random - from evennia import DefaultRoom, TICKER_HANDLER - - ECHOES = ["The sky is clear.", - "Clouds gather overhead.", - "It's starting to drizzle.", - "A breeze of wind is felt.", - "The wind is picking up"] # etc - - class WeatherRoom(DefaultRoom): - "This room is ticked at regular intervals" - - def at_object_creation(self): - "called only when the object is first created" - TICKER_HANDLER.add(60 * 60, self.at_weather_update) - - def at_weather_update(self, *args, **kwargs): - "ticked at regular intervals" - echo = random.choice(ECHOES) - self.msg_contents(echo) -``` - -In the `at_object_creation` method, we simply added ourselves to the TickerHandler and tell it to -call `at_weather_update` every hour (`60*60` seconds). During testing you might want to play with a -shorter time duration. - -For this to work we also create a custom hook `at_weather_update(*args, **kwargs)`, which is the -call sign required by TickerHandler hooks. - -Henceforth the room will inform everyone inside it when the weather changes. This particular example -is of course very simplistic - the weather echoes are just randomly chosen and don't care what -weather came before it. Expanding it to be more realistic is a useful exercise. diff --git a/docs/0.9.5/_sources/Web-Character-Generation.md.txt b/docs/0.9.5/_sources/Web-Character-Generation.md.txt deleted file mode 100644 index ed417ce9c2..0000000000 --- a/docs/0.9.5/_sources/Web-Character-Generation.md.txt +++ /dev/null @@ -1,637 +0,0 @@ -# Web Character Generation - - -## Introduction - -This tutorial will create a simple web-based interface for generating a new in-game Character. -Accounts will need to have first logged into the website (with their `AccountDB` account). Once -finishing character generation the Character will be created immediately and the Accounts can then -log into the game and play immediately (the Character will not require staff approval or anything -like that). This guide does not go over how to create an AccountDB on the website with the right -permissions to transfer to their web-created characters. - -It is probably most useful to set `MULTISESSION_MODE = 2` or `3` (which gives you a character- -selection screen when you log into the game later). Other modes can be used with some adaptation to -auto-puppet the new Character. - -You should have some familiarity with how Django sets up its Model Template View framework. You need -to understand what is happening in the basic [Web Character View tutorial](./Web-Character-View-Tutorial.md). If you don’t understand the listed tutorial or have a grasp of Django basics, please look -at the [Django tutorial](https://docs.djangoproject.com/en/1.8/intro/) to get a taste of what Django -does, before throwing Evennia into the mix (Evennia shares its API and attributes with the website -interface). This guide will outline the format of the models, views, urls, and html templates -needed. - -## Pictures - -Here are some screenshots of the simple app we will be making. - -Index page, with no character application yet done: - -*** -![Index page, with no character application yet done.](https://lh3.googleusercontent.com/-57KuSWHXQ_M/VWcULN152tI/AAAAAAAAEZg/kINTmVlHf6M/w425-h189-no/webchargen_index2.gif) -*** - -Having clicked the "create" link you get to create your character (here we will only have name and -background, you can add whatever is needed to fit your game): - -*** -![Character creation.](https://lh3.googleusercontent.com/-ORiOEM2R_yQ/VWcUKgy84rI/AAAAAAAAEZY/B3CBh3FHii4/w607-h60-no/webchargen_creation.gif) -*** - -Back to the index page. Having entered our character application (we called our character "TestApp") -you see it listed: - -*** -![Having entered an application.](https://lh6.googleusercontent.com/-HlxvkvAimj4/VWcUKjFxEiI/AAAAAAAAEZo/gLppebr05JI/w321-h194-no/webchargen_index1.gif) -*** - -We can also view an already written character application by clicking on it - this brings us to the -*detail* page: - -*** -![Detail view of character application.](https://lh6.googleusercontent.com/-2m1UhSE7s_k/VWcUKfLRfII/AAAAAAAAEZc/UFmBOqVya4k/w267-h175-no/webchargen_detail.gif) -*** - -## Installing an App - -Assuming your game is named "mygame", navigate to your `mygame/` directory, and type: - - evennia startapp chargen - -This will initialize a new Django app we choose to call "chargen". It is directory containing some -basic starting things Django needs. You will need to move this directory: for the time being, it is -in your `mygame` directory. Better to move it in your `mygame/web` directory, so you have -`mygame/web/chargen` in the end. - -Next, navigate to `mygame/server/conf/settings.py` and add or edit the following line to make -Evennia (and Django) aware of our new app: - - INSTALLED_APPS += ('web.chargen',) - -After this, we will get into defining our *models* (the description of the database storage), -*views* (the server-side website content generators), *urls* (how the web browser finds the pages) -and *templates* (how the web page should be structured). - -### Installing - Checkpoint: - -* you should have a folder named `chargen` or whatever you chose in your mygame/web/ directory -* you should have your application name added to your INSTALLED_APPS in settings.py - -## Create Models - -Models are created in `mygame/web/chargen/models.py`. - -A [Django database model](./New-Models.md) is a Python class that describes the database storage of the -data you want to manage. Any data you choose to store is stored in the same database as the game and -you have access to all the game's objects here. - -We need to define what a character application actually is. This will differ from game to game so -for this tutorial we will define a simple character sheet with the following database fields: - - -* `app_id` (AutoField): Primary key for this character application sheet. -* `char_name` (CharField): The new character's name. -* `date_applied` (DateTimeField): Date that this application was received. -* `background` (TextField): Character story background. -* `account_id` (IntegerField): Which account ID does this application belong to? This is an -AccountID from the AccountDB object. -* `submitted` (BooleanField): `True`/`False` depending on if the application has been submitted yet. - -> Note: In a full-fledged game, you’d likely want them to be able to select races, skills, -attributes and so on. - -Our `models.py` file should look something like this: - -```python -# in mygame/web/chargen/models.py - -from django.db import models - -class CharApp(models.Model): - app_id = models.AutoField(primary_key=True) - char_name = models.CharField(max_length=80, verbose_name='Character Name') - date_applied = models.DateTimeField(verbose_name='Date Applied') - background = models.TextField(verbose_name='Background') - account_id = models.IntegerField(default=1, verbose_name='Account ID') - submitted = models.BooleanField(default=False) -``` - -You should consider how you are going to link your application to your account. For this tutorial, -we are using the account_id attribute on our character application model in order to keep track of -which characters are owned by which accounts. Since the account id is a primary key in Evennia, it -is a good candidate, as you will never have two of the same IDs in Evennia. You can feel free to use -anything else, but for the purposes of this guide, we are going to use account ID to join the -character applications with the proper account. - -### Model - Checkpoint: - -* you should have filled out `mygame/web/chargen/models.py` with the model class shown above -(eventually adding fields matching what you need for your game). - -## Create Views - -*Views* are server-side constructs that make dynamic data available to a web page. We are going to -add them to `mygame/web/chargen.views.py`. Each view in our example represents the backbone of a -specific web page. We will use three views and three pages here: - -* The index (managing `index.html`). This is what you see when you navigate to -`http://yoursite.com/chargen`. -* The detail display sheet (manages `detail.html`). A page that passively displays the stats of a -given Character. -* Character creation sheet (manages `create.html`). This is the main form with fields to fill in. - -### *Index* view - -Let’s get started with the index first. - -We’ll want characters to be able to see their created characters so let’s - -```python -# file mygame/web/chargen.views.py - -from .models import CharApp - -def index(request): - current_user = request.user # current user logged in - p_id = current_user.id # the account id - # submitted Characters by this account - sub_apps = CharApp.objects.filter(account_id=p_id, submitted=True) - context = {'sub_apps': sub_apps} - # make the variables in 'context' available to the web page template - return render(request, 'chargen/index.html', context) -``` - -### *Detail* view - -Our detail page will have pertinent character application information our users can see. Since this -is a basic demonstration, our detail page will only show two fields: - -* Character name -* Character background - -We will use the account ID again just to double-check that whoever tries to check our character page -is actually the account who owns the application. - -```python -# file mygame/web/chargen.views.py - -def detail(request, app_id): - app = CharApp.objects.get(app_id=app_id) - name = app.char_name - background = app.background - submitted = app.submitted - p_id = request.user.id - context = {'name': name, 'background': background, - 'p_id': p_id, 'submitted': submitted} - return render(request, 'chargen/detail.html', context) -``` - -## *Creating* view - -Predictably, our *create* function will be the most complicated of the views, as it needs to accept -information from the user, validate the information, and send the information to the server. Once -the form content is validated will actually create a playable Character. - -The form itself we will define first. In our simple example we are just looking for the Character's -name and background. This form we create in `mygame/web/chargen/forms.py`: - -```python -# file mygame/web/chargen/forms.py - -from django import forms - -class AppForm(forms.Form): - name = forms.CharField(label='Character Name', max_length=80) - background = forms.CharField(label='Background') -``` - -Now we make use of this form in our view. - -```python -# file mygame/web/chargen/views.py - -from web.chargen.models import CharApp -from web.chargen.forms import AppForm -from django.http import HttpResponseRedirect -from datetime import datetime -from evennia.objects.models import ObjectDB -from django.conf import settings -from evennia.utils import create - -def creating(request): - user = request.user - if request.method == 'POST': - form = AppForm(request.POST) - if form.is_valid(): - name = form.cleaned_data['name'] - background = form.cleaned_data['background'] - applied_date = datetime.now() - submitted = True - if 'save' in request.POST: - submitted = False - app = CharApp(char_name=name, background=background, - date_applied=applied_date, account_id=user.id, - submitted=submitted) - app.save() - if submitted: - # Create the actual character object - typeclass = settings.BASE_CHARACTER_TYPECLASS - home = ObjectDB.objects.get_id(settings.GUEST_HOME) - # turn the permissionhandler to a string - perms = str(user.permissions) - # create the character - char = create.create_object(typeclass=typeclass, key=name, - home=home, permissions=perms) - user.db._playable_characters.append(char) - # add the right locks for the character so the account can - # puppet it - char.locks.add("puppet:id(%i) or pid(%i) or perm(Developers) " - "or pperm(Developers)" % (char.id, user.id)) - char.db.background = background # set the character background - return HttpResponseRedirect('/chargen') - else: - form = AppForm() - return render(request, 'chargen/create.html', {'form': form}) -``` - -> Note also that we basically create the character using the Evennia API, and we grab the proper -permissions from the `AccountDB` object and copy them to the character object. We take the user -permissions attribute and turn that list of strings into a string object in order for the -create_object function to properly process the permissions. - -Most importantly, the following attributes must be set on the created character object: - -* Evennia [permissions](./Locks.md#permissions) (copied from the `AccountDB`). -* The right `puppet` [locks](./Locks.md) so the Account can actually play as this Character later. -* The relevant Character [typeclass](./Typeclasses.md) -* Character name (key) -* The Character's home room location (`#2` by default) - -Other attributes are strictly speaking optional, such as the `background` attribute on our -character. It may be a good idea to decompose this function and create a separate _create_character -function in order to set up your character object the account owns. But with the Evennia API, -setting custom attributes is as easy as doing it in the meat of your Evennia game directory. - -After all of this, our `views.py` file should look like something like this: - -```python -# file mygame/web/chargen/views.py - -from django.shortcuts import render -from web.chargen.models import CharApp -from web.chargen.forms import AppForm -from django.http import HttpResponseRedirect -from datetime import datetime -from evennia.objects.models import ObjectDB -from django.conf import settings -from evennia.utils import create - -def index(request): - current_user = request.user # current user logged in - p_id = current_user.id # the account id - # submitted apps under this account - sub_apps = CharApp.objects.filter(account_id=p_id, submitted=True) - context = {'sub_apps': sub_apps} - return render(request, 'chargen/index.html', context) - -def detail(request, app_id): - app = CharApp.objects.get(app_id=app_id) - name = app.char_name - background = app.background - submitted = app.submitted - p_id = request.user.id - context = {'name': name, 'background': background, - 'p_id': p_id, 'submitted': submitted} - return render(request, 'chargen/detail.html', context) - -def creating(request): - user = request.user - if request.method == 'POST': - form = AppForm(request.POST) - if form.is_valid(): - name = form.cleaned_data['name'] - background = form.cleaned_data['background'] - applied_date = datetime.now() - submitted = True - if 'save' in request.POST: - submitted = False - app = CharApp(char_name=name, background=background, - date_applied=applied_date, account_id=user.id, - submitted=submitted) - app.save() - if submitted: - # Create the actual character object - typeclass = settings.BASE_CHARACTER_TYPECLASS - home = ObjectDB.objects.get_id(settings.GUEST_HOME) - # turn the permissionhandler to a string - perms = str(user.permissions) - # create the character - char = create.create_object(typeclass=typeclass, key=name, - home=home, permissions=perms) - user.db._playable_characters.append(char) - # add the right locks for the character so the account can - # puppet it - char.locks.add("puppet:id(%i) or pid(%i) or perm(Developers) " - "or pperm(Developers)" % (char.id, user.id)) - char.db.background = background # set the character background - return HttpResponseRedirect('/chargen') - else: - form = AppForm() - return render(request, 'chargen/create.html', {'form': form}) -``` - -### Create Views - Checkpoint: - -* you’ve defined a `views.py` that has an index, detail, and creating functions. -* you’ve defined a forms.py with the `AppForm` class needed by the `creating` function of -`views.py`. -* your `mygame/web/chargen` directory should now have a `views.py` and `forms.py` file - -## Create URLs - -URL patterns helps redirect requests from the web browser to the right views. These patterns are -created in `mygame/web/chargen/urls.py`. - -```python -# file mygame/web/chargen/urls.py - -from django.conf.urls import url -from web.chargen import views - -urlpatterns = [ - # ex: /chargen/ - url(r'^$', views.index, name='index'), - # ex: /chargen/5/ - url(r'^(?P[0-9]+)/$', views.detail, name='detail'), - # ex: /chargen/create - url(r'^create/$', views.creating, name='creating'), -] -``` - -You could change the format as you desire. To make it more secure, you could remove app_id from the -"detail" url, and instead just fetch the account’s applications using a unifying field like -account_id to find all the character application objects to display. - -We must also update the main `mygame/web/urls.py` file (that is, one level up from our chargen app), -so the main website knows where our app's views are located. Find the `patterns` variable, and -change it to include: - -```python -# in file mygame/web/urls.py - -from django.conf.urls import url, include - -# default evennia patterns -from evennia.web.urls import urlpatterns - -# eventual custom patterns -custom_patterns = [ - # url(r'/desired/url/', view, name='example'), -] - -# this is required by Django. -urlpatterns += [ - url(r'^chargen/', include('web.chargen.urls')), -] - -urlpatterns = custom_patterns + urlpatterns -``` - -### URLs - Checkpoint: - -* You’ve created a urls.py file in the `mygame/web/chargen` directory -* You have edited the main `mygame/web/urls.py` file to include urls to the `chargen` directory. - -## HTML Templates - -So we have our url patterns, views, and models defined. Now we must define our HTML templates that -the actual user will see and interact with. For this tutorial we us the basic *prosimii* template -that comes with Evennia. - -Take note that we use `user.is_authenticated` to make sure that the user cannot create a character -without logging in. - -These files will all go into the `/mygame/web/chargen/templates/chargen/` directory. - -### index.html - -This HTML template should hold a list of all the applications the account currently has active. For -this demonstration, we will only list the applications that the account has submitted. You could -easily adjust this to include saved applications, or other types of applications if you have -different kinds. - -Please refer back to `views.py` to see where we define the variables these templates make use of. - -```html - - -{% extends "base.html" %} -{% block content %} -{% if user.is_authenticated %} -

Character Generation

- {% if sub_apps %} - - {% else %} -

You haven't submitted any character applications.

- {% endif %} - {% else %} -

Please loginfirst.

-{% endif %} -{% endblock %} -``` - -### detail.html - -This page should show a detailed character sheet of their application. This will only show their -name and character background. You will likely want to extend this to show many more fields for your -game. In a full-fledged character generation, you may want to extend the boolean attribute of -submitted to allow accounts to save character applications and submit them later. - -```html - - -{% extends "base.html" %} -{% block content %} -

Character Information

-{% if user.is_authenticated %} - {% if user.id == p_id %} -

{{name}}

-

Background

-

{{background}}

-

Submitted: {{submitted}}

- {% else %} -

You didn't submit this character.

- {% endif %} -{% else %} -

You aren't logged in.

-{% endif %} -{% endblock %} -``` - -### create.html - -Our create HTML template will use the Django form we defined back in views.py/forms.py to drive the -majority of the application process. There will be a form input for every field we defined in -forms.py, which is handy. We have used POST as our method because we are sending information to the -server that will update the database. As an alternative, GET would be much less secure. You can read -up on documentation elsewhere on the web for GET vs. POST. - -```html - - -{% extends "base.html" %} -{% block content %} -

Character Creation

-{% if user.is_authenticated %} -
- {% csrf_token %} - {{ form }} - -
-{% else %} -

You aren't logged in.

-{% endif %} -{% endblock %} -``` - -### Templates - Checkpoint: - -* Create a `index.html`, `detail.html` and `create.html` template in your -`mygame/web/chargen/templates/chargen` directory - -## Activating your new character generation - -After finishing this tutorial you should have edited or created the following files: - -```bash -mygame/web/urls.py -mygame/web/chargen/models.py -mygame/web/chargen/views.py -mygame/web/chargen/urls.py -mygame/web/chargen/templates/chargen/index.html -mygame/web/chargen/templates/chargen/create.html -mygame/web/chargen/templates/chargen/detail.html -``` - -Once you have all these files stand in your `mygame/`folder and run: - -```bash -evennia makemigrations -evennia migrate -``` - -This will create and update the models. If you see any errors at this stage, read the traceback -carefully, it should be relatively easy to figure out where the error is. - -Login to the website (you need to have previously registered an Player account with the game to do -this). Next you navigate to `http://yourwebsite.com/chargen` (if you are running locally this will -be something like `http://localhost:4001/chargen` and you will see your new app in action. - -This should hopefully give you a good starting point in figuring out how you’d like to approach your -own web generation. The main difficulties are in setting the appropriate settings on your newly -created character object. Thankfully, the Evennia API makes this easy. - -## Adding a no CAPCHA reCAPCHA on your character generation - -As sad as it is, if your server is open to the web, bots might come to visit and take advantage of -your open form to create hundreds, thousands, millions of characters if you give them the -opportunity. This section shows you how to use the [No CAPCHA reCAPCHA](https://www.google.com/recaptcha/intro/invisible.html) designed by Google. Not only is it -easy to use, it is user-friendly... for humans. A simple checkbox to check, except if Google has -some suspicion, in which case you will have a more difficult test with an image and the usual text -inside. It's worth pointing out that, as long as Google doesn't suspect you of being a robot, this -is quite useful, not only for common users, but to screen-reader users, to which reading inside of -an image is pretty difficult, if not impossible. And to top it all, it will be so easy to add in -your website. - -### Step 1: Obtain a SiteKey and secret from Google - -The first thing is to ask Google for a way to safely authenticate your website to their service. To -do it, we need to create a site key and a secret. Go to -[https://www.google.com/recaptcha/admin](https://www.google.com/recaptcha/admin) to create such a -site key. It's quite easy when you have a Google account. - -When you have created your site key, save it safely. Also copy your secret key as well. You should -find both information on the web page. Both would contain a lot of letters and figures. - -### Step 2: installing and configuring the dedicated Django app - -Since Evennia runs on Django, the easiest way to add our CAPCHA and perform the proper check is to -install the dedicated Django app. Quite easy: - - pip install django-nocaptcha-recaptcha - -And add it to the installed apps in your settings. In your `mygame/server/conf/settings.py`, you -might have something like this: - -```python -# ... -INSTALLED_APPS += ( - 'web.chargen', - 'nocaptcha_recaptcha', -) -``` - -Don't close the setting file just yet. We have to add in the site key and secret key. You can add -them below: - -```python -# NoReCAPCHA site key -NORECAPTCHA_SITE_KEY = "PASTE YOUR SITE KEY HERE" -# NoReCAPCHA secret key -NORECAPTCHA_SECRET_KEY = "PUT YOUR SECRET KEY HERE" -``` - -### Step 3: Adding the CAPCHA to our form - -Finally we have to add the CAPCHA to our form. It will be pretty easy too. First, open your -`web/chargen/forms.py` file. We're going to add a new field, but hopefully, all the hard work has -been done for us. Update at your convenience, You might end up with something like this: - -```python -from django import forms -from nocaptcha_recaptcha.fields import NoReCaptchaField - -class AppForm(forms.Form): - name = forms.CharField(label='Character Name', max_length=80) - background = forms.CharField(label='Background') - captcha = NoReCaptchaField() -``` - -As you see, we added a line of import (line 2) and a field in our form. - -And lastly, we need to update our HTML file to add in the Google library. You can open -`web/chargen/templates/chargen/create.html`. There's only one line to add: - -```html - -``` - -And you should put it at the bottom of the page. Just before the closing body would be good, but -for the time being, the base page doesn't provide a footer block, so we'll put it in the content -block. Note that it's not the best place, but it will work. In the end, your -`web/chargen/templates/chargen/create.html` file should look like this: - -```html -{% extends "base.html" %} -{% block content %} -

Character Creation

-{% if user.is_authenticated %} -
- {% csrf_token %} - {{ form }} - -
-{% else %} -

You aren't logged in.

-{% endif %} - -{% endblock %} -``` - -Reload and open [http://localhost:4001/chargen/create](http://localhost:4001/chargen/create/) and -you should see your beautiful CAPCHA just before the "submit" button. Try not to check the checkbox -to see what happens. And do the same while checking the checkbox! diff --git a/docs/0.9.5/_sources/Web-Character-View-Tutorial.md.txt b/docs/0.9.5/_sources/Web-Character-View-Tutorial.md.txt deleted file mode 100644 index 893ba0330a..0000000000 --- a/docs/0.9.5/_sources/Web-Character-View-Tutorial.md.txt +++ /dev/null @@ -1,228 +0,0 @@ -# Web Character View Tutorial - - -**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](./Web-Tutorial.md).** - -In this tutorial we will create a web page that displays the stats of a game character. For this, -and all other pages we want to make specific to our game, we'll need to create our own Django "app" - -We'll call our app `character`, since it will be dealing with character information. From your game -dir, run - - evennia startapp character - -This will create a directory named `character` in the root of your game dir. It contains all basic -files that a Django app needs. To keep `mygame` well ordered, move it to your `mygame/web/` -directory instead: - - mv character web/ - -Note that we will not edit all files in this new directory, many of the generated files are outside -the scope of this tutorial. - -In order for Django to find our new web app, we'll need to add it to the `INSTALLED_APPS` setting. -Evennia's default installed apps are already set, so in `server/conf/settings.py`, we'll just extend -them: - -```python -INSTALLED_APPS += ('web.character',) -``` - -> Note: That end comma is important. It makes sure that Python interprets the addition as a tuple -instead of a string. - -The first thing we need to do is to create a *view* and an *URL pattern* to point to it. A view is a -function that generates the web page that a visitor wants to see, while the URL pattern lets Django -know what URL should trigger the view. The pattern may also provide some information of its own as -we shall see. - -Here is our `character/urls.py` file (**Note**: you may have to create this file if a blank one -wasn't generated for you): - -```python -# URL patterns for the character app - -from django.conf.urls import url -from web.character.views import sheet - -urlpatterns = [ - url(r'^sheet/(?P\d+)/$', sheet, name="sheet") -] -``` - -This file contains all of the URL patterns for the application. The `url` function in the -`urlpatterns` list are given three arguments. The first argument is a pattern-string used to -identify which URLs are valid. Patterns are specified as *regular expressions*. Regular expressions -are used to match strings and are written in a special, very compact, syntax. A detailed description -of regular expressions is beyond this tutorial but you can learn more about them -[here](https://docs.python.org/2/howto/regex.html). For now, just accept that this regular -expression requires that the visitor's URL looks something like this: - -```` -sheet/123/ -```` - -That is, `sheet/` followed by a number, rather than some other possible URL pattern. We will -interpret this number as object ID. Thanks to how the regular expression is formulated, the pattern -recognizer stores the number in a variable called `object_id`. This will be passed to the view (see -below). We add the imported view function (`sheet`) in the second argument. We also add the `name` -keyword to identify the URL pattern itself. You should always name your URL patterns, this makes -them easy to refer to in html templates using the `{% url %}` tag (but we won't get more into that -in this tutorial). - -> Security Note: Normally, users do not have the ability to see object IDs within the game (it's -restricted to superusers only). Exposing the game's object IDs to the public like this enables -griefers to perform what is known as an [account enumeration -attack](http://www.sans.edu/research/security-laboratory/article/attacks-browsing) in the efforts of -hijacking your superuser account. Consider this: in every Evennia installation, there are two -objects that we can *always* expect to exist and have the same object IDs-- Limbo (#2) and the -superuser you create in the beginning (#1). Thus, the griefer can get 50% of the information they -need to hijack the admin account (the admin's username) just by navigating to `sheet/1`! - -Next we create `views.py`, the view file that `urls.py` refers to. - -```python -# Views for our character app - -from django.http import Http404 -from django.shortcuts import render -from django.conf import settings - -from evennia.utils.search import object_search -from evennia.utils.utils import inherits_from - -def sheet(request, object_id): - object_id = '#' + object_id - try: - character = object_search(object_id)[0] - except IndexError: - raise Http404("I couldn't find a character with that ID.") - if not inherits_from(character, settings.BASE_CHARACTER_TYPECLASS): - raise Http404("I couldn't find a character with that ID. " - "Found something else instead.") - return render(request, 'character/sheet.html', {'character': character}) -``` - -As explained earlier, the URL pattern parser in `urls.py` parses the URL and passes `object_id` to -our view function `sheet`. We do a database search for the object using this number. We also make -sure such an object exists and that it is actually a Character. The view function is also handed a -`request` object. This gives us information about the request, such as if a logged-in user viewed it -- we won't use that information here but it is good to keep in mind. - -On the last line, we call the `render` function. Apart from the `request` object, the `render` -function takes a path to an html template and a dictionary with extra data you want to pass into -said template. As extra data we pass the Character object we just found. In the template it will be -available as the variable "character". - -The html template is created as `templates/character/sheet.html` under your `character` app folder. -You may have to manually create both `template` and its subfolder `character`. Here's the template -to create: - -````html -{% extends "base.html" %} -{% block content %} - -

{{ character.name }}

- -

{{ character.db.desc }}

- -

Stats

- - - - - - - - - - - - - - - - - - - - - -
StatValue
Strength{{ character.db.str }}
Intelligence{{ character.db.int }}
Speed{{ character.db.spd }}
- -

Skills

-
    - {% for skill in character.db.skills %} -
  • {{ skill }}
  • - {% empty %} -
  • This character has no skills yet.
  • - {% endfor %} -
- - {% if character.db.approved %} -

This character has been approved!

- {% else %} -

This character has not yet been approved!

- {% endif %} -{% endblock %} -```` - -In Django templates, `{% ... %}` denotes special in-template "functions" that Django understands. -The `{{ ... }}` blocks work as "slots". They are replaced with whatever value the code inside the -block returns. - -The first line, `{% extends "base.html" %}`, tells Django that this template extends the base -template that Evennia is using. The base template is provided by the theme. Evennia comes with the -open-source third-party theme `prosimii`. You can find it and its `base.html` in -`evennia/web/templates/prosimii`. Like other templates, these can be overwritten. - -The next line is `{% block content %}`. The `base.html` file has `block`s, which are placeholders -that templates can extend. The main block, and the one we use, is named `content`. - -We can access the `character` variable anywhere in the template because we passed it in the `render` -call at the end of `view.py`. That means we also have access to the Character's `db` attributes, -much like you would in normal Python code. You don't have the ability to call functions with -arguments in the template-- in fact, if you need to do any complicated logic, you should do it in -`view.py` and pass the results as more variables to the template. But you still have a great deal of -flexibility in how you display the data. - -We can do a little bit of logic here as well. We use the `{% for %} ... {% endfor %}` and `{% if %} -... {% else %} ... {% endif %}` structures to change how the template renders depending on how many -skills the user has, or if the user is approved (assuming your game has an approval system). - -The last file we need to edit is the master URLs file. This is needed in order to smoothly integrate -the URLs from your new `character` app with the URLs from Evennia's existing pages. Find the file -`web/urls.py` and update its `patterns` list as follows: - -```python -# web/urls.py - -custom_patterns = [ - url(r'^character/', include('web.character.urls')) - ] -``` - -Now reload the server with `evennia reload` and visit the page in your browser. If you haven't -changed your defaults, you should be able to find the sheet for character `#1` at -`http://localhost:4001/character/sheet/1/` - -Try updating the stats in-game and refresh the page in your browser. The results should show -immediately. - -As an optional final step, you can also change your character typeclass to have a method called -'get_absolute_url'. -```python -# typeclasses/characters.py - - # inside Character - def get_absolute_url(self): - from django.urls import reverse - return reverse('character:sheet', kwargs={'object_id':self.id}) -``` -Doing so will give you a 'view on site' button in the top right of the Django Admin Objects -changepage that links to your new character sheet, and allow you to get the link to a character's -page by using {{ object.get_absolute_url }} in any template where you have a given object. - -*Now that you've made a basic page and app with Django, you may want to read the full Django -tutorial to get a better idea of what it can do. [You can find Django's tutorial -here](https://docs.djangoproject.com/en/1.8/intro/tutorial01/).* diff --git a/docs/0.9.5/_sources/Web-Features.md.txt b/docs/0.9.5/_sources/Web-Features.md.txt deleted file mode 100644 index aab41c597d..0000000000 --- a/docs/0.9.5/_sources/Web-Features.md.txt +++ /dev/null @@ -1,131 +0,0 @@ -# Web Features - - -Evennia is its own webserver and hosts a default website and browser webclient. - -## Web site - -The Evennia website is a Django application that ties in with the MUD database. Since the website -shares this database you could, for example, tell website visitors how many accounts are logged into -the game at the moment, how long the server has been up and any other database information you may -want. During development you can access the website by pointing your browser to -`http://localhost:4001`. - -> You may also want to set `DEBUG = True` in your settings file for debugging the website. You will -then see proper tracebacks in the browser rather than just error codes. Note however that this will -*leak memory a lot* (it stores everything all the time) and is *not to be used in production*. It's -recommended to only use `DEBUG` for active web development and to turn it off otherwise. - -A Django (and thus Evennia) website basically consists of three parts, a -[view](https://docs.djangoproject.com/en/1.9/topics/http/views/) an associated -[template](https://docs.djangoproject.com/en/1.9/topics/templates/) and an `urls.py` file. Think of -the view as the Python back-end and the template as the HTML files you are served, optionally filled -with data from the back-end. The urls file is a sort of mapping that tells Django that if a specific -URL is given in the browser, a particular view should be triggered. You are wise to review the -Django documentation for details on how to use these components. - -Evennia's default website is located in -[evennia/web/website](https://github.com/evennia/evennia/tree/master/evennia/web/website). In this -folder you'll find the simple default view as well as subfolders `templates` and `static`. Static -files are things like images, CSS files and Javascript. - -### Customizing the Website - -You customize your website from your game directory. In the folder `web` you'll find folders -`static`, `templates`, `static_overrides` and `templates_overrides`. The first two of those are -populated automatically by Django and used to serve the website. You should not edit anything in -them - the change will be lost. To customize the website you'll need to copy the file you want to -change from the `web/website/template/` or `web/website/static/` path to the corresponding place -under one of `_overrides` directories. - -Example: To override or modify `evennia/web/website/template/website/index.html` you need to -add/modify `mygame/web/template_overrides/website/index.html`. - -The detailed description on how to customize the website is best described in tutorial form. See the -[Web Tutorial](./Web-Tutorial.md) for more information. - -### Overloading Django views - -The Python backend for every HTML page is called a [Django -view](https://docs.djangoproject.com/en/1.9/topics/http/views/). A view can do all sorts of -functions, but the main one is to update variables data that the page can display, like how your -out-of-the-box website will display statistics about number of users and database objects. - -To re-point a given page to a `view.py` of your own, you need to modify `mygame/web/urls.py`. An -[URL pattern](https://docs.djangoproject.com/en/1.9/topics/http/urls/) is a [regular -expression](https://en.wikipedia.org/wiki/Regular_expression) that you need to enter in the address -field of your web browser to get to the page in question. If you put your own URL pattern *before* -the default ones, your own view will be used instead. The file `urls.py` even marks where you should -put your change. - -Here's an example: - -```python -# mygame/web/urls.py - -from django.conf.urls import url, include -# default patterns -from evennia.web.urls import urlpatterns - -# our own view to use as a replacement -from web.myviews import myview - -# custom patterns to add -patterns = [ - # overload the main page view - url(r'^', myview, name='mycustomview'), -] - -urlpatterns = patterns + urlpatterns - -``` - -Django will always look for a list named `urlpatterns` which consists of the results of `url()` -calls. It will use the *first* match it finds in this list. Above, we add a new URL redirect from -the root of the website. It will now our own function `myview` from a new module -`mygame/web/myviews.py`. - -> If our game is found on `http://mygame.com`, the regular expression `"^"` means we just entered -`mygame.com` in the address bar. If we had wanted to add a view for `http://mygame.com/awesome`, the -regular expression would have been `^/awesome`. - -Look at [evennia/web/website/views.py](https://github.com/evennia/evennia/blob/master/evennia/web/website/views.py#L82) to see the inputs and outputs you must have to define a view. Easiest may be to -copy the default file to `mygame/web` to have something to modify and expand on. - -Restart the server and reload the page in the browser - the website will now use your custom view. -If there are errors, consider turning on `settings.DEBUG` to see the full tracebacks - in debug mode -you will also log all requests in `mygame/server/logs/http_requests.log`. - -## Web client - - -Evennia comes with a MUD client accessible from a normal web browser. During -development you can try it at `http://localhost:4001/webclient`. -[See the Webclient page](./Webclient.md) for more details. - - -## The Django 'Admin' Page - -Django comes with a built-in [admin -website](https://docs.djangoproject.com/en/1.10/ref/contrib/admin/). This is accessible by clicking -the 'admin' button from your game website. The admin site allows you to see, edit and create objects -in your database from a graphical interface. - -The behavior of default Evennia models are controlled by files `admin.py` in the Evennia package. -New database models you choose to add yourself (such as in the Web Character View Tutorial) can/will -also have `admin.py` files. New models are registered to the admin website by a call of -`admin.site.register(model class, admin class)` inside an admin.py file. It is an error to attempt -to register a model that has already been registered. - -To overload Evennia's admin files you don't need to modify Evennia itself. To customize you can call -`admin.site.unregister(model class)`, then follow that with `admin.site.register` in one of your own -admin.py files in a new app that you add. - -## More reading - -Evennia relies on Django for its web features. For details on expanding your web experience, the -[Django documentation](https://docs.djangoproject.com/en) or the [Django -Book](http://www.djangobook.com/en/2.0/index.html) are the main resources to look into. In Django -lingo, the Evennia is a django "project" that consists of Django "applications". For the sake of web -implementation, the relevant django "applications" in default Evennia are `web/website` or -`web/webclient`. diff --git a/docs/0.9.5/_sources/Web-Tutorial.md.txt b/docs/0.9.5/_sources/Web-Tutorial.md.txt deleted file mode 100644 index ef4075d1ef..0000000000 --- a/docs/0.9.5/_sources/Web-Tutorial.md.txt +++ /dev/null @@ -1,127 +0,0 @@ -# Web Tutorial - - -Evennia uses the [Django](https://www.djangoproject.com/) web framework as the basis of both its -database configuration and the website it provides. While a full understanding of Django requires -reading the Django documentation, we have provided this tutorial to get you running with the basics -and how they pertain to Evennia. This text details getting everything set up. The [Web-based -Character view Tutorial](./Web-Character-View-Tutorial.md) gives a more explicit example of making a -custom web page connected to your game, and you may want to read that after finishing this guide. - -## A Basic Overview - -Django is a web framework. It gives you a set of development tools for building a website quickly -and easily. - -Django projects are split up into *apps* and these apps all contribute to one project. For instance, -you might have an app for conducting polls, or an app for showing news posts or, like us, one for -creating a web client. - -Each of these applications has a `urls.py` file, which specifies what -[URL](http://en.wikipedia.org/wiki/Uniform_resource_locator)s are used by the app, a `views.py` file -for the code that the URLs activate, a `templates` directory for displaying the results of that code -in [HTML](http://en.wikipedia.org/wiki/Html) for the user, and a `static` folder that holds assets -like [CSS](http://en.wikipedia.org/wiki/CSS), [Javascript](http://en.wikipedia.org/wiki/Javascript), -and Image files (You may note your mygame/web folder does not have a `static` or `template` folder. -This is intended and explained further below). Django applications may also have a `models.py` file -for storing information in the database. We "cmd: attr(locktest, 80, compare=gt)"will not change any -models here, take a look at the [New Models](./New-Models.md) page (as well as the [Django -docs](https://docs.djangoproject.com/en/1.7/topics/db/models/) on models) if you are interested. - -There is also a root `urls.py` that determines the URL structure for the entire project. A starter -`urls.py` is included in the default game template, and automatically imports all of Evennia's -default URLs for you. This is located in `web/urls.py`. - -## Changing the logo on the front page - -Evennia's default logo is a fun little googly-eyed snake wrapped around a gear globe. As cute as it -is, it probably doesn't represent your game. So one of the first things you may wish to do is -replace it with a logo of your own. - -Django web apps all have _static assets_: CSS files, Javascript files, and Image files. In order to -make sure the final project has all the static files it needs, the system collects the files from -every app's `static` folder and places it in the `STATIC_ROOT` defined in `settings.py`. By default, -the Evennia `STATIC_ROOT` is in `web/static`. - -Because Django pulls files from all of those separate places and puts them in one folder, it's -possible for one file to overwrite another. We will use this to plug in our own files without having -to change anything in the Evennia itself. - -By default, Evennia is configured to pull files you put in the `web/static_overrides` *after* all -other static files. That means that files in `static_overrides` folder will overwrite any previously -loaded files *having the same path under its static folder*. This last part is important to repeat: -To overload the static resource from a standard `static` folder you need to replicate the path of -folders and file names from that `static` folder in exactly the same way inside `static_overrides`. - -Let's see how this works for our logo. The default web application is in the Evennia library itself, -in `evennia/web/`. We can see that there is a `static` folder here. If we browse down, we'll -eventually find the full path to the Evennia logo file: -`evennia/web/website/static/website/images/evennia_logo.png`. - -Inside our `static_overrides` we must replicate the part of the path inside the website's `static` -folder, in other words, we must replicate `website/images/evennia_logo.png`. - -So, to change the logo, we need to create the folder path `website/images/` in `static_overrides`. -You may already have this folder structure prepared for you. We then rename our own logo file to -`evennia_logo.png` and copy it there. The final path for this file would thus be: -`mygame/web/static_overrides/website/images/evennia_logo.png`. - -To get this file pulled in, just change to your own game directory and reload the server: - -``` -evennia reload -``` - -This will reload the configuration and bring in the new static file(s). If you didn't want to reload -the server you could instead use - -``` -evennia collectstatic -``` - -to only update the static files without any other changes. - -> **Note**: Evennia will collect static files automatically during startup. So if `evennia -collectstatic` reports finding 0 files to collect, make sure you didn't start the engine at some -point - if so the collector has already done its work! To make sure, connect to the website and -check so the logo has actually changed to your own version. - -> **Note**: Sometimes the static asset collector can get confused. If no matter what you do, your -overridden files aren't getting copied over the defaults, try removing the target file (or -everything) in the `web/static` directory, and re-running `collectstatic` to gather everything from -scratch. - -## Changing the Front Page's Text - -The default front page for Evennia contains information about the Evennia project. You'll probably -want to replace this information with information about your own project. Changing the page template -is done in a similar way to changing static resources. - -Like static files, Django looks through a series of template folders to find the file it wants. The -difference is that Django does not copy all of the template files into one place, it just searches -through the template folders until it finds a template that matches what it's looking for. This -means that when you edit a template, the changes are instant. You don't have to reload the server or -run any extra commands to see these changes - reloading the web page in your browser is enough. - -To replace the index page's text, we'll need to find the template for it. We'll go into more detail -about how to determine which template is used for rendering a page in the [Web-based Character view -Tutorial](./Web-Character-View-Tutorial.md). For now, you should know that the template we want to change -is stored in `evennia/web/website/templates/website/index.html`. - -To replace this template file, you will put your changed template inside the -`web/template_overrides/website` directory in your game folder. In the same way as with static -resources you must replicate the path inside the default `template` directory exactly. So we must -copy our replacement template named `index.html` there (or create the `website` directory in -web/template_overrides` if it does not exist, first). The final path to the file should thus be: -`web/template_overrides/website/index.html` within your game directory. - -Note that it is usually easier to just copy the original template over and edit it in place. The -original file already has all the markup and tags, ready for editing. - -## Further reading - -For further hints on working with the web presence, you could now continue to the [Web-based -Character view Tutorial](./Web-Character-View-Tutorial.md) where you learn to make a web page that -displays in-game character stats. You can also look at [Django's own -tutorial](https://docs.djangoproject.com/en/3.1/) to get more insight in how Django works and what -possibilities exist. diff --git a/docs/0.9.5/_sources/Webclient-brainstorm.md.txt b/docs/0.9.5/_sources/Webclient-brainstorm.md.txt deleted file mode 100644 index 941338a73d..0000000000 --- a/docs/0.9.5/_sources/Webclient-brainstorm.md.txt +++ /dev/null @@ -1,354 +0,0 @@ -# Webclient brainstorm - -# Ideas for a future webclient gui - -*This is a brainstorming whitepage. Add your own comments in a named section below.* - -## From Chat on 2019/09/02 -``` - Griatch (IRC)APP @friarzen: Could one (with goldenlayout) programmatically provide pane positions -and sizes? -I recall it was not trivial for the old split.js solution. - friarzen take a look at the goldenlayout_default_config.js -It is kinda cryptic but that is the layout json. - Griatch (IRC)APP @friarzen: Hm, so dynamically replacing the goldenlayout_config in the global -scope at the right - thing would do it? - friarzen yep - friarzen the biggest pain in the butt is that goldenlayout_config needs to be set before the -goldenlayout init() -is called, which isn't trivial with the current structure, but it isn't impossible. - Griatch (IRC)APP One could in principle re-run init() at a later date though, right? - friarzen Hmm...not sure I've ever tried it... seems doable off the top of my head... -right now, that whole file exists to be overridden on page load as a separate -``` -Remember, plugins are load-order dependent, so make sure the new ` - - - - - - - - - - -
- -
-
-
-
- -
-

evennia

-
- -
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.accounts.accounts.html b/docs/0.9.5/api/evennia.accounts.accounts.html deleted file mode 100644 index a67d58f5c7..0000000000 --- a/docs/0.9.5/api/evennia.accounts.accounts.html +++ /dev/null @@ -1,1101 +0,0 @@ - - - - - - - - - evennia.accounts.accounts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.accounts.accounts

-

Typeclass for Account objects

-

Note that this object is primarily intended to -store OOC information, not game info! This -object represents the actual user (not their -character) and has NO actual presence in the -game world (this is handled by the associated -character object, so you should customize that -instead for most things).

-
-
-class evennia.accounts.accounts.DefaultAccount(*args, **kwargs)[source]
-

Bases: evennia.accounts.models.AccountDB

-

This is the base Typeclass for all Accounts. Accounts represent -the person playing the game and tracks account info, password -etc. They are OOC entities without presence in-game. An Account -can connect to a Character Object in order to “enter” the -game.

-

Account Typeclass API:

-
    -
  • Available properties (only available on initiated typeclass objects)

  • -
-
-
    -
  • key (string) - name of account

  • -
  • name (string)- wrapper for user.username

  • -
  • -
    aliases (list of strings) - aliases to the object. Will be saved to

    database as AliasDB entries but returned as strings.

    -
    -
    -
  • -
  • dbref (int, read-only) - unique #id-number. Also “id” can be used.

  • -
  • date_created (string) - time stamp of object creation

  • -
  • permissions (list of strings) - list of permission strings

  • -
  • user (User, read-only) - django User authorization object

  • -
  • -
    obj (Object) - game object controlled by account. ‘character’ can also

    be used.

    -
    -
    -
  • -
  • sessions (list of Sessions) - sessions connected to this account

  • -
  • is_superuser (bool, read-only) - if the connected user is a superuser

  • -
-
-
    -
  • Handlers

  • -
-
-
    -
  • locks - lock-handler: use locks.add() to add new lock strings

  • -
  • -
    db - attribute-handler: store/retrieve database attributes on this

    self.db.myattr=val, val=self.db.myattr

    -
    -
    -
  • -
  • -
    ndb - non-persistent attribute handler: same as db but does not

    create a database entry when storing data

    -
    -
    -
  • -
  • scripts - script-handler. Add new scripts to object with scripts.add()

  • -
  • cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object

  • -
  • nicks - nick-handler. New nicks with nicks.add().

  • -
-
-
    -
  • Helper methods

  • -
-
-
    -
  • msg(text=None, from_obj=None, session=None, options=None, **kwargs)

  • -
  • execute_cmd(raw_string)

  • -
  • -
    search(ostring, global_search=False, attribute_name=None,

    use_nicks=False, location=None, -ignore_errors=False, account=False)

    -
    -
    -
  • -
  • is_typeclass(typeclass, exact=False)

  • -
  • swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)

  • -
  • access(accessing_obj, access_type=’read’, default=False, no_superuser_bypass=False)

  • -
  • check_permstring(permstring)

  • -
-
-
    -
  • Hook methods

  • -
-
-

basetype_setup() -at_account_creation()

-
-
> note that the following hooks are also found on Objects and are

usually handled on the character level:

-
-
-
    -
  • at_init()

  • -
  • at_access()

  • -
  • at_cmdset_get(**kwargs)

  • -
  • at_first_login()

  • -
  • at_post_login(session=None)

  • -
  • at_disconnect()

  • -
  • at_message_receive()

  • -
  • at_message_send()

  • -
  • at_server_reload()

  • -
  • at_server_shutdown()

  • -
-
-
-
-objects = <evennia.accounts.manager.AccountManager object>
-
- -
-
-cmdset[source]
-
- -
-
-scripts[source]
-
- -
-
-nicks[source]
-
- -
-
-sessions[source]
-
- -
-
-options[source]
-
- -
-
-property characters
-
- -
-
-disconnect_session_from_account(session, reason=None)[source]
-

Access method for disconnecting a given session from the -account (connection happens automatically in the -sessionhandler)

-
-
Parameters
-
    -
  • session (Session) – Session to disconnect.

  • -
  • reason (str, optional) – Eventual reason for the disconnect.

  • -
-
-
-
- -
-
-puppet_object(session, obj)[source]
-

Use the given session to control (puppet) the given object (usually -a Character type).

-
-
Parameters
-
    -
  • session (Session) – session to use for puppeting

  • -
  • obj (Object) – the object to start puppeting

  • -
-
-
Raises
-

RuntimeError – If puppeting is not possible, the -exception.msg will contain the reason.

-
-
-
- -
-
-unpuppet_object(session)[source]
-

Disengage control over an object.

-
-
Parameters
-

session (Session or list) – The session or a list of -sessions to disengage from their puppets.

-
-
Raises
-

RuntimeError With message about error.

-
-
-
- -
-
-unpuppet_all()[source]
-

Disconnect all puppets. This is called by server before a -reset/shutdown.

-
- -
-
-get_puppet(session)[source]
-

Get an object puppeted by this session through this account. This is -the main method for retrieving the puppeted object from the -account’s end.

-
-
Parameters
-

session (Session) – Find puppeted object based on this session

-
-
Returns
-

puppet (Object) – The matching puppeted object, if any.

-
-
-
- -
-
-get_all_puppets()[source]
-

Get all currently puppeted objects.

-
-
Returns
-

puppets (list)

-
-
All puppeted objects currently controlled

by this Account.

-
-
-

-
-
-
- -
-
-property character
-

This is a legacy convenience link for use with MULTISESSION_MODE.

-
-
Returns
-

puppets (Object or list)

-
-
Users of MULTISESSION_MODE 0 or 1 will

always get the first puppet back. Users of higher **MULTISESSION_MODE**s will -get a list of all puppeted objects.

-
-
-

-
-
-
- -
-
-property puppet
-

This is a legacy convenience link for use with MULTISESSION_MODE.

-
-
Returns
-

puppets (Object or list)

-
-
Users of MULTISESSION_MODE 0 or 1 will

always get the first puppet back. Users of higher **MULTISESSION_MODE**s will -get a list of all puppeted objects.

-
-
-

-
-
-
- -
-
-classmethod is_banned(**kwargs)[source]
-

Checks if a given username or IP is banned.

-
-
Keyword Arguments
-
    -
  • ip (str, optional) – IP address.

  • -
  • username (str, optional) – Username.

  • -
-
-
Returns
-

is_banned (bool) – Whether either is banned or not.

-
-
-
- -
-
-classmethod get_username_validators(validator_config=[{'NAME': 'django.contrib.auth.validators.ASCIIUsernameValidator'}, {'NAME': 'django.core.validators.MinLengthValidator', 'OPTIONS': {'limit_value': 3}}, {'NAME': 'django.core.validators.MaxLengthValidator', 'OPTIONS': {'limit_value': 30}}, {'NAME': 'evennia.server.validators.EvenniaUsernameAvailabilityValidator'}])[source]
-

Retrieves and instantiates validators for usernames.

-
-
Parameters
-

validator_config (list) – List of dicts comprising the battery of -validators to apply to a username.

-
-
Returns
-

validators (list) – List of instantiated Validator objects.

-
-
-
- -
-
-classmethod authenticate(username, password, ip='', **kwargs)[source]
-

Checks the given username/password against the database to see if the -credentials are valid.

-

Note that this simply checks credentials and returns a valid reference -to the user– it does not log them in!

-

To finish the job: -After calling this from a Command, associate the account with a Session: -- session.sessionhandler.login(session, account)

-

…or after calling this from a View, associate it with an HttpRequest: -- django.contrib.auth.login(account, request)

-
-
Parameters
-
    -
  • username (str) – Username of account

  • -
  • password (str) – Password of account

  • -
  • ip (str, optional) – IP address of client

  • -
-
-
Keyword Arguments
-

session (Session, optional) – Session requesting authentication

-
-
Returns
-

account (DefaultAccount, None)

-
-
Account whose credentials were

provided if not banned.

-
-
-

errors (list): Error messages of any failures.

-

-
-
-
- -
-
-classmethod normalize_username(username)[source]
-

Django: Applies NFKC Unicode normalization to usernames so that visually -identical characters with different Unicode code points are considered -identical.

-

(This deals with the Turkish “i” problem and similar -annoyances. Only relevant if you go out of your way to allow Unicode -usernames though– Evennia accepts ASCII by default.)

-

In this case we’re simply piggybacking on this feature to apply -additional normalization per Evennia’s standards.

-
- -
-
-classmethod validate_username(username)[source]
-

Checks the given username against the username validator associated with -Account objects, and also checks the database to make sure it is unique.

-
-
Parameters
-

username (str) – Username to validate

-
-
Returns
-

valid (bool) – Whether or not the password passed validation -errors (list): Error messages of any failures

-
-
-
- -
-
-classmethod validate_password(password, account=None)[source]
-

Checks the given password against the list of Django validators enabled -in the server.conf file.

-
-
Parameters
-

password (str) – Password to validate

-
-
Keyword Arguments
-

account (DefaultAccount, optional) – Account object to validate the -password for. Optional, but Django includes some validators to -do things like making sure users aren’t setting passwords to the -same value as their username. If left blank, these user-specific -checks are skipped.

-
-
Returns
-

valid (bool) – Whether or not the password passed validation -error (ValidationError, None): Any validation error(s) raised. Multiple

-
-

errors can be nested within a single object.

-
-

-
-
-
- -
-
-set_password(password, **kwargs)[source]
-

Applies the given password to the account. Logs and triggers the at_password_change hook.

-
-
Parameters
-

password (str) – Password to set.

-
-
-

Notes

-

This is called by Django also when logging in; it should not be mixed up with validation, since that -would mean old passwords in the database (pre validation checks) could get invalidated.

-
- -
-
-create_character(*args, **kwargs)[source]
-

Create a character linked to this account.

-
-
Parameters
-
    -
  • key (str, optional) – If not given, use the same name as the account.

  • -
  • typeclass (str, optional) – Typeclass to use for this character. If -not given, use settings.BASE_CHARACTER_TYPECLASS.

  • -
  • permissions (list, optional) – If not given, use the account’s permissions.

  • -
  • ip (str, optiona) – The client IP creating this character. Will fall back to the -one stored for the account if not given.

  • -
  • kwargs (any) – Other kwargs will be used in the create_call.

  • -
-
-
Returns
-

Object – A new character of the character_typeclass type. None on an error. -list or None: A list of errors, or None.

-
-
-
- -
-
-classmethod create(*args, **kwargs)[source]
-

Creates an Account (or Account/Character pair for MULTISESSION_MODE<2) -with default (or overridden) permissions and having joined them to the -appropriate default channels.

-
-
Keyword Arguments
-
    -
  • username (str) – Username of Account owner

  • -
  • password (str) – Password of Account owner

  • -
  • email (str, optional) – Email address of Account owner

  • -
  • ip (str, optional) – IP address of requesting connection

  • -
  • guest (bool, optional) – Whether or not this is to be a Guest account

  • -
  • permissions (str, optional) – Default permissions for the Account

  • -
  • typeclass (str, optional) – Typeclass to use for new Account

  • -
  • character_typeclass (str, optional) – Typeclass to use for new char -when applicable.

  • -
-
-
Returns
-

account (Account) – Account if successfully created; None if not -errors (list): List of error messages in string form

-
-
-
- -
-
-delete(*args, **kwargs)[source]
-

Deletes the account permanently.

-

Notes

-
-
*args and **kwargs are passed on to the base delete

mechanism (these are usually not used).

-
-
-
- -
-
-msg(text=None, from_obj=None, session=None, options=None, **kwargs)[source]
-

Evennia -> User -This is the main route for sending data back to the user from the -server.

-
-
Parameters
-
    -
  • text (str or tuple, optional) – The message to send. This -is treated internally like any send-command, so its -value can be a tuple if sending multiple arguments to -the text oob command.

  • -
  • from_obj (Object or Account or list, optional) – Object sending. If given, its -at_msg_send() hook will be called. If iterable, call on all entities.

  • -
  • session (Session or list, optional) – Session object or a list of -Sessions to receive this send. If given, overrules the -default send behavior for the current -MULTISESSION_MODE.

  • -
  • options (list) – Protocol-specific options. Passed on to the protocol.

  • -
-
-
Keyword Arguments
-

any (dict) – All other keywords are passed on to the protocol.

-
-
-
- -
-
-execute_cmd(raw_string, session=None, **kwargs)[source]
-

Do something as this account. This method is never called normally, -but only when the account object itself is supposed to execute the -command. It takes account nicks into account, but not nicks of -eventual puppets.

-
-
Parameters
-
    -
  • raw_string (str) – Raw command input coming from the command line.

  • -
  • session (Session, optional) – The session to be responsible -for the command-send

  • -
-
-
Keyword Arguments
-

kwargs (any) – Other keyword arguments will be added to the -found command object instance as variables before it -executes. This is unused by default Evennia but may be -used to set flags and change operating paramaters for -commands at run-time.

-
-
-
- -
-
-search(searchdata, return_puppet=False, search_object=False, typeclass=None, nofound_string=None, multimatch_string=None, use_nicks=True, quiet=False, **kwargs)[source]
-

This is similar to DefaultObject.search but defaults to searching -for Accounts only.

-
-
Parameters
-
    -
  • searchdata (str or int) – Search criterion, the Account’s -key or dbref to search for.

  • -
  • return_puppet (bool, optional) – Instructs the method to -return matches as the object the Account controls rather -than the Account itself (or None) if nothing is puppeted).

  • -
  • search_object (bool, optional) – Search for Objects instead of -Accounts. This is used by e.g. the @examine command when -wanting to examine Objects while OOC.

  • -
  • typeclass (Account typeclass, optional) – Limit the search -only to this particular typeclass. This can be used to -limit to specific account typeclasses or to limit the search -to a particular Object typeclass if search_object is True.

  • -
  • nofound_string (str, optional) – A one-time error message -to echo if searchdata leads to no matches. If not given, -will fall back to the default handler.

  • -
  • multimatch_string (str, optional) – A one-time error -message to echo if searchdata leads to multiple matches. -If not given, will fall back to the default handler.

  • -
  • use_nicks (bool, optional) – Use account-level nick replacement.

  • -
  • quiet (bool, optional) – If set, will not show any error to the user, -and will also lead to returning a list of matches.

  • -
-
-
Returns
-

match (Account, Object or None) – A single Account or Object match. -list: If quiet=True this is a list of 0, 1 or more Account or Object matches.

-
-
-

Notes

-

Extra keywords are ignored, but are allowed in call in -order to make API more consistent with -objects.objects.DefaultObject.search.

-
- -
-
-access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False, **kwargs)[source]
-

Determines if another object has permission to access this -object in whatever way.

-
-
Parameters
-
    -
  • accessing_obj (Object) – Object trying to access this one.

  • -
  • access_type (str, optional) – Type of access sought.

  • -
  • default (bool, optional) – What to return if no lock of -access_type was found

  • -
  • no_superuser_bypass (bool, optional) – Turn off superuser -lock bypassing. Be careful with this one.

  • -
-
-
Keyword Arguments
-

kwargs (any) – Passed to the at_access hook along with the result.

-
-
Returns
-

result (bool) – Result of access check.

-
-
-
- -
-
-property idle_time
-

Returns the idle time of the least idle session in seconds. If -no sessions are connected it returns nothing.

-
- -
-
-property connection_time
-

Returns the maximum connection time of all connected sessions -in seconds. Returns nothing if there are no sessions.

-
- -
-
-basetype_setup()[source]
-

This sets up the basic properties for an account. Overload this -with at_account_creation rather than changing this method.

-
- -
-
-at_account_creation()[source]
-

This is called once, the very first time the account is created -(i.e. first time they register with the game). It’s a good -place to store attributes all accounts should have, like -configuration values etc.

-
- -
-
-at_init()[source]
-

This is always called whenever this object is initiated – -that is, whenever it its typeclass is cached from memory. This -happens on-demand first time the object is used or activated -in some way after being created but also after each server -restart or reload. In the case of account objects, this usually -happens the moment the account logs in or reconnects after a -reload.

-
- -
-
-at_first_save()[source]
-

This is a generic hook called by Evennia when this object is -saved to the database the very first time. You generally -don’t override this method but the hooks called by it.

-
- -
-
-at_access(result, accessing_obj, access_type, **kwargs)[source]
-
-
This is triggered after an access-call on this Account has

completed.

-
-
-
-
Parameters
-
    -
  • result (bool) – The result of the access check.

  • -
  • accessing_obj (any) – The object requesting the access -check.

  • -
  • access_type (str) – The type of access checked.

  • -
-
-
Keyword Arguments
-

kwargs (any) – These are passed on from the access check -and can be used to relay custom instructions from the -check mechanism.

-
-
-

Notes

-

This method cannot affect the result of the lock check and -its return value is not used in any way. It can be used -e.g. to customize error messages in a central location or -create other effects based on the access result.

-
- -
-
-at_cmdset_get(**kwargs)[source]
-

Called just before cmdsets on this account are requested by -the command handler. The cmdsets are available as -self.cmdset. If changes need to be done on the fly to the -cmdset before passing them on to the cmdhandler, this is the -place to do it. This is called also if the account currently -have no cmdsets. kwargs are usually not used unless the -cmdset is generated dynamically.

-
- -
-
-at_first_login(**kwargs)[source]
-

Called the very first time this account logs into the game. -Note that this is called before at_pre_login, so no session -is established and usually no character is yet assigned at -this point. This hook is intended for account-specific setup -like configurations.

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
- -
-
-at_password_change(**kwargs)[source]
-

Called after a successful password set/modify.

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
- -
-
-at_pre_login(**kwargs)[source]
-

Called every time the user logs in, just before the actual -login-state is set.

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
- -
-
-at_post_login(session=None, **kwargs)[source]
-

Called at the end of the login process, just before letting -the account loose.

-
-
Parameters
-
    -
  • session (Session, optional) – Session logging in, if any.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

This is called before an eventual Character’s -at_post_login hook. By default it is used to set up -auto-puppeting based on MULTISESSION_MODE.

-
- -
-
-at_failed_login(session, **kwargs)[source]
-

Called by the login process if a user account is targeted correctly -but provided with an invalid password. By default it does nothing, -but exists to be overriden.

-
-
Parameters
-
    -
  • session (session) – Session logging in.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_disconnect(reason=None, **kwargs)[source]
-

Called just before user is disconnected.

-
-
Parameters
-
    -
  • reason (str, optional) – The reason given for the disconnect, -(echoed to the connection channel by default).

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_post_disconnect(**kwargs)[source]
-

This is called after disconnection is complete. No messages -can be relayed to the account from here. After this call, the -account should not be accessed any more, making this a good -spot for deleting it (in the case of a guest account account, -for example).

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
- -
-
-at_msg_receive(text=None, from_obj=None, **kwargs)[source]
-

This hook is called whenever someone sends a message to this -object using the msg method.

-

Note that from_obj may be None if the sender did not include -itself as an argument to the obj.msg() call - so you have to -check for this. .

-

Consider this a pre-processing method before msg is passed on -to the user session. If this method returns False, the msg -will not be passed on.

-
-
Parameters
-
    -
  • text (str, optional) – The message received.

  • -
  • from_obj (any, optional) – The object sending the message.

  • -
-
-
Keyword Arguments
-

includes any keywords sent to the msg method. (This) –

-
-
Returns
-

receive (bool) – If this message should be received.

-
-
-

Notes

-

If this method returns False, the msg operation -will abort without sending the message.

-
- -
-
-at_msg_send(text=None, to_obj=None, **kwargs)[source]
-

This is a hook that is called when this object sends a -message to another object with obj.msg(text, to_obj=obj).

-
-
Parameters
-
    -
  • text (str, optional) – Text to send.

  • -
  • to_obj (any, optional) – The object to send to.

  • -
-
-
Keyword Arguments
-

passed from msg() (Keywords) –

-
-
-

Notes

-

Since this method is executed by from_obj, if no from_obj -was passed to DefaultCharacter.msg this hook will never -get called.

-
- -
-
-at_server_reload()[source]
-

This hook is called whenever the server is shutting down for -restart/reboot. If you want to, for example, save -non-persistent properties across a restart, this is the place -to do it.

-
- -
-
-at_server_shutdown()[source]
-

This hook is called whenever the server is shutting down fully -(i.e. not for a restart).

-
- -
-
-at_look(target=None, session=None, **kwargs)[source]
-

Called when this object executes a look. It allows to customize -just what this means.

-
-
Parameters
-
    -
  • target (Object or list, optional) – An object or a list -objects to inspect.

  • -
  • session (Session, optional) – The session doing this look.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

look_string (str)

-
-
A prepared look string, ready to send

off to any recipient (usually to ourselves)

-
-
-

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.accounts.models.AccountDB.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.accounts.models.AccountDB.MultipleObjectsReturned

-
- -
-
-path = 'evennia.accounts.accounts.DefaultAccount'
-
- -
-
-typename = 'DefaultAccount'
-
- -
- -
-
-class evennia.accounts.accounts.DefaultGuest(*args, **kwargs)[source]
-

Bases: evennia.accounts.accounts.DefaultAccount

-

This class is used for guest logins. Unlike Accounts, Guests and -their characters are deleted after disconnection.

-
-
-classmethod create(**kwargs)[source]
-

Forwards request to cls.authenticate(); returns a DefaultGuest object -if one is available for use.

-
- -
-
-classmethod authenticate(**kwargs)[source]
-

Gets or creates a Guest account object.

-
-
Keyword Arguments
-

ip (str, optional) – IP address of requestor; used for ban checking, -throttling and logging

-
-
Returns
-

account (Object) – Guest account object, if available -errors (list): List of error messages accrued during this request.

-
-
-
- -
-
-at_post_login(session=None, **kwargs)[source]
-

In theory, guests only have one character regardless of which -MULTISESSION_MODE we’re in. They don’t get a choice.

-
-
Parameters
-
    -
  • session (Session, optional) – Session connecting.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_server_shutdown()[source]
-

We repeat the functionality of at_disconnect() here just to -be on the safe side.

-
- -
-
-at_post_disconnect(**kwargs)[source]
-

Once having disconnected, destroy the guest’s characters and

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.accounts.accounts.DefaultAccount.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.accounts.accounts.DefaultAccount.MultipleObjectsReturned

-
- -
-
-path = 'evennia.accounts.accounts.DefaultGuest'
-
- -
-
-typename = 'DefaultGuest'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.accounts.admin.html b/docs/0.9.5/api/evennia.accounts.admin.html deleted file mode 100644 index 534b341b49..0000000000 --- a/docs/0.9.5/api/evennia.accounts.admin.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - - - - - - evennia.accounts.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.accounts.admin

-
-
-class evennia.accounts.admin.AccountDBChangeForm(*args, **kwargs)[source]
-

Bases: django.contrib.auth.forms.UserChangeForm

-

Modify the accountdb class.

-
-
-class Meta[source]
-

Bases: object

-
-
-model
-

alias of evennia.accounts.models.AccountDB

-
- -
-
-fields = '__all__'
-
- -
- -
-
-clean_username()[source]
-

Clean the username and check its existence.

-
- -
-
-base_fields = {'date_joined': <django.forms.fields.DateTimeField object>, 'db_attributes': <django.forms.models.ModelMultipleChoiceField object>, 'db_cmdset_storage': <django.forms.fields.CharField object>, 'db_is_bot': <django.forms.fields.BooleanField object>, 'db_is_connected': <django.forms.fields.BooleanField object>, 'db_key': <django.forms.fields.CharField object>, 'db_lock_storage': <django.forms.fields.CharField object>, 'db_tags': <django.forms.models.ModelMultipleChoiceField object>, 'db_typeclass_path': <django.forms.fields.CharField object>, 'email': <django.forms.fields.EmailField object>, 'first_name': <django.forms.fields.CharField object>, 'groups': <django.forms.models.ModelMultipleChoiceField object>, 'is_active': <django.forms.fields.BooleanField object>, 'is_staff': <django.forms.fields.BooleanField object>, 'is_superuser': <django.forms.fields.BooleanField object>, 'last_login': <django.forms.fields.DateTimeField object>, 'last_name': <django.forms.fields.CharField object>, 'password': <django.contrib.auth.forms.ReadOnlyPasswordHashField object>, 'user_permissions': <django.forms.models.ModelMultipleChoiceField object>, 'username': <django.forms.fields.RegexField object>}
-
- -
-
-declared_fields = {'password': <django.contrib.auth.forms.ReadOnlyPasswordHashField object>, 'username': <django.forms.fields.RegexField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.accounts.admin.AccountDBCreationForm(*args, **kwargs)[source]
-

Bases: django.contrib.auth.forms.UserCreationForm

-

Create a new AccountDB instance.

-
-
-class Meta[source]
-

Bases: object

-
-
-model
-

alias of evennia.accounts.models.AccountDB

-
- -
-
-fields = '__all__'
-
- -
- -
-
-clean_username()[source]
-

Cleanup username.

-
- -
-
-base_fields = {'date_joined': <django.forms.fields.DateTimeField object>, 'db_attributes': <django.forms.models.ModelMultipleChoiceField object>, 'db_cmdset_storage': <django.forms.fields.CharField object>, 'db_is_bot': <django.forms.fields.BooleanField object>, 'db_is_connected': <django.forms.fields.BooleanField object>, 'db_key': <django.forms.fields.CharField object>, 'db_lock_storage': <django.forms.fields.CharField object>, 'db_tags': <django.forms.models.ModelMultipleChoiceField object>, 'db_typeclass_path': <django.forms.fields.CharField object>, 'email': <django.forms.fields.EmailField object>, 'first_name': <django.forms.fields.CharField object>, 'groups': <django.forms.models.ModelMultipleChoiceField object>, 'is_active': <django.forms.fields.BooleanField object>, 'is_staff': <django.forms.fields.BooleanField object>, 'is_superuser': <django.forms.fields.BooleanField object>, 'last_login': <django.forms.fields.DateTimeField object>, 'last_name': <django.forms.fields.CharField object>, 'password': <django.forms.fields.CharField object>, 'password1': <django.forms.fields.CharField object>, 'password2': <django.forms.fields.CharField object>, 'user_permissions': <django.forms.models.ModelMultipleChoiceField object>, 'username': <django.forms.fields.RegexField object>}
-
- -
-
-declared_fields = {'password1': <django.forms.fields.CharField object>, 'password2': <django.forms.fields.CharField object>, 'username': <django.forms.fields.RegexField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.accounts.admin.AccountForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]
-

Bases: django.forms.models.ModelForm

-

Defines how to display Accounts

-
-
-class Meta[source]
-

Bases: object

-
-
-model
-

alias of evennia.accounts.models.AccountDB

-
- -
-
-fields = '__all__'
-
- -
-
-app_label = 'accounts'
-
- -
- -
-
-base_fields = {'date_joined': <django.forms.fields.DateTimeField object>, 'db_attributes': <django.forms.models.ModelMultipleChoiceField object>, 'db_cmdset_storage': <django.forms.fields.CharField object>, 'db_is_bot': <django.forms.fields.BooleanField object>, 'db_is_connected': <django.forms.fields.BooleanField object>, 'db_key': <django.forms.fields.RegexField object>, 'db_lock_storage': <django.forms.fields.CharField object>, 'db_permissions': <django.forms.fields.CharField object>, 'db_tags': <django.forms.models.ModelMultipleChoiceField object>, 'db_typeclass_path': <django.forms.fields.CharField object>, 'email': <django.forms.fields.EmailField object>, 'first_name': <django.forms.fields.CharField object>, 'groups': <django.forms.models.ModelMultipleChoiceField object>, 'is_active': <django.forms.fields.BooleanField object>, 'is_staff': <django.forms.fields.BooleanField object>, 'is_superuser': <django.forms.fields.BooleanField object>, 'last_login': <django.forms.fields.DateTimeField object>, 'last_name': <django.forms.fields.CharField object>, 'password': <django.forms.fields.CharField object>, 'user_permissions': <django.forms.models.ModelMultipleChoiceField object>, 'username': <django.forms.fields.CharField object>}
-
- -
-
-declared_fields = {'db_cmdset_storage': <django.forms.fields.CharField object>, 'db_key': <django.forms.fields.RegexField object>, 'db_lock_storage': <django.forms.fields.CharField object>, 'db_permissions': <django.forms.fields.CharField object>, 'db_typeclass_path': <django.forms.fields.CharField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.accounts.admin.AccountInline(parent_model, admin_site)[source]
-

Bases: django.contrib.admin.options.StackedInline

-

Inline creation of Account

-
-
-model
-

alias of evennia.accounts.models.AccountDB

-
- -
-
-template = 'admin/accounts/stacked.html'
-
- -
-
-form
-

alias of AccountForm

-
- -
-
-fieldsets = (('In-game Permissions and Locks', {'fields': ('db_lock_storage',), 'description': '<i>These are permissions/locks for in-game use. They are unrelated to website access rights.</i>'}), ('In-game Account data', {'fields': ('db_typeclass_path', 'db_cmdset_storage'), 'description': '<i>These fields define in-game-specific properties for the Account object in-game.</i>'}))
-
- -
-
-extra = 1
-
- -
-
-max_num = 1
-
- -
-
-property media
-
- -
- -
-
-class evennia.accounts.admin.AccountTagInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.TagInline

-

Inline Account Tags.

-
-
-model
-

alias of evennia.accounts.models.AccountDB_db_tags

-
- -
-
-related_field = 'accountdb'
-
- -
-
-property media
-
- -
- -
-
-class evennia.accounts.admin.AccountAttributeInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.AttributeInline

-

Inline Account Attributes.

-
-
-model
-

alias of evennia.accounts.models.AccountDB_db_attributes

-
- -
-
-related_field = 'accountdb'
-
- -
-
-property media
-
- -
- -
-
-class evennia.accounts.admin.AccountDBAdmin(model, admin_site)[source]
-

Bases: django.contrib.auth.admin.UserAdmin

-

This is the main creation screen for Users/accounts

-
-
-list_display = ('username', 'email', 'is_staff', 'is_superuser')
-
- -
-
-form
-

alias of AccountDBChangeForm

-
- -
-
-add_form
-

alias of AccountDBCreationForm

-
- -
-
-inlines = [<class 'evennia.accounts.admin.AccountTagInline'>, <class 'evennia.accounts.admin.AccountAttributeInline'>]
-
- -
-
-fieldsets = ((None, {'fields': ('username', 'password', 'email')}), ('Website profile', {'fields': ('first_name', 'last_name'), 'description': '<i>These are not used in the default system.</i>'}), ('Website dates', {'fields': ('last_login', 'date_joined'), 'description': '<i>Relevant only to the website.</i>'}), ('Website Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions', 'groups'), 'description': '<i>These are permissions/permission groups for accessing the admin site. They are unrelated to in-game access rights.</i>'}), ('Game Options', {'fields': ('db_typeclass_path', 'db_cmdset_storage', 'db_lock_storage'), 'description': '<i>These are attributes that are more relevant to gameplay.</i>'}))
-
- -
-
-add_fieldsets = ((None, {'fields': ('username', 'password1', 'password2', 'email'), 'description': '<i>These account details are shared by the admin system and the game.</i>'}),)
-
- -
-
-user_change_password(request, id, form_url='')[source]
-
- -
-
-save_model(request, obj, form, change)[source]
-

Custom save actions.

-
-
Parameters
-
    -
  • request (Request) – Incoming request.

  • -
  • obj (Object) – Object to save.

  • -
  • form (Form) – Related form instance.

  • -
  • change (bool) – False if this is a new save and not an update.

  • -
-
-
-
- -
-
-response_add(request, obj, post_url_continue=None)[source]
-

Determine the HttpResponse for the add_view stage. It mostly defers to -its superclass implementation but is customized because the User model -has a slightly different workflow.

-
- -
-
-property media
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.accounts.bots.html b/docs/0.9.5/api/evennia.accounts.bots.html deleted file mode 100644 index 60d3a15cdf..0000000000 --- a/docs/0.9.5/api/evennia.accounts.bots.html +++ /dev/null @@ -1,522 +0,0 @@ - - - - - - - - - evennia.accounts.bots — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.accounts.bots

-

Bots are a special child typeclasses of -Account that are controlled by the server.

-
-
-class evennia.accounts.bots.BotStarter(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This non-repeating script has the -sole purpose of kicking its bot -into gear when it is initialized.

-
-
-at_script_creation()[source]
-

Called once, when script is created.

-
- -
-
-at_start()[source]
-

Kick bot into gear.

-
- -
-
-at_repeat()[source]
-

Called self.interval seconds to keep connection. We cannot use -the IDLE command from inside the game since the system will -not catch it (commands executed from the server side usually -has no sessions). So we update the idle counter manually here -instead. This keeps the bot getting hit by IDLE_TIMEOUT.

-
- -
-
-at_server_reload()[source]
-

If server reloads we don’t need to reconnect the protocol -again, this is handled by the portal reconnect mechanism.

-
- -
-
-at_server_shutdown()[source]
-

Make sure we are shutdown.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.accounts.bots.BotStarter'
-
- -
-
-typename = 'BotStarter'
-
- -
- -
-
-class evennia.accounts.bots.Bot(*args, **kwargs)[source]
-

Bases: evennia.accounts.accounts.DefaultAccount

-

A Bot will start itself when the server starts (it will generally -not do so on a reload - that will be handled by the normal Portal -session resync)

-
-
-basetype_setup()[source]
-

This sets up the basic properties for the bot.

-
- -
-
-start(**kwargs)[source]
-

This starts the bot, whatever that may mean.

-
- -
-
-msg(text=None, from_obj=None, session=None, options=None, **kwargs)[source]
-

Evennia -> outgoing protocol

-
- -
-
-execute_cmd(raw_string, session=None)[source]
-

Incoming protocol -> Evennia

-
- -
-
-at_server_shutdown()[source]
-

We need to handle this case manually since the shutdown may be -a reset.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.accounts.accounts.DefaultAccount.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.accounts.accounts.DefaultAccount.MultipleObjectsReturned

-
- -
-
-path = 'evennia.accounts.bots.Bot'
-
- -
-
-typename = 'Bot'
-
- -
- -
-
-class evennia.accounts.bots.IRCBot(*args, **kwargs)[source]
-

Bases: evennia.accounts.bots.Bot

-

Bot for handling IRC connections.

-
-
-factory_path = 'evennia.server.portal.irc.IRCBotFactory'
-
- -
-
-start(ev_channel=None, irc_botname=None, irc_channel=None, irc_network=None, irc_port=None, irc_ssl=None)[source]
-

Start by telling the portal to start a new session.

-
-
Parameters
-
    -
  • ev_channel (str) – Key of the Evennia channel to connect to.

  • -
  • irc_botname (str) – Name of bot to connect to irc channel. If -not set, use self.key.

  • -
  • irc_channel (str) – Name of channel on the form #channelname.

  • -
  • irc_network (str) – URL of the IRC network, like irc.freenode.net.

  • -
  • irc_port (str) – Port number of the irc network, like 6667.

  • -
  • irc_ssl (bool) – Indicates whether to use SSL connection.

  • -
-
-
-
- -
-
-at_msg_send(**kwargs)[source]
-

Shortcut here or we can end up in infinite loop

-
- -
-
-get_nicklist(caller)[source]
-

Retrive the nick list from the connected channel.

-
-
Parameters
-

caller (Object or Account) – The requester of the list. This will -be stored and echoed to when the irc network replies with the -requested info.

-
-
-
-
Notes: Since the return is asynchronous, the caller is stored internally

in a list; all callers in this list will get the nick info once it -returns (it is a custom OOB inputfunc option). The callback will not -survive a reload (which should be fine, it’s very quick).

-
-
-
- -
-
-ping(caller)[source]
-

Fire a ping to the IRC server.

-
-
Parameters
-

caller (Object or Account) – The requester of the ping.

-
-
-
- -
-
-reconnect()[source]
-

Force a protocol-side reconnect of the client without -having to destroy/recreate the bot “account”.

-
- -
-
-msg(text=None, **kwargs)[source]
-

Takes text from connected channel (only).

-
-
Parameters
-

text (str, optional) – Incoming text from channel.

-
-
Keyword Arguments
-

options (dict) – Options dict with the following allowed keys: -- from_channel (str): dbid of a channel this text originated from. -- from_obj (list): list of objects sending this text.

-
-
-
- -
-
-execute_cmd(session=None, txt=None, **kwargs)[source]
-

Take incoming data and send it to connected channel. This is -triggered by the bot_data_in Inputfunc.

-
-
Parameters
-
    -
  • session (Session, optional) – Session responsible for this -command. Note that this is the bot.

  • -
  • txt (str, optional) – Command string.

  • -
-
-
Keyword Arguments
-
    -
  • user (str) – The name of the user who sent the message.

  • -
  • channel (str) – The name of channel the message was sent to.

  • -
  • type (str) – Nature of message. Either ‘msg’, ‘action’, ‘nicklist’ -or ‘ping’.

  • -
  • nicklist (list, optional) – Set if type=’nicklist’. This is a list -of nicks returned by calling the self.get_nicklist. It must look -for a list self._nicklist_callers which will contain all callers -waiting for the nicklist.

  • -
  • timings (float, optional) – Set if type=’ping’. This is the return -(in seconds) of a ping request triggered with self.ping. The -return must look for a list self._ping_callers which will contain -all callers waiting for the ping return.

  • -
-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.accounts.bots.Bot.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.accounts.bots.Bot.MultipleObjectsReturned

-
- -
-
-path = 'evennia.accounts.bots.IRCBot'
-
- -
-
-typename = 'IRCBot'
-
- -
- -
-
-class evennia.accounts.bots.RSSBot(*args, **kwargs)[source]
-

Bases: evennia.accounts.bots.Bot

-

An RSS relayer. The RSS protocol itself runs a ticker to update -its feed at regular intervals.

-
-
-start(ev_channel=None, rss_url=None, rss_rate=None)[source]
-

Start by telling the portal to start a new RSS session

-
-
Parameters
-
    -
  • ev_channel (str) – Key of the Evennia channel to connect to.

  • -
  • rss_url (str) – Full URL to the RSS feed to subscribe to.

  • -
  • rss_rate (int) – How often for the feedreader to update.

  • -
-
-
Raises
-

RuntimeError – If ev_channel does not exist.

-
-
-
- -
-
-execute_cmd(txt=None, session=None, **kwargs)[source]
-

Take incoming data and send it to connected channel. This is -triggered by the bot_data_in Inputfunc.

-
-
Parameters
-
    -
  • session (Session, optional) – Session responsible for this -command.

  • -
  • txt (str, optional) – Command string.

  • -
  • kwargs (dict, optional) – Additional Information passed from bot. -Not used by the RSSbot by default.

  • -
-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.accounts.bots.Bot.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.accounts.bots.Bot.MultipleObjectsReturned

-
- -
-
-path = 'evennia.accounts.bots.RSSBot'
-
- -
-
-typename = 'RSSBot'
-
- -
- -
-
-class evennia.accounts.bots.GrapevineBot(*args, **kwargs)[source]
-

Bases: evennia.accounts.bots.Bot

-

g Grapevine (https://grapevine.haus) relayer. The channel to connect to is the first -name in the settings.GRAPEVINE_CHANNELS list.

-
-
-factory_path = 'evennia.server.portal.grapevine.RestartingWebsocketServerFactory'
-
- -
-
-start(ev_channel=None, grapevine_channel=None)[source]
-

Start by telling the portal to connect to the grapevine network.

-
- -
-
-at_msg_send(**kwargs)[source]
-

Shortcut here or we can end up in infinite loop

-
- -
-
-msg(text=None, **kwargs)[source]
-

Takes text from connected channel (only).

-
-
Parameters
-

text (str, optional) – Incoming text from channel.

-
-
Keyword Arguments
-

options (dict) – Options dict with the following allowed keys: -- from_channel (str): dbid of a channel this text originated from. -- from_obj (list): list of objects sending this text.

-
-
-
- -
-
-execute_cmd(txt=None, session=None, event=None, grapevine_channel=None, sender=None, game=None, **kwargs)[source]
-

Take incoming data from protocol and send it to connected channel. This is -triggered by the bot_data_in Inputfunc.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.accounts.bots.Bot.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.accounts.bots.Bot.MultipleObjectsReturned

-
- -
-
-path = 'evennia.accounts.bots.GrapevineBot'
-
- -
-
-typename = 'GrapevineBot'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.accounts.html b/docs/0.9.5/api/evennia.accounts.html deleted file mode 100644 index 59b3f3daa2..0000000000 --- a/docs/0.9.5/api/evennia.accounts.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - evennia.accounts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.accounts

-

This sub-package defines the out-of-character entities known as -Accounts. These are equivalent to ‘accounts’ and can puppet one or -more Objects depending on settings. An Account has no in-game existence.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.accounts.manager.html b/docs/0.9.5/api/evennia.accounts.manager.html deleted file mode 100644 index 249b5eb503..0000000000 --- a/docs/0.9.5/api/evennia.accounts.manager.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - - - - evennia.accounts.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.accounts.manager

-

The managers for the custom Account object and permissions.

-
-
-class evennia.accounts.manager.AccountManager(*args, **kwargs)[source]
-

Bases: evennia.accounts.manager.AccountDBManager, evennia.typeclasses.managers.TypeclassManager

-
- -
-
-class evennia.accounts.manager.AccountDBManager(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.managers.TypedObjectManager, django.contrib.auth.models.UserManager

-

This AccountManager implements methods for searching -and manipulating Accounts directly from the database.

-

Evennia-specific search methods (will return Characters if -possible or a Typeclass/list of Typeclassed objects, whereas -Django-general methods will return Querysets or database objects):

-

dbref (converter) -dbref_search -get_dbref_range -object_totals -typeclass_search -num_total_accounts -get_connected_accounts -get_recently_created_accounts -get_recently_connected_accounts -get_account_from_email -get_account_from_uid -get_account_from_name -account_search (equivalent to evennia.search_account)

-
-
-num_total_accounts()[source]
-

Get total number of accounts.

-
-
Returns
-

count (int) – The total number of registered accounts.

-
-
-
- -
-
-get_connected_accounts()[source]
-

Get all currently connected accounts.

-
-
Returns
-

count (list)

-
-
Account objects with currently

connected sessions.

-
-
-

-
-
-
- -
-
-get_recently_created_accounts(days=7)[source]
-

Get accounts recently created.

-
-
Parameters
-

days (int, optional) – How many days in the past “recently” means.

-
-
Returns
-

accounts (list) – The Accounts created the last days interval.

-
-
-
- -
-
-get_recently_connected_accounts(days=7)[source]
-

Get accounts recently connected to the game.

-
-
Parameters
-

days (int, optional) – Number of days backwards to check

-
-
Returns
-

accounts (list)

-
-
The Accounts connected to the game in the

last days interval.

-
-
-

-
-
-
- -
-
-get_account_from_email(uemail)[source]
-

Search account by -Returns an account object based on email address.

-
-
Parameters
-

uemail (str) – An email address to search for.

-
-
Returns
-

account (Account) – A found account, if found.

-
-
-
- -
-
-get_account_from_uid(uid)[source]
-

Get an account by id.

-
-
Parameters
-

uid (int) – Account database id.

-
-
Returns
-

account (Account) – The result.

-
-
-
- -
-
-get_account_from_name(uname)[source]
-

Get account object based on name.

-
-
Parameters
-

uname (str) – The Account name to search for.

-
-
Returns
-

account (Account) – The found account.

-
-
-
- -
-
-search_account(ostring, exact=True, typeclass=None)[source]
-

Searches for a particular account by name or -database id.

-
-
Parameters
-
    -
  • ostring (str or int) – A key string or database id.

  • -
  • exact (bool, optional) – Only valid for string matches. If -True, requires exact (non-case-sensitive) match, -otherwise also match also keys containing the ostring -(non-case-sensitive fuzzy match).

  • -
  • typeclass (str or Typeclass, optional) – Limit the search only to -accounts of this typeclass.

  • -
-
-
-
- -
- -

Searches for a particular account by name or -database id.

-
-
Parameters
-
    -
  • ostring (str or int) – A key string or database id.

  • -
  • exact (bool, optional) – Only valid for string matches. If -True, requires exact (non-case-sensitive) match, -otherwise also match also keys containing the ostring -(non-case-sensitive fuzzy match).

  • -
  • typeclass (str or Typeclass, optional) – Limit the search only to -accounts of this typeclass.

  • -
-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.accounts.models.html b/docs/0.9.5/api/evennia.accounts.models.html deleted file mode 100644 index 0233e2de2c..0000000000 --- a/docs/0.9.5/api/evennia.accounts.models.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - - - - - evennia.accounts.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.accounts.models

-

Account

-

The account class is an extension of the default Django user class, -and is customized for the needs of Evennia.

-

We use the Account to store a more mud-friendly style of permission -system as well as to allow the admin more flexibility by storing -attributes on the Account. Within the game we should normally use the -Account manager’s methods to create users so that permissions are set -correctly.

-

To make the Account model more flexible for your own game, it can also -persistently store attributes of its own. This is ideal for extra -account info and OOC account configuration variables etc.

-
-
-class evennia.accounts.models.AccountDB(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.models.TypedObject, django.contrib.auth.models.AbstractUser

-

This is a special model using Django’s ‘profile’ functionality -and extends the default Django User model. It is defined as such -by use of the variable AUTH_PROFILE_MODULE in the settings. -One accesses the fields/methods. We try use this model as much -as possible rather than User, since we can customize this to -our liking.

-

The TypedObject supplies the following (inherited) properties:

-
-
    -
  • key - main name

  • -
  • typeclass_path - the path to the decorating typeclass

  • -
  • typeclass - auto-linked typeclass

  • -
  • date_created - time stamp of object creation

  • -
  • permissions - perm strings

  • -
  • dbref - #id of object

  • -
  • db - persistent attribute storage

  • -
  • ndb - non-persistent attribute storage

  • -
-
-

The AccountDB adds the following properties:

-
-
    -
  • is_connected - If any Session is currently connected to this Account

  • -
  • name - alias for user.username

  • -
  • sessions - sessions connected to this account

  • -
  • is_superuser - bool if this account is a superuser

  • -
  • is_bot - bool if this account is a bot and not a real account

  • -
-
-
-
-db_is_connected
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_cmdset_storage
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_is_bot
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-objects = <evennia.accounts.manager.AccountDBManager object>
-
- -
-
-property cmdset_storage
-

Getter. Allows for value = self.name. Returns a list of cmdset_storage.

-
- -
-
-property name
-
- -
-
-property key
-
- -
-
-property uid
-

Getter. Retrieves the user id

-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-account_subscription_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_attributes
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_tags
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-get_next_by_date_joined(*, field=<django.db.models.fields.DateTimeField: date_joined>, is_next=True, **kwargs)
-
- -
-
-get_next_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=True, **kwargs)
-
- -
-
-get_previous_by_date_joined(*, field=<django.db.models.fields.DateTimeField: date_joined>, is_next=False, **kwargs)
-
- -
-
-get_previous_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=False, **kwargs)
-
- -
-
-groups
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-hide_from_accounts_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-property is_bot
-

A wrapper for getting database field db_is_bot.

-
- -
-
-property is_connected
-

A wrapper for getting database field db_is_connected.

-
- -
-
-logentry_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-objectdb_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-path = 'evennia.accounts.models.AccountDB'
-
- -
-
-receiver_account_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-scriptdb_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-sender_account_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
-
-user_permissions
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.cmdhandler.html b/docs/0.9.5/api/evennia.commands.cmdhandler.html deleted file mode 100644 index 89e75be58e..0000000000 --- a/docs/0.9.5/api/evennia.commands.cmdhandler.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - - - evennia.commands.cmdhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.cmdhandler

-

Command handler

-

This module contains the infrastructure for accepting commands on the -command line. The processing of a command works as follows:

-
    -
  1. The calling object (caller) is analyzed based on its callertype.

  2. -
  3. Cmdsets are gathered from different sources: -- channels: all available channel names are auto-created into a cmdset, to allow

    -
    -

    for giving the channel name and have the following immediately -sent to the channel. The sending is performed by the CMD_CHANNEL -system command.

    -
    -
      -
    • object cmdsets: all objects at caller’s location are scanned for non-empty -cmdsets. This includes cmdsets on exits.

    • -
    • caller: the caller is searched for its own currently active cmdset.

    • -
    • account: lastly the cmdsets defined on caller.account are added.

    • -
    -
  4. -
  5. The collected cmdsets are merged together to a combined, current cmdset.

  6. -
  7. If the input string is empty -> check for CMD_NOINPUT command in -current cmdset or fallback to error message. Exit.

  8. -
  9. The Command Parser is triggered, using the current cmdset to analyze the -input string for possible command matches.

  10. -
  11. If multiple matches are found -> check for CMD_MULTIMATCH in current -cmdset, or fallback to error message. Exit.

  12. -
  13. If no match was found -> check for CMD_NOMATCH in current cmdset or -fallback to error message. Exit.

  14. -
  15. A single match was found. If this is a channel-command (i.e. the -ommand name is that of a channel), –> check for CMD_CHANNEL in -current cmdset or use channelhandler default. Exit.

  16. -
  17. At this point we have found a normal command. We assign useful variables to it that -will be available to the command coder at run-time.

  18. -
-
    -
  1. We have a unique cmdobject, primed for use. Call all hooks:

  2. -
-
-

at_pre_cmd(), cmdobj.parse(), cmdobj.func() and finally at_post_cmd().

-
-
    -
  1. Return deferred that will fire with the return from cmdobj.func() (unused by default).

  2. -
-
-
-evennia.commands.cmdhandler.cmdhandler(called_by, raw_string, _testing=False, callertype='session', session=None, cmdobj=None, cmdobj_key=None, **kwargs)[source]
-

This is the main mechanism that handles any string sent to the engine.

-
-
Parameters
-
    -
  • called_by (Session, Account or Object) – Object from which this -command was called. which this was called from. What this is -depends on the game state.

  • -
  • raw_string (str) – The command string as given on the command line.

  • -
  • _testing (bool, optional) – Used for debug purposes and decides if we -should actually execute the command or not. If True, the -command instance will be returned.

  • -
  • callertype (str, optional) – One of “session”, “account” or -“object”. These are treated in decending order, so when the -Session is the caller, it will merge its own cmdset into -cmdsets from both Account and eventual puppeted Object (and -cmdsets in its room etc). An Account will only include its own -cmdset and the Objects and so on. Merge order is the same -order, so that Object cmdsets are merged in last, giving them -precendence for same-name and same-prio commands.

  • -
  • session (Session, optional) – Relevant if callertype is “account” - the session will help -retrieve the correct cmdsets from puppeted objects.

  • -
  • cmdobj (Command, optional) – If given a command instance, this will be executed using -called_by as the caller, raw_string representing its arguments and (optionally) -cmdobj_key as its input command name. No cmdset lookup will be performed but -all other options apply as normal. This allows for running a specific Command -within the command system mechanism.

  • -
  • cmdobj_key (string, optional) – Used together with cmdobj keyword to specify -which cmdname should be assigned when calling the specified Command instance. This -is made available as self.cmdstring when the Command runs. -If not given, the command will be assumed to be called as cmdobj.key.

  • -
-
-
Keyword Arguments
-

kwargs (any) – other keyword arguments will be assigned as named variables on the -retrieved command object before it is executed. This is unused -in default Evennia but may be used by code to set custom flags or -special operating conditions for a command as it executes.

-
-
Returns
-

deferred (Deferred) – This deferred is fired with the return -value of the command’s func method. This is not used in -default Evennia.

-
-
-
- -
-
-exception evennia.commands.cmdhandler.InterruptCommand[source]
-

Bases: Exception

-

Cleanly interrupt a command.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.cmdparser.html b/docs/0.9.5/api/evennia.commands.cmdparser.html deleted file mode 100644 index f63dbe8ab2..0000000000 --- a/docs/0.9.5/api/evennia.commands.cmdparser.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - - - - - evennia.commands.cmdparser — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.cmdparser

-

The default command parser. Use your own by assigning -settings.COMMAND_PARSER to a Python path to a module containing the -replacing cmdparser function. The replacement parser must accept the -same inputs as the default one.

-
-
-evennia.commands.cmdparser.create_match(cmdname, string, cmdobj, raw_cmdname)[source]
-

Builds a command match by splitting the incoming string and -evaluating the quality of the match.

-
-
Parameters
-
    -
  • cmdname (str) – Name of command to check for.

  • -
  • string (str) – The string to match against.

  • -
  • cmdobj (str) – The full Command instance.

  • -
  • raw_cmdname (str, optional) – If CMD_IGNORE_PREFIX is set and the cmdname starts with -one of the prefixes to ignore, this contains the raw, unstripped cmdname, -otherwise it is None.

  • -
-
-
Returns
-

match (tuple)

-
-
This is on the form (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname),

where cmdname is the command’s name and args is the rest of the incoming -string, without said command name. cmdobj is -the Command instance, the cmdlen is the same as len(cmdname) and mratio -is a measure of how big a part of the full input string the cmdname -takes up - an exact match would be 1.0. Finally, the raw_cmdname is -the cmdname unmodified by eventual prefix-stripping.

-
-
-

-
-
-
- -
-
-evennia.commands.cmdparser.build_matches(raw_string, cmdset, include_prefixes=False)[source]
-

Build match tuples by matching raw_string against available commands.

-
-
Parameters
-
    -
  • raw_string (str) – Input string that can look in any way; the only assumption is -that the sought command’s name/alias must be first in the string.

  • -
  • cmdset (CmdSet) – The current cmdset to pick Commands from.

  • -
  • include_prefixes (bool) – If set, include prefixes like @, ! etc (specified in settings) -in the match, otherwise strip them before matching.

  • -
-
-
Returns
-

matches (list) A list of match tuples created by cmdparser.create_match.

-
-
-
- -
-
-evennia.commands.cmdparser.try_num_prefixes(raw_string)[source]
-

Test if user tried to separate multi-matches with a number separator -(default 1-name, 2-name etc). This is usually called last, if no other -match was found.

-
-
Parameters
-

raw_string (str) – The user input to parse.

-
-
Returns
-

mindex, new_raw_string (tuple)

-
-
If a multimatch-separator was detected,

this is stripped out as an integer to separate between the matches. The -new_raw_string is the result of stripping out that identifier. If no -such form was found, returns (None, None).

-
-
-

-
-
-

Example

-

In the default configuration, entering 2-ball (e.g. in a room will more -than one ‘ball’ object), will lead to a multimatch and this function -will parse “2-ball” and return (2, “ball”).

-
- -
-
-evennia.commands.cmdparser.cmdparser(raw_string, cmdset, caller, match_index=None)[source]
-

This function is called by the cmdhandler once it has -gathered and merged all valid cmdsets valid for this particular parsing.

-
-
Parameters
-
    -
  • raw_string (str) – The unparsed text entered by the caller.

  • -
  • cmdset (CmdSet) – The merged, currently valid cmdset

  • -
  • caller (Session, Account or Object) – The caller triggering this parsing.

  • -
  • match_index (int, optional) – Index to pick a given match in a -list of same-named command matches. If this is given, it suggests -this is not the first time this function was called: normally -the first run resulted in a multimatch, and the index is given -to select between the results for the second run.

  • -
-
-
Returns
-

matches (list)

-
-
This is a list of match-tuples as returned by create_match.

If no matches were found, this is an empty list.

-
-
-

-
-
-

Notes

-

The cmdparser understand the following command combinations (where -[] marks optional parts.

-

[cmdname[ cmdname2 cmdname3 …] [the rest]

-

A command may consist of any number of space-separated words of any -length, and contain any character. It may also be empty.

-

The parser makes use of the cmdset to find command candidates. The -parser return a list of matches. Each match is a tuple with its -first three elements being the parsed cmdname (lower case), -the remaining arguments, and the matched cmdobject from the cmdset.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.cmdset.html b/docs/0.9.5/api/evennia.commands.cmdset.html deleted file mode 100644 index 7cfe1cfafa..0000000000 --- a/docs/0.9.5/api/evennia.commands.cmdset.html +++ /dev/null @@ -1,451 +0,0 @@ - - - - - - - - - evennia.commands.cmdset — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.cmdset

-

A Command Set (CmdSet) holds a set of commands. The Cmdsets can be -merged and combined to create new sets of commands in a -non-destructive way. This makes them very powerful for implementing -custom game states where different commands (or different variations -of commands) are available to the accounts depending on circumstance.

-

The available merge operations are partly borrowed from mathematical -Set theory.

-
    -
  • -
    Union The two command sets are merged so that as many commands as

    possible of each cmdset ends up in the merged cmdset. Same-name -commands are merged by priority. This is the most common default. -Ex: A1,A3 + B1,B2,B4,B5 = A1,B2,A3,B4,B5

    -
    -
    -
  • -
  • -
    Intersect - Only commands found in both cmdsets (i.e. which have

    same names) end up in the merged cmdset, with the higher-priority -cmdset replacing the lower one. Ex: A1,A3 + B1,B2,B4,B5 = A1

    -
    -
    -
  • -
  • -
    Replace - The commands of this cmdset completely replaces the

    lower-priority cmdset’s commands, regardless of if same-name commands -exist. Ex: A1,A3 + B1,B2,B4,B5 = A1,A3

    -
    -
    -
  • -
  • -
    Remove - This removes the relevant commands from the

    lower-priority cmdset completely. They are not replaced with -anything, so this in effects uses the high-priority cmdset as a filter -to affect the low-priority cmdset. Ex: A1,A3 + B1,B2,B4,B5 = B2,B4,B5

    -
    -
    -
  • -
-
-
-class evennia.commands.cmdset.CmdSet(cmdsetobj=None, key=None)[source]
-

Bases: object

-

This class describes a unique cmdset that understands priorities. -CmdSets can be merged and made to perform various set operations -on each other. CmdSets have priorities that affect which of their -ingoing commands gets used.

-

In the examples, cmdset A always have higher priority than cmdset B.

-

key - the name of the cmdset. This can be used on its own for game -operations

-

mergetype (partly from Set theory):

-
-
-
Union - The two command sets are merged so that as many

commands as possible of each cmdset ends up in the -merged cmdset. Same-name commands are merged by -priority. This is the most common default. -Ex: A1,A3 + B1,B2,B4,B5 = A1,B2,A3,B4,B5

-
-
Intersect - Only commands found in both cmdsets

(i.e. which have same names) end up in the merged -cmdset, with the higher-priority cmdset replacing the -lower one. Ex: A1,A3 + B1,B2,B4,B5 = A1

-
-
Replace - The commands of this cmdset completely replaces

the lower-priority cmdset’s commands, regardless -of if same-name commands exist. -Ex: A1,A3 + B1,B2,B4,B5 = A1,A3

-
-
Remove - This removes the relevant commands from the
-

lower-priority cmdset completely. They are not -replaced with anything, so this in effects uses the -high-priority cmdset as a filter to affect the -low-priority cmdset. -Ex: A1,A3 + B1,B2,B4,B5 = B2,B4,B5

-
-
-
Note: Commands longer than 2 characters and starting

with double underscrores, like ‘__noinput_command’ -are considered ‘system commands’ and are -excempt from all merge operations - they are -ALWAYS included across mergers and only affected -if same-named system commands replace them.

-
-
-
-
-
-
-
priority- All cmdsets are always merged in pairs of two so that

the higher set’s mergetype is applied to the -lower-priority cmdset. Default commands have priority 0, -high-priority ones like Exits and Channels have 10 and 9. -Priorities can be negative as well to give default -commands preference.

-
-
duplicates - determines what happens when two sets of equal
-

priority merge (only). Defaults to None and has the first of them in the -merger (i.e. A above) automatically taking -precedence. But if duplicates is true, the -result will be a merger with more than one of each -name match. This will usually lead to the account -receiving a multiple-match error higher up the road, -but can be good for things like cmdsets on non-account -objects in a room, to allow the system to warn that -more than one ‘ball’ in the room has the same ‘kick’ -command defined on it, so it may offer a chance to -select which ball to kick … Allowing duplicates -only makes sense for Union and Intersect, the setting -is ignored for the other mergetypes. -Note that the duplicates flag is not propagated in -a cmdset merger. So A + B = C will result in -a cmdset with duplicate commands, but C.duplicates will -be None. For duplication to apply to a whole cmdset -stack merge, _all_ cmdsets in the stack must have -.duplicates=True set.

-
-

Finally, if a final cmdset has .duplicates=None (the normal -unless created alone with another value), the cmdhandler -will assume True for object-based cmdsets and False for -all other. This is usually the most intuitive outcome.

-
-
key_mergetype (dict) - allows the cmdset to define a unique

mergetype for particular cmdsets. Format is -{CmdSetkeystring:mergetype}. Priorities still apply. -Example: {‘Myevilcmdset’,’Replace’} which would make -sure for this set to always use ‘Replace’ on -Myevilcmdset no matter what overall mergetype this set -has.

-
-
no_objs - don’t include any commands from nearby objects

when searching for suitable commands

-
-
no_exits - ignore the names of exits when matching against

commands

-
-
no_channels - ignore the name of channels when matching against

commands (WARNING- this is dangerous since the -account can then not even ask staff for help if -something goes wrong)

-
-
-
-
-mergetype = 'Union'
-
- -
-
-priority = 0
-
- -
-
-no_exits = None
-
- -
-
-no_objs = None
-
- -
-
-no_channels = None
-
- -
-
-duplicates = None
-
- -
-
-permanent = False
-
- -
-
-key_mergetypes = {}
-
- -
-
-errmessage = ''
-
- -
-
-to_duplicate = ('key', 'cmdsetobj', 'no_exits', 'no_objs', 'no_channels', 'permanent', 'mergetype', 'priority', 'duplicates', 'errmessage')
-
- -
-
-__init__(cmdsetobj=None, key=None)[source]
-

Creates a new CmdSet instance.

-
-
Parameters
-
    -
  • cmdsetobj (Session, Account, Object, optional) – This is the database object -to which this particular instance of cmdset is related. It -is often a character but may also be a regular object, Account -or Session.

  • -
  • key (str, optional) – The idenfier for this cmdset. This -helps if wanting to selectively remov cmdsets.

  • -
-
-
-
- -
-
-key = 'Unnamed CmdSet'
-
- -
-
-add(cmd, allow_duplicates=False)[source]
-

Add a new command or commands to this CmdSet, a list of -commands or a cmdset to this cmdset. Note that this is not -a merge operation (that is handled by the + operator).

-
-
Parameters
-
    -
  • cmd (Command, list, Cmdset) – This allows for adding one or -more commands to this Cmdset in one go. If another Cmdset -is given, all its commands will be added.

  • -
  • allow_duplicates (bool, optional) – If set, will not try to remove -duplicate cmds in the set. This is needed during the merge process -to avoid wiping commands coming from cmdsets with duplicate=True.

  • -
-
-
-

Notes

-

If cmd already exists in set, it will replace the old one -(no priority checking etc happens here). This is very useful -when overloading default commands).

-

If cmd is another cmdset class or -instance, the commands of -that command set is added to this one, as if they were part of -the original cmdset definition. No merging or priority checks -are made, rather later added commands will simply replace -existing ones to make a unique set.

-
- -
-
-remove(cmd)[source]
-

Remove a command instance from the cmdset.

-
-
Parameters
-

cmd (Command or str) – Either the Command object to remove -or the key of such a command.

-
-
-
- -
-
-get(cmd)[source]
-

Get a command from the cmdset. This is mostly useful to -check if the command is part of this cmdset or not.

-
-
Parameters
-

cmd (Command or str) – Either the Command object or its key.

-
-
Returns
-

cmd (Command) – The first matching Command in the set.

-
-
-
- -
-
-count()[source]
-

Number of commands in set.

-
-
Returns
-

N (int) – Number of commands in this Cmdset.

-
-
-
- -
-
-get_system_cmds()[source]
-

Get system commands in cmdset

-
-
Returns
-

sys_cmds (list) – The system commands in the set.

-
-
-

Notes

-

As far as the Cmdset is concerned, system commands are any -commands with a key starting with double underscore __. -These are excempt from merge operations.

-
- -
-
-make_unique(caller)[source]
-

Remove duplicate command-keys (unsafe)

-
-
Parameters
-

caller (object) – Commands on this object will -get preference in the duplicate removal.

-
-
-

Notes

-

This is an unsafe command meant to clean out a cmdset of -doublet commands after it has been created. It is useful -for commands inheriting cmdsets from the cmdhandler where -obj-based cmdsets always are added double. Doublets will -be weeded out with preference to commands defined on -caller, otherwise just by first-come-first-served.

-
- -
-
-get_all_cmd_keys_and_aliases(caller=None)[source]
-

Collects keys/aliases from commands

-
-
Parameters
-

caller (Object, optional) – If set, this is used to check access permissions -on each command. Only commands that pass are returned.

-
-
Returns
-

names (list)

-
-
A list of all command keys and aliases in this cmdset. If caller

was given, this list will only contain commands to which caller passed -the call locktype check.

-
-
-

-
-
-
- -
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.commands.cmdset.CmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.cmdsethandler.html b/docs/0.9.5/api/evennia.commands.cmdsethandler.html deleted file mode 100644 index 021749a299..0000000000 --- a/docs/0.9.5/api/evennia.commands.cmdsethandler.html +++ /dev/null @@ -1,419 +0,0 @@ - - - - - - - - - evennia.commands.cmdsethandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.cmdsethandler

-

CmdSethandler

-

The Cmdsethandler tracks an object’s ‘Current CmdSet’, which is the -current merged sum of all CmdSets added to it.

-

A CmdSet constitues a set of commands. The CmdSet works as a special -intelligent container that, when added to other CmdSet make sure that -same-name commands are treated correctly (usually so there are no -doublets). This temporary but up-to-date merger of CmdSet is jointly -called the Current Cmset. It is this Current CmdSet that the -commandhandler looks through whenever an account enters a command (it -also adds CmdSets from objects in the room in real-time). All account -objects have a ‘default cmdset’ containing all the normal in-game mud -commands (look etc).

-

So what is all this cmdset complexity good for?

-

In its simplest form, a CmdSet has no commands, only a key name. In -this case the cmdset’s use is up to each individual game - it can be -used by an AI module for example (mobs in cmdset ‘roam’ move from room -to room, in cmdset ‘attack’ they enter combat with accounts).

-

Defining commands in cmdsets offer some further powerful game-design -consequences however. Here are some examples:

-

As mentioned above, all accounts always have at least the Default -CmdSet. This contains the set of all normal-use commands in-game, -stuff like look and @desc etc. Now assume our players end up in a dark -room. You don’t want the player to be able to do much in that dark -room unless they light a candle. You could handle this by changing all -your normal commands to check if the player is in a dark room. This -rapidly goes unwieldly and error prone. Instead you just define a -cmdset with only those commands you want to be available in the ‘dark’ -cmdset - maybe a modified look command and a ‘light candle’ command - -and have this completely replace the default cmdset.

-

Another example: Say you want your players to be able to go -fishing. You could implement this as a ‘fish’ command that fails -whenever the account has no fishing rod. Easy enough. But what if you -want to make fishing more complex - maybe you want four-five different -commands for throwing your line, reeling in, etc? Most players won’t -(we assume) have fishing gear, and having all those detailed commands -is cluttering up the command list. And what if you want to use the -‘throw’ command also for throwing rocks etc instead of ‘using it up’ -for a minor thing like fishing?

-

So instead you put all those detailed fishing commands into their own -CommandSet called ‘Fishing’. Whenever the player gives the command -‘fish’ (presumably the code checks there is also water nearby), only -THEN this CommandSet is added to the Cmdhandler of the account. The -‘throw’ command (which normally throws rocks) is replaced by the -custom ‘fishing variant’ of throw. What has happened is that the -Fishing CommandSet was merged on top of the Default ones, and due to -how we defined it, its command overrules the default ones.

-

When we are tired of fishing, we give the ‘go home’ command (or -whatever) and the Cmdhandler simply removes the fishing CommandSet -so that we are back at defaults (and can throw rocks again).

-

Since any number of CommandSets can be piled on top of each other, you -can then implement separate sets for different situations. For -example, you can have a ‘On a boat’ set, onto which you then tack on -the ‘Fishing’ set. Fishing from a boat? No problem!

-
-
-evennia.commands.cmdsethandler.import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False)[source]
-

This helper function is used by the cmdsethandler to load a cmdset -instance from a python module, given a python_path. It’s usually accessed -through the cmdsethandler’s add() and add_default() methods. -path - This is the full path to the cmdset object on python dot-form

-
-
Parameters
-
    -
  • path (str) – The path to the command set to load.

  • -
  • cmdsetobj (CmdSet) – The database object/typeclass on which this cmdset is to be -assigned (this can be also channels and exits, as well as accounts -but there will always be such an object)

  • -
  • emit_to_obj (Object, optional) – If given, error is emitted to -this object (in addition to logging)

  • -
  • no_logging (bool, optional) – Don’t log/send error messages. -This can be useful if import_cmdset is just used to check if -this is a valid python path or not.

  • -
-
-
Returns
-

cmdset (CmdSet)

-
-
The imported command set. If an error was

encountered, commands.cmdsethandler._ErrorCmdSet is returned -for the benefit of the handler.

-
-
-

-
-
-
- -
-
-class evennia.commands.cmdsethandler.CmdSetHandler(obj, init_true=True)[source]
-

Bases: object

-

The CmdSetHandler is always stored on an object, this object is supplied -as an argument.

-

The ‘current’ cmdset is the merged set currently active for this object. -This is the set the game engine will retrieve when determining which -commands are available to the object. The cmdset_stack holds a history of -all CmdSets to allow the handler to remove/add cmdsets at will. Doing so -will re-calculate the ‘current’ cmdset.

-
-
-__init__(obj, init_true=True)[source]
-

This method is called whenever an object is recreated.

-
-
Parameters
-
    -
  • obj (Object) – An reference to the game object this handler -belongs to.

  • -
  • init_true (bool, optional) – Set when the handler is initializing -and loads the current cmdset.

  • -
-
-
-
- -
-
-update(init_mode=False)[source]
-

Re-adds all sets in the handler to have an updated current

-
-
Parameters
-

init_mode (bool, optional) – Used automatically right after -this handler was created; it imports all permanent cmdsets -from the database.

-
-
-

Notes

-

This method is necessary in order to always have a .current -cmdset when working with the cmdsethandler in code. But the -CmdSetHandler doesn’t (cannot) consider external cmdsets and game -state. This means that the .current calculated from this method -will likely not match the true current cmdset as determined at -run-time by cmdhandler.get_and_merge_cmdsets(). So in a running -game the responsibility of keeping .current upt-to-date belongs -to the central cmdhandler.get_and_merge_cmdsets()!

-
- -
-
-add(cmdset, emit_to_obj=None, permanent=False, default_cmdset=False)[source]
-

Add a cmdset to the handler, on top of the old ones, unless it -is set as the default one (it will then end up at the bottom of the stack)

-
-
Parameters
-
    -
  • cmdset (CmdSet or str) – Can be a cmdset object or the python path -to such an object.

  • -
  • emit_to_obj (Object, optional) – An object to receive error messages.

  • -
  • permanent (bool, optional) – This cmdset will remain across a server reboot.

  • -
  • default_cmdset (Cmdset, optional) – Insert this to replace the -default cmdset position (there is only one such position, -always at the bottom of the stack).

  • -
-
-
-

Notes

-

An interesting feature of this method is if you were to send -it an already instantiated cmdset (i.e. not a class), the -current cmdsethandler’s obj attribute will then not be -transferred over to this already instantiated set (this is -because it might be used elsewhere and can cause strange -effects). This means you could in principle have the -handler launch command sets tied to a different object -than the handler. Not sure when this would be useful, but -it’s a ‘quirk’ that has to be documented.

-
- -
-
-add_default(cmdset, emit_to_obj=None, permanent=True)[source]
-

Shortcut for adding a default cmdset.

-
-
Parameters
-
    -
  • cmdset (Cmdset) – The Cmdset to add.

  • -
  • emit_to_obj (Object, optional) – Gets error messages

  • -
  • permanent (bool, optional) – The new Cmdset should survive a server reboot.

  • -
-
-
-
- -
-
-remove(cmdset=None, default_cmdset=False)[source]
-

Remove a cmdset from the handler.

-
-
Parameters
-
    -
  • cmdset (CommandSet or str, optional) – This can can be supplied either as a cmdset-key, -an instance of the CmdSet or a python path to the cmdset. -If no key is given, the last cmdset in the stack is -removed. Whenever the cmdset_stack changes, the cmdset is -updated. If default_cmdset is set, this argument is ignored.

  • -
  • default_cmdset (bool, optional) – If set, this will remove the -default cmdset (at the bottom of the stack).

  • -
-
-
-
- -
-
-delete(cmdset=None, default_cmdset=False)
-

Remove a cmdset from the handler.

-
-
Parameters
-
    -
  • cmdset (CommandSet or str, optional) – This can can be supplied either as a cmdset-key, -an instance of the CmdSet or a python path to the cmdset. -If no key is given, the last cmdset in the stack is -removed. Whenever the cmdset_stack changes, the cmdset is -updated. If default_cmdset is set, this argument is ignored.

  • -
  • default_cmdset (bool, optional) – If set, this will remove the -default cmdset (at the bottom of the stack).

  • -
-
-
-
- -
-
-remove_default()[source]
-

This explicitly deletes only the default cmdset.

-
- -
-
-delete_default()
-

This explicitly deletes only the default cmdset.

-
- -
-
-get()[source]
-

Get all cmdsets.

-
-
Returns
-

cmdsets (list) – All the command sets currently in the handler.

-
-
-
- -
-
-all()
-

Get all cmdsets.

-
-
Returns
-

cmdsets (list) – All the command sets currently in the handler.

-
-
-
- -
-
-clear()[source]
-

Removes all Command Sets from the handler except the default one -(use self.remove_default to remove that).

-
- -
-
-has(cmdset, must_be_default=False)[source]
-

checks so the cmdsethandler contains a given cmdset

-
-
Parameters
-
    -
  • cmdset (str or Cmdset) – Cmdset key, pythonpath or -Cmdset to check the existence for.

  • -
  • must_be_default (bool, optional) – Only return True if -the checked cmdset is the default one.

  • -
-
-
Returns
-

has_cmdset (bool) – Whether or not the cmdset is in the handler.

-
-
-
- -
-
-has_cmdset(cmdset, must_be_default=False)
-

checks so the cmdsethandler contains a given cmdset

-
-
Parameters
-
    -
  • cmdset (str or Cmdset) – Cmdset key, pythonpath or -Cmdset to check the existence for.

  • -
  • must_be_default (bool, optional) – Only return True if -the checked cmdset is the default one.

  • -
-
-
Returns
-

has_cmdset (bool) – Whether or not the cmdset is in the handler.

-
-
-
- -
-
-reset()[source]
-

Force reload of all cmdsets in handler. This should be called -after _CACHED_CMDSETS have been cleared (normally this is -handled automatically by @reload).

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.command.html b/docs/0.9.5/api/evennia.commands.command.html deleted file mode 100644 index 101531d80b..0000000000 --- a/docs/0.9.5/api/evennia.commands.command.html +++ /dev/null @@ -1,520 +0,0 @@ - - - - - - - - - evennia.commands.command — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.command

-

The base Command class.

-

All commands in Evennia inherit from the ‘Command’ class in this module.

-
-
-class evennia.commands.command.CommandMeta(*args, **kwargs)[source]
-

Bases: type

-

The metaclass cleans up all properties on the class

-
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
- -
-
-class evennia.commands.command.Command(**kwargs)[source]
-

Bases: object

-

Base command

-
-
Usage:

command [args]

-
-
-

This is the base command class. Inherit from this -to create new commands.

-

The cmdhandler makes the following variables available to the -command methods (so you can always assume them to be there): -self.caller - the game object calling the command -self.cmdstring - the command name used to trigger this command (allows

-
-

you to know which alias was used, for example)

-
-
-
cmd.args - everything supplied to the command following the cmdstring

(this is usually what is parsed in self.parse())

-
-
cmd.cmdset - the cmdset from which this command was matched (useful only

seldomly, notably for help-type commands, to create dynamic -help entries and lists)

-
-
cmd.obj - the object on which this command is defined. If a default command,

this is usually the same as caller.

-
-
-

cmd.rawstring - the full raw string input, including any args and no parsing.

-

The following class properties can/should be defined on your child class:

-

key - identifier for command (e.g. “look”) -aliases - (optional) list of aliases (e.g. [“l”, “loo”]) -locks - lock string (default is “cmd:all()”) -help_category - how to organize this help entry in help system

-
-

(default is “General”)

-
-

auto_help - defaults to True. Allows for turning off auto-help generation -arg_regex - (optional) raw string regex defining how the argument part of

-
-

the command should look in order to match for this command -(e.g. must it be a space between cmdname and arg?)

-
-
-
auto_help_display_key - (optional) if given, this replaces the string shown

in the auto-help listing. This is particularly useful for system-commands -whose actual key is not really meaningful.

-
-
-

(Note that if auto_help is on, this initial string is also used by the -system to create the help entry for the command, so it’s a good idea to -format it similar to this one). This behavior can be changed by -overriding the method ‘get_help’ of a command: by default, this -method returns cmd.__doc__ (that is, this very docstring, or -the docstring of your command). You can, however, extend or -replace this without disabling auto_help.

-
-
-key = 'command'
-
- -
-
-aliases = []
-
- -
-
-locks = 'cmd:all();'
-
- -
-
-help_category = 'general'
-
- -
-
-auto_help = True
-
- -
-
-is_exit = False
-
- -
-
-arg_regex = None
-
- -
-
-msg_all_sessions = False
-
- -
-
-__init__(**kwargs)[source]
-

The lockhandler works the same as for objects. -optional kwargs will be set as properties on the Command at runtime, -overloading evential same-named class properties.

-
- -
-
-lockhandler[source]
-
- -
-
-set_key(new_key)[source]
-

Update key.

-
-
Parameters
-

new_key (str) – The new key.

-
-
-

Notes

-

This is necessary to use to make sure the optimization -caches are properly updated as well.

-
- -
-
-set_aliases(new_aliases)[source]
-

Replace aliases with new ones.

-
-
Parameters
-

new_aliases (str or list) – Either a ;-separated string -or a list of aliases. These aliases will replace the -existing ones, if any.

-
-
-

Notes

-

This is necessary to use to make sure the optimization -caches are properly updated as well.

-
- -
-
-match(cmdname)[source]
-

This is called by the system when searching the available commands, -in order to determine if this is the one we wanted. cmdname was -previously extracted from the raw string by the system.

-
-
Parameters
-

cmdname (str) – Always lowercase when reaching this point.

-
-
Returns
-

result (bool) – Match result.

-
-
-
- -
-
-access(srcobj, access_type='cmd', default=False)[source]
-

This hook is called by the cmdhandler to determine if srcobj -is allowed to execute this command. It should return a boolean -value and is not normally something that need to be changed since -it’s using the Evennia permission system directly.

-
-
Parameters
-
    -
  • srcobj (Object) – Object trying to gain permission

  • -
  • access_type (str, optional) – The lock type to check.

  • -
  • default (bool, optional) – The fallback result if no lock -of matching access_type is found on this Command.

  • -
-
-
-
- -
-
-msg(text=None, to_obj=None, from_obj=None, session=None, **kwargs)[source]
-

This is a shortcut instead of calling msg() directly on an -object - it will detect if caller is an Object or an Account and -also appends self.session automatically if self.msg_all_sessions is False.

-
-
Parameters
-
    -
  • text (str, optional) – Text string of message to send.

  • -
  • to_obj (Object, optional) – Target object of message. Defaults to self.caller.

  • -
  • from_obj (Object, optional) – Source of message. Defaults to to_obj.

  • -
  • session (Session, optional) – Supply data only to a unique -session (ignores the value of self.msg_all_sessions).

  • -
-
-
Keyword Arguments
-
    -
  • options (dict) – Options to the protocol.

  • -
  • any (any) – All other keywords are interpreted as th -name of send-instructions.

  • -
-
-
-
- -
-
-execute_cmd(raw_string, session=None, obj=None, **kwargs)[source]
-

A shortcut of execute_cmd on the caller. It appends the -session automatically.

-
-
Parameters
-
    -
  • raw_string (str) – Execute this string as a command input.

  • -
  • session (Session, optional) – If not given, the current command’s Session will be used.

  • -
  • obj (Object or Account, optional) – Object or Account on which to call the execute_cmd. -If not given, self.caller will be used.

  • -
-
-
Keyword Arguments
-
    -
  • keyword arguments will be added to the found command (Other) –

  • -
  • instace as variables before it executes. This is (object) –

  • -
  • by default Evennia but may be used to set flags and (unused) –

  • -
  • operating paramaters for commands at run-time. (change) –

  • -
-
-
-
- -
-
-at_pre_cmd()[source]
-

This hook is called before self.parse() on all commands. If -this hook returns anything but False/None, the command -sequence is aborted.

-
- -
-
-at_post_cmd()[source]
-

This hook is called after the command has finished executing -(after self.func()).

-
- -
-
-parse()[source]
-

Once the cmdhandler has identified this as the command we -want, this function is run. If many of your commands have a -similar syntax (for example ‘cmd arg1 = arg2’) you should -simply define this once and just let other commands of the -same form inherit from this. See the docstring of this module -for which object properties are available to use (notably -self.args).

-
- -
-
-get_command_info()[source]
-

This is the default output of func() if no func() overload is done. -Provided here as a separate method so that it can be called for debugging -purposes when making commands.

-
- -
-
-func()[source]
-

This is the actual executing part of the command. It is -called directly after self.parse(). See the docstring of this -module for which object properties are available (beyond those -set in self.parse())

-
- -
-
-get_extra_info(caller, **kwargs)[source]
-

Display some extra information that may help distinguish this -command from others, for instance, in a disambiguity prompt.

-

If this command is a potential match in an ambiguous -situation, one distinguishing feature may be its attachment to -a nearby object, so we include this if available.

-
-
Parameters
-
    -
  • caller (TypedObject) – The caller who typed an ambiguous

  • -
  • handed to the search function. (term) –

  • -
-
-
Returns
-

A string with identifying information to disambiguate the -object, conventionally with a preceding space.

-
-
-
- -
-
-get_help(caller, cmdset)[source]
-

Return the help message for this command and this caller.

-

By default, return self.__doc__ (the docstring just under -the class definition). You can override this behavior, -though, and even customize it depending on the caller, or other -commands the caller can use.

-
-
Parameters
-
    -
  • caller (Object or Account) – the caller asking for help on the command.

  • -
  • cmdset (CmdSet) – the command set (if you need additional commands).

  • -
-
-
Returns
-

docstring (str) – the help text to provide the caller for this command.

-
-
-
- -
-
-client_width()[source]
-

Get the client screenwidth for the session using this command.

-
-
Returns
-

client width (int) – The width (in characters) of the client window.

-
-
-
- -
-
-client_height()[source]
-

Get the client screenheight for the session using this command.

-
-
Returns
-

client height (int) – The height (in characters) of the client window.

-
-
-
- -
-
-styled_table(*args, **kwargs)[source]
-

Create an EvTable styled by on user preferences.

-
-
Parameters
-

*args (str) – Column headers. If not colored explicitly, these will get colors -from user options.

-
-
Keyword Arguments
-

any (str, int or dict) – EvTable options, including, optionally a table dict -detailing the contents of the table.

-
-
Returns
-

table (EvTable)

-
-
An initialized evtable entity, either complete (if using table kwarg)

or incomplete and ready for use with .add_row or .add_collumn.

-
-
-

-
-
-
- -
-
-styled_header(*args, **kwargs)[source]
-

Create a pretty header.

-
- -
-
-styled_separator(*args, **kwargs)[source]
-

Create a separator.

-
- -
- -

Create a pretty footer.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
-
-save_for_next = False
-
- -
- -
-
-exception evennia.commands.command.InterruptCommand[source]
-

Bases: Exception

-

Cleanly interrupt a command.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.account.html b/docs/0.9.5/api/evennia.commands.default.account.html deleted file mode 100644 index b0883f258d..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.account.html +++ /dev/null @@ -1,822 +0,0 @@ - - - - - - - - - evennia.commands.default.account — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.account

-

Account (OOC) commands. These are stored on the Account object -and self.caller is thus always an Account, not an Object/Character.

-

These commands go in the AccountCmdset and are accessible also -when puppeting a Character (although with lower priority)

-

These commands use the account_caller property which tells the command -parent (MuxCommand, usually) to setup caller correctly. They use -self.account to make sure to always use the account object rather than -self.caller (which change depending on the level you are calling from) -The property self.character can be used to access the character when -these commands are triggered with a connected character (such as the -case of the ooc command), it is None if we are OOC.

-

Note that under MULTISESSION_MODE > 2, Account commands should use -self.msg() and similar methods to reroute returns to the correct -method. Otherwise all text will be returned to all connected sessions.

-
-
-class evennia.commands.default.account.CmdOOCLook(**kwargs)[source]
-

Bases: evennia.commands.default.account.MuxAccountLookCommand

-

look while out-of-character

-
-
Usage:

look

-
-
-

Look in the ooc state.

-
-
-key = 'look'
-
- -
-
-aliases = ['l', 'ls']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

implement the ooc look command

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.account.CmdIC(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

control an object you have permission to puppet

-
-
Usage:

ic <character>

-
-
-

Go in-character (IC) as a given Character.

-

This will attempt to “become” a different object assuming you have -the right to do so. Note that it’s the ACCOUNT character that puppets -characters/objects and which needs to have the correct permission!

-

You cannot become an object that is already controlled by another -account. In principle <character> can be any in-game object as long -as you the account have access right to puppet it.

-
-
-key = 'ic'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-aliases = ['puppet']
-
- -
-
-help_category = 'general'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Main puppet method

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.account.CmdOOC(**kwargs)[source]
-

Bases: evennia.commands.default.account.MuxAccountLookCommand

-

stop puppeting and go ooc

-
-
Usage:

ooc

-
-
-

Go out-of-character (OOC).

-

This will leave your current character and put you in a incorporeal OOC state.

-
-
-key = 'ooc'
-
- -
-
-locks = 'cmd:pperm(Player)'
-
- -
-
-aliases = ['unpuppet']
-
- -
-
-help_category = 'general'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implement function

-
- -
-
-lock_storage = 'cmd:pperm(Player)'
-
- -
- -
-
-class evennia.commands.default.account.CmdPassword(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

change your password

-
-
Usage:

password <old password> = <new password>

-
-
-

Changes your password. Make sure to pick a safe one.

-
-
-key = 'password'
-
- -
-
-locks = 'cmd:pperm(Player)'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

hook function.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:pperm(Player)'
-
- -
- -
-
-class evennia.commands.default.account.CmdQuit(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

quit the game

-
-
Usage:

quit

-
-
Switch:

all - disconnect all connected sessions

-
-
-

Gracefully disconnect your current session from the -game. Use the /all switch to disconnect from all sessions.

-
-
-key = 'quit'
-
- -
-
-switch_options = ('all',)
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

hook function

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.account.CmdCharCreate(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

create a new character

-
-
Usage:

charcreate <charname> [= desc]

-
-
-

Create a new character, optionally giving it a description. You -may use upper-case letters in the name - you will nevertheless -always be able to access your character using lower-case letters -if you want.

-
-
-key = 'charcreate'
-
- -
-
-locks = 'cmd:pperm(Player)'
-
- -
-
-help_category = 'general'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

create the new character

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:pperm(Player)'
-
- -
- -
-
-class evennia.commands.default.account.CmdOption(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Set an account option

-
-
Usage:

option[/save] [name = value]

-
-
Switches:

save - Save the current option settings for future logins. -clear - Clear the saved options.

-
-
-

This command allows for viewing and setting client interface -settings. Note that saved options may not be able to be used if -later connecting with a client with different capabilities.

-
-
-key = 'option'
-
- -
-
-aliases = ['options']
-
- -
-
-switch_options = ('save', 'clear')
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implements the command

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.account.CmdSessions(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

check your connected session(s)

-
-
Usage:

sessions

-
-
-

Lists the sessions currently connected to your account.

-
-
-key = 'sessions'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implement function

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.account.CmdWho(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

list who is currently online

-
-
Usage:

who -doing

-
-
-

Shows who is currently online. Doing is an alias that limits info -also for those with all permissions.

-
-
-key = 'who'
-
- -
-
-aliases = ['doing']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Get all connected accounts by polling session.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.account.CmdColorTest(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

testing which colors your client support

-
-
Usage:

color ansi||xterm256

-
-
-

Prints a color map along with in-mud color codes to use to produce -them. It also tests what is supported in your client. Choices are -16-color ansi (supported in most muds) or the 256-color xterm256 -standard. No checking is done to determine your client supports -color - if not you will see rubbish appear.

-
-
-key = 'color'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-account_caller = True
-
- -
-
-slice_bright_fg = slice(7, 15, None)
-
- -
-
-slice_dark_fg = slice(15, 23, None)
-
- -
-
-slice_dark_bg = slice(-8, None, None)
-
- -
-
-slice_bright_bg = slice(None, None, None)
-
- -
-
-table_format(table)[source]
-

Helper method to format the ansi/xterm256 tables. -Takes a table of columns [[val,val,…],[val,val,…],…]

-
- -
-
-func()[source]
-

Show color tables

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.account.CmdQuell(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

use character’s permissions instead of account’s

-
-
Usage:

quell -unquell

-
-
-

Normally the permission level of the Account is used when puppeting a -Character/Object to determine access. This command will switch the lock -system to make use of the puppeted Object’s permissions instead. This is -useful mainly for testing. -Hierarchical permission quelling only work downwards, thus an Account cannot -use a higher-permission Character to escalate their permission level. -Use the unquell command to revert back to normal operation.

-
-
-key = 'quell'
-
- -
-
-aliases = ['unquell']
-
- -
-
-locks = 'cmd:pperm(Player)'
-
- -
-
-help_category = 'general'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Perform the command

-
- -
-
-lock_storage = 'cmd:pperm(Player)'
-
- -
- -
-
-class evennia.commands.default.account.CmdCharDelete(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

delete a character - this cannot be undone!

-
-
Usage:

chardelete <charname>

-
-
-

Permanently deletes one of your characters.

-
-
-key = 'chardelete'
-
- -
-
-locks = 'cmd:pperm(Player)'
-
- -
-
-help_category = 'general'
-
- -
-
-func()[source]
-

delete the character

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:pperm(Player)'
-
- -
- -
-
-class evennia.commands.default.account.CmdStyle(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

In-game style options

-
-
Usage:

style -style <option> = <value>

-
-
-

Configure stylings for in-game display elements like table borders, help -entriest etc. Use without arguments to see all available options.

-
-
-key = 'style'
-
- -
-
-switch_options = ['clear']
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-list_styles()[source]
-
- -
-
-set()[source]
-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.admin.html b/docs/0.9.5/api/evennia.commands.default.admin.html deleted file mode 100644 index fc745748e6..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.admin.html +++ /dev/null @@ -1,532 +0,0 @@ - - - - - - - - - evennia.commands.default.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.admin

-

Admin commands

-
-
-class evennia.commands.default.admin.CmdBoot(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

kick an account from the server.

-
-
Usage

boot[/switches] <account obj> [: reason]

-
-
Switches:

quiet - Silently boot without informing account -sid - boot by session id instead of name or dbref

-
-
-

Boot an account object from the server. If a reason is -supplied it will be echoed to the user unless /quiet is set.

-
-
-key = 'boot'
-
- -
-
-switch_options = ('quiet', 'sid')
-
- -
-
-locks = 'cmd:perm(boot) or perm(Admin)'
-
- -
-
-help_category = 'admin'
-
- -
-
-func()[source]
-

Implementing the function

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(boot) or perm(Admin)'
-
- -
- -
-
-class evennia.commands.default.admin.CmdBan(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

ban an account from the server

-
-
Usage:

ban [<name or ip> [: reason]]

-
-
-

Without any arguments, shows numbered list of active bans.

-

This command bans a user from accessing the game. Supply an optional -reason to be able to later remember why the ban was put in place.

-

It is often preferable to ban an account from the server than to -delete an account with accounts/delete. If banned by name, that account -account can no longer be logged into.

-

IP (Internet Protocol) address banning allows blocking all access -from a specific address or subnet. Use an asterisk (*) as a -wildcard.

-

Examples

-

ban thomas - ban account ‘thomas’ -ban/ip 134.233.2.111 - ban specific ip address -ban/ip 134.233.2.* - ban all in a subnet -ban/ip 134.233.*.* - even wider ban

-

A single IP filter can be easy to circumvent by changing computers -or requesting a new IP address. Setting a wide IP block filter with -wildcards might be tempting, but remember that it may also -accidentally block innocent users connecting from the same country -or region.

-
-
-key = 'ban'
-
- -
-
-aliases = ['bans']
-
- -
-
-locks = 'cmd:perm(ban) or perm(Developer)'
-
- -
-
-help_category = 'admin'
-
- -
-
-func()[source]
-

Bans are stored in a serverconf db object as a list of -dictionaries:

-
-
-
[ (name, ip, ipregex, date, reason),

(name, ip, ipregex, date, reason),… ]

-
-
-
-

where name and ip are set by the user and are shown in -lists. ipregex is a converted form of ip where the * is -replaced by an appropriate regex pattern for fast -matching. date is the time stamp the ban was instigated and -‘reason’ is any optional info given to the command. Unset -values in each tuple is set to the empty string.

-
- -
-
-lock_storage = 'cmd:perm(ban) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.admin.CmdUnban(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

remove a ban from an account

-
-
Usage:

unban <banid>

-
-
-

This will clear an account name/ip ban previously set with the ban -command. Use this command without an argument to view a numbered -list of bans. Use the numbers in this list to select which one to -unban.

-
-
-key = 'unban'
-
- -
-
-locks = 'cmd:perm(unban) or perm(Developer)'
-
- -
-
-help_category = 'admin'
-
- -
-
-func()[source]
-

Implement unbanning

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(unban) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.admin.CmdEmit(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

admin command for emitting message to multiple objects

-
-
Usage:

emit[/switches] [<obj>, <obj>, … =] <message> -remit [<obj>, <obj>, … =] <message> -pemit [<obj>, <obj>, … =] <message>

-
-
Switches:

room - limit emits to rooms only (default) -accounts - limit emits to accounts only -contents - send to the contents of matched objects too

-
-
-

Emits a message to the selected objects or to -your immediate surroundings. If the object is a room, -send to its contents. remit and pemit are just -limited forms of emit, for sending to rooms and -to accounts respectively.

-
-
-key = 'emit'
-
- -
-
-aliases = ['remit', 'pemit']
-
- -
-
-switch_options = ('room', 'accounts', 'contents')
-
- -
-
-locks = 'cmd:perm(emit) or perm(Builder)'
-
- -
-
-help_category = 'admin'
-
- -
-
-func()[source]
-

Implement the command

-
- -
-
-lock_storage = 'cmd:perm(emit) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.admin.CmdNewPassword(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

change the password of an account

-
-
Usage:

userpassword <user obj> = <new password>

-
-
-

Set an account’s password.

-
-
-key = 'userpassword'
-
- -
-
-locks = 'cmd:perm(newpassword) or perm(Admin)'
-
- -
-
-help_category = 'admin'
-
- -
-
-func()[source]
-

Implement the function.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(newpassword) or perm(Admin)'
-
- -
- -
-
-class evennia.commands.default.admin.CmdPerm(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

set the permissions of an account/object

-
-
Usage:

perm[/switch] <object> [= <permission>[,<permission>,…]] -perm[/switch] *<account> [= <permission>[,<permission>,…]]

-
-
Switches:

del - delete the given permission from <object> or <account>. -account - set permission on an account (same as adding * to name)

-
-
-

This command sets/clears individual permission strings on an object -or account. If no permission is given, list all permissions on <object>.

-
-
-key = 'perm'
-
- -
-
-aliases = ['setperm']
-
- -
-
-switch_options = ('del', 'account')
-
- -
-
-locks = 'cmd:perm(perm) or perm(Developer)'
-
- -
-
-help_category = 'admin'
-
- -
-
-func()[source]
-

Implement function

-
- -
-
-lock_storage = 'cmd:perm(perm) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.admin.CmdWall(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

make an announcement to all

-
-
Usage:

wall <message>

-
-
-

Announces a message to all connected sessions -including all currently unlogged in.

-
-
-key = 'wall'
-
- -
-
-locks = 'cmd:perm(wall) or perm(Admin)'
-
- -
-
-help_category = 'admin'
-
- -
-
-func()[source]
-

Implements command

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(wall) or perm(Admin)'
-
- -
- -
-
-class evennia.commands.default.admin.CmdForce(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

forces an object to execute a command

-
-
Usage:

force <object>=<command string>

-
-
-

Example

-

force bob=get stick

-
-
-key = 'force'
-
- -
-
-locks = 'cmd:perm(spawn) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-perm_used = 'edit'
-
- -
-
-func()[source]
-

Implements the force command

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(spawn) or perm(Builder)'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.batchprocess.html b/docs/0.9.5/api/evennia.commands.default.batchprocess.html deleted file mode 100644 index eddc3b8e06..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.batchprocess.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - - - - evennia.commands.default.batchprocess — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.batchprocess

-

Batch processors

-

These commands implements the ‘batch-command’ and ‘batch-code’ -processors, using the functionality in evennia.utils.batchprocessors. -They allow for offline world-building.

-

Batch-command is the simpler system. This reads a file (*.ev) -containing a list of in-game commands and executes them in sequence as -if they had been entered in the game (including permission checks -etc).

-

Batch-code is a full-fledged python code interpreter that reads blocks -of python code (*.py) and executes them in sequence. This allows for -much more power than Batch-command, but requires knowing Python and -the Evennia API. It is also a severe security risk and should -therefore always be limited to superusers only.

-
-
-class evennia.commands.default.batchprocess.CmdBatchCommands(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

build from batch-command file

-
-
Usage:

batchcommands[/interactive] <python.path.to.file>

-
-
Switch:
-
interactive - this mode will offer more control when

executing the batch file, like stepping, -skipping, reloading etc.

-
-
-
-
-

Runs batches of commands from a batch-cmd text file (*.ev).

-
-
-key = 'batchcommands'
-
- -
-
-aliases = ['batchcommand', 'batchcmd']
-
- -
-
-switch_options = ('interactive',)
-
- -
-
-locks = 'cmd:perm(batchcommands) or perm(Developer)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Starts the processor.

-
- -
-
-lock_storage = 'cmd:perm(batchcommands) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.batchprocess.CmdBatchCode(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

build from batch-code file

-
-
Usage:

batchcode[/interactive] <python path to file>

-
-
Switch:
-
interactive - this mode will offer more control when

executing the batch file, like stepping, -skipping, reloading etc.

-
-
debug - auto-delete all objects that has been marked as

deletable in the script file (see example files for -syntax). This is useful so as to to not leave multiple -object copies behind when testing out the script.

-
-
-
-
-

Runs batches of commands from a batch-code text file (*.py).

-
-
-key = 'batchcode'
-
- -
-
-aliases = ['batchcodes']
-
- -
-
-switch_options = ('interactive', 'debug')
-
- -
-
-locks = 'cmd:superuser()'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Starts the processor.

-
- -
-
-lock_storage = 'cmd:superuser()'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.building.html b/docs/0.9.5/api/evennia.commands.default.building.html deleted file mode 100644 index 0b690b7999..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.building.html +++ /dev/null @@ -1,1885 +0,0 @@ - - - - - - - - - evennia.commands.default.building — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.building

-

Building and world design commands

-
-
-class evennia.commands.default.building.ObjManipCommand(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

This is a parent class for some of the defining objmanip commands -since they tend to have some more variables to define new objects.

-

Each object definition can have several components. First is -always a name, followed by an optional alias list and finally an -some optional data, such as a typeclass or a location. A comma ‘,’ -separates different objects. Like this:

-
-

name1;alias;alias;alias:option, name2;alias;alias …

-
-

Spaces between all components are stripped.

-

A second situation is attribute manipulation. Such commands -are simpler and offer combinations

-
-

objname/attr/attr/attr, objname/attr, …

-
-
-
-parse()[source]
-

We need to expand the default parsing to get all -the cases, see the module doc.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'command'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.commands.default.building.CmdSetObjAlias(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

adding permanent aliases for object

-
-
Usage:

alias <obj> [= [alias[,alias,alias,…]]] -alias <obj> = -alias/category <obj> = [alias[,alias,…]:<category>

-
-
Switches:
-
category - requires ending input with :category, to store the

given aliases with the given category.

-
-
-
-
-

Assigns aliases to an object so it can be referenced by more -than one name. Assign empty to remove all aliases from object. If -assigning a category, all aliases given will be using this category.

-

Observe that this is not the same thing as personal aliases -created with the ‘nick’ command! Aliases set with alias are -changing the object in question, making those aliases usable -by everyone.

-
-
-key = 'alias'
-
- -
-
-aliases = ['setobjalias']
-
- -
-
-switch_options = ('category',)
-
- -
-
-locks = 'cmd:perm(setobjalias) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Set the aliases.

-
- -
-
-lock_storage = 'cmd:perm(setobjalias) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdCopy(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

copy an object and its properties

-
-
Usage:

copy <original obj> [= <new_name>][;alias;alias..] -[:<new_location>] [,<new_name2> …]

-
-
-

Create one or more copies of an object. If you don’t supply any targets, -one exact copy of the original object will be created with the name *_copy.

-
-
-key = 'copy'
-
- -
-
-locks = 'cmd:perm(copy) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Uses ObjManipCommand.parse()

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(copy) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdCpAttr(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

copy attributes between objects

-
-
Usage:

cpattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,…] -cpattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,…] -cpattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,…] -cpattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,…]

-
-
Switches:

move - delete the attribute from the source object after copying.

-
-
-

Example

-

cpattr coolness = Anna/chillout, Anna/nicety, Tom/nicety --> -copies the coolness attribute (defined on yourself), to attributes -on Anna and Tom.

-

Copy the attribute one object to one or more attributes on another object. -If you don’t supply a source object, yourself is used.

-
-
-key = 'cpattr'
-
- -
-
-switch_options = ('move',)
-
- -
-
-locks = 'cmd:perm(cpattr) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-check_from_attr(obj, attr, clear=False)[source]
-

Hook for overriding on subclassed commands. Checks to make sure a -caller can copy the attr from the object in question. If not, return a -false value and the command will abort. An error message should be -provided by this function.

-

If clear is True, user is attempting to move the attribute.

-
- -
-
-check_to_attr(obj, attr)[source]
-

Hook for overriding on subclassed commands. Checks to make sure a -caller can write to the specified attribute on the specified object. -If not, return a false value and the attribute will be skipped. An -error message should be provided by this function.

-
- -
-
-check_has_attr(obj, attr)[source]
-

Hook for overriding on subclassed commands. Do any preprocessing -required and verify an object has an attribute.

-
- -
-
-get_attr(obj, attr)[source]
-

Hook for overriding on subclassed commands. Do any preprocessing -required and get the attribute from the object.

-
- -
-
-func()[source]
-

Do the copying.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(cpattr) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdMvAttr(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

move attributes between objects

-
-
Usage:

mvattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,…] -mvattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,…] -mvattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,…] -mvattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,…]

-
-
Switches:

copy - Don’t delete the original after moving.

-
-
-

Move an attribute from one object to one or more attributes on another -object. If you don’t supply a source object, yourself is used.

-
-
-key = 'mvattr'
-
- -
-
-switch_options = ('copy',)
-
- -
-
-locks = 'cmd:perm(mvattr) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Do the moving

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(mvattr) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdCreate(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

create new objects

-
-
Usage:

create[/drop] <objname>[;alias;alias…][:typeclass], <objname>…

-
-
switch:
-
drop - automatically drop the new object into your current

location (this is not echoed). This also sets the new -object’s home to the current location rather than to you.

-
-
-
-
-

Creates one or more new objects. If typeclass is given, the object -is created as a child of this typeclass. The typeclass script is -assumed to be located under types/ and any further -directory structure is given in Python notation. So if you have a -correct typeclass ‘RedButton’ defined in -types/examples/red_button.py, you could create a new -object of this type like this:

-
-

create/drop button;red : examples.red_button.RedButton

-
-
-
-key = 'create'
-
- -
-
-switch_options = ('drop',)
-
- -
-
-locks = 'cmd:perm(create) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-new_obj_lockstring = 'control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)'
-
- -
-
-func()[source]
-

Creates the object.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(create) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdDesc(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

describe an object or the current room.

-
-
Usage:

desc [<obj> =] <description>

-
-
Switches:

edit - Open up a line editor for more advanced editing.

-
-
-

Sets the “desc” attribute on an object. If an object is not given, -describe the current room.

-
-
-key = 'desc'
-
- -
-
-aliases = ['describe']
-
- -
-
-switch_options = ('edit',)
-
- -
-
-locks = 'cmd:perm(desc) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-edit_handler()[source]
-
- -
-
-func()[source]
-

Define command

-
- -
-
-lock_storage = 'cmd:perm(desc) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdDestroy(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

permanently delete objects

-
-
Usage:

destroy[/switches] [obj, obj2, obj3, [dbref-dbref], …]

-
-
Switches:
-
override - The destroy command will usually avoid accidentally

destroying account objects. This switch overrides this safety.

-
-
-

force - destroy without confirmation.

-
-
-

Examples

-

destroy house, roof, door, 44-78 -destroy 5-10, flower, 45 -destroy/force north

-

Destroys one or many objects. If dbrefs are used, a range to delete can be -given, e.g. 4-10. Also the end points will be deleted. This command -displays a confirmation before destroying, to make sure of your choice. -You can specify the /force switch to bypass this confirmation.

-
-
-key = 'destroy'
-
- -
-
-aliases = ['delete', 'del']
-
- -
-
-switch_options = ('override', 'force')
-
- -
-
-locks = 'cmd:perm(destroy) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-confirm = True
-
- -
-
-default_confirm = 'yes'
-
- -
-
-func()[source]
-

Implements the command.

-
- -
-
-lock_storage = 'cmd:perm(destroy) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdDig(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

build new rooms and connect them to the current location

-
-
Usage:
-
dig[/switches] <roomname>[;alias;alias…][:typeclass]
-
[= <exit_to_there>[;alias][:typeclass]]

[, <exit_to_here>[;alias][:typeclass]]

-
-
-
-
-
-
Switches:

tel or teleport - move yourself to the new room

-
-
-

Examples

-

dig kitchen = north;n, south;s -dig house:myrooms.MyHouseTypeclass -dig sheer cliff;cliff;sheer = climb up, climb down

-

This command is a convenient way to build rooms quickly; it creates the -new room and you can optionally set up exits back and forth between your -current room and the new one. You can add as many aliases as you -like to the name of the room and the exits in question; an example -would be ‘north;no;n’.

-
-
-key = 'dig'
-
- -
-
-switch_options = ('teleport',)
-
- -
-
-locks = 'cmd:perm(dig) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-new_room_lockstring = 'control:id({id}) or perm(Admin); delete:id({id}) or perm(Admin); edit:id({id}) or perm(Admin)'
-
- -
-
-func()[source]
-

Do the digging. Inherits variables from ObjManipCommand.parse()

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(dig) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdTunnel(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

create new rooms in cardinal directions only

-
-
Usage:

tunnel[/switch] <direction>[:typeclass] [= <roomname>[;alias;alias;…][:typeclass]]

-
-
Switches:

oneway - do not create an exit back to the current location -tel - teleport to the newly created room

-
-
-

Example

-

tunnel n -tunnel n = house;mike’s place;green building

-
-
This is a simple way to build using pre-defined directions:

|wn,ne,e,se,s,sw,w,nw|n (north, northeast etc) -|wu,d|n (up and down) -|wi,o|n (in and out)

-
-
-

The full names (north, in, southwest, etc) will always be put as -main name for the exit, using the abbreviation as an alias (so an -exit will always be able to be used with both “north” as well as -“n” for example). Opposite directions will automatically be -created back from the new room unless the /oneway switch is given. -For more flexibility and power in creating rooms, use dig.

-
-
-key = 'tunnel'
-
- -
-
-aliases = ['tun']
-
- -
-
-switch_options = ('oneway', 'tel')
-
- -
-
-locks = 'cmd: perm(tunnel) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-directions = {'d': ('down', 'u'), 'e': ('east', 'w'), 'i': ('in', 'o'), 'n': ('north', 's'), 'ne': ('northeast', 'sw'), 'nw': ('northwest', 'se'), 'o': ('out', 'i'), 's': ('south', 'n'), 'se': ('southeast', 'nw'), 'sw': ('southwest', 'ne'), 'u': ('up', 'd'), 'w': ('west', 'e')}
-
- -
-
-func()[source]
-

Implements the tunnel command

-
- -
-
-lock_storage = 'cmd: perm(tunnel) or perm(Builder)'
-
- -
- -
- -

Bases: evennia.commands.default.muxcommand.MuxCommand

-

link existing rooms together with exits

-
-
Usage:

link[/switches] <object> = <target> -link[/switches] <object> = -link[/switches] <object>

-
-
Switch:
-
twoway - connect two exits. For this to work, BOTH <object>

and <target> must be exit objects.

-
-
-
-
-

If <object> is an exit, set its destination to <target>. Two-way operation -instead sets the destination to the locations of the respective given -arguments. -The second form (a lone =) sets the destination to None (same as -the unlink command) and the third form (without =) just shows the -currently set destination.

-
-
-key = 'link'
-
- -
-
-locks = 'cmd:perm(link) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Perform the link

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(link) or perm(Builder)'
-
- -
- -
- -

Bases: evennia.commands.default.building.CmdLink

-

remove exit-connections between rooms

-
-
Usage:

unlink <Object>

-
-
-

Unlinks an object, for example an exit, disconnecting -it from whatever it was connected to.

-
-
-key = 'unlink'
-
- -
-
-locks = 'cmd:perm(unlink) or perm(Builder)'
-
- -
-
-help_key = 'Building'
-
- -
-
-func()[source]
-

All we need to do here is to set the right command -and call func in CmdLink

-
- -
-
-aliases = []
-
- -
-
-help_category = 'building'
-
- -
-
-lock_storage = 'cmd:perm(unlink) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdSetHome(**kwargs)[source]
-

Bases: evennia.commands.default.building.CmdLink

-

set an object’s home location

-
-
Usage:

sethome <obj> [= <home_location>] -sethom <obj>

-
-
-

The “home” location is a “safety” location for objects; they -will be moved there if their current location ceases to exist. All -objects should always have a home location for this reason. -It is also a convenient target of the “home” command.

-

If no location is given, just view the object’s home location.

-
-
-key = 'sethome'
-
- -
-
-locks = 'cmd:perm(sethome) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

implement the command

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(sethome) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdListCmdSets(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

list command sets defined on an object

-
-
Usage:

cmdsets <obj>

-
-
-

This displays all cmdsets assigned -to a user. Defaults to yourself.

-
-
-key = 'cmdsets'
-
- -
-
-aliases = ['listcmsets']
-
- -
-
-locks = 'cmd:perm(listcmdsets) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

list the cmdsets

-
- -
-
-lock_storage = 'cmd:perm(listcmdsets) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdName(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

change the name and/or aliases of an object

-
-
Usage:

name <obj> = <newname>;alias1;alias2

-
-
-

Rename an object to something new. Use *obj to -rename an account.

-
-
-key = 'name'
-
- -
-
-aliases = ['rename']
-
- -
-
-locks = 'cmd:perm(rename) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

change the name

-
- -
-
-lock_storage = 'cmd:perm(rename) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdOpen(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

open a new exit from the current room

-
-
Usage:

open <new exit>[;alias;alias..][:typeclass] [,<return exit>[;alias;..][:typeclass]]] = <destination>

-
-
-

Handles the creation of exits. If a destination is given, the exit -will point there. The <return exit> argument sets up an exit at the -destination leading back to the current room. Destination name -can be given both as a #dbref and a name, if that name is globally -unique.

-
-
-key = 'open'
-
- -
-
-locks = 'cmd:perm(open) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-new_obj_lockstring = 'control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)'
-
- -
-
-create_exit(exit_name, location, destination, exit_aliases=None, typeclass=None)[source]
-

Helper function to avoid code duplication. -At this point we know destination is a valid location

-
- -
-
-func()[source]
-

This is where the processing starts. -Uses the ObjManipCommand.parser() for pre-processing -as well as the self.create_exit() method.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(open) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdSetAttribute(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

set attribute on an object or account

-
-
Usage:

set <obj>/<attr> = <value> -set <obj>/<attr> = -set <obj>/<attr> -set *<account>/<attr> = <value>

-
-
Switch:

edit: Open the line editor (string values only) -script: If we’re trying to set an attribute on a script -channel: If we’re trying to set an attribute on a channel -account: If we’re trying to set an attribute on an account -room: Setting an attribute on a room (global search) -exit: Setting an attribute on an exit (global search) -char: Setting an attribute on a character (global search) -character: Alias for char, as above.

-
-
-

Sets attributes on objects. The second example form above clears a -previously set attribute while the third form inspects the current value of -the attribute (if any). The last one (with the star) is a shortcut for -operating on a player Account rather than an Object.

-

The most common data to save with this command are strings and -numbers. You can however also set Python primitives such as lists, -dictionaries and tuples on objects (this might be important for -the functionality of certain custom objects). This is indicated -by you starting your value with one of |c’|n, |c”|n, |c(|n, |c[|n -or |c{ |n.

-

Once you have stored a Python primitive as noted above, you can include -|c[<key>]|n in <attr> to reference nested values in e.g. a list or dict.

-

Remember that if you use Python primitives like this, you must -write proper Python syntax too - notably you must include quotes -around your strings or you will get an error.

-
-
-key = 'set'
-
- -
-
-locks = 'cmd:perm(set) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-nested_re = re.compile('\\[.*?\\]')
-
- -
-
-not_found = <object object>
-
- -
-
-check_obj(obj)[source]
-

This may be overridden by subclasses in case restrictions need to be -placed on whether certain objects can have attributes set by certain -accounts.

-

This function is expected to display its own error message.

-

Returning False will abort the command.

-
- -
-
-check_attr(obj, attr_name)[source]
-

This may be overridden by subclasses in case restrictions need to be -placed on what attributes can be set by who beyond the normal lock.

-

This functions is expected to display its own error message. It is -run once for every attribute that is checked, blocking only those -attributes which are not permitted and letting the others through.

-
- -
-
-split_nested_attr(attr)[source]
-

Yields tuples of (possible attr name, nested keys on that attr). -For performance, this is biased to the deepest match, but allows compatability -with older attrs that might have been named with []’s.

-

> list(split_nested_attr(“nested[‘asdf’][0]”)) -[

-
-

(‘nested’, [‘asdf’, 0]), -(“nested[‘asdf’]”, [0]), -(“nested[‘asdf’][0]”, []),

-
-

]

-
- -
-
-do_nested_lookup(value, *keys)[source]
-
- -
-
-view_attr(obj, attr)[source]
-

Look up the value of an attribute and return a string displaying it.

-
- -
-
-rm_attr(obj, attr)[source]
-

Remove an attribute from the object, or a nested data structure, and report back.

-
- -
-
-set_attr(obj, attr, value)[source]
-
- -
-
-edit_handler(obj, attr)[source]
-

Activate the line editor

-
- -
-
-search_for_obj(objname)[source]
-

Searches for an object matching objname. The object may be of different typeclasses. -:param objname: Name of the object we’re looking for

-
-
Returns
-

A typeclassed object, or None if nothing is found.

-
-
-
- -
-
-func()[source]
-

Implement the set attribute - a limited form of py.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(set) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdTypeclass(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

set or change an object’s typeclass

-
-
Usage:

typeclass[/switch] <object> [= typeclass.path] -typeclass/prototype <object> = prototype_key

-

typeclass/list/show [typeclass.path] -swap - this is a shorthand for using /force/reset flags. -update - this is a shorthand for using the /force/reload flag.

-
-
Switch:
-
show, examine - display the current typeclass of object (default) or, if

given a typeclass path, show the docstring of that typeclass.

-
-
update - only re-run at_object_creation on this object

meaning locks or other properties set later may remain.

-
-
reset - clean out all the attributes and properties on the

object - basically making this a new clean object. This will -also reset cmdsets.

-
-
force - change to the typeclass also if the object

already has a typeclass of the same name.

-
-
list - show available typeclasses. Only typeclasses in modules actually

imported or used from somewhere in the code will show up here -(those typeclasses are still available if you know the path)

-
-
prototype - clean and overwrite the object with the specified

prototype key - effectively making a whole new object.

-
-
-
-
-

Example

-

type button = examples.red_button.RedButton -type/prototype button=a red button

-

If the typeclass_path is not given, the current object’s typeclass is -assumed.

-

View or set an object’s typeclass. If setting, the creation hooks of the -new typeclass will be run on the object. If you have clashing properties on -the old class, use /reset. By default you are protected from changing to a -typeclass of the same name as the one you already have - use /force to -override this protection.

-

The given typeclass must be identified by its location using python -dot-notation pointing to the correct module and class. If no typeclass is -given (or a wrong typeclass is given). Errors in the path or new typeclass -will lead to the old typeclass being kept. The location of the typeclass -module is searched from the default typeclass directory, as defined in the -server settings.

-
-
-key = 'typeclass'
-
- -
-
-aliases = ['swap', 'type', 'parent', 'update']
-
- -
-
-switch_options = ('show', 'examine', 'update', 'reset', 'force', 'list', 'prototype')
-
- -
-
-locks = 'cmd:perm(typeclass) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Implements command

-
- -
-
-lock_storage = 'cmd:perm(typeclass) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdWipe(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

clear all attributes from an object

-
-
Usage:

wipe <object>[/<attr>[/<attr>…]]

-
-
-

Example

-

wipe box -wipe box/colour

-

Wipes all of an object’s attributes, or optionally only those -matching the given attribute-wildcard search string.

-
-
-key = 'wipe'
-
- -
-
-locks = 'cmd:perm(wipe) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

inp is the dict produced in ObjManipCommand.parse()

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(wipe) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdLock(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

assign a lock definition to an object

-
-
Usage:

lock <object or *account>[ = <lockstring>] -or -lock[/switch] <object or *account>/<access_type>

-
-
Switch:

del - delete given access type -view - view lock associated with given access type (default)

-
-
-

If no lockstring is given, shows all locks on -object.

-
-
Lockstring is of the form

access_type:[NOT] func1(args)[ AND|OR][ NOT] func2(args) …]

-
-
-

Where func1, func2 … valid lockfuncs with or without arguments. -Separator expressions need not be capitalized.

-
-
For example:

‘get: id(25) or perm(Admin)’

-
-
-

The ‘get’ lock access_type is checked e.g. by the ‘get’ command. -An object locked with this example lock will only be possible to pick up -by Admins or by an object with id=25.

-

You can add several access_types after one another by separating -them by ‘;’, i.e:

-
-

‘get:id(25); delete:perm(Builder)’

-
-
-
-key = 'lock'
-
- -
-
-aliases = ['locks']
-
- -
-
-locks = 'cmd: perm(locks) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Sets up the command

-
- -
-
-lock_storage = 'cmd: perm(locks) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdExamine(**kwargs)[source]
-

Bases: evennia.commands.default.building.ObjManipCommand

-

get detailed information about an object

-
-
Usage:

examine [<object>[/attrname]] -examine [*<account>[/attrname]]

-
-
Switch:

account - examine an Account (same as adding *) -object - examine an Object (useful when OOC)

-
-
-

The examine command shows detailed game info about an -object and optionally a specific attribute on it. -If object is not specified, the current location is examined.

-

Append a * before the search string to examine an account.

-
-
-key = 'examine'
-
- -
-
-aliases = ['exam', 'ex']
-
- -
-
-locks = 'cmd:perm(examine) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-arg_regex = re.compile('(/\\w+?(\\s|$))|\\s|$', re.IGNORECASE)
-
- -
-
-account_mode = False
-
- -
-
-detail_color = '|c'
-
- -
-
-header_color = '|w'
-
- -
-
-quell_color = '|r'
-
- -
-
-separator = '-'
-
- -
-
-list_attribute(crop, attr, category, value)[source]
-

Formats a single attribute line.

-
-
Parameters
-
    -
  • crop (bool) – If output should be cropped if too long.

  • -
  • attr (str) – Attribute key.

  • -
  • category (str) – Attribute category.

  • -
  • value (any) – Attribute value.

  • -
-
-
-

Returns:

-
- -
-
-format_attributes(obj, attrname=None, crop=True)[source]
-

Helper function that returns info about attributes and/or -non-persistent data stored on object

-
- -
-
-format_output(obj, current_cmdset)[source]
-

Helper function that creates a nice report about an object.

-
-
Parameters
-
    -
  • obj (any) – Object to analyze.

  • -
  • current_cmdset (CmdSet) – Current cmdset for object.

  • -
-
-
Returns
-

str – The formatted string.

-
-
-
- -
-
-func()[source]
-

Process command

-
- -
-
-lock_storage = 'cmd:perm(examine) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdFind(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

search the database for objects

-
-
Usage:

find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]] -locate - this is a shorthand for using the /loc switch.

-
-
Switches:

room - only look for rooms (location=None) -exit - only look for exits (destination!=None) -char - only look for characters (BASE_CHARACTER_TYPECLASS) -exact - only exact matches are returned. -loc - display object location if exists and match has one result -startswith - search for names starting with the string, rather than containing

-
-
-

Searches the database for an object of a particular name or exact #dbref. -Use *accountname to search for an account. The switches allows for -limiting object matches to certain game entities. Dbrefmin and dbrefmax -limits matches to within the given dbrefs range, or above/below if only -one is given.

-
-
-key = 'find'
-
- -
-
-aliases = ['search', 'locate']
-
- -
-
-switch_options = ('room', 'exit', 'char', 'exact', 'loc', 'startswith')
-
- -
-
-locks = 'cmd:perm(find) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Search functionality

-
- -
-
-lock_storage = 'cmd:perm(find) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdTeleport(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

teleport object to another location

-
-
Usage:

tel/switch [<object> to||=] <target location>

-
-
-

Examples

-

tel Limbo -tel/quiet box = Limbo -tel/tonone box

-
-
Switches:
-
quiet - don’t echo leave/arrive messages to the source/target

locations for the move.

-
-
intoexit - if target is an exit, teleport INTO

the exit object instead of to its destination

-
-
tonone - if set, teleport the object to a None-location. If this

switch is set, <target location> is ignored. -Note that the only way to retrieve -an object from a None location is by direct #dbref -reference. A puppeted object cannot be moved to None.

-
-
-

loc - teleport object to the target’s location instead of its contents

-
-
-

Teleports an object somewhere. If no object is given, you yourself are -teleported to the target location.

-
-
-key = 'tel'
-
- -
-
-aliases = ['teleport']
-
- -
-
-switch_options = ('quiet', 'intoexit', 'tonone', 'loc')
-
- -
-
-rhs_split = ('=', ' to ')
-
- -
-
-locks = 'cmd:perm(teleport) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Performs the teleport

-
- -
-
-lock_storage = 'cmd:perm(teleport) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdScript(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

attach a script to an object

-
-
Usage:

script[/switch] <obj> [= script_path or <scriptkey>]

-
-
Switches:

start - start all non-running scripts on object, or a given script only -stop - stop all scripts on objects, or a given script only

-
-
-

If no script path/key is given, lists all scripts active on the given -object. -Script path can be given from the base location for scripts as given in -settings. If adding a new script, it will be started automatically -(no /start switch is needed). Using the /start or /stop switches on an -object without specifying a script key/path will start/stop ALL scripts on -the object.

-
-
-key = 'script'
-
- -
-
-aliases = ['addscript']
-
- -
-
-switch_options = ('start', 'stop')
-
- -
-
-locks = 'cmd:perm(script) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Do stuff

-
- -
-
-lock_storage = 'cmd:perm(script) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdTag(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

handles the tags of an object

-
-
Usage:

tag[/del] <obj> [= <tag>[:<category>]] -tag/search <tag>[:<category]

-
-
Switches:

search - return all objects with a given Tag -del - remove the given tag. If no tag is specified,

-
-

clear all tags on object.

-
-
-
-

Manipulates and lists tags on objects. Tags allow for quick -grouping of and searching for objects. If only <obj> is given, -list all tags on the object. If /search is used, list objects -with the given tag. -The category can be used for grouping tags themselves, but it -should be used with restrain - tags on their own are usually -enough to for most grouping schemes.

-
-
-key = 'tag'
-
- -
-
-aliases = ['tags']
-
- -
-
-options = ('search', 'del')
-
- -
-
-locks = 'cmd:perm(tag) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-arg_regex = re.compile('(/\\w+?(\\s|$))|\\s|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Implement the tag functionality

-
- -
-
-lock_storage = 'cmd:perm(tag) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.building.CmdSpawn(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

spawn objects from prototype

-
-
Usage:

spawn[/noloc] <prototype_key> -spawn[/noloc] <prototype_dict>

-

spawn/search [prototype_keykey][;tag[,tag]] -spawn/list [tag, tag, …] -spawn/list modules - list only module-based prototypes -spawn/show [<prototype_key>] -spawn/update <prototype_key>

-

spawn/save <prototype_dict> -spawn/edit [<prototype_key>] -olc - equivalent to spawn/edit

-
-
Switches:
-
noloc - allow location to be None if not specified explicitly. Otherwise,

location will default to caller’s current location.

-
-
-

search - search prototype by name or tags. -list - list available prototypes, optionally limit by tags. -show, examine - inspect prototype by key. If not given, acts like list. -raw - show the raw dict of the prototype as a one-line string for manual editing. -save - save a prototype to the database. It will be listable by /list. -delete - remove a prototype from database, if allowed to. -update - find existing objects with the same prototype_key and update

-
-

them with latest version of given prototype. If given with /save, -will auto-update all objects with the old version of the prototype -without asking first.

-
-

edit, menu, olc - create/manipulate prototype in a menu interface.

-
-
-

Example

-

spawn GOBLIN -spawn {“key”:”goblin”, “typeclass”:”monster.Monster”, “location”:”#2”} -spawn/save {“key”: “grunt”, prototype: “goblin”};;mobs;edit:all()

-
-
Dictionary keys:
-
|wprototype_parent |n - name of parent prototype to use. Required if typeclass is

not set. Can be a path or a list for multiple inheritance (inherits -left to right). If set one of the parents must have a typeclass.

-
-
-

|wtypeclass |n - string. Required if prototype_parent is not set. -|wkey |n - string, the main object identifier -|wlocation |n - this should be a valid object or #dbref -|whome |n - valid object or #dbref -|wdestination|n - only valid for exits (object or dbref) -|wpermissions|n - string or list of permission strings -|wlocks |n - a lock-string -|waliases |n - string or list of strings. -|wndb_|n<name> - value of a nattribute (ndb_ is stripped)

-
-
|wprototype_key|n - name of this prototype. Unique. Used to store/retrieve from db

and update existing prototyped objects if desired.

-
-
-

|wprototype_desc|n - desc of this prototype. Used in listings -|wprototype_locks|n - locks of this prototype. Limits who may use prototype -|wprototype_tags|n - tags of this prototype. Used to find prototype

-

any other keywords are interpreted as Attributes and their values.

-
-
-

The available prototypes are defined globally in modules set in -settings.PROTOTYPE_MODULES. If spawn is used without arguments it -displays a list of available prototypes.

-
-
-key = 'spawn'
-
- -
-
-aliases = ['olc']
-
- -
-
-switch_options = ('noloc', 'search', 'list', 'show', 'raw', 'examine', 'save', 'delete', 'menu', 'olc', 'update', 'edit')
-
- -
-
-locks = 'cmd:perm(spawn) or perm(Builder)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Implements the spawner

-
- -
-
-lock_storage = 'cmd:perm(spawn) or perm(Builder)'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.cmdset_account.html b/docs/0.9.5/api/evennia.commands.default.cmdset_account.html deleted file mode 100644 index c69e81f26a..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.cmdset_account.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - evennia.commands.default.cmdset_account — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.cmdset_account

-

This is the cmdset for Account (OOC) commands. These are -stored on the Account object and should thus be able to handle getting -an Account object as caller rather than a Character.

-

Note - in order for session-rerouting (in MULTISESSION_MODE=2) to -function, all commands in this cmdset should use the self.msg() -command method rather than caller.msg().

-
-
-class evennia.commands.default.cmdset_account.AccountCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Implements the account command set.

-
-
-key = 'DefaultAccount'
-
- -
-
-priority = -10
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset

-
- -
-
-path = 'evennia.commands.default.cmdset_account.AccountCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.cmdset_character.html b/docs/0.9.5/api/evennia.commands.default.cmdset_character.html deleted file mode 100644 index 484358206f..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.cmdset_character.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - evennia.commands.default.cmdset_character — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.cmdset_character

-

This module ties together all the commands default Character objects have -available (i.e. IC commands). Note that some commands, such as -communication-commands are instead put on the account level, in the -Account cmdset. Account commands remain available also to Characters.

-
-
-class evennia.commands.default.cmdset_character.CharacterCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Implements the default command set.

-
-
-key = 'DefaultCharacter'
-
- -
-
-priority = 0
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset

-
- -
-
-path = 'evennia.commands.default.cmdset_character.CharacterCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.cmdset_session.html b/docs/0.9.5/api/evennia.commands.default.cmdset_session.html deleted file mode 100644 index ad3226cb52..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.cmdset_session.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - evennia.commands.default.cmdset_session — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.cmdset_session

-

This module stores session-level commands.

-
-
-class evennia.commands.default.cmdset_session.SessionCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Sets up the unlogged cmdset.

-
-
-key = 'DefaultSession'
-
- -
-
-priority = -20
-
- -
-
-at_cmdset_creation()[source]
-

Populate the cmdset

-
- -
-
-path = 'evennia.commands.default.cmdset_session.SessionCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.cmdset_unloggedin.html b/docs/0.9.5/api/evennia.commands.default.cmdset_unloggedin.html deleted file mode 100644 index 75c656415c..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.cmdset_unloggedin.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - evennia.commands.default.cmdset_unloggedin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.cmdset_unloggedin

-

This module describes the unlogged state of the default game. -The setting STATE_UNLOGGED should be set to the python path -of the state instance in this module.

-
-
-class evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Sets up the unlogged cmdset.

-
-
-key = 'DefaultUnloggedin'
-
- -
-
-priority = 0
-
- -
-
-at_cmdset_creation()[source]
-

Populate the cmdset

-
- -
-
-path = 'evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.comms.html b/docs/0.9.5/api/evennia.commands.default.comms.html deleted file mode 100644 index 93ca8a152e..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.comms.html +++ /dev/null @@ -1,1010 +0,0 @@ - - - - - - - - - evennia.commands.default.comms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.comms

-

Comsystem command module.

-

Comm commands are OOC commands and intended to be made available to -the Account at all times (they go into the AccountCmdSet). So we -make sure to homogenize self.caller to always be the account object -for easy handling.

-
-
-class evennia.commands.default.comms.CmdAddCom(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

add a channel alias and/or subscribe to a channel

-
-
Usage:

addcom [alias=] <channel>

-
-
-

Joins a given channel. If alias is given, this will allow you to -refer to the channel by this alias rather than the full channel -name. Subsequent calls of this command can be used to add multiple -aliases to an already joined channel.

-
-
-key = 'addcom'
-
- -
-
-aliases = ['chanalias', 'aliaschan']
-
- -
-
-help_category = 'comms'
-
- -
-
-locks = 'cmd:not pperm(channel_banned)'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implement the command

-
- -
-
-lock_storage = 'cmd:not pperm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdDelCom(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

remove a channel alias and/or unsubscribe from channel

-
-
Usage:

delcom <alias or channel> -delcom/all <channel>

-
-
-

If the full channel name is given, unsubscribe from the -channel. If an alias is given, remove the alias but don’t -unsubscribe. If the ‘all’ switch is used, remove all aliases -for that channel.

-
-
-key = 'delcom'
-
- -
-
-aliases = ['delchanalias', 'delaliaschan']
-
- -
-
-help_category = 'comms'
-
- -
-
-locks = 'cmd:not perm(channel_banned)'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implementing the command.

-
- -
-
-lock_storage = 'cmd:not perm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdAllCom(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

perform admin operations on all channels

-
-
Usage:

allcom [on | off | who | destroy]

-
-
-

Allows the user to universally turn off or on all channels they are on, as -well as perform a ‘who’ for all channels they are on. Destroy deletes all -channels that you control.

-

Without argument, works like comlist.

-
-
-key = 'allcom'
-
- -
-
-locks = 'cmd: not pperm(channel_banned)'
-
- -
-
-help_category = 'comms'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Runs the function

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd: not pperm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdChannels(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

list all channels available to you

-
-
Usage:

channels -clist -comlist

-
-
-

Lists all channels available to you, whether you listen to them or not. -Use ‘comlist’ to only view your current channel subscriptions. -Use addcom/delcom to join and leave channels

-
-
-key = 'channels'
-
- -
-
-aliases = ['all channels', 'clist', 'channellist', 'chanlist', 'comlist']
-
- -
-
-help_category = 'comms'
-
- -
-
-locks = 'cmd: not pperm(channel_banned)'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implement function

-
- -
-
-lock_storage = 'cmd: not pperm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdCdestroy(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

destroy a channel you created

-
-
Usage:

cdestroy <channel>

-
-
-

Destroys a channel that you control.

-
-
-key = 'cdestroy'
-
- -
-
-help_category = 'comms'
-
- -
-
-locks = 'cmd: not pperm(channel_banned)'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Destroy objects cleanly.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd: not pperm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdCBoot(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

kick an account from a channel you control

-
-
Usage:

cboot[/quiet] <channel> = <account> [:reason]

-
-
Switch:

quiet - don’t notify the channel

-
-
-

Kicks an account or object from a channel you control.

-
-
-key = 'cboot'
-
- -
-
-switch_options = ('quiet',)
-
- -
-
-locks = 'cmd: not pperm(channel_banned)'
-
- -
-
-help_category = 'comms'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

implement the function

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd: not pperm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdCemit(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

send an admin message to a channel you control

-
-
Usage:

cemit[/switches] <channel> = <message>

-
-
Switches:

sendername - attach the sender’s name before the message -quiet - don’t echo the message back to sender

-
-
-

Allows the user to broadcast a message over a channel as long as -they control it. It does not show the user’s name unless they -provide the /sendername switch.

-
-
-key = 'cemit'
-
- -
-
-aliases = ['cmsg']
-
- -
-
-switch_options = ('sendername', 'quiet')
-
- -
-
-locks = 'cmd: not pperm(channel_banned) and pperm(Player)'
-
- -
-
-help_category = 'comms'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implement function

-
- -
-
-lock_storage = 'cmd: not pperm(channel_banned) and pperm(Player)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdCWho(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

show who is listening to a channel

-
-
Usage:

cwho <channel>

-
-
-

List who is connected to a given channel you have access to.

-
-
-key = 'cwho'
-
- -
-
-locks = 'cmd: not pperm(channel_banned)'
-
- -
-
-help_category = 'comms'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

implement function

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd: not pperm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdChannelCreate(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

create a new channel

-
-
Usage:

ccreate <new channel>[;alias;alias…] = description

-
-
-

Creates a new channel owned by you.

-
-
-key = 'ccreate'
-
- -
-
-aliases = ['channelcreate']
-
- -
-
-locks = 'cmd:not pperm(channel_banned) and pperm(Player)'
-
- -
-
-help_category = 'comms'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implement the command

-
- -
-
-lock_storage = 'cmd:not pperm(channel_banned) and pperm(Player)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdClock(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

change channel locks of a channel you control

-
-
Usage:

clock <channel> [= <lockstring>]

-
-
-

Changes the lock access restrictions of a channel. If no -lockstring was given, view the current lock definitions.

-
-
-key = 'clock'
-
- -
-
-locks = 'cmd:not pperm(channel_banned)'
-
- -
-
-aliases = []
-
- -
-
-help_category = 'comms'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

run the function

-
- -
-
-lock_storage = 'cmd:not pperm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdCdesc(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

describe a channel you control

-
-
Usage:

cdesc <channel> = <description>

-
-
-

Changes the description of the channel as shown in -channel lists.

-
-
-key = 'cdesc'
-
- -
-
-locks = 'cmd:not pperm(channel_banned)'
-
- -
-
-help_category = 'comms'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implement command

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:not pperm(channel_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdPage(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

send a private message to another account

-
-
Usage:

page[/switches] [<account>,<account>,… = <message>] -tell ‘’ -page <number>

-
-
Switch:

last - shows who you last messaged -list - show your last <number> of tells/pages (default)

-
-
-

Send a message to target user (if online). If no -argument is given, you will get a list of your latest messages.

-
-
-key = 'page'
-
- -
-
-aliases = ['tell']
-
- -
-
-switch_options = ('last', 'list')
-
- -
-
-locks = 'cmd:not pperm(page_banned)'
-
- -
-
-help_category = 'comms'
-
- -
-
-account_caller = True
-
- -
-
-func()[source]
-

Implement function using the Msg methods

-
- -
-
-lock_storage = 'cmd:not pperm(page_banned)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdIRC2Chan(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Link an evennia channel to an external IRC channel

-
-
Usage:

irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>[:typeclass] -irc2chan/delete botname|#dbid

-
-
Switches:
-
/delete
-
    -
  • this will delete the bot and remove the irc connection

  • -
-

to the channel. Requires the botname or #dbid as input.

-
-
/remove
-
    -
  • alias to /delete

  • -
-
-
-

/disconnect - alias to /delete -/list - show all irc<->evennia mappings -/ssl - use an SSL-encrypted connection

-
-
-

Example

-

irc2chan myircchan = irc.dalnet.net 6667 #mychannel evennia-bot -irc2chan public = irc.freenode.net 6667 #evgaming #evbot:accounts.mybot.MyBot

-

This creates an IRC bot that connects to a given IRC network and -channel. If a custom typeclass path is given, this will be used -instead of the default bot class. -The bot will relay everything said in the evennia channel to the -IRC channel and vice versa. The bot will automatically connect at -server start, so this command need only be given once. The -/disconnect switch will permanently delete the bot. To only -temporarily deactivate it, use the |wservices|n command instead. -Provide an optional bot class path to use a custom bot.

-
-
-key = 'irc2chan'
-
- -
-
-switch_options = ('delete', 'remove', 'disconnect', 'list', 'ssl')
-
- -
-
-locks = 'cmd:serversetting(IRC_ENABLED) and pperm(Developer)'
-
- -
-
-help_category = 'comms'
-
- -
-
-func()[source]
-

Setup the irc-channel mapping

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:serversetting(IRC_ENABLED) and pperm(Developer)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdIRCStatus(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Check and reboot IRC bot.

-
-
Usage:

ircstatus [#dbref ping||nicklist||reconnect]

-
-
-

If not given arguments, will return a list of all bots (like -irc2chan/list). The ‘ping’ argument will ping the IRC network to -see if the connection is still responsive. The ‘nicklist’ argument -(aliases are ‘who’ and ‘users’) will return a list of users on the -remote IRC channel. Finally, ‘reconnect’ will force the client to -disconnect and reconnect again. This may be a last resort if the -client has silently lost connection (this may happen if the remote -network experience network issues). During the reconnection -messages sent to either channel will be lost.

-
-
-key = 'ircstatus'
-
- -
-
-locks = 'cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builder))'
-
- -
-
-help_category = 'comms'
-
- -
-
-func()[source]
-

Handles the functioning of the command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builder))'
-
- -
- -
-
-class evennia.commands.default.comms.CmdRSS2Chan(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

link an evennia channel to an external RSS feed

-
-
Usage:

rss2chan[/switches] <evennia_channel> = <rss_url>

-
-
Switches:
-
/disconnect - this will stop the feed and remove the connection to the

channel.

-
-
-
-
/remove
-
    -
  • -
-
-
/list
-
    -
  • show all rss->evennia mappings

  • -
-
-
-
-
-

Example

-

rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic

-

This creates an RSS reader that connects to a given RSS feed url. Updates -will be echoed as a title and news link to the given channel. The rate of -updating is set with the RSS_UPDATE_INTERVAL variable in settings (default -is every 10 minutes).

-

When disconnecting you need to supply both the channel and url again so as -to identify the connection uniquely.

-
-
-key = 'rss2chan'
-
- -
-
-switch_options = ('disconnect', 'remove', 'list')
-
- -
-
-locks = 'cmd:serversetting(RSS_ENABLED) and pperm(Developer)'
-
- -
-
-help_category = 'comms'
-
- -
-
-func()[source]
-

Setup the rss-channel mapping

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:serversetting(RSS_ENABLED) and pperm(Developer)'
-
- -
- -
-
-class evennia.commands.default.comms.CmdGrapevine2Chan(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Link an Evennia channel to an exteral Grapevine channel

-
-
Usage:

grapevine2chan[/switches] <evennia_channel> = <grapevine_channel> -grapevine2chan/disconnect <connection #id>

-
-
Switches:
-
/list
-
    -
  • (or no switch): show existing grapevine <-> Evennia

  • -
-

mappings and available grapevine chans

-
-
/remove
-
    -
  • alias to disconnect

  • -
-
-
/delete
-
    -
  • alias to disconnect

  • -
-
-
-
-
-

Example

-

grapevine2chan mygrapevine = gossip

-

This creates a link between an in-game Evennia channel and an external -Grapevine channel. The game must be registered with the Grapevine network -(register at https://grapevine.haus) and the GRAPEVINE_* auth information -must be added to game settings.

-
-
-key = 'grapevine2chan'
-
- -
-
-switch_options = ('disconnect', 'remove', 'delete', 'list')
-
- -
-
-locks = 'cmd:serversetting(GRAPEVINE_ENABLED) and pperm(Developer)'
-
- -
-
-help_category = 'comms'
-
- -
-
-func()[source]
-

Setup the Grapevine channel mapping

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:serversetting(GRAPEVINE_ENABLED) and pperm(Developer)'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.general.html b/docs/0.9.5/api/evennia.commands.default.general.html deleted file mode 100644 index d6d733e26e..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.general.html +++ /dev/null @@ -1,749 +0,0 @@ - - - - - - - - - evennia.commands.default.general — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.general

-

General Character commands usually available to all characters

-
-
-class evennia.commands.default.general.CmdHome(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

move to your character’s home location

-
-
Usage:

home

-
-
-

Teleports you to your home location.

-
-
-key = 'home'
-
- -
-
-locks = 'cmd:perm(home) or perm(Builder)'
-
- -
-
-arg_regex = re.compile('$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Implement the command

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:perm(home) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.general.CmdLook(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

look at location or object

-
-
Usage:

look -look <obj> -look *<account>

-
-
-

Observes your location or objects in your vicinity.

-
-
-key = 'look'
-
- -
-
-aliases = ['l', 'ls']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Handle the looking.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdNick(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

define a personal alias/nick by defining a string to -match and replace it with another on the fly

-
-
Usage:

nick[/switches] <string> [= [replacement_string]] -nick[/switches] <template> = <replacement_template> -nick/delete <string> or number -nicks

-
-
Switches:

inputline - replace on the inputline (default) -object - replace on object-lookup -account - replace on account-lookup -list - show all defined aliases (also “nicks” works) -delete - remove nick by index in /list -clearall - clear all nicks

-
-
-

Examples

-

nick hi = say Hello, I’m Sarah! -nick/object tom = the tall man -nick build $1 $2 = create/drop $1;$2 -nick tell $1 $2=page $1=$2 -nick tm?$1=page tallman=$1 -nick tm=$1=page tallman=$1

-

A ‘nick’ is a personal string replacement. Use $1, $2, … to catch arguments. -Put the last $-marker without an ending space to catch all remaining text. You -can also use unix-glob matching for the left-hand side <string>:

-
-
    -
    • -
    • matches everything

    • -
    -
  • -
-

? - matches 0 or 1 single characters -[abcd] - matches these chars in any order -[!abcd] - matches everything not among these chars -= - escape literal ‘=’ you want in your <string>

-
-

Note that no objects are actually renamed or changed by this command - your nicks -are only available to you. If you want to permanently add keywords to an object -for everyone to use, you need build privileges and the alias command.

-
-
-key = 'nick'
-
- -
-
-switch_options = ('inputline', 'object', 'account', 'list', 'delete', 'clearall')
-
- -
-
-aliases = ['nickname', 'nicks']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-parse()[source]
-

Support escaping of = with =

-
- -
-
-func()[source]
-

Create the nickname

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdInventory(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

view inventory

-
-
Usage:

inventory -inv

-
-
-

Shows your inventory.

-
-
-key = 'inventory'
-
- -
-
-aliases = ['inv', 'i']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('$', re.IGNORECASE)
-
- -
-
-func()[source]
-

check inventory

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdSetDesc(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

describe yourself

-
-
Usage:

setdesc <description>

-
-
-

Add a description to yourself. This -will be visible to people when they -look at you.

-
-
-key = 'setdesc'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

add the description

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdGet(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

pick up something

-
-
Usage:

get <obj>

-
-
-

Picks up an object from your location and puts it in -your inventory.

-
-
-key = 'get'
-
- -
-
-aliases = ['grab']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

implements the command.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdDrop(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

drop something

-
-
Usage:

drop <obj>

-
-
-

Lets you drop an object from your inventory into the -location you are currently in.

-
-
-key = 'drop'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Implement command

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdGive(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

give away something to someone

-
-
Usage:

give <inventory obj> <to||=> <target>

-
-
-

Gives an items from your inventory to another character, -placing it in their inventory.

-
-
-key = 'give'
-
- -
-
-rhs_split = ('=', ' to ')
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Implement give

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdSay(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

speak as your character

-
-
Usage:

say <message>

-
-
-

Talk to those in your current location.

-
-
-key = 'say'
-
- -
-
-aliases = ["'", '"']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Run the say command

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdWhisper(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Speak privately as your character to another

-
-
Usage:

whisper <character> = <message> -whisper <char1>, <char2> = <message>

-
-
-

Talk privately to one or more characters in your current location, without -others in the room being informed.

-
-
-key = 'whisper'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Run the whisper command

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdPose(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

strike a pose

-
-
Usage:

pose <pose text> -pose’s <pose text>

-
-
-

Example

-
-
pose is standing by the wall, smiling.

-> others will see:

-
-
-

Tom is standing by the wall, smiling.

-

Describe an action being taken. The pose text will -automatically begin with your name.

-
-
-key = 'pose'
-
- -
-
-aliases = [':', 'emote']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-parse()[source]
-

Custom parse the cases where the emote -starts with some special letter, such -as ‘s, at which we don’t want to separate -the caller’s name and the emote with a -space.

-
- -
-
-func()[source]
-

Hook function

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.general.CmdAccess(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

show your current game access

-
-
Usage:

access

-
-
-

This command shows you the permission hierarchy and -which permission groups you are a member of.

-
-
-key = 'access'
-
- -
-
-aliases = ['groups', 'hierarchy']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Load the permission groups

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.help.html b/docs/0.9.5/api/evennia.commands.default.help.html deleted file mode 100644 index 1dae5b54a9..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.help.html +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - - - - evennia.commands.default.help — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.help

-

The help command. The basic idea is that help texts for commands -are best written by those that write the commands - the admins. So -command-help is all auto-loaded and searched from the current command -set. The normal, database-tied help system is used for collaborative -creation of other help topics such as RP help or game-world aides.

-
-
-class evennia.commands.default.help.CmdHelp(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

View help or a list of topics

-
-
Usage:

help <topic or command> -help list -help all

-
-
-

This will search for help on commands and other -topics related to the game.

-
-
-key = 'help'
-
- -
-
-aliases = ['?']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s|$', re.IGNORECASE)
-
- -
-
-return_cmdset = True
-
- -
-
-help_more = True
-
- -
-
-suggestion_cutoff = 0.6
-
- -
-
-suggestion_maxnum = 5
-
- -
-
-msg_help(text)[source]
-

messages text to the caller, adding an extra oob argument to indicate -that this is a help command result and could be rendered in a separate -help window

-
- -
-
-static format_help_entry(title, help_text, aliases=None, suggested=None)[source]
-

This visually formats the help entry. -This method can be overriden to customize the way a help -entry is displayed.

-
-
Parameters
-
    -
  • title (str) – the title of the help entry.

  • -
  • help_text (str) – the text of the help entry.

  • -
  • aliases (list of str or None) – the list of aliases.

  • -
  • suggested (list of str or None) – suggested reading.

  • -
-
-
-

Returns the formatted string, ready to be sent.

-
- -
-
-static format_help_list(hdict_cmds, hdict_db)[source]
-

Output a category-ordered list. The input are the -pre-loaded help files for commands and database-helpfiles -respectively. You can override this method to return a -custom display of the list of commands and topics.

-
- -
-
-check_show_help(cmd, caller)[source]
-

Helper method. If this return True, the given cmd -auto-help will be viewable in the help listing. -Override this to easily select what is shown to -the account. Note that only commands available -in the caller’s merged cmdset are available.

-
-
Parameters
-
    -
  • cmd (Command) – Command class from the merged cmdset

  • -
  • caller (Character, Account or Session) – The current caller -executing the help command.

  • -
-
-
-
- -
-
-should_list_cmd(cmd, caller)[source]
-

Should the specified command appear in the help table?

-

This method only checks whether a specified command should -appear in the table of topics/commands. The command can be -used by the caller (see the ‘check_show_help’ method) and -the command will still be available, for instance, if a -character type ‘help name of the command’. However, if -you return False, the specified command will not appear in -the table. This is sometimes useful to “hide” commands in -the table, but still access them through the help system.

-
-
Parameters
-
    -
  • cmd – the command to be tested.

  • -
  • caller – the caller of the help system.

  • -
-
-
Returns
-

True – the command should appear in the table. -False: the command shouldn’t appear in the table.

-
-
-
- -
-
-parse()[source]
-

input is a string containing the command or topic to match.

-
- -
-
-func()[source]
-

Run the dynamic help entry creator.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.help.CmdSetHelp(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Edit the help database.

-
-
Usage:

help[/switches] <topic>[[;alias;alias][,category[,locks]] [= <text>]

-
-
Switches:

edit - open a line editor to edit the topic’s help text. -replace - overwrite existing help topic. -append - add text to the end of existing topic with a newline between. -extend - as append, but don’t add a newline. -delete - remove help topic.

-
-
-

Examples

-

sethelp throw = This throws something at … -sethelp/append pickpocketing,Thievery = This steals … -sethelp/replace pickpocketing, ,attr(is_thief) = This steals … -sethelp/edit thievery

-

This command manipulates the help database. A help entry can be created, -appended/merged to and deleted. If you don’t assign a category, the -“General” category will be used. If no lockstring is specified, default -is to let everyone read the help file.

-
-
-key = 'sethelp'
-
- -
-
-switch_options = ('edit', 'replace', 'append', 'extend', 'delete')
-
- -
-
-locks = 'cmd:perm(Helper)'
-
- -
-
-help_category = 'building'
-
- -
-
-func()[source]
-

Implement the function

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(Helper)'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.html b/docs/0.9.5/api/evennia.commands.default.html deleted file mode 100644 index 9056e74d89..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - - evennia.commands.default — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.muxcommand.html b/docs/0.9.5/api/evennia.commands.default.muxcommand.html deleted file mode 100644 index 339c8845de..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.muxcommand.html +++ /dev/null @@ -1,299 +0,0 @@ - - - - - - - - - evennia.commands.default.muxcommand — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.muxcommand

-

The command template for the default MUX-style command set. There -is also an Account/OOC version that makes sure caller is an Account object.

-
-
-class evennia.commands.default.muxcommand.MuxCommand(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

This sets up the basis for a MUX command. The idea -is that most other Mux-related commands should just -inherit from this and don’t have to implement much -parsing of their own unless they do something particularly -advanced.

-

Note that the class’s __doc__ string (this text) is -used by Evennia to create the automatic help entry for -the command, so make sure to document consistently here.

-
-
-has_perm(srcobj)[source]
-

This is called by the cmdhandler to determine -if srcobj is allowed to execute this command. -We just show it here for completeness - we -are satisfied using the default check in Command.

-
- -
-
-at_pre_cmd()[source]
-

This hook is called before self.parse() on all commands

-
- -
-
-at_post_cmd()[source]
-

This hook is called after the command has finished executing -(after self.func()).

-
- -
-
-parse()[source]
-

This method is called by the cmdhandler once the command name -has been identified. It creates a new set of member variables -that can be later accessed from self.func() (see below)

-

The following variables are available for our use when entering this -method (from the command definition, and assigned on the fly by the -cmdhandler):

-
-

self.key - the name of this command (‘look’) -self.aliases - the aliases of this cmd (‘l’) -self.permissions - permission string for this command -self.help_category - overall category of command

-

self.caller - the object calling this command -self.cmdstring - the actual command name used to call this

-
-
-
(this allows you to know which alias was used,

for example)

-
-
-
-

self.args - the raw input; everything following self.cmdstring. -self.cmdset - the cmdset from which this command was picked. Not

-
-

often used (useful for commands like ‘help’ or to -list all available commands etc)

-
-
-
self.obj - the object on which this command was defined. It is often

the same as self.caller.

-
-
-
-

A MUX command has the following possible syntax:

-
-

name[ with several words][/switch[/switch..]] arg1[,arg2,…] [[=|,] arg[,..]]

-
-

The ‘name[ with several words]’ part is already dealt with by the -cmdhandler at this point, and stored in self.cmdname (we don’t use -it here). The rest of the command is stored in self.args, which can -start with the switch indicator /.

-
-
Optional variables to aid in parsing, if set:
-
self.switch_options - (tuple of valid /switches expected by this

command (without the /))

-
-
self.rhs_split - Alternate string delimiter or tuple of strings

to separate left/right hand sides. tuple form -gives priority split to first string delimiter.

-
-
-
-
-

This parser breaks self.args into its constituents and stores them in the -following variables:

-
-

self.switches = [list of /switches (without the /)] -self.raw = This is the raw argument input, including switches -self.args = This is re-defined to be everything except the switches -self.lhs = Everything to the left of = (lhs:’left-hand side’). If

-
-

no = is found, this is identical to self.args.

-
-
-
self.rhs: Everything to the right of = (rhs:’right-hand side’).

If no ‘=’ is found, this is None.

-
-
-

self.lhslist - [self.lhs split into a list by comma] -self.rhslist - [list of self.rhs split into a list by comma] -self.arglist = [list of space-separated args (stripped, including ‘=’ if it exists)]

-

All args and list members are stripped of excess whitespace around the -strings, but case is preserved.

-
-
- -
-
-get_command_info()[source]
-

Update of parent class’s get_command_info() for MuxCommand.

-
- -
-
-func()[source]
-
-
This is the hook function that actually does all the work. It is called

by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
-
-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'command'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.commands.default.muxcommand.MuxAccountCommand(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

This is an on-Account version of the MuxCommand. Since these commands sit -on Accounts rather than on Characters/Objects, we need to check -this in the parser.

-

Account commands are available also when puppeting a Character, it’s -just that they are applied with a lower priority and are always -available, also when disconnected from a character (i.e. “ooc”).

-

This class makes sure that caller is always an Account object, while -creating a new property “character” that is set only if a -character is actually attached to this Account and Session.

-
-
-account_caller = True
-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'command'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.syscommands.html b/docs/0.9.5/api/evennia.commands.default.syscommands.html deleted file mode 100644 index 4a60a6f286..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.syscommands.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - - - - evennia.commands.default.syscommands — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.syscommands

-

System commands

-

These are the default commands called by the system commandhandler -when various exceptions occur. If one of these commands are not -implemented and part of the current cmdset, the engine falls back -to a default solution instead.

-

Some system commands are shown in this module -as a REFERENCE only (they are not all added to Evennia’s -default cmdset since they don’t currently do anything differently from the -default backup systems hard-wired in the engine).

-

Overloading these commands in a cmdset can be used to create -interesting effects. An example is using the NoMatch system command -to implement a line-editor where you don’t have to start each -line with a command (if there is no match to a known command, -the line is just added to the editor buffer).

-
-
-class evennia.commands.default.syscommands.SystemNoInput(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

This is called when there is no input given

-
-
-key = '__noinput_command'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Do nothing.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.syscommands.SystemNoMatch(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

No command was found matching the given input.

-
-
-key = '__nomatch_command'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

This is given the failed raw string as input.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.syscommands.SystemMultimatch(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Multiple command matches.

-

The cmdhandler adds a special attribute ‘matches’ to this -system command.

-
-

matches = [(cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname) , (cmdname, …), …]

-
-

Here, cmdname is the command’s name and args the rest of the incoming string, -without said command name. cmdobj is the Command instance, the cmdlen is -the same as len(cmdname) and mratio is a measure of how big a part of the -full input string the cmdname takes up - an exact match would be 1.0. Finally, -the raw_cmdname is the cmdname unmodified by eventual prefix-stripping.

-
-
-key = '__multimatch_command'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Handle multiple-matches by using the at_search_result default handler.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.syscommands.SystemSendToChannel(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

This is a special command that the cmdhandler calls -when it detects that the command given matches -an existing Channel object key (or alias).

-
-
-key = '__send_to_channel_command'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-parse()[source]
-

This method is called by the cmdhandler once the command name -has been identified. It creates a new set of member variables -that can be later accessed from self.func() (see below)

-

The following variables are available for our use when entering this -method (from the command definition, and assigned on the fly by the -cmdhandler):

-
-

self.key - the name of this command (‘look’) -self.aliases - the aliases of this cmd (‘l’) -self.permissions - permission string for this command -self.help_category - overall category of command

-

self.caller - the object calling this command -self.cmdstring - the actual command name used to call this

-
-
-
(this allows you to know which alias was used,

for example)

-
-
-
-

self.args - the raw input; everything following self.cmdstring. -self.cmdset - the cmdset from which this command was picked. Not

-
-

often used (useful for commands like ‘help’ or to -list all available commands etc)

-
-
-
self.obj - the object on which this command was defined. It is often

the same as self.caller.

-
-
-
-

A MUX command has the following possible syntax:

-
-

name[ with several words][/switch[/switch..]] arg1[,arg2,…] [[=|,] arg[,..]]

-
-

The ‘name[ with several words]’ part is already dealt with by the -cmdhandler at this point, and stored in self.cmdname (we don’t use -it here). The rest of the command is stored in self.args, which can -start with the switch indicator /.

-
-
Optional variables to aid in parsing, if set:
-
self.switch_options - (tuple of valid /switches expected by this

command (without the /))

-
-
self.rhs_split - Alternate string delimiter or tuple of strings

to separate left/right hand sides. tuple form -gives priority split to first string delimiter.

-
-
-
-
-

This parser breaks self.args into its constituents and stores them in the -following variables:

-
-

self.switches = [list of /switches (without the /)] -self.raw = This is the raw argument input, including switches -self.args = This is re-defined to be everything except the switches -self.lhs = Everything to the left of = (lhs:’left-hand side’). If

-
-

no = is found, this is identical to self.args.

-
-
-
self.rhs: Everything to the right of = (rhs:’right-hand side’).

If no ‘=’ is found, this is None.

-
-
-

self.lhslist - [self.lhs split into a list by comma] -self.rhslist - [list of self.rhs split into a list by comma] -self.arglist = [list of space-separated args (stripped, including ‘=’ if it exists)]

-

All args and list members are stripped of excess whitespace around the -strings, but case is preserved.

-
-
- -
-
-func()[source]
-

Create a new message and send it to channel, using -the already formatted input.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.system.html b/docs/0.9.5/api/evennia.commands.default.system.html deleted file mode 100644 index c279cb8f04..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.system.html +++ /dev/null @@ -1,760 +0,0 @@ - - - - - - - - - evennia.commands.default.system — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.system

-

System commands

-
-
-class evennia.commands.default.system.CmdReload(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

reload the server

-
-
Usage:

reload [reason]

-
-
-

This restarts the server. The Portal is not -affected. Non-persistent scripts will survive a reload (use -reset to purge) and at_reload() hooks will be called.

-
-
-key = 'reload'
-
- -
-
-aliases = ['restart']
-
- -
-
-locks = 'cmd:perm(reload) or perm(Developer)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

Reload the system.

-
- -
-
-lock_storage = 'cmd:perm(reload) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.system.CmdReset(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

reset and reboot the server

-
-
Usage:

reset

-
-
-

Notes

-

For normal updating you are recommended to use reload rather -than this command. Use shutdown for a complete stop of -everything.

-

This emulates a cold reboot of the Server component of Evennia. -The difference to shutdown is that the Server will auto-reboot -and that it does not affect the Portal, so no users will be -disconnected. Contrary to reload however, all shutdown hooks will -be called and any non-database saved scripts, ndb-attributes, -cmdsets etc will be wiped.

-
-
-key = 'reset'
-
- -
-
-aliases = ['reboot']
-
- -
-
-locks = 'cmd:perm(reload) or perm(Developer)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

Reload the system.

-
- -
-
-lock_storage = 'cmd:perm(reload) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.system.CmdShutdown(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

stop the server completely

-
-
Usage:

shutdown [announcement]

-
-
-

Gracefully shut down both Server and Portal.

-
-
-key = 'shutdown'
-
- -
-
-locks = 'cmd:perm(shutdown) or perm(Developer)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

Define function

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(shutdown) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.system.CmdPy(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

execute a snippet of python code

-
-
Usage:

py [cmd] -py/edit -py/time <cmd> -py/clientraw <cmd> -py/noecho

-
-
Switches:

time - output an approximate execution time for <cmd> -edit - open a code editor for multi-line code experimentation -clientraw - turn off all client-specific escaping. Note that this may

-
-

lead to different output depending on prototocol (such as angular brackets -being parsed as HTML in the webclient but not in telnet clients)

-
-
-
noecho - in Python console mode, turn off the input echo (e.g. if your client

does this for you already)

-
-
-
-
-

Without argument, open a Python console in-game. This is a full console, -accepting multi-line Python code for testing and debugging. Type exit() to -return to the game. If Evennia is reloaded, the console will be closed.

-

Enter a line of instruction after the ‘py’ command to execute it -immediately. Separate multiple commands by ‘;’ or open the code editor -using the /edit switch (all lines added in editor will be executed -immediately when closing or using the execute command in the editor).

-

A few variables are made available for convenience in order to offer access -to the system (you can import more at execution time).

-
-
Available variables in py environment:

self, me : caller -here : caller.location -evennia : the evennia API -inherits_from(obj, parent) : check object inheritance

-
-
-

You can explore The evennia API from inside the game by calling -the __doc__ property on entities:

-
-

py evennia.__doc__ -py evennia.managers.__doc__

-
-

|rNote: In the wrong hands this command is a severe security risk. It -should only be accessible by trusted server admins/superusers.|n

-
-
-key = 'py'
-
- -
-
-aliases = ['!']
-
- -
-
-switch_options = ('time', 'edit', 'clientraw', 'noecho')
-
- -
-
-locks = 'cmd:perm(py) or perm(Developer)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

hook function

-
- -
-
-lock_storage = 'cmd:perm(py) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.system.CmdScripts(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

list and manage all running scripts

-
-
Usage:

scripts[/switches] [#dbref, key, script.path or <obj>]

-
-
Switches:

start - start a script (must supply a script path) -stop - stops an existing script -kill - kills a script - without running its cleanup hooks -validate - run a validation on the script(s)

-
-
-

If no switches are given, this command just views all active -scripts. The argument can be either an object, at which point it -will be searched for all scripts defined on it, or a script name -or #dbref. For using the /stop switch, a unique script #dbref is -required since whole classes of scripts often have the same name.

-

Use script for managing commands on objects.

-
-
-key = 'scripts'
-
- -
-
-aliases = ['globalscript', 'listscripts']
-
- -
-
-switch_options = ('start', 'stop', 'kill', 'validate')
-
- -
-
-locks = 'cmd:perm(listscripts) or perm(Admin)'
-
- -
-
-help_category = 'system'
-
- -
-
-excluded_typeclass_paths = ['evennia.prototypes.prototypes.DbPrototype']
-
- -
-
-func()[source]
-

implement method

-
- -
-
-lock_storage = 'cmd:perm(listscripts) or perm(Admin)'
-
- -
- -
-
-class evennia.commands.default.system.CmdObjects(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

statistics on objects in the database

-
-
Usage:

objects [<nr>]

-
-
-

Gives statictics on objects in database as well as -a list of <nr> latest objects in database. If not -given, <nr> defaults to 10.

-
-
-key = 'objects'
-
- -
-
-aliases = ['listobjs', 'listobjects', 'db', 'stats']
-
- -
-
-locks = 'cmd:perm(listobjects) or perm(Builder)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

Implement the command

-
- -
-
-lock_storage = 'cmd:perm(listobjects) or perm(Builder)'
-
- -
- -
-
-class evennia.commands.default.system.CmdService(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

manage system services

-
-
Usage:

service[/switch] <service>

-
-
Switches:

list - shows all available services (default) -start - activates or reactivate a service -stop - stops/inactivate a service (can often be restarted) -delete - tries to permanently remove a service

-
-
-

Service management system. Allows for the listing, -starting, and stopping of services. If no switches -are given, services will be listed. Note that to operate on the -service you have to supply the full (green or red) name as given -in the list.

-
-
-key = 'service'
-
- -
-
-aliases = ['services']
-
- -
-
-switch_options = ('list', 'start', 'stop', 'delete')
-
- -
-
-locks = 'cmd:perm(service) or perm(Developer)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

Implement command

-
- -
-
-lock_storage = 'cmd:perm(service) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.system.CmdAbout(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

show Evennia info

-
-
Usage:

about

-
-
-

Display info about the game engine.

-
-
-key = 'about'
-
- -
-
-aliases = ['version']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

Display information about server or target

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.system.CmdTime(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

show server time statistics

-
-
Usage:

time

-
-
-

List Server time statistics such as uptime -and the current time stamp.

-
-
-key = 'time'
-
- -
-
-aliases = ['uptime']
-
- -
-
-locks = 'cmd:perm(time) or perm(Player)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

Show server time data in a table.

-
- -
-
-lock_storage = 'cmd:perm(time) or perm(Player)'
-
- -
- -
-
-class evennia.commands.default.system.CmdServerLoad(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

show server load and memory statistics

-
-
Usage:

server[/mem]

-
-
Switches:

mem - return only a string of the current memory usage -flushmem - flush the idmapper cache

-
-
-

This command shows server load statistics and dynamic memory -usage. It also allows to flush the cache of accessed database -objects.

-

Some Important statistics in the table:

-

|wServer load|n is an average of processor usage. It’s usually -between 0 (no usage) and 1 (100% usage), but may also be -temporarily higher if your computer has multiple CPU cores.

-

The |wResident/Virtual memory|n displays the total memory used by -the server process.

-

Evennia |wcaches|n all retrieved database entities when they are -loaded by use of the idmapper functionality. This allows Evennia -to maintain the same instances of an entity and allowing -non-persistent storage schemes. The total amount of cached objects -are displayed plus a breakdown of database object types.

-

The |wflushmem|n switch allows to flush the object cache. Please -note that due to how Python’s memory management works, releasing -caches may not show you a lower Residual/Virtual memory footprint, -the released memory will instead be re-used by the program.

-
-
-key = 'server'
-
- -
-
-aliases = ['serverprocess', 'serverload']
-
- -
-
-switch_options = ('mem', 'flushmem')
-
- -
-
-locks = 'cmd:perm(list) or perm(Developer)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

Show list.

-
- -
-
-lock_storage = 'cmd:perm(list) or perm(Developer)'
-
- -
- -
-
-class evennia.commands.default.system.CmdAccounts(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Manage registered accounts

-
-
Usage:

accounts [nr] -accounts/delete <name or #id> [: reason]

-
-
Switches:

delete - delete an account from the server

-
-
-

By default, lists statistics about the Accounts registered with the game. -It will list the <nr> amount of latest registered accounts -If not given, <nr> defaults to 10.

-
-
-key = 'accounts'
-
- -
-
-aliases = ['account', 'listaccounts']
-
- -
-
-switch_options = ('delete',)
-
- -
-
-locks = 'cmd:perm(listaccounts) or perm(Admin)'
-
- -
-
-help_category = 'system'
-
- -
-
-func()[source]
-

List the accounts

-
- -
-
-lock_storage = 'cmd:perm(listaccounts) or perm(Admin)'
-
- -
- -
-
-class evennia.commands.default.system.CmdTickers(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

View running tickers

-
-
Usage:

tickers

-
-
-

Note: Tickers are created, stopped and manipulated in Python code -using the TickerHandler. This is merely a convenience function for -inspecting the current status.

-
-
-key = 'tickers'
-
- -
-
-help_category = 'system'
-
- -
-
-locks = 'cmd:perm(tickers) or perm(Builder)'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(tickers) or perm(Builder)'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.tests.html b/docs/0.9.5/api/evennia.commands.default.tests.html deleted file mode 100644 index f2c39fb234..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.tests.html +++ /dev/null @@ -1,718 +0,0 @@ - - - - - - - - - evennia.commands.default.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.tests

-

This is part of the Evennia unittest framework, for testing the -stability and integrity of the codebase during updates. This module -test the default command set. It is instantiated by the -evennia/objects/tests.py module, which in turn is run by as part of the -main test suite started with

-
-

> python game/manage.py test.

-
-
-
-class evennia.commands.default.tests.CommandTest(methodName='runTest')[source]
-

Bases: evennia.utils.test_resources.EvenniaTest

-

Tests a command

-
-
-call(cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, receiver=None, cmdstring=None, obj=None, inputs=None, raw_string=None)[source]
-

Test a command by assigning all the needed -properties to cmdobj and running

-
-

cmdobj.at_pre_cmd() -cmdobj.parse() -cmdobj.func() -cmdobj.at_post_cmd()

-
-

The msgreturn value is compared to eventual -output sent to caller.msg in the game

-
-
Returns
-

msg (str) – The received message that was sent to the caller.

-
-
-
- -
- -
-
-class evennia.commands.default.tests.TestGeneral(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_look()[source]
-
- -
-
-test_home()[source]
-
- -
-
-test_inventory()[source]
-
- -
-
-test_pose()[source]
-
- -
-
-test_nick()[source]
-
- -
-
-test_get_and_drop()[source]
-
- -
-
-test_give()[source]
-
- -
-
-test_mux_command()[source]
-
- -
-
-test_say()[source]
-
- -
-
-test_whisper()[source]
-
- -
-
-test_access()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestHelp(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_help()[source]
-
- -
-
-test_set_help()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestSystem(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_py()[source]
-
- -
-
-test_scripts()[source]
-
- -
-
-test_objects()[source]
-
- -
-
-test_about()[source]
-
- -
-
-test_server_load()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestAdmin(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_emit()[source]
-
- -
-
-test_perm()[source]
-
- -
-
-test_wall()[source]
-
- -
-
-test_ban()[source]
-
- -
-
-test_force()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestAccount(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_ooc_look()[source]
-
- -
-
-test_ooc()[source]
-
- -
-
-test_ic()[source]
-
- -
-
-test_ic__other_object()[source]
-
- -
-
-test_ic__nonaccess()[source]
-
- -
-
-test_password()[source]
-
- -
-
-test_option()[source]
-
- -
-
-test_who()[source]
-
- -
-
-test_quit()[source]
-
- -
-
-test_sessions()[source]
-
- -
-
-test_color_test()[source]
-
- -
-
-test_char_create()[source]
-
- -
-
-test_char_delete()[source]
-
- -
-
-test_quell()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestBuilding(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_create()[source]
-
- -
-
-test_examine()[source]
-
- -
-
-test_set_obj_alias()[source]
-
- -
-
-test_copy()[source]
-
- -
-
-test_attribute_commands()[source]
-
- -
-
-test_nested_attribute_commands()[source]
-
- -
-
-test_split_nested_attr()[source]
-
- -
-
-test_do_nested_lookup()[source]
-
- -
-
-test_name()[source]
-
- -
-
-test_desc()[source]
-
- -
-
-test_empty_desc()[source]
-

empty desc sets desc as ‘’

-
- -
-
-test_desc_default_to_room()[source]
-

no rhs changes room’s desc

-
- -
-
-test_destroy()[source]
-
- -
-
-test_destroy_sequence()[source]
-
- -
-
-test_dig()[source]
-
- -
-
-test_tunnel()[source]
-
- -
-
-test_tunnel_exit_typeclass()[source]
-
- -
-
-test_exit_commands()[source]
-
- -
-
-test_set_home()[source]
-
- -
-
-test_list_cmdsets()[source]
-
- -
-
-test_typeclass()[source]
-
- -
-
-test_lock()[source]
-
- -
-
-test_find()[source]
-
- -
-
-test_script()[source]
-
- -
-
-test_teleport()[source]
-
- -
-
-test_tag()[source]
-
- -
-
-test_spawn()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestComms(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-setUp()[source]
-

Sets up testing environment

-
- -
-
-test_toggle_com()[source]
-
- -
-
-test_channels()[source]
-
- -
-
-test_all_com()[source]
-
- -
-
-test_clock()[source]
-
- -
-
-test_cdesc()[source]
-
- -
-
-test_cemit()[source]
-
- -
-
-test_cwho()[source]
-
- -
-
-test_page()[source]
-
- -
-
-test_cboot()[source]
-
- -
-
-test_cdestroy()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestBatchProcess(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_batch_commands()[source]
-
- -
- -
-
-class evennia.commands.default.tests.CmdInterrupt(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Base command

-
-
Usage:

command [args]

-
-
-

This is the base command class. Inherit from this -to create new commands.

-

The cmdhandler makes the following variables available to the -command methods (so you can always assume them to be there): -self.caller - the game object calling the command -self.cmdstring - the command name used to trigger this command (allows

-
-

you to know which alias was used, for example)

-
-
-
cmd.args - everything supplied to the command following the cmdstring

(this is usually what is parsed in self.parse())

-
-
cmd.cmdset - the cmdset from which this command was matched (useful only

seldomly, notably for help-type commands, to create dynamic -help entries and lists)

-
-
cmd.obj - the object on which this command is defined. If a default command,

this is usually the same as caller.

-
-
-

cmd.rawstring - the full raw string input, including any args and no parsing.

-

The following class properties can/should be defined on your child class:

-

key - identifier for command (e.g. “look”) -aliases - (optional) list of aliases (e.g. [“l”, “loo”]) -locks - lock string (default is “cmd:all()”) -help_category - how to organize this help entry in help system

-
-

(default is “General”)

-
-

auto_help - defaults to True. Allows for turning off auto-help generation -arg_regex - (optional) raw string regex defining how the argument part of

-
-

the command should look in order to match for this command -(e.g. must it be a space between cmdname and arg?)

-
-
-
auto_help_display_key - (optional) if given, this replaces the string shown

in the auto-help listing. This is particularly useful for system-commands -whose actual key is not really meaningful.

-
-
-

(Note that if auto_help is on, this initial string is also used by the -system to create the help entry for the command, so it’s a good idea to -format it similar to this one). This behavior can be changed by -overriding the method ‘get_help’ of a command: by default, this -method returns cmd.__doc__ (that is, this very docstring, or -the docstring of your command). You can, however, extend or -replace this without disabling auto_help.

-
-
-key = 'interrupt'
-
- -
-
-parse()[source]
-

Once the cmdhandler has identified this as the command we -want, this function is run. If many of your commands have a -similar syntax (for example ‘cmd arg1 = arg2’) you should -simply define this once and just let other commands of the -same form inherit from this. See the docstring of this module -for which object properties are available to use (notably -self.args).

-
- -
-
-func()[source]
-

This is the actual executing part of the command. It is -called directly after self.parse(). See the docstring of this -module for which object properties are available (beyond those -set in self.parse())

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.commands.default.tests.TestInterruptCommand(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_interrupt_command()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestUnconnectedCommand(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_info_command()[source]
-
- -
- -
-
-class evennia.commands.default.tests.TestSystemCommands(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-
-
-test_simple_defaults()[source]
-
- -
-
-test_multimatch()[source]
-
- -
-
-test_channelcommand(mock_channeldb)[source]
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.default.unloggedin.html b/docs/0.9.5/api/evennia.commands.default.unloggedin.html deleted file mode 100644 index 2c2488fa8b..0000000000 --- a/docs/0.9.5/api/evennia.commands.default.unloggedin.html +++ /dev/null @@ -1,486 +0,0 @@ - - - - - - - - - evennia.commands.default.unloggedin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.commands.default.unloggedin

-

Commands that are available from the connect screen.

-
-
-class evennia.commands.default.unloggedin.CmdUnconnectedConnect(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

connect to the game

-
-
Usage (at login screen):

connect accountname password -connect “account name” “pass word”

-
-
-

Use the create command to first create an account before logging in.

-

If you have spaces in your name, enclose it in double quotes.

-
-
-key = 'connect'
-
- -
-
-aliases = ['conn', 'co', 'con']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s.*?|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Uses the Django admin api. Note that unlogged-in commands -have a unique position in that their func() receives -a session object instead of a source_object like all -other types of logged-in commands (this is because -there is no object yet before the account has logged in)

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.unloggedin.CmdUnconnectedCreate(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

create a new account account

-
-
Usage (at login screen):

create <accountname> <password> -create “account name” “pass word”

-
-
-

This creates a new account account.

-

If you have spaces in your name, enclose it in double quotes.

-
-
-key = 'create'
-
- -
-
-aliases = ['cr', 'cre']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s.*?|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Do checks and create account

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.unloggedin.CmdUnconnectedQuit(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

quit when in unlogged-in state

-
-
Usage:

quit

-
-
-

We maintain a different version of the quit command -here for unconnected accounts for the sake of simplicity. The logged in -version is a bit more complicated.

-
-
-key = 'quit'
-
- -
-
-aliases = ['qu', 'q']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Simply close the connection.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.unloggedin.CmdUnconnectedLook(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

look when in unlogged-in state

-
-
Usage:

look

-
-
-

This is an unconnected version of the look command for simplicity.

-

This is called by the server and kicks everything in gear. -All it does is display the connect screen.

-
-
-key = '__unloggedin_look_command'
-
- -
-
-aliases = ['l', 'look']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Show the connect screen.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.unloggedin.CmdUnconnectedHelp(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

get help when in unconnected-in state

-
-
Usage:

help

-
-
-

This is an unconnected version of the help command, -for simplicity. It shows a pane of info.

-
-
-key = 'help'
-
- -
-
-aliases = ['h', '?']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Shows help

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.unloggedin.CmdUnconnectedEncoding(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

set which text encoding to use in unconnected-in state

-
-
Usage:

encoding/switches [<encoding>]

-
-
Switches:

clear - clear your custom encoding

-
-
-

This sets the text encoding for communicating with Evennia. This is mostly -an issue only if you want to use non-ASCII characters (i.e. letters/symbols -not found in English). If you see that your characters look strange (or you -get encoding errors), you should use this command to set the server -encoding to be the same used in your client program.

-

Common encodings are utf-8 (default), latin-1, ISO-8859-1 etc.

-

If you don’t submit an encoding, the current encoding will be displayed -instead.

-
-
-key = 'encoding'
-
- -
-
-aliases = ['encode']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Sets the encoding.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.unloggedin.CmdUnconnectedInfo(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Provides MUDINFO output, so that Evennia games can be added to Mudconnector -and Mudstats. Sadly, the MUDINFO specification seems to have dropped off the -face of the net, but it is still used by some crawlers. This implementation -was created by looking at the MUDINFO implementation in MUX2, TinyMUSH, Rhost, -and PennMUSH.

-
-
-key = 'info'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.commands.default.unloggedin.CmdUnconnectedScreenreader(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Activate screenreader mode.

-
-
Usage:

screenreader

-
-
-

Used to flip screenreader mode on and off before logging in (when -logged in, use option screenreader on).

-
-
-key = 'screenreader'
-
- -
-
-func()[source]
-

Flips screenreader setting.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.commands.html b/docs/0.9.5/api/evennia.commands.html deleted file mode 100644 index e2284915bb..0000000000 --- a/docs/0.9.5/api/evennia.commands.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - evennia.commands — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.comms.admin.html b/docs/0.9.5/api/evennia.comms.admin.html deleted file mode 100644 index 9d40926149..0000000000 --- a/docs/0.9.5/api/evennia.comms.admin.html +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - - - - evennia.comms.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.comms.admin

-

This defines how Comm models are displayed in the web admin interface.

-
-
-class evennia.comms.admin.ChannelAttributeInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.AttributeInline

-

Inline display of Channel Attribute - experimental

-
-
-model
-

alias of evennia.comms.models.ChannelDB_db_attributes

-
- -
-
-related_field = 'channeldb'
-
- -
-
-property media
-
- -
- -
-
-class evennia.comms.admin.ChannelTagInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.TagInline

-

Inline display of Channel Tags - experimental

-
-
-model
-

alias of evennia.comms.models.ChannelDB_db_tags

-
- -
-
-related_field = 'channeldb'
-
- -
-
-property media
-
- -
- -
-
-class evennia.comms.admin.MsgAdmin(model, admin_site)[source]
-

Bases: django.contrib.admin.options.ModelAdmin

-

Defines display for Msg objects

-
-
-list_display = ('id', 'db_date_created', 'db_sender', 'db_receivers', 'db_channels', 'db_message', 'db_lock_storage')
-
- -
- -
- -
-
-ordering = ['db_date_created', 'db_sender', 'db_receivers', 'db_channels']
-
- -
-
-search_fields = ['id', '^db_date_created', '^db_message']
-
- -
-
-save_as = True
-
- -
-
-save_on_top = True
-
- -
- -
- -
-
-property media
-
- -
- -
-
-class evennia.comms.admin.ChannelAdmin(model, admin_site)[source]
-

Bases: django.contrib.admin.options.ModelAdmin

-

Defines display for Channel objects

-
-
-inlines = [<class 'evennia.comms.admin.ChannelTagInline'>, <class 'evennia.comms.admin.ChannelAttributeInline'>]
-
- -
-
-list_display = ('id', 'db_key', 'db_lock_storage', 'subscriptions')
-
- -
- -
- -
-
-ordering = ['db_key']
-
- -
-
-search_fields = ['id', 'db_key', 'db_tags__db_key']
-
- -
-
-save_as = True
-
- -
-
-save_on_top = True
-
- -
- -
- -
-
-raw_id_fields = ('db_object_subscriptions', 'db_account_subscriptions')
-
- -
-
-fieldsets = ((None, {'fields': (('db_key',), 'db_lock_storage', 'db_account_subscriptions', 'db_object_subscriptions')}),)
-
- -
-
-subscriptions(obj)[source]
-

Helper method to get subs from a channel.

-
-
Parameters
-

obj (Channel) – The channel to get subs from.

-
-
-
- -
-
-save_model(request, obj, form, change)[source]
-

Model-save hook.

-
-
Parameters
-
    -
  • request (Request) – Incoming request.

  • -
  • obj (Object) – Database object.

  • -
  • form (Form) – Form instance.

  • -
  • change (bool) – If this is a change or a new object.

  • -
-
-
-
- -
-
-response_add(request, obj, post_url_continue=None)[source]
-

Determine the HttpResponse for the add_view stage.

-
- -
-
-property media
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.comms.channelhandler.html b/docs/0.9.5/api/evennia.comms.channelhandler.html deleted file mode 100644 index 0b91a8022c..0000000000 --- a/docs/0.9.5/api/evennia.comms.channelhandler.html +++ /dev/null @@ -1,332 +0,0 @@ - - - - - - - - - evennia.comms.channelhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.comms.channelhandler

-

The channel handler, accessed from this module as CHANNEL_HANDLER is a -singleton that handles the stored set of channels and how they are -represented against the cmdhandler.

-

If there is a channel named ‘newbie’, we want to be able to just write

-
-

newbie Hello!

-
-

For this to work, ‘newbie’, the name of the channel, must be -identified by the cmdhandler as a command name. The channelhandler -stores all channels as custom ‘commands’ that the cmdhandler can -import and look through.

-

> Warning - channel names take precedence over command names, so make -sure to not pick clashing channel names.

-

Unless deleting a channel you normally don’t need to bother about the -channelhandler at all - the create_channel method handles the update.

-

To delete a channel cleanly, delete the channel object, then call -update() on the channelhandler. Or use Channel.objects.delete() which -does this for you.

-
-
-class evennia.comms.channelhandler.ChannelCommand(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

{channelkey} channel

-

{channeldesc}

-
-
Usage:

{lower_channelkey} <message> -{lower_channelkey}/history [start] -{lower_channelkey} off - mutes the channel -{lower_channelkey} on - unmutes the channel

-
-
Switch:
-
history: View 20 previous messages, either from the end or

from <start> number of messages from the end.

-
-
-
-
-

Example

-

{lower_channelkey} Hello World! -{lower_channelkey}/history -{lower_channelkey}/history 30

-
-
-is_channel = True
-
- -
-
-key = 'general'
-
- -
-
-help_category = 'channel names'
-
- -
-
-obj = None
-
- -
-
-arg_regex = re.compile('\\s.*?|/history.*?', re.IGNORECASE)
-
- -
-
-parse()[source]
-

Simple parser

-
- -
-
-func()[source]
-

Create a new message and send it to channel, using -the already formatted input.

-
- -
-
-get_extra_info(caller, **kwargs)[source]
-

Let users know that this command is for communicating on a channel.

-
-
Parameters
-

caller (TypedObject) – A Character or Account who has entered an ambiguous command.

-
-
Returns
-

A string with identifying information to disambiguate the object, conventionally with a preceding space.

-
-
-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.comms.channelhandler.ChannelHandler[source]
-

Bases: object

-

The ChannelHandler manages all active in-game channels and -dynamically creates channel commands for users so that they can -just give the channel’s key or alias to write to it. Whenever a -new channel is created in the database, the update() method on -this handler must be called to sync it with the database (this is -done automatically if creating the channel with -evennia.create_channel())

-
-
-__init__()[source]
-

Initializes the channel handler’s internal state.

-
- -
-
-clear()[source]
-

Reset the cache storage.

-
- -
-
-add(channel)[source]
-

Add an individual channel to the handler. This is called -whenever a new channel is created.

-
-
Parameters
-

channel (Channel) – The channel to add.

-
-
-

Notes

-

To remove a channel, simply delete the channel object and -run self.update on the handler. This should usually be -handled automatically by one of the deletion methos of -the Channel itself.

-
- -
-
-add_channel(channel)
-

Add an individual channel to the handler. This is called -whenever a new channel is created.

-
-
Parameters
-

channel (Channel) – The channel to add.

-
-
-

Notes

-

To remove a channel, simply delete the channel object and -run self.update on the handler. This should usually be -handled automatically by one of the deletion methos of -the Channel itself.

-
- -
-
-remove(channel)[source]
-

Remove channel from channelhandler. This will also delete it.

-
-
Parameters
-

channel (Channel) – Channel to remove/delete.

-
-
-
- -
-
-update()[source]
-

Updates the handler completely, including removing old removed -Channel objects. This must be called after deleting a Channel.

-
- -
-
-get(channelname=None)[source]
-

Get a channel from the handler, or all channels

-
-
Parameters
-

channelame (str, optional) – Channel key, case insensitive.

-
-
-
-
Returns
-
channels (list): The matching channels in a list, or all

channels in the handler.

-
-
-
-
-
- -
-
-get_cmdset(source_object)[source]
-

Retrieve cmdset for channels this source_object has -access to send to.

-
-
Parameters
-

source_object (Object) – An object subscribing to one -or more channels.

-
-
Returns
-

cmdsets (list)

-
-
The Channel-Cmdsets source_object has

access to.

-
-
-

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.comms.comms.html b/docs/0.9.5/api/evennia.comms.comms.html deleted file mode 100644 index 94995894fc..0000000000 --- a/docs/0.9.5/api/evennia.comms.comms.html +++ /dev/null @@ -1,805 +0,0 @@ - - - - - - - - - evennia.comms.comms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.comms.comms

-

Base typeclass for in-game Channels.

-
-
-class evennia.comms.comms.DefaultChannel(*args, **kwargs)[source]
-

Bases: evennia.comms.models.ChannelDB

-

This is the base class for all Channel Comms. Inherit from this to -create different types of communication channels.

-
-
-objects = <evennia.comms.managers.ChannelManager object>
-
- -
-
-at_first_save()[source]
-

Called by the typeclass system the very first time the channel -is saved to the database. Generally, don’t overload this but -the hooks called by this method.

-
- -
-
-basetype_setup()[source]
-
- -
-
-at_channel_creation()[source]
-

Called once, when the channel is first created.

-
- -
-
-has_connection(subscriber)[source]
-

Checks so this account is actually listening -to this channel.

-
-
Parameters
-

subscriber (Account or Object) – Entity to check.

-
-
Returns
-

has_sub (bool)

-
-
Whether the subscriber is subscribing to

this channel or not.

-
-
-

-
-
-

Notes

-
-
This will first try Account subscribers and only try Object

if the Account fails.

-
-
-
- -
-
-property mutelist
-
- -
-
-property wholist
-
- -
-
-mute(subscriber, **kwargs)[source]
-

Adds an entity to the list of muted subscribers. -A muted subscriber will no longer see channel messages, -but may use channel commands.

-
-
Parameters
-
    -
  • subscriber (Object or Account) – Subscriber to mute.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-unmute(subscriber, **kwargs)[source]
-

Removes an entity to the list of muted subscribers. A muted subscriber will no longer see channel messages, -but may use channel commands.

-
-
Parameters
-
    -
  • subscriber (Object or Account) – The subscriber to unmute.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-connect(subscriber, **kwargs)[source]
-

Connect the user to this channel. This checks access.

-
-
Parameters
-
    -
  • subscriber (Account or Object) – the entity to subscribe -to this channel.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

success (bool)

-
-
Whether or not the addition was

successful.

-
-
-

-
-
-
- -
-
-disconnect(subscriber, **kwargs)[source]
-

Disconnect entity from this channel.

-
-
Parameters
-
    -
  • subscriber (Account of Object) – the -entity to disconnect.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

success (bool)

-
-
Whether or not the removal was

successful.

-
-
-

-
-
-
- -
-
-access(accessing_obj, access_type='listen', default=False, no_superuser_bypass=False, **kwargs)[source]
-

Determines if another object has permission to access.

-
-
Parameters
-
    -
  • accessing_obj (Object) – Object trying to access this one.

  • -
  • access_type (str, optional) – Type of access sought.

  • -
  • default (bool, optional) – What to return if no lock of access_type was found

  • -
  • no_superuser_bypass (bool, optional) – Turns off superuser -lock bypass. Be careful with this one.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

return (bool) – Result of lock check.

-
-
-
- -
-
-classmethod create(key, account=None, *args, **kwargs)[source]
-

Creates a basic Channel with default parameters, unless otherwise -specified or extended.

-

Provides a friendlier interface to the utils.create_channel() function.

-
-
Parameters
-
    -
  • key (str) – This must be unique.

  • -
  • account (Account) – Account to attribute this object to.

  • -
-
-
Keyword Arguments
-
    -
  • aliases (list of str) – List of alternative (likely shorter) keynames.

  • -
  • description (str) – A description of the channel, for use in listings.

  • -
  • locks (str) – Lockstring.

  • -
  • keep_log (bool) – Log channel throughput.

  • -
  • typeclass (str or class) – The typeclass of the Channel (not -often used).

  • -
  • ip (str) – IP address of creator (for object auditing).

  • -
-
-
Returns
-

channel (Channel) – A newly created Channel. -errors (list): A list of errors in string form, if any.

-
-
-
- -
-
-delete()[source]
-

Deletes channel while also cleaning up channelhandler.

-
- -
-
-message_transform(msgobj, emit=False, prefix=True, sender_strings=None, external=False, **kwargs)[source]
-

Generates the formatted string sent to listeners on a channel.

-
-
Parameters
-
    -
  • msgobj (Msg) – Message object to send.

  • -
  • emit (bool, optional) – In emit mode the message is not associated -with a specific sender name.

  • -
  • prefix (bool, optional) – Prefix msg with a text given by self.channel_prefix.

  • -
  • sender_strings (list, optional) – Used by bots etc, one string per external sender.

  • -
  • external (bool, optional) – If this is an external sender or not.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-distribute_message(msgobj, online=False, **kwargs)[source]
-

Method for grabbing all listeners that a message should be -sent to on this channel, and sending them a message.

-
-
Parameters
-
    -
  • msgobj (Msg or TempMsg) – Message to distribute.

  • -
  • online (bool) – Only send to receivers who are actually online -(not currently used):

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

This is also where logging happens, if enabled.

-
- -
-
-msg(msgobj, header=None, senders=None, sender_strings=None, keep_log=None, online=False, emit=False, external=False)[source]
-

Send the given message to all accounts connected to channel. Note that -no permission-checking is done here; it is assumed to have been -done before calling this method. The optional keywords are not used if -persistent is False.

-
-
Parameters
-
    -
  • msgobj (Msg, TempMsg or str) – If a Msg/TempMsg, the remaining -keywords will be ignored (since the Msg/TempMsg object already -has all the data). If a string, this will either be sent as-is -(if persistent=False) or it will be used together with header -and senders keywords to create a Msg instance on the fly.

  • -
  • header (str, optional) – A header for building the message.

  • -
  • senders (Object, Account or list, optional) – Optional if persistent=False, used -to build senders for the message.

  • -
  • sender_strings (list, optional) – Name strings of senders. Used for external -connections where the sender is not an account or object. -When this is defined, external will be assumed. The list will be -filtered so each sender-string only occurs once.

  • -
  • keep_log (bool or None, optional) – This allows to temporarily change the logging status of -this channel message. If None, the Channel’s keep_log Attribute will -be used. If True or False, that logging status will be used for this -message only (note that for unlogged channels, a True value here will -create a new log file only for this message).

  • -
  • online (bool, optional) – online. Otherwise, messages all accounts connected. This can -make things faster, but may not trigger listeners on accounts -that are offline.

  • -
  • emit (bool, optional) – not to be directly associated with a name.

  • -
  • external (bool, optional) – Treat this message as being -agnostic of its sender.

  • -
-
-
Returns
-

success (bool)

-
-
Returns True if message sending was

successful, False otherwise.

-
-
-

-
-
-
- -
-
-tempmsg(message, header=None, senders=None)[source]
-

A wrapper for sending non-persistent messages.

-
-
Parameters
-
    -
  • message (str) – Message to send.

  • -
  • header (str, optional) – Header of message to send.

  • -
  • senders (Object or list, optional) – Senders of message to send.

  • -
-
-
-
- -
-
-channel_prefix(msg=None, emit=False, **kwargs)[source]
-

Hook method. How the channel should prefix itself for users.

-
-
Parameters
-
    -
  • msg (str, optional) – Prefix text

  • -
  • emit (bool, optional) – Switches to emit mode, which usually -means to not prefix the channel’s info.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

prefix (str) – The created channel prefix.

-
-
-
- -
-
-format_senders(senders=None, **kwargs)[source]
-

Hook method. Function used to format a list of sender names.

-
-
Parameters
-
    -
  • senders (list) – Sender object names.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

formatted_list (str) – The list of names formatted appropriately.

-
-
-

Notes

-

This function exists separately so that external sources -can use it to format source names in the same manner as -normal object/account names.

-
- -
-
-pose_transform(msgobj, sender_string, **kwargs)[source]
-

Hook method. Detects if the sender is posing, and modifies the -message accordingly.

-
-
Parameters
-
    -
  • msgobj (Msg or TempMsg) – The message to analyze for a pose.

  • -
  • sender_string (str) – The name of the sender/poser.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

string (str)

-
-
A message that combines the sender_string

component with msg in different ways depending on if a -pose was performed or not (this must be analyzed by the -hook).

-
-
-

-
-
-
- -
-
-format_external(msgobj, senders, emit=False, **kwargs)[source]
-

Hook method. Used for formatting external messages. This is -needed as a separate operation because the senders of external -messages may not be in-game objects/accounts, and so cannot -have things like custom user preferences.

-
-
Parameters
-
    -
  • msgobj (Msg or TempMsg) – The message to send.

  • -
  • senders (list) – Strings, one per sender.

  • -
  • emit (bool, optional) – A sender-agnostic message or not.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

transformed (str) – A formatted string.

-
-
-
- -
-
-format_message(msgobj, emit=False, **kwargs)[source]
-

Hook method. Formats a message body for display.

-
-
Parameters
-
    -
  • msgobj (Msg or TempMsg) – The message object to send.

  • -
  • emit (bool, optional) – The message is agnostic of senders.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

transformed (str) – The formatted message.

-
-
-
- -
-
-pre_join_channel(joiner, **kwargs)[source]
-

Hook method. Runs right before a channel is joined. If this -returns a false value, channel joining is aborted.

-
-
Parameters
-
    -
  • joiner (object) – The joining object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

should_join (bool) – If False, channel joining is aborted.

-
-
-
- -
-
-post_join_channel(joiner, **kwargs)[source]
-

Hook method. Runs right after an object or account joins a channel.

-
-
Parameters
-
    -
  • joiner (object) – The joining object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-pre_leave_channel(leaver, **kwargs)[source]
-

Hook method. Runs right before a user leaves a channel. If this returns a false -value, leaving the channel will be aborted.

-
-
Parameters
-
    -
  • leaver (object) – The leaving object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

should_leave (bool) – If False, channel parting is aborted.

-
-
-
- -
-
-post_leave_channel(leaver, **kwargs)[source]
-

Hook method. Runs right after an object or account leaves a channel.

-
-
Parameters
-
    -
  • leaver (object) – The leaving object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-pre_send_message(msg, **kwargs)[source]
-

Hook method. Runs before a message is sent to the channel and -should return the message object, after any transformations. -If the message is to be discarded, return a false value.

-
-
Parameters
-
    -
  • msg (Msg or TempMsg) – Message to send.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

result (Msg, TempMsg or bool) – If False, abort send.

-
-
-
- -
-
-post_send_message(msg, **kwargs)[source]
-

Hook method. Run after a message is sent to the channel.

-
-
Parameters
-
    -
  • msg (Msg or TempMsg) – Message sent.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_init()[source]
-

Hook method. This is always called whenever this channel is -initiated – that is, whenever it its typeclass is cached from -memory. This happens on-demand first time the channel is used -or activated in some way after being created but also after -each server restart or reload.

-
- -
-
-web_get_admin_url()[source]
-

Returns the URI path for the Django Admin page for this object.

-

ex. Account#1 = ‘/admin/accounts/accountdb/1/change/’

-
-
Returns
-

path (str) – URI path to Django Admin page for object.

-
-
-
- -
-
-classmethod web_get_create_url()[source]
-

Returns the URI path for a View that allows users to create new -instances of this object.

-

ex. Chargen = ‘/characters/create/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘channel-create’ would be referenced by this method.

-

ex. -url(r’channels/create/’, ChannelCreateView.as_view(), name=’channel-create’)

-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can create new objects is the -developer’s responsibility.

-
-
Returns
-

path (str) – URI path to object creation page, if defined.

-
-
-
- -
-
-web_get_detail_url()[source]
-

Returns the URI path for a View that allows users to view details for -this object.

-

ex. Oscar (Character) = ‘/characters/oscar/1/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘channel-detail’ would be referenced by this method.

-

ex. -url(r’channels/(?P<slug>[wd-]+)/$’,

-
-

ChannelDetailView.as_view(), name=’channel-detail’)

-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can view this object is the developer’s -responsibility.

-
-
Returns
-

path (str) – URI path to object detail page, if defined.

-
-
-
- -
-
-web_get_update_url()[source]
-

Returns the URI path for a View that allows users to update this -object.

-

ex. Oscar (Character) = ‘/characters/oscar/1/change/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘channel-update’ would be referenced by this method.

-

ex. -url(r’channels/(?P<slug>[wd-]+)/(?P<pk>[0-9]+)/change/$’,

-
-

ChannelUpdateView.as_view(), name=’channel-update’)

-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can modify objects is the developer’s -responsibility.

-
-
Returns
-

path (str) – URI path to object update page, if defined.

-
-
-
- -
-
-web_get_delete_url()[source]
-

Returns the URI path for a View that allows users to delete this object.

-

ex. Oscar (Character) = ‘/characters/oscar/1/delete/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘channel-delete’ would be referenced by this method.

-

ex. -url(r’channels/(?P<slug>[wd-]+)/(?P<pk>[0-9]+)/delete/$’,

-
-

ChannelDeleteView.as_view(), name=’channel-delete’)

-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can delete this object is the developer’s -responsibility.

-
-
Returns
-

path (str) – URI path to object deletion page, if defined.

-
-
-
- -
-
-get_absolute_url()
-

Returns the URI path for a View that allows users to view details for -this object.

-

ex. Oscar (Character) = ‘/characters/oscar/1/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘channel-detail’ would be referenced by this method.

-

ex. -url(r’channels/(?P<slug>[wd-]+)/$’,

-
-

ChannelDetailView.as_view(), name=’channel-detail’)

-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can view this object is the developer’s -responsibility.

-
-
Returns
-

path (str) – URI path to object detail page, if defined.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.comms.models.ChannelDB.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.comms.models.ChannelDB.MultipleObjectsReturned

-
- -
-
-path = 'evennia.comms.comms.DefaultChannel'
-
- -
-
-typename = 'DefaultChannel'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.comms.html b/docs/0.9.5/api/evennia.comms.html deleted file mode 100644 index 9ce7534e73..0000000000 --- a/docs/0.9.5/api/evennia.comms.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - evennia.comms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.comms

-

This sub-package contains Evennia’s comms-system, a set of models and -handlers for in-game communication via channels and messages as well -as code related to external communication like IRC or RSS.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.comms.managers.html b/docs/0.9.5/api/evennia.comms.managers.html deleted file mode 100644 index 6ac6a72784..0000000000 --- a/docs/0.9.5/api/evennia.comms.managers.html +++ /dev/null @@ -1,402 +0,0 @@ - - - - - - - - - evennia.comms.managers — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.comms.managers

-

These managers define helper methods for accessing the database from -Comm system components.

-
-
-exception evennia.comms.managers.CommError[source]
-

Bases: Exception

-

Raised by comm system, to allow feedback to player when caught.

-
- -
-
-evennia.comms.managers.identify_object(inp)[source]
-

Helper function. Identifies if an object is an account or an object; -return its database model

-
-
Parameters
-

inp (any) – Entity to be idtified.

-
-
Returns
-

identified (tuple)

-
-
This is a tuple with (inp, identifier)

where identifier is one of “account”, “object”, “channel”, -“string”, “dbref” or None.

-
-
-

-
-
-
- -
-
-evennia.comms.managers.to_object(inp, objtype='account')[source]
-

Locates the object related to the given accountname or channel key. -If input was already the correct object, return it.

-
-
Parameters
-
    -
  • inp (any) – The input object/string

  • -
  • objtype (str) – Either ‘account’ or ‘channel’.

  • -
-
-
Returns
-

obj (object) – The correct object related to inp.

-
-
-
- -
-
-class evennia.comms.managers.MsgManager(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.managers.TypedObjectManager

-

This MsgManager implements methods for searching and manipulating -Messages directly from the database.

-

These methods will all return database objects (or QuerySets) -directly.

-

A Message represents one unit of communication, be it over a -Channel or via some form of in-game mail system. Like an e-mail, -it always has a sender and can have any number of receivers (some -of which may be Channels).

-
-
-identify_object(inp)[source]
-

Wrapper to identify_object if accessing via the manager directly.

-
-
Parameters
-

inp (any) – Entity to be idtified.

-
-
Returns
-

identified (tuple)

-
-
This is a tuple with (inp, identifier)

where identifier is one of “account”, “object”, “channel”, -“string”, “dbref” or None.

-
-
-

-
-
-
- -
-
-get_message_by_id(idnum)[source]
-

Retrieve message by its id.

-
-
Parameters
-

idnum (int or str) – The dbref to retrieve.

-
-
Returns
-

message (Msg) – The message.

-
-
-
- -
-
-get_messages_by_sender(sender, exclude_channel_messages=False)[source]
-

Get all messages sent by one entity - this could be either a -account or an object

-
-
Parameters
-
    -
  • sender (Account or Object) – The sender of the message.

  • -
  • exclude_channel_messages (bool, optional) – Only return messages -not aimed at a channel (that is, private tells for example)

  • -
-
-
Returns
-

messages (list) – List of matching messages

-
-
Raises
-

CommError – For incorrect sender types.

-
-
-
- -
-
-get_messages_by_receiver(recipient)[source]
-

Get all messages sent to one given recipient.

-
-
Parameters
-

recipient (Object, Account or Channel) – The recipient of the messages to search for.

-
-
Returns
-

messages (list) – Matching messages.

-
-
Raises
-

CommError – If the recipient is not of a valid type.

-
-
-
- -
-
-get_messages_by_channel(channel)[source]
-

Get all persistent messages sent to one channel.

-
-
Parameters
-

channel (Channel) – The channel to find messages for.

-
-
Returns
-

messages (list) – Persistent Msg objects saved for this channel.

-
-
-
- -
-
-search_message(sender=None, receiver=None, freetext=None, dbref=None)[source]
-

Search the message database for particular messages. At least -one of the arguments must be given to do a search.

-
-
Parameters
-
    -
  • sender (Object or Account, optional) – Get messages sent by a particular account or object

  • -
  • receiver (Object, Account or Channel, optional) – Get messages -received by a certain account,object or channel

  • -
  • freetext (str) – Search for a text string in a message. NOTE: -This can potentially be slow, so make sure to supply one of -the other arguments to limit the search.

  • -
  • dbref (int) – The exact database id of the message. This will override -all other search criteria since it’s unique and -always gives only one match.

  • -
-
-
Returns
-

messages (list or Msg) – A list of message matches or a single match if dbref was given.

-
-
-
- -
- -

Search the message database for particular messages. At least -one of the arguments must be given to do a search.

-
-
Parameters
-
    -
  • sender (Object or Account, optional) – Get messages sent by a particular account or object

  • -
  • receiver (Object, Account or Channel, optional) – Get messages -received by a certain account,object or channel

  • -
  • freetext (str) – Search for a text string in a message. NOTE: -This can potentially be slow, so make sure to supply one of -the other arguments to limit the search.

  • -
  • dbref (int) – The exact database id of the message. This will override -all other search criteria since it’s unique and -always gives only one match.

  • -
-
-
Returns
-

messages (list or Msg) – A list of message matches or a single match if dbref was given.

-
-
-
- -
- -
-
-class evennia.comms.managers.ChannelDBManager(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.managers.TypedObjectManager

-

This ChannelManager implements methods for searching and -manipulating Channels directly from the database.

-

These methods will all return database objects (or QuerySets) -directly.

-

A Channel is an in-game venue for communication. It’s essentially -representation of a re-sender: Users sends Messages to the -Channel, and the Channel re-sends those messages to all users -subscribed to the Channel.

-
-
-get_all_channels()[source]
-

Get all channels.

-
-
Returns
-

channels (list) – All channels in game.

-
-
-
- -
-
-get_channel(channelkey)[source]
-

Return the channel object if given its key. -Also searches its aliases.

-
-
Parameters
-

channelkey (str) – Channel key to search for.

-
-
Returns
-

channel (Channel or None) – A channel match.

-
-
-
- -
-
-get_subscriptions(subscriber)[source]
-

Return all channels a given entity is subscribed to.

-
-
Parameters
-

subscriber (Object or Account) – The one subscribing.

-
-
Returns
-

subscriptions (list) – Channel subscribed to.

-
-
-
- -
-
-search_channel(ostring, exact=True)[source]
-

Search the channel database for a particular channel.

-
-
Parameters
-
    -
  • ostring (str) – The key or database id of the channel.

  • -
  • exact (bool, optional) – Require an exact (but not -case sensitive) match.

  • -
-
-
-
- -
- -

Search the channel database for a particular channel.

-
-
Parameters
-
    -
  • ostring (str) – The key or database id of the channel.

  • -
  • exact (bool, optional) – Require an exact (but not -case sensitive) match.

  • -
-
-
-
- -
- -
-
-class evennia.comms.managers.ChannelManager(*args, **kwargs)[source]
-

Bases: evennia.comms.managers.ChannelDBManager, evennia.typeclasses.managers.TypeclassManager

-

Wrapper to group the typeclass manager to a consistent name.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.comms.models.html b/docs/0.9.5/api/evennia.comms.models.html deleted file mode 100644 index 56dcf0e997..0000000000 --- a/docs/0.9.5/api/evennia.comms.models.html +++ /dev/null @@ -1,754 +0,0 @@ - - - - - - - - - evennia.comms.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.comms.models

-

Models for the in-game communication system.

-

The comm system could take the form of channels, but can also be -adopted for storing tells or in-game mail.

-

The comsystem’s main component is the Message (Msg), which carries the -actual information between two parties. Msgs are stored in the -database and usually not deleted. A Msg always have one sender (a -user), but can have any number targets, both users and channels.

-

For non-persistent (and slightly faster) use one can also use the -TempMsg, which mimics the Msg API but without actually saving to the -database.

-

Channels are central objects that act as targets for Msgs. Accounts can -connect to channels by use of a ChannelConnect object (this object is -necessary to easily be able to delete connections on the fly).

-
-
-class evennia.comms.models.Msg(*args, **kwargs)[source]
-

Bases: evennia.utils.idmapper.models.SharedMemoryModel

-

A single message. This model describes all ooc messages -sent in-game, both to channels and between accounts.

-

The Msg class defines the following database fields (all -accessed via specific handler methods):

-
    -
  • db_sender_accounts: Account senders

  • -
  • db_sender_objects: Object senders

  • -
  • db_sender_scripts: Script senders

  • -
  • db_sender_external: External senders (defined as string names)

  • -
  • db_receivers_accounts: Receiving accounts

  • -
  • db_receivers_objects: Receiving objects

  • -
  • db_receivers_scripts: Receiveing scripts

  • -
  • db_receivers_channels: Receiving channels

  • -
  • db_header: Header text

  • -
  • db_message: The actual message text

  • -
  • db_date_created: time message was created / sent

  • -
  • db_hide_from_sender: bool if message should be hidden from sender

  • -
  • db_hide_from_receivers: list of receiver objects to hide message from

  • -
  • db_hide_from_channels: list of channels objects to hide message from

  • -
  • db_lock_storage: Internal storage of lock strings.

  • -
-
-
-db_sender_accounts
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_sender_objects
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_sender_scripts
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_sender_external
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_receivers_accounts
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_receivers_objects
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_receivers_scripts
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_receivers_channels
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_header
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_message
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_date_created
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_lock_storage
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_hide_from_accounts
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_hide_from_objects
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_hide_from_channels
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_tags
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-objects = <evennia.comms.managers.MsgManager object>
-
- -
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-locks[source]
-
- -
-
-tags[source]
-
- -
-
-property senders
-

Getter. Allows for value = self.sender

-
- -
-
-remove_sender(senders)[source]
-

Remove a single sender or a list of senders.

-
-
Parameters
-

senders (Account, Object, str or list) – Senders to remove.

-
-
-
- -
-
-property receivers
-

Getter. Allows for value = self.receivers. -Returns four lists of receivers: accounts, objects, scripts and channels.

-
- -
-
-remove_receiver(receivers)[source]
-

Remove a single receiver or a list of receivers.

-
-
Parameters
-

receivers (Account, Object, Script, Channel or list) – Receiver to remove.

-
-
-
- -
-
-property channels
-

Getter. Allows for value = self.channels. Returns a list of channels.

-
- -
-
-property hide_from
-

Getter. Allows for value = self.hide_from. -Returns 3 lists of accounts, objects and channels

-
- -
-
-access(accessing_obj, access_type='read', default=False)[source]
-

Checks lock access.

-
-
Parameters
-
    -
  • accessing_obj (Object or Account) – The object trying to gain access.

  • -
  • access_type (str, optional) – The type of lock access to check.

  • -
  • default (bool) – Fallback to use if access_type lock is not defined.

  • -
-
-
Returns
-

result (bool) – If access was granted or not.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-property date_created
-

A wrapper for getting database field db_date_created.

-
- -
-
-get_next_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=True, **kwargs)
-
- -
-
-get_previous_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=False, **kwargs)
-
- -
-
-property header
-

A wrapper for getting database field db_header.

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-property lock_storage
-

A wrapper for getting database field db_lock_storage.

-
- -
-
-property message
-

A wrapper for getting database field db_message.

-
- -
-
-path = 'evennia.comms.models.Msg'
-
- -
-
-property sender_external
-

A wrapper for getting database field db_sender_external.

-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
-
-class evennia.comms.models.TempMsg(senders=None, receivers=None, channels=None, message='', header='', type='', lockstring='', hide_from=None)[source]
-

Bases: object

-

This is a non-persistent object for sending temporary messages -that will not be stored. It mimics the “real” Msg object, but -doesn’t require sender to be given.

-
-
-__init__(senders=None, receivers=None, channels=None, message='', header='', type='', lockstring='', hide_from=None)[source]
-

Creates the temp message.

-
-
Parameters
-
    -
  • senders (any or list, optional) – Senders of the message.

  • -
  • receivers (Account, Object, Channel or list, optional) – Receivers of this message.

  • -
  • channels (Channel or list, optional) – Channels to send to.

  • -
  • message (str, optional) – Message to send.

  • -
  • header (str, optional) – Header of message.

  • -
  • type (str, optional) – Message class, if any.

  • -
  • lockstring (str, optional) – Lock for the message.

  • -
  • hide_from (Account, Object, Channel or list, optional) – Entities to hide this message from.

  • -
-
-
-
- -
-
-locks[source]
-
- -
-
-remove_sender(sender)[source]
-

Remove a sender or a list of senders.

-
-
Parameters
-

sender (Object, Account, str or list) – Senders to remove.

-
-
-
- -
-
-remove_receiver(receiver)[source]
-

Remove a receiver or a list of receivers

-
-
Parameters
-

receiver (Object, Account, Channel, str or list) – Receivers to remove.

-
-
-
- -
-
-access(accessing_obj, access_type='read', default=False)[source]
-

Checks lock access.

-
-
Parameters
-
    -
  • accessing_obj (Object or Account) – The object trying to gain access.

  • -
  • access_type (str, optional) – The type of lock access to check.

  • -
  • default (bool) – Fallback to use if access_type lock is not defined.

  • -
-
-
Returns
-

result (bool) – If access was granted or not.

-
-
-
- -
- -
-
-class evennia.comms.models.ChannelDB(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.models.TypedObject

-

This is the basis of a comm channel, only implementing -the very basics of distributing messages.

-

The Channel class defines the following database fields -beyond the ones inherited from TypedObject:

-
-
    -
  • db_account_subscriptions: The Account subscriptions.

  • -
  • db_object_subscriptions: The Object subscriptions.

  • -
-
-
-
-db_account_subscriptions
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_object_subscriptions
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-objects = <evennia.comms.managers.ChannelDBManager object>
-
- -
-
-subscriptions[source]
-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-channel_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_attributes
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_tags
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-get_next_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=True, **kwargs)
-
- -
-
-get_previous_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=False, **kwargs)
-
- -
-
-hide_from_channels_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-path = 'evennia.comms.models.ChannelDB'
-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.barter.html b/docs/0.9.5/api/evennia.contrib.barter.html deleted file mode 100644 index aaa45d309f..0000000000 --- a/docs/0.9.5/api/evennia.contrib.barter.html +++ /dev/null @@ -1,868 +0,0 @@ - - - - - - - - - evennia.contrib.barter — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.barter

-

Barter system

-

Evennia contribution - Griatch 2012

-

This implements a full barter system - a way for players to safely -trade items between each other using code rather than simple free-form -talking. The advantage of this is increased buy/sell safety but it -also streamlines the process and makes it faster when doing many -transactions (since goods are automatically exchanged once both -agree).

-

This system is primarily intended for a barter economy, but can easily -be used in a monetary economy as well – just let the “goods” on one -side be coin objects (this is more flexible than a simple “buy” -command since you can mix coins and goods in your trade).

-

In this module, a “barter” is generally referred to as a “trade”.

-
    -
  • Trade example

  • -
-

A trade (barter) action works like this: A and B are the parties.

-
    -
  1. opening a trade

  2. -
-

A: trade B: Hi, I have a nice extra sword. You wanna trade? -B sees: A says: “Hi, I have a nice extra sword. You wanna trade?”

-
-

A wants to trade with you. Enter ‘trade A <emote>’ to accept.

-
-

B: trade A: Hm, I could use a good sword … -A sees: B says: “Hm, I could use a good sword …

-
-

B accepts the trade. Use ‘trade help’ for aid.

-
-

B sees: You are now trading with A. Use ‘trade help’ for aid.

-
    -
  1. negotiating

  2. -
-

A: offer sword: This is a nice sword. I would need some rations in trade. -B sees: A says: “This is a nice sword. I would need some rations in trade.”

-
-

[A offers Sword of might.]

-
-

B evaluate sword -B sees: <Sword’s description and possibly stats> -B: offer ration: This is a prime ration. -A sees: B says: “This is a prime ration.”

-
-

[B offers iron ration]

-
-

A: say Hey, this is a nice sword, I need something more for it. -B sees: A says: “Hey this is a nice sword, I need something more for it.” -B: offer sword,apple: Alright. I will also include a magic apple. That’s my last offer. -A sees: B says: “Alright, I will also include a magic apple. That’s my last offer.”

-
-

[B offers iron ration and magic apple]

-
-

A accept: You are killing me here, but alright. -B sees: A says: “You are killing me here, but alright.”

-
-

[A accepts your offer. You must now also accept.]

-
-
-
B accept: Good, nice making business with you.

You accept the deal. Deal is made and goods changed hands.

-
-
A sees: B says: “Good, nice making business with you.”

B accepts the deal. Deal is made and goods changed hands.

-
-
-

At this point the trading system is exited and the negotiated items -are automatically exchanged between the parties. In this example B was -the only one changing their offer, but also A could have changed their -offer until the two parties found something they could agree on. The -emotes are optional but useful for RP-heavy worlds.

-
    -
  • Technical info

  • -
-

The trade is implemented by use of a TradeHandler. This object is a -common place for storing the current status of negotiations. It is -created on the object initiating the trade, and also stored on the -other party once that party agrees to trade. The trade request times -out after a certain time - this is handled by a Script. Once trade -starts, the CmdsetTrade cmdset is initiated on both parties along with -the commands relevant for the trading.

-
    -
  • Ideas for NPC bartering:

  • -
-

This module is primarily intended for trade between two players. But -it can also in principle be used for a player negotiating with an -AI-controlled NPC. If the NPC uses normal commands they can use it -directly – but more efficient is to have the NPC object send its -replies directly through the tradehandler to the player. One may want -to add some functionality to the decline command, so players can -decline specific objects in the NPC offer (decline <object>) and allow -the AI to maybe offer something else and make it into a proper -barter. Along with an AI that “needs” things or has some sort of -personality in the trading, this can make bartering with NPCs at least -moderately more interesting than just plain ‘buy’.

-
    -
  • Installation:

  • -
-

Just import the CmdTrade command into (for example) the default -cmdset. This will make the trade (or barter) command available -in-game.

-
-
-class evennia.contrib.barter.TradeTimeout(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This times out the trade request, in case player B did not reply in time.

-
-
-at_script_creation()[source]
-

Called when script is first created

-
- -
-
-at_repeat()[source]
-

called once

-
- -
-
-is_valid()[source]
-

Only valid if the trade has not yet started

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.barter.TradeTimeout'
-
- -
-
-typename = 'TradeTimeout'
-
- -
- -
-
-class evennia.contrib.barter.TradeHandler(part_a, part_b)[source]
-

Bases: object

-

Objects of this class handles the ongoing trade, notably storing the current -offers from each side and wether both have accepted or not.

-
-
-__init__(part_a, part_b)[source]
-

Initializes the trade. This is called when part A tries to -initiate a trade with part B. The trade will not start until -part B repeats this command (B will then call the self.join() -command)

-
-
Parameters
-
    -
  • part_a (object) – The party trying to start barter.

  • -
  • part_b (object) – The party asked to barter.

  • -
-
-
-

Notes

-

We also store the back-reference from the respective party -to this object.

-
- -
-
-msg_other(sender, string)[source]
-

Relay a message to the other party without needing to know -which party that is. This allows the calling command to not -have to worry about which party they are in the handler.

-
-
Parameters
-
    -
  • sender (object) – One of A or B. The method will figure -out the other party to send to.

  • -
  • string (str) – Text to send.

  • -
-
-
-
- -
-
-get_other(party)[source]
-

Returns the other party of the trade

-
-
Parameters
-

party (object) – One of the parties of the negotiation

-
-
Returns
-

party_other (object) – The other party, not the first party.

-
-
-
- -
-
-join(part_b)[source]
-

This is used once B decides to join the trade

-
-
Parameters
-

part_b (object) – The party accepting the barter.

-
-
-
- -
-
-unjoin(part_b)[source]
-

This is used if B decides not to join the trade.

-
-
Parameters
-

part_b (object) – The party leaving the barter.

-
-
-
- -
-
-offer(party, *args)[source]
-

Change the current standing offer. We leave it up to the -command to do the actual checks that the offer consists -of real, valid, objects.

-
-
Parameters
-
    -
  • party (object) – Who is making the offer

  • -
  • args (objects or str) – Offerings.

  • -
-
-
-
- -
-
-list()[source]
-

List current offers.

-
-
Returns
-

offers (tuple) – A tuple with two lists, (A_offers, B_offers).

-
-
-
- -
-
-search(offername)[source]
-

Search current offers.

-
-
Parameters
-

offername (str or int) – Object to search for, or its index in -the list of offered items.

-
-
Returns
-

offer (object) – An object on offer, based on the search criterion.

-
-
-
- -
-
-accept(party)[source]
-

Accept the current offer.

-
-
Parameters
-

party (object) – The party accepting the deal.

-
-
Returns
-

result (object)

-
-
True if this closes the deal, False

otherwise

-
-
-

-
-
-

Notes

-

This will only close the deal if both parties have -accepted independently. This is done by calling the -finish() method.

-
- -
-
-decline(party)[source]
-

Decline the offer (or change one’s mind).

-
-
Parameters
-

party (object) – Party declining the deal.

-
-
Returns
-

did_decline (bool)

-
-
True if there was really an

accepted status to change, False otherwise.

-
-
-

-
-
-

Notes

-

If previously having used the accept command, this -function will only work as long as the other party has not -yet accepted.

-
- -
-
-finish(force=False)[source]
-

Conclude trade - move all offers and clean up

-
-
Parameters
-

force (bool, optional) – Force cleanup regardless of if the -trade was accepted or not (if not, no goods will change -hands but trading will stop anyway)

-
-
Returns
-

result (bool) – If the finish was successful.

-
-
-
- -
- -
-
-class evennia.contrib.barter.CmdTradeBase(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Base command for Trade commands to inherit from. Implements the -custom parsing.

-
-
-parse()[source]
-

Parse the relevant parts and make it easily -available to the command

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'command'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.barter.CmdTradeHelp(**kwargs)[source]
-

Bases: evennia.contrib.barter.CmdTradeBase

-

help command for the trade system.

-
-
Usage:

trade help

-
-
-

Displays help for the trade commands.

-
-
-key = 'trade help'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'trade'
-
- -
-
-func()[source]
-

Show the help

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.barter.CmdOffer(**kwargs)[source]
-

Bases: evennia.contrib.barter.CmdTradeBase

-

offer one or more items in trade.

-
-
Usage:

offer <object> [, object2, …][:emote]

-
-
-

Offer objects in trade. This will replace the currently -standing offer.

-
-
-key = 'offer'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'trading'
-
- -
-
-func()[source]
-

implement the offer

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.barter.CmdAccept(**kwargs)[source]
-

Bases: evennia.contrib.barter.CmdTradeBase

-

accept the standing offer

-
-
Usage:

accept [:emote] -agreee [:emote]

-
-
-

This will accept the current offer. The other party must also accept -for the deal to go through. You can use the ‘decline’ command to change -your mind as long as the other party has not yet accepted. You can inspect -the current offer using the ‘offers’ command.

-
-
-key = 'accept'
-
- -
-
-aliases = ['agree']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'trading'
-
- -
-
-func()[source]
-

accept the offer

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.barter.CmdDecline(**kwargs)[source]
-

Bases: evennia.contrib.barter.CmdTradeBase

-

decline the standing offer

-
-
Usage:

decline [:emote]

-
-
-

This will decline a previously ‘accept’ed offer (so this allows you to -change your mind). You can only use this as long as the other party -has not yet accepted the deal. Also, changing the offer will automatically -decline the old offer.

-
-
-key = 'decline'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'trading'
-
- -
-
-func()[source]
-

decline the offer

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.barter.CmdEvaluate(**kwargs)[source]
-

Bases: evennia.contrib.barter.CmdTradeBase

-

evaluate objects on offer

-
-
Usage:

evaluate <offered object>

-
-
-

This allows you to examine any object currently on offer, to -determine if it’s worth your while.

-
-
-key = 'evaluate'
-
- -
-
-aliases = ['eval']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'trading'
-
- -
-
-func()[source]
-

evaluate an object

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.barter.CmdStatus(**kwargs)[source]
-

Bases: evennia.contrib.barter.CmdTradeBase

-

show a list of the current deal

-
-
Usage:

status -deal -offers

-
-
-

Shows the currently suggested offers on each sides of the deal. To -accept the current deal, use the ‘accept’ command. Use ‘offer’ to -change your deal. You might also want to use ‘say’, ‘emote’ etc to -try to influence the other part in the deal.

-
-
-key = 'status'
-
- -
-
-aliases = ['offers', 'deal']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'trading'
-
- -
-
-func()[source]
-

Show the current deal

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.barter.CmdFinish(**kwargs)[source]
-

Bases: evennia.contrib.barter.CmdTradeBase

-

end the trade prematurely

-
-
Usage:

end trade [:say] -finish trade [:say]

-
-
-

This ends the trade prematurely. No trade will take place.

-
-
-key = 'end trade'
-
- -
-
-aliases = ['finish trade']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'trading'
-
- -
-
-func()[source]
-

end trade

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.barter.CmdsetTrade(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

This cmdset is added when trade is initated. It is handled by the -trade event handler.

-
-
-key = 'cmdset_trade'
-
- -
-
-at_cmdset_creation()[source]
-

Called when cmdset is created

-
- -
-
-path = 'evennia.contrib.barter.CmdsetTrade'
-
- -
- -
-
-class evennia.contrib.barter.CmdTrade(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Initiate trade with another party

-
-
Usage:

trade <other party> [:say] -trade <other party> accept [:say] -trade <other party> decline [:say]

-
-
-

Initiate trade with another party. The other party needs to repeat -this command with trade accept/decline within a minute in order to -properly initiate the trade action. You can use the decline option -yourself if you want to retract an already suggested trade. The -optional say part works like the say command and allows you to add -info to your choice.

-
-
-key = 'trade'
-
- -
-
-aliases = ['barter']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-func()[source]
-

Initiate trade

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.building_menu.html b/docs/0.9.5/api/evennia.contrib.building_menu.html deleted file mode 100644 index f395107991..0000000000 --- a/docs/0.9.5/api/evennia.contrib.building_menu.html +++ /dev/null @@ -1,936 +0,0 @@ - - - - - - - - - evennia.contrib.building_menu — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.building_menu

-

Module containing the building menu system.

-

Evennia contributor: vincent-lg 2018

-

Building menus are in-game menus, not unlike EvMenu though using a -different approach. Building menus have been specifically designed to edit -information as a builder. Creating a building menu in a command allows -builders quick-editing of a given object, like a room. If you follow the -steps below to add the contrib, you will have access to an @edit command -that will edit any default object offering to change its key and description.

-
    -
  1. Import the GenericBuildingCmd class from this contrib in your mygame/commands/default_cmdset.py file:

    -
    -
    from evennia.contrib.building_menu import GenericBuildingCmd
    -
    -
    -
    -
  2. -
  3. Below, add the command in the CharacterCmdSet:

    -
    -
    # ... These lines should exist in the file
    -class CharacterCmdSet(default_cmds.CharacterCmdSet):
    -    key = "DefaultCharacter"
    -
    -    def at_cmdset_creation(self):
    -        super(CharacterCmdSet, self).at_cmdset_creation()
    -        # ... add the line below
    -        self.add(GenericBuildingCmd())
    -
    -
    -
    -
  4. -
-

The @edit command will allow you to edit any object. You will need to -specify the object name or ID as an argument. For instance: @edit here -will edit the current room. However, building menus can perform much more -than this very simple example, read on for more details.

-

Building menus can be set to edit about anything. Here is an example of -output you could obtain when editing the room:

-
-

Editing the room: Limbo(#2)

-

[T]itle: the limbo room -[D]escription

-
-

This is the limbo room. You can easily change this default description, -either by using the |y@desc/edit|n command, or simply by entering this -menu (enter |yd|n).

-
-
-
[E]xits:

north to A parking(#4)

-
-
-

[Q]uit this menu

-
-

From there, you can open the title choice by pressing t. You can then -change the room title by simply entering text, and go back to the -main menu entering @ (all this is customizable). Press q to quit this menu.

-

The first thing to do is to create a new module and place a class -inheriting from BuildingMenu in it.

-
from evennia.contrib.building_menu import BuildingMenu
-
-class RoomBuildingMenu(BuildingMenu):
-    # ...
-
-
-

Next, override the init method. You can add choices (like the title, -description, and exits choices as seen above) by using the add_choice -method.

-
-
class RoomBuildingMenu(BuildingMenu):
-
def init(self, room):

self.add_choice(“title”, “t”, attr=”key”)

-
-
-
-
-

That will create the first choice, the title choice. If one opens your menu -and enter t, she will be in the title choice. She can change the title -(it will write in the room’s key attribute) and then go back to the -main menu using @.

-

add_choice has a lot of arguments and offers a great deal of -flexibility. The most useful ones is probably the usage of callbacks, -as you can set almost any argument in add_choice to be a callback, a -function that you have defined above in your module. This function will be -called when the menu element is triggered.

-

Notice that in order to edit a description, the best method to call isn’t -add_choice, but add_choice_edit. This is a convenient shortcut -which is available to quickly open an EvEditor when entering this choice -and going back to the menu when the editor closes.

-
-
class RoomBuildingMenu(BuildingMenu):
-
def init(self, room):

self.add_choice(“title”, “t”, attr=”key”) -self.add_choice_edit(“description”, key=”d”, attr=”db.desc”)

-
-
-
-
-

When you wish to create a building menu, you just need to import your -class, create it specifying your intended caller and object to edit, -then call open:

-
from <wherever> import RoomBuildingMenu
-
-class CmdEdit(Command):
-
-    key = "redit"
-
-    def func(self):
-        menu = RoomBuildingMenu(self.caller, self.caller.location)
-        menu.open()
-
-
-

This is a very short introduction. For more details, see the online tutorial -(https://github.com/evennia/evennia/wiki/Building-menus) or read the -heavily-documented code below.

-
-
-evennia.contrib.building_menu.menu_setattr(menu, choice, obj, string)[source]
-

Set the value at the specified attribute.

-
-
Parameters
-
    -
  • menu (BuildingMenu) – the menu object.

  • -
  • choice (Chocie) – the specific choice.

  • -
  • obj (Object) – the object to modify.

  • -
  • string (str) – the string with the new value.

  • -
-
-
-
-

Note

-

This function is supposed to be used as a default to -BuildingMenu.add_choice, when an attribute name is specified -(in the attr argument) but no function on_nomatch is defined.

-
-
- -
-
-evennia.contrib.building_menu.menu_quit(caller, menu)[source]
-

Quit the menu, closing the CmdSet.

-
-
Parameters
-
    -
  • caller (Account or Object) – the caller.

  • -
  • menu (BuildingMenu) – the building menu to close.

  • -
-
-
-
-

Note

-

This callback is used by default when using the -BuildingMenu.add_choice_quit method. This method is called -automatically if the menu has no parent.

-
-
- -
-
-evennia.contrib.building_menu.menu_edit(caller, choice, obj)[source]
-

Open the EvEditor to edit a specified attribute.

-
-
Parameters
-
    -
  • caller (Account or Object) – the caller.

  • -
  • choice (Choice) – the choice object.

  • -
  • obj (Object) – the object to edit.

  • -
-
-
-
- -
-
-class evennia.contrib.building_menu.CmdNoInput(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

No input has been found.

-
-
-key = '__noinput_command'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-__init__(**kwargs)[source]
-

The lockhandler works the same as for objects. -optional kwargs will be set as properties on the Command at runtime, -overloading evential same-named class properties.

-
- -
-
-func()[source]
-

Display the menu or choice text.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.building_menu.CmdNoMatch(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

No input has been found.

-
-
-key = '__nomatch_command'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-__init__(**kwargs)[source]
-

The lockhandler works the same as for objects. -optional kwargs will be set as properties on the Command at runtime, -overloading evential same-named class properties.

-
- -
-
-func()[source]
-

Call the proper menu or redirect to nomatch.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.building_menu.BuildingMenuCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Building menu CmdSet.

-
-
-key = 'building_menu'
-
- -
-
-priority = 5
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset with commands.

-
- -
-
-path = 'evennia.contrib.building_menu.BuildingMenuCmdSet'
-
- -
- -
-
-class evennia.contrib.building_menu.Choice(title, key=None, aliases=None, attr=None, text=None, glance=None, on_enter=None, on_nomatch=None, on_leave=None, menu=None, caller=None, obj=None)[source]
-

Bases: object

-

A choice object, created by add_choice.

-
-
-__init__(title, key=None, aliases=None, attr=None, text=None, glance=None, on_enter=None, on_nomatch=None, on_leave=None, menu=None, caller=None, obj=None)[source]
-

Constructor.

-
-
Parameters
-
    -
  • title (str) – the choice’s title.

  • -
  • key (str, optional) – the key of the letters to type to access -the choice. If not set, try to guess it based on the title.

  • -
  • aliases (list of str, optional) – the allowed aliases for this choice.

  • -
  • attr (str, optional) – the name of the attribute of ‘obj’ to set.

  • -
  • text (str or callable, optional) – a text to be displayed for this -choice. It can be a callable.

  • -
  • glance (str or callable, optional) – an at-a-glance summary of the -sub-menu shown in the main menu. It can be set to -display the current value of the attribute in the -main menu itself.

  • -
  • menu (BuildingMenu, optional) – the parent building menu.

  • -
  • on_enter (callable, optional) – a callable to call when the -caller enters into the choice.

  • -
  • on_nomatch (callable, optional) – a callable to call when no -match is entered in the choice.

  • -
  • on_leave (callable, optional) – a callable to call when the caller -leaves the choice.

  • -
  • caller (Account or Object, optional) – the caller.

  • -
  • obj (Object, optional) – the object to edit.

  • -
-
-
-
- -
-
-property keys
-

Return a tuple of keys separated by sep_keys.

-
- -
-
-format_text()[source]
-

Format the choice text and return it, or an empty string.

-
- -
-
-enter(string)[source]
-

Called when the user opens the choice.

-
-
Parameters
-

string (str) – the entered string.

-
-
-
- -
-
-nomatch(string)[source]
-

Called when the user entered something in the choice.

-
-
Parameters
-

string (str) – the entered string.

-
-
Returns
-

to_display (bool) – The return value of nomatch if set or -True. The rule is that if no_match returns True, -then the choice or menu is displayed.

-
-
-
- -
-
-leave(string)[source]
-

Called when the user closes the choice.

-
-
Parameters
-

string (str) – the entered string.

-
-
-
- -
- -
-
-class evennia.contrib.building_menu.BuildingMenu(caller=None, obj=None, title='Building menu: {obj}', keys=None, parents=None, persistent=False)[source]
-

Bases: object

-

Class allowing to create and set building menus to edit specific objects.

-

A building menu is somewhat similar to EvMenu, but designed to edit -objects by builders, although it can be used for players in some contexts. -You could, for instance, create a building menu to edit a room with a -sub-menu for the room’s key, another for the room’s description, -another for the room’s exits, and so on.

-

To add choices (simple sub-menus), you should call add_choice (see the -full documentation of this method). With most arguments, you can -specify either a plain string or a callback. This callback will be -called when the operation is to be performed.

-

Some methods are provided for frequent needs (see the add_choice_* -methods). Some helper functions are defined at the top of this -module in order to be used as arguments to add_choice -in frequent cases.

-
-
-keys_go_back = ['@']
-
- -
-
-sep_keys = '.'
-
- -
-
-joker_key = '*'
-
- -
-
-min_shortcut = 1
-
- -
-
-__init__(caller=None, obj=None, title='Building menu: {obj}', keys=None, parents=None, persistent=False)[source]
-

Constructor, you shouldn’t override. See init instead.

-
-
Parameters
-
    -
  • caller (Account or Object) – the caller.

  • -
  • obj (Object) – the object to be edited, like a room.

  • -
  • title (str, optional) – the menu title.

  • -
  • keys (list of str, optional) – the starting menu keys (None -to start from the first level).

  • -
  • parents (tuple, optional) – information for parent menus, -automatically supplied.

  • -
  • persistent (bool, optional) – should this building menu -survive a reload/restart?

  • -
-
-
-
-

Note

-

If some of these options have to be changed, it is -preferable to do so in the init method and not to -override __init__. For instance:

-
-
-
class RoomBuildingMenu(BuildingMenu):
-
def init(self, room):

self.title = “Menu for room: {obj.key}(#{obj.id})” -# …

-
-
-
-
-
-
-
- -
-
-property current_choice
-

Return the current choice or None.

-
-
Returns
-

choice (Choice) – the current choice or None.

-
-
-
-

Note

-

We use the menu keys to identify the current position of -the caller in the menu. The menu keys hold a list of -keys that should match a choice to be usable.

-
-
- -
-
-property relevant_choices
-

Only return the relevant choices according to the current meny key.

-
-
Returns
-

relevant (list of Choice object) – the relevant choices.

-
-
-
-

Note

-

We use the menu keys to identify the current position of -the caller in the menu. The menu keys hold a list of -keys that should match a choice to be usable.

-
-
- -
-
-init(obj)[source]
-

Create the sub-menu to edit the specified object.

-
-
Parameters
-

obj (Object) – the object to edit.

-
-
-
-

Note

-

This method is probably to be overridden in your subclasses. -Use add_choice and its variants to create menu choices.

-
-
- -
-
-add_choice(title, key=None, aliases=None, attr=None, text=None, glance=None, on_enter=None, on_nomatch=None, on_leave=None)[source]
-

Add a choice, a valid sub-menu, in the current builder menu.

-
-
Parameters
-
    -
  • title (str) – the choice’s title.

  • -
  • key (str, optional) – the key of the letters to type to access -the sub-neu. If not set, try to guess it based on the -choice title.

  • -
  • aliases (list of str, optional) – the aliases for this choice.

  • -
  • attr (str, optional) – the name of the attribute of ‘obj’ to set. -This is really useful if you want to edit an -attribute of the object (that’s a frequent need). If -you don’t want to do so, just use the on_* arguments.

  • -
  • text (str or callable, optional) – a text to be displayed when -the menu is opened It can be a callable.

  • -
  • glance (str or callable, optional) – an at-a-glance summary of the -sub-menu shown in the main menu. It can be set to -display the current value of the attribute in the -main menu itself.

  • -
  • on_enter (callable, optional) – a callable to call when the -caller enters into this choice.

  • -
  • on_nomatch (callable, optional) – a callable to call when -the caller enters something in this choice. If you -don’t set this argument but you have specified -attr, then obj.**attr** will be set with the value -entered by the user.

  • -
  • on_leave (callable, optional) – a callable to call when the -caller leaves the choice.

  • -
-
-
Returns
-

choice (Choice) – the newly-created choice.

-
-
Raises
-

ValueError if the choice cannot be added.

-
-
-
-

Note

-

Most arguments can be callables, like functions. This has the -advantage of allowing great flexibility. If you specify -a callable in most of the arguments, the callable should return -the value expected by the argument (a str more often than -not). For instance, you could set a function to be called -to get the menu text, which allows for some filtering:

-
-
-
def text_exits(menu):

return “Some text to display”

-
-
class RoomBuildingMenu(BuildingMenu):
-
def init(self):

self.add_choice(“exits”, key=”x”, text=text_exits)

-
-
-
-
-
-

The allowed arguments in a callable are specific to the -argument names (they are not sensitive to orders, not all -arguments have to be present). For more information, see -_call_or_get.

-
-
- -
-
-add_choice_edit(title='description', key='d', aliases=None, attr='db.desc', glance='\n {obj.db.desc}', on_enter=None)[source]
-

Add a simple choice to edit a given attribute in the EvEditor.

-
-
Parameters
-
    -
  • title (str, optional) – the choice’s title.

  • -
  • key (str, optional) – the choice’s key.

  • -
  • aliases (list of str, optional) – the choice’s aliases.

  • -
  • glance (str or callable, optional) – the at-a-glance description.

  • -
  • on_enter (callable, optional) – a different callable to edit -the attribute.

  • -
-
-
Returns
-

choice (Choice) – the newly-created choice.

-
-
-
-

Note

-

This is just a shortcut method, calling add_choice. -If on_enter is not set, use menu_edit which opens -an EvEditor to edit the specified attribute. -When the caller closes the editor (with :q), the menu -will be re-opened.

-
-
- -
-
-add_choice_quit(title='quit the menu', key='q', aliases=None, on_enter=None)[source]
-

Add a simple choice just to quit the building menu.

-
-
Parameters
-
    -
  • title (str, optional) – the choice’s title.

  • -
  • key (str, optional) – the choice’s key.

  • -
  • aliases (list of str, optional) – the choice’s aliases.

  • -
  • on_enter (callable, optional) – a different callable -to quit the building menu.

  • -
-
-
-
-

Note

-

This is just a shortcut method, calling add_choice. -If on_enter is not set, use menu_quit which simply -closes the menu and displays a message. It also -removes the CmdSet from the caller. If you supply -another callable instead, make sure to do the same.

-
-
- -
-
-open()[source]
-

Open the building menu for the caller.

-
-

Note

-

This method should be called once when the building menu -has been instanciated. From there, the building menu will -be re-created automatically when the server -reloads/restarts, assuming persistent is set to True.

-
-
- -
-
-open_parent_menu()[source]
-

Open the parent menu, using self.parents.

-
-

Note

-

You probably don’t need to call this method directly, -since the caller can go back to the parent menu using the -keys_go_back automatically.

-
-
- -
-
-open_submenu(submenu_class, submenu_obj, parent_keys=None)[source]
-

Open a sub-menu, closing the current menu and opening the new one.

-
-
Parameters
-
    -
  • submenu_class (str) – the submenu class as a Python path.

  • -
  • submenu_obj (Object) – the object to give to the submenu.

  • -
  • parent_keys (list of str, optional) – the parent keys when -the submenu is closed.

  • -
-
-
-
-

Note

-

When the user enters @ in the submenu, she will go back to -the current menu, with the parent_keys set as its keys. -Therefore, you should set it on the keys of the choice that -should be opened when the user leaves the submenu.

-
-
-
Returns
-

new_menu (BuildingMenu) – the new building menu or None.

-
-
-
- -
-
-move(key=None, back=False, quiet=False, string='')[source]
-

Move inside the menu.

-
-
Parameters
-
    -
  • key (any) – the portion of the key to add to the current -menu keys. If you wish to go back in the menu -tree, don’t provide a key, just set back to True.

  • -
  • back (bool, optional) – go back in the menu (False by default).

  • -
  • quiet (bool, optional) – should the menu or choice be -displayed afterward?

  • -
  • string (str, optional) – the string sent by the caller to move.

  • -
-
-
-
-

Note

-

This method will need to be called directly should you -use more than two levels in your menu. For instance, -in your room menu, if you want to have an “exits” -option, and then be able to enter “north” in this -choice to edit an exit. The specific exit choice -could be a different menu (with a different class), but -it could also be an additional level in your original menu. -If that’s the case, you will need to use this method.

-
-
- -
-
-close()[source]
-

Close the building menu, removing the CmdSet.

-
- -
-
-display_title()[source]
-

Return the menu title to be displayed.

-
- -
-
-display_choice(choice)[source]
-

Display the specified choice.

-
-
Parameters
-

choice (Choice) – the menu choice.

-
-
-
- -
-
-display()[source]
-

Display the entire menu or a single choice, depending on the keys.

-
- -
-
-static restore(caller)[source]
-

Restore the building menu for the caller.

-
-
Parameters
-

caller (Account or Object) – the caller.

-
-
-
-

Note

-

This method should be automatically called if a menu is -saved in the caller, but the object itself cannot be found.

-
-
- -
- -
-
-class evennia.contrib.building_menu.GenericBuildingMenu(caller=None, obj=None, title='Building menu: {obj}', keys=None, parents=None, persistent=False)[source]
-

Bases: evennia.contrib.building_menu.BuildingMenu

-

A generic building menu, allowing to edit any object.

-

This is more a demonstration menu. By default, it allows to edit the -object key and description. Nevertheless, it will be useful to demonstrate -how building menus are meant to be used.

-
-
-init(obj)[source]
-

Build the meny, adding the ‘key’ and ‘description’ choices.

-
-
Parameters
-

obj (Object) – any object to be edited, like a character or room.

-
-
-
-

Note

-

The ‘quit’ choice will be automatically added, though you can -call add_choice_quit to add this choice with different options.

-
-
- -
- -
-
-class evennia.contrib.building_menu.GenericBuildingCmd(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Generic building command.

-
-
Syntax:

@edit [object]

-
-
-

Open a building menu to edit the specified object. This menu allows to -change the object’s key and description.

-

Examples

-

@edit here -@edit self -@edit #142

-
-
-key = '@edit'
-
- -
-
-func()[source]
-

This is the actual executing part of the command. It is -called directly after self.parse(). See the docstring of this -module for which object properties are available (beyond those -set in self.parse())

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.chargen.html b/docs/0.9.5/api/evennia.contrib.chargen.html deleted file mode 100644 index d12901c845..0000000000 --- a/docs/0.9.5/api/evennia.contrib.chargen.html +++ /dev/null @@ -1,245 +0,0 @@ - - - - - - - - - evennia.contrib.chargen — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.chargen

-

Contribution - Griatch 2011

-

> Note - with the advent of MULTISESSION_MODE=2, this is not really as -necessary anymore - the ooclook and @charcreate commands in that mode -replaces this module with better functionality. This remains here for -inspiration.

-

This is a simple character creation commandset for the Account level. -It shows some more info and gives the Account the option to create a -character without any more customizations than their name (further -options are unique for each game anyway).

-

In MULTISESSION_MODEs 0 and 1, you will automatically log into an -existing Character. When using @ooc you will then end up in this -cmdset.

-

Installation:

-

Import this module to mygame/commands/default_cmdsets.py and -add chargen.OOCCMdSetCharGen to the AccountCmdSet class -(it says where to add it). Reload.

-
-
-class evennia.contrib.chargen.CmdOOCLook(**kwargs)[source]
-

Bases: evennia.commands.default.general.CmdLook

-

ooc look

-
-
Usage:

look -look <character>

-
-
-

This is an OOC version of the look command. Since an Account doesn’t -have an in-game existence, there is no concept of location or -“self”.

-

If any characters are available for you to control, you may look -at them with this command.

-
-
-key = 'look'
-
- -
-
-aliases = ['l', 'ls']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-func()[source]
-

Implements the ooc look command

-

We use an attribute _character_dbrefs on the account in order -to figure out which characters are “theirs”. A drawback of this -is that only the CmdCharacterCreate command adds this attribute, -and thus e.g. account #1 will not be listed (although it will work). -Existence in this list does not depend on puppeting rights though, -that is checked by the @ic command directly.

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.chargen.CmdOOCCharacterCreate(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

creates a character

-
-
Usage:

create <character name>

-
-
-

This will create a new character, assuming -the given character name does not already exist.

-
-
-key = 'create'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Tries to create the Character object. We also put an -attribute on ourselves to remember it.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.chargen.OOCCmdSetCharGen(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.default.cmdset_account.AccountCmdSet

-

Extends the default OOC cmdset.

-
-
-at_cmdset_creation()[source]
-

Install everything from the default set, then overload

-
- -
-
-path = 'evennia.contrib.chargen.OOCCmdSetCharGen'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.clothing.html b/docs/0.9.5/api/evennia.contrib.clothing.html deleted file mode 100644 index e1d032f019..0000000000 --- a/docs/0.9.5/api/evennia.contrib.clothing.html +++ /dev/null @@ -1,728 +0,0 @@ - - - - - - - - - evennia.contrib.clothing — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.clothing

-

Clothing - Provides a typeclass and commands for wearable clothing, -which is appended to a character’s description when worn.

-

Evennia contribution - Tim Ashley Jenkins 2017

-

Clothing items, when worn, are added to the character’s description -in a list. For example, if wearing the following clothing items:

-
-

a thin and delicate necklace -a pair of regular ol’ shoes -one nice hat -a very pretty dress

-
-

A character’s description may look like this:

-
-

Superuser(#1) -This is User #1.

-

Superuser is wearing one nice hat, a thin and delicate necklace, -a very pretty dress and a pair of regular ol’ shoes.

-
-

Characters can also specify the style of wear for their clothing - I.E. -to wear a scarf ‘tied into a tight knot around the neck’ or ‘draped -loosely across the shoulders’ - to add an easy avenue of customization. -For example, after entering:

-
-

wear scarf draped loosely across the shoulders

-
-

The garment appears like so in the description:

-
-

Superuser(#1) -This is User #1.

-

Superuser is wearing a fanciful-looking scarf draped loosely -across the shoulders.

-
-

Items of clothing can be used to cover other items, and many options -are provided to define your own clothing types and their limits and -behaviors. For example, to have undergarments automatically covered -by outerwear, or to put a limit on the number of each type of item -that can be worn. The system as-is is fairly freeform - you -can cover any garment with almost any other, for example - but it -can easily be made more restrictive, and can even be tied into a -system for armor or other equipment.

-

To install, import this module and have your default character -inherit from ClothedCharacter in your game’s characters.py file:

-
-

from evennia.contrib.clothing import ClothedCharacter

-

class Character(ClothedCharacter):

-
-

And then add ClothedCharacterCmdSet in your character set in your -game’s commands/default_cmdsets.py:

-
-

from evennia.contrib.clothing import ClothedCharacterCmdSet

-
-
class CharacterCmdSet(default_cmds.CharacterCmdSet):

… -at_cmdset_creation(self):

-
-

super().at_cmdset_creation() -… -self.add(ClothedCharacterCmdSet) # <– add this

-
-
-
-
-

From here, you can use the default builder commands to create clothes -with which to test the system:

-
-

@create a pretty shirt : evennia.contrib.clothing.Clothing -@set shirt/clothing_type = ‘top’ -wear shirt

-
-
-
-evennia.contrib.clothing.order_clothes_list(clothes_list)[source]
-

Orders a given clothes list by the order specified in CLOTHING_TYPE_ORDER.

-
-
Parameters
-

clothes_list (list) – List of clothing items to put in order

-
-
Returns
-

ordered_clothes_list (list)

-
-
The same list as passed, but re-ordered

according to the hierarchy of clothing types -specified in CLOTHING_TYPE_ORDER.

-
-
-

-
-
-
- -
-
-evennia.contrib.clothing.get_worn_clothes(character, exclude_covered=False)[source]
-

Get a list of clothes worn by a given character.

-
-
Parameters
-

character (obj) – The character to get a list of worn clothes from.

-
-
Keyword Arguments
-

exclude_covered (bool) – If True, excludes clothes covered by other -clothing from the returned list.

-
-
Returns
-

ordered_clothes_list (list)

-
-
A list of clothing items worn by the

given character, ordered according to -the CLOTHING_TYPE_ORDER option specified -in this module.

-
-
-

-
-
-
- -
-
-evennia.contrib.clothing.clothing_type_count(clothes_list)[source]
-

Returns a dictionary of the number of each clothing type -in a given list of clothing objects.

-
-
Parameters
-

clothes_list (list) – A list of clothing items from which -to count the number of clothing types -represented among them.

-
-
Returns
-

types_count (dict)

-
-
A dictionary of clothing types represented

in the given list and the number of each -clothing type represented.

-
-
-

-
-
-
- -
-
-evennia.contrib.clothing.single_type_count(clothes_list, type)[source]
-

Returns an integer value of the number of a given type of clothing in a list.

-
-
Parameters
-
    -
  • clothes_list (list) – List of clothing objects to count from

  • -
  • type (str) – Clothing type to count

  • -
-
-
Returns
-

type_count (int)

-
-
Number of garments of the specified type in the given

list of clothing objects

-
-
-

-
-
-
- -
-
-class evennia.contrib.clothing.Clothing(id, db_key, db_typeclass_path, db_date_created, db_lock_storage, db_account, db_sessid, db_location, db_home, db_destination, db_cmdset_storage)[source]
-

Bases: evennia.objects.objects.DefaultObject

-
-
-wear(wearer, wearstyle, quiet=False)[source]
-

Sets clothes to ‘worn’ and optionally echoes to the room.

-
-
Parameters
-
    -
  • wearer (obj) – character object wearing this clothing object

  • -
  • wearstyle (True or str) – string describing the style of wear or True for none

  • -
-
-
Keyword Arguments
-

quiet (bool) – If false, does not message the room

-
-
-

Notes

-

Optionally sets db.worn with a ‘wearstyle’ that appends a short passage to -the end of the name of the clothing to describe how it’s worn that shows -up in the wearer’s desc - I.E. ‘around his neck’ or ‘tied loosely around -her waist’. If db.worn is set to ‘True’ then just the name will be shown.

-
- -
-
-remove(wearer, quiet=False)[source]
-

Removes worn clothes and optionally echoes to the room.

-
-
Parameters
-

wearer (obj) – character object wearing this clothing object

-
-
Keyword Arguments
-

quiet (bool) – If false, does not message the room

-
-
-
- -
-
-at_get(getter)[source]
-

Makes absolutely sure clothes aren’t already set as ‘worn’ -when they’re picked up, in case they’ve somehow had their -location changed without getting removed.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.clothing.Clothing'
-
- -
-
-typename = 'Clothing'
-
- -
- -
-
-class evennia.contrib.clothing.ClothedCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultCharacter

-

Character that displays worn clothing when looked at. You can also -just copy the return_appearance hook defined below to your own game’s -character typeclass.

-
-
-return_appearance(looker)[source]
-

This formats a description. It is the hook a ‘look’ command -should call.

-
-
Parameters
-

looker (Object) – Object doing the looking.

-
-
-

Notes

-

The name of every clothing item carried and worn by the character -is appended to their description. If the clothing’s db.worn value -is set to True, only the name is appended, but if the value is a -string, the string is appended to the end of the name, to allow -characters to specify how clothing is worn.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.clothing.ClothedCharacter'
-
- -
-
-typename = 'ClothedCharacter'
-
- -
- -
-
-class evennia.contrib.clothing.CmdWear(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Puts on an item of clothing you are holding.

-
-
Usage:

wear <obj> [wear style]

-
-
-

Examples

-

wear shirt -wear scarf wrapped loosely about the shoulders

-

All the clothes you are wearing are appended to your description. -If you provide a ‘wear style’ after the command, the message you -provide will be displayed after the clothing’s name.

-
-
-key = 'wear'
-
- -
-
-help_category = 'clothing'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.clothing.CmdRemove(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Takes off an item of clothing.

-
-
Usage:

remove <obj>

-
-
-

Removes an item of clothing you are wearing. You can’t remove -clothes that are covered up by something else - you must take -off the covering item first.

-
-
-key = 'remove'
-
- -
-
-help_category = 'clothing'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.clothing.CmdCover(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Covers a worn item of clothing with another you’re holding or wearing.

-
-
Usage:

cover <obj> [with] <obj>

-
-
-

When you cover a clothing item, it is hidden and no longer appears in -your description until it’s uncovered or the item covering it is removed. -You can’t remove an item of clothing if it’s covered.

-
-
-key = 'cover'
-
- -
-
-help_category = 'clothing'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.clothing.CmdUncover(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Reveals a worn item of clothing that’s currently covered up.

-
-
Usage:

uncover <obj>

-
-
-

When you uncover an item of clothing, you allow it to appear in your -description without having to take off the garment that’s currently -covering it. You can’t uncover an item of clothing if the item covering -it is also covered by something else.

-
-
-key = 'uncover'
-
- -
-
-help_category = 'clothing'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.clothing.CmdDrop(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

drop something

-
-
Usage:

drop <obj>

-
-
-

Lets you drop an object from your inventory into the -location you are currently in.

-
-
-key = 'drop'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Implement command

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.clothing.CmdGive(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

give away something to someone

-
-
Usage:

give <inventory obj> = <target>

-
-
-

Gives an items from your inventory to another character, -placing it in their inventory.

-
-
-key = 'give'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('\\s|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

Implement give

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.clothing.CmdInventory(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

view inventory

-
-
Usage:

inventory -inv

-
-
-

Shows your inventory.

-
-
-key = 'inventory'
-
- -
-
-aliases = ['inv', 'i']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-arg_regex = re.compile('$', re.IGNORECASE)
-
- -
-
-func()[source]
-

check inventory

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.clothing.ClothedCharacterCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.default.cmdset_character.CharacterCmdSet

-

Command set for clothing, including new versions of ‘give’ and ‘drop’ -that take worn and covered clothing into account, as well as a new -version of ‘inventory’ that differentiates between carried and worn -items.

-
-
-key = 'DefaultCharacter'
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset

-
- -
-
-path = 'evennia.contrib.clothing.ClothedCharacterCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.color_markups.html b/docs/0.9.5/api/evennia.contrib.color_markups.html deleted file mode 100644 index 70fcc44d00..0000000000 --- a/docs/0.9.5/api/evennia.contrib.color_markups.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - - - evennia.contrib.color_markups — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.color_markups

-

Color markups

-

Contribution, Griatch 2017

-

Additional color markup styles for Evennia (extending or replacing the default |r, |234 etc).

-

Installation:

-

Import the desired style variables from this module into mygame/server/conf/settings.py and add them -to these settings variables. Each are specified as a list, and multiple such lists can be added to -each variable to support multiple formats. Note that list order affects which regexes are applied -first. You must restart both Portal and Server for color tags to update.

-

Assign to the following settings variables:

-
-

COLOR_ANSI_EXTRA_MAP - a mapping between regexes and ANSI colors -COLOR_XTERM256_EXTRA_FG - regex for defining XTERM256 foreground colors -COLOR_XTERM256_EXTRA_BG - regex for defining XTERM256 background colors -COLOR_XTERM256_EXTRA_GFG - regex for defining XTERM256 grayscale foreground colors -COLOR_XTERM256_EXTRA_GBG - regex for defining XTERM256 grayscale background colors -COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = ANSI does not support bright backgrounds; we fake

-
-

this by mapping ANSI markup to matching bright XTERM256 backgrounds

-
-
-
COLOR_NO_DEFAULT - Set True/False. If False (default), extend the default markup, otherwise

replace it completely.

-
-
-
-

To add the {- “curly-bracket” style, add the following to your settings file, then reboot both -Server and Portal:

-

from evennia.contrib import color_markups -COLOR_ANSI_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_EXTRA_MAP -COLOR_XTERM256_EXTRA_FG = color_markups.CURLY_COLOR_XTERM256_EXTRA_FG -COLOR_XTERM256_EXTRA_BG = color_markups.CURLY_COLOR_XTERM256_EXTRA_BG -COLOR_XTERM256_EXTRA_GFG = color_markups.CURLY_COLOR_XTERM256_EXTRA_GFG -COLOR_XTERM256_EXTRA_GBG = color_markups.CURLY_COLOR_XTERM256_EXTRA_GBG -COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BG_EXTRA_MAP

-

To add the %c- “mux/mush” style, add the following to your settings file, then reboot both Server -and Portal:

-

from evennia.contrib import color_markups -COLOR_ANSI_EXTRA_MAP = color_markups.MUX_COLOR_ANSI_EXTRA_MAP -COLOR_XTERM256_EXTRA_FG = color_markups.MUX_COLOR_XTERM256_EXTRA_FG -COLOR_XTERM256_EXTRA_BG = color_markups.MUX_COLOR_XTERM256_EXTRA_BG -COLOR_XTERM256_EXTRA_GFG = color_markups.MUX_COLOR_XTERM256_EXTRA_GFG -COLOR_XTERM256_EXTRA_GBG = color_markups.MUX_COLOR_XTERM256_EXTRA_GBG -COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.custom_gametime.html b/docs/0.9.5/api/evennia.contrib.custom_gametime.html deleted file mode 100644 index b3e55c8de3..0000000000 --- a/docs/0.9.5/api/evennia.contrib.custom_gametime.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - - evennia.contrib.custom_gametime — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.custom_gametime

-

Custom gametime

-

Contrib - Griatch 2017, vlgeoff 2017

-

This implements the evennia.utils.gametime module but supporting -a custom calendar for your game world. It allows for scheduling -events to happen at given in-game times, taking this custom -calendar into account.

-

Usage:

-

Use as the normal gametime module, that is by importing and using the -helper functions in this module in your own code. The calendar can be -customized by adding the TIME_UNITS dictionary to your settings -file. This maps unit names to their length, expressed in the smallest -unit. Here’s the default as an example:

-
-
-
TIME_UNITS = {

“sec”: 1, -“min”: 60, -“hr”: 60 * 60, -“hour”: 60 * 60, -“day”: 60 * 60 * 24, -“week”: 60 * 60 * 24 * 7, -“month”: 60 * 60 * 24 * 7 * 4, -“yr”: 60 * 60 * 24 * 7 * 4 * 12, -“year”: 60 * 60 * 24 * 7 * 4 * 12, }

-
-
-
-

When using a custom calendar, these time unit names are used as kwargs to -the converter functions in this module.

-
-
-evennia.contrib.custom_gametime.time_to_tuple(seconds, *divisors)[source]
-

Helper function. Creates a tuple of even dividends given a range -of divisors.

-
-
Parameters
-
    -
  • seconds (int) – Number of seconds to format

  • -
  • *divisors (int) – a sequence of numbers of integer dividends. The -number of seconds will be integer-divided by the first number in -this sequence, the remainder will be divided with the second and -so on.

  • -
-
-
Returns
-

time (tuple)

-
-
This tuple has length len(*args)+1, with the

last element being the last remaining seconds not evenly -divided by the supplied dividends.

-
-
-

-
-
-
- -
-
-evennia.contrib.custom_gametime.gametime_to_realtime(format=False, **kwargs)[source]
-

This method helps to figure out the real-world time it will take until an -in-game time has passed. E.g. if an event should take place a month later -in-game, you will be able to find the number of real-world seconds this -corresponds to (hint: Interval events deal with real life seconds).

-
-
Keyword Arguments
-
    -
  • format (bool) – Formatting the output.

  • -
  • month etc (days,) – These are the names of time units that must -match the settings.TIME_UNITS dict keys.

  • -
-
-
Returns
-

time (float or tuple)

-
-
The realtime difference or the same

time split up into time units.

-
-
-

-
-
-

Example

-
-
gametime_to_realtime(days=2) -> number of seconds in real life from

now after which 2 in-game days will have passed.

-
-
-
- -
-
-evennia.contrib.custom_gametime.realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0, months=0, yrs=0, format=False)[source]
-

This method calculates how much in-game time a real-world time -interval would correspond to. This is usually a lot less -interesting than the other way around.

-
-
Keyword Arguments
-
    -
  • times (int) – The various components of the time.

  • -
  • format (bool) – Formatting the output.

  • -
-
-
Returns
-

time (float or tuple)

-
-
-
The gametime difference or the same

time split up into time units.

-
-
-
-
-
Example:

realtime_to_gametime(days=2) -> number of game-world seconds

-
-
-

-
-
-
- -
-
-evennia.contrib.custom_gametime.custom_gametime(absolute=False)[source]
-

Return the custom game time as a tuple of units, as defined in settings.

-
-
Parameters
-

absolute (bool, optional) – return the relative or absolute time.

-
-
Returns
-

The tuple describing the game time. The length of the tuple -is related to the number of unique units defined in the -settings. By default, the tuple would be (year, month, -week, day, hour, minute, second).

-
-
-
- -
-
-evennia.contrib.custom_gametime.real_seconds_until(**kwargs)[source]
-

Return the real seconds until game time.

-

If the game time is 5:00, TIME_FACTOR is set to 2 and you ask -the number of seconds until it’s 5:10, then this function should -return 300 (5 minutes).

-
-
Parameters
-

(str (times) – int): the time units.

-
-
-

Example

-

real_seconds_until(hour=5, min=10, sec=0)

-
-
Returns
-

The number of real seconds before the given game time is up.

-
-
-
- -
-
-evennia.contrib.custom_gametime.schedule(callback, repeat=False, **kwargs)[source]
-

Call the callback when the game time is up.

-
-
Parameters
-
    -
  • callback (function) – The callback function that will be called. This -must be a top-level function since the script will be persistent.

  • -
  • repeat (bool, optional) – Should the callback be called regularly?

  • -
  • day – int): The time units to call the callback; should -match the keys of TIME_UNITS.

  • -
  • month – int): The time units to call the callback; should -match the keys of TIME_UNITS.

  • -
  • (str (etc) – int): The time units to call the callback; should -match the keys of TIME_UNITS.

  • -
-
-
Returns
-

script (Script) – The created script.

-
-
-

Examples

-

schedule(func, min=5, sec=0) # Will call next hour at :05. -schedule(func, hour=2, min=30, sec=0) # Will call the next day at 02:30.

-

Notes

-

This function will setup a script that will be called when the -time corresponds to the game time. If the game is stopped for -more than a few seconds, the callback may be called with a -slight delay. If repeat is set to True, the callback will be -called again next time the game time matches the given time. -The time is given in units as keyword arguments.

-
- -
-
-class evennia.contrib.custom_gametime.GametimeScript(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

Gametime-sensitive script.

-
-
-at_script_creation()[source]
-

The script is created.

-
- -
-
-at_repeat()[source]
-

Call the callback and reset interval.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.custom_gametime.GametimeScript'
-
- -
-
-typename = 'GametimeScript'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.dice.html b/docs/0.9.5/api/evennia.contrib.dice.html deleted file mode 100644 index 6dc941e2e3..0000000000 --- a/docs/0.9.5/api/evennia.contrib.dice.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - - - evennia.contrib.dice — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.dice

-

Dice - rolls dice for roleplaying, in-game gambling or GM:ing

-

Evennia contribution - Griatch 2012

-

This module implements a full-fledged dice-roller and a ‘dice’ command -to go with it. It uses standard RPG ‘d’-syntax (e.g. 2d6 to roll two -six-sided die) and also supports modifiers such as 3d6 + 5.

-

One can also specify a standard Python operator in order to specify -eventual target numbers and get results in a fair and guaranteed -unbiased way. For example a GM could (using the dice command) from -the start define the roll as 2d6 < 8 to show that a roll below 8 is -required to succeed. The command will normally echo this result to all -parties (although it also has options for hidden and secret rolls).

-

Installation:

-

To use in your code, just import the roll_dice function from this module.

-

To use the dice/roll command, just import this module in your custom -cmdset module and add the following line to the end of DefaultCmdSet’s -at_cmdset_creation():

-
-

self.add(dice.CmdDice())

-
-

After a reload the dice (or roll) command will be available in-game.

-
-
-evennia.contrib.dice.roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False)[source]
-

This is a standard dice roller.

-
-
Parameters
-
    -
  • dicenum (int) – Number of dice to roll (the result to be added).

  • -
  • dicetype (int) – Number of sides of the dice to be rolled.

  • -
  • modifier (tuple) – A tuple (operator, value), where operator is -one of “+”, “-”, “/” or “*”. The result of the dice -roll(s) will be modified by this value.

  • -
  • conditional (tuple) – A tuple (conditional, value), where -conditional is one of “==”,**”<”,”>”,”>=”,”<=**” or “!=”. -This allows the roller to directly return a result depending -on if the conditional was passed or not.

  • -
  • return_tuple (bool) – Return a tuple with all individual roll -results or not.

  • -
-
-
Returns
-

roll_result (int)

-
-
The result of the roll + modifiers. This is the

default return.

-
-
condition_result (bool): A True/False value returned if conditional

is set but not return_tuple. This effectively hides the result -of the roll.

-
-
full_result (tuple): If, return_tuple** is True, instead

return a tuple (result, outcome, diff, rolls). Here, -result is the normal result of the roll + modifiers. -outcome and diff are the boolean result of the roll and -absolute difference to the conditional input; they will -be will be None if conditional is not set. rolls is -itself a tuple holding all the individual rolls in the case of -multiple die-rolls.

-
-
-

-
-
Raises
-

TypeError if non-supported modifiers or conditionals are given.

-
-
-

Notes

-

All input numbers are converted to integers.

-

Examples

-

print roll_dice(2, 6) # 2d6 -<<< 7 -print roll_dice(1, 100, (‘+’, 5) # 1d100 + 5 -<<< 34 -print roll_dice(1, 20, conditional=(‘<’, 10) # let’say we roll 3 -<<< True -print roll_dice(3, 10, return_tuple=True) -<<< (11, None, None, (2, 5, 4)) -print roll_dice(2, 20, (‘-’, 2), conditional=(‘>=’, 10), return_tuple=True) -<<< (8, False, 2, (4, 6)) # roll was 4 + 6 - 2 = 8

-
- -
-
-class evennia.contrib.dice.CmdDice(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

roll dice

-
-
Usage:

dice[/switch] <nr>d<sides> [modifier] [success condition]

-
-
Switch:

hidden - tell the room the roll is being done, but don’t show the result -secret - don’t inform the room about neither roll nor result

-
-
-

Examples

-

dice 3d6 + 4 -dice 1d100 - 2 < 50

-

This will roll the given number of dice with given sides and modifiers. -So e.g. 2d6 + 3 means to ‘roll a 6-sided die 2 times and add the result, -then add 3 to the total’. -Accepted modifiers are +, -, * and /. -A success condition is given as normal Python conditionals -(<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed -only if the final result is above 8. If a success condition is given, the -outcome (pass/fail) will be echoed along with how much it succeeded/failed -with. The hidden/secret switches will hide all or parts of the roll from -everyone but the person rolling.

-
-
-key = 'dice'
-
- -
-
-aliases = ['@dice', 'roll']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Mostly parsing for calling the dice roller function

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.dice.DiceCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

a small cmdset for testing purposes. -Add with @py self.cmdset.add(“contrib.dice.DiceCmdSet”)

-
-
-at_cmdset_creation()[source]
-

Called when set is created

-
- -
-
-path = 'evennia.contrib.dice.DiceCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.email_login.html b/docs/0.9.5/api/evennia.contrib.email_login.html deleted file mode 100644 index 3770828bef..0000000000 --- a/docs/0.9.5/api/evennia.contrib.email_login.html +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - - - - evennia.contrib.email_login — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.email_login

-

Email-based login system

-

Evennia contrib - Griatch 2012

-

This is a variant of the login system that requires an email-address -instead of a username to login.

-

This used to be the default Evennia login before replacing it with a -more standard username + password system (having to supply an email -for some reason caused a lot of confusion when people wanted to expand -on it. The email is not strictly needed internally, nor is any -confirmation email sent out anyway).

-

Installation is simple:

-

To your settings file, add/edit the line:

-
CMDSET_UNLOGGEDIN = "contrib.email_login.UnloggedinCmdSet"
-
-
-

That’s it. Reload the server and try to log in to see it.

-

The initial login “graphic” will still not mention email addresses -after this change. The login splashscreen is taken from strings in -the module given by settings.CONNECTION_SCREEN_MODULE.

-
-
-class evennia.contrib.email_login.CmdUnconnectedConnect(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Connect to the game.

-
-
Usage (at login screen):

connect <email> <password>

-
-
-

Use the create command to first create an account before logging in.

-
-
-key = 'connect'
-
- -
-
-aliases = ['conn', 'co', 'con']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Uses the Django admin api. Note that unlogged-in commands -have a unique position in that their func() receives -a session object instead of a source_object like all -other types of logged-in commands (this is because -there is no object yet before the account has logged in)

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.email_login.CmdUnconnectedCreate(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Create a new account.

-
-
Usage (at login screen):

create “accountname” <email> <password>

-
-
-

This creates a new account account.

-
-
-key = 'create'
-
- -
-
-aliases = ['cr', 'cre']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-parse()[source]
-

The parser must handle the multiple-word account -name enclosed in quotes:

-
-

connect “Long name with many words” my@myserv.com mypassw

-
-
- -
-
-func()[source]
-

Do checks and create account

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.email_login.CmdUnconnectedQuit(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

We maintain a different version of the quit command -here for unconnected accounts for the sake of simplicity. The logged in -version is a bit more complicated.

-
-
-key = 'quit'
-
- -
-
-aliases = ['qu', 'q']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Simply close the connection.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.email_login.CmdUnconnectedLook(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

This is an unconnected version of the look command for simplicity.

-

This is called by the server and kicks everything in gear. -All it does is display the connect screen.

-
-
-key = '__unloggedin_look_command'
-
- -
-
-aliases = ['l', 'look']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Show the connect screen.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.email_login.CmdUnconnectedHelp(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

This is an unconnected version of the help command, -for simplicity. It shows a pane of info.

-
-
-key = 'help'
-
- -
-
-aliases = ['h', '?']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Shows help

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.extended_room.html b/docs/0.9.5/api/evennia.contrib.extended_room.html deleted file mode 100644 index 82cd005b3a..0000000000 --- a/docs/0.9.5/api/evennia.contrib.extended_room.html +++ /dev/null @@ -1,553 +0,0 @@ - - - - - - - - - evennia.contrib.extended_room — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.extended_room

-

Extended Room

-

Evennia Contribution - Griatch 2012, vincent-lg 2019

-

This is an extended Room typeclass for Evennia. It is supported -by an extended Look command and an extended desc command, also -in this module.

-

Features:

-
    -
  1. Time-changing description slots

  2. -
-

This allows to change the full description text the room shows -depending on larger time variations. Four seasons (spring, summer, -autumn and winter) are used by default. The season is calculated -on-demand (no Script or timer needed) and updates the full text block.

-

There is also a general description which is used as fallback if -one or more of the seasonal descriptions are not set when their -time comes.

-

An updated desc command allows for setting seasonal descriptions.

-

The room uses the evennia.utils.gametime.GameTime global script. This is -started by default, but if you have deactivated it, you need to -supply your own time keeping mechanism.

-
    -
  1. In-description changing tags

  2. -
-

Within each seasonal (or general) description text, you can also embed -time-of-day dependent sections. Text inside such a tag will only show -during that particular time of day. The tags looks like <timeslot> … -</timeslot>. By default there are four timeslots per day - morning, -afternoon, evening and night.

-
    -
  1. Details

  2. -
-

The Extended Room can be “detailed” with special keywords. This makes -use of a special Look command. Details are “virtual” targets to look -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 <target> command it -will also look through the available details at the current location -if applicable. The @detail command is used to change details.

-
    -
  1. Extra commands

  2. -
-
-

CmdExtendedRoomLook - look command supporting room details -CmdExtendedRoomDesc - desc command allowing to add seasonal descs, -CmdExtendedRoomDetail - command allowing to manipulate details in this room

-
-

as well as listing them

-
-
-
CmdExtendedRoomGameTime - A simple time command, displaying the current

time and season.

-
-
-
-

Installation/testing:

-

Adding the ExtendedRoomCmdset to the default character cmdset will add all -new commands for use.

-

In more detail, in mygame/commands/default_cmdsets.py:

-

… -from evennia.contrib import extended_room # <-new

-
-
class CharacterCmdset(default_cmds.Character_CmdSet):

… -def at_cmdset_creation(self):

-
-

… -self.add(extended_room.ExtendedRoomCmdSet) # <-new

-
-
-
-

Then reload to make the bew commands available. Note that they only work -on rooms with the typeclass ExtendedRoom. Create new rooms with the right -typeclass or use the typeclass command to swap existing rooms.

-
-
-class evennia.contrib.extended_room.ExtendedRoom(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultRoom

-

This room implements a more advanced look functionality depending on -time. It also allows for “details”, together with a slightly modified -look command.

-
-
-at_object_creation()[source]
-

Called when room is first created only.

-
- -
-
-get_time_and_season()[source]
-

Calculate the current time and season ids.

-
- -
-
-replace_timeslots(raw_desc, curr_time)[source]
-

Filter so that only time markers <timeslot>…</timeslot> of -the correct timeslot remains in the description.

-
-
Parameters
-
    -
  • raw_desc (str) – The unmodified description.

  • -
  • curr_time (str) – A timeslot identifier.

  • -
-
-
Returns
-

description (str) – A possibly moified description.

-
-
-
- -
-
-return_detail(key)[source]
-

This will attempt to match a “detail” to look for in the room.

-
-
Parameters
-

key (str) – A detail identifier.

-
-
Returns
-

detail (str or None) – A detail matching the given key.

-
-
-

Notes

-

A detail is a way to offer more things to look at in a room -without having to add new objects. For this to work, we -require a custom look command that allows for look -<detail> - the look command should defer to this method on -the current location (if it exists) before giving up on -finding the target.

-

Details are not season-sensitive, but are parsed for timeslot -markers.

-
- -
-
-set_detail(detailkey, description)[source]
-

This sets a new detail, using an Attribute “details”.

-
-
Parameters
-
    -
  • 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.

  • -
-
-
-
- -
-
-del_detail(detailkey, description)[source]
-

Delete a detail.

-

The description is ignored.

-
-
Parameters
-
    -
  • 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.

-
- -
-
-return_appearance(looker, **kwargs)[source]
-

This is called when e.g. the look command wants to retrieve -the description of this object.

-
-
Parameters
-
    -
  • looker (Object) – The object looking at us.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

description (str) – Our description.

-
-
-
- -
-
-update_current_description()[source]
-

This will update the description of the room if the time or season -has changed since last checked.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.extended_room.ExtendedRoom'
-
- -
-
-typename = 'ExtendedRoom'
-
- -
- -
-
-class evennia.contrib.extended_room.CmdExtendedRoomLook(**kwargs)[source]
-

Bases: evennia.commands.default.general.CmdLook

-

look

-
-
Usage:

look -look <obj> -look <room detail> -look *<account>

-
-
-

Observes your location, details at your location or objects in your vicinity.

-
-
-func()[source]
-

Handle the looking - add fallback to details.

-
- -
-
-aliases = ['l', 'ls']
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'look'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.extended_room.CmdExtendedRoomDesc(**kwargs)[source]
-

Bases: evennia.commands.default.building.CmdDesc

-

desc - describe an object or room.

-
-
Usage:

desc[/switch] [<obj> =] <description>

-
-
Switches for desc:

spring - set description for <season> in current room. -summer -autumn -winter

-
-
-

Sets the “desc” attribute on an object. If an object is not given, -describe the current room.

-

You can also embed special time markers in your room description, like this:

-
-

<night>In the darkness, the forest looks foreboding.</night>.

-
-

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 seasons and time-of-day slots only work on rooms in this -version of the desc command.

-
-
-aliases = ['describe']
-
- -
-
-switch_options = ()
-
- -
-
-reset_times(obj)[source]
-

By deleteting the caches we force a re-load.

-
- -
-
-func()[source]
-

Define extended command

-
- -
-
-help_category = 'building'
-
- -
-
-key = 'desc'
-
- -
-
-lock_storage = 'cmd:perm(desc) or perm(Builder)'
-
- -
- -
-
-class evennia.contrib.extended_room.CmdExtendedRoomDetail(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

sets a detail on a room

-
-
Usage:

@detail[/del] <key> [= <description>] -@detail <key>;<alias>;… = 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'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(Builder)'
-
- -
- -
-
-class evennia.contrib.extended_room.CmdExtendedRoomGameTime(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Check the game time

-
-
Usage:

time

-
-
-

Shows the current in-game time and season.

-
-
-key = 'time'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-func()[source]
-

Reads time info from current room

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.extended_room.ExtendedRoomCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Groups the extended-room commands.

-
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.contrib.extended_room.ExtendedRoomCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.fieldfill.html b/docs/0.9.5/api/evennia.contrib.fieldfill.html deleted file mode 100644 index 68b3fb6cef..0000000000 --- a/docs/0.9.5/api/evennia.contrib.fieldfill.html +++ /dev/null @@ -1,467 +0,0 @@ - - - - - - - - - evennia.contrib.fieldfill — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.fieldfill

-

Easy fillable form

-

Contrib - Tim Ashley Jenkins 2018

-

This module contains a function that calls an easily customizable EvMenu - this -menu presents the player with a fillable form, with fields that can be filled -out in any order. Each field’s value can be verified, with the function -allowing easy checks for text and integer input, minimum and maximum values / -character lengths, or can even be verified by a custom function. Once the form -is submitted, the form’s data is submitted as a dictionary to any callable of -your choice.

-

The function that initializes the fillable form menu is fairly simple, and -includes the caller, the template for the form, and the callback(caller, result) to which the form -data will be sent to upon submission.

-
-

init_fill_field(formtemplate, caller, formcallback)

-
-

Form templates are defined as a list of dictionaries - each dictionary -represents a field in the form, and contains the data for the field’s name and -behavior. For example, this basic form template will allow a player to fill out -a brief character profile:

-
-

PROFILE_TEMPLATE = [ -{“fieldname”:”Name”, “fieldtype”:”text”}, -{“fieldname”:”Age”, “fieldtype”:”number”}, -{“fieldname”:”History”, “fieldtype”:”text”}, -]

-
-

This will present the player with an EvMenu showing this basic form:

-
-
-
-
Name:

Age:

-
-
-
-

History:

-
-

While in this menu, the player can assign a new value to any field with the -syntax <field> = <new value>, like so:

-
-

> name = Ashley -Field ‘Name’ set to: Ashley

-
-

Typing ‘look’ by itself will show the form and its current values.

-
-
-

> look

-
-
-
Name: Ashley

Age:

-
-
-
-
-

History:

-
-

Number fields require an integer input, and will reject any text that can’t -be converted into an integer.

-
-

> age = youthful -Field ‘Age’ requires a number. -> age = 31 -Field ‘Age’ set to: 31

-
-

Form data is presented as an EvTable, so text of any length will wrap cleanly.

-
-
-

> history = EVERY MORNING I WAKE UP AND OPEN PALM SLAM[…] -Field ‘History’ set to: EVERY MORNING I WAKE UP AND[…] -> look

-
-
-
Name: Ashley

Age: 31

-
-
-
-
-
-
History: EVERY MORNING I WAKE UP AND OPEN PALM SLAM A VHS INTO THE SLOT.

IT’S CHRONICLES OF RIDDICK AND RIGHT THEN AND THERE I START DOING -THE MOVES ALONGSIDE WITH THE MAIN CHARACTER, RIDDICK. I DO EVERY -MOVE AND I DO EVERY MOVE HARD.

-
-
-
-

When the player types ‘submit’ (or your specified submit command), the menu -quits and the form’s data is passed to your specified function as a dictionary, -like so:

-
-

formdata = {“Name”:”Ashley”, “Age”:31, “History”:”EVERY MORNING I[…]”}

-
-

You can do whatever you like with this data in your function - forms can be used -to set data on a character, to help builders create objects, or for players to -craft items or perform other complicated actions with many variables involved.

-

The data that your form will accept can also be specified in your form template - -let’s say, for example, that you won’t accept ages under 18 or over 100. You can -do this by specifying “min” and “max” values in your field’s dictionary:

-
-

PROFILE_TEMPLATE = [ -{“fieldname”:”Name”, “fieldtype”:”text”}, -{“fieldname”:”Age”, “fieldtype”:”number”, “min”:18, “max”:100}, -{“fieldname”:”History”, “fieldtype”:”text”} -]

-
-

Now if the player tries to enter a value out of range, the form will not acept the -given value.

-
-

> age = 10 -Field ‘Age’ reqiures a minimum value of 18. -> age = 900 -Field ‘Age’ has a maximum value of 100.

-
-

Setting ‘min’ and ‘max’ for a text field will instead act as a minimum or -maximum character length for the player’s input.

-

There are lots of ways to present the form to the player - fields can have default -values or show a custom message in place of a blank value, and player input can be -verified by a custom function, allowing for a great deal of flexibility. There -is also an option for ‘bool’ fields, which accept only a True / False input and -can be customized to represent the choice to the player however you like (E.G. -Yes/No, On/Off, Enabled/Disabled, etc.)

-

This module contains a simple example form that demonstrates all of the included -functionality - a command that allows a player to compose a message to another -online character and have it send after a custom delay. You can test it by -importing this module in your game’s default_cmdsets.py module and adding -CmdTestMenu to your default character’s command set.

-

FIELD TEMPLATE KEYS: -Required:

-
-

fieldname (str): Name of the field, as presented to the player. -fieldtype (str): Type of value required: ‘text’, ‘number’, or ‘bool’.

-
-
-
Optional:

max (int): Maximum character length (if text) or value (if number). -min (int): Minimum charater length (if text) or value (if number). -truestr (str): String for a ‘True’ value in a bool field.

-
-

(E.G. ‘On’, ‘Enabled’, ‘Yes’)

-
-
-
falsestr (str): String for a ‘False’ value in a bool field.

(E.G. ‘Off’, ‘Disabled’, ‘No’)

-
-
-

default (str): Initial value (blank if not given). -blankmsg (str): Message to show in place of value when field is blank. -cantclear (bool): Field can’t be cleared if True. -required (bool): If True, form cannot be submitted while field is blank. -verifyfunc (callable): Name of a callable used to verify input - takes

-
-

(caller, value) as arguments. If the function returns True, -the player’s input is considered valid - if it returns False, -the input is rejected. Any other value returned will act as -the field’s new value, replacing the player’s input. This -allows for values that aren’t strings or integers (such as -object dbrefs). For boolean fields, return ‘0’ or ‘1’ to set -the field to False or True.

-
-
-
-
-
-class evennia.contrib.fieldfill.FieldEvMenu(caller, menudata, startnode='start', cmdset_mergetype='Replace', cmdset_priority=1, auto_quit=True, auto_look=True, auto_help=True, cmd_on_exit='look', persistent=False, startnode_input='', session=None, debug=False, **kwargs)[source]
-

Bases: evennia.utils.evmenu.EvMenu

-

Custom EvMenu type with its own node formatter - removes extraneous lines

-
-
-node_formatter(nodetext, optionstext)[source]
-

Formats the entirety of the node.

-
-
Parameters
-
    -
  • nodetext (str) – The node text as returned by self.nodetext_formatter.

  • -
  • optionstext (str) – The options display as returned by self.options_formatter.

  • -
  • caller (Object, Account or None, optional) – The caller of the node.

  • -
-
-
Returns
-

node (str) – The formatted node to display.

-
-
-
- -
- -
-
-evennia.contrib.fieldfill.init_fill_field(formtemplate, caller, formcallback, pretext='', posttext='', submitcmd='submit', borderstyle='cells', formhelptext=None, persistent=False, initial_formdata=None)[source]
-

Initializes a menu presenting a player with a fillable form - once the form -is submitted, the data will be passed as a dictionary to your chosen -function.

-
-
Parameters
-
    -
  • formtemplate (list of dicts) – The template for the form’s fields.

  • -
  • caller (obj) – Player who will be filling out the form.

  • -
  • formcallback (callable) – Function to pass the completed form’s data to.

  • -
-
-
-
-
Options:

pretext (str): Text to put before the form in the menu. -posttext (str): Text to put after the form in the menu. -submitcmd (str): Command used to submit the form. -borderstyle (str): Form’s EvTable border style. -formhelptext (str): Help text for the form menu (or default is provided). -persistent (bool): Whether to make the EvMenu persistent across reboots. -initial_formdata (dict): Initial data for the form - a blank form with

-
-

defaults specified in the template will be generated otherwise. -In the case of a form used to edit properties on an object or a -similar application, you may want to generate the initial form -data dynamically before calling init_fill_field.

-
-
-
-
- -
-
-evennia.contrib.fieldfill.menunode_fieldfill(caller, raw_string, **kwargs)[source]
-

This is an EvMenu node, which calls itself over and over in order to -allow a player to enter values into a fillable form. When the form is -submitted, the form data is passed to a callback as a dictionary.

-
- -
-
-evennia.contrib.fieldfill.form_template_to_dict(formtemplate)[source]
-

Initializes a dictionary of form data from the given list-of-dictionaries -form template, as formatted above.

-
-
Parameters
-

formtemplate (list of dicts) – Tempate for the form to be initialized.

-
-
Returns
-

formdata (dict) – Dictionary of initalized form data.

-
-
-
- -
-
-evennia.contrib.fieldfill.display_formdata(formtemplate, formdata, pretext='', posttext='', borderstyle='cells')[source]
-

Displays a form’s current data as a table. Used in the form menu.

-
-
Parameters
-
    -
  • formtemplate (list of dicts) – Template for the form

  • -
  • formdata (dict) – Form’s current data

  • -
-
-
-
-
Options:

pretext (str): Text to put before the form table. -posttext (str): Text to put after the form table. -borderstyle (str): EvTable’s border style.

-
-
-
- -
-
-evennia.contrib.fieldfill.verify_online_player(caller, value)[source]
-

Example ‘verify function’ that matches player input to an online character -or else rejects their input as invalid.

-
-
Parameters
-
    -
  • caller (obj) – Player entering the form data.

  • -
  • value (str) – String player entered into the form, to be verified.

  • -
-
-
Returns
-

matched_character (obj or False)

-
-
dbref to a currently logged in

character object - reference to the object will be stored in -the form instead of a string. Returns False if no match is -made.

-
-
-

-
-
-
- -
-
-class evennia.contrib.fieldfill.CmdTestMenu(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

This test command will initialize a menu that presents you with a form. -You can fill out the fields of this form in any order, and then type in -‘send’ to send a message to another online player, which will reach them -after a delay you specify.

-
-
Usage:

<field> = <new value> -clear <field> -help -look -quit -send

-
-
-
-
-key = 'testmenu'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-evennia.contrib.fieldfill.sendmessage(obj, text)[source]
-

Callback to send a message to a player.

-
-
Parameters
-
    -
  • obj (obj) – Player to message.

  • -
  • text (str) – Message.

  • -
-
-
-
- -
-
-evennia.contrib.fieldfill.init_delayed_message(caller, formdata)[source]
-

Initializes a delayed message, using data from the example form.

-
-
Parameters
-
    -
  • caller (obj) – Character submitting the message.

  • -
  • formdata (dict) – Data from submitted form.

  • -
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.gendersub.html b/docs/0.9.5/api/evennia.contrib.gendersub.html deleted file mode 100644 index ae14b6e8a6..0000000000 --- a/docs/0.9.5/api/evennia.contrib.gendersub.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - - - - - evennia.contrib.gendersub — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.gendersub

-

Gendersub

-

Griatch 2015

-

This is a simple gender-aware Character class for allowing users to -insert custom markers in their text to indicate gender-aware -messaging. It relies on a modified msg() and is meant as an -inspiration and starting point to how to do stuff like this.

-
-
An object can have the following genders:
    -
  • male (he/his)

  • -
  • female (her/hers)

  • -
  • neutral (it/its)

  • -
  • ambiguous (they/them/their/theirs)

  • -
-
-
-

When in use, messages can contain special tags to indicate pronouns gendered -based on the one being addressed. Capitalization will be retained.

-
    -
  • |s, |S: Subjective form: he, she, it, He, She, It, They

  • -
  • |o, |O: Objective form: him, her, it, Him, Her, It, Them

  • -
  • |p, |P: Possessive form: his, her, its, His, Her, Its, Their

  • -
  • |a, |A: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs

  • -
-

For example,

-

char.msg(“%s falls on |p face with a thud.” % char.key) -“Tom falls on his face with a thud”

-

The default gender is “ambiguous” (they/them/their/theirs).

-

To use, have DefaultCharacter inherit from this, or change -setting.DEFAULT_CHARACTER to point to this class.

-

The @gender command is used to set the gender. It needs to be added to the -default cmdset before it becomes available.

-
-
-class evennia.contrib.gendersub.SetGender(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Sets gender on yourself

-
-
Usage:

@gender male||female||neutral||ambiguous

-
-
-
-
-key = '@gender'
-
- -
-
-aliases = ['@sex']
-
- -
-
-locks = 'cmd:all();call:all()'
-
- -
-
-func()[source]
-

Implements the command.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();call:all()'
-
- -
- -
-
-class evennia.contrib.gendersub.GenderCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultCharacter

-

This is a Character class aware of gender.

-
-
-at_object_creation()[source]
-

Called once when the object is created.

-
- -
-
-msg(text=None, from_obj=None, session=None, **kwargs)[source]
-

Emits something to a session attached to the object. -Overloads the default msg() implementation to include -gender-aware markers in output.

-
-
Parameters
-
    -
  • text (str or tuple, optional) – The message to send. This -is treated internally like any send-command, so its -value can be a tuple if sending multiple arguments to -the text oob command.

  • -
  • from_obj (obj, optional) – object that is sending. If -given, at_msg_send will be called

  • -
  • session (Session or list, optional) – session or list of -sessions to relay to, if any. If set, will -force send regardless of MULTISESSION_MODE.

  • -
-
-
-

Notes

-

at_msg_receive will be called on this Object. -All extra kwargs will be passed on to the protocol.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.gendersub.GenderCharacter'
-
- -
-
-typename = 'GenderCharacter'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.health_bar.html b/docs/0.9.5/api/evennia.contrib.health_bar.html deleted file mode 100644 index 101052f9ce..0000000000 --- a/docs/0.9.5/api/evennia.contrib.health_bar.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - - - evennia.contrib.health_bar — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.health_bar

-

Health Bar

-

Contrib - Tim Ashley Jenkins 2017

-

The function provided in this module lets you easily display visual -bars or meters - “health bar” is merely the most obvious use for this, -though these bars are highly customizable and can be used for any sort -of appropriate data besides player health.

-

Today’s players may be more used to seeing statistics like health, -stamina, magic, and etc. displayed as bars rather than bare numerical -values, so using this module to present this data this way may make it -more accessible. Keep in mind, however, that players may also be using -a screen reader to connect to your game, which will not be able to -represent the colors of the bar in any way. By default, the values -represented are rendered as text inside the bar which can be read by -screen readers.

-

The health bar will account for current values above the maximum or -below 0, rendering them as a completely full or empty bar with the -values displayed within.

-
-
-evennia.contrib.health_bar.display_meter(cur_value, max_value, length=30, fill_color=['R', 'Y', 'G'], empty_color='B', text_color='w', align='left', pre_text='', post_text='', show_values=True)[source]
-

Represents a current and maximum value given as a “bar” rendered with -ANSI or xterm256 background colors.

-
-
Parameters
-
    -
  • cur_value (int) – Current value to display

  • -
  • max_value (int) – Maximum value to display

  • -
-
-
-
-
Options:

length (int): Length of meter returned, in characters -fill_color (list): List of color codes for the full portion

-
-

of the bar, sans any sort of prefix - both ANSI and xterm256 -colors are usable. When the bar is empty, colors toward the -start of the list will be chosen - when the bar is full, colors -towards the end are picked. You can adjust the ‘weights’ of -the changing colors by adding multiple entries of the same -color - for example, if you only want the bar to change when -it’s close to empty, you could supply [‘R’,’Y’,’G’,’G’,’G’]

-
-

empty_color (str): Color code for the empty portion of the bar. -text_color (str): Color code for text inside the bar. -align (str): “left”, “right”, or “center” - alignment of text in the bar -pre_text (str): Text to put before the numbers in the bar -post_text (str): Text to put after the numbers in the bar -show_values (bool): If true, shows the numerical values represented by

-
-

the bar. It’s highly recommended you keep this on, especially if -there’s no info given in pre_text or post_text, as players on screen -readers will be unable to read the graphical aspect of the bar.

-
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.html b/docs/0.9.5/api/evennia.contrib.html deleted file mode 100644 index 8f7f7e5751..0000000000 --- a/docs/0.9.5/api/evennia.contrib.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - evennia.contrib — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib

-

This sub-package holds Evennia’s contributions - code that may be -useful but are deemed too game-specific to go into the core library.

-

See README.md for more info.

- - -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.ingame_python.callbackhandler.html b/docs/0.9.5/api/evennia.contrib.ingame_python.callbackhandler.html deleted file mode 100644 index 7bf42bfd13..0000000000 --- a/docs/0.9.5/api/evennia.contrib.ingame_python.callbackhandler.html +++ /dev/null @@ -1,347 +0,0 @@ - - - - - - - - - evennia.contrib.ingame_python.callbackhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.ingame_python.callbackhandler

-

Module containing the CallbackHandler for individual objects.

-
-
-class evennia.contrib.ingame_python.callbackhandler.CallbackHandler(obj)[source]
-

Bases: object

-

The callback handler for a specific object.

-

The script that contains all callbacks will be reached through this -handler. This handler is therefore a shortcut to be used by -developers. This handler (accessible through obj.callbacks) is a -shortcut to manipulating callbacks within this object, getting, -adding, editing, deleting and calling them.

-
-
-script = None
-
- -
-
-__init__(obj)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-all()[source]
-

Return all callbacks linked to this object.

-
-
Returns
-

All callbacks in a dictionary callback_name – callback}. The callback -is returned as a namedtuple to simplify manipulation.

-
-
-
- -
-
-get(callback_name)[source]
-

Return the callbacks associated with this name.

-
-
Parameters
-

callback_name (str) – the name of the callback.

-
-
Returns
-

A list of callbacks associated with this object and of this name.

-
-
-
-

Note

-

This method returns a list of callback objects (namedtuple -representations). If the callback name cannot be found in the -object’s callbacks, return an empty list.

-
-
- -
-
-get_variable(variable_name)[source]
-

Return the variable value or None.

-
-
Parameters
-

variable_name (str) – the name of the variable.

-
-
Returns
-

Either the variable’s value or None.

-
-
-
- -
-
-add(callback_name, code, author=None, valid=False, parameters='')[source]
-

Add a new callback for this object.

-
-
Parameters
-
    -
  • callback_name (str) – the name of the callback to add.

  • -
  • code (str) – the Python code associated with this callback.

  • -
  • author (Character or Account, optional) – the author of the callback.

  • -
  • valid (bool, optional) – should the callback be connected?

  • -
  • parameters (str, optional) – optional parameters.

  • -
-
-
Returns
-

The callback definition that was added or None.

-
-
-
- -
-
-edit(callback_name, number, code, author=None, valid=False)[source]
-

Edit an existing callback bound to this object.

-
-
Parameters
-
    -
  • callback_name (str) – the name of the callback to edit.

  • -
  • number (int) – the callback number to be changed.

  • -
  • code (str) – the Python code associated with this callback.

  • -
  • author (Character or Account, optional) – the author of the callback.

  • -
  • valid (bool, optional) – should the callback be connected?

  • -
-
-
Returns
-

The callback definition that was edited or None.

-
-
Raises
-

RuntimeError if the callback is locked.

-
-
-
- -
-
-remove(callback_name, number)[source]
-

Delete the specified callback bound to this object.

-
-
Parameters
-
    -
  • callback_name (str) – the name of the callback to delete.

  • -
  • number (int) – the number of the callback to delete.

  • -
-
-
Raises
-

RuntimeError if the callback is locked.

-
-
-
- -
-
-call(callback_name, *args, **kwargs)[source]
-

Call the specified callback(s) bound to this object.

-
-
Parameters
-
    -
  • callback_name (str) – the callback name to call.

  • -
  • *args – additional variables for this callback.

  • -
-
-
Keyword Arguments
-
    -
  • number (int, optional) – call just a specific callback.

  • -
  • parameters (str, optional) – call a callback with parameters.

  • -
  • locals (dict, optional) – a locals replacement.

  • -
-
-
Returns
-

True to report the callback was called without interruption, -False otherwise. If the callbackHandler isn’t found, return -None.

-
-
-
- -
-
-static format_callback(callback)[source]
-

Return the callback namedtuple to represent the specified callback.

-
-
Parameters
-

callback (dict) – the callback definition.

-
-
-

The callback given in argument should be a dictionary containing -the expected fields for a callback (code, author, valid…).

-
- -
- -
-
-class evennia.contrib.ingame_python.callbackhandler.Callback(obj, name, number, code, author, valid, parameters, created_on, updated_by, updated_on)
-

Bases: tuple

-
-
-property author
-

Alias for field number 4

-
- -
-
-property code
-

Alias for field number 3

-
- -
-
-property created_on
-

Alias for field number 7

-
- -
-
-property name
-

Alias for field number 1

-
- -
-
-property number
-

Alias for field number 2

-
- -
-
-property obj
-

Alias for field number 0

-
- -
-
-property parameters
-

Alias for field number 6

-
- -
-
-property updated_by
-

Alias for field number 8

-
- -
-
-property updated_on
-

Alias for field number 9

-
- -
-
-property valid
-

Alias for field number 5

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.ingame_python.commands.html b/docs/0.9.5/api/evennia.contrib.ingame_python.commands.html deleted file mode 100644 index b88bdbfca9..0000000000 --- a/docs/0.9.5/api/evennia.contrib.ingame_python.commands.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - evennia.contrib.ingame_python.commands — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.ingame_python.commands

-

Module containing the commands of the in-game Python system.

-
-
-class evennia.contrib.ingame_python.commands.CmdCallback(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Command to edit callbacks.

-
-
-key = '@call'
-
- -
-
-aliases = ['@calls', '@callbacks', '@callback']
-
- -
-
-locks = 'cmd:perm(developer)'
-
- -
-
-help_category = 'building'
-
- -
-
-get_help(caller, cmdset)[source]
-

Return the help message for this command and this caller.

-

The help text of this specific command will vary depending -on user permission.

-
-
Parameters
-
    -
  • caller (Object or Account) – the caller asking for help on the command.

  • -
  • cmdset (CmdSet) – the command set (if you need additional commands).

  • -
-
-
Returns
-

docstring (str) – the help text to provide the caller for this command.

-
-
-
- -
-
-func()[source]
-

Command body.

-
- -
-
-list_callbacks()[source]
-

Display the list of callbacks connected to the object.

-
- -
-
-add_callback()[source]
-

Add a callback.

-
- -
-
-edit_callback()[source]
-

Edit a callback.

-
- -
-
-del_callback()[source]
-

Delete a callback.

-
- -
-
-accept_callback()[source]
-

Accept a callback.

-
- -
-
-list_tasks()[source]
-

List the active tasks.

-
- -
-
-lock_storage = 'cmd:perm(developer)'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.ingame_python.eventfuncs.html b/docs/0.9.5/api/evennia.contrib.ingame_python.eventfuncs.html deleted file mode 100644 index 9d6abe8f81..0000000000 --- a/docs/0.9.5/api/evennia.contrib.ingame_python.eventfuncs.html +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - - evennia.contrib.ingame_python.eventfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.ingame_python.eventfuncs

-

Module defining basic eventfuncs for the event system.

-

Eventfuncs are just Python functions that can be used inside of calllbacks.

-
-
-evennia.contrib.ingame_python.eventfuncs.deny()[source]
-

Deny, that is stop, the callback here.

-

Notes

-

This function will raise an exception to terminate the callback -in a controlled way. If you use this function in an event called -prior to a command, the command will be cancelled as well. Good -situations to use the deny() function are in events that begins -by can_, because they usually can be cancelled as easily as that.

-
- -
-
-evennia.contrib.ingame_python.eventfuncs.get(**kwargs)[source]
-

Return an object with the given search option or None if None is found.

-
-
Keyword Arguments
-

searchable data or property (Any) –

-
-
Returns
-

The object found that meet these criteria for research, or -None if none is found.

-
-
-

Notes

-

This function is very useful to retrieve objects with a specific -ID. You know that room #32 exists, but you don’t have it in -the callback variables. Quite simple:

-
-

room = get(id=32)

-
-

This function doesn’t perform a search on objects, but a direct -search in the database. It’s recommended to use it for objects -you know exist, using their IDs or other unique attributes. -Looking for objects by key is possible (use db_key as an -argument) but remember several objects can share the same key.

-
- -
-
-evennia.contrib.ingame_python.eventfuncs.call_event(obj, event_name, seconds=0)[source]
-

Call the specified event in X seconds.

-
-
Parameters
-
    -
  • obj (Object) – the typeclassed object containing the event.

  • -
  • event_name (str) – the event name to be called.

  • -
  • seconds (int or float) – the number of seconds to wait before calling -the event.

  • -
-
-
-

Notes

-

This eventfunc can be used to call other events from inside of an -event in a given time. This will create a pause between events. This -will not freeze the game, and you can expect characters to move -around (unless you prevent them from doing so).

-

Variables that are accessible in your event using ‘call()’ will be -kept and passed on to the event to call.

-

Chained callbacks are designed for this very purpose: they -are never called automatically by the game, rather, they need -to be called from inside another event.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.ingame_python.html b/docs/0.9.5/api/evennia.contrib.ingame_python.html deleted file mode 100644 index 9487ec69db..0000000000 --- a/docs/0.9.5/api/evennia.contrib.ingame_python.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - evennia.contrib.ingame_python — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.ingame_python.scripts.html b/docs/0.9.5/api/evennia.contrib.ingame_python.scripts.html deleted file mode 100644 index fc2d82386a..0000000000 --- a/docs/0.9.5/api/evennia.contrib.ingame_python.scripts.html +++ /dev/null @@ -1,468 +0,0 @@ - - - - - - - - - evennia.contrib.ingame_python.scripts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.ingame_python.scripts

-

Scripts for the in-game Python system.

-
-
-class evennia.contrib.ingame_python.scripts.EventHandler(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

The event handler that contains all events in a global script.

-

This script shouldn’t be created more than once. It contains -event (in a non-persistent attribute) and callbacks (in a -persistent attribute). The script method would help adding, -editing and deleting these events and callbacks.

-
-
-at_script_creation()[source]
-

Hook called when the script is created.

-
- -
-
-at_start()[source]
-

Set up the event system when starting.

-

Note that this hook is called every time the server restarts -(including when it’s reloaded). This hook performs the following -tasks:

-
    -
  • Create temporarily stored events.

  • -
  • Generate locals (individual events’ namespace).

  • -
  • Load eventfuncs, including user-defined ones.

  • -
  • Re-schedule tasks that aren’t set to fire anymore.

  • -
  • Effectively connect the handler to the main script.

  • -
-
- -
-
-get_events(obj)[source]
-

Return a dictionary of events on this object.

-
-
Parameters
-

obj (Object or typeclass) – the connected object or a general typeclass.

-
-
Returns
-

A dictionary of the object’s events.

-
-
-

Notes

-

Events would define what the object can have as -callbacks. Note, however, that chained callbacks will not -appear in events and are handled separately.

-

You can also request the events of a typeclass, not a -connected object. This is useful to get the global list -of events for a typeclass that has no object yet.

-
- -
-
-get_variable(variable_name)[source]
-

Return the variable defined in the locals.

-

This can be very useful to check the value of a variable that can be modified in an event, and whose value will be used in code. This system allows additional customization.

-
-
Parameters
-

variable_name (str) – the name of the variable to return.

-
-
Returns
-

The variable if found in the locals. -None if not found in the locals.

-
-
-
-

Note

-

This will return the variable from the current locals. -Keep in mind that locals are shared between events. As -every event is called one by one, this doesn’t pose -additional problems if you get the variable right after -an event has been executed. If, however, you differ, -there’s no guarantee the variable will be here or will -mean the same thing.

-
-
- -
-
-get_callbacks(obj)[source]
-

Return a dictionary of the object’s callbacks.

-
-
Parameters
-

obj (Object) – the connected objects.

-
-
Returns
-

A dictionary of the object’s callbacks.

-
-
-
-

Note

-

This method can be useful to override in some contexts, -when several objects would share callbacks.

-
-
- -
-
-add_callback(obj, callback_name, code, author=None, valid=False, parameters='')[source]
-

Add the specified callback.

-
-
Parameters
-
    -
  • obj (Object) – the Evennia typeclassed object to be extended.

  • -
  • callback_name (str) – the name of the callback to add.

  • -
  • code (str) – the Python code associated with this callback.

  • -
  • author (Character or Account, optional) – the author of the callback.

  • -
  • valid (bool, optional) – should the callback be connected?

  • -
  • parameters (str, optional) – optional parameters.

  • -
-
-
-
-

Note

-

This method doesn’t check that the callback type exists.

-
-
- -
-
-edit_callback(obj, callback_name, number, code, author=None, valid=False)[source]
-

Edit the specified callback.

-
-
Parameters
-
    -
  • obj (Object) – the Evennia typeclassed object to be edited.

  • -
  • callback_name (str) – the name of the callback to edit.

  • -
  • number (int) – the callback number to be changed.

  • -
  • code (str) – the Python code associated with this callback.

  • -
  • author (Character or Account, optional) – the author of the callback.

  • -
  • valid (bool, optional) – should the callback be connected?

  • -
-
-
Raises
-

RuntimeError if the callback is locked.

-
-
-
-

Note

-

This method doesn’t check that the callback type exists.

-
-
- -
-
-del_callback(obj, callback_name, number)[source]
-

Delete the specified callback.

-
-
Parameters
-
    -
  • obj (Object) – the typeclassed object containing the callback.

  • -
  • callback_name (str) – the name of the callback to delete.

  • -
  • number (int) – the number of the callback to delete.

  • -
-
-
Raises
-

RuntimeError if the callback is locked.

-
-
-
- -
-
-accept_callback(obj, callback_name, number)[source]
-

Valid a callback.

-
-
Parameters
-
    -
  • obj (Object) – the object containing the callback.

  • -
  • callback_name (str) – the name of the callback.

  • -
  • number (int) – the number of the callback.

  • -
-
-
-
- -
-
-call(obj, callback_name, *args, **kwargs)[source]
-

Call the connected callbacks.

-
-
Parameters
-
    -
  • obj (Object) – the Evennia typeclassed object.

  • -
  • callback_name (str) – the callback name to call.

  • -
  • *args – additional variables for this callback.

  • -
-
-
Keyword Arguments
-
    -
  • number (int, optional) – call just a specific callback.

  • -
  • parameters (str, optional) – call a callback with parameters.

  • -
  • locals (dict, optional) – a locals replacement.

  • -
-
-
Returns
-

True to report the callback was called without interruption, -False otherwise.

-
-
-
- -
-
-handle_error(callback, trace)[source]
-

Handle an error in a callback.

-
-
Parameters
-
    -
  • callback (dict) – the callback representation.

  • -
  • trace (list) – the traceback containing the exception.

  • -
-
-
-

Notes

-

This method can be useful to override to change the default -handling of errors. By default, the error message is sent to -the character who last updated the callback, if connected. -If not, display to the everror channel.

-
- -
-
-add_event(typeclass, name, variables, help_text, custom_call, custom_add)[source]
-

Add a new event for a defined typeclass.

-
-
Parameters
-
    -
  • typeclass (str) – the path leading to the typeclass.

  • -
  • name (str) – the name of the event to add.

  • -
  • variables (list of str) – list of variable names for this event.

  • -
  • help_text (str) – the long help text of the event.

  • -
  • custom_call (callable or None) – the function to be called -when the event fires.

  • -
  • custom_add (callable or None) – the function to be called when -a callback is added.

  • -
-
-
-
- -
-
-set_task(seconds, obj, callback_name)[source]
-

Set and schedule a task to run.

-
-
Parameters
-
    -
  • seconds (int, float) – the delay in seconds from now.

  • -
  • obj (Object) – the typecalssed object connected to the event.

  • -
  • callback_name (str) – the callback’s name.

  • -
-
-
-

Notes

-

This method allows to schedule a “persistent” task. -‘utils.delay’ is called, but a copy of the task is kept in -the event handler, and when the script restarts (after reload), -the differed delay is called again. -The dictionary of locals is frozen and will be available -again when the task runs. This feature, however, is limited -by the database: all data cannot be saved. Lambda functions, -class methods, objects inside an instance and so on will -not be kept in the locals dictionary.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.ingame_python.scripts.EventHandler'
-
- -
-
-typename = 'EventHandler'
-
- -
- -
-
-class evennia.contrib.ingame_python.scripts.TimeEventScript(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

Gametime-sensitive script.

-
-
-at_script_creation()[source]
-

The script is created.

-
- -
-
-at_repeat()[source]
-

Call the event and reset interval.

-

It is necessary to restart the script to reset its interval -only twice after a reload. When the script has undergone -down time, there’s usually a slight shift in game time. Once -the script restarts once, it will set the average time it -needs for all its future intervals and should not need to be -restarted. In short, a script that is created shouldn’t need -to restart more than once, and a script that is reloaded should -restart only twice.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.ingame_python.scripts.TimeEventScript'
-
- -
-
-typename = 'TimeEventScript'
-
- -
- -
-
-evennia.contrib.ingame_python.scripts.complete_task(task_id)[source]
-

Mark the task in the event handler as complete.

-
-
Parameters
-

task_id (int) – the task ID.

-
-
-
-

Note

-

This function should be called automatically for individual tasks.

-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.ingame_python.tests.html b/docs/0.9.5/api/evennia.contrib.ingame_python.tests.html deleted file mode 100644 index 2f7d4fa8f4..0000000000 --- a/docs/0.9.5/api/evennia.contrib.ingame_python.tests.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - - - evennia.contrib.ingame_python.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.ingame_python.tests

-

Module containing the test cases for the in-game Python system.

-
-
-class evennia.contrib.ingame_python.tests.TestEventHandler(methodName='runTest')[source]
-

Bases: evennia.utils.test_resources.EvenniaTest

-

Test cases of the event handler to add, edit or delete events.

-
-
-setUp()[source]
-

Create the event handler.

-
- -
-
-tearDown()[source]
-

Stop the event handler.

-
- -
-
-test_start()[source]
-

Simply make sure the handler runs with proper initial values.

-
- -
-
-test_add_validation()[source]
-

Add a callback while needing validation.

-
- -
-
-test_edit()[source]
-

Test editing a callback.

-
- -
-
-test_edit_validation()[source]
-

Edit a callback when validation isn’t automatic.

-
- -
-
-test_del()[source]
-

Try to delete a callback.

-
- -
-
-test_accept()[source]
-

Accept an callback.

-
- -
-
-test_call()[source]
-

Test to call amore complex callback.

-
- -
-
-test_handler()[source]
-

Test the object handler.

-
- -
- -
-
-class evennia.contrib.ingame_python.tests.TestCmdCallback(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-

Test the @callback command.

-
-
-setUp()[source]
-

Create the callback handler.

-
- -
-
-tearDown()[source]
-

Stop the callback handler.

-
- -
-
-test_list()[source]
-

Test listing callbacks with different rights.

-
- -
-
-test_add()[source]
-

Test to add an callback.

-
- -
-
-test_del()[source]
-

Add and remove an callback.

-
- -
-
-test_lock()[source]
-

Test the lock of multiple editing.

-
- -
-
-test_accept()[source]
-

Accept an callback.

-
- -
- -
-
-class evennia.contrib.ingame_python.tests.TestDefaultCallbacks(methodName='runTest')[source]
-

Bases: evennia.commands.default.tests.CommandTest

-

Test the default callbacks.

-
-
-setUp()[source]
-

Create the callback handler.

-
- -
-
-tearDown()[source]
-

Stop the callback handler.

-
- -
-
-test_exit()[source]
-

Test the callbacks of an exit.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.ingame_python.typeclasses.html b/docs/0.9.5/api/evennia.contrib.ingame_python.typeclasses.html deleted file mode 100644 index 3ffd3392b8..0000000000 --- a/docs/0.9.5/api/evennia.contrib.ingame_python.typeclasses.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - evennia.contrib.ingame_python.typeclasses — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.ingame_python.typeclasses

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.ingame_python.utils.html b/docs/0.9.5/api/evennia.contrib.ingame_python.utils.html deleted file mode 100644 index 027eb1cf68..0000000000 --- a/docs/0.9.5/api/evennia.contrib.ingame_python.utils.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - - evennia.contrib.ingame_python.utils — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.ingame_python.utils

-

Functions to extend the event system.

-

These functions are to be used by developers to customize events and callbacks.

-
-
-evennia.contrib.ingame_python.utils.get_event_handler()[source]
-

Return the event handler or None.

-
- -
-
-evennia.contrib.ingame_python.utils.register_events(path_or_typeclass)[source]
-

Register the events in this typeclass.

-
-
Parameters
-

path_or_typeclass (str or type) – the Python path leading to the -class containing events, or the class itself.

-
-
Returns
-

The typeclass itself.

-
-
-

Notes

-

This function will read events from the _events class variable -defined in the typeclass given in parameters. It will add -the events, either to the script if it exists, or to some -temporary storage, waiting for the script to be initialized.

-
- -
-
-evennia.contrib.ingame_python.utils.get_next_wait(format)[source]
-

Get the length of time in seconds before format.

-
-
Parameters
-

format (str) – a time format matching the set calendar.

-
-
Returns
-

until (int or float) – the number of seconds until the event. -usual (int or float): the usual number of seconds between events. -format (str): a string format representing the time.

-
-
-

Notes

-

The time format could be something like “2018-01-08 12:00”. The -number of units set in the calendar affects the way seconds are -calculated.

-
- -
-
-evennia.contrib.ingame_python.utils.time_event(obj, event_name, number, parameters)[source]
-

Create a time-related event.

-
-
Parameters
-
    -
  • obj (Object) – the object on which sits the event.

  • -
  • event_name (str) – the event’s name.

  • -
  • number (int) – the number of the event.

  • -
  • parameters (str) – the parameter of the event.

  • -
-
-
-
- -
-
-evennia.contrib.ingame_python.utils.keyword_event(callbacks, parameters)[source]
-

Custom call for events with keywords (like push, or pull, or turn…).

-
-
Parameters
-
    -
  • callbacks (list of dict) – the list of callbacks to be called.

  • -
  • parameters (str) – the actual parameters entered to trigger the callback.

  • -
-
-
Returns
-

A list containing the callback dictionaries to be called.

-
-
-

Notes

-

This function should be imported and added as a custom_call -parameter to add the event when the event supports keywords -as parameters. Keywords in parameters are one or more words -separated by a comma. For instance, a ‘push 1, one’ callback can -be set to trigger when the player ‘push 1’ or ‘push one’.

-
- -
-
-evennia.contrib.ingame_python.utils.phrase_event(callbacks, parameters)[source]
-

Custom call for events with keywords in sentences (like say or whisper).

-
-
Parameters
-
    -
  • callbacks (list of dict) – the list of callbacks to be called.

  • -
  • parameters (str) – the actual parameters entered to trigger the callback.

  • -
-
-
Returns
-

A list containing the callback dictionaries to be called.

-
-
-

Notes

-

This function should be imported and added as a custom_call -parameter to add the event when the event supports keywords -in phrases as parameters. Keywords in parameters are one or more -words separated by a comma. For instance, a ‘say yes, okay’ callback -can be set to trigger when the player says something containing -either “yes” or “okay” (maybe ‘say I don’t like it, but okay’).

-
- -
-
-exception evennia.contrib.ingame_python.utils.InterruptEvent[source]
-

Bases: RuntimeError

-

Interrupt the current event.

-

You shouldn’t have to use this exception directly, probably use the -deny() function that handles it instead.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.mail.html b/docs/0.9.5/api/evennia.contrib.mail.html deleted file mode 100644 index fc00aaf9bd..0000000000 --- a/docs/0.9.5/api/evennia.contrib.mail.html +++ /dev/null @@ -1,361 +0,0 @@ - - - - - - - - - evennia.contrib.mail — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.mail

-

In-Game Mail system

-

Evennia Contribution - grungies1138 2016

-

A simple Brandymail style @mail system that uses the Msg class from Evennia -Core. It has two Commands, both of which can be used on their own:

-
-
    -
  • CmdMail - this should sit on the Account cmdset and makes the @mail command

  • -
-
-

available both IC and OOC. Mails will always go to Accounts (other players).

-
-
    -
  • CmdMailCharacter - this should sit on the Character cmdset and makes the @mail

  • -
-
-

command ONLY available when puppeting a character. Mails will be sent to other -Characters only and will not be available when OOC.

-
-
    -
  • If adding both commands to their respective cmdsets, you’ll get two separate

  • -
-
-

IC and OOC mailing systems, with different lists of mail for IC and OOC modes.

-
-
-

Installation:

-

Install one or both of the following (see above):

-
    -
  • CmdMail (IC + OOC mail, sent between players)

    -
    -

    # mygame/commands/default_cmds.py

    -

    from evennia.contrib import mail

    -
    -
    # in AccountCmdSet.at_cmdset_creation:

    self.add(mail.CmdMail())

    -
    -
    -
    -
  • -
  • CmdMailCharacter (optional, IC only mail, sent between characters)

    -
    -

    # mygame/commands/default_cmds.py

    -

    from evennia.contrib import mail

    -
    -
    # in CharacterCmdSet.at_cmdset_creation:

    self.add(mail.CmdMailCharacter())

    -
    -
    -
    -
  • -
-

Once installed, use help mail in game for help with the mail command. Use -@ic/@ooc to switch in and out of IC/OOC modes.

-
-
-class evennia.contrib.mail.CmdMail(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxAccountCommand

-

Communicate with others by sending mail.

-
-
Usage:

@mail - Displays all the mail an account has in their mailbox -@mail <#> - Displays a specific message -@mail <accounts>=<subject>/<message>

-
-
    -
  • Sends a message to the comma separated list of accounts.

  • -
-
-

@mail/delete <#> - Deletes a specific message -@mail/forward <account list>=<#>[/<Message>]

-
-
    -
  • Forwards an existing message to the specified list of accounts, -original message is delivered with optional Message prepended.

  • -
-
-
-
@mail/reply <#>=<message>
    -
  • Replies to a message #. Prepends message to the original -message text.

  • -
-
-
-
-
Switches:

delete - deletes a message -forward - forward a received message to another object with an optional message attached. -reply - Replies to a received message, appending the original message to the bottom.

-
-
-

Examples

-

@mail 2 -@mail Griatch=New mail/Hey man, I am sending you a message! -@mail/delete 6 -@mail/forward feend78 Griatch=4/You guys should read this. -@mail/reply 9=Thanks for the info!

-
-
-key = '@mail'
-
- -
-
-aliases = ['mail']
-
- -
-
-lock = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-parse()[source]
-

Add convenience check to know if caller is an Account or not since this cmd -will be able to add to either Object- or Account level.

-
- -
-
-search_targets(namelist)[source]
-

Search a list of targets of the same type as caller.

-
-
Parameters
-
    -
  • caller (Object or Account) – The type of object to search.

  • -
  • namelist (list) – List of strings for objects to search for.

  • -
-
-
Returns
-

targetlist (Queryset) – Any target matches.

-
-
-
- -
-
-get_all_mail()[source]
-
-
Returns a list of all the messages where the caller is a recipient. These

are all messages tagged with tags of the mail category.

-
-
-
-
Returns
-

messages (QuerySet) – Matching Msg objects.

-
-
-
- -
-
-send_mail(recipients, subject, message, caller)[source]
-

Function for sending new mail. Also useful for sending notifications -from objects or systems.

-
-
Parameters
-
    -
  • recipients (list) – list of Account or Character objects to receive -the newly created mails.

  • -
  • subject (str) – The header or subject of the message to be delivered.

  • -
  • message (str) – The body of the message being sent.

  • -
  • caller (obj) – The object (or Account or Character) that is sending the message.

  • -
-
-
-
- -
-
-func()[source]
-

Do the main command functionality

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.mail.CmdMailCharacter(**kwargs)[source]
-

Bases: evennia.contrib.mail.CmdMail

-

Communicate with others by sending mail.

-
-
Usage:

@mail - Displays all the mail an account has in their mailbox -@mail <#> - Displays a specific message -@mail <accounts>=<subject>/<message>

-
-
    -
  • Sends a message to the comma separated list of accounts.

  • -
-
-

@mail/delete <#> - Deletes a specific message -@mail/forward <account list>=<#>[/<Message>]

-
-
    -
  • Forwards an existing message to the specified list of accounts, -original message is delivered with optional Message prepended.

  • -
-
-
-
@mail/reply <#>=<message>
    -
  • Replies to a message #. Prepends message to the original -message text.

  • -
-
-
-
-
Switches:

delete - deletes a message -forward - forward a received message to another object with an optional message attached. -reply - Replies to a received message, appending the original message to the bottom.

-
-
-

Examples

-

@mail 2 -@mail Griatch=New mail/Hey man, I am sending you a message! -@mail/delete 6 -@mail/forward feend78 Griatch=4/You guys should read this. -@mail/reply 9=Thanks for the info!

-
-
-account_caller = False
-
- -
-
-aliases = ['mail']
-
- -
-
-help_category = 'general'
-
- -
-
-key = '@mail'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.mapbuilder.html b/docs/0.9.5/api/evennia.contrib.mapbuilder.html deleted file mode 100644 index 39b53a3631..0000000000 --- a/docs/0.9.5/api/evennia.contrib.mapbuilder.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - evennia.contrib.mapbuilder — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.mapbuilder

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.menu_login.html b/docs/0.9.5/api/evennia.contrib.menu_login.html deleted file mode 100644 index 972f208ad0..0000000000 --- a/docs/0.9.5/api/evennia.contrib.menu_login.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - evennia.contrib.menu_login — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.menu_login

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.multidescer.html b/docs/0.9.5/api/evennia.contrib.multidescer.html deleted file mode 100644 index 3eb68bd20b..0000000000 --- a/docs/0.9.5/api/evennia.contrib.multidescer.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - - - evennia.contrib.multidescer — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.multidescer

-

Evennia Mutltidescer

-

Contrib - Griatch 2016

-

A “multidescer” is a concept from the MUSH world. It allows for -creating, managing and switching between multiple character -descriptions. This multidescer will not require any changes to the -Character class, rather it will use the multidescs Attribute (a -list) and create it if it does not exist.

-

This contrib also works well together with the rpsystem contrib (which -also adds the short descriptions and the sdesc command).

-

Installation:

-

Edit mygame/commands/default_cmdsets.py and add -from evennia.contrib.multidescer import CmdMultiDesc to the top.

-

Next, look up the at_cmdset_create method of the CharacterCmdSet -class and add a line self.add(CmdMultiDesc()) to the end -of it.

-

Reload the server and you should have the +desc command available (it -will replace the default desc command).

-
-
-exception evennia.contrib.multidescer.DescValidateError[source]
-

Bases: ValueError

-

Used for tracebacks from desc systems

-
- -
-
-class evennia.contrib.multidescer.CmdMultiDesc(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Manage multiple descriptions

-
-
Usage:

+desc [key] - show current desc desc with <key> -+desc <key> = <text> - add/replace desc with <key> -+desc/list - list descriptions (abbreviated) -+desc/list/full - list descriptions (full texts) -+desc/edit <key> - add/edit desc <key> in line editor -+desc/del <key> - delete desc <key> -+desc/swap <key1>-<key2> - swap positions of <key1> and <key2> in list -+desc/set <key> [+key+…] - set desc as default or combine multiple descs

-
-
-

Notes

-

When combining multiple descs with +desc/set <key> + <key2> + …, -any keys not matching an actual description will be inserted -as plain text. Use e.g. ansi line break ||/ to add a new -paragraph and + + or ansi space ||_ to add extra whitespace.

-
-
-key = '+desc'
-
- -
-
-aliases = ['desc']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-func()[source]
-

Implements the multidescer. We will use db.desc for the -description in use and db.multidesc to store all descriptions.

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.puzzles.html b/docs/0.9.5/api/evennia.contrib.puzzles.html deleted file mode 100644 index 7ecc6d7089..0000000000 --- a/docs/0.9.5/api/evennia.contrib.puzzles.html +++ /dev/null @@ -1,547 +0,0 @@ - - - - - - - - - evennia.contrib.puzzles — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.puzzles

-

Puzzles System - Provides a typeclass and commands for -objects that can be combined (i.e. ‘use’d) to produce -new objects.

-

Evennia contribution - Henddher 2018

-

A Puzzle is a recipe of what objects (aka parts) must -be combined by a player so a new set of objects -(aka results) are automatically created.

-

Consider this simple Puzzle:

-
-

orange, mango, yogurt, blender = fruit smoothie

-
-

As a Builder:

-
-

@create/drop orange -@create/drop mango -@create/drop yogurt -@create/drop blender -@create/drop fruit smoothie

-

@puzzle smoothie, orange, mango, yogurt, blender = fruit smoothie -… -Puzzle smoothie(#1234) created successfuly.

-

@destroy/force orange, mango, yogurt, blender, fruit smoothie

-

@armpuzzle #1234 -Part orange is spawned at … -Part mango is spawned at … -…. -Puzzle smoothie(#1234) has been armed successfully

-
-

As Player:

-
-

use orange, mango, yogurt, blender -… -Genius, you blended all fruits to create a fruit smoothie!

-
-

Details:

-

Puzzles are created from existing objects. The given -objects are introspected to create prototypes for the -puzzle parts and results. These prototypes become the -puzzle recipe. (See PuzzleRecipe and @puzzle -command). Once the recipe is created, all parts and result -can be disposed (i.e. destroyed).

-

At a later time, a Builder or a Script can arm the puzzle -and spawn all puzzle parts in their respective -locations (See @armpuzzle).

-

A regular player can collect the puzzle parts and combine -them (See use command). If player has specified -all pieces, the puzzle is considered solved and all -its puzzle parts are destroyed while the puzzle results -are spawened on their corresponding location.

-

Installation:

-

Add the PuzzleSystemCmdSet to all players. -Alternatively:

-
-

@py self.cmdset.add(‘evennia.contrib.puzzles.PuzzleSystemCmdSet’)

-
-
-
-evennia.contrib.puzzles.proto_def(obj, with_tags=True)[source]
-

Basic properties needed to spawn -and compare recipe with candidate part

-
- -
-
-evennia.contrib.puzzles.maskout_protodef(protodef, mask)[source]
-

Returns a new protodef after removing protodef values based on mask

-
- -
-
-class evennia.contrib.puzzles.PuzzleRecipe(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

Definition of a Puzzle Recipe

-
-
-save_recipe(puzzle_name, parts, results)[source]
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.puzzles.PuzzleRecipe'
-
- -
-
-typename = 'PuzzleRecipe'
-
- -
- -
-
-class evennia.contrib.puzzles.CmdCreatePuzzleRecipe(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Creates a puzzle recipe. A puzzle consists of puzzle-parts that -the player can ‘use’ together to create a specified result.

-
-
Usage:

@puzzle name,<part1[,part2,…>] = <result1[,result2,…]>

-
-
-

Example

-

create/drop balloon -create/drop glass of water -create/drop water balloon -@puzzle waterballon,balloon,glass of water = water balloon -@del ballon, glass of water, water balloon -@armpuzzle #1

-

Notes: -Each part and result are objects that must (temporarily) exist and be placed in their -corresponding location in order to create the puzzle. After the creation of the puzzle, -these objects are not needed anymore and can be deleted. Components of the puzzle -will be re-created by use of the @armpuzzle command later.

-
-
-key = '@puzzle'
-
- -
-
-aliases = ['@puzzlerecipe']
-
- -
-
-locks = 'cmd:perm(puzzle) or perm(Builder)'
-
- -
-
-help_category = 'puzzles'
-
- -
-
-confirm = True
-
- -
-
-default_confirm = 'no'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-lock_storage = 'cmd:perm(puzzle) or perm(Builder)'
-
- -
- -
-
-class evennia.contrib.puzzles.CmdEditPuzzle(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Edits puzzle properties

-
-
Usage:

@puzzleedit[/delete] <#dbref> -@puzzleedit <#dbref>/use_success_message = <Custom message> -@puzzleedit <#dbref>/use_success_location_message = <Custom message from {caller} producing {result_names}> -@puzzleedit <#dbref>/mask = attr1[,attr2,…]> -@puzzleedit[/addpart] <#dbref> = <obj[,obj2,…]> -@puzzleedit[/delpart] <#dbref> = <obj[,obj2,…]> -@puzzleedit[/addresult] <#dbref> = <obj[,obj2,…]> -@puzzleedit[/delresult] <#dbref> = <obj[,obj2,…]>

-
-
Switches:

addpart - adds parts to the puzzle -delpart - removes parts from the puzzle -addresult - adds results to the puzzle -delresult - removes results from the puzzle -delete - deletes the recipe. Existing parts and results aren’t modified

-

mask - attributes to exclude during matching (e.g. location, desc, etc.) -use_success_location_message containing {result_names} and {caller} will

-
-

automatically be replaced with correct values. Both are optional.

-
-

When removing parts/results, it’s possible to remove all.

-
-
-
-
-key = '@puzzleedit'
-
- -
-
-locks = 'cmd:perm(puzzleedit) or perm(Builder)'
-
- -
-
-help_category = 'puzzles'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(puzzleedit) or perm(Builder)'
-
- -
- -
-
-class evennia.contrib.puzzles.CmdArmPuzzle(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Arms a puzzle by spawning all its parts.

-
-
Usage:

@armpuzzle <puzzle #dbref>

-
-
-

Notes

-

Create puzzles with @puzzle; get list of -defined puzzles using @lspuzzlerecipes.

-
-
-key = '@armpuzzle'
-
- -
-
-locks = 'cmd:perm(armpuzzle) or perm(Builder)'
-
- -
-
-help_category = 'puzzles'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(armpuzzle) or perm(Builder)'
-
- -
- -
-
-class evennia.contrib.puzzles.CmdUsePuzzleParts(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Use an object, or a group of objects at once.

-

Example

-

You look around you and see a pole, a long string, and a needle.

-

use pole, long string, needle

-

Genius! You built a fishing pole.

-
-
Usage:

use <obj1> [,obj2,…]

-
-
-
-
-key = 'use'
-
- -
-
-aliases = ['combine']
-
- -
-
-locks = 'cmd:pperm(use) or pperm(Player)'
-
- -
-
-help_category = 'puzzles'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-lock_storage = 'cmd:pperm(use) or pperm(Player)'
-
- -
- -
-
-class evennia.contrib.puzzles.CmdListPuzzleRecipes(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Searches for all puzzle recipes

-
-
Usage:

@lspuzzlerecipes

-
-
-
-
-key = '@lspuzzlerecipes'
-
- -
-
-locks = 'cmd:perm(lspuzzlerecipes) or perm(Builder)'
-
- -
-
-help_category = 'puzzles'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(lspuzzlerecipes) or perm(Builder)'
-
- -
- -
-
-class evennia.contrib.puzzles.CmdListArmedPuzzles(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Searches for all armed puzzles

-
-
Usage:

@lsarmedpuzzles

-
-
-
-
-key = '@lsarmedpuzzles'
-
- -
-
-locks = 'cmd:perm(lsarmedpuzzles) or perm(Builder)'
-
- -
-
-help_category = 'puzzles'
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(lsarmedpuzzles) or perm(Builder)'
-
- -
- -
-
-class evennia.contrib.puzzles.PuzzleSystemCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

CmdSet to create, arm and resolve Puzzles

-
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.contrib.puzzles.PuzzleSystemCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.random_string_generator.html b/docs/0.9.5/api/evennia.contrib.random_string_generator.html deleted file mode 100644 index 139cb03dd8..0000000000 --- a/docs/0.9.5/api/evennia.contrib.random_string_generator.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - - - - - evennia.contrib.random_string_generator — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.random_string_generator

-

Pseudo-random generator and registry

-

Evennia contribution - Vincent Le Goff 2017

-

This contrib can be used to generate pseudo-random strings of information -with specific criteria. You could, for instance, use it to generate -phone numbers, license plate numbers, validation codes, non-sensivite -passwords and so on. The strings generated by the generator will be -stored and won’t be available again in order to avoid repetition. -Here’s a very simple example:

-
from evennia.contrib.random_string_generator import RandomStringGenerator
-# Create a generator for phone numbers
-phone_generator = RandomStringGenerator("phone number", r"555-[0-9]{3}-[0-9]{4}")
-# Generate a phone number (555-XXX-XXXX with X as numbers)
-number = phone_generator.get()
-# **number** will contain something like: "555-981-2207"
-# If you call **phone_generator.get**, it won't give the same anymore.phone_generator.all()
-# Will return a list of all currently-used phone numbers
-phone_generator.remove("555-981-2207")
-# The number can be generated again
-
-
-

To use it, you will need to:

-
    -
  1. Import the RandomStringGenerator class from the contrib.

  2. -
  3. Create an instance of this class taking two arguments: -- The name of the gemerator (like “phone number”, “license plate”…). -- The regular expression representing the expected results.

  4. -
  5. Use the generator’s all, get and remove methods as shown above.

  6. -
-

To understand how to read and create regular expressions, you can refer to -[the documentation on the re module](https://docs.python.org/2/library/re.html). -Some examples of regular expressions you could use:

-
    -
  • r”555-d{3}-d{4}”: 555, a dash, 3 digits, another dash, 4 digits.

  • -
  • r”[0-9]{3}[A-Z][0-9]{3}”: 3 digits, a capital letter, 3 digits.

  • -
  • r”[A-Za-z0-9]{8,15}”: between 8 and 15 letters and digits.

  • -
  • -
-

Behind the scenes, a script is created to store the generated information -for a single generator. The RandomStringGenerator object will also -read the regular expression you give to it to see what information is -required (letters, digits, a more restricted class, simple characters…)… -More complex regular expressions (with branches for instance) might not be -available.

-
-
-exception evennia.contrib.random_string_generator.RejectedRegex[source]
-

Bases: RuntimeError

-

The provided regular expression has been rejected.

-

More details regarding why this error occurred will be provided in -the message. The usual reason is the provided regular expression is -not specific enough and could lead to inconsistent generating.

-
- -
-
-exception evennia.contrib.random_string_generator.ExhaustedGenerator[source]
-

Bases: RuntimeError

-

The generator hasn’t any available strings to generate anymore.

-
- -
-
-class evennia.contrib.random_string_generator.RandomStringGeneratorScript(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

The global script to hold all generators.

-

It will be automatically created the first time generate is called -on a RandomStringGenerator object.

-
-
-at_script_creation()[source]
-

Hook called when the script is created.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.random_string_generator.RandomStringGeneratorScript'
-
- -
-
-typename = 'RandomStringGeneratorScript'
-
- -
- -
-
-class evennia.contrib.random_string_generator.RandomStringGenerator(name, regex)[source]
-

Bases: object

-

A generator class to generate pseudo-random strings with a rule.

-

The “rule” defining what the generator should provide in terms of -string is given as a regular expression when creating instances of -this class. You can use the all method to get all generated strings, -the get method to generate a new string, the remove method -to remove a generated string, or the clear method to remove all -generated strings.

-

Bear in mind, however, that while the generated strings will be -stored to avoid repetition, the generator will not concern itself -with how the string is stored on the object you use. You probably -want to create a tag to mark this object. This is outside of the scope -of this class.

-
-
-script = None
-
- -
-
-__init__(name, regex)[source]
-

Create a new generator.

-
-
Parameters
-
    -
  • name (str) – name of the generator to create.

  • -
  • regex (str) – regular expression describing the generator.

  • -
-
-
-

Notes

-

name should be an explicit name. If you use more than one -generator in your game, be sure to give them different names. -This name will be used to store the generated information -in the global script, and in case of errors.

-

The regular expression should describe the generator, what -it should generate: a phone number, a license plate, a password -or something else. Regular expressions allow you to use -pretty advanced criteria, but be aware that some regular -expressions will be rejected if not specific enough.

-
-
Raises
-
    -
  • RejectedRegex – the provided regular expression couldn’t be

  • -
  • accepted as a valid generator description.

  • -
-
-
-
- -
-
-all()[source]
-

Return all generated strings for this generator.

-
-
Returns
-

strings (list of strr) – the list of strings that are already -used. The strings that were generated first come first in the list.

-
-
-
- -
-
-get(store=True, unique=True)[source]
-

Generate a pseudo-random string according to the regular expression.

-
-
Parameters
-
    -
  • store (bool, optional) – store the generated string in the script.

  • -
  • unique (bool, optional) – keep on trying if the string is already used.

  • -
-
-
Returns
-

The newly-generated string.

-
-
Raises
-

ExhaustedGenerator – if there’s no available string in this generator.

-
-
-
-

Note

-

Unless asked explicitly, the returned string can’t repeat itself.

-
-
- -
-
-remove(element)[source]
-

Remove a generated string from the list of stored strings.

-
-
Parameters
-

element (str) – the string to remove from the list of generated strings.

-
-
Raises
-

ValueError – the specified value hasn’t been generated and is not present.

-
-
-
-

Note

-

The specified string has to be present in the script (so -has to have been generated). It will remove this entry -from the script, so this string could be generated again by -calling the get method.

-
-
- -
-
-clear()[source]
-

Clear the generator of all generated strings.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.rplanguage.html b/docs/0.9.5/api/evennia.contrib.rplanguage.html deleted file mode 100644 index f4a0e4298c..0000000000 --- a/docs/0.9.5/api/evennia.contrib.rplanguage.html +++ /dev/null @@ -1,407 +0,0 @@ - - - - - - - - - evennia.contrib.rplanguage — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.rplanguage

-

Language and whisper obfuscation system

-

Evennia contrib - Griatch 2015

-

This module is intented to be used with an emoting system (such as -contrib/rpsystem.py). It offers the ability to obfuscate spoken words -in the game in various ways:

-
    -
  • -
    Language: The language functionality defines a pseudo-language map

    to any number of languages. The string will be obfuscated depending -on a scaling that (most likely) will be input as a weighted average of -the language skill of the speaker and listener.

    -
    -
    -
  • -
  • -
    Whisper: The whisper functionality will gradually “fade out” a

    whisper along as scale 0-1, where the fading is based on gradually -removing sections of the whisper that is (supposedly) easier to -overhear (for example “s” sounds tend to be audible even when no other -meaning can be determined).

    -
    -
    -
  • -
-

Usage:

-
-
from evennia.contrib import rplanguage
-
-# need to be done once, here we create the "default" lang
-rplanguage.add_language()
-
-say = "This is me talking."
-whisper = "This is me whispering.
-
-print rplanguage.obfuscate_language(say, level=0.0)
-<<< "This is me talking."
-print rplanguage.obfuscate_language(say, level=0.5)
-<<< "This is me byngyry."
-print rplanguage.obfuscate_language(say, level=1.0)
-<<< "Daly ly sy byngyry."
-
-result = rplanguage.obfuscate_whisper(whisper, level=0.0)
-<<< "This is me whispering"
-result = rplanguage.obfuscate_whisper(whisper, level=0.2)
-<<< "This is m- whisp-ring"
-result = rplanguage.obfuscate_whisper(whisper, level=0.5)
-<<< "---s -s -- ---s------"
-result = rplanguage.obfuscate_whisper(whisper, level=0.7)
-<<< "---- -- -- ----------"
-result = rplanguage.obfuscate_whisper(whisper, level=1.0)
-<<< "..."
-
-
-

To set up new languages, import and use the add_language() -helper method in this module. This allows you to customize the -“feel” of the semi-random language you are creating. Especially -the word_length_variance helps vary the length of translated -words compared to the original and can help change the “feel” for -the language you are creating. You can also add your own -dictionary and “fix” random words for a list of input words.

-

Below is an example of “elvish”, using “rounder” vowels and sounds:

-
phonemes = "oi oh ee ae aa eh ah ao aw ay er ey ow ia ih iy "                "oy ua uh uw y p b t d f v t dh s z sh zh ch jh k "                "ng g m n l r w",
-vowels = "eaoiuy"
-grammar = "v vv vvc vcc vvcc cvvc vccv vvccv vcvccv vcvcvcc vvccvvcc "               "vcvvccvvc cvcvvcvvcc vcvcvvccvcvv",
-word_length_variance = 1
-noun_postfix = "'la"
-manual_translations = {"the":"y'e", "we":"uyi", "she":"semi", "he":"emi",
-                      "you": "do", 'me':'mi','i':'me', 'be':"hy'e", 'and':'y'}
-
-rplanguage.add_language(key="elvish", phonemes=phonemes, grammar=grammar,
-                         word_length_variance=word_length_variance,
-                         noun_postfix=noun_postfix, vowels=vowels,
-                         manual_translations=manual_translations
-                         auto_translations="my_word_file.txt")
-
-
-

This will produce a decicively more “rounded” and “soft” language -than the default one. The few manual_translations also make sure -to make it at least look superficially “reasonable”.

-

The auto_translations keyword is useful, this accepts either a -list or a path to a file of words (one per line) to automatically -create fixed translations for according to the grammatical rules. -This allows to quickly build a large corpus of translated words -that never change (if this is desired).

-
-
-
-exception evennia.contrib.rplanguage.LanguageError[source]
-

Bases: RuntimeError

-
- -
-
-exception evennia.contrib.rplanguage.LanguageExistsError[source]
-

Bases: evennia.contrib.rplanguage.LanguageError

-
- -
-
-class evennia.contrib.rplanguage.LanguageHandler(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This is a storage class that should usually not be created on its -own. It’s automatically created by a call to obfuscate_language -or add_language below.

-

Languages are implemented as a “logical” pseudo- consistent language -algorith here. The idea is that a language is built up from -phonemes. These are joined together according to a “grammar” of -possible phoneme- combinations and allowed characters. It may -sound simplistic, but this allows to easily make -“similar-sounding” languages. One can also custom-define a -dictionary of some common words to give further consistency. -Optionally, the system also allows an input list of common words -to be loaded and given random translations. These will be stored -to disk and will thus not change. This gives a decent “stability” -of the language but if the goal is to obfuscate, this may allow -players to eventually learn to understand the gist of a sentence -even if their characters can not. Any number of languages can be -created this way.

-

This nonsense language will partially replace the actual spoken -language when so desired (usually because the speaker/listener -don’t know the language well enough).

-
-
-at_script_creation()[source]
-

Called when script is first started

-
- -
-
-add(key='default', phonemes='ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh s z sh zh ch jh k ng g m n l r w', grammar='v cv vc cvv vcc vcv cvcc vccv cvccv cvcvcc cvccvcv vccvccvc cvcvccvv cvcvcvcvv', word_length_variance=0, noun_translate=False, noun_prefix='', noun_postfix='', vowels='eaoiuy', manual_translations=None, auto_translations=None, force=False)[source]
-

Add a new language. Note that you generally only need to do -this once per language and that adding an existing language -will re-initialize all the random components to new permanent -values.

-
-
Parameters
-
    -
  • key (str, optional) – The name of the language. This -will be used as an identifier for the language so it -should be short and unique.

  • -
  • phonemes (str, optional) – Space-separated string of all allowed -phonemes in this language. If either of the base phonemes -(c, v, cc, vv) are present in the grammar, the phoneme list must -at least include one example of each.

  • -
  • grammar (str) – All allowed consonant (c) and vowel (v) combinations -allowed to build up words. Grammars are broken into the base phonemes -(c, v, cc, vv) prioritizing the longer bases. So cvv would be a -the c + vv (would allow for a word like ‘die’ whereas -cvcvccc would be c+v+c+v+cc+c (a word like ‘galosch’).

  • -
  • word_length_variance (real) – The variation of length of words. -0 means a minimal variance, higher variance may mean words -have wildly varying length; this strongly affects how the -language “looks”.

  • -
  • noun_translate (bool, optional) – If a proper noun, identified as a -capitalized word, should be translated or not. By default they -will not, allowing for e.g. the names of characters to be understandable.

  • -
  • noun_prefix (str, optional) – A prefix to go before every noun -in this language (if any).

  • -
  • noun_postfix (str, optuonal) – A postfix to go after every noun -in this language (if any, usually best to avoid combining -with noun_prefix or language becomes very wordy).

  • -
  • vowels (str, optional) – Every vowel allowed in this language.

  • -
  • manual_translations (dict, optional) – This allows for custom-setting -certain words in the language to mean the same thing. It is -on the form {real_word: fictional_word}, for example -{“the”, “y’e”} .

  • -
  • auto_translations (str or list, optional) – These are lists -words that should be auto-translated with a random, but -fixed, translation. If a path to a file, this file should -contain a list of words to produce translations for, one -word per line. If a list, the list’s elements should be -the words to translate. The manual_translations will -always override overlapping translations created -automatically.

  • -
  • force (bool, optional) – Unless true, will not allow the addition -of a language that is already created.

  • -
-
-
Raises
-

LanguageExistsError – Raised if trying to adding a language -with a key that already exists, without force being set.

-
-
-

Notes

-

The word_file is for example a word-frequency list for -the N most common words in the host language. The -translations will be random, but will be stored -persistently to always be the same. This allows for -building a quick, decently-sounding fictive language that -tend to produce the same “translation” (mostly) with the -same input sentence.

-
- -
-
-translate(text, level=0.0, language='default')[source]
-

Translate the text according to the given level.

-
-
Parameters
-
    -
  • text (str) – The text to translate

  • -
  • level (real) – Value between 0.0 and 1.0, where -0.0 means no obfuscation (text returned unchanged) and -1.0 means full conversion of every word. The closer to -1, the shorter words will be translated.

  • -
  • language (str) – The language key identifier.

  • -
-
-
Returns
-

text (str) – A translated string.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.rplanguage.LanguageHandler'
-
- -
-
-typename = 'LanguageHandler'
-
- -
- -
-
-evennia.contrib.rplanguage.obfuscate_language(text, level=0.0, language='default')[source]
-

Main access method for the language parser.

-
-
Parameters
-
    -
  • text (str) – Text to obfuscate.

  • -
  • level (real, optional) – A value from 0.0-1.0 determining -the level of obfuscation where 0 means no jobfuscation -(string returned unchanged) and 1.0 means the entire -string is obfuscated.

  • -
  • language (str, optional) – The identifier of a language -the system understands.

  • -
-
-
Returns
-

translated (str) – The translated text.

-
-
-
- -
-
-evennia.contrib.rplanguage.add_language(**kwargs)[source]
-

Access function to creating a new language. See the docstring of -LanguageHandler.add for list of keyword arguments.

-
- -
-
-evennia.contrib.rplanguage.available_languages()[source]
-

Returns all available language keys.

-
-
Returns
-

languages (list) – List of key strings of all available -languages.

-
-
-
- -
-
-evennia.contrib.rplanguage.obfuscate_whisper(whisper, level=0.0)[source]
-

Obfuscate whisper depending on a pre-calculated level -(that may depend on distance, listening skill etc)

-
-
Parameters
-
    -
  • whisper (str) – The whisper string to obscure. The -entire string will be considered in the obscuration.

  • -
  • level (real, optional) – This is a value 0-1, where 0 -means not obscured (whisper returned unchanged) and 1 -means fully obscured.

  • -
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.rpsystem.html b/docs/0.9.5/api/evennia.contrib.rpsystem.html deleted file mode 100644 index 80729fb150..0000000000 --- a/docs/0.9.5/api/evennia.contrib.rpsystem.html +++ /dev/null @@ -1,1278 +0,0 @@ - - - - - - - - - evennia.contrib.rpsystem — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.rpsystem

-

Roleplaying base system for Evennia

-

Contribution - Griatch, 2015

-

This module contains the ContribRPObject, ContribRPRoom and -ContribRPCharacter typeclasses. If you inherit your -objects/rooms/character from these (or make them the defaults) from -these you will get the following features:

-
-
    -
  • Objects/Rooms will get the ability to have poses and will report

  • -
-

the poses of items inside them (the latter most useful for Rooms). -- Characters will get poses and also sdescs (short descriptions) -that will be used instead of their keys. They will gain commands -for managing recognition (custom sdesc-replacement), masking -themselves as well as an advanced free-form emote command.

-
-

To use, simply import the typclasses you want from this module and use -them to create your objects, or set them to default.

-

In more detail, This RP base system introduces the following features -to a game, common to many RP-centric games:

-
-
    -
  • -
    emote system using director stance emoting (names/sdescs).

    This uses a customizable replacement noun (/me, @ etc) to -represent you in the emote. You can use /sdesc, /nick, /key or -/alias to reference objects in the room. You can use any -number of sdesc sub-parts to differentiate a local sdesc, or -use /1-sdesc etc to differentiate them. The emote also -identifies nested says.

    -
    -
    -
  • -
  • -
    sdesc obscuration of real character names for use in emotes

    and in any referencing such as object.search(). This relies -on an SdescHandler sdesc being set on the Character and -makes use of a custom Character.get_display_name hook. If -sdesc is not set, the character’s key is used instead. This -is particularly used in the emoting system.

    -
    -
    -
  • -
  • -
    recog system to assign your own nicknames to characters, can then

    be used for referencing. The user may recog a user and assign -any personal nick to them. This will be shown in descriptions -and used to reference them. This is making use of the nick -functionality of Evennia.

    -
    -
    -
  • -
  • masks to hide your identity (using a simple lock).

  • -
  • -
    pose system to set room-persistent poses, visible in room

    descriptions and when looking at the person/object. This is a -simple Attribute that modifies how the characters is viewed when -in a room as sdesc + pose.

    -
    -
    -
  • -
  • -
    in-emote says, including seamless integration with language

    obscuration routine (such as contrib/rplanguage.py)

    -
    -
    -
  • -
-
-

Examples:

-

> look -Tavern -The tavern is full of nice people

-

A tall man is standing by the bar.

-

Above is an example of a player with an sdesc “a tall man”. It is also -an example of a static pose: The “standing by the bar” has been set -by the player of the tall man, so that people looking at him can tell -at a glance what is going on.

-

> emote /me looks at /tall and says “Hello!”

-
-
I see:

Griatch looks at Tall man and says “Hello”.

-
-
Tall man (assuming his name is Tom) sees:

The godlike figure looks at Tom and says “Hello”.

-
-
-

Verbose Installation Instructions:

-
-
    -
  1. In typeclasses/character.py: -Import the ContribRPCharacter class:

    -
    -

    from evennia.contrib.rpsystem import ContribRPCharacter

    -
    -
    -
    Inherit ContribRPCharacter:

    Change “class Character(DefaultCharacter):” to -class Character(ContribRPCharacter):

    -
    -
    If you have any overriden calls in at_object_creation(self):

    Add super().at_object_creation() as the top line.

    -
    -
    -
  2. -
  3. -
    In typeclasses/rooms.py:

    Import the ContribRPRoom class: -from evennia.contrib.rpsystem import ContribRPRoom

    -
    -
    Inherit ContribRPRoom:

    Change class Room(DefaultRoom): to -class Room(ContribRPRoom):

    -
    -
    -
  4. -
  5. -
    In typeclasses/objects.py

    Import the ContribRPObject class: -from evennia.contrib.rpsystem import ContribRPObject

    -
    -
    Inherit ContribRPObject:

    Change class Object(DefaultObject): to -class Object(ContribRPObject):

    -
    -
    -
  6. -
  7. Reload the server (@reload or from console: “evennia reload”)

  8. -
  9. -
    Force typeclass updates as required. Example for your character:

    @type/reset/force me = typeclasses.characters.Character

    -
    -
    -
  10. -
-
-
-
-exception evennia.contrib.rpsystem.EmoteError[source]
-

Bases: Exception

-
- -
-
-exception evennia.contrib.rpsystem.SdescError[source]
-

Bases: Exception

-
- -
-
-exception evennia.contrib.rpsystem.RecogError[source]
-

Bases: Exception

-
- -
-
-exception evennia.contrib.rpsystem.LanguageError[source]
-

Bases: Exception

-
- -
-
-evennia.contrib.rpsystem.ordered_permutation_regex(sentence)[source]
-

Builds a regex that matches ‘ordered permutations’ of a sentence’s -words.

-
-
Parameters
-

sentence (str) – The sentence to build a match pattern to

-
-
Returns
-

regex (re object)

-
-
Compiled regex object represented the

possible ordered permutations of the sentence, from longest to -shortest.

-
-
-

-
-
-

Example

-

The sdesc_regex for an sdesc of ” very tall man” will -result in the following allowed permutations, -regex-matched in inverse order of length (case-insensitive): -“the very tall man”, “the very tall”, “very tall man”, -“very tall”, “the very”, “tall man”, “the”, “very”, “tall”, -and “man”. -We also add regex to make sure it also accepts num-specifiers, -like /2-tall.

-
- -
-
-evennia.contrib.rpsystem.regex_tuple_from_key_alias(obj)[source]
-

This will build a regex tuple for any object, not just from those -with sdesc/recog handlers. It’s used as a legacy mechanism for -being able to mix this contrib with objects not using sdescs, but -note that creating the ordered permutation regex dynamically for -every object will add computational overhead.

-
-
Parameters
-

obj (Object) – This object’s key and eventual aliases will -be used to build the tuple.

-
-
Returns
-

regex_tuple (tuple)

-
-
A tuple

(ordered_permutation_regex, obj, key/alias)

-
-
-

-
-
-
- -
-
-evennia.contrib.rpsystem.parse_language(speaker, emote)[source]
-

Parse the emote for language. This is -used with a plugin for handling languages.

-
-
Parameters
-
    -
  • speaker (Object) – The object speaking.

  • -
  • emote (str) – An emote possibly containing -language references.

  • -
-
-
Returns
-

(emote, mapping) (tuple)

-
-
A tuple where the

emote is the emote string with all says -(including quotes) replaced with reference -markers on the form {##n} where n is a running -number. The mapping is a dictionary between -the markers and a tuple (langname, saytext), where -langname can be None.

-
-
-

-
-
Raises
-

rplanguage.LanguageError – If an invalid language was specified.

-
-
-

Notes

-

Note that no errors are raised if the wrong language identifier -is given. -This data, together with the identity of the speaker, is -intended to be used by the “listener” later, since with this -information the language skill of the speaker can be offset to -the language skill of the listener to determine how much -information is actually conveyed.

-
- -
-
-evennia.contrib.rpsystem.parse_sdescs_and_recogs(sender, candidates, string, search_mode=False)[source]
-

Read a raw emote and parse it into an intermediary -format for distributing to all observers.

-
-
Parameters
-
    -
  • sender (Object) – The object sending the emote. This object’s -recog data will be considered in the parsing.

  • -
  • candidates (iterable) – A list of objects valid for referencing -in the emote.

  • -
-
-
-

string (str): The string (like an emote) we want to analyze for keywords. -search_mode (bool, optional): If True, the “emote” is a query string

-
-

we want to analyze. If so, the return value is changed.

-
-
-
Returns
-

(emote, mapping) (tuple)

-
-
If search_mode is False

(default), a tuple where the emote is the emote string, with -all references replaced with internal-representation {#dbref} -markers and mapping is a dictionary {“#dbref”:obj, …}.

-
-
result (list): If search_mode is True we are

performing a search query on string, looking for a specific -object. A list with zero, one or more matches.

-
-
-

-
-
Raises
-

EmoteException – For various ref-matching errors.

-
-
-

Notes

-

The parser analyzes and should understand the following -_PREFIX-tagged structures in the emote: -- self-reference (/me) -- recogs (any part of it) stored on emoter, matching obj in candidates. -- sdesc (any part of it) from any obj in candidates. -- N-sdesc, N-recog separating multi-matches (1-tall, 2-tall) -- says, “…” are

-
- -
-
-evennia.contrib.rpsystem.send_emote(sender, receivers, emote, anonymous_add='first', **kwargs)[source]
-

Main access function for distribute an emote.

-
-
Parameters
-
    -
  • sender (Object) – The one sending the emote.

  • -
  • receivers (iterable) – Receivers of the emote. These -will also form the basis for which sdescs are -‘valid’ to use in the emote.

  • -
  • emote (str) – The raw emote string as input by emoter.

  • -
  • anonymous_add (str or None, optional) – If sender is not -self-referencing in the emote, this will auto-add -sender’s data to the emote. Possible values are -- None: No auto-add at anonymous emote -- ‘last’: Add sender to the end of emote as [sender] -- ‘first’: Prepend sender to start of emote.

  • -
-
-
-
- -
-
-class evennia.contrib.rpsystem.SdescHandler(obj)[source]
-

Bases: object

-

This Handler wraps all operations with sdescs. We -need to use this since we do a lot preparations on -sdescs when updating them, in order for them to be -efficient to search for and query.

-

The handler stores data in the following Attributes

-
-

_sdesc - a string -_regex - an empty dictionary

-
-
-
-__init__(obj)[source]
-

Initialize the handler

-
-
Parameters
-

obj (Object) – The entity on which this handler is stored.

-
-
-
- -
-
-add(sdesc, max_length=60)[source]
-

Add a new sdesc to object, replacing the old one.

-
-
Parameters
-
    -
  • sdesc (str) – The sdesc to set. This may be stripped -of control sequences before setting.

  • -
  • max_length (int, optional) – The max limit of the sdesc.

  • -
-
-
Returns
-

sdesc (str) – The actually set sdesc.

-
-
Raises
-
    -
  • SdescError – If the sdesc is empty, can not be set or is

  • -
  • longer than max_length.

  • -
-
-
-
- -
-
-get()[source]
-

Simple getter. The sdesc should never be allowed to -be empty, but if it is we must fall back to the key.

-
- -
-
-get_regex_tuple()[source]
-

Return data for sdesc/recog handling

-
-
Returns
-

tup (tuple) – tuple (sdesc_regex, obj, sdesc)

-
-
-
- -
- -
-
-class evennia.contrib.rpsystem.RecogHandler(obj)[source]
-

Bases: object

-

This handler manages the recognition mapping -of an Object.

-

The handler stores data in Attributes as dictionaries of -the following names:

-
-

_recog_ref2recog -_recog_obj2recog -_recog_obj2regex

-
-
-
-__init__(obj)[source]
-

Initialize the handler

-
-
Parameters
-

obj (Object) – The entity on which this handler is stored.

-
-
-
- -
-
-add(obj, recog, max_length=60)[source]
-

Assign a custom recog (nick) to the given object.

-
-
Parameters
-
    -
  • obj (Object) – The object ot associate with the recog -string. This is usually determined from the sdesc in the -room by a call to parse_sdescs_and_recogs, but can also be -given.

  • -
  • recog (str) – The replacement string to use with this object.

  • -
  • max_length (int, optional) – The max length of the recog string.

  • -
-
-
Returns
-

recog (str) – The (possibly cleaned up) recog string actually set.

-
-
Raises
-

SdescError – When recog could not be set or sdesc longer -than max_length.

-
-
-
- -
-
-get(obj)[source]
-

Get recog replacement string, if one exists, otherwise -get sdesc and as a last resort, the object’s key.

-
-
Parameters
-

obj (Object) – The object, whose sdesc to replace

-
-
Returns
-

recog (str) – The replacement string to use.

-
-
-

Notes

-

This method will respect a “enable_recog” lock set on -obj (True by default) in order to turn off recog -mechanism. This is useful for adding masks/hoods etc.

-
- -
-
-all()[source]
-

Get a mapping of the recogs stored in handler.

-
-
Returns
-

recogs (dict) – A mapping of {recog: obj} stored in handler.

-
-
-
- -
-
-remove(obj)[source]
-

Clear recog for a given object.

-
-
Parameters
-

obj (Object) – The object for which to remove recog.

-
-
-
- -
-
-get_regex_tuple(obj)[source]
-
-
Returns
-

rec (tuple) – Tuple (recog_regex, obj, recog)

-
-
-
- -
- -
-
-class evennia.contrib.rpsystem.RPCommand(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

simple parent

-
-
-parse()[source]
-

strip extra whitespace

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'command'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.rpsystem.CmdEmote(**kwargs)[source]
-

Bases: evennia.contrib.rpsystem.RPCommand

-

Emote an action, allowing dynamic replacement of -text in the emote.

-
-
Usage:

emote text

-
-
-

Example

-

emote /me looks around. -emote With a flurry /me attacks /tall man with his sword. -emote “Hello”, /me says.

-

Describes an event in the world. This allows the use of /ref -markers to replace with the short descriptions or recognized -strings of objects in the same room. These will be translated to -emotes to match each person seeing it. Use “…” for saying -things and langcode”…” without spaces to say something in -a different language.

-
-
-key = 'emote'
-
- -
-
-aliases = [':']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Perform the emote.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.rpsystem.CmdSay(**kwargs)[source]
-

Bases: evennia.contrib.rpsystem.RPCommand

-

speak as your character

-
-
Usage:

say <message>

-
-
-

Talk to those in your current location.

-
-
-key = 'say'
-
- -
-
-aliases = ["'", '"']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Run the say command

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.rpsystem.CmdSdesc(**kwargs)[source]
-

Bases: evennia.contrib.rpsystem.RPCommand

-

Assign yourself a short description (sdesc).

-
-
Usage:

sdesc <short description>

-
-
-

Assigns a short description to yourself.

-
-
-key = 'sdesc'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Assign the sdesc

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.rpsystem.CmdPose(**kwargs)[source]
-

Bases: evennia.contrib.rpsystem.RPCommand

-

Set a static pose

-
-
Usage:

pose <pose> -pose default <pose> -pose reset -pose obj = <pose> -pose default obj = <pose> -pose reset obj =

-
-
-

Examples

-

pose leans against the tree -pose is talking to the barkeep. -pose box = is sitting on the floor.

-

Set a static pose. This is the end of a full sentence that starts -with your sdesc. If no full stop is given, it will be added -automatically. The default pose is the pose you get when using -pose reset. Note that you can use sdescs/recogs to reference -people in your pose, but these always appear as that person’s -sdesc in the emote, regardless of who is seeing it.

-
-
-key = 'pose'
-
- -
-
-parse()[source]
-

Extract the “default” alternative to the pose.

-
- -
-
-func()[source]
-

Create the pose

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.rpsystem.CmdRecog(**kwargs)[source]
-

Bases: evennia.contrib.rpsystem.RPCommand

-

Recognize another person in the same room.

-
-
Usage:

recog -recog sdesc as alias -forget alias

-
-
-

Example

-

recog tall man as Griatch -forget griatch

-

This will assign a personal alias for a person, or forget said alias. -Using the command without arguments will list all current recogs.

-
-
-key = 'recog'
-
- -
-
-aliases = ['recognize', 'forget']
-
- -
-
-parse()[source]
-

Parse for the sdesc as alias structure

-
- -
-
-func()[source]
-

Assign the recog

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.rpsystem.CmdMask(**kwargs)[source]
-

Bases: evennia.contrib.rpsystem.RPCommand

-

Wear a mask

-
-
Usage:

mask <new sdesc> -unmask

-
-
-

This will put on a mask to hide your identity. When wearing -a mask, your sdesc will be replaced by the sdesc you pick and -people’s recognitions of you will be disabled.

-
-
-key = 'mask'
-
- -
-
-aliases = ['unmask']
-
- -
-
-func()[source]
-

This is the actual executing part of the command. It is -called directly after self.parse(). See the docstring of this -module for which object properties are available (beyond those -set in self.parse())

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.rpsystem.RPSystemCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Mix-in for adding rp-commands to default cmdset.

-
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.contrib.rpsystem.RPSystemCmdSet'
-
- -
- -
-
-class evennia.contrib.rpsystem.ContribRPObject(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

This class is meant as a mix-in or parent for objects in an -rp-heavy game. It implements the base functionality for poses.

-
-
-at_object_creation()[source]
-

Called at initial creation.

-
- -
-
-search(searchdata, global_search=False, use_nicks=True, typeclass=None, location=None, attribute_name=None, quiet=False, exact=False, candidates=None, nofound_string=None, multimatch_string=None, use_dbref=None)[source]
-

Returns an Object matching a search string/condition, taking -sdescs into account.

-

Perform a standard object search in the database, handling -multiple results and lack thereof gracefully. By default, only -objects in the current location of self or its inventory are searched for.

-
-
Parameters
-
    -
  • searchdata (str or obj) –

    Primary search criterion. Will be matched -against object.key (with object.aliases second) unless -the keyword attribute_name specifies otherwise. -Special strings: -- #<num>: search by unique dbref. This is always

    -
    -

    a global search.

    -
    -
      -
    • me,self: self-reference to this object

    • -
    • -
      <num>-<string> - can be used to differentiate

      between multiple same-named matches

      -
      -
      -
    • -
    -

  • -
  • global_search (bool) – Search all objects globally. This is overruled -by location keyword.

  • -
  • use_nicks (bool) – Use nickname-replace (nicktype “object”) on searchdata.

  • -
  • typeclass (str or Typeclass, or list of either) – Limit search only -to Objects with this typeclass. May be a list of typeclasses -for a broader search.

  • -
  • location (Object or list) – Specify a location or multiple locations -to search. Note that this is used to query the contents of a -location and will not match for the location itself - -if you want that, don’t set this or use candidates to specify -exactly which objects should be searched.

  • -
  • attribute_name (str) – Define which property to search. If set, no -key+alias search will be performed. This can be used -to search database fields (db_ will be automatically -appended), and if that fails, it will try to return -objects having Attributes with this name and value -equal to searchdata. A special use is to search for -“key” here if you want to do a key-search without -including aliases.

  • -
  • quiet (bool) – don’t display default error messages - this tells the -search method that the user wants to handle all errors -themselves. It also changes the return value type, see -below.

  • -
  • exact (bool) – if unset (default) - prefers to match to beginning of -string rather than not matching at all. If set, requires -exact matching of entire string.

  • -
  • candidates (list of objects) – this is an optional custom list of objects -to search (filter) between. It is ignored if global_search -is given. If not set, this list will automatically be defined -to include the location, the contents of location and the -caller’s contents (inventory).

  • -
  • nofound_string (str) – optional custom string for not-found error message.

  • -
  • multimatch_string (str) – optional custom string for multimatch error header.

  • -
  • use_dbref (bool or None) – If None, only turn off use_dbref if we are of a lower -permission than Builder. Otherwise, honor the True/False value.

  • -
-
-
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.

-
-
-

-
-
-

Notes

-

To find Accounts, use eg. evennia.account_search. If -quiet=False, error messages will be handled by -settings.SEARCH_AT_RESULT and echoed automatically (on -error, return will be None). If quiet=True, the error -messaging is assumed to be handled by the caller.

-
- -
-
-get_display_name(looker, **kwargs)[source]
-

Displays the name of the object in a viewer-aware manner.

-
-
Parameters
-

looker (TypedObject) – The object or account that is looking -at/getting inforamtion for this object.

-
-
Keyword Arguments
-

pose (bool) – Include the pose (if available) in the return.

-
-
Returns
-

name (str) – A string of the sdesc containing the name of the object, -if this is defined.

-
-

including the DBREF if this user is privileged to control -said object.

-
-

-
-
-

Notes

-

The RPObject version doesn’t add color to its display.

-
- -
-
-return_appearance(looker)[source]
-

This formats a description. It is the hook a ‘look’ command -should call.

-
-
Parameters
-

looker (Object) – Object doing the looking.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.rpsystem.ContribRPObject'
-
- -
-
-typename = 'ContribRPObject'
-
- -
- -
-
-class evennia.contrib.rpsystem.ContribRPRoom(*args, **kwargs)[source]
-

Bases: evennia.contrib.rpsystem.ContribRPObject

-

Dummy inheritance for rooms.

-
-
-exception DoesNotExist
-

Bases: evennia.contrib.rpsystem.ContribRPObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.rpsystem.ContribRPObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.rpsystem.ContribRPRoom'
-
- -
-
-typename = 'ContribRPRoom'
-
- -
- -
-
-class evennia.contrib.rpsystem.ContribRPCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultCharacter, evennia.contrib.rpsystem.ContribRPObject

-

This is a character class that has poses, sdesc and recog.

-
-
-sdesc[source]
-
- -
-
-recog[source]
-
- -
-
-get_display_name(looker, **kwargs)[source]
-

Displays the name of the object in a viewer-aware manner.

-
-
Parameters
-

looker (TypedObject) – The object or account that is looking -at/getting inforamtion for this object.

-
-
Keyword Arguments
-

pose (bool) – Include the pose (if available) in the return.

-
-
Returns
-

name (str) – A string of the sdesc containing the name of the object, -if this is defined.

-
-

including the DBREF if this user is privileged to control -said object.

-
-

-
-
-

Notes

-

The RPCharacter version of this method colors its display to make -characters stand out from other objects.

-
- -
-
-at_object_creation()[source]
-

Called at initial creation.

-
- -
-
-at_before_say(message, **kwargs)[source]
-

Called before the object says or whispers anything, return modified message.

-
-
Parameters
-

message (str) – The suggested say/whisper text spoken by self.

-
-
Keyword Arguments
-

whisper (bool) – If True, this is a whisper rather than a say.

-
-
-
- -
-
-process_sdesc(sdesc, obj, **kwargs)[source]
-

Allows to customize how your sdesc is displayed (primarily by -changing colors).

-
-
Parameters
-
    -
  • sdesc (str) – The sdesc to display.

  • -
  • obj (Object) – The object to which the adjoining sdesc -belongs. If this object is equal to yourself, then -you are viewing yourself (and sdesc is your key). -This is not used by default.

  • -
-
-
Returns
-

sdesc (str)

-
-
The processed sdesc ready

for display.

-
-
-

-
-
-
- -
-
-process_recog(recog, obj, **kwargs)[source]
-

Allows to customize how a recog string is displayed.

-
-
Parameters
-
    -
  • recog (str) – The recog string. It has already been -translated from the original sdesc at this point.

  • -
  • obj (Object) – The object the recog:ed string belongs to. -This is not used by default.

  • -
-
-
Returns
-

recog (str) – The modified recog string.

-
-
-
- -
-
-process_language(text, speaker, language, **kwargs)[source]
-

Allows to process the spoken text, for example -by obfuscating language based on your and the -speaker’s language skills. Also a good place to -put coloring.

-
-
Parameters
-
    -
  • text (str) – The text to process.

  • -
  • speaker (Object) – The object delivering the text.

  • -
  • language (str) – An identifier string for the language.

  • -
-
-
Returns
-

text (str) – The optionally processed text.

-
-
-

Notes

-

This is designed to work together with a string obfuscator -such as the obfuscate_language or obfuscate_whisper in -the evennia.contrib.rplanguage module.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist, evennia.contrib.rpsystem.ContribRPObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned, evennia.contrib.rpsystem.ContribRPObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.rpsystem.ContribRPCharacter'
-
- -
-
-typename = 'ContribRPCharacter'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.security.auditing.html b/docs/0.9.5/api/evennia.contrib.security.auditing.html deleted file mode 100644 index d18c53c38d..0000000000 --- a/docs/0.9.5/api/evennia.contrib.security.auditing.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - evennia.contrib.security.auditing — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
- - -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.security.auditing.outputs.html b/docs/0.9.5/api/evennia.contrib.security.auditing.outputs.html deleted file mode 100644 index 0d42cfe25e..0000000000 --- a/docs/0.9.5/api/evennia.contrib.security.auditing.outputs.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - - - evennia.contrib.security.auditing.outputs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.security.auditing.outputs

-

Auditable Server Sessions - Example Outputs -Example methods demonstrating output destinations for logs generated by -audited server sessions.

-

This is designed to be a single source of events for developers to customize -and add any additional enhancements before events are written out– i.e. if you -want to keep a running list of what IPs a user logs in from on account/character -objects, or if you want to perform geoip or ASN lookups on IPs before committing, -or tag certain events with the results of a reputational lookup, this should be -the easiest place to do it. Write a method and invoke it via -settings.AUDIT_CALLBACK to have log data objects passed to it.

-

Evennia contribution - Johnny 2017

-
-
-evennia.contrib.security.auditing.outputs.to_file(data)[source]
-

Writes dictionaries of data generated by an AuditedServerSession to files -in JSON format, bucketed by date.

-

Uses Evennia’s native logger and writes to the default -log directory (~/yourgame/server/logs/ or settings.LOG_DIR)

-
-
Parameters
-

data (dict) – Parsed session transmission data.

-
-
-
- -
-
-evennia.contrib.security.auditing.outputs.to_syslog(data)[source]
-

Writes dictionaries of data generated by an AuditedServerSession to syslog.

-

Takes advantage of your system’s native logger and writes to wherever -you have it configured, which is independent of Evennia. -Linux systems tend to write to /var/log/syslog.

-

If you’re running rsyslog, you can configure it to dump and/or forward logs -to disk and/or an external data warehouse (recommended– if your server is -compromised or taken down, losing your logs along with it is no help!).

-
-
Parameters
-

data (dict) – Parsed session transmission data.

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.security.auditing.server.html b/docs/0.9.5/api/evennia.contrib.security.auditing.server.html deleted file mode 100644 index 236c044914..0000000000 --- a/docs/0.9.5/api/evennia.contrib.security.auditing.server.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - evennia.contrib.security.auditing.server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.security.auditing.server

-

Auditable Server Sessions: -Extension of the stock ServerSession that yields objects representing -user inputs and system outputs.

-

Evennia contribution - Johnny 2017

-
-
-class evennia.contrib.security.auditing.server.AuditedServerSession[source]
-

Bases: evennia.server.serversession.ServerSession

-

This particular implementation parses all server inputs and/or outputs and -passes a dict containing the parsed metadata to a callback method of your -creation. This is useful for recording player activity where necessary for -security auditing, usage analysis or post-incident forensic discovery.

-

* WARNING * -All strings are recorded and stored in plaintext. This includes those strings -which might contain sensitive data (create, connect, @password). These commands -have their arguments masked by default, but you must mask or mask any -custom commands of your own that handle sensitive information.

-

See README.md for installation/configuration instructions.

-
-
-audit(**kwargs)[source]
-

Extracts messages and system data from a Session object upon message -send or receive.

-
-
Keyword Arguments
-
    -
  • src (str) – Source of data; ‘client’ or ‘server’. Indicates direction.

  • -
  • text (str or list) – Client sends messages to server in the form of -lists. Server sends messages to client as string.

  • -
-
-
Returns
-

log (dict)

-
-
Dictionary object containing parsed system and user data

related to this message.

-
-
-

-
-
-
- -
-
-mask(msg)[source]
-

Masks potentially sensitive user information within messages before -writing to log. Recording cleartext password attempts is bad policy.

-
-
Parameters
-

msg (str) – Raw text string sent from client <-> server

-
-
Returns
-

msg (str) – Text string with sensitive information masked out.

-
-
-
- -
-
-data_out(**kwargs)[source]
-

Generic hook for sending data out through the protocol.

-
-
Keyword Arguments
-

kwargs (any) – Other data to the protocol.

-
-
-
- -
-
-data_in(**kwargs)[source]
-

Hook for protocols to send incoming data to the engine.

-
-
Keyword Arguments
-

kwargs (any) – Other data from the protocol.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.security.auditing.tests.html b/docs/0.9.5/api/evennia.contrib.security.auditing.tests.html deleted file mode 100644 index ac022d5e6e..0000000000 --- a/docs/0.9.5/api/evennia.contrib.security.auditing.tests.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - - evennia.contrib.security.auditing.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.security.auditing.tests

-

Module containing the test cases for the Audit system.

-
-
-class evennia.contrib.security.auditing.tests.AuditingTest(methodName='runTest')[source]
-

Bases: evennia.utils.test_resources.EvenniaTest

-
-
-test_mask()[source]
-

Make sure the ‘mask’ function is properly masking potentially sensitive -information from strings.

-
- -
-
-test_audit()[source]
-

Make sure the ‘audit’ function is returning a dictionary based on values -parsed from the Session object.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.security.html b/docs/0.9.5/api/evennia.contrib.security.html deleted file mode 100644 index 669381c7cf..0000000000 --- a/docs/0.9.5/api/evennia.contrib.security.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - evennia.contrib.security — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
- - -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.simpledoor.html b/docs/0.9.5/api/evennia.contrib.simpledoor.html deleted file mode 100644 index 3290eec8a2..0000000000 --- a/docs/0.9.5/api/evennia.contrib.simpledoor.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - - - - evennia.contrib.simpledoor — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.simpledoor

-

SimpleDoor

-

Contribution - Griatch 2016

-

A simple two-way exit that represents a door that can be opened and -closed. Can easily be expanded from to make it lockable, destroyable -etc. Note that the simpledoor is based on Evennia locks, so it will -not work for a superuser (which bypasses all locks) - the superuser -will always appear to be able to close/open the door over and over -without the locks stopping you. To use the door, use @quell or a -non-superuser account.

-

Installation:

-
-

Import this module in mygame/commands/default_cmdsets and add -the CmdOpen and CmdOpenCloseDoor commands to the CharacterCmdSet; -then reload the server.

-
-

To try it out, @dig a new room and then use the (overloaded) @open -commmand to open a new doorway to it like this:

-
-

@open doorway:contrib.simpledoor.SimpleDoor = otherroom

-
-

You can then use open doorway’ and **close doorway to change the open -state. If you are not superuser (@quell yourself) you’ll find you -cannot pass through either side of the door once it’s closed from the -other side.

-
-
-class evennia.contrib.simpledoor.SimpleDoor(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultExit

-

A two-way exit “door” with some methods for affecting both “sides” -of the door at the same time. For example, set a lock on either of the two -sides using exitname.setlock(“traverse:false())

-
-
-at_object_creation()[source]
-

Called the very first time the door is created.

-
- -
-
-setlock(lockstring)[source]
-

Sets identical locks on both sides of the door.

-
-
Parameters
-

lockstring (str) – A lockstring, like “traverse:true()”.

-
-
-
- -
-
-setdesc(description)[source]
-

Sets identical descs on both sides of the door.

-
-
Parameters
-

setdesc (str) – A description.

-
-
-
- -
-
-delete()[source]
-

Deletes both sides of the door.

-
- -
-
-at_failed_traverse(traversing_object)[source]
-

Called when door traverse: lock fails.

-
-
Parameters
-

traversing_object (Typeclassed entity) – The object -attempting the traversal.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultExit.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultExit.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.simpledoor.SimpleDoor'
-
- -
-
-typename = 'SimpleDoor'
-
- -
- -
-
-class evennia.contrib.simpledoor.CmdOpen(**kwargs)[source]
-

Bases: evennia.commands.default.building.CmdOpen

-

open a new exit from the current room

-
-
Usage:

open <new exit>[;alias;alias..][:typeclass] [,<return exit>[;alias;..][:typeclass]]] = <destination>

-
-
-

Handles the creation of exits. If a destination is given, the exit -will point there. The <return exit> argument sets up an exit at the -destination leading back to the current room. Destination name -can be given both as a #dbref and a name, if that name is globally -unique.

-
-
-create_exit(exit_name, location, destination, exit_aliases=None, typeclass=None)[source]
-

Simple wrapper for the default CmdOpen.create_exit

-
- -
-
-aliases = []
-
- -
-
-help_category = 'building'
-
- -
-
-key = 'open'
-
- -
-
-lock_storage = 'cmd:perm(open) or perm(Builder)'
-
- -
- -
-
-class evennia.contrib.simpledoor.CmdOpenCloseDoor(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Open and close a door

-
-
Usage:

open <door> -close <door>

-
-
-
-
-key = 'open'
-
- -
-
-aliases = ['close']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-func()[source]
-

implement the door functionality

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.slow_exit.html b/docs/0.9.5/api/evennia.contrib.slow_exit.html deleted file mode 100644 index 55c8aa32e1..0000000000 --- a/docs/0.9.5/api/evennia.contrib.slow_exit.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - - - - - - evennia.contrib.slow_exit — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.slow_exit

-

Slow Exit typeclass

-

Contribution - Griatch 2014

-

This is an example of an Exit-type that delays its traversal.This -simulates slow movement, common in many different types of games. The -contrib also contains two commands, CmdSetSpeed and CmdStop for changing -the movement speed and abort an ongoing traversal, respectively.

-

To try out an exit of this type, you could connect two existing rooms -using something like this:

-

@open north:contrib.slow_exit.SlowExit = <destination>

-

Installation:

-

To make this your new default exit, modify mygame/typeclasses/exits.py -to import this module and change the default Exit class to inherit -from SlowExit instead.

-

To get the ability to change your speed and abort your movement, -simply import and add CmdSetSpeed and CmdStop from this module to your -default cmdset (see tutorials on how to do this if you are unsure).

-

Notes:

-

This implementation is efficient but not persistent; so incomplete -movement will be lost in a server reload. This is acceptable for most -game types - to simulate longer travel times (more than the couple of -seconds assumed here), a more persistent variant using Scripts or the -TickerHandler might be better.

-
-
-class evennia.contrib.slow_exit.SlowExit(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultExit

-

This overloads the way moving happens.

-
-
-at_traverse(traversing_object, target_location)[source]
-

Implements the actual traversal, using utils.delay to delay the move_to.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultExit.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultExit.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.slow_exit.SlowExit'
-
- -
-
-typename = 'SlowExit'
-
- -
- -
-
-class evennia.contrib.slow_exit.CmdSetSpeed(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

set your movement speed

-
-
Usage:

setspeed stroll|walk|run|sprint

-
-
-

This will set your movement speed, determining how long time -it takes to traverse exits. If no speed is set, ‘walk’ speed -is assumed.

-
-
-key = 'setspeed'
-
- -
-
-func()[source]
-

Simply sets an Attribute used by the SlowExit above.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.slow_exit.CmdStop(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

stop moving

-
-
Usage:

stop

-
-
-

Stops the current movement, if any.

-
-
-key = 'stop'
-
- -
-
-func()[source]
-

This is a very simple command, using the -stored deferred from the exit traversal above.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.talking_npc.html b/docs/0.9.5/api/evennia.contrib.talking_npc.html deleted file mode 100644 index 40ad6c562d..0000000000 --- a/docs/0.9.5/api/evennia.contrib.talking_npc.html +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - - - evennia.contrib.talking_npc — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.talking_npc

-

Evennia Talkative NPC

-

Contribution - Griatch 2011, grungies1138, 2016

-

This is a static NPC object capable of holding a simple menu-driven -conversation. It’s just meant as an example. Create it by creating an -object of typeclass contrib.talking_npc.TalkingNPC, For example using -@create:

-
-

@create/drop John : contrib.talking_npc.TalkingNPC

-
-

Walk up to it and give the talk command to strike up a conversation. -If there are many talkative npcs in the same room you will get to -choose which one’s talk command to call (Evennia handles this -automatically). This use of EvMenu is very simplistic; See EvMenu for -a lot more complex possibilities.

-
-
-evennia.contrib.talking_npc.menu_start_node(caller)[source]
-
- -
-
-evennia.contrib.talking_npc.info1(caller)[source]
-
- -
-
-evennia.contrib.talking_npc.info2(caller)[source]
-
- -
-
-evennia.contrib.talking_npc.info3(caller)[source]
-
- -
-
-evennia.contrib.talking_npc.END(caller)[source]
-
- -
-
-class evennia.contrib.talking_npc.CmdTalk(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Talks to an npc

-
-
Usage:

talk

-
-
-

This command is only available if a talkative non-player-character -(NPC) is actually present. It will strike up a conversation with -that NPC and give you options on what to talk about.

-
-
-key = 'talk'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'general'
-
- -
-
-func()[source]
-

Implements the command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.talking_npc.TalkingCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Stores the talk command.

-
-
-key = 'talkingcmdset'
-
- -
-
-at_cmdset_creation()[source]
-

populates the cmdset

-
- -
-
-path = 'evennia.contrib.talking_npc.TalkingCmdSet'
-
- -
- -
-
-class evennia.contrib.talking_npc.TalkingNPC(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

This implements a simple Object using the talk command and using -the conversation defined above.

-
-
-at_object_creation()[source]
-

This is called when object is first created.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.talking_npc.TalkingNPC'
-
- -
-
-typename = 'TalkingNPC'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tree_select.html b/docs/0.9.5/api/evennia.contrib.tree_select.html deleted file mode 100644 index 63fa276996..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tree_select.html +++ /dev/null @@ -1,462 +0,0 @@ - - - - - - - - - evennia.contrib.tree_select — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tree_select

-

Easy menu selection tree

-

Contrib - Tim Ashley Jenkins 2017

-

This module allows you to create and initialize an entire branching EvMenu -instance with nothing but a multi-line string passed to one function.

-

EvMenu is incredibly powerful and flexible, but using it for simple menus -can often be fairly cumbersome - a simple menu that can branch into five -categories would require six nodes, each with options represented as a list -of dictionaries.

-

This module provides a function, init_tree_selection, which acts as a frontend -for EvMenu, dynamically sourcing the options from a multi-line string you provide. -For example, if you define a string as such:

-
-

TEST_MENU = ‘’’Foo -Bar -Baz -Qux’’’

-
-

And then use TEST_MENU as the ‘treestr’ source when you call init_tree_selection -on a player:

-
-

init_tree_selection(TEST_MENU, caller, callback)

-
-

The player will be presented with an EvMenu, like so:

-
-

Foo -Bar -Baz -Qux

-
-

Making a selection will pass the selection’s key to the specified callback as a -string along with the caller, as well as the index of the selection (the line number -on the source string) along with the source string for the tree itself.

-

In addition to specifying selections on the menu, you can also specify categories. -Categories are indicated by putting options below it preceded with a ‘-’ character. -If a selection is a category, then choosing it will bring up a new menu node, prompting -the player to select between those options, or to go back to the previous menu. In -addition, categories are marked by default with a ‘[+]’ at the end of their key. Both -this marker and the option to go back can be disabled.

-

Categories can be nested in other categories as well - just go another ‘-’ deeper. You -can do this as many times as you like. There’s no hard limit to the number of -categories you can go down.

-

For example, let’s add some more options to our menu, turning ‘Bar’ into a category.

-
-

TEST_MENU = ‘’’Foo -Bar --You’ve got to know -–When to hold em -–When to fold em -–When to walk away -Baz -Qux’’’

-
-

Now when we call the menu, we can see that ‘Bar’ has become a category instead of a -selectable option.

-
-

Foo -Bar [+] -Baz -Qux

-
-

Note the [+] next to ‘Bar’. If we select ‘Bar’, it’ll show us the option listed under it.

-
-

You’ve got to know [+] -<< Go Back: Return to the previous menu.

-
-

Just the one option, which is a category itself, and the option to go back, which will -take us back to the previous menu. Let’s select ‘You’ve got to know’.

-
-

When to hold em -When to fold em -When to walk away -<< Go Back: Return to the previous menu.

-
-

Now we see the three options listed under it, too. We can select one of them or use ‘Go -Back’ to return to the ‘Bar’ menu we were just at before. It’s very simple to make a -branching tree of selections!

-

One last thing - you can set the descriptions for the various options simply by adding a -‘:’ character followed by the description to the option’s line. For example, let’s add a -description to ‘Baz’ in our menu:

-
-

TEST_MENU = ‘’’Foo -Bar --You’ve got to know -–When to hold em -–When to fold em -–When to walk away -Baz: Look at this one: the best option. -Qux’’’

-
-

Now we see that the Baz option has a description attached that’s separate from its key:

-
-

Foo -Bar [+] -Baz: Look at this one: the best option. -Qux

-
-

Once the player makes a selection - let’s say, ‘Foo’ - the menu will terminate and call -your specified callback with the selection, like so:

-
-

callback(caller, TEST_MENU, 0, “Foo”)

-
-

The index of the selection is given along with a string containing the selection’s key. -That way, if you have two selections in the menu with the same key, you can still -differentiate between them.

-

And that’s all there is to it! For simple branching-tree selections, using this system is -much easier than manually creating EvMenu nodes. It also makes generating menus with dynamic -options much easier - since the source of the menu tree is just a string, you could easily -generate that string procedurally before passing it to the init_tree_selection function. -For example, if a player casts a spell or does an attack without specifying a target, instead -of giving them an error, you could present them with a list of valid targets to select by -generating a multi-line string of targets and passing it to init_tree_selection, with the -callable performing the maneuver once a selection is made.

-

This selection system only works for simple branching trees - doing anything really complicated -like jumping between categories or prompting for arbitrary input would still require a full -EvMenu implementation. For simple selections, however, I’m sure you will find using this function -to be much easier!

-

Included in this module is a sample menu and function which will let a player change the color -of their name - feel free to mess with it to get a feel for how this system works by importing -this module in your game’s default_cmdsets.py module and adding CmdNameColor to your default -character’s command set.

-
-
-evennia.contrib.tree_select.init_tree_selection(treestr, caller, callback, index=None, mark_category=True, go_back=True, cmd_on_exit='look', start_text='Make your selection:')[source]
-

Prompts a player to select an option from a menu tree given as a multi-line string.

-
-
Parameters
-
    -
  • treestr (str) – Multi-lne string representing menu options

  • -
  • caller (obj) – Player to initialize the menu for

  • -
  • callback (callable) – Function to run when a selection is made. Must take 4 args: -caller (obj): Caller given above -treestr (str): Menu tree string given above -index (int): Index of final selection -selection (str): Key of final selection

  • -
-
-
-
-
Options:

index (int or None): Index to start the menu at, or None for top level -mark_category (bool): If True, marks categories with a [+] symbol in the menu -go_back (bool): If True, present an option to go back to previous categories -start_text (str): Text to display at the top level of the menu -cmd_on_exit(str): Command to enter when the menu exits - ‘look’ by default

-
-
-

Notes

-

This function will initialize an instance of EvMenu with options generated -dynamically from the source string, and passes the menu user’s selection to -a function of your choosing. The EvMenu is made of a single, repeating node, -which will call itself over and over at different levels of the menu tree as -categories are selected.

-

Once a non-category selection is made, the user’s selection will be passed to -the given callable, both as a string and as an index number. The index is given -to ensure every selection has a unique identifier, so that selections with the -same key in different categories can be distinguished between.

-

The menus called by this function are not persistent and cannot perform -complicated tasks like prompt for arbitrary input or jump multiple category -levels at once - you’ll have to use EvMenu itself if you want to take full -advantage of its features.

-
- -
-
-evennia.contrib.tree_select.dashcount(entry)[source]
-

Counts the number of dashes at the beginning of a string. This -is needed to determine the depth of options in categories.

-
-
Parameters
-

entry (str) – String to count the dashes at the start of

-
-
Returns
-

dashes (int) – Number of dashes at the start

-
-
-
- -
-
-evennia.contrib.tree_select.is_category(treestr, index)[source]
-

Determines whether an option in a tree string is a category by -whether or not there are additional options below it.

-
-
Parameters
-
    -
  • treestr (str) – Multi-line string representing menu options

  • -
  • index (int) – Which line of the string to test

  • -
-
-
Returns
-

is_category (bool) – Whether the option is a category

-
-
-
- -
-
-evennia.contrib.tree_select.parse_opts(treestr, category_index=None)[source]
-

Parses a tree string and given index into a list of options. If -category_index is none, returns all the options at the top level of -the menu. If category_index corresponds to a category, returns a list -of options under that category. If category_index corresponds to -an option that is not a category, it’s a selection and returns True.

-
-
Parameters
-
    -
  • treestr (str) – Multi-line string representing menu options

  • -
  • category_index (int) – Index of category or None for top level

  • -
-
-
Returns
-

kept_opts (list or True)

-
-
Either a list of options in the selected

category or True if a selection was made

-
-
-

-
-
-
- -
-
-evennia.contrib.tree_select.index_to_selection(treestr, index, desc=False)[source]
-

Given a menu tree string and an index, returns the corresponding selection’s -name as a string. If ‘desc’ is set to True, will return the selection’s -description as a string instead.

-
-
Parameters
-
    -
  • treestr (str) – Multi-line string representing menu options

  • -
  • index (int) – Index to convert to selection key or description

  • -
-
-
-
-
Options:

desc (bool): If true, returns description instead of key

-
-
-
-
Returns
-

selection (str) – Selection key or description if ‘desc’ is set

-
-
-
- -
-
-evennia.contrib.tree_select.go_up_one_category(treestr, index)[source]
-

Given a menu tree string and an index, returns the category that the given option -belongs to. Used for the ‘go back’ option.

-
-
Parameters
-
    -
  • treestr (str) – Multi-line string representing menu options

  • -
  • index (int) – Index to determine the parent category of

  • -
-
-
Returns
-

parent_category (int) – Index of parent category

-
-
-
- -
-
-evennia.contrib.tree_select.optlist_to_menuoptions(treestr, optlist, index, mark_category, go_back)[source]
-

Takes a list of options processed by parse_opts and turns it into -a list/dictionary of menu options for use in menunode_treeselect.

-
-
Parameters
-
    -
  • treestr (str) – Multi-line string representing menu options

  • -
  • optlist (list) – List of options to convert to EvMenu’s option format

  • -
  • index (int) – Index of current category

  • -
  • mark_category (bool) – Whether or not to mark categories with [+]

  • -
  • go_back (bool) – Whether or not to add an option to go back in the menu

  • -
-
-
Returns
-

menuoptions (list of dicts)

-
-
List of menu options formatted for use

in EvMenu, each passing a different “newindex” kwarg that changes -the menu level or makes a selection

-
-
-

-
-
-
- -
-
-evennia.contrib.tree_select.menunode_treeselect(caller, raw_string, **kwargs)[source]
-

This is the repeating menu node that handles the tree selection.

-
- -
-
-class evennia.contrib.tree_select.CmdNameColor(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Set or remove a special color on your name. Just an example for the -easy menu selection tree contrib.

-
-
-key = 'namecolor'
-
- -
-
-func()[source]
-

This is the actual executing part of the command. It is -called directly after self.parse(). See the docstring of this -module for which object properties are available (beyond those -set in self.parse())

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-evennia.contrib.tree_select.change_name_color(caller, treestr, index, selection)[source]
-

Changes a player’s name color.

-
-
Parameters
-
    -
  • caller (obj) – Character whose name to color.

  • -
  • treestr (str) – String for the color change menu - unused

  • -
  • index (int) – Index of menu selection - unused

  • -
  • selection (str) – Selection made from the name color menu - used -to determine the color the player chose.

  • -
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.turnbattle.html b/docs/0.9.5/api/evennia.contrib.turnbattle.html deleted file mode 100644 index 51405c9d5f..0000000000 --- a/docs/0.9.5/api/evennia.contrib.turnbattle.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - evennia.contrib.turnbattle — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
- - -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_basic.html b/docs/0.9.5/api/evennia.contrib.turnbattle.tb_basic.html deleted file mode 100644 index 4383f10009..0000000000 --- a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_basic.html +++ /dev/null @@ -1,797 +0,0 @@ - - - - - - - - - evennia.contrib.turnbattle.tb_basic — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.turnbattle.tb_basic

-

Simple turn-based combat system

-

Contrib - Tim Ashley Jenkins 2017

-

This is a framework for a simple turn-based combat system, similar -to those used in D&D-style tabletop role playing games. It allows -any character to start a fight in a room, at which point initiative -is rolled and a turn order is established. Each participant in combat -has a limited time to decide their action for that turn (30 seconds by -default), and combat progresses through the turn order, looping through -the participants until the fight ends.

-

Only simple rolls for attacking are implemented here, but this system -is easily extensible and can be used as the foundation for implementing -the rules from your turn-based tabletop game of choice or making your -own battle system.

-

To install and test, import this module’s TBBasicCharacter object into -your game’s character.py module:

-
-

from evennia.contrib.turnbattle.tb_basic import TBBasicCharacter

-
-

And change your game’s character typeclass to inherit from TBBasicCharacter -instead of the default:

-
-

class Character(TBBasicCharacter):

-
-

Next, import this module into your default_cmdsets.py module:

-
-

from evennia.contrib.turnbattle import tb_basic

-
-

And add the battle command set to your default command set:

-
-

# -# any commands you add below will overload the default ones. -# -self.add(tb_basic.BattleCmdSet())

-
-

This module is meant to be heavily expanded on, so you may want to copy it -to your game’s ‘world’ folder and modify it there rather than importing it -in your game and using it as-is.

-
-
-evennia.contrib.turnbattle.tb_basic.ACTIONS_PER_TURN = 1
-
- -
-
-evennia.contrib.turnbattle.tb_basic.roll_init(character)[source]
-

Rolls a number between 1-1000 to determine initiative.

-
-
Parameters
-

character (obj) – The character to determine initiative for

-
-
Returns
-

initiative (int) – The character’s place in initiative - higher -numbers go first.

-
-
-

Notes

-

By default, does not reference the character and simply returns -a random integer from 1 to 1000.

-

Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

-

return (randint(1,20)) + character.db.dexterity

-

This way, characters with a higher dexterity will go first more often.

-
- -
-
-evennia.contrib.turnbattle.tb_basic.get_attack(attacker, defender)[source]
-

Returns a value for an attack roll.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
Returns
-

attack_value (int)

-
-
Attack roll value, compared against a defense value

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

By default, returns a random integer from 1 to 100 without using any -properties from either the attacker or defender.

-

This can easily be expanded to return a value based on characters stats, -equipment, and abilities. This is why the attacker and defender are passed -to this function, even though nothing from either one are used in this example.

-
- -
-
-evennia.contrib.turnbattle.tb_basic.get_defense(attacker, defender)[source]
-

Returns a value for defense, which an attack roll must equal or exceed in order -for an attack to hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
Returns
-

defense_value (int)

-
-
Defense value, compared against an attack roll

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

By default, returns 50, not taking any properties of the defender or -attacker into account.

-

As above, this can be expanded upon based on character stats and equipment.

-
- -
-
-evennia.contrib.turnbattle.tb_basic.get_damage(attacker, defender)[source]
-

Returns a value for damage to be deducted from the defender’s HP after abilities -successful hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being damaged

  • -
-
-
Returns
-

damage_value (int)

-
-
Damage value, which is to be deducted from the defending

character’s HP.

-
-
-

-
-
-

Notes

-

By default, returns a random integer from 15 to 25 without using any -properties from either the attacker or defender.

-

Again, this can be expanded upon.

-
- -
-
-evennia.contrib.turnbattle.tb_basic.apply_damage(defender, damage)[source]
-

Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

-
-
Parameters
-
    -
  • defender (obj) – Character taking damage

  • -
  • damage (int) – Amount of damage being taken

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_basic.at_defeat(defeated)[source]
-

Announces the defeat of a fighter in combat.

-
-
Parameters
-

defeated (obj) – Fighter that’s been defeated.

-
-
-

Notes

-

All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

-
- -
-
-evennia.contrib.turnbattle.tb_basic.resolve_attack(attacker, defender, attack_value=None, defense_value=None)[source]
-

Resolves an attack and outputs the result.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
-

Notes

-

Even though the attack and defense values are calculated -extremely simply, they are separated out into their own functions -so that they are easier to expand upon.

-
- -
-
-evennia.contrib.turnbattle.tb_basic.combat_cleanup(character)[source]
-

Cleans up all the temporary combat-related attributes on a character.

-
-
Parameters
-

character (obj) – Character to have their combat attributes removed

-
-
-

Notes

-

Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

-
- -
-
-evennia.contrib.turnbattle.tb_basic.is_in_combat(character)[source]
-

Returns true if the given character is in combat.

-
-
Parameters
-

character (obj) – Character to determine if is in combat or not

-
-
Returns
-

(bool) – True if in combat or False if not in combat

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_basic.is_turn(character)[source]
-

Returns true if it’s currently the given character’s turn in combat.

-
-
Parameters
-

character (obj) – Character to determine if it is their turn or not

-
-
Returns
-

(bool) – True if it is their turn or False otherwise

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_basic.spend_action(character, actions, action_name=None)[source]
-

Spends a character’s available combat actions and checks for end of turn.

-
-
Parameters
-
    -
  • character (obj) – Character spending the action

  • -
  • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

  • -
-
-
Keyword Arguments
-
    -
  • action_name (str or None) – If a string is given, sets character’s last action in

  • -
  • to provided string (combat) –

  • -
-
-
-
- -
-
-class evennia.contrib.turnbattle.tb_basic.TBBasicCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultCharacter

-

A character able to participate in turn-based combat. Has attributes for current -and maximum HP, and access to combat commands.

-
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-
- -
-
-at_before_move(destination)[source]
-

Called just before starting to move this object to -destination.

-
-
Parameters
-

destination (Object) – The object we are moving to

-
-
Returns
-

shouldmove (bool) – If we should move or not.

-
-
-

Notes

-

If this method returns False/None, the move is cancelled -before it is even started.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_basic.TBBasicCharacter'
-
- -
-
-typename = 'TBBasicCharacter'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_basic.TBBasicTurnHandler(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This is the script that handles the progression of combat through turns. -On creation (when a fight is started) it adds all combat-ready characters -to its roster and then sorts them into a turn order. There can only be one -fight going on in a single room at a time, so the script is assigned to a -room as its object.

-

Fights persist until only one participant is left with any HP or all -remaining participants choose to end the combat with the ‘disengage’ command.

-
-
-at_script_creation()[source]
-

Called once, when the script is created.

-
- -
-
-at_stop()[source]
-

Called at script termination.

-
- -
-
-at_repeat()[source]
-

Called once every self.interval seconds.

-
- -
-
-initialize_for_combat(character)[source]
-

Prepares a character for combat when starting or entering a fight.

-
-
Parameters
-

character (obj) – Character to initialize for combat.

-
-
-
- -
-
-start_turn(character)[source]
-

Readies a character for the start of their turn by replenishing their -available actions and notifying them that their turn has come up.

-
-
Parameters
-

character (obj) – Character to be readied.

-
-
-

Notes

-

Here, you only get one action per turn, but you might want to allow more than -one per turn, or even grant a number of actions based on a character’s -attributes. You can even add multiple different kinds of actions, I.E. actions -separated for movement, by adding “character.db.combat_movesleft = 3” or -something similar.

-
- -
-
-next_turn()[source]
-

Advances to the next character in the turn order.

-
- -
-
-turn_end_check(character)[source]
-

Tests to see if a character’s turn is over, and cycles to the next turn if it is.

-
-
Parameters
-

character (obj) – Character to test for end of turn

-
-
-
- -
-
-join_fight(character)[source]
-

Adds a new character to a fight already in progress.

-
-
Parameters
-

character (obj) – Character to be added to the fight.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_basic.TBBasicTurnHandler'
-
- -
-
-typename = 'TBBasicTurnHandler'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_basic.CmdFight(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Starts a fight with everyone in the same room as you.

-
-
Usage:

fight

-
-
-

When you start a fight, everyone in the room who is able to -fight is added to combat, and a turn order is randomly rolled. -When it’s your turn, you can attack other characters.

-
-
-key = 'fight'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_basic.CmdAttack(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Attacks another character.

-
-
Usage:

attack <target>

-
-
-

When in a fight, you may attack another character. The attack has -a chance to hit, and if successful, will deal damage.

-
-
-key = 'attack'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_basic.CmdPass(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes on your turn.

-
-
Usage:

pass

-
-
-

When in a fight, you can use this command to end your turn early, even -if there are still any actions you can take.

-
-
-key = 'pass'
-
- -
-
-aliases = ['hold', 'wait']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_basic.CmdDisengage(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes your turn and attempts to end combat.

-
-
Usage:

disengage

-
-
-

Ends your turn early and signals that you’re trying to end -the fight. If all participants in a fight disengage, the -fight ends.

-
-
-key = 'disengage'
-
- -
-
-aliases = ['spare']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_basic.CmdRest(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Recovers damage.

-
-
Usage:

rest

-
-
-

Resting recovers your HP to its maximum, but you can only -rest if you’re not in a fight.

-
-
-key = 'rest'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_basic.CmdCombatHelp(**kwargs)[source]
-

Bases: evennia.commands.default.help.CmdHelp

-

View help or a list of topics

-
-
Usage:

help <topic or command> -help list -help all

-
-
-

This will search for help on commands and other -topics related to the game.

-
-
-func()[source]
-

Run the dynamic help entry creator.

-
- -
-
-aliases = ['?']
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'help'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_basic.BattleCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.default.cmdset_character.CharacterCmdSet

-

This command set includes all the commmands used in the battle system.

-
-
-key = 'DefaultCharacter'
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_basic.BattleCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_equip.html b/docs/0.9.5/api/evennia.contrib.turnbattle.tb_equip.html deleted file mode 100644 index a9e6929a11..0000000000 --- a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_equip.html +++ /dev/null @@ -1,1076 +0,0 @@ - - - - - - - - - evennia.contrib.turnbattle.tb_equip — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.turnbattle.tb_equip

-

Simple turn-based combat system with equipment

-

Contrib - Tim Ashley Jenkins 2017

-

This is a version of the ‘turnbattle’ contrib with a basic system for -weapons and armor implemented. Weapons can have unique damage ranges -and accuracy modifiers, while armor can reduce incoming damage and -change one’s chance of getting hit. The ‘wield’ command is used to -equip weapons and the ‘don’ command is used to equip armor.

-

Some prototypes are included at the end of this module - feel free to -copy them into your game’s prototypes.py module in your ‘world’ folder -and create them with the @spawn command. (See the tutorial for using -the @spawn command for details.)

-

For the example equipment given, heavier weapons deal more damage -but are less accurate, while light weapons are more accurate but -deal less damage. Similarly, heavy armor reduces incoming damage by -a lot but increases your chance of getting hit, while light armor is -easier to dodge in but reduces incoming damage less. Light weapons are -more effective against lightly armored opponents and heavy weapons are -more damaging against heavily armored foes, but heavy weapons and armor -are slightly better than light weapons and armor overall.

-

This is a fairly bare implementation of equipment that is meant to be -expanded to fit your game - weapon and armor slots, damage types and -damage bonuses, etc. should be fairly simple to implement according to -the rules of your preferred system or the needs of your own game.

-

To install and test, import this module’s TBEquipCharacter object into -your game’s character.py module:

-
-

from evennia.contrib.turnbattle.tb_equip import TBEquipCharacter

-
-

And change your game’s character typeclass to inherit from TBEquipCharacter -instead of the default:

-
-

class Character(TBEquipCharacter):

-
-

Next, import this module into your default_cmdsets.py module:

-
-

from evennia.contrib.turnbattle import tb_equip

-
-

And add the battle command set to your default command set:

-
-

# -# any commands you add below will overload the default ones. -# -self.add(tb_equip.BattleCmdSet())

-
-

This module is meant to be heavily expanded on, so you may want to copy it -to your game’s ‘world’ folder and modify it there rather than importing it -in your game and using it as-is.

-
-
-evennia.contrib.turnbattle.tb_equip.ACTIONS_PER_TURN = 1
-
- -
-
-evennia.contrib.turnbattle.tb_equip.roll_init(character)[source]
-

Rolls a number between 1-1000 to determine initiative.

-
-
Parameters
-

character (obj) – The character to determine initiative for

-
-
Returns
-

initiative (int) – The character’s place in initiative - higher -numbers go first.

-
-
-

Notes

-

By default, does not reference the character and simply returns -a random integer from 1 to 1000.

-

Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

-

return (randint(1,20)) + character.db.dexterity

-

This way, characters with a higher dexterity will go first more often.

-
- -
-
-evennia.contrib.turnbattle.tb_equip.get_attack(attacker, defender)[source]
-

Returns a value for an attack roll.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
Returns
-

attack_value (int)

-
-
Attack roll value, compared against a defense value

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

In this example, a weapon’s accuracy bonus is factored into the attack -roll. Lighter weapons are more accurate but less damaging, and heavier -weapons are less accurate but deal more damage. Of course, you can -change this paradigm completely in your own game.

-
- -
-
-evennia.contrib.turnbattle.tb_equip.get_defense(attacker, defender)[source]
-

Returns a value for defense, which an attack roll must equal or exceed in order -for an attack to hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
Returns
-

defense_value (int)

-
-
Defense value, compared against an attack roll

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

Characters are given a default defense value of 50 which can be -modified up or down by armor. In this example, wearing armor actually -makes you a little easier to hit, but reduces incoming damage.

-
- -
-
-evennia.contrib.turnbattle.tb_equip.get_damage(attacker, defender)[source]
-

Returns a value for damage to be deducted from the defender’s HP after abilities -successful hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being damaged

  • -
-
-
Returns
-

damage_value (int)

-
-
Damage value, which is to be deducted from the defending

character’s HP.

-
-
-

-
-
-

Notes

-

Damage is determined by the attacker’s wielded weapon, or the attacker’s -unarmed damage range if no weapon is wielded. Incoming damage is reduced -by the defender’s armor.

-
- -
-
-evennia.contrib.turnbattle.tb_equip.apply_damage(defender, damage)[source]
-

Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

-
-
Parameters
-
    -
  • defender (obj) – Character taking damage

  • -
  • damage (int) – Amount of damage being taken

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_equip.at_defeat(defeated)[source]
-

Announces the defeat of a fighter in combat.

-
-
Parameters
-

defeated (obj) – Fighter that’s been defeated.

-
-
-

Notes

-

All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

-
- -
-
-evennia.contrib.turnbattle.tb_equip.resolve_attack(attacker, defender, attack_value=None, defense_value=None)[source]
-

Resolves an attack and outputs the result.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
-

Notes

-

Even though the attack and defense values are calculated -extremely simply, they are separated out into their own functions -so that they are easier to expand upon.

-
- -
-
-evennia.contrib.turnbattle.tb_equip.combat_cleanup(character)[source]
-

Cleans up all the temporary combat-related attributes on a character.

-
-
Parameters
-

character (obj) – Character to have their combat attributes removed

-
-
-

Notes

-

Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

-
- -
-
-evennia.contrib.turnbattle.tb_equip.is_in_combat(character)[source]
-

Returns true if the given character is in combat.

-
-
Parameters
-

character (obj) – Character to determine if is in combat or not

-
-
Returns
-

(bool) – True if in combat or False if not in combat

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_equip.is_turn(character)[source]
-

Returns true if it’s currently the given character’s turn in combat.

-
-
Parameters
-

character (obj) – Character to determine if it is their turn or not

-
-
Returns
-

(bool) – True if it is their turn or False otherwise

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_equip.spend_action(character, actions, action_name=None)[source]
-

Spends a character’s available combat actions and checks for end of turn.

-
-
Parameters
-
    -
  • character (obj) – Character spending the action

  • -
  • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

  • -
-
-
Keyword Arguments
-
    -
  • action_name (str or None) – If a string is given, sets character’s last action in

  • -
  • to provided string (combat) –

  • -
-
-
-
- -
-
-class evennia.contrib.turnbattle.tb_equip.TBEquipTurnHandler(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This is the script that handles the progression of combat through turns. -On creation (when a fight is started) it adds all combat-ready characters -to its roster and then sorts them into a turn order. There can only be one -fight going on in a single room at a time, so the script is assigned to a -room as its object.

-

Fights persist until only one participant is left with any HP or all -remaining participants choose to end the combat with the ‘disengage’ command.

-
-
-at_script_creation()[source]
-

Called once, when the script is created.

-
- -
-
-at_stop()[source]
-

Called at script termination.

-
- -
-
-at_repeat()[source]
-

Called once every self.interval seconds.

-
- -
-
-initialize_for_combat(character)[source]
-

Prepares a character for combat when starting or entering a fight.

-
-
Parameters
-

character (obj) – Character to initialize for combat.

-
-
-
- -
-
-start_turn(character)[source]
-

Readies a character for the start of their turn by replenishing their -available actions and notifying them that their turn has come up.

-
-
Parameters
-

character (obj) – Character to be readied.

-
-
-

Notes

-

Here, you only get one action per turn, but you might want to allow more than -one per turn, or even grant a number of actions based on a character’s -attributes. You can even add multiple different kinds of actions, I.E. actions -separated for movement, by adding “character.db.combat_movesleft = 3” or -something similar.

-
- -
-
-next_turn()[source]
-

Advances to the next character in the turn order.

-
- -
-
-turn_end_check(character)[source]
-

Tests to see if a character’s turn is over, and cycles to the next turn if it is.

-
-
Parameters
-

character (obj) – Character to test for end of turn

-
-
-
- -
-
-join_fight(character)[source]
-

Adds a new character to a fight already in progress.

-
-
Parameters
-

character (obj) – Character to be added to the fight.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_equip.TBEquipTurnHandler'
-
- -
-
-typename = 'TBEquipTurnHandler'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.TBEWeapon(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

A weapon which can be wielded in combat with the ‘wield’ command.

-
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-
- -
-
-at_drop(dropper)[source]
-

Stop being wielded if dropped.

-
- -
-
-at_give(giver, getter)[source]
-

Stop being wielded if given.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_equip.TBEWeapon'
-
- -
-
-typename = 'TBEWeapon'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.TBEArmor(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

A set of armor which can be worn with the ‘don’ command.

-
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-
- -
-
-at_before_drop(dropper)[source]
-

Can’t drop in combat.

-
- -
-
-at_drop(dropper)[source]
-

Stop being wielded if dropped.

-
- -
-
-at_before_give(giver, getter)[source]
-

Can’t give away in combat.

-
- -
-
-at_give(giver, getter)[source]
-

Stop being wielded if given.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_equip.TBEArmor'
-
- -
-
-typename = 'TBEArmor'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.TBEquipCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultCharacter

-

A character able to participate in turn-based combat. Has attributes for current -and maximum HP, and access to combat commands.

-
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-
- -
-
-at_before_move(destination)[source]
-

Called just before starting to move this object to -destination.

-
-
Parameters
-

destination (Object) – The object we are moving to

-
-
Returns
-

shouldmove (bool) – If we should move or not.

-
-
-

Notes

-

If this method returns False/None, the move is cancelled -before it is even started.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_equip.TBEquipCharacter'
-
- -
-
-typename = 'TBEquipCharacter'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdFight(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Starts a fight with everyone in the same room as you.

-
-
Usage:

fight

-
-
-

When you start a fight, everyone in the room who is able to -fight is added to combat, and a turn order is randomly rolled. -When it’s your turn, you can attack other characters.

-
-
-key = 'fight'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdAttack(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Attacks another character.

-
-
Usage:

attack <target>

-
-
-

When in a fight, you may attack another character. The attack has -a chance to hit, and if successful, will deal damage.

-
-
-key = 'attack'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdPass(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes on your turn.

-
-
Usage:

pass

-
-
-

When in a fight, you can use this command to end your turn early, even -if there are still any actions you can take.

-
-
-key = 'pass'
-
- -
-
-aliases = ['hold', 'wait']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdDisengage(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes your turn and attempts to end combat.

-
-
Usage:

disengage

-
-
-

Ends your turn early and signals that you’re trying to end -the fight. If all participants in a fight disengage, the -fight ends.

-
-
-key = 'disengage'
-
- -
-
-aliases = ['spare']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdRest(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Recovers damage.

-
-
Usage:

rest

-
-
-

Resting recovers your HP to its maximum, but you can only -rest if you’re not in a fight.

-
-
-key = 'rest'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdCombatHelp(**kwargs)[source]
-

Bases: evennia.commands.default.help.CmdHelp

-

View help or a list of topics

-
-
Usage:

help <topic or command> -help list -help all

-
-
-

This will search for help on commands and other -topics related to the game.

-
-
-func()[source]
-

Run the dynamic help entry creator.

-
- -
-
-aliases = ['?']
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'help'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdWield(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Wield a weapon you are carrying

-
-
Usage:

wield <weapon>

-
-
-

Select a weapon you are carrying to wield in combat. If -you are already wielding another weapon, you will switch -to the weapon you specify instead. Using this command in -combat will spend your action for your turn. Use the -“unwield” command to stop wielding any weapon you are -currently wielding.

-
-
-key = 'wield'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdUnwield(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Stop wielding a weapon.

-
-
Usage:

unwield

-
-
-

After using this command, you will stop wielding any -weapon you are currently wielding and become unarmed.

-
-
-key = 'unwield'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdDon(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Don armor that you are carrying

-
-
Usage:

don <armor>

-
-
-

Select armor to wear in combat. You can’t use this -command in the middle of a fight. Use the “doff” -command to remove any armor you are wearing.

-
-
-key = 'don'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.CmdDoff(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Stop wearing armor.

-
-
Usage:

doff

-
-
-

After using this command, you will stop wearing any -armor you are currently using and become unarmored. -You can’t use this command in combat.

-
-
-key = 'doff'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_equip.BattleCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.default.cmdset_character.CharacterCmdSet

-

This command set includes all the commmands used in the battle system.

-
-
-key = 'DefaultCharacter'
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_equip.BattleCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_items.html b/docs/0.9.5/api/evennia.contrib.turnbattle.tb_items.html deleted file mode 100644 index c58f087ca3..0000000000 --- a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_items.html +++ /dev/null @@ -1,1071 +0,0 @@ - - - - - - - - - evennia.contrib.turnbattle.tb_items — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.turnbattle.tb_items

-

Simple turn-based combat system with items and status effects

-

Contrib - Tim Ashley Jenkins 2017

-

This is a version of the ‘turnbattle’ combat system that includes -conditions and usable items, which can instill these conditions, cure -them, or do just about anything else.

-

Conditions are stored on characters as a dictionary, where the key -is the name of the condition and the value is a list of two items: -an integer representing the number of turns left until the condition -runs out, and the character upon whose turn the condition timer is -ticked down. Unlike most combat-related attributes, conditions aren’t -wiped once combat ends - if out of combat, they tick down in real time -instead.

-

This module includes a number of example conditions:

-
-

Regeneration: Character recovers HP every turn -Poisoned: Character loses HP every turn -Accuracy Up: +25 to character’s attack rolls -Accuracy Down: -25 to character’s attack rolls -Damage Up: +5 to character’s damage -Damage Down: -5 to character’s damage -Defense Up: +15 to character’s defense -Defense Down: -15 to character’s defense -Haste: +1 action per turn -Paralyzed: No actions per turn -Frightened: Character can’t use the ‘attack’ command

-
-

Since conditions can have a wide variety of effects, their code is -scattered throughout the other functions wherever they may apply.

-

Items aren’t given any sort of special typeclass - instead, whether or -not an object counts as an item is determined by its attributes. To make -an object into an item, it must have the attribute ‘item_func’, with -the value given as a callable - this is the function that will be called -when an item is used. Other properties of the item, such as how many -uses it has, whether it’s destroyed when its uses are depleted, and such -can be specified on the item as well, but they are optional.

-

To install and test, import this module’s TBItemsCharacter object into -your game’s character.py module:

-
-

from evennia.contrib.turnbattle.tb_items import TBItemsCharacter

-
-

And change your game’s character typeclass to inherit from TBItemsCharacter -instead of the default:

-
-

class Character(TBItemsCharacter):

-
-

Next, import this module into your default_cmdsets.py module:

-
-

from evennia.contrib.turnbattle import tb_items

-
-

And add the battle command set to your default command set:

-
-

# -# any commands you add below will overload the default ones. -# -self.add(tb_items.BattleCmdSet())

-
-

This module is meant to be heavily expanded on, so you may want to copy it -to your game’s ‘world’ folder and modify it there rather than importing it -in your game and using it as-is.

-
-
-evennia.contrib.turnbattle.tb_items.DEF_DOWN_MOD = -15
-
- -
-
-evennia.contrib.turnbattle.tb_items.roll_init(character)[source]
-

Rolls a number between 1-1000 to determine initiative.

-
-
Parameters
-

character (obj) – The character to determine initiative for

-
-
Returns
-

initiative (int) – The character’s place in initiative - higher -numbers go first.

-
-
-

Notes

-

By default, does not reference the character and simply returns -a random integer from 1 to 1000.

-

Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

-

return (randint(1,20)) + character.db.dexterity

-

This way, characters with a higher dexterity will go first more often.

-
- -
-
-evennia.contrib.turnbattle.tb_items.get_attack(attacker, defender)[source]
-

Returns a value for an attack roll.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
Returns
-

attack_value (int)

-
-
Attack roll value, compared against a defense value

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

This is where conditions affecting attack rolls are applied, as well. -Accuracy Up and Accuracy Down are also accounted for in itemfunc_attack(), -so that attack items’ accuracy is affected as well.

-
- -
-
-evennia.contrib.turnbattle.tb_items.get_defense(attacker, defender)[source]
-

Returns a value for defense, which an attack roll must equal or exceed in order -for an attack to hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
Returns
-

defense_value (int)

-
-
Defense value, compared against an attack roll

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

This is where conditions affecting defense are accounted for.

-
- -
-
-evennia.contrib.turnbattle.tb_items.get_damage(attacker, defender)[source]
-

Returns a value for damage to be deducted from the defender’s HP after abilities -successful hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being damaged

  • -
-
-
Returns
-

damage_value (int)

-
-
Damage value, which is to be deducted from the defending

character’s HP.

-
-
-

-
-
-

Notes

-

This is where conditions affecting damage are accounted for. Since attack items -roll their own damage in itemfunc_attack(), their damage is unaffected by any -conditions.

-
- -
-
-evennia.contrib.turnbattle.tb_items.apply_damage(defender, damage)[source]
-

Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

-
-
Parameters
-
    -
  • defender (obj) – Character taking damage

  • -
  • damage (int) – Amount of damage being taken

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_items.at_defeat(defeated)[source]
-

Announces the defeat of a fighter in combat.

-
-
Parameters
-

defeated (obj) – Fighter that’s been defeated.

-
-
-

Notes

-

All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

-
- -
-
-evennia.contrib.turnbattle.tb_items.resolve_attack(attacker, defender, attack_value=None, defense_value=None, damage_value=None, inflict_condition=[])[source]
-

Resolves an attack and outputs the result.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
-
-
Options:

attack_value (int): Override for attack roll -defense_value (int): Override for defense value -damage_value (int): Override for damage value -inflict_condition (list): Conditions to inflict upon hit, a

-
-

list of tuples formated as (condition(str), duration(int))

-
-
-
-

Notes

-

This function is called by normal attacks as well as attacks -made with items.

-
- -
-
-evennia.contrib.turnbattle.tb_items.combat_cleanup(character)[source]
-

Cleans up all the temporary combat-related attributes on a character.

-
-
Parameters
-

character (obj) – Character to have their combat attributes removed

-
-
-

Notes

-

Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

-
- -
-
-evennia.contrib.turnbattle.tb_items.is_in_combat(character)[source]
-

Returns true if the given character is in combat.

-
-
Parameters
-

character (obj) – Character to determine if is in combat or not

-
-
Returns
-

(bool) – True if in combat or False if not in combat

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_items.is_turn(character)[source]
-

Returns true if it’s currently the given character’s turn in combat.

-
-
Parameters
-

character (obj) – Character to determine if it is their turn or not

-
-
Returns
-

(bool) – True if it is their turn or False otherwise

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_items.spend_action(character, actions, action_name=None)[source]
-

Spends a character’s available combat actions and checks for end of turn.

-
-
Parameters
-
    -
  • character (obj) – Character spending the action

  • -
  • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

  • -
-
-
Keyword Arguments
-
    -
  • action_name (str or None) – If a string is given, sets character’s last action in

  • -
  • to provided string (combat) –

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_items.spend_item_use(item, user)[source]
-

Spends one use on an item with limited uses.

-
-
Parameters
-
    -
  • item (obj) – Item being used

  • -
  • user (obj) – Character using the item

  • -
-
-
-

Notes

-

If item.db.item_consumable is ‘True’, the item is destroyed if it -runs out of uses - if it’s a string instead of ‘True’, it will also -spawn a new object as residue, using the value of item.db.item_consumable -as the name of the prototype to spawn.

-
- -
-
-evennia.contrib.turnbattle.tb_items.use_item(user, item, target)[source]
-

Performs the action of using an item.

-
-
Parameters
-
    -
  • user (obj) – Character using the item

  • -
  • item (obj) – Item being used

  • -
  • target (obj) – Target of the item use

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_items.condition_tickdown(character, turnchar)[source]
-

Ticks down the duration of conditions on a character at the start of a given character’s turn.

-
-
Parameters
-
    -
  • character (obj) – Character to tick down the conditions of

  • -
  • turnchar (obj) – Character whose turn it currently is

  • -
-
-
-

Notes

-

In combat, this is called on every fighter at the start of every character’s turn. Out of -combat, it’s instead called when a character’s at_update() hook is called, which is every -30 seconds by default.

-
- -
-
-evennia.contrib.turnbattle.tb_items.add_condition(character, turnchar, condition, duration)[source]
-

Adds a condition to a fighter.

-
-
Parameters
-
    -
  • character (obj) – Character to give the condition to

  • -
  • turnchar (obj) – Character whose turn to tick down the condition on in combat

  • -
  • condition (str) – Name of the condition

  • -
  • duration (int or True) – Number of turns the condition lasts, or True for indefinite

  • -
-
-
-
- -
-
-class evennia.contrib.turnbattle.tb_items.TBItemsCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultCharacter

-

A character able to participate in turn-based combat. Has attributes for current -and maximum HP, and access to combat commands.

-
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-
- -
-
-at_before_move(destination)[source]
-

Called just before starting to move this object to -destination.

-
-
Parameters
-

destination (Object) – The object we are moving to

-
-
Returns
-

shouldmove (bool) – If we should move or not.

-
-
-

Notes

-

If this method returns False/None, the move is cancelled -before it is even started.

-
- -
-
-at_turn_start()[source]
-

Hook called at the beginning of this character’s turn in combat.

-
- -
-
-apply_turn_conditions()[source]
-

Applies the effect of conditions that occur at the start of each -turn in combat, or every 30 seconds out of combat.

-
- -
-
-at_update()[source]
-

Fires every 30 seconds.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_items.TBItemsCharacter'
-
- -
-
-typename = 'TBItemsCharacter'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.TBItemsCharacterTest(*args, **kwargs)[source]
-

Bases: evennia.contrib.turnbattle.tb_items.TBItemsCharacter

-

Just like the TBItemsCharacter, but doesn’t subscribe to the TickerHandler. -This makes it easier to run unit tests on.

-
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.turnbattle.tb_items.TBItemsCharacter.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.turnbattle.tb_items.TBItemsCharacter.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_items.TBItemsCharacterTest'
-
- -
-
-typename = 'TBItemsCharacterTest'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.TBItemsTurnHandler(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This is the script that handles the progression of combat through turns. -On creation (when a fight is started) it adds all combat-ready characters -to its roster and then sorts them into a turn order. There can only be one -fight going on in a single room at a time, so the script is assigned to a -room as its object.

-

Fights persist until only one participant is left with any HP or all -remaining participants choose to end the combat with the ‘disengage’ command.

-
-
-at_script_creation()[source]
-

Called once, when the script is created.

-
- -
-
-at_stop()[source]
-

Called at script termination.

-
- -
-
-at_repeat()[source]
-

Called once every self.interval seconds.

-
- -
-
-initialize_for_combat(character)[source]
-

Prepares a character for combat when starting or entering a fight.

-
-
Parameters
-

character (obj) – Character to initialize for combat.

-
-
-
- -
-
-start_turn(character)[source]
-

Readies a character for the start of their turn by replenishing their -available actions and notifying them that their turn has come up.

-
-
Parameters
-

character (obj) – Character to be readied.

-
-
-

Notes

-

Here, you only get one action per turn, but you might want to allow more than -one per turn, or even grant a number of actions based on a character’s -attributes. You can even add multiple different kinds of actions, I.E. actions -separated for movement, by adding “character.db.combat_movesleft = 3” or -something similar.

-
- -
-
-next_turn()[source]
-

Advances to the next character in the turn order.

-
- -
-
-turn_end_check(character)[source]
-

Tests to see if a character’s turn is over, and cycles to the next turn if it is.

-
-
Parameters
-

character (obj) – Character to test for end of turn

-
-
-
- -
-
-join_fight(character)[source]
-

Adds a new character to a fight already in progress.

-
-
Parameters
-

character (obj) – Character to be added to the fight.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_items.TBItemsTurnHandler'
-
- -
-
-typename = 'TBItemsTurnHandler'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.CmdFight(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Starts a fight with everyone in the same room as you.

-
-
Usage:

fight

-
-
-

When you start a fight, everyone in the room who is able to -fight is added to combat, and a turn order is randomly rolled. -When it’s your turn, you can attack other characters.

-
-
-key = 'fight'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.CmdAttack(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Attacks another character.

-
-
Usage:

attack <target>

-
-
-

When in a fight, you may attack another character. The attack has -a chance to hit, and if successful, will deal damage.

-
-
-key = 'attack'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.CmdPass(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes on your turn.

-
-
Usage:

pass

-
-
-

When in a fight, you can use this command to end your turn early, even -if there are still any actions you can take.

-
-
-key = 'pass'
-
- -
-
-aliases = ['hold', 'wait']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.CmdDisengage(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes your turn and attempts to end combat.

-
-
Usage:

disengage

-
-
-

Ends your turn early and signals that you’re trying to end -the fight. If all participants in a fight disengage, the -fight ends.

-
-
-key = 'disengage'
-
- -
-
-aliases = ['spare']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.CmdRest(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Recovers damage.

-
-
Usage:

rest

-
-
-

Resting recovers your HP to its maximum, but you can only -rest if you’re not in a fight.

-
-
-key = 'rest'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.CmdCombatHelp(**kwargs)[source]
-

Bases: evennia.commands.default.help.CmdHelp

-

View help or a list of topics

-
-
Usage:

help <topic or command> -help list -help all

-
-
-

This will search for help on commands and other -topics related to the game.

-
-
-func()[source]
-

Run the dynamic help entry creator.

-
- -
-
-aliases = ['?']
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'help'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.CmdUse(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Use an item.

-
-
Usage:

use <item> [= target]

-
-
-

An item can have various function - looking at the item may -provide information as to its effects. Some items can be used -to attack others, and as such can only be used in combat.

-
-
-key = 'use'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_items.BattleCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.default.cmdset_character.CharacterCmdSet

-

This command set includes all the commmands used in the battle system.

-
-
-key = 'DefaultCharacter'
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_items.BattleCmdSet'
-
- -
- -
-
-evennia.contrib.turnbattle.tb_items.itemfunc_heal(item, user, target, **kwargs)[source]
-

Item function that heals HP.

-
-
kwargs:

min_healing(int): Minimum amount of HP recovered -max_healing(int): Maximum amount of HP recovered

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_items.itemfunc_add_condition(item, user, target, **kwargs)[source]
-

Item function that gives the target one or more conditions.

-
-
kwargs:
-
conditions (list): Conditions added by the item

formatted as a list of tuples: (condition (str), duration (int or True))

-
-
-
-
-

Notes

-

Should mostly be used for beneficial conditions - use itemfunc_attack -for an item that can give an enemy a harmful condition.

-
- -
-
-evennia.contrib.turnbattle.tb_items.itemfunc_cure_condition(item, user, target, **kwargs)[source]
-

Item function that’ll remove given conditions from a target.

-
-
kwargs:

to_cure(list): List of conditions (str) that the item cures when used

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_items.itemfunc_attack(item, user, target, **kwargs)[source]
-

Item function that attacks a target.

-
-
kwargs:

min_damage(int): Minimum damage dealt by the attack -max_damage(int): Maximum damage dealth by the attack -accuracy(int): Bonus / penalty to attack accuracy roll -inflict_condition(list): List of conditions inflicted on hit,

-
-

formatted as a (str, int) tuple containing condition name -and duration.

-
-
-
-

Notes

-

Calls resolve_attack at the end.

-
- -
-
-evennia.contrib.turnbattle.tb_items.ITEMFUNCS = {'add_condition': <function itemfunc_add_condition>, 'attack': <function itemfunc_attack>, 'cure_condition': <function itemfunc_cure_condition>, 'heal': <function itemfunc_heal>}
-

You can paste these prototypes into your game’s prototypes.py module in your -/world/ folder, and use the spawner to create them - they serve as examples -of items you can make and a handy way to demonstrate the system for -conditions as well.

-

Items don’t have any particular typeclass - any object with a db entry -“item_func” that references one of the functions given above can be used as -an item with the ‘use’ command.

-

Only “item_func” is required, but item behavior can be further modified by -specifying any of the following:

-
-

item_uses (int): If defined, item has a limited number of uses

-

item_selfonly (bool): If True, user can only use the item on themself

-
-
item_consumable(True or str): If True, item is destroyed when it runs

out of uses. If a string is given, the item will spawn a new -object as it’s destroyed, with the string specifying what prototype -to spawn.

-
-
item_kwargs (dict): Keyword arguments to pass to the function defined in

item_func. Unique to each function, and can be used to make multiple -items using the same function work differently.

-
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_magic.html b/docs/0.9.5/api/evennia.contrib.turnbattle.tb_magic.html deleted file mode 100644 index 45e59490ce..0000000000 --- a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_magic.html +++ /dev/null @@ -1,1010 +0,0 @@ - - - - - - - - - evennia.contrib.turnbattle.tb_magic — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.turnbattle.tb_magic

-

Simple turn-based combat system with spell casting

-

Contrib - Tim Ashley Jenkins 2017

-

This is a version of the ‘turnbattle’ contrib that includes a basic, -expandable framework for a ‘magic system’, whereby players can spend -a limited resource (MP) to achieve a wide variety of effects, both in -and out of combat. This does not have to strictly be a system for -magic - it can easily be re-flavored to any other sort of resource -based mechanic, like psionic powers, special moves and stamina, and -so forth.

-

In this system, spells are learned by name with the ‘learnspell’ -command, and then used with the ‘cast’ command. Spells can be cast in or -out of combat - some spells can only be cast in combat, some can only be -cast outside of combat, and some can be cast any time. However, if you -are in combat, you can only cast a spell on your turn, and doing so will -typically use an action (as specified in the spell’s funciton).

-

Spells are defined at the end of the module in a database that’s a -dictionary of dictionaries - each spell is matched by name to a function, -along with various parameters that restrict when the spell can be used and -what the spell can be cast on. Included is a small variety of spells that -damage opponents and heal HP, as well as one that creates an object.

-

Because a spell can call any function, a spell can be made to do just -about anything at all. The SPELLS dictionary at the bottom of the module -even allows kwargs to be passed to the spell function, so that the same -function can be re-used for multiple similar spells.

-

Spells in this system work on a very basic resource: MP, which is spent -when casting spells and restored by resting. It shouldn’t be too difficult -to modify this system to use spell slots, some physical fuel or resource, -or whatever else your game requires.

-

To install and test, import this module’s TBMagicCharacter object into -your game’s character.py module:

-
-

from evennia.contrib.turnbattle.tb_magic import TBMagicCharacter

-
-

And change your game’s character typeclass to inherit from TBMagicCharacter -instead of the default:

-
-

class Character(TBMagicCharacter):

-
-

Note: If your character already existed you need to also make sure -to re-run the creation hooks on it to set the needed Attributes. -Use update self to try on yourself or use py to call at_object_creation() -on all existing Characters.

-

Next, import this module into your default_cmdsets.py module:

-
-

from evennia.contrib.turnbattle import tb_magic

-
-

And add the battle command set to your default command set:

-
-

# -# any commands you add below will overload the default ones. -# -self.add(tb_magic.BattleCmdSet())

-
-

This module is meant to be heavily expanded on, so you may want to copy it -to your game’s ‘world’ folder and modify it there rather than importing it -in your game and using it as-is.

-
-
-evennia.contrib.turnbattle.tb_magic.ACTIONS_PER_TURN = 1
-
- -
-
-evennia.contrib.turnbattle.tb_magic.roll_init(character)[source]
-

Rolls a number between 1-1000 to determine initiative.

-
-
Parameters
-

character (obj) – The character to determine initiative for

-
-
Returns
-

initiative (int) – The character’s place in initiative - higher -numbers go first.

-
-
-

Notes

-

By default, does not reference the character and simply returns -a random integer from 1 to 1000.

-

Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

-

return (randint(1,20)) + character.db.dexterity

-

This way, characters with a higher dexterity will go first more often.

-
- -
-
-evennia.contrib.turnbattle.tb_magic.get_attack(attacker, defender)[source]
-

Returns a value for an attack roll.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
Returns
-

attack_value (int)

-
-
Attack roll value, compared against a defense value

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

By default, returns a random integer from 1 to 100 without using any -properties from either the attacker or defender.

-

This can easily be expanded to return a value based on characters stats, -equipment, and abilities. This is why the attacker and defender are passed -to this function, even though nothing from either one are used in this example.

-
- -
-
-evennia.contrib.turnbattle.tb_magic.get_defense(attacker, defender)[source]
-

Returns a value for defense, which an attack roll must equal or exceed in order -for an attack to hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
Returns
-

defense_value (int)

-
-
Defense value, compared against an attack roll

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

By default, returns 50, not taking any properties of the defender or -attacker into account.

-

As above, this can be expanded upon based on character stats and equipment.

-
- -
-
-evennia.contrib.turnbattle.tb_magic.get_damage(attacker, defender)[source]
-

Returns a value for damage to be deducted from the defender’s HP after abilities -successful hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being damaged

  • -
-
-
Returns
-

damage_value (int)

-
-
Damage value, which is to be deducted from the defending

character’s HP.

-
-
-

-
-
-

Notes

-

By default, returns a random integer from 15 to 25 without using any -properties from either the attacker or defender.

-

Again, this can be expanded upon.

-
- -
-
-evennia.contrib.turnbattle.tb_magic.apply_damage(defender, damage)[source]
-

Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

-
-
Parameters
-
    -
  • defender (obj) – Character taking damage

  • -
  • damage (int) – Amount of damage being taken

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_magic.at_defeat(defeated)[source]
-

Announces the defeat of a fighter in combat.

-
-
Parameters
-

defeated (obj) – Fighter that’s been defeated.

-
-
-

Notes

-

All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

-
- -
-
-evennia.contrib.turnbattle.tb_magic.resolve_attack(attacker, defender, attack_value=None, defense_value=None)[source]
-

Resolves an attack and outputs the result.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
-
-
-

Notes

-

Even though the attack and defense values are calculated -extremely simply, they are separated out into their own functions -so that they are easier to expand upon.

-
- -
-
-evennia.contrib.turnbattle.tb_magic.combat_cleanup(character)[source]
-

Cleans up all the temporary combat-related attributes on a character.

-
-
Parameters
-

character (obj) – Character to have their combat attributes removed

-
-
-

Notes

-

Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

-
- -
-
-evennia.contrib.turnbattle.tb_magic.is_in_combat(character)[source]
-

Returns true if the given character is in combat.

-
-
Parameters
-

character (obj) – Character to determine if is in combat or not

-
-
Returns
-

(bool) – True if in combat or False if not in combat

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_magic.is_turn(character)[source]
-

Returns true if it’s currently the given character’s turn in combat.

-
-
Parameters
-

character (obj) – Character to determine if it is their turn or not

-
-
Returns
-

(bool) – True if it is their turn or False otherwise

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_magic.spend_action(character, actions, action_name=None)[source]
-

Spends a character’s available combat actions and checks for end of turn.

-
-
Parameters
-
    -
  • character (obj) – Character spending the action

  • -
  • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

  • -
-
-
Keyword Arguments
-
    -
  • action_name (str or None) – If a string is given, sets character’s last action in

  • -
  • to provided string (combat) –

  • -
-
-
-
- -
-
-class evennia.contrib.turnbattle.tb_magic.TBMagicCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultCharacter

-

A character able to participate in turn-based combat. Has attributes for current -and maximum HP, and access to combat commands.

-
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-

Adds attributes for a character’s current and maximum HP. -We’re just going to set this value at ‘100’ by default.

-

You may want to expand this to include various ‘stats’ that -can be changed at creation and factor into combat calculations.

-
- -
-
-at_before_move(destination)[source]
-

Called just before starting to move this object to -destination.

-
-
Parameters
-

destination (Object) – The object we are moving to

-
-
Returns
-

shouldmove (bool) – If we should move or not.

-
-
-

Notes

-

If this method returns False/None, the move is cancelled -before it is even started.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_magic.TBMagicCharacter'
-
- -
-
-typename = 'TBMagicCharacter'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.TBMagicTurnHandler(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This is the script that handles the progression of combat through turns. -On creation (when a fight is started) it adds all combat-ready characters -to its roster and then sorts them into a turn order. There can only be one -fight going on in a single room at a time, so the script is assigned to a -room as its object.

-

Fights persist until only one participant is left with any HP or all -remaining participants choose to end the combat with the ‘disengage’ command.

-
-
-at_script_creation()[source]
-

Called once, when the script is created.

-
- -
-
-at_stop()[source]
-

Called at script termination.

-
- -
-
-at_repeat()[source]
-

Called once every self.interval seconds.

-
- -
-
-initialize_for_combat(character)[source]
-

Prepares a character for combat when starting or entering a fight.

-
-
Parameters
-

character (obj) – Character to initialize for combat.

-
-
-
- -
-
-start_turn(character)[source]
-

Readies a character for the start of their turn by replenishing their -available actions and notifying them that their turn has come up.

-
-
Parameters
-

character (obj) – Character to be readied.

-
-
-

Notes

-

Here, you only get one action per turn, but you might want to allow more than -one per turn, or even grant a number of actions based on a character’s -attributes. You can even add multiple different kinds of actions, I.E. actions -separated for movement, by adding “character.db.combat_movesleft = 3” or -something similar.

-
- -
-
-next_turn()[source]
-

Advances to the next character in the turn order.

-
- -
-
-turn_end_check(character)[source]
-

Tests to see if a character’s turn is over, and cycles to the next turn if it is.

-
-
Parameters
-

character (obj) – Character to test for end of turn

-
-
-
- -
-
-join_fight(character)[source]
-

Adds a new character to a fight already in progress.

-
-
Parameters
-

character (obj) – Character to be added to the fight.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_magic.TBMagicTurnHandler'
-
- -
-
-typename = 'TBMagicTurnHandler'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdFight(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Starts a fight with everyone in the same room as you.

-
-
Usage:

fight

-
-
-

When you start a fight, everyone in the room who is able to -fight is added to combat, and a turn order is randomly rolled. -When it’s your turn, you can attack other characters.

-
-
-key = 'fight'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdAttack(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Attacks another character.

-
-
Usage:

attack <target>

-
-
-

When in a fight, you may attack another character. The attack has -a chance to hit, and if successful, will deal damage.

-
-
-key = 'attack'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdPass(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes on your turn.

-
-
Usage:

pass

-
-
-

When in a fight, you can use this command to end your turn early, even -if there are still any actions you can take.

-
-
-key = 'pass'
-
- -
-
-aliases = ['hold', 'wait']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdDisengage(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes your turn and attempts to end combat.

-
-
Usage:

disengage

-
-
-

Ends your turn early and signals that you’re trying to end -the fight. If all participants in a fight disengage, the -fight ends.

-
-
-key = 'disengage'
-
- -
-
-aliases = ['spare']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdLearnSpell(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Learn a magic spell.

-
-
Usage:

learnspell <spell name>

-
-
-

Adds a spell by name to your list of spells known.

-

The following spells are provided as examples:

-
-
-
|wmagic missile|n (3 MP): Fires three missiles that never miss. Can target

up to three different enemies.

-
-
-

|wflame shot|n (3 MP): Shoots a high-damage jet of flame at one target.

-

|wcure wounds|n (5 MP): Heals damage on one target.

-
-
|wmass cure wounds|n (10 MP): Like ‘cure wounds’, but can heal up to 5

targets at once.

-
-
-

|wfull heal|n (12 MP): Heals one target back to full HP.

-

|wcactus conjuration|n (2 MP): Creates a cactus.

-
-
-
-key = 'learnspell'
-
- -
-
-help_category = 'magic'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdCast(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Cast a magic spell that you know, provided you have the MP -to spend on its casting.

-
-
Usage:

cast <spellname> [= <target1>, <target2>, etc…]

-
-
-

Some spells can be cast on multiple targets, some can be cast -on only yourself, and some don’t need a target specified at all. -Typing ‘cast’ by itself will give you a list of spells you know.

-
-
-key = 'cast'
-
- -
-
-help_category = 'magic'
-
- -
-
-func()[source]
-

This performs the actual command.

-

Note: This is a quite long command, since it has to cope with all -the different circumstances in which you may or may not be able -to cast a spell. None of the spell’s effects are handled by the -command - all the command does is verify that the player’s input -is valid for the spell being cast and then call the spell’s -function.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdRest(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Recovers damage and restores MP.

-
-
Usage:

rest

-
-
-

Resting recovers your HP and MP to their maximum, but you can -only rest if you’re not in a fight.

-
-
-key = 'rest'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdStatus(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Gives combat information.

-
-
Usage:

status

-
-
-

Shows your current and maximum HP and your distance from -other targets in combat.

-
-
-key = 'status'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.CmdCombatHelp(**kwargs)[source]
-

Bases: evennia.commands.default.help.CmdHelp

-

View help or a list of topics

-
-
Usage:

help <topic or command> -help list -help all

-
-
-

This will search for help on commands and other -topics related to the game.

-
-
-func()[source]
-

Run the dynamic help entry creator.

-
- -
-
-aliases = ['?']
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'help'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_magic.BattleCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.default.cmdset_character.CharacterCmdSet

-

This command set includes all the commmands used in the battle system.

-
-
-key = 'DefaultCharacter'
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_magic.BattleCmdSet'
-
- -
- -
-
-evennia.contrib.turnbattle.tb_magic.spell_healing(caster, spell_name, targets, cost, **kwargs)[source]
-

Spell that restores HP to a target or targets.

-
-
kwargs:
-
healing_range (tuple): Minimum and maximum amount healed to

each target. (20, 40) by default.

-
-
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_magic.spell_attack(caster, spell_name, targets, cost, **kwargs)[source]
-

Spell that deals damage in combat. Similar to resolve_attack.

-
-
kwargs:
-
attack_name (tuple): Single and plural describing the sort of

attack or projectile that strikes each enemy.

-
-
damage_range (tuple): Minimum and maximum damage dealt by the

spell. (10, 20) by default.

-
-
accuracy (int): Modifier to the spell’s attack roll, determining

an increased or decreased chance to hit. 0 by default.

-
-
attack_count (int): How many individual attacks are made as part

of the spell. If the number of attacks exceeds the number of -targets, the first target specified will be attacked more -than once. Just 1 by default - if the attack_count is less -than the number targets given, each target will only be -attacked once.

-
-
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_magic.spell_conjure(caster, spell_name, targets, cost, **kwargs)[source]
-

Spell that creates an object.

-
-
kwargs:

obj_key (str): Key of the created object. -obj_desc (str): Desc of the created object. -obj_typeclass (str): Typeclass path of the object.

-
-
-

If you want to make more use of this particular spell funciton, -you may want to modify it to use the spawner (in evennia.utils.spawner) -instead of creating objects directly.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_range.html b/docs/0.9.5/api/evennia.contrib.turnbattle.tb_range.html deleted file mode 100644 index 91da9c133f..0000000000 --- a/docs/0.9.5/api/evennia.contrib.turnbattle.tb_range.html +++ /dev/null @@ -1,1271 +0,0 @@ - - - - - - - - - evennia.contrib.turnbattle.tb_range — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.turnbattle.tb_range

-

Simple turn-based combat system with range and movement

-

Contrib - Tim Ashley Jenkins 2017

-

This is a version of the ‘turnbattle’ contrib that includes a system -for abstract movement and positioning in combat, including distinction -between melee and ranged attacks. In this system, a fighter or object’s -exact position is not recorded - only their relative distance to other -actors in combat.

-

In this example, the distance between two objects in combat is expressed -as an integer value: 0 for “engaged” objects that are right next to each -other, 1 for “reach” which is for objects that are near each other but -not directly adjacent, and 2 for “range” for objects that are far apart.

-

When combat starts, all fighters are at reach with each other and other -objects, and at range from any exits. On a fighter’s turn, they can use -the “approach” command to move closer to an object, or the “withdraw” -command to move further away from an object, either of which takes an -action in combat. In this example, fighters are given two actions per -turn, allowing them to move and attack in the same round, or to attack -twice or move twice.

-

When you move toward an object, you will also move toward anything else -that’s close to your target - the same goes for moving away from a target, -which will also move you away from anything close to your target. Moving -toward one target may also move you away from anything you’re already -close to, but withdrawing from a target will never inadvertently bring -you closer to anything else.

-

In this example, there are two attack commands. ‘Attack’ can only hit -targets that are ‘engaged’ (range 0) with you. ‘Shoot’ can hit any target -on the field, but cannot be used if you are engaged with any other fighters. -In addition, strikes made with the ‘attack’ command are more accurate than -‘shoot’ attacks. This is only to provide an example of how melee and ranged -attacks can be made to work differently - you can, of course, modify this -to fit your rules system.

-

When in combat, the ranges of objects are also accounted for - you can’t -pick up an object unless you’re engaged with it, and can’t give an object -to another fighter without being engaged with them either. Dropped objects -are automatically assigned a range of ‘engaged’ with the fighter who dropped -them. Additionally, giving or getting an object will take an action in combat. -Dropping an object does not take an action, but can only be done on your turn.

-

When combat ends, all range values are erased and all restrictions on getting -or getting objects are lifted - distances are no longer tracked and objects in -the same room can be considered to be in the same space, as is the default -behavior of Evennia and most MUDs.

-

This system allows for strategies in combat involving movement and -positioning to be implemented in your battle system without the use of -a ‘grid’ of coordinates, which can be difficult and clunky to navigate -in text and disadvantageous to players who use screen readers. This loose, -narrative method of tracking position is based around how the matter is -handled in tabletop RPGs played without a grid - typically, a character’s -exact position in a room isn’t important, only their relative distance to -other actors.

-

You may wish to expand this system with a method of distinguishing allies -from enemies (to prevent allied characters from blocking your ranged attacks) -as well as some method by which melee-focused characters can prevent enemies -from withdrawing or punish them from doing so, such as by granting “attacks of -opportunity” or something similar. If you wish, you can also expand the breadth -of values allowed for range - rather than just 0, 1, and 2, you can allow ranges -to go up to much higher values, and give attacks and movements more varying -values for distance for a more granular system. You may also want to implement -a system for fleeing or changing rooms in combat by approaching exits, which -are objects placed in the range field like any other.

-

To install and test, import this module’s TBRangeCharacter object into -your game’s character.py module:

-
-

from evennia.contrib.turnbattle.tb_range import TBRangeCharacter

-
-

And change your game’s character typeclass to inherit from TBRangeCharacter -instead of the default:

-
-

class Character(TBRangeCharacter):

-
-

Do the same thing in your game’s objects.py module for TBRangeObject:

-
-

from evennia.contrib.turnbattle.tb_range import TBRangeObject -class Object(TBRangeObject):

-
-

Next, import this module into your default_cmdsets.py module:

-
-

from evennia.contrib.turnbattle import tb_range

-
-

And add the battle command set to your default command set:

-
-

# -# any commands you add below will overload the default ones. -# -self.add(tb_range.BattleCmdSet())

-
-

This module is meant to be heavily expanded on, so you may want to copy it -to your game’s ‘world’ folder and modify it there rather than importing it -in your game and using it as-is.

-
-
-evennia.contrib.turnbattle.tb_range.ACTIONS_PER_TURN = 2
-
- -
-
-evennia.contrib.turnbattle.tb_range.roll_init(character)[source]
-

Rolls a number between 1-1000 to determine initiative.

-
-
Parameters
-

character (obj) – The character to determine initiative for

-
-
Returns
-

initiative (int) – The character’s place in initiative - higher -numbers go first.

-
-
-

Notes

-

By default, does not reference the character and simply returns -a random integer from 1 to 1000.

-

Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

-

return (randint(1,20)) + character.db.dexterity

-

This way, characters with a higher dexterity will go first more often.

-
- -
-
-evennia.contrib.turnbattle.tb_range.get_attack(attacker, defender, attack_type)[source]
-

Returns a value for an attack roll.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
  • attack_type (str) – Type of attack (‘melee’ or ‘ranged’)

  • -
-
-
Returns
-

attack_value (int)

-
-
Attack roll value, compared against a defense value

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

By default, generates a random integer from 1 to 100 without using any -properties from either the attacker or defender, and modifies the result -based on whether it’s for a melee or ranged attack.

-

This can easily be expanded to return a value based on characters stats, -equipment, and abilities. This is why the attacker and defender are passed -to this function, even though nothing from either one are used in this example.

-
- -
-
-evennia.contrib.turnbattle.tb_range.get_defense(attacker, defender, attack_type)[source]
-

Returns a value for defense, which an attack roll must equal or exceed in order -for an attack to hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
  • attack_type (str) – Type of attack (‘melee’ or ‘ranged’)

  • -
-
-
Returns
-

defense_value (int)

-
-
Defense value, compared against an attack roll

to determine whether an attack hits or misses.

-
-
-

-
-
-

Notes

-

By default, returns 50, not taking any properties of the defender or -attacker into account.

-

As above, this can be expanded upon based on character stats and equipment.

-
- -
-
-evennia.contrib.turnbattle.tb_range.get_damage(attacker, defender)[source]
-

Returns a value for damage to be deducted from the defender’s HP after abilities -successful hit.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being damaged

  • -
-
-
Returns
-

damage_value (int)

-
-
Damage value, which is to be deducted from the defending

character’s HP.

-
-
-

-
-
-

Notes

-

By default, returns a random integer from 15 to 25 without using any -properties from either the attacker or defender.

-

Again, this can be expanded upon.

-
- -
-
-evennia.contrib.turnbattle.tb_range.apply_damage(defender, damage)[source]
-

Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

-
-
Parameters
-
    -
  • defender (obj) – Character taking damage

  • -
  • damage (int) – Amount of damage being taken

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_range.at_defeat(defeated)[source]
-

Announces the defeat of a fighter in combat.

-
-
Parameters
-

defeated (obj) – Fighter that’s been defeated.

-
-
-

Notes

-

All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

-
- -
-
-evennia.contrib.turnbattle.tb_range.resolve_attack(attacker, defender, attack_type, attack_value=None, defense_value=None)[source]
-

Resolves an attack and outputs the result.

-
-
Parameters
-
    -
  • attacker (obj) – Character doing the attacking

  • -
  • defender (obj) – Character being attacked

  • -
  • attack_type (str) – Type of attack (melee or ranged)

  • -
-
-
-

Notes

-

Even though the attack and defense values are calculated -extremely simply, they are separated out into their own functions -so that they are easier to expand upon.

-
- -
-
-evennia.contrib.turnbattle.tb_range.get_range(obj1, obj2)[source]
-

Gets the combat range between two objects.

-
-
Parameters
-
    -
  • obj1 (obj) – First object

  • -
  • obj2 (obj) – Second object

  • -
-
-
Returns
-

range (int or None) – Distance between two objects or None if not applicable

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_range.distance_inc(mover, target)[source]
-

Function that increases distance in range field between mover and target.

-
-
Parameters
-
    -
  • mover (obj) – The object moving

  • -
  • target (obj) – The object to be moved away from

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_range.approach(mover, target)[source]
-

Manages a character’s whole approach, including changes in ranges to other characters.

-
-
Parameters
-
    -
  • mover (obj) – The object moving

  • -
  • target (obj) – The object to be moved toward

  • -
-
-
-

Notes

-

The mover will also automatically move toward any objects that are closer to the -target than the mover is. The mover will also move away from anything they started -out close to.

-
- -
-
-evennia.contrib.turnbattle.tb_range.withdraw(mover, target)[source]
-

Manages a character’s whole withdrawal, including changes in ranges to other characters.

-
-
Parameters
-
    -
  • mover (obj) – The object moving

  • -
  • target (obj) – The object to be moved away from

  • -
-
-
-

Notes

-

The mover will also automatically move away from objects that are close to the target -of their withdrawl. The mover will never inadvertently move toward anything else while -withdrawing - they can be considered to be moving to open space.

-
- -
-
-evennia.contrib.turnbattle.tb_range.combat_cleanup(character)[source]
-

Cleans up all the temporary combat-related attributes on a character.

-
-
Parameters
-

character (obj) – Character to have their combat attributes removed

-
-
-

Notes

-

Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

-
- -
-
-evennia.contrib.turnbattle.tb_range.is_in_combat(character)[source]
-

Returns true if the given character is in combat.

-
-
Parameters
-

character (obj) – Character to determine if is in combat or not

-
-
Returns
-

(bool) – True if in combat or False if not in combat

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_range.is_turn(character)[source]
-

Returns true if it’s currently the given character’s turn in combat.

-
-
Parameters
-

character (obj) – Character to determine if it is their turn or not

-
-
Returns
-

(bool) – True if it is their turn or False otherwise

-
-
-
- -
-
-evennia.contrib.turnbattle.tb_range.spend_action(character, actions, action_name=None)[source]
-

Spends a character’s available combat actions and checks for end of turn.

-
-
Parameters
-
    -
  • character (obj) – Character spending the action

  • -
  • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

  • -
-
-
Keyword Arguments
-
    -
  • action_name (str or None) – If a string is given, sets character’s last action in

  • -
  • to provided string (combat) –

  • -
-
-
-
- -
-
-evennia.contrib.turnbattle.tb_range.combat_status_message(fighter)[source]
-

Sends a message to a player with their current HP and -distances to other fighters and objects. Called at turn -start and by the ‘status’ command.

-
- -
-
-class evennia.contrib.turnbattle.tb_range.TBRangeTurnHandler(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This is the script that handles the progression of combat through turns. -On creation (when a fight is started) it adds all combat-ready characters -to its roster and then sorts them into a turn order. There can only be one -fight going on in a single room at a time, so the script is assigned to a -room as its object.

-

Fights persist until only one participant is left with any HP or all -remaining participants choose to end the combat with the ‘disengage’ -command.

-
-
-at_script_creation()[source]
-

Called once, when the script is created.

-
- -
-
-at_stop()[source]
-

Called at script termination.

-
- -
-
-at_repeat()[source]
-

Called once every self.interval seconds.

-
- -
-
-init_range(to_init)[source]
-

Initializes range values for an object at the start of a fight.

-
-
Parameters
-

to_init (object) – Object to initialize range field for.

-
-
-
- -
-
-join_rangefield(to_init, anchor_obj=None, add_distance=0)[source]
-

Adds a new object to the range field of a fight in progress.

-
-
Parameters
-

to_init (object) – Object to initialize range field for.

-
-
Keyword Arguments
-
    -
  • anchor_obj (object) – Object to copy range values from, or None for a random object.

  • -
  • add_distance (int) – Distance to put between to_init object and anchor object.

  • -
-
-
-
- -
-
-initialize_for_combat(character)[source]
-

Prepares a character for combat when starting or entering a fight.

-
-
Parameters
-

character (obj) – Character to initialize for combat.

-
-
-
- -
-
-start_turn(character)[source]
-

Readies a character for the start of their turn by replenishing their -available actions and notifying them that their turn has come up.

-
-
Parameters
-

character (obj) – Character to be readied.

-
-
-

Notes

-

In this example, characters are given two actions per turn. This allows -characters to both move and attack in the same turn (or, alternately, -move twice or attack twice).

-
- -
-
-next_turn()[source]
-

Advances to the next character in the turn order.

-
- -
-
-turn_end_check(character)[source]
-

Tests to see if a character’s turn is over, and cycles to the next turn if it is.

-
-
Parameters
-

character (obj) – Character to test for end of turn

-
-
-
- -
-
-join_fight(character)[source]
-

Adds a new character to a fight already in progress.

-
-
Parameters
-

character (obj) – Character to be added to the fight.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_range.TBRangeTurnHandler'
-
- -
-
-typename = 'TBRangeTurnHandler'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.TBRangeCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultCharacter

-

A character able to participate in turn-based combat. Has attributes for current -and maximum HP, and access to combat commands.

-
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-
- -
-
-at_before_move(destination)[source]
-

Called just before starting to move this object to -destination.

-
-
Parameters
-

destination (Object) – The object we are moving to

-
-
Returns
-

shouldmove (bool) – If we should move or not.

-
-
-

Notes

-

If this method returns False/None, the move is cancelled -before it is even started.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_range.TBRangeCharacter'
-
- -
-
-typename = 'TBRangeCharacter'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.TBRangeObject(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

An object that is assigned range values in combat. Getting, giving, and dropping -the object has restrictions in combat - you must be next to an object to get it, -must be next to your target to give them something, and can only interact with -objects on your own turn.

-
-
-at_before_drop(dropper)[source]
-

Called by the default drop command before this object has been -dropped.

-
-
Parameters
-
    -
  • dropper (Object) – The object which will drop this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

shoulddrop (bool) – If the object should be dropped or not.

-
-
-

Notes

-

If this method returns False/None, the dropping is cancelled -before it is even started.

-
- -
-
-at_drop(dropper)[source]
-

Called by the default drop command when this object has been -dropped.

-
-
Parameters
-
    -
  • dropper (Object) – The object which just dropped this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

This hook cannot stop the drop from happening. Use -permissions or the at_before_drop() hook for that.

-
- -
-
-at_before_get(getter)[source]
-

Called by the default get command before this object has been -picked up.

-
-
Parameters
-
    -
  • getter (Object) – The object about to get this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

shouldget (bool) – If the object should be gotten or not.

-
-
-

Notes

-

If this method returns False/None, the getting is cancelled -before it is even started.

-
- -
-
-at_get(getter)[source]
-

Called by the default get command when this object has been -picked up.

-
-
Parameters
-
    -
  • getter (Object) – The object getting this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

This hook cannot stop the pickup from happening. Use -permissions or the at_before_get() hook for that.

-
- -
-
-at_before_give(giver, getter)[source]
-

Called by the default give command before this object has been -given.

-
-
Parameters
-
    -
  • giver (Object) – The object about to give this object.

  • -
  • getter (Object) – The object about to get this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

shouldgive (bool) – If the object should be given or not.

-
-
-

Notes

-

If this method returns False/None, the giving is cancelled -before it is even started.

-
- -
-
-at_give(giver, getter)[source]
-

Called by the default give command when this object has been -given.

-
-
Parameters
-
    -
  • giver (Object) – The object giving this object.

  • -
  • getter (Object) – The object getting this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

This hook cannot stop the give from happening. Use -permissions or the at_before_give() hook for that.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_range.TBRangeObject'
-
- -
-
-typename = 'TBRangeObject'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdFight(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Starts a fight with everyone in the same room as you.

-
-
Usage:

fight

-
-
-

When you start a fight, everyone in the room who is able to -fight is added to combat, and a turn order is randomly rolled. -When it’s your turn, you can attack other characters.

-
-
-key = 'fight'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdAttack(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Attacks another character in melee.

-
-
Usage:

attack <target>

-
-
-

When in a fight, you may attack another character. The attack has -a chance to hit, and if successful, will deal damage. You can only -attack engaged targets - that is, targets that are right next to -you. Use the ‘approach’ command to get closer to a target.

-
-
-key = 'attack'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdShoot(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Attacks another character from range.

-
-
Usage:

shoot <target>

-
-
-

When in a fight, you may shoot another character. The attack has -a chance to hit, and if successful, will deal damage. You can attack -any target in combat by shooting, but can’t shoot if there are any -targets engaged with you. Use the ‘withdraw’ command to retreat from -nearby enemies.

-
-
-key = 'shoot'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdApproach(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Approaches an object.

-
-
Usage:

approach <target>

-
-
-

Move one space toward a character or object. You can only attack -characters you are 0 spaces away from.

-
-
-key = 'approach'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdWithdraw(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Moves away from an object.

-
-
Usage:

withdraw <target>

-
-
-

Move one space away from a character or object.

-
-
-key = 'withdraw'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdPass(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes on your turn.

-
-
Usage:

pass

-
-
-

When in a fight, you can use this command to end your turn early, even -if there are still any actions you can take.

-
-
-key = 'pass'
-
- -
-
-aliases = ['hold', 'wait']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdDisengage(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Passes your turn and attempts to end combat.

-
-
Usage:

disengage

-
-
-

Ends your turn early and signals that you’re trying to end -the fight. If all participants in a fight disengage, the -fight ends.

-
-
-key = 'disengage'
-
- -
-
-aliases = ['spare']
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdRest(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Recovers damage.

-
-
Usage:

rest

-
-
-

Resting recovers your HP to its maximum, but you can only -rest if you’re not in a fight.

-
-
-key = 'rest'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdStatus(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Gives combat information.

-
-
Usage:

status

-
-
-

Shows your current and maximum HP and your distance from -other targets in combat.

-
-
-key = 'status'
-
- -
-
-help_category = 'combat'
-
- -
-
-func()[source]
-

This performs the actual command.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.CmdCombatHelp(**kwargs)[source]
-

Bases: evennia.commands.default.help.CmdHelp

-

View help or a list of topics

-
-
Usage:

help <topic or command> -help list -help all

-
-
-

This will search for help on commands and other -topics related to the game.

-
-
-func()[source]
-

Run the dynamic help entry creator.

-
- -
-
-aliases = ['?']
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'help'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.turnbattle.tb_range.BattleCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.default.cmdset_character.CharacterCmdSet

-

This command set includes all the commmands used in the battle system.

-
-
-key = 'DefaultCharacter'
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset

-
- -
-
-path = 'evennia.contrib.turnbattle.tb_range.BattleCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.bodyfunctions.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.bodyfunctions.html deleted file mode 100644 index 1ac38136a2..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.bodyfunctions.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_examples.bodyfunctions — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_examples.bodyfunctions

-

Example script for testing. This adds a simple timer that has your -character make observations and notices at irregular intervals.

-
-
To test, use

@script me = tutorial_examples.bodyfunctions.BodyFunctions

-
-
-

The script will only send messages to the object it is stored on, so -make sure to put it on yourself or you won’t see any messages!

-
-
-class evennia.contrib.tutorial_examples.bodyfunctions.BodyFunctions(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This class defines the script itself

-
-
-at_script_creation()[source]
-

Only called once, when script is first created.

-
- -
-
-at_repeat()[source]
-

This gets called every self.interval seconds. We make -a random check here so as to only return 33% of the time.

-
- -
-
-send_random_message()[source]
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_examples.bodyfunctions.BodyFunctions'
-
- -
-
-typename = 'BodyFunctions'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.cmdset_red_button.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.cmdset_red_button.html deleted file mode 100644 index cb6a867501..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.cmdset_red_button.html +++ /dev/null @@ -1,555 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_examples.cmdset_red_button — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_examples.cmdset_red_button

-

This defines the cmdset for the red_button. Here we have defined -the commands and the cmdset in the same module, but if you -have many different commands to merge it is often better -to define the cmdset separately, picking and choosing from -among the available commands as to what should be included in the -cmdset - this way you can often re-use the commands too.

-
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.CmdNudge(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Try to nudge the button’s lid

-
-
Usage:

nudge lid

-
-
-

This command will have you try to -push the lid of the button away.

-
-
-key = 'nudge lid'
-
- -
-
-aliases = ['nudge']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

nudge the lid. Random chance of success to open it.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.CmdPush(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Push the red button

-
-
Usage:

push button

-
-
-
-
-key = 'push button'
-
- -
-
-aliases = ['press button', 'press', 'push']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Note that we choose to implement this with checking for -if the lid is open/closed. This is because this command -is likely to be tried regardless of the state of the lid.

-

An alternative would be to make two versions of this command -and tuck them into the cmdset linked to the Open and Closed -lid-state respectively.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.CmdSmashGlass(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

smash glass

-
-
Usage:

smash glass

-
-
-

Try to smash the glass of the button.

-
-
-key = 'smash glass'
-
- -
-
-aliases = ['smash', 'break lid', 'smash lid']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

The lid won’t open, but there is a small chance -of causing the lamp to break.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.CmdOpenLid(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

open lid

-
-
Usage:

open lid

-
-
-
-
-key = 'open lid'
-
- -
-
-aliases = ['open button', 'open']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

simply call the right function.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.CmdCloseLid(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

close the lid

-
-
Usage:

close lid

-
-
-

Closes the lid of the red button.

-
-
-key = 'close lid'
-
- -
-
-aliases = ['close']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Close the lid

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.CmdBlindLook(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Looking around in darkness

-
-
Usage:

look <obj>

-
-
-

… not that there’s much to see in the dark.

-
-
-key = 'look'
-
- -
-
-aliases = ['examine', 'get', 'listen', 'feel', 'l', 'ex']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

This replaces all the senses when blinded.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.CmdBlindHelp(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Help function while in the blinded state

-
-
Usage:

help

-
-
-
-
-key = 'help'
-
- -
-
-aliases = ['h']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Give a message.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.DefaultCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

The default cmdset always sits -on the button object and whereas other -command sets may be added/merge onto it -and hide it, removing them will always -bring it back. It’s added to the object -using obj.cmdset.add_default().

-
-
-key = 'RedButtonDefault'
-
- -
-
-mergetype = 'Union'
-
- -
-
-at_cmdset_creation()[source]
-

Init the cmdset

-
- -
-
-path = 'evennia.contrib.tutorial_examples.cmdset_red_button.DefaultCmdSet'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.LidClosedCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

A simple cmdset tied to the redbutton object.

-

It contains the commands that launches the other -command sets, making the red button a self-contained -item (i.e. you don’t have to manually add any -scripts etc to it when creating it).

-
-
-key = 'LidClosedCmdSet'
-
- -
-
-key_mergetype = {'LidOpenCmdSet': 'Replace'}
-
- -
-
-at_cmdset_creation()[source]
-

Populates the cmdset when it is instantiated.

-
- -
-
-path = 'evennia.contrib.tutorial_examples.cmdset_red_button.LidClosedCmdSet'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.LidOpenCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

This is the opposite of the Closed cmdset.

-
-
-key = 'LidOpenCmdSet'
-
- -
-
-key_mergetype = {'LidClosedCmdSet': 'Replace'}
-
- -
-
-at_cmdset_creation()[source]
-

setup the cmdset (just one command)

-
- -
-
-path = 'evennia.contrib.tutorial_examples.cmdset_red_button.LidOpenCmdSet'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.cmdset_red_button.BlindCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

This is the cmdset added to the account when -the button is pushed.

-
-
-key = 'BlindCmdSet'
-
- -
-
-mergetype = 'Replace'
-
- -
-
-no_exits = True
-
- -
-
-no_objs = True
-
- -
-
-at_cmdset_creation()[source]
-

Setup the blind cmdset

-
- -
-
-path = 'evennia.contrib.tutorial_examples.cmdset_red_button.BlindCmdSet'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.example_batch_code.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.example_batch_code.html deleted file mode 100644 index 807f8ba797..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.example_batch_code.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_examples.example_batch_code — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_examples.example_batch_code

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.html deleted file mode 100644 index 8e8a884ed2..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_examples — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button.html deleted file mode 100644 index f5d518af86..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_examples.red_button — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_examples.red_button

-

This is a more advanced example object. It combines functions from -script.examples as well as commands.examples to make an interactive -button typeclass.

-

Create this button with

-
-

@create/drop examples.red_button.RedButton

-
-

Note that you must drop the button before you can see its messages!

-
-
-class evennia.contrib.tutorial_examples.red_button.RedButton(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

This class describes an evil red button. It will use the script -definition in contrib/examples/red_button_scripts to blink at regular -intervals. It also uses a series of script and commands to handle -pushing the button and causing effects when doing so.

-
-
The following attributes can be set on the button:

desc_lid_open - description when lid is open -desc_lid_closed - description when lid is closed -desc_lamp_broken - description when lamp is broken

-
-
-
-
-at_object_creation()[source]
-

This function is called when object is created. Use this -instead of e.g. __init__.

-
- -
-
-open_lid()[source]
-

Opens the glass lid and start the timer so it will soon close -again.

-
- -
-
-close_lid()[source]
-

Close the glass lid. This validates all scripts on the button, -which means that scripts only being valid when the lid is open -will go away automatically.

-
- -
-
-break_lamp(feedback=True)[source]
-

Breaks the lamp in the button, stopping it from blinking.

-
-
Parameters
-

feedback (bool) – Show a message about breaking the lamp.

-
-
-
- -
-
-press_button(pobject)[source]
-

Someone was foolish enough to press the button!

-
-
Parameters
-

pobject (Object) – The person pressing the button

-
-
-
- -
- -

The script system will regularly call this -function to make the button blink. Now and then -it won’t blink at all though, to add some randomness -to how often the message is echoed.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_examples.red_button.RedButton'
-
- -
-
-typename = 'RedButton'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button_scripts.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button_scripts.html deleted file mode 100644 index 1b102115c7..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button_scripts.html +++ /dev/null @@ -1,456 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_examples.red_button_scripts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_examples.red_button_scripts

-

Example of scripts.

-

These are scripts intended for a particular object - the -red_button object type in contrib/examples. A few variations -on uses of scripts are included.

-
-
-class evennia.contrib.tutorial_examples.red_button_scripts.ClosedLidState(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This manages the cmdset for the “closed” button state. What this -means is that while this script is valid, we add the RedButtonClosed -cmdset to it (with commands like open, nudge lid etc)

-
-
-at_script_creation()[source]
-

Called when script first created.

-
- -
-
-at_start()[source]
-

This is called once every server restart, so we want to add the -(memory-resident) cmdset to the object here. is_valid is automatically -checked so we don’t need to worry about adding the script to an -open lid.

-
- -
-
-is_valid()[source]
-

The script is only valid while the lid is closed. -self.obj is the red_button on which this script is defined.

-
- -
-
-at_stop()[source]
-

When the script stops we must make sure to clean up after us.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_examples.red_button_scripts.ClosedLidState'
-
- -
-
-typename = 'ClosedLidState'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.red_button_scripts.OpenLidState(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This manages the cmdset for the “open” button state. This will add -the RedButtonOpen

-
-
-at_script_creation()[source]
-

Called when script first created.

-
- -
-
-at_start()[source]
-

This is called once every server restart, so we want to add the -(memory-resident) cmdset to the object here. is_valid is -automatically checked, so we don’t need to worry about -adding the cmdset to a closed lid-button.

-
- -
-
-is_valid()[source]
-

The script is only valid while the lid is open. -self.obj is the red_button on which this script is defined.

-
- -
-
-at_stop()[source]
-

When the script stops (like if the lid is closed again) -we must make sure to clean up after us.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_examples.red_button_scripts.OpenLidState'
-
- -
-
-typename = 'OpenLidState'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.red_button_scripts.BlindedState(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This is a timed state.

-

This adds a (very limited) cmdset TO THE ACCOUNT, during a certain time, -after which the script will close and all functions are -restored. It’s up to the function starting the script to actually -set it on the right account object.

-
-
-at_script_creation()[source]
-

We set up the script here.

-
- -
-
-at_start()[source]
-

We want to add the cmdset to the linked object.

-

Note that the RedButtonBlind cmdset is defined to completly -replace the other cmdsets on the stack while it is active -(this means that while blinded, only operations in this cmdset -will be possible for the account to perform). It is however -not persistent, so should there be a bug in it, we just need -to restart the server to clear out of it during development.

-
- -
-
-at_stop()[source]
-

It’s important that we clear out that blinded cmdset -when we are done!

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_examples.red_button_scripts.BlindedState'
-
- -
-
-typename = 'BlindedState'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.red_button_scripts.CloseLidEvent(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This event closes the glass lid over the button -some time after it was opened. It’s a one-off -script that should be started/created when the -lid is opened.

-
-
-at_script_creation()[source]
-

Called when script object is first created. Sets things up. -We want to have a lid on the button that the user can pull -aside in order to make the button ‘pressable’. But after a set -time that lid should auto-close again, making the button safe -from pressing (and deleting this command).

-
- -
-
-is_valid()[source]
-

This script can only operate if the lid is open; if it -is already closed, the script is clearly invalid.

-

Note that we are here relying on an self.obj being -defined (and being a RedButton object) - this we should be able to -expect since this type of script is always tied to one individual -red button object and not having it would be an error.

-
- -
-
-at_repeat()[source]
-

Called after self.interval seconds. It closes the lid. Before this method is -called, self.is_valid() is automatically checked, so there is no need to -check this manually.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_examples.red_button_scripts.CloseLidEvent'
-
- -
-
-typename = 'CloseLidEvent'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.red_button_scripts.BlinkButtonEvent(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This timed script lets the button flash at regular intervals.

-
-
-at_script_creation()[source]
-

Sets things up. We want the button’s lamp to blink at -regular intervals, unless it’s broken (can happen -if you try to smash the glass, say).

-
- -
-
-is_valid()[source]
-

Button will keep blinking unless it is broken.

-
- -
-
-at_repeat()[source]
-

Called every self.interval seconds. Makes the lamp in -the button blink.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_examples.red_button_scripts.BlinkButtonEvent'
-
- -
-
-typename = 'BlinkButtonEvent'
-
- -
- -
-
-class evennia.contrib.tutorial_examples.red_button_scripts.DeactivateButtonEvent(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This deactivates the button for a short while (it won’t blink, won’t -close its lid etc). It is meant to be called when the button is pushed -and run as long as the blinded effect lasts. We cannot put these methods -in the AddBlindedCmdSet script since that script is defined on the account -whereas this one must be defined on the button.

-
-
-at_script_creation()[source]
-

Sets things up.

-
- -
-
-at_start()[source]
-

Deactivate the button. Observe that this method is always -called directly, regardless of the value of self.start_delay -(that just controls when at_repeat() is called)

-
- -
-
-at_repeat()[source]
-

When this is called, reset the functionality of the button.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_examples.red_button_scripts.DeactivateButtonEvent'
-
- -
-
-typename = 'DeactivateButtonEvent'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.tests.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.tests.html deleted file mode 100644 index 3198753fc2..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.tests.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_examples.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_examples.tests

-
-
-class evennia.contrib.tutorial_examples.tests.TestBodyFunctions(methodName='runTest')[source]
-

Bases: evennia.utils.test_resources.EvenniaTest

-
-
-script_typeclass
-

alias of evennia.contrib.tutorial_examples.bodyfunctions.BodyFunctions

-
- -
-
-setUp()[source]
-

Sets up testing environment

-
- -
-
-tearDown()[source]
-

Hook method for deconstructing the test fixture after testing it.

-
- -
-
-test_at_repeat(mock_random)[source]
-

test that no message will be sent when below the 66% threshold

-
- -
-
-test_send_random_message(mock_random)[source]
-

Test that correct message is sent for each random value

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_world.html b/docs/0.9.5/api/evennia.contrib.tutorial_world.html deleted file mode 100644 index 94937c9f55..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_world.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_world — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_world

-

This package holds the demo game of Evennia.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_world.intro_menu.html b/docs/0.9.5/api/evennia.contrib.tutorial_world.intro_menu.html deleted file mode 100644 index e4a8acd6be..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_world.intro_menu.html +++ /dev/null @@ -1,303 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_world.intro_menu — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_world.intro_menu

-

Intro menu / game tutor

-

Evennia contrib - Griatch 2020

-

This contrib is an intro-menu for general MUD and evennia usage using the -EvMenu menu-templating system.

-

EvMenu templating is a way to create a menu using a string-format instead -of creating all nodes manually. Of course, for full functionality one must -still create the goto-callbacks.

-
-
-evennia.contrib.tutorial_world.intro_menu.do_nothing(caller, raw_string, **kwargs)[source]
-

Re-runs the current node

-
- -
-
-evennia.contrib.tutorial_world.intro_menu.send_testing_tagged(caller, raw_string, **kwargs)[source]
-

Test to send a message to a pane tagged with ‘testing’ in the webclient.

-
- -
-
-class evennia.contrib.tutorial_world.intro_menu.DemoCommandSetHelp(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Demo the help command

-
-
-key = 'Help Demo Set'
-
- -
-
-priority = 2
-
- -
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.contrib.tutorial_world.intro_menu.DemoCommandSetHelp'
-
- -
- -
-
-evennia.contrib.tutorial_world.intro_menu.goto_command_demo_help(caller, raw_string, **kwargs)[source]
-

Sets things up before going to the help-demo node

-
- -
-
-class evennia.contrib.tutorial_world.intro_menu.DemoCommandSetComms(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Demo communications

-
-
-key = 'Color Demo Set'
-
- -
-
-priority = 2
-
- -
-
-no_exits = True
-
- -
-
-no_objs = True
-
- -
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.contrib.tutorial_world.intro_menu.DemoCommandSetComms'
-
- -
- -
-
-evennia.contrib.tutorial_world.intro_menu.goto_command_demo_comms(caller, raw_string, **kwargs)[source]
-

Setup and go to the color demo node.

-
- -
-
-class evennia.contrib.tutorial_world.intro_menu.DemoCommandSetRoom(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Demo some general in-game commands command.

-
-
-key = 'Room Demo Set'
-
- -
-
-priority = 2
-
- -
-
-no_exits = False
-
- -
-
-no_objs = False
-
- -
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.contrib.tutorial_world.intro_menu.DemoCommandSetRoom'
-
- -
- -
-
-evennia.contrib.tutorial_world.intro_menu.goto_command_demo_room(caller, raw_string, **kwargs)[source]
-

Setup and go to the demo-room node. Generates a little 2-room environment -for testing out some commands.

-
- -
-
-evennia.contrib.tutorial_world.intro_menu.goto_cleanup_cmdsets(caller, raw_strings, **kwargs)[source]
-

Cleanup all cmdsets.

-
- -
-
-class evennia.contrib.tutorial_world.intro_menu.TutorialEvMenu(caller, menudata, startnode='start', cmdset_mergetype='Replace', cmdset_priority=1, auto_quit=True, auto_look=True, auto_help=True, cmd_on_exit='look', persistent=False, startnode_input='', session=None, debug=False, **kwargs)[source]
-

Bases: evennia.utils.evmenu.EvMenu

-

Custom EvMenu for displaying the intro-menu

-
-
-close_menu()[source]
-

Custom cleanup actions when closing menu

-
- -
-
-options_formatter(optionslist)[source]
-

Formats the option block.

-
-
Parameters
-
    -
  • optionlist (list) – List of (key, description) tuples for every -option related to this node.

  • -
  • caller (Object, Account or None, optional) – The caller of the node.

  • -
-
-
Returns
-

options (str) – The formatted option display.

-
-
-
- -
- -
-
-evennia.contrib.tutorial_world.intro_menu.init_menu(caller)[source]
-

Call to initialize the menu.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_world.mob.html b/docs/0.9.5/api/evennia.contrib.tutorial_world.mob.html deleted file mode 100644 index 699748a431..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_world.mob.html +++ /dev/null @@ -1,347 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_world.mob — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_world.mob

-

This module implements a simple mobile object with -a very rudimentary AI as well as an aggressive enemy -object based on that mobile class.

-
-
-class evennia.contrib.tutorial_world.mob.CmdMobOnOff(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Activates/deactivates Mob

-
-
Usage:

mobon <mob> -moboff <mob>

-
-
-

This turns the mob from active (alive) mode -to inactive (dead) mode. It is used during -building to activate the mob once it’s -prepared.

-
-
-key = 'mobon'
-
- -
-
-aliases = ['moboff']
-
- -
-
-locks = 'cmd:superuser()'
-
- -
-
-func()[source]
-

Uses the mob’s set_alive/set_dead methods -to turn on/off the mob.”

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:superuser()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.mob.MobCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Holds the admin command controlling the mob

-
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.contrib.tutorial_world.mob.MobCmdSet'
-
- -
- -
-
-class evennia.contrib.tutorial_world.mob.Mob(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject

-

This is a state-machine AI mobile. It has several states which are -controlled from setting various Attributes. All default to True:

-
-
-
patrolling: if set, the mob will move randomly

from room to room, but preferring to not return -the way it came. If unset, the mob will remain -stationary (idling) until attacked.

-
-
aggressive: if set, will attack Characters in

the same room using whatever Weapon it -carries (see tutorial_world.objects.Weapon). -if unset, the mob will never engage in combat -no matter what.

-
-
hunting: if set, the mob will pursue enemies trying

to flee from it, so it can enter combat. If unset, -it will return to patrolling/idling if fled from.

-
-
-

immortal: If set, the mob cannot take any damage. -irregular_echoes: list of strings the mob generates at irregular intervals. -desc_alive: the physical description while alive -desc_dead: the physical descripion while dead -send_defeated_to: unique key/alias for location to send defeated enemies to -defeat_msg: message to echo to defeated opponent -defeat_msg_room: message to echo to room. Accepts %s as the name of the defeated. -hit_msg: message to echo when this mob is hit. Accepts %s for the mob’s key. -weapon_ineffective_msg: message to echo for useless attacks -death_msg: message to echo to room when this mob dies. -patrolling_pace: how many seconds per tick, when patrolling -aggressive_pace: -”- attacking -hunting_pace: -”- hunting -death_pace: -”- returning to life when dead

-
-
field ‘home’ - the home location should set to someplace inside

the patrolling area. The mob will use this if it should -happen to roam into a room with no exits.

-
-
-
-
-
-at_init()[source]
-

When initialized from cache (after a server reboot), set up -the AI state.

-
- -
-
-at_object_creation()[source]
-

Called the first time the object is created. -We set up the base properties and flags here.

-
- -
-
-set_alive(*args, **kwargs)[source]
-

Set the mob to “alive” mode. This effectively -resurrects it from the dead state.

-
- -
-
-set_dead()[source]
-

Set the mob to “dead” mode. This turns it off -and makes sure it can take no more damage. -It also starts a ticker for when it will return.

-
- -
-
-start_idle()[source]
-

Starts just standing around. This will kill -the ticker and do nothing more.

-
- -
-
-start_patrolling()[source]
-

Start the patrolling state by -registering us with the ticker-handler -at a leasurely pace.

-
- -
-
-start_hunting()[source]
-

Start the hunting state

-
- -
-
-start_attacking()[source]
-

Start the attacking state

-
- -
-
-do_patrol(*args, **kwargs)[source]
-

Called repeatedly during patrolling mode. In this mode, the -mob scans its surroundings and randomly chooses a viable exit. -One should lock exits with the traverse:has_account() lock in -order to block the mob from moving outside its area while -allowing account-controlled characters to move normally.

-
- -
-
-do_hunting(*args, **kwargs)[source]
-

Called regularly when in hunting mode. In hunting mode the mob -scans adjacent rooms for enemies and moves towards them to -attack if possible.

-
- -
-
-do_attack(*args, **kwargs)[source]
-

Called regularly when in attacking mode. In attacking mode -the mob will bring its weapons to bear on any targets -in the room.

-
- -
-
-at_hit(weapon, attacker, damage)[source]
-

Someone landed a hit on us. Check our status -and start attacking if not already doing so.

-
- -
-
-at_new_arrival(new_character)[source]
-

This is triggered whenever a new character enters the room. -This is called by the TutorialRoom the mob stands in and -allows it to be aware of changes immediately without needing -to poll for them all the time. For example, the mob can react -right away, also when patrolling on a very slow ticker.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.mob.Mob'
-
- -
-
-typename = 'Mob'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html b/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html deleted file mode 100644 index c13e95f34a..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html +++ /dev/null @@ -1,983 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_world.objects — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_world.objects

-

TutorialWorld - basic objects - Griatch 2011

-

This module holds all “dead” object definitions for -the tutorial world. Object-commands and -cmdsets -are also defined here, together with the object.

-

Objects:

-

TutorialObject

-

TutorialReadable -TutorialClimbable -Obelisk -LightSource -CrumblingWall -Weapon -WeaponRack

-
-
-class evennia.contrib.tutorial_world.objects.TutorialObject(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

This is the baseclass for all objects in the tutorial.

-
-
-at_object_creation()[source]
-

Called when the object is first created.

-
- -
-
-reset()[source]
-

Resets the object, whatever that may mean.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.TutorialObject'
-
- -
-
-typename = 'TutorialObject'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdRead(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-
-
Usage:

read [obj]

-
-
-

Read some text of a readable object.

-
-
-key = 'read'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Implements the read command. This simply looks for an -Attribute “readable_text” on the object and displays that.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdSetReadable(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

A CmdSet for readables.

-
-
-at_cmdset_creation()[source]
-

Called when the cmdset is created.

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.CmdSetReadable'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.TutorialReadable(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject

-

This simple object defines some attributes and

-
-
-at_object_creation()[source]
-

Called when object is created. We make sure to set the needed -Attribute and add the readable cmdset.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.TutorialReadable'
-
- -
-
-typename = 'TutorialReadable'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdClimb(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Climb an object

-
-
Usage:

climb <object>

-
-
-

This allows you to climb.

-
-
-key = 'climb'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Implements function

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdSetClimbable(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Climbing cmdset

-
-
-at_cmdset_creation()[source]
-

populate set

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.CmdSetClimbable'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.TutorialClimbable(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject

-

A climbable object. All that is special about it is that it has -the “climb” command available on it.

-
-
-at_object_creation()[source]
-

Called at initial creation only

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.TutorialClimbable'
-
- -
-
-typename = 'TutorialClimbable'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.Obelisk(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject

-

This object changes its description randomly, and which is shown -determines which order “clue id” is stored on the Character for -future puzzles.

-
-
Important Attribute:
-
puzzle_descs (list): list of descriptions. One of these is

picked randomly when this object is looked at and its index -in the list is used as a key for to solve the puzzle.

-
-
-
-
-
-
-at_object_creation()[source]
-

Called when object is created.

-
- -
-
-return_appearance(caller)[source]
-

This hook is called by the look command to get the description -of the object. We overload it with our own version.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.Obelisk'
-
- -
-
-typename = 'Obelisk'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdLight(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Creates light where there was none. Something to burn.

-
-
-key = 'on'
-
- -
-
-aliases = ['burn', 'light']
-
- -
-
-locks = 'cmd:holds()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Implements the light command. Since this command is designed -to sit on a “lightable” object, we operate only on self.obj.

-
- -
-
-lock_storage = 'cmd:holds()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdSetLight(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

CmdSet for the lightsource commands

-
-
-key = 'lightsource_cmdset'
-
- -
-
-priority = 3
-
- -
-
-at_cmdset_creation()[source]
-

called at cmdset creation

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.CmdSetLight'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.LightSource(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject

-

This implements a light source object.

-

When burned out, the object will be deleted.

-
-
-at_init()[source]
-

If this is called with the Attribute is_giving_light already -set, we know that the timer got killed by a server -reload/reboot before it had time to finish. So we kill it here -instead. This is the price we pay for the simplicity of the -non-persistent delay() method.

-
- -
-
-at_object_creation()[source]
-

Called when object is first created.

-
- -
-
-light()[source]
-

Light this object - this is called by Light command.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.LightSource'
-
- -
-
-typename = 'LightSource'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdShiftRoot(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Shifts roots around.

-
-
Usage:

shift blue root left/right -shift red root left/right -shift yellow root up/down -shift green root up/down

-
-
-
-
-key = 'shift'
-
- -
-
-aliases = ['shiftroot', 'move', 'pull', 'push']
-
- -
-
-locks = 'cmd:locattr(is_lit)'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-parse()[source]
-

Custom parser; split input by spaces for simplicity.

-
- -
-
-func()[source]
-
-
Implement the command.

blue/red - vertical roots -yellow/green - horizontal roots

-
-
-
- -
-
-lock_storage = 'cmd:locattr(is_lit)'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdPressButton(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Presses a button.

-
-
-key = 'press'
-
- -
-
-aliases = ['press button', 'push button', 'button']
-
- -
-
-locks = 'cmd:objattr(button_exposed) and objlocattr(is_lit)'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Implements the command

-
- -
-
-lock_storage = 'cmd:objattr(button_exposed) and objlocattr(is_lit)'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdSetCrumblingWall(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Group the commands for crumblingWall

-
-
-key = 'crumblingwall_cmdset'
-
- -
-
-priority = 2
-
- -
-
-at_cmdset_creation()[source]
-

called when object is first created.

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.CmdSetCrumblingWall'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CrumblingWall(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject, evennia.objects.objects.DefaultExit

-

This is a custom Exit.

-

The CrumblingWall can be examined in various ways, but only if a -lit light source is in the room. The traversal itself is blocked -by a traverse: lock on the exit that only allows passage if a -certain attribute is set on the trying account.

-
-
Important attribute
-
destination - this property must be set to make this a valid exit

whenever the button is pushed (this hides it as an exit -until it actually is)

-
-
-
-
-
-
-at_init()[source]
-

Called when object is recalled from cache.

-
- -
-
-at_object_creation()[source]
-

called when the object is first created.

-
- -
-
-open_wall()[source]
-

This method is called by the push button command once the puzzle -is solved. It opens the wall and sets a timer for it to reset -itself.

-
- -
-
-return_appearance(caller)[source]
-

This is called when someone looks at the wall. We need to echo the -current root positions.

-
- -
-
-at_after_traverse(traverser, source_location)[source]
-

This is called after we traversed this exit. Cleans up and resets -the puzzle.

-
- -
-
-at_failed_traverse(traverser)[source]
-

This is called if the account fails to pass the Exit.

-
- -
-
-reset()[source]
-

Called by tutorial world runner, or whenever someone successfully -traversed the Exit.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.DoesNotExist, evennia.objects.objects.DefaultExit.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.MultipleObjectsReturned, evennia.objects.objects.DefaultExit.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.CrumblingWall'
-
- -
-
-typename = 'CrumblingWall'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdAttack(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Attack the enemy. Commands:

-
-

stab <enemy> -slash <enemy> -parry

-
-

stab - (thrust) makes a lot of damage but is harder to hit with. -slash - is easier to land, but does not make as much damage. -parry - forgoes your attack but will make you harder to hit on next

-
-

enemy attack.

-
-
-
-key = 'attack'
-
- -
-
-aliases = ['slash', 'fight', 'stab', 'bash', 'kill', 'defend', 'chop', 'parry', 'pierce', 'thrust', 'hit']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Implements the stab

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdSetWeapon(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Holds the attack command.

-
-
-at_cmdset_creation()[source]
-

called at first object creation.

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.CmdSetWeapon'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.Weapon(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject

-

This defines a bladed weapon.

-
-
Important attributes (set at creation):

hit - chance to hit (0-1) -parry - chance to parry (0-1) -damage - base damage given (modified by hit success and

-
-

type of attack) (0-10)

-
-
-
-
-
-at_object_creation()[source]
-

Called at first creation of the object

-
- -
-
-reset()[source]
-

When reset, the weapon is simply deleted, unless it has a place -to return to.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.Weapon'
-
- -
-
-typename = 'Weapon'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdGetWeapon(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-
-
Usage:

get weapon

-
-
-

This will try to obtain a weapon from the container.

-
-
-key = 'get weapon'
-
- -
-
-aliases = []
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Get a weapon from the container. It will -itself handle all messages.

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.CmdSetWeaponRack(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

The cmdset for the rack.

-
-
-key = 'weaponrack_cmdset'
-
- -
-
-at_cmdset_creation()[source]
-

Called at first creation of cmdset

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.CmdSetWeaponRack'
-
- -
- -
-
-class evennia.contrib.tutorial_world.objects.WeaponRack(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject

-

This object represents a weapon store. When people use the -“get weapon” command on this rack, it will produce one -random weapon from among those registered to exist -on it. This will also set a property on the character -to make sure they can’t get more than one at a time.

-
-
Attributes to set on this object:
-
available_weapons: list of prototype-keys from

WEAPON_PROTOTYPES, the weapons available in this rack.

-
-
no_more_weapons_msg - error message to return to accounts

who already got one weapon from the rack and tries to -grab another one.

-
-
-
-
-
-
-at_object_creation()[source]
-

called at creation

-
- -
-
-produce_weapon(caller)[source]
-

This will produce a new weapon from the rack, -assuming the caller hasn’t already gotten one. When -doing so, the caller will get Tagged with the id -of this rack, to make sure they cannot keep -pulling weapons from it indefinitely.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.objects.TutorialObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.objects.WeaponRack'
-
- -
-
-typename = 'WeaponRack'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_world.rooms.html b/docs/0.9.5/api/evennia.contrib.tutorial_world.rooms.html deleted file mode 100644 index 141610760c..0000000000 --- a/docs/0.9.5/api/evennia.contrib.tutorial_world.rooms.html +++ /dev/null @@ -1,1225 +0,0 @@ - - - - - - - - - evennia.contrib.tutorial_world.rooms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.tutorial_world.rooms

-

Room Typeclasses for the TutorialWorld.

-

This defines special types of Rooms available in the tutorial. To keep -everything in one place we define them together with the custom -commands needed to control them. Those commands could also have been -in a separate module (e.g. if they could have been re-used elsewhere.)

-
-
-class evennia.contrib.tutorial_world.rooms.CmdTutorial(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Get help during the tutorial

-
-
Usage:

tutorial [obj]

-
-
-

This command allows you to get behind-the-scenes info -about an object or the current location.

-
-
-key = 'tutorial'
-
- -
-
-aliases = ['tut']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

All we do is to scan the current location for an Attribute -called tutorial_info and display that.

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdTutorialSetDetail(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

sets a detail on a room

-
-
Usage:

@detail <key> = <description> -@detail <key>;<alias>;… = description

-
-
-

Example

-

@detail walls = The walls are covered in … -@detail castle;ruin;tower = The distant ruin …

-

This sets a “detail” on the object this command is defined on -(TutorialRoom for this tutorial). This detail can be accessed with -the TutorialRoomLook command sitting on TutorialRoom objects (details -are set as a simple dictionary on the room). This is a Builder command.

-

We custom parse the key for the ;-separator in order to create -multiple aliases to the detail all at once.

-
-
-key = '@detail'
-
- -
-
-locks = 'cmd:perm(Builder)'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

All this does is to check if the object has -the set_detail method and uses it.

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:perm(Builder)'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdTutorialLook(**kwargs)[source]
-

Bases: evennia.commands.default.general.CmdLook

-

looks at the room and on details

-
-
Usage:

look <obj> -look <room detail> -look *<account>

-
-
-

Observes your location, details at your location or objects -in your vicinity.

-

Tutorial: This is a child of the default Look command, that also -allows us to look at “details” in the room. These details are -things to examine and offers some extra description without -actually having to be actual database objects. It uses the -return_detail() hook on TutorialRooms for this.

-
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Handle the looking. This is a copy of the default look -code except for adding in the details.

-
- -
-
-aliases = ['l', 'ls']
-
- -
-
-key = 'look'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdTutorialGiveUp(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Give up the tutorial-world quest and return to Limbo, the start room of the -server.

-
-
-key = 'give up'
-
- -
-
-aliases = ['abort']
-
- -
-
-func()[source]
-

This is the hook function that actually does all the work. It is called -by the cmdhandler right after self.parser() finishes, and so has access -to all the variables defined therein.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.TutorialRoomCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Implements the simple tutorial cmdset. This will overload the look -command in the default CharacterCmdSet since it has a higher -priority (ChracterCmdSet has prio 0)

-
-
-key = 'tutorial_cmdset'
-
- -
-
-priority = 1
-
- -
-
-at_cmdset_creation()[source]
-

add the tutorial-room commands

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.TutorialRoomCmdSet'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.TutorialRoom(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultRoom

-

This is the base room type for all rooms in the tutorial world. -It defines a cmdset on itself for reading tutorial info about the location.

-
-
-at_object_creation()[source]
-

Called when room is first created

-
- -
-
-at_object_receive(new_arrival, source_location)[source]
-

When an object enter a tutorial room we tell other objects in -the room about it by trying to call a hook on them. The Mob object -uses this to cheaply get notified of enemies without having -to constantly scan for them.

-
-
Parameters
-
    -
  • new_arrival (Object) – the object that just entered this room.

  • -
  • source_location (Object) – the previous location of new_arrival.

  • -
-
-
-
- -
-
-return_detail(detailkey)[source]
-

This looks for an Attribute “obj_details” and possibly -returns the value of it.

-
-
Parameters
-

detailkey (str) – The detail being looked at. This is -case-insensitive.

-
-
-
- -
-
-set_detail(detailkey, description)[source]
-

This sets a new detail, using an Attribute “details”.

-
-
Parameters
-
    -
  • 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.

  • -
-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.TutorialRoom'
-
- -
-
-typename = 'TutorialRoom'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.WeatherRoom(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom

-

This should probably better be called a rainy room…

-

This sets up an outdoor room typeclass. At irregular intervals, -the effects of weather will show in the room. Outdoor rooms should -inherit from this.

-
-
-at_object_creation()[source]
-

Called when object is first created. -We set up a ticker to update this room regularly.

-

Note that we could in principle also use a Script to manage -the ticking of the room; the TickerHandler works fine for -simple things like this though.

-
- -
-
-update_weather(*args, **kwargs)[source]
-

Called by the tickerhandler at regular intervals. Even so, we -only update 20% of the time, picking a random weather message -when we do. The tickerhandler requires that this hook accepts -any arguments and keyword arguments (hence the *args, **kwargs -even though we don’t actually use them in this example)

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.WeatherRoom'
-
- -
-
-typename = 'WeatherRoom'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdEvenniaIntro(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Start the Evennia intro wizard.

-
-
Usage:

intro

-
-
-
-
-key = 'intro'
-
- -
-
-func()[source]
-

This is the actual executing part of the command. It is -called directly after self.parse(). See the docstring of this -module for which object properties are available (beyond those -set in self.parse())

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdSetEvenniaIntro(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-
-
-key = 'Evennia Intro StartSet'
-
- -
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.CmdSetEvenniaIntro'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.IntroRoom(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom

-

Intro room

-
-
properties to customize:

char_health - integer > 0 (default 20)

-
-
-
-
-at_object_creation()[source]
-

Called when the room is first created.

-
- -
-
-at_object_receive(character, source_location)[source]
-

Assign properties on characters

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.IntroRoom'
-
- -
-
-typename = 'IntroRoom'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdEast(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Go eastwards across the bridge.

-
-
Tutorial info:
-

This command relies on the caller having two Attributes -(assigned by the room when entering):

-
-
    -
  • east_exit: a unique name or dbref to the room to go to -when exiting east.

  • -
  • west_exit: a unique name or dbref to the room to go to -when exiting west.

  • -
-
-
-
-
The room must also have the following Attributes
    -
  • tutorial_bridge_posistion: the current position on -on the bridge, 0 - 4.

  • -
-
-
-
-
-
-
-key = 'east'
-
- -
-
-aliases = ['e']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

move one step eastwards

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdWest(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Go westwards across the bridge.

-
-
Tutorial info:

This command relies on the caller having two Attributes -(assigned by the room when entering):

-
-
    -
  • east_exit: a unique name or dbref to the room to go to -when exiting east.

  • -
  • west_exit: a unique name or dbref to the room to go to -when exiting west.

  • -
-
-
-
The room must also have the following property:
    -
  • tutorial_bridge_posistion: the current position on -on the bridge, 0 - 4.

  • -
-
-
-
-
-
-
-key = 'west'
-
- -
-
-aliases = ['w']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

move one step westwards

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdLookBridge(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

looks around at the bridge.

-
-
Tutorial info:

This command assumes that the room has an Attribute -“fall_exit”, a unique name or dbref to the place they end upp -if they fall off the bridge.

-
-
-
-
-key = 'look'
-
- -
-
-aliases = ['l']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Looking around, including a chance to fall.

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdBridgeHelp(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Overwritten help command while on the bridge.

-
-
-key = 'help'
-
- -
-
-aliases = ['h', '?']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorial world'
-
- -
-
-func()[source]
-

Implements the command.

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.BridgeCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

This groups the bridge commands. We will store it on the room.

-
-
-key = 'Bridge commands'
-
- -
-
-priority = 2
-
- -
-
-at_cmdset_creation()[source]
-

Called at first cmdset creation

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.BridgeCmdSet'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.BridgeRoom(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.rooms.WeatherRoom

-

The bridge room implements an unsafe bridge. It also enters the player into -a state where they get new commands so as to try to cross the bridge.

-
-

We want this to result in the account getting a special set of -commands related to crossing the bridge. The result is that it -will take several steps to cross it, despite it being represented -by only a single room.

-

We divide the bridge into steps:

-
-
-
self.db.west_exit - - | - - self.db.east_exit

0 1 2 3 4

-
-
-
-

The position is handled by a variable stored on the character -when entering and giving special move commands will -increase/decrease the counter until the bridge is crossed.

-

We also has self.db.fall_exit, which points to a gathering -location to end up if we happen to fall off the bridge (used by -the CmdLookBridge command).

-
-
-
-at_object_creation()[source]
-

Setups the room

-
- -
-
-update_weather(*args, **kwargs)[source]
-

This is called at irregular intervals and makes the passage -over the bridge a little more interesting.

-
- -
-
-at_object_receive(character, source_location)[source]
-

This hook is called by the engine whenever the player is moved -into this room.

-
- -
-
-at_object_leave(character, target_location)[source]
-

This is triggered when the player leaves the bridge room.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.rooms.WeatherRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.rooms.WeatherRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.BridgeRoom'
-
- -
-
-typename = 'BridgeRoom'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdLookDark(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Look around in darkness

-
-
Usage:

look

-
-
-

Look around in the darkness, trying -to find something.

-
-
-key = 'look'
-
- -
-
-aliases = ['search', 'fiddle', 'feel', 'feel around', 'l']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Implement the command.

-

This works both as a look and a search command; there is a -random chance of eventually finding a light source.

-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdDarkHelp(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Help command for the dark state.

-
-
-key = 'help'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_category = 'tutorialworld'
-
- -
-
-func()[source]
-

Replace the the help command with a not-so-useful help

-
- -
-
-aliases = []
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.CmdDarkNoMatch(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

This is a system command. Commands with special keys are used to -override special sitations in the game. The CMD_NOMATCH is used -when the given command is not found in the current command set (it -replaces Evennia’s default behavior or offering command -suggestions)

-
-
-key = '__nomatch_command'
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-func()[source]
-

Implements the command.

-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.DarkCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Groups the commands of the dark room together. We also import the -default say command here so that players can still talk in the -darkness.

-

We give the cmdset the mergetype “Replace” to make sure it -completely replaces whichever command set it is merged onto -(usually the default cmdset)

-
-
-key = 'darkroom_cmdset'
-
- -
-
-mergetype = 'Replace'
-
- -
-
-priority = 2
-
- -
-
-at_cmdset_creation()[source]
-

populate the cmdset.

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.DarkCmdSet'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.DarkRoom(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom

-

A dark room. This tries to start the DarkState script on all -objects entering. The script is responsible for making sure it is -valid (that is, that there is no light source shining in the room).

-

The is_lit Attribute is used to define if the room is currently lit -or not, so as to properly echo state changes.

-

Since this room (in the tutorial) is meant as a sort of catch-all, -we also make sure to heal characters ending up here, since they -may have been beaten up by the ghostly apparition at this point.

-
-
-at_object_creation()[source]
-

Called when object is first created.

-
- -
-
-at_init()[source]
-

Called when room is first recached (such as after a reload)

-
- -
-
-check_light_state(exclude=None)[source]
-

This method checks if there are any light sources in the room. -If there isn’t it makes sure to add the dark cmdset to all -characters in the room. It is called whenever characters enter -the room and also by the Light sources when they turn on.

-
-
Parameters
-

exclude (Object) – An object to not include in the light check.

-
-
-
- -
-
-at_object_receive(obj, source_location)[source]
-

Called when an object enters the room.

-
- -
-
-at_object_leave(obj, target_location)[source]
-

In case people leave with the light, we make sure to clear the -DarkCmdSet if necessary. This also works if they are -teleported away.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.DarkRoom'
-
- -
-
-typename = 'DarkRoom'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.TeleportRoom(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom

-

Teleporter - puzzle room.

-
-
Important attributes (set at creation):

puzzle_key - which attr to look for on character -puzzle_value - what char.db.puzzle_key must be set to -success_teleport_to - where to teleport in case if success -success_teleport_msg - message to echo while teleporting to success -failure_teleport_to - where to teleport to in case of failure -failure_teleport_msg - message to echo while teleporting to failure

-
-
-
-
-at_object_creation()[source]
-

Called at first creation

-
- -
-
-at_object_receive(character, source_location)[source]
-

This hook is called by the engine whenever the player is moved into -this room.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.TeleportRoom'
-
- -
-
-typename = 'TeleportRoom'
-
- -
- -
-
-class evennia.contrib.tutorial_world.rooms.OutroRoom(*args, **kwargs)[source]
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom

-

Outro room.

-

Called when exiting the tutorial, cleans the -character of tutorial-related attributes.

-
-
-at_object_creation()[source]
-

Called when the room is first created.

-
- -
-
-at_object_receive(character, source_location)[source]
-

Do cleanup.

-
- -
-
-at_object_leave(character, destination)[source]
-

Called just before an object leaves from inside this object

-
-
Parameters
-
    -
  • moved_obj (Object) – The object leaving

  • -
  • target_location (Object) – Where moved_obj is going.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.contrib.tutorial_world.rooms.TutorialRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.tutorial_world.rooms.OutroRoom'
-
- -
-
-typename = 'OutroRoom'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.unixcommand.html b/docs/0.9.5/api/evennia.contrib.unixcommand.html deleted file mode 100644 index 711ac1af1d..0000000000 --- a/docs/0.9.5/api/evennia.contrib.unixcommand.html +++ /dev/null @@ -1,393 +0,0 @@ - - - - - - - - - evennia.contrib.unixcommand — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.unixcommand

-

Unix-like Command style parent

-

Evennia contribution, Vincent Le Geoff 2017

-

This module contains a command class that allows for unix-style command syntax in-game, using -–options, positional arguments and stuff like -n 10 etc similarly to a unix command. It might not -the best syntax for the average player but can be really useful for builders when they need to have -a single command do many things with many options. It uses the ArgumentParser from Python’s standard -library under the hood.

-

To use, inherit UnixCommand from this module from your own commands. You need -to override two methods:

-
    -
  • -
    The init_parser method, which adds options to the parser. Note that you should normally

    not override the normal parse method when inheriting from UnixCommand.

    -
    -
    -
  • -
  • The func method, called to execute the command once parsed (like any Command).

  • -
-

Here’s a short example:

-
class CmdPlant(UnixCommand):
-
-    '''
-    Plant a tree or plant.
-
-    This command is used to plant something in the room you are in.
-
-    Examples:
-      plant orange -a 8
-      plant strawberry --hidden
-      plant potato --hidden --age 5
-
-    '''
-
-    key = "plant"
-
-    def init_parser(self):
-        "Add the arguments to the parser."
-        # 'self.parser' inherits **argparse.ArgumentParser**
-        self.parser.add_argument("key",
-                help="the key of the plant to be planted here")
-        self.parser.add_argument("-a", "--age", type=int,
-                default=1, help="the age of the plant to be planted")
-        self.parser.add_argument("--hidden", action="store_true",
-                help="should the newly-planted plant be hidden to players?")
-
-    def func(self):
-        "func is called only if the parser succeeded."
-        # 'self.opts' contains the parsed options
-        key = self.opts.key
-        age = self.opts.age
-        hidden = self.opts.hidden
-        self.msg("Going to plant '{}', age={}, hidden={}.".format(
-                key, age, hidden))
-
-
-

To see the full power of argparse and the types of supported options, visit -[the documentation of argparse](https://docs.python.org/2/library/argparse.html).

-
-
-exception evennia.contrib.unixcommand.ParseError[source]
-

Bases: Exception

-

An error occurred during parsing.

-
- -
-
-class evennia.contrib.unixcommand.UnixCommandParser(prog, description='', epilog='', command=None, **kwargs)[source]
-

Bases: argparse.ArgumentParser

-

A modifier command parser for unix commands.

-

This parser is used to replace argparse.ArgumentParser. It -is aware of the command calling it, and can more easily report to -the caller. Some features (like the “brutal exit” of the original -parser) are disabled or replaced. This parser is used by UnixCommand -and creating one directly isn’t recommended nor necessary. Even -adding a sub-command will use this replaced parser automatically.

-
-
-__init__(prog, description='', epilog='', command=None, **kwargs)[source]
-

Build a UnixCommandParser with a link to the command using it.

-
-
Parameters
-
    -
  • prog (str) – the program name (usually the command key).

  • -
  • description (str) – a very brief line to show in the usage text.

  • -
  • epilog (str) – the epilog to show below options.

  • -
  • command (Command) – the command calling the parser.

  • -
-
-
Keyword Arguments
-
    -
  • keyword arguments are directly sent to (Additional) –

  • -
  • You will find them on the (argparse.ArgumentParser.) –

  • -
  • documentation](https ([parser's) – //docs.python.org/2/library/argparse.html).

  • -
-
-
-
-

Note

-

It’s doubtful you would need to create this parser manually. -The UnixCommand does that automatically. If you create -sub-commands, this class will be used.

-
-
- -
-
-format_usage()[source]
-

Return the usage line.

-
-

Note

-

This method is present to return the raw-escaped usage line, -in order to avoid unintentional color codes.

-
-
- -
-
-format_help()[source]
-

Return the parser help, including its epilog.

-
-

Note

-

This method is present to return the raw-escaped help, -in order to avoid unintentional color codes. Color codes -in the epilog (the command docstring) are supported.

-
-
- -
-
-print_usage(file=None)[source]
-

Print the usage to the caller.

-
-
Parameters
-

file (file-object) – not used here, the caller is used.

-
-
-
-

Note

-

This method will override argparse.ArgumentParser’s in order -to not display the help on stdout or stderr, but to the -command’s caller.

-
-
- -
-
-print_help(file=None)[source]
-

Print the help to the caller.

-
-
Parameters
-

file (file-object) – not used here, the caller is used.

-
-
-
-

Note

-

This method will override argparse.ArgumentParser’s in order -to not display the help on stdout or stderr, but to the -command’s caller.

-
-
- -
- -
-
-class evennia.contrib.unixcommand.HelpAction(option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)[source]
-

Bases: argparse.Action

-

Override the -h/–help action in the default parser.

-

Using the default -h/–help will call the exit function in different -ways, preventing the entire help message to be provided. Hence -this override.

-
- -
-
-class evennia.contrib.unixcommand.UnixCommand(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Unix-type commands, supporting short and long options.

-

This command syntax uses the Unix-style commands with short options -(-X) and long options (–something). The argparse module is -used to parse the command.

-

In order to use it, you should override two methods: -- init_parser: this method is called when the command is created.

-
-

It can be used to set options in the parser. self.parser -contains the argparse.ArgumentParser, so you can add arguments -here.

-
-
    -
  • func: this method is called to execute the command, but after -the parser has checked the arguments given to it are valid. -You can access the namespace of valid arguments in self.opts -at this point.

  • -
-

The help of UnixCommands is derived from the docstring, in a -slightly different way than usual: the first line of the docstring -is used to represent the program description (the very short -line at the top of the help message). The other lines below are -used as the program’s “epilog”, displayed below the options. It -means in your docstring, you don’t have to write the options. -They will be automatically provided by the parser and displayed -accordingly. The argparse module provides a default ‘-h’ or -‘–help’ option on the command. Typing |whelp commandname|n will -display the same as |wcommandname -h|n, though this behavior can -be changed.

-
-
-__init__(**kwargs)[source]
-

The lockhandler works the same as for objects. -optional kwargs will be set as properties on the Command at runtime, -overloading evential same-named class properties.

-
- -
-
-init_parser()[source]
-

Configure the argument parser, adding in options.

-
-

Note

-

This method is to be overridden in order to add options -to the argument parser. Use self.parser, which contains -the argparse.ArgumentParser. You can, for instance, -use its add_argument method.

-
-
- -
-
-func()[source]
-

Override to handle the command execution.

-
- -
-
-get_help(caller, cmdset)[source]
-

Return the help message for this command and this caller.

-
-
Parameters
-
    -
  • caller (Object or Player) – the caller asking for help on the command.

  • -
  • cmdset (CmdSet) – the command set (if you need additional commands).

  • -
-
-
Returns
-

docstring (str) – the help text to provide the caller for this command.

-
-
-
- -
-
-parse()[source]
-

Process arguments provided in self.args.

-
-

Note

-

You should not override this method. Consider overriding -init_parser instead.

-
-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'command'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.contrib.wilderness.html b/docs/0.9.5/api/evennia.contrib.wilderness.html deleted file mode 100644 index a76c822eeb..0000000000 --- a/docs/0.9.5/api/evennia.contrib.wilderness.html +++ /dev/null @@ -1,739 +0,0 @@ - - - - - - - - - evennia.contrib.wilderness — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.contrib.wilderness

-

Wilderness system

-

Evennia contrib - titeuf87 2017

-

This contrib provides a wilderness map. This is an area that can be huge where -the rooms are mostly similar, except for some small cosmetic changes like the -room name.

-

Usage:

-
-

This contrib does not provide any commands. Instead the @py command can be -used.

-

A wilderness map needs to created first. There can be different maps, all -with their own name. If no name is provided, then a default one is used. Internally, -the wilderness is stored as a Script with the name you specify. If you don’t -specify the name, a script named “default” will be created and used.

-

@py from evennia.contrib import wilderness; wilderness.create_wilderness()

-

Once created, it is possible to move into that wilderness map:

-

@py from evennia.contrib import wilderness; wilderness.enter_wilderness(me)

-

All coordinates used by the wilderness map are in the format of (x, y) -tuples. x goes from left to right and y goes from bottom to top. So (0, 0) -is the bottom left corner of the map.

-
-

Customisation:

-
-

The defaults, while useable, are meant to be customised. When creating a -new wilderness map it is possible to give a “map provider”: this is a -python object that is smart enough to create the map.

-

The default provider, WildernessMapProvider, just creates a grid area that -is unlimited in size. -This WildernessMapProvider can be subclassed to create more interesting -maps and also to customize the room/exit typeclass used.

-

There is also no command that allows players to enter the wilderness. This -still needs to be added: it can be a command or an exit, depending on your -needs.

-
-

Customisation example:

-
-

To give an example of how to customize, we will create a very simple (and -small) wilderness map that is shaped like a pyramid. The map will be -provided as a string: a “.” symbol is a location we can walk on.

-

Let’s create a file world/pyramid.py:

-
map_str = """
-     .
-    ...
-   .....
-  .......
-"""
-
-from evennia.contrib import wilderness
-
-class PyramidMapProvider(wilderness.WildernessMapProvider):
-
-    def is_valid_coordinates(self, wilderness, coordinates):
-        "Validates if these coordinates are inside the map"
-        x, y = coordinates
-        try:
-            lines = map_str.split("
-
-
-
-
“)
-
-

# The reverse is needed because otherwise the pyramid will be -# upside down -lines.reverse() -line = lines[y] -column = line[x] -return column == “.”

-
-
-
except IndexError:

return False

-
-
-
-
-
def get_location_name(self, coordinates):

“Set the location name” -x, y = coordinates -if y == 3:

-
-

return “Atop the pyramid.”

-
-
-
else:

return “Inside a pyramid.”

-
-
-
-
def at_prepare_room(self, coordinates, caller, room):

“Any other changes done to the room before showing it” -x, y = coordinates -desc = “This is a room in the pyramid.” -if y == 3 :

-
-

desc = “You can see far and wide from the top of the pyramid.”

-
-

room.db.desc = desc

-
-
-
-
-

Now we can use our new pyramid-shaped wilderness map. From inside Evennia we -create a new wilderness (with the name “default”) but using our new map provider:

-

@py from world import pyramid as p; p.wilderness.create_wilderness(mapprovider=p.PyramidMapProvider())

-

@py from evennia.contrib import wilderness; wilderness.enter_wilderness(me, coordinates=(4, 1))

-
-

Implementation details:

-
-

When a character moves into the wilderness, they get their own room. If -they move, instead of moving the character, the room changes to match the -new coordinates. -If a character meets another character in the wilderness, then their room -merges. When one of the character leaves again, they each get their own -separate rooms. -Rooms are created as needed. Unneeded rooms are stored away to avoid the -overhead cost of creating new rooms again in the future.

-
-
-
-evennia.contrib.wilderness.create_wilderness(name='default', mapprovider=None)[source]
-

Creates a new wilderness map. Does nothing if a wilderness map already -exists with the same name.

-
-
Parameters
-
    -
  • name (str, optional) – the name to use for that wilderness map

  • -
  • mapprovider (WildernessMap instance, optional) – an instance of a -WildernessMap class (or subclass) that will be used to provide the -layout of this wilderness map. If none is provided, the default -infinite grid map will be used.

  • -
-
-
-
- -
-
-evennia.contrib.wilderness.enter_wilderness(obj, coordinates=0, 0, name='default')[source]
-

Moves obj into the wilderness. The wilderness needs to exist first and the -provided coordinates needs to be valid inside that wilderness.

-
-
Parameters
-
    -
  • obj (object) – the object to move into the wilderness

  • -
  • coordinates (tuple), optional) – the coordinates to move obj to into -the wilderness. If not provided, defaults (0, 0)

  • -
  • name (str, optional) – name of the wilderness map, if not using the -default one

  • -
-
-
Returns
-

bool – True if obj successfully moved into the wilderness.

-
-
-
- -
-
-evennia.contrib.wilderness.get_new_coordinates(coordinates, direction)[source]
-

Returns the coordinates of direction applied to the provided coordinates.

-
-
Parameters
-
    -
  • coordinates – tuple of (x, y)

  • -
  • direction – a direction string (like “northeast”)

  • -
-
-
Returns
-

tuple – tuple of (x, y) coordinates

-
-
-
- -
-
-class evennia.contrib.wilderness.WildernessScript(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This is the main “handler” for the wilderness system: inside here the -coordinates of every item currently inside the wilderness is stored. This -script is responsible for creating rooms as needed and storing rooms away -into storage when they are not needed anymore.

-
-
-at_script_creation()[source]
-

Only called once, when the script is created. This is a default Evennia -hook.

-
- -
-
-property mapprovider
-

Shortcut property to the map provider.

-
-
Returns
-

MapProvider – the mapprovider used with this wilderness

-
-
-
- -
-
-property itemcoordinates
-

Returns a dictionary with the coordinates of every item inside this -wilderness map. The key is the item, the value are the coordinates as -(x, y) tuple.

-
-
Returns
-

{item – coordinates}

-
-
-
- -
-
-at_start()[source]
-

Called when the script is started and also after server reloads.

-
- -
-
-is_valid_coordinates(coordinates)[source]
-

Returns True if coordinates are valid (and can be travelled to). -Otherwise returns False

-
-
Parameters
-

coordinates (tuple) – coordinates as (x, y) tuple

-
-
Returns
-

bool – True if the coordinates are valid

-
-
-
- -
-
-get_obj_coordinates(obj)[source]
-

Returns the coordinates of obj in the wilderness.

-

Returns (x, y)

-
-
Parameters
-

obj (object) – an object inside the wilderness

-
-
Returns
-

tuple – (x, y) tuple of where obj is located

-
-
-
- -
-
-get_objs_at_coordinates(coordinates)[source]
-

Returns a list of every object at certain coordinates.

-

Imeplementation detail: this uses a naive iteration through every -object inside the wilderness which could cause slow downs when there -are a lot of objects in the map.

-
-
Parameters
-

coordinates (tuple) – a coordinate tuple like (x, y)

-
-
Returns
-

[Object, ] – list of Objects at coordinates

-
-
-
- -
-
-move_obj(obj, new_coordinates)[source]
-

Moves obj to new coordinates in this wilderness.

-
-
Parameters
-
    -
  • obj (object) – the object to move

  • -
  • new_coordinates (tuple) – tuple of (x, y) where to move obj to.

  • -
-
-
-
- -
-
-at_after_object_leave(obj)[source]
-

Called after an object left this wilderness map. Used for cleaning up.

-
-
Parameters
-

obj (object) – the object that left

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.wilderness.WildernessScript'
-
- -
-
-typename = 'WildernessScript'
-
- -
- -
-
-class evennia.contrib.wilderness.WildernessRoom(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultRoom

-

This is a single room inside the wilderness. This room provides a “view” -into the wilderness map. When an account moves around, instead of going to -another room as with traditional rooms, they stay in the same room but the -room itself changes to display another area of the wilderness.

-
-
-property wilderness
-

Shortcut property to the wilderness script this room belongs to.

-
-
Returns
-

WildernessScript – the WildernessScript attached to this room

-
-
-
- -
-
-property location_name
-

Returns the name of the wilderness at this room’s coordinates.

-
-
Returns
-

name (str)

-
-
-
- -
-
-property coordinates
-

Returns the coordinates of this room into the wilderness.

-
-
Returns
-

tuple

-
-
(x, y) coordinates of where this room is inside the

wilderness.

-
-
-

-
-
-
- -
-
-at_object_receive(moved_obj, source_location)[source]
-

Called after an object has been moved into this object. This is a -default Evennia hook.

-
-
Parameters
-
    -
  • moved_obj (Object) – The object moved into this one.

  • -
  • source_location (Object) – Where moved_obj came from.

  • -
-
-
-
- -
-
-at_object_leave(moved_obj, target_location)[source]
-

Called just before an object leaves from inside this object. This is a -default Evennia hook.

-
-
Parameters
-
    -
  • moved_obj (Object) – The object leaving

  • -
  • target_location (Object) – Where moved_obj is going.

  • -
-
-
-
- -
-
-set_active_coordinates(new_coordinates, obj)[source]
-

Changes this room to show the wilderness map from other coordinates.

-
-
Parameters
-
    -
  • new_coordinates (tuple) – coordinates as tuple of (x, y)

  • -
  • obj (Object) – the object that moved into this room and caused the -coordinates to change

  • -
-
-
-
- -
-
-get_display_name(looker, **kwargs)[source]
-

Displays the name of the object in a viewer-aware manner.

-
-
Parameters
-

looker (TypedObject) – The object or account that is looking -at/getting inforamtion for this object.

-
-
Returns
-

name (str)

-
-
A string containing the name of the object,

including the DBREF if this user is privileged to control -said object and also its coordinates into the wilderness map.

-
-
-

-
-
-

Notes

-

This function could be extended to change how object names -appear to users in character, but be wary. This function -does not change an object’s keys or aliases when -searching, and is expected to produce something useful for -builders.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultRoom.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultRoom.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.wilderness.WildernessRoom'
-
- -
-
-typename = 'WildernessRoom'
-
- -
- -
-
-class evennia.contrib.wilderness.WildernessExit(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultExit

-

This is an Exit object used inside a WildernessRoom. Instead of changing -the location of an Object traversing through it (like a traditional exit -would do) it changes the coordinates of that traversing Object inside -the wilderness map.

-
-
-property wilderness
-

Shortcut property to the wilderness script.

-
-
Returns
-

WildernessScript – the WildernessScript attached to this exit’s room

-
-
-
- -
-
-property mapprovider
-

Shortcut property to the map provider.

-
-
Returns
-

MapProvider object

-
-
the mapprovider object used with this

wilderness map.

-
-
-

-
-
-
- -
-
-at_traverse_coordinates(traversing_object, current_coordinates, new_coordinates)[source]
-

Called when an object wants to travel from one place inside the -wilderness to another place inside the wilderness.

-

If this returns True, then the traversing can happen. Otherwise it will -be blocked.

-

This method is similar how the at_traverse works on normal exits.

-
-
Parameters
-
    -
  • traversing_object (Object) – The object doing the travelling.

  • -
  • current_coordinates (tuple) – (x, y) coordinates where -traversing_object currently is.

  • -
  • new_coordinates (tuple) – (x, y) coordinates of where -traversing_object wants to travel to.

  • -
-
-
Returns
-

bool – True if traversing_object is allowed to traverse

-
-
-
- -
-
-at_traverse(traversing_object, target_location)[source]
-

This implements the actual traversal. The traverse lock has -already been checked (in the Exit command) at this point.

-
-
Parameters
-
    -
  • traversing_object (Object) – Object traversing us.

  • -
  • target_location (Object) – Where target is going.

  • -
-
-
Returns
-

bool – True if the traverse is allowed to happen

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultExit.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultExit.MultipleObjectsReturned

-
- -
-
-path = 'evennia.contrib.wilderness.WildernessExit'
-
- -
-
-typename = 'WildernessExit'
-
- -
- -
-
-class evennia.contrib.wilderness.WildernessMapProvider[source]
-

Bases: object

-

Default Wilderness Map provider.

-

This is a simple provider that just creates an infinite large grid area.

-
-
-room_typeclass
-

alias of WildernessRoom

-
- -
-
-exit_typeclass
-

alias of WildernessExit

-
- -
-
-is_valid_coordinates(wilderness, coordinates)[source]
-

Returns True if coordinates is valid and can be walked to.

-
-
Parameters
-
    -
  • wilderness – the wilderness script

  • -
  • coordinates (tuple) – the coordinates to check as (x, y) tuple.

  • -
-
-
Returns
-

bool – True if the coordinates are valid

-
-
-
- -
-
-get_location_name(coordinates)[source]
-

Returns a name for the position at coordinates.

-
-
Parameters
-

coordinates (tuple) – the coordinates as (x, y) tuple.

-
-
Returns
-

name (str)

-
-
-
- -
-
-at_prepare_room(coordinates, caller, room)[source]
-

Called when a room gets activated for certain coordinates. This happens -after every object is moved in it. -This can be used to set a custom room desc for instance or run other -customisations on the room.

-
-
Parameters
-
    -
  • coordinates (tuple) – the coordinates as (x, y) where room is -located at

  • -
  • caller (Object) – the object that moved into this room

  • -
  • room (WildernessRoom) – the room object that will be used at that -wilderness location

  • -
-
-
-

Example

-

An example use of this would to plug in a randomizer to show different -descriptions for different coordinates, or place a treasure at a special -coordinate.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.help.admin.html b/docs/0.9.5/api/evennia.help.admin.html deleted file mode 100644 index 0f2ad65135..0000000000 --- a/docs/0.9.5/api/evennia.help.admin.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - - - - evennia.help.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.help.admin

-

This defines how to edit help entries in Admin.

-
-
-class evennia.help.admin.HelpTagInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.TagInline

-
-
-model
-

alias of evennia.help.models.HelpEntry_db_tags

-
- -
-
-related_field = 'helpentry'
-
- -
-
-property media
-
- -
- -
-
-class evennia.help.admin.HelpEntryForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]
-

Bases: django.forms.models.ModelForm

-

Defines how to display the help entry

-
-
-class Meta[source]
-

Bases: object

-
-
-model
-

alias of evennia.help.models.HelpEntry

-
- -
-
-fields = '__all__'
-
- -
- -
-
-base_fields = {'db_entrytext': <django.forms.fields.CharField object>, 'db_help_category': <django.forms.fields.CharField object>, 'db_key': <django.forms.fields.CharField object>, 'db_lock_storage': <django.forms.fields.CharField object>, 'db_staff_only': <django.forms.fields.BooleanField object>, 'db_tags': <django.forms.models.ModelMultipleChoiceField object>}
-
- -
-
-declared_fields = {'db_help_category': <django.forms.fields.CharField object>, 'db_lock_storage': <django.forms.fields.CharField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.help.admin.HelpEntryAdmin(model, admin_site)[source]
-

Bases: django.contrib.admin.options.ModelAdmin

-

Sets up the admin manaager for help entries

-
-
-inlines = [<class 'evennia.help.admin.HelpTagInline'>]
-
- -
-
-list_display = ('id', 'db_key', 'db_help_category', 'db_lock_storage')
-
- -
- -
- -
-
-search_fields = ['^db_key', 'db_entrytext']
-
- -
-
-ordering = ['db_help_category', 'db_key']
-
- -
-
-save_as = True
-
- -
-
-save_on_top = True
-
- -
- -
- -
-
-form
-

alias of HelpEntryForm

-
- -
-
-fieldsets = ((None, {'fields': (('db_key', 'db_help_category'), 'db_entrytext', 'db_lock_storage'), 'description': 'Sets a Help entry. Set lock to <i>view:all()</I> unless you want to restrict it.'}),)
-
- -
-
-property media
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.help.html b/docs/0.9.5/api/evennia.help.html deleted file mode 100644 index db79a5546d..0000000000 --- a/docs/0.9.5/api/evennia.help.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - evennia.help — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.help

-

This sub-package defines the help system of Evennia. It is pretty -simple, mainly consisting of a database model to hold help entries. -The auto-cmd-help is rather handled by the default ‘help’ command -itself.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.help.manager.html b/docs/0.9.5/api/evennia.help.manager.html deleted file mode 100644 index 3d13148623..0000000000 --- a/docs/0.9.5/api/evennia.help.manager.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - evennia.help.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.help.manager

-

Custom manager for HelpEntry objects.

-
-
-class evennia.help.manager.HelpEntryManager(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.managers.TypedObjectManager

-

This HelpEntryManager implements methods for searching -and manipulating HelpEntries directly from the database.

-

These methods will all return database objects -(or QuerySets) directly.

-

Evennia-specific: -find_topicmatch -find_apropos -find_topicsuggestions -find_topics_with_category -all_to_category -search_help (equivalent to evennia.search_helpentry)

-
-
-find_topicmatch(topicstr, exact=False)[source]
-

Searches for matching topics or aliases based on player’s -input.

-
-
Parameters
-
    -
  • topcistr (str) – Help topic to search for.

  • -
  • exact (bool, optional) – Require exact match -(non-case-sensitive). If False (default), match -sub-parts of the string.

  • -
-
-
Returns
-

matches (HelpEntries) – Query results.

-
-
-
- -
-
-find_apropos(topicstr)[source]
-

Do a very loose search, returning all help entries containing -the search criterion in their titles.

-
-
Parameters
-

topicstr (str) – Search criterion.

-
-
Returns
-

matches (HelpEntries) – Query results.

-
-
-
- -
-
-find_topicsuggestions(topicstr)[source]
-

Do a fuzzy match, preferably within the category of the -current topic.

-
-
Parameters
-

topicstr (str) – Search criterion.

-
-
Returns
-

matches (Helpentries) – Query results.

-
-
-
- -
-
-find_topics_with_category(help_category)[source]
-

Search topics having a particular category.

-
-
Parameters
-

help_category (str) – Category query criterion.

-
-
Returns
-

matches (HelpEntries) – Query results.

-
-
-
- -
-
-get_all_topics()[source]
-

Get all topics.

-
-
Returns
-

all (HelpEntries) – All topics.

-
-
-
- -
-
-get_all_categories()[source]
-

Return all defined category names with at least one topic in -them.

-
-
Returns
-

matches (list)

-
-
Unique list of category names across all

topics.

-
-
-

-
-
-
- -
-
-all_to_category(default_category)[source]
-

Shifts all help entries in database to default_category. This -action cannot be reverted. It is used primarily by the engine -when importing a default help database, making sure this ends -up in one easily separated category.

-
-
Parameters
-

default_category (str) – Category to move entries to.

-
-
-
- -
-
-search_help(ostring, help_category=None)[source]
-

Retrieve a search entry object.

-
-
Parameters
-
    -
  • ostring (str) – The help topic to look for.

  • -
  • category (str) – Limit the search to a particular help topic

  • -
-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.help.models.html b/docs/0.9.5/api/evennia.help.models.html deleted file mode 100644 index f46b1b6a44..0000000000 --- a/docs/0.9.5/api/evennia.help.models.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - - - - - - evennia.help.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.help.models

-

Models for the help system.

-

The database-tied help system is only half of Evennia’s help -functionality, the other one being the auto-generated command help -that is created on the fly from each command’s __doc__ string. The -persistent database system defined here is intended for all other -forms of help that do not concern commands, like information about the -game world, policy info, rules and similar.

-
-
-class evennia.help.models.HelpEntry(*args, **kwargs)[source]
-

Bases: evennia.utils.idmapper.models.SharedMemoryModel

-

A generic help entry.

-
-
An HelpEntry object has the following properties defined:

key - main name of entry -help_category - which category entry belongs to (defaults to General) -entrytext - the actual help text -permissions - perm strings

-
-
Method:

access

-
-
-
-
-db_key
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_help_category
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_entrytext
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_lock_storage
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_tags
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_staff_only
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-objects = <evennia.help.manager.HelpEntryManager object>
-
- -
-
-locks[source]
-
- -
-
-tags[source]
-
- -
-
-aliases[source]
-
- -
-
-access(accessing_obj, access_type='read', default=False)[source]
-

Determines if another object has permission to access. -accessing_obj - object trying to access this one -access_type - type of access sought -default - what to return if no lock of access_type was found

-
- -
-
-web_get_admin_url()[source]
-

Returns the URI path for the Django Admin page for this object.

-

ex. Account#1 = ‘/admin/accounts/accountdb/1/change/’

-
-
Returns
-

path (str) – URI path to Django Admin page for object.

-
-
-
- -
-
-classmethod web_get_create_url()[source]
-

Returns the URI path for a View that allows users to create new -instances of this object.

-

ex. Chargen = ‘/characters/create/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-create’ would be referenced by this method.

-

ex. -url(r’characters/create/’, ChargenView.as_view(), name=’character-create’)

-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can create new objects is the -developer’s responsibility.

-
-
Returns
-

path (str) – URI path to object creation page, if defined.

-
-
-
- -
-
-web_get_detail_url()[source]
-

Returns the URI path for a View that allows users to view details for -this object.

-

ex. Oscar (Character) = ‘/characters/oscar/1/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-detail’ would be referenced by this method.

-

ex. -url(r’characters/(?P<slug>[wd-]+)/(?P<pk>[0-9]+)/$’,

-
-

CharDetailView.as_view(), name=’character-detail’)

-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can view this object is the developer’s -responsibility.

-
-
Returns
-

path (str) – URI path to object detail page, if defined.

-
-
-
- -
-
-web_get_update_url()[source]
-

Returns the URI path for a View that allows users to update this -object.

-

ex. Oscar (Character) = ‘/characters/oscar/1/change/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-update’ would be referenced by this method.

-

ex. -url(r’characters/(?P<slug>[wd-]+)/(?P<pk>[0-9]+)/change/$’,

-
-

CharUpdateView.as_view(), name=’character-update’)

-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can modify objects is the developer’s -responsibility.

-
-
Returns
-

path (str) – URI path to object update page, if defined.

-
-
-
- -
-
-web_get_delete_url()[source]
-

Returns the URI path for a View that allows users to delete this object.

-

ex. Oscar (Character) = ‘/characters/oscar/1/delete/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-detail’ would be referenced by this method.

-

ex. -url(r’characters/(?P<slug>[wd-]+)/(?P<pk>[0-9]+)/delete/$’,

-
-

CharDeleteView.as_view(), name=’character-delete’)

-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can delete this object is the developer’s -responsibility.

-
-
Returns
-

path (str) – URI path to object deletion page, if defined.

-
-
-
- -
-
-get_absolute_url()
-

Returns the URI path for a View that allows users to view details for -this object.

-

ex. Oscar (Character) = ‘/characters/oscar/1/’

-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-detail’ would be referenced by this method.

-

ex. -url(r’characters/(?P<slug>[wd-]+)/(?P<pk>[0-9]+)/$’,

-
-

CharDetailView.as_view(), name=’character-detail’)

-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can view this object is the developer’s -responsibility.

-
-
Returns
-

path (str) – URI path to object detail page, if defined.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-property entrytext
-

A wrapper for getting database field db_entrytext.

-
- -
-
-property help_category
-

A wrapper for getting database field db_help_category.

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-property key
-

A wrapper for getting database field db_key.

-
- -
-
-property lock_storage
-

A wrapper for getting database field db_lock_storage.

-
- -
-
-path = 'evennia.help.models.HelpEntry'
-
- -
-
-property staff_only
-

A wrapper for getting database field db_staff_only.

-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.html b/docs/0.9.5/api/evennia.html deleted file mode 100644 index 1c3725e917..0000000000 --- a/docs/0.9.5/api/evennia.html +++ /dev/null @@ -1,500 +0,0 @@ - - - - - - - - - evennia — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia

-

Evennia MU* creation system.

-

Online manual and API docs are found at http://www.evennia.com.

-

Flat-API shortcut names:

-
    -
  • evennia.ANSIString

  • -
  • evennia.AccountDB

  • -
  • evennia.CHANNEL_HANDLER

  • -
  • evennia.ChannelDB

  • -
  • evennia.CmdSet

  • -
  • evennia.Command

  • -
  • evennia.DefaultAccount

  • -
  • evennia.DefaultChannel

  • -
  • evennia.DefaultCharacter

  • -
  • evennia.DefaultExit

  • -
  • evennia.DefaultGuest

  • -
  • evennia.DefaultObject

  • -
  • evennia.DefaultRoom

  • -
  • evennia.DefaultScript

  • -
  • evennia.EvEditor

  • -
  • evennia.EvForm

  • -
  • evennia.EvMenu

  • -
  • evennia.EvMore

  • -
  • evennia.EvTable

  • -
  • evennia.GLOBAL_SCRIPTS

  • -
  • evennia.InterruptCommand

  • -
  • evennia.MONITOR_HANDLER

  • -
  • evennia.Msg

  • -
  • evennia.OPTION_CLASSES

  • -
  • evennia.ObjectDB

  • -
  • evennia.SESSION_HANDLER

  • -
  • evennia.ScriptDB

  • -
  • evennia.TASK_HANDLER

  • -
  • evennia.TICKER_HANDLER

  • -
  • evennia.ansi

  • -
  • evennia.contrib

  • -
  • evennia.create_account

  • -
  • evennia.create_channel

  • -
  • evennia.create_help_entry

  • -
  • evennia.create_message

  • -
  • evennia.create_object

  • -
  • evennia.create_script

  • -
  • evennia.default_cmds

  • -
  • evennia.gametime

  • -
  • evennia.inputhandler

  • -
  • evennia.lockfuncs

  • -
  • evennia.logger

  • -
  • evennia.managers

  • -
  • evennia.search_account

  • -
  • evennia.search_channel

  • -
  • evennia.search_help

  • -
  • evennia.search_message

  • -
  • evennia.search_object

  • -
  • evennia.search_script

  • -
  • evennia.search_tag

  • -
  • evennia.set_trace

  • -
  • evennia.settings

  • -
  • evennia.signals

  • -
  • evennia.spawn

  • -
  • evennia.syscmdkeys

  • -
-
-
-evennia.set_trace(term_size=140, 80, debugger='auto')[source]
-

Helper function for running a debugger inside the Evennia event loop.

-
-
Parameters
-
    -
  • term_size (tuple, optional) – Only used for Pudb and defines the size of the terminal -(width, height) in number of characters.

  • -
  • debugger (str, optional) – One of ‘auto’, ‘pdb’ or ‘pudb’. Pdb is the standard debugger. Pudb -is an external package with a different, more ‘graphical’, ncurses-based UI. With -‘auto’, will use pudb if possible, otherwise fall back to pdb. Pudb is available through -pip install pudb.

  • -
-
-
-

Notes

-

To use:

-
    -
  1. add this to a line to act as a breakpoint for entering the debugger:

    -
    -

    from evennia import set_trace; set_trace()

    -
    -
  2. -
  3. restart evennia in interactive mode

    -
    -

    evennia istart

    -
    -
  4. -
  5. debugger will appear in the interactive terminal when breakpoint is reached. Exit -with ‘q’, remove the break line and restart server when finished.

  6. -
-
- - -
- -
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.locks.html b/docs/0.9.5/api/evennia.locks.html deleted file mode 100644 index fe8f03bb81..0000000000 --- a/docs/0.9.5/api/evennia.locks.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - evennia.locks — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.locks

-

This sub-package defines the lock (access) mechanism of Evennia. All -lock strings are processed through the lockhandler in this package. It -also contains the default lock functions used in lock definitions.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.locks.lockfuncs.html b/docs/0.9.5/api/evennia.locks.lockfuncs.html deleted file mode 100644 index ee296e280a..0000000000 --- a/docs/0.9.5/api/evennia.locks.lockfuncs.html +++ /dev/null @@ -1,576 +0,0 @@ - - - - - - - - - evennia.locks.lockfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.locks.lockfuncs

-

This module provides a set of permission lock functions for use -with Evennia’s permissions system.

-

To call these locks, make sure this module is included in the -settings tuple PERMISSION_FUNC_MODULES then define a lock on the form -‘<access_type>:func(args)’ and add it to the object’s lockhandler. -Run the access() method of the handler to execute the lock check.

-

Note that accessing_obj and accessed_obj can be any object type -with a lock variable/field, so be careful to not expect -a certain object type.

-

Appendix: MUX locks

-

Below is a list nicked from the MUX help file on the locks available -in standard MUX. Most of these are not relevant to core Evennia since -locks in Evennia are considerably more flexible and can be implemented -on an individual command/typeclass basis rather than as globally -available like the MUX ones. So many of these are not available in -basic Evennia, but could all be implemented easily if needed for the -individual game.

-
-

MUX Name: Affects: Effect:

-
-
DefaultLock: Exits: controls who may traverse the exit to
-
-
-
its destination.

Evennia: “traverse:<lockfunc()>”

-
-
-
-
-
Rooms: controls whether the account sees the

SUCC or FAIL message for the room -following the room description when -looking at the room.

-
-

Evennia: Custom typeclass

-
-
-
Accounts/Things: controls who may GET the object.

Evennia: “get:<lockfunc()”

-
-
-
-
-
EnterLock: Accounts/Things: controls who may ENTER the object

Evennia:

-
-
GetFromLock: All but Exits: controls who may gets things from a
-
given location.

Evennia:

-
-
-
-
GiveLock: Accounts/Things: controls who may give the object.

Evennia:

-
-
LeaveLock: Accounts/Things: controls who may LEAVE the object.

Evennia:

-
-
LinkLock: All but Exits: controls who may link to the location

if the location is LINK_OK (for linking -exits or setting drop-tos) or ABODE (for -setting homes)

-
-

Evennia:

-
-
-
MailLock: Accounts: controls who may @mail the account.

Evennia:

-
-
OpenLock: All but Exits: controls who may open an exit.

Evennia:

-
-
PageLock: Accounts: controls who may page the account.

Evennia: “send:<lockfunc()>”

-
-
ParentLock: All: controls who may make @parent links to
-
the object.

Evennia: Typeclasses and

-
-
-

“puppet:<lockstring()>”

-
-
ReceiveLock: Accounts/Things: controls who may give things to the
-
object.

Evennia:

-
-
-
-
SpeechLock: All but Exits: controls who may speak in that location

Evennia:

-
-
TeloutLock: All but Exits: controls who may teleport out of the
-
location.

Evennia:

-
-
-
-
TportLock: Rooms/Things: controls who may teleport there

Evennia:

-
-
UseLock: All but Exits: controls who may USE the object, GIVE

the object money and have the PAY -attributes run, have their messages -heard and possibly acted on by LISTEN -and AxHEAR, and invoke $-commands -stored on the object.

-
-

Evennia: Commands and Cmdsets.

-
-
-
DropLock: All but rooms: controls who may drop that object.

Evennia:

-
-
VisibleLock: All: Controls object visibility when the

object is not dark and the looker -passes the lock. In DARK locations, the -object must also be set LIGHT and the -viewer must pass the VisibleLock.

-
-
-
Evennia: Room typeclass with

Dark/light script

-
-
-
-
-
-
-
-
-
-evennia.locks.lockfuncs.true(*args, **kwargs)[source]
-

Always returns True.

-
- -
-
-evennia.locks.lockfuncs.all(*args, **kwargs)[source]
-
- -
-
-evennia.locks.lockfuncs.false(*args, **kwargs)[source]
-

Always returns False

-
- -
-
-evennia.locks.lockfuncs.none(*args, **kwargs)[source]
-
- -
-
-evennia.locks.lockfuncs.self(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

Check if accessing_obj is the same as accessed_obj

-
-
Usage:

self()

-
-
-

This can be used to lock specifically only to -the same object that the lock is defined on.

-
- -
-
-evennia.locks.lockfuncs.perm(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

The basic permission-checker. Ignores case.

-
-
Usage:

perm(<permission>)

-
-
-

where <permission> is the permission accessing_obj must -have in order to pass the lock.

-

If the given permission is part of settings.PERMISSION_HIERARCHY, -permission is also granted to all ranks higher up in the hierarchy.

-

If accessing_object is an Object controlled by an Account, the -permissions of the Account is used unless the Attribute _quell -is set to True on the Object. In this case however, the -LOWEST hieararcy-permission of the Account/Object-pair will be used -(this is order to avoid Accounts potentially escalating their own permissions -by use of a higher-level Object)

-
- -
-
-evennia.locks.lockfuncs.perm_above(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

Only allow objects with a permission higher in the permission -hierarchy than the one given. If there is no such higher rank, -it’s assumed we refer to superuser. If no hierarchy is defined, -this function has no meaning and returns False.

-
- -
-
-evennia.locks.lockfuncs.pperm(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

The basic permission-checker only for Account objects. Ignores case.

-
-
Usage:

pperm(<permission>)

-
-
-

where <permission> is the permission accessing_obj must -have in order to pass the lock. If the given permission -is part of _PERMISSION_HIERARCHY, permission is also granted -to all ranks higher up in the hierarchy.

-
- -
-
-evennia.locks.lockfuncs.pperm_above(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

Only allow Account objects with a permission higher in the permission -hierarchy than the one given. If there is no such higher rank, -it’s assumed we refer to superuser. If no hierarchy is defined, -this function has no meaning and returns False.

-
- -
-
-evennia.locks.lockfuncs.dbref(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

dbref(3)

-
-
-

This lock type checks if the checking object -has a particular dbref. Note that this only -works for checking objects that are stored -in the database (e.g. not for commands)

-
- -
-
-evennia.locks.lockfuncs.pdbref(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

Same as dbref, but making sure accessing_obj is an account.

-
- -
-
-evennia.locks.lockfuncs.id(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

Alias to dbref

-
- -
-
-evennia.locks.lockfuncs.pid(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

Alias to dbref, for Accounts

-
- -
-
-evennia.locks.lockfuncs.attr(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

attr(attrname) -attr(attrname, value) -attr(attrname, value, compare=type)

-
-
-

where compare’s type is one of (eq,gt,lt,ge,le,ne) and signifies -how the value should be compared with one on accessing_obj (so -compare=gt means the accessing_obj must have a value greater than -the one given).

-

Searches attributes and properties stored on the accessing_obj. -if accessing_obj has a property “obj”, then this is used as -accessing_obj (this makes this usable for Commands too)

-

The first form works like a flag - if the attribute/property -exists on the object, the value is checked for True/False. The -second form also requires that the value of the attribute/property -matches. Note that all retrieved values will be converted to -strings before doing the comparison.

-
- -
-
-evennia.locks.lockfuncs.objattr(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

objattr(attrname) -objattr(attrname, value) -objattr(attrname, value, compare=type)

-
-
-

Works like attr, except it looks for an attribute on -accessed_obj instead.

-
- -
-
-evennia.locks.lockfuncs.locattr(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

locattr(attrname) -locattr(attrname, value) -locattr(attrname, value, compare=type)

-
-
-

Works like attr, except it looks for an attribute on -accessing_obj.location, if such an entity exists.

-

if accessing_obj has a property “.obj” (such as is the case for a -Command), then accessing_obj.obj.location is used instead.

-
- -
-
-evennia.locks.lockfuncs.objlocattr(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

locattr(attrname) -locattr(attrname, value) -locattr(attrname, value, compare=type)

-
-
-

Works like attr, except it looks for an attribute on -accessed_obj.location, if such an entity exists.

-

if accessed_obj has a property “.obj” (such as is the case for a -Command), then accessing_obj.obj.location is used instead.

-
- -
-
-evennia.locks.lockfuncs.attr_eq(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

attr_gt(attrname, 54)

-
-
-
- -
-
-evennia.locks.lockfuncs.attr_gt(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

attr_gt(attrname, 54)

-
-
-

Only true if access_obj’s attribute > the value given.

-
- -
-
-evennia.locks.lockfuncs.attr_ge(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

attr_gt(attrname, 54)

-
-
-

Only true if access_obj’s attribute >= the value given.

-
- -
-
-evennia.locks.lockfuncs.attr_lt(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

attr_gt(attrname, 54)

-
-
-

Only true if access_obj’s attribute < the value given.

-
- -
-
-evennia.locks.lockfuncs.attr_le(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

attr_gt(attrname, 54)

-
-
-

Only true if access_obj’s attribute <= the value given.

-
- -
-
-evennia.locks.lockfuncs.attr_ne(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

attr_gt(attrname, 54)

-
-
-

Only true if access_obj’s attribute != the value given.

-
- -
-
-evennia.locks.lockfuncs.tag(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

tag(tagkey) -tag(tagkey, category)

-
-
-

Only true if accessing_obj has the specified tag and optional -category. -If accessing_obj has the “.obj” property (such as is the case for -a command), then accessing_obj.obj is used instead.

-
- -
-
-evennia.locks.lockfuncs.objtag(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

objtag(tagkey) -objtag(tagkey, category)

-
-
-

Only true if accessed_obj has the specified tag and optional -category.

-
- -
-
-evennia.locks.lockfuncs.inside(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

inside()

-
-
-

True if accessing_obj is ‘inside’ accessing_obj. Note that this only checks -one level down. So if if the lock is on a room, you will pass but not your -inventory (since their location is you, not the locked object). If you -want also nested objects to pass the lock, use the insiderecursive -lockfunc.

-
- -
-
-evennia.locks.lockfuncs.inside_rec(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:

inside_rec()

-
-
-

True if accessing_obj is inside the accessed obj, at up to 10 levels -of recursion (so if this lock is on a room, then an object inside a box -in your inventory will also pass the lock).

-
- -
-
-evennia.locks.lockfuncs.holds(accessing_obj, accessed_obj, *args, **kwargs)[source]
-
-
Usage:
-
holds() checks if accessed_obj or accessed_obj.obj

is held by accessing_obj

-
-
holds(key/dbref) checks if accessing_obj holds an object

with given key/dbref

-
-
holds(attrname, value) checks if accessing_obj holds an

object with the given attrname and value

-
-
-
-
-

This is passed if accessed_obj is carried by accessing_obj (that is, -accessed_obj.location == accessing_obj), or if accessing_obj itself holds -an object matching the given key.

-
- -
-
-evennia.locks.lockfuncs.superuser(*args, **kwargs)[source]
-

Only accepts an accesing_obj that is superuser (e.g. user #1)

-

Since a superuser would not ever reach this check (superusers -bypass the lock entirely), any user who gets this far cannot be a -superuser, hence we just return False. :)

-
- -
-
-evennia.locks.lockfuncs.has_account(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

Only returns true if accessing_obj has_account is true, that is, -this is an account-controlled object. It fails on actual accounts!

-

This is a useful lock for traverse-locking Exits to restrain NPC -mobiles from moving outside their areas.

-
- -
-
-evennia.locks.lockfuncs.serversetting(accessing_obj, accessed_obj, *args, **kwargs)[source]
-

Only returns true if the Evennia settings exists, alternatively has -a certain value.

-
-
Usage:

serversetting(IRC_ENABLED) -serversetting(BASE_SCRIPT_PATH, [‘types’])

-
-
-

A given True/False or integers will be converted properly. Note that -everything will enter this function as strings, so they have to be -unpacked to their real value. We only support basic properties.

-
- -
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.locks.lockhandler.html b/docs/0.9.5/api/evennia.locks.lockhandler.html deleted file mode 100644 index 0a16930ef3..0000000000 --- a/docs/0.9.5/api/evennia.locks.lockhandler.html +++ /dev/null @@ -1,484 +0,0 @@ - - - - - - - - - evennia.locks.lockhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.locks.lockhandler

-

A lock defines access to a particular subsystem or property of -Evennia. For example, the “owner” property can be impmemented as a -lock. Or the disability to lift an object or to ban users.

-

A lock consists of three parts:

-
-
    -
  • access_type - this defines what kind of access this lock regulates. This -just a string.

  • -
  • function call - this is one or many calls to functions that will determine -if the lock is passed or not.

  • -
  • lock function(s). These are regular python functions with a special -set of allowed arguments. They should always return a boolean depending -on if they allow access or not.

  • -
-
-

A lock function is defined by existing in one of the modules -listed by settings.LOCK_FUNC_MODULES. It should also always -take four arguments looking like this:

-
-
-
funcname(accessing_obj, accessed_obj, *args, **kwargs):

[…]

-
-
-
-

The accessing object is the object wanting to gain access. -The accessed object is the object this lock resides on -args and kwargs will hold optional arguments and/or keyword arguments -to the function as a list and a dictionary respectively.

-

Example

-
-
perm(accessing_obj, accessed_obj, *args, **kwargs):

“Checking if the object has a particular, desired permission” -if args:

-
-

desired_perm = args[0] -return desired_perm in accessing_obj.permissions.all()

-
-

return False

-
-
-

Lock functions should most often be pretty general and ideally possible to -re-use and combine in various ways to build clever locks.

-

Lock definition (“Lock string”)

-

A lock definition is a string with a special syntax. It is added to -each object’s lockhandler, making that lock available from then on.

-

The lock definition looks like this:

-
-

‘access_type:[NOT] func1(args)[ AND|OR][NOT] func2() …’

-
-

That is, the access_type, a colon followed by calls to lock functions -combined with AND or OR. NOT negates the result of the following call.

-

Example

-

We want to limit who may edit a particular object (let’s call this access_type

-

for ‘edit’, it depends on what the command is looking for). We want this to -only work for those with the Permission ‘Builder’. So we use our lock -function above and define it like this:

-
-

‘edit:perm(Builder)’

-
-

Here, the lock-function perm() will be called with the string -‘Builder’ (accessing_obj and accessed_obj are added automatically, -you only need to add the args/kwargs, if any).

-

If we wanted to make sure the accessing object was BOTH a Builder and a -GoodGuy, we could use AND:

-
-

‘edit:perm(Builder) AND perm(GoodGuy)’

-
-

To allow EITHER Builder and GoodGuys, we replace AND with OR. perm() is just -one example, the lock function can do anything and compare any properties of -the calling object to decide if the lock is passed or not.

-
-

‘lift:attrib(very_strong) AND NOT attrib(bad_back)’

-
-

To make these work, add the string to the lockhandler of the object you want -to apply the lock to:

-
-

obj.lockhandler.add(‘edit:perm(Builder)’)

-
-

From then on, a command that wants to check for ‘edit’ access on this -object would do something like this:

-
-
-
if not target_obj.lockhandler.has_perm(caller, ‘edit’):

caller.msg(“Sorry, you cannot edit that.”)

-
-
-
-

All objects also has a shortcut called ‘access’ that is recommended to -use instead:

-
-
-
if not target_obj.access(caller, ‘edit’):

caller.msg(“Sorry, you cannot edit that.”)

-
-
-
-

Permissions

-

Permissions are just text strings stored in a comma-separated list on -typeclassed objects. The default perm() lock function uses them, -taking into account settings.PERMISSION_HIERARCHY. Also, the -restricted @perm command sets them, but otherwise they are identical -to any other identifier you can use.

-
-
-class evennia.locks.lockhandler.LockHandler(obj)[source]
-

Bases: object

-

This handler should be attached to all objects implementing -permission checks, under the property ‘lockhandler’.

-
-
-__init__(obj)[source]
-

Loads and pre-caches all relevant locks and their functions.

-
-
Parameters
-
    -
  • obj (object) – The object on which the lockhandler is

  • -
  • defined.

  • -
-
-
-
- -
-
-cache_lock_bypass(obj)[source]
-

We cache superuser bypass checks here for efficiency. This -needs to be re-run when an account is assigned to a character. -We need to grant access to superusers. We need to check both -directly on the object (accounts), through obj.account and using -the get_account() method (this sits on serversessions, in some -rare cases where a check is done before the login process has -yet been fully finalized)

-
-
Parameters
-

obj (object) – This is checked for the is_superuser property.

-
-
-
- -
-
-add(lockstring, validate_only=False)[source]
-

Add a new lockstring to handler.

-
-
Parameters
-
    -
  • lockstring (str or list) – A string on the form -“<access_type>:<functions>”. Multiple access types -should be separated by semicolon (;). Alternatively, -a list with lockstrings.

  • -
  • validate_only (bool, optional) – If True, validate the lockstring but -don’t actually store it.

  • -
-
-
Returns
-

success (bool)

-
-
The outcome of the addition, False on

error. If validate_only is True, this will be a tuple -(bool, error), for pass/fail and a string error.

-
-
-

-
-
-
- -
-
-validate(lockstring)[source]
-

Validate lockstring syntactically, without saving it.

-
-
Parameters
-

lockstring (str) – Lockstring to validate.

-
-
Returns
-

valid (bool) – If validation passed or not.

-
-
-
- -
-
-replace(lockstring)[source]
-

Replaces the lockstring entirely.

-
-
Parameters
-

lockstring (str) – The new lock definition.

-
-
Returns
-

success (bool) – False if an error occurred.

-
-
Raises
-

LockException – If a critical error occurred. -If so, the old string is recovered.

-
-
-
- -
-
-get(access_type=None)[source]
-

Get the full lockstring or the lockstring of a particular -access type.

-
-
Parameters
-

access_type (str, optional) –

-
-
Returns
-

lockstring (str)

-
-
The matched lockstring, or the full

lockstring if no access_type was given.

-
-
-

-
-
-
- -
-
-all()[source]
-

Return all lockstrings

-
-
Returns
-

lockstrings (list) – All separate lockstrings

-
-
-
- -
-
-remove(access_type)[source]
-

Remove a particular lock from the handler

-
-
Parameters
-

access_type (str) – The type of lock to remove.

-
-
Returns
-

success (bool)

-
-
If the access_type was not found

in the lock, this returns False.

-
-
-

-
-
-
- -
-
-delete(access_type)
-

Remove a particular lock from the handler

-
-
Parameters
-

access_type (str) – The type of lock to remove.

-
-
Returns
-

success (bool)

-
-
If the access_type was not found

in the lock, this returns False.

-
-
-

-
-
-
- -
-
-clear()[source]
-

Remove all locks in the handler.

-
- -
-
-reset()[source]
-

Set the reset flag, so the the lock will be re-cached at next -checking. This is usually called by @reload.

-
- -
-
-append(access_type, lockstring, op='or')[source]
-

Append a lock definition to access_type if it doesn’t already exist.

-
-
Parameters
-
    -
  • access_type (str) – Access type.

  • -
  • lockstring (str) – A valid lockstring, without the operator to -link it to an eventual existing lockstring.

  • -
  • op (str) – An operator ‘and’, ‘or’, ‘and not’, ‘or not’ used -for appending the lockstring to an existing access-type.

  • -
-
-
-
-

Note

-

The most common use of this method is for use in commands where -the user can specify their own lockstrings. This method allows -the system to auto-add things like Admin-override access.

-
-
- -
-
-check(accessing_obj, access_type, default=False, no_superuser_bypass=False)[source]
-

Checks a lock of the correct type by passing execution off to -the lock function(s).

-
-
Parameters
-
    -
  • accessing_obj (object) – The object seeking access.

  • -
  • access_type (str) – The type of access wanted.

  • -
  • default (bool, optional) – If no suitable lock type is -found, default to this result.

  • -
  • no_superuser_bypass (bool) – Don’t use this unless you -really, really need to, it makes supersusers susceptible -to the lock check.

  • -
-
-
-

Notes

-

A lock is executed in the follwoing way:

-

Parsing the lockstring, we (during cache) extract the valid -lock functions and store their function objects in the right -order along with their args/kwargs. These are now executed in -sequence, creating a list of True/False values. This is put -into the evalstring, which is a string of AND/OR/NOT entries -separated by placeholders where each function result should -go. We just put those results in and evaluate the string to -get a final, combined True/False value for the lockstring.

-

The important bit with this solution is that the full -lockstring is never blindly evaluated, and thus there (should -be) no way to sneak in malign code in it. Only “safe” lock -functions (as defined by your settings) are executed.

-
- -
-
-check_lockstring(accessing_obj, lockstring, no_superuser_bypass=False, default=False, access_type=None)[source]
-

Do a direct check against a lockstring (‘atype:func()..’), -without any intermediary storage on the accessed object.

-
-
Parameters
-
    -
  • accessing_obj (object or None) – The object seeking access. -Importantly, this can be left unset if the lock functions -don’t access it, no updating or storage of locks are made -against this object in this method.

  • -
  • lockstring (str) – Lock string to check, on the form -“access_type:lock_definition” where the access_type -part can potentially be set to a dummy value to just check -a lock condition.

  • -
  • no_superuser_bypass (bool, optional) – Force superusers to heed lock.

  • -
  • default (bool, optional) – Fallback result to use if access_type is set -but no such access_type is found in the given lockstring.

  • -
  • access_type (str, bool) – If set, only this access_type will be looked up -among the locks defined by lockstring.

  • -
-
-
Returns
-

access (bool) – If check is passed or not.

-
-
-
- -
- -
-
-exception evennia.locks.lockhandler.LockException[source]
-

Bases: Exception

-

Raised during an error in a lock.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.objects.admin.html b/docs/0.9.5/api/evennia.objects.admin.html deleted file mode 100644 index cf0b2c215b..0000000000 --- a/docs/0.9.5/api/evennia.objects.admin.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - - - - evennia.objects.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.objects.admin

-
-
-class evennia.objects.admin.ObjectAttributeInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.AttributeInline

-

Defines inline descriptions of Attributes (experimental)

-
-
-model
-

alias of evennia.objects.models.ObjectDB_db_attributes

-
- -
-
-related_field = 'objectdb'
-
- -
-
-property media
-
- -
- -
-
-class evennia.objects.admin.ObjectTagInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.TagInline

-

Defines inline descriptions of Tags (experimental)

-
-
-model
-

alias of evennia.objects.models.ObjectDB_db_tags

-
- -
-
-related_field = 'objectdb'
-
- -
-
-property media
-
- -
- -
-
-class evennia.objects.admin.ObjectCreateForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]
-

Bases: django.forms.models.ModelForm

-

This form details the look of the fields.

-
-
-class Meta[source]
-

Bases: object

-
-
-model
-

alias of evennia.objects.models.ObjectDB

-
- -
-
-fields = '__all__'
-
- -
- -
-
-raw_id_fields = ('db_destination', 'db_location', 'db_home')
-
- -
-
-base_fields = {'db_account': <django.forms.models.ModelChoiceField object>, 'db_attributes': <django.forms.models.ModelMultipleChoiceField object>, 'db_cmdset_storage': <django.forms.fields.CharField object>, 'db_destination': <django.forms.models.ModelChoiceField object>, 'db_home': <django.forms.models.ModelChoiceField object>, 'db_key': <django.forms.fields.CharField object>, 'db_location': <django.forms.models.ModelChoiceField object>, 'db_lock_storage': <django.forms.fields.CharField object>, 'db_sessid': <django.forms.fields.CharField object>, 'db_tags': <django.forms.models.ModelMultipleChoiceField object>, 'db_typeclass_path': <django.forms.fields.CharField object>}
-
- -
-
-declared_fields = {'db_cmdset_storage': <django.forms.fields.CharField object>, 'db_key': <django.forms.fields.CharField object>, 'db_typeclass_path': <django.forms.fields.CharField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.objects.admin.ObjectEditForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]
-

Bases: evennia.objects.admin.ObjectCreateForm

-

Form used for editing. Extends the create one with more fields

-
-
-class Meta[source]
-

Bases: object

-
-
-fields = '__all__'
-
- -
- -
-
-base_fields = {'db_cmdset_storage': <django.forms.fields.CharField object>, 'db_key': <django.forms.fields.CharField object>, 'db_lock_storage': <django.forms.fields.CharField object>, 'db_typeclass_path': <django.forms.fields.CharField object>}
-
- -
-
-declared_fields = {'db_cmdset_storage': <django.forms.fields.CharField object>, 'db_key': <django.forms.fields.CharField object>, 'db_lock_storage': <django.forms.fields.CharField object>, 'db_typeclass_path': <django.forms.fields.CharField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.objects.admin.ObjectDBAdmin(model, admin_site)[source]
-

Bases: django.contrib.admin.options.ModelAdmin

-

Describes the admin page for Objects.

-
-
-inlines = [<class 'evennia.objects.admin.ObjectTagInline'>, <class 'evennia.objects.admin.ObjectAttributeInline'>]
-
- -
-
-list_display = ('id', 'db_key', 'db_account', 'db_typeclass_path')
-
- -
- -
- -
-
-ordering = ['db_account', 'db_typeclass_path', 'id']
-
- -
-
-search_fields = ['=id', '^db_key', 'db_typeclass_path', '^db_account__db_key']
-
- -
-
-raw_id_fields = ('db_destination', 'db_location', 'db_home')
-
- -
-
-save_as = True
-
- -
-
-save_on_top = True
-
- -
- -
- -
-
-list_filter = ('db_typeclass_path',)
-
- -
-
-form
-

alias of ObjectEditForm

-
- -
-
-fieldsets = ((None, {'fields': (('db_key', 'db_typeclass_path'), ('db_lock_storage',), ('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage')}),)
-
- -
-
-add_form
-

alias of ObjectCreateForm

-
- -
-
-add_fieldsets = ((None, {'fields': (('db_key', 'db_typeclass_path'), ('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage')}),)
-
- -
-
-get_fieldsets(request, obj=None)[source]
-

Return fieldsets.

-
-
Parameters
-
    -
  • request (Request) – Incoming request.

  • -
  • obj (ObjectDB, optional) – Database object.

  • -
-
-
-
- -
-
-get_form(request, obj=None, **kwargs)[source]
-

Use special form during creation.

-
-
Parameters
-
    -
  • request (Request) – Incoming request.

  • -
  • obj (Object, optional) – Database object.

  • -
-
-
-
- -
-
-save_model(request, obj, form, change)[source]
-

Model-save hook.

-
-
Parameters
-
    -
  • request (Request) – Incoming request.

  • -
  • obj (Object) – Database object.

  • -
  • form (Form) – Form instance.

  • -
  • change (bool) – If this is a change or a new object.

  • -
-
-
-
- -
-
-response_add(request, obj, post_url_continue=None)[source]
-

Determine the HttpResponse for the add_view stage.

-
- -
-
-property media
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.objects.html b/docs/0.9.5/api/evennia.objects.html deleted file mode 100644 index fcff921252..0000000000 --- a/docs/0.9.5/api/evennia.objects.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - evennia.objects — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.objects

-

This sub-package defines the basic in-game “Object”. All in-game -objects inherit from classes in this package.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.objects.manager.html b/docs/0.9.5/api/evennia.objects.manager.html deleted file mode 100644 index cbda5416b1..0000000000 --- a/docs/0.9.5/api/evennia.objects.manager.html +++ /dev/null @@ -1,474 +0,0 @@ - - - - - - - - - evennia.objects.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.objects.manager

-

Custom manager for Objects.

-
-
-class evennia.objects.manager.ObjectManager(*args, **kwargs)[source]
-

Bases: evennia.objects.manager.ObjectDBManager, evennia.typeclasses.managers.TypeclassManager

-
- -
-
-class evennia.objects.manager.ObjectDBManager(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.managers.TypedObjectManager

-

This ObjectManager implements methods for searching -and manipulating Objects directly from the database.

-

Evennia-specific search methods (will return Typeclasses or -lists of Typeclasses, whereas Django-general methods will return -Querysets or database objects).

-

dbref (converter) -get_id (alias: dbref_search) -get_dbref_range -object_totals -typeclass_search -get_object_with_account -get_objs_with_key_and_typeclass -get_objs_with_attr -get_objs_with_attr_match -get_objs_with_db_property -get_objs_with_db_property_match -get_objs_with_key_or_alias -get_contents -object_search (interface to many of the above methods,

-
-

equivalent to evennia.search_object)

-
-

copy_object

-
-
-get_object_with_account(ostring, exact=True, candidates=None)[source]
-

Search for an object based on its account’s name or dbref.

-
-
Parameters
-
    -
  • ostring (str or int) – Search criterion or dbref. Searching -for an account is sometimes initiated by appending an * to -the beginning of the search criterion (e.g. in -local_and_global_search). This is stripped here.

  • -
  • exact (bool, optional) – Require an exact account match.

  • -
  • candidates (list, optional) – Only search among this list of possible -object candidates.

  • -
-
-
Returns
-

match (query) – Matching query.

-
-
-
- -
-
-get_objs_with_key_and_typeclass(oname, otypeclass_path, candidates=None)[source]
-

Returns objects based on simultaneous key and typeclass match.

-
-
Parameters
-
    -
  • oname (str) – Object key to search for

  • -
  • otypeclass_path (str) – Full Python path to tyepclass to search for

  • -
  • candidates (list, optional) – Only match among the given list of candidates.

  • -
-
-
Returns
-

matches (query) – The matching objects.

-
-
-
- -
-
-get_objs_with_attr(attribute_name, candidates=None)[source]
-

Get objects based on having a certain Attribute defined.

-
-
Parameters
-
    -
  • attribute_name (str) – Attribute name to search for.

  • -
  • candidates (list, optional) – Only match among the given list of object -candidates.

  • -
-
-
Returns
-

matches (query) – All objects having the given attribute_name defined at all.

-
-
-
- -
-
-get_objs_with_attr_value(attribute_name, attribute_value, candidates=None, typeclasses=None)[source]
-

Get all objects having the given attrname set to the given value.

-
-
Parameters
-
    -
  • attribute_name (str) – Attribute key to search for.

  • -
  • attribute_value (any) – Attribute value to search for. This can also be database objects.

  • -
  • candidates (list, optional) – Candidate objects to limit search to.

  • -
  • typeclasses (list, optional) – Python pats to restrict matches with.

  • -
-
-
Returns
-

matches (query) – Objects fullfilling both the attribute_name and -attribute_value criterions.

-
-
-

Notes

-

This uses the Attribute’s PickledField to transparently search the database by matching -the internal representation. This is reasonably effective but since Attribute values -cannot be indexed, searching by Attribute key is to be preferred whenever possible.

-
- -
-
-get_objs_with_db_property(property_name, candidates=None)[source]
-

Get all objects having a given db field property.

-
-
Parameters
-
    -
  • property_name (str) – The name of the field to match for.

  • -
  • candidates (list, optional) – Only search among th egiven candidates.

  • -
-
-
Returns
-

matches (list) – The found matches.

-
-
-
- -
-
-get_objs_with_db_property_value(property_name, property_value, candidates=None, typeclasses=None)[source]
-

Get objects with a specific field name and value.

-
-
Parameters
-
    -
  • property_name (str) – Field name to search for.

  • -
  • property_value (any) – Value required for field with property_name to have.

  • -
  • candidates (list, optional) – List of objects to limit search to.

  • -
  • typeclasses (list, optional) – List of typeclass-path strings to restrict matches with

  • -
-
-
-
- -
-
-get_contents(location, excludeobj=None)[source]
-

Get all objects that has a location set to this one.

-
-
Parameters
-
    -
  • location (Object) – Where to get contents from.

  • -
  • excludeobj (Object or list, optional) – One or more objects -to exclude from the match.

  • -
-
-
Returns
-

contents (query) – Matching contents, without excludeobj, if given.

-
-
-
- -
-
-get_objs_with_key_or_alias(ostring, exact=True, candidates=None, typeclasses=None)[source]
-
-
Parameters
-
    -
  • ostring (str) – A search criterion.

  • -
  • exact (bool, optional) – Require exact match of ostring -(still case-insensitive). If False, will do fuzzy matching -using evennia.utils.utils.string_partial_matching algorithm.

  • -
  • candidates (list) – Only match among these candidates.

  • -
  • typeclasses (list) – Only match objects with typeclasses having thess path strings.

  • -
-
-
Returns
-

matches (query) – A list of matches of length 0, 1 or more.

-
-
-
- -
-
-search_object(searchdata, attribute_name=None, typeclass=None, candidates=None, exact=True, use_dbref=True)[source]
-

Search as an object globally or in a list of candidates and -return results. The result is always an Object. Always returns -a list.

-
-
Parameters
-
    -
  • searchdata (str or Object) – The entity to match for. This is -usually a key string but may also be an object itself. -By default (if no attribute_name is set), this will -search object.key and object.aliases in order. -Can also be on the form #dbref, which will (if -exact=True) be matched against primary key.

  • -
  • attribute_name (str) – Use this named Attribute to -match searchdata against, instead of the defaults. If -this is the name of a database field (with or without -the db_ prefix), that will be matched too.

  • -
  • typeclass (str or TypeClass) – restrict matches to objects -having this typeclass. This will help speed up global -searches.

  • -
  • candidates (list) – If supplied, search will -only be performed among the candidates in this list. A -common list of candidates is the contents of the -current location searched.

  • -
  • exact (bool) – Match names/aliases exactly or partially. -Partial matching matches the beginning of words in the -names/aliases, using a matching routine to separate -multiple matches in names with multiple components (so -“bi sw” will match “Big sword”). Since this is more -expensive than exact matching, it is recommended to be -used together with the candidates keyword to limit the -number of possibilities. This value has no meaning if -searching for attributes/properties.

  • -
  • use_dbref (bool) – If False, bypass direct lookup of a string -on the form #dbref and treat it like any string.

  • -
-
-
Returns
-

matches (list) – Matching objects

-
-
-
- -
- -

Search as an object globally or in a list of candidates and -return results. The result is always an Object. Always returns -a list.

-
-
Parameters
-
    -
  • searchdata (str or Object) – The entity to match for. This is -usually a key string but may also be an object itself. -By default (if no attribute_name is set), this will -search object.key and object.aliases in order. -Can also be on the form #dbref, which will (if -exact=True) be matched against primary key.

  • -
  • attribute_name (str) – Use this named Attribute to -match searchdata against, instead of the defaults. If -this is the name of a database field (with or without -the db_ prefix), that will be matched too.

  • -
  • typeclass (str or TypeClass) – restrict matches to objects -having this typeclass. This will help speed up global -searches.

  • -
  • candidates (list) – If supplied, search will -only be performed among the candidates in this list. A -common list of candidates is the contents of the -current location searched.

  • -
  • exact (bool) – Match names/aliases exactly or partially. -Partial matching matches the beginning of words in the -names/aliases, using a matching routine to separate -multiple matches in names with multiple components (so -“bi sw” will match “Big sword”). Since this is more -expensive than exact matching, it is recommended to be -used together with the candidates keyword to limit the -number of possibilities. This value has no meaning if -searching for attributes/properties.

  • -
  • use_dbref (bool) – If False, bypass direct lookup of a string -on the form #dbref and treat it like any string.

  • -
-
-
Returns
-

matches (list) – Matching objects

-
-
-
- -
-
-search(searchdata, attribute_name=None, typeclass=None, candidates=None, exact=True, use_dbref=True)
-

Search as an object globally or in a list of candidates and -return results. The result is always an Object. Always returns -a list.

-
-
Parameters
-
    -
  • searchdata (str or Object) – The entity to match for. This is -usually a key string but may also be an object itself. -By default (if no attribute_name is set), this will -search object.key and object.aliases in order. -Can also be on the form #dbref, which will (if -exact=True) be matched against primary key.

  • -
  • attribute_name (str) – Use this named Attribute to -match searchdata against, instead of the defaults. If -this is the name of a database field (with or without -the db_ prefix), that will be matched too.

  • -
  • typeclass (str or TypeClass) – restrict matches to objects -having this typeclass. This will help speed up global -searches.

  • -
  • candidates (list) – If supplied, search will -only be performed among the candidates in this list. A -common list of candidates is the contents of the -current location searched.

  • -
  • exact (bool) – Match names/aliases exactly or partially. -Partial matching matches the beginning of words in the -names/aliases, using a matching routine to separate -multiple matches in names with multiple components (so -“bi sw” will match “Big sword”). Since this is more -expensive than exact matching, it is recommended to be -used together with the candidates keyword to limit the -number of possibilities. This value has no meaning if -searching for attributes/properties.

  • -
  • use_dbref (bool) – If False, bypass direct lookup of a string -on the form #dbref and treat it like any string.

  • -
-
-
Returns
-

matches (list) – Matching objects

-
-
-
- -
-
-copy_object(original_object, new_key=None, new_location=None, new_home=None, new_permissions=None, new_locks=None, new_aliases=None, new_destination=None)[source]
-

Create and return a new object as a copy of the original object. All -will be identical to the original except for the arguments given -specifically to this method. Object contents will not be copied.

-
-
Parameters
-
    -
  • original_object (Object) – The object to make a copy from.

  • -
  • new_key (str, optional) – Name of the copy, if different -from the original.

  • -
  • new_location (Object, optional) – Alternate location.

  • -
  • new_home (Object, optional) – Change the home location

  • -
  • new_aliases (list, optional) – Give alternate object -aliases as a list of strings.

  • -
  • new_destination (Object, optional) – Used only by exits.

  • -
-
-
Returns
-

copy (Object or None)

-
-
The copy of original_object,

optionally modified as per the ingoing keyword -arguments. None if an error was encountered.

-
-
-

-
-
-
- -
-
-clear_all_sessids()[source]
-

Clear the db_sessid field of all objects having also the -db_account field set.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.objects.models.html b/docs/0.9.5/api/evennia.objects.models.html deleted file mode 100644 index c3400f0eee..0000000000 --- a/docs/0.9.5/api/evennia.objects.models.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - evennia.objects.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.objects.models

-

This module defines the database models for all in-game objects, that -is, all objects that has an actual existence in-game.

-

Each database object is ‘decorated’ with a ‘typeclass’, a normal -python class that implements all the various logics needed by the game -in question. Objects created of this class transparently communicate -with its related database object for storing all attributes. The -admin should usually not have to deal directly with this database -object layer.

-

Attributes are separate objects that store values persistently onto -the database object. Like everything else, they can be accessed -transparently through the decorating TypeClass.

-
-
-class evennia.objects.models.ContentsHandler(obj)[source]
-

Bases: object

-

Handles and caches the contents of an object to avoid excessive -lookups (this is done very often due to cmdhandler needing to look -for object-cmdsets). It is stored on the ‘contents_cache’ property -of the ObjectDB.

-
-
-__init__(obj)[source]
-

Sets up the contents handler.

-
-
Parameters
-

obj (Object) – The object on which the -handler is defined

-
-
-
- -
-
-init()[source]
-

Re-initialize the content cache

-
- -
-
-get(exclude=None)[source]
-

Return the contents of the cache.

-
-
Parameters
-

exclude (Object or list of Object) – object(s) to ignore

-
-
Returns
-

objects (list) – the Objects inside this location

-
-
-
- -
-
-add(obj)[source]
-

Add a new object to this location

-
-
Parameters
-

obj (Object) – object to add

-
-
-
- -
-
-remove(obj)[source]
-

Remove object from this location

-
-
Parameters
-

obj (Object) – object to remove

-
-
-
- -
-
-clear()[source]
-

Clear the contents cache and re-initialize

-
- -
- -
-
-class evennia.objects.models.ObjectDB(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.models.TypedObject

-

All objects in the game use the ObjectDB model to store -data in the database. This is handled transparently through -the typeclass system.

-

Note that the base objectdb is very simple, with -few defined fields. Use attributes to extend your -type class with new database-stored variables.

-

The TypedObject supplies the following (inherited) properties:

-
-
    -
  • key - main name

  • -
  • name - alias for key

  • -
  • db_typeclass_path - the path to the decorating typeclass

  • -
  • db_date_created - time stamp of object creation

  • -
  • permissions - perm strings

  • -
  • locks - lock definitions (handler)

  • -
  • dbref - #id of object

  • -
  • db - persistent attribute storage

  • -
  • ndb - non-persistent attribute storage

  • -
-
-

The ObjectDB adds the following properties:

-
-
    -
  • account - optional connected account (always together with sessid)

  • -
  • sessid - optional connection session id (always together with account)

  • -
  • location - in-game location of object

  • -
  • home - safety location for object (handler)

  • -
  • scripts - scripts assigned to object (handler from typeclass)

  • -
  • cmdset - active cmdset on object (handler from typeclass)

  • -
  • aliases - aliases for this object (property)

  • -
  • nicks - nicknames for other things in Evennia (handler)

  • -
  • sessions - sessions connected to this object (see also account)

  • -
  • has_account - bool if an active account is currently connected

  • -
  • contents - other objects having this object as location

  • -
  • exits - exits from this object

  • -
-
-
-
-db_account
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-db_sessid
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_location
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-db_home
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-db_destination
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-db_cmdset_storage
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-objects = <evennia.objects.manager.ObjectDBManager object>
-
- -
-
-contents_cache[source]
-
- -
-
-property cmdset_storage
-

getter

-
- -
-
-property location
-

Get location

-
- -
-
-at_db_location_postsave(new)[source]
-

This is called automatically after the location field was -saved, no matter how. It checks for a variable -_safe_contents_update to know if the save was triggered via -the location handler (which updates the contents cache) or -not.

-
-
Parameters
-

new (bool) – Set if this location has not yet been saved before.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-property account
-

A wrapper for getting database field db_account.

-
- -
-
-db_account_id
-
- -
-
-db_attributes
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_destination_id
-
- -
-
-db_home_id
-
- -
-
-db_location_id
-
- -
-
-db_tags
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-property destination
-

A wrapper for getting database field db_destination.

-
- -
-
-destinations_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-get_next_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=True, **kwargs)
-
- -
-
-get_previous_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=False, **kwargs)
-
- -
-
-hide_from_objects_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-property home
-

A wrapper for getting database field db_home.

-
- -
-
-homes_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-locations_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-object_subscription_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-path = 'evennia.objects.models.ObjectDB'
-
- -
-
-receiver_object_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-scriptdb_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-sender_object_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-property sessid
-

A wrapper for getting database field db_sessid.

-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.objects.objects.html b/docs/0.9.5/api/evennia.objects.objects.html deleted file mode 100644 index 7c5657ea80..0000000000 --- a/docs/0.9.5/api/evennia.objects.objects.html +++ /dev/null @@ -1,1959 +0,0 @@ - - - - - - - - - evennia.objects.objects — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.objects.objects

-

This module defines the basic DefaultObject and its children -DefaultCharacter, DefaultAccount, DefaultRoom and DefaultExit. -These are the (default) starting points for all in-game visible -entities.

-
-
-class evennia.objects.objects.ObjectSessionHandler(obj)[source]
-

Bases: object

-

Handles the get/setting of the sessid -comma-separated integer field

-
-
-__init__(obj)[source]
-

Initializes the handler.

-
-
Parameters
-

obj (Object) – The object on which the handler is defined.

-
-
-
- -
-
-get(sessid=None)[source]
-

Get the sessions linked to this Object.

-
-
Parameters
-

sessid (int, optional) – A specific session id.

-
-
Returns
-

sessions (list)

-
-
The sessions connected to this object. If sessid is given,

this is a list of one (or zero) elements.

-
-
-

-
-
-

Notes

-

Aliased to self.all().

-
- -
-
-all()[source]
-

Alias to get(), returning all sessions.

-
-
Returns
-

sessions (list) – All sessions.

-
-
-
- -
-
-add(session)[source]
-

Add session to handler.

-
-
Parameters
-

session (Session or int) – Session or session id to add.

-
-
-

Notes

-

We will only add a session/sessid if this actually also exists -in the the core sessionhandler.

-
- -
-
-remove(session)[source]
-

Remove session from handler.

-
-
Parameters
-

session (Session or int) – Session or session id to remove.

-
-
-
- -
-
-clear()[source]
-

Clear all handled sessids.

-
- -
-
-count()[source]
-

Get amount of sessions connected.

-
-
Returns
-

sesslen (int) – Number of sessions handled.

-
-
-
- -
- -
-
-class evennia.objects.objects.DefaultObject(*args, **kwargs)[source]
-

Bases: evennia.objects.models.ObjectDB

-

This is the root typeclass object, representing all entities that -have an actual presence in-game. DefaultObjects generally have a -location. They can also be manipulated and looked at. Game -entities you define should inherit from DefaultObject at some distance.

-

It is recommended to create children of this class using the -evennia.create_object() function rather than to initialize the class -directly - this will both set things up and efficiently save the object -without obj.save() having to be called explicitly.

-
-
-lockstring = 'control:id({account_id}) or perm(Admin);delete:id({account_id}) or perm(Admin)'
-
- -
-
-objects = <evennia.objects.manager.ObjectManager object>
-
- -
-
-cmdset[source]
-
- -
-
-scripts[source]
-
- -
-
-nicks[source]
-
- -
-
-sessions[source]
-
- -
-
-property is_connected
-
- -
-
-property has_account
-

Convenience property for checking if an active account is -currently connected to this object.

-
- -
-
-property is_superuser
-

Check if user has an account, and if so, if it is a superuser.

-
- -
-
-contents_get(exclude=None)[source]
-

Returns the contents of this object, i.e. all -objects that has this object set as its location. -This should be publically available.

-
-
Parameters
-

exclude (Object) – Object to exclude from returned -contents list

-
-
Returns
-

contents (list) – List of contents of this Object.

-
-
-

Notes

-

Also available as the contents property.

-
- -
-
-contents_set(*args)[source]
-

You cannot replace this property

-
- -
-
-property contents
-

Returns the contents of this object, i.e. all -objects that has this object set as its location. -This should be publically available.

-
-
Parameters
-

exclude (Object) – Object to exclude from returned -contents list

-
-
Returns
-

contents (list) – List of contents of this Object.

-
-
-

Notes

-

Also available as the contents property.

-
- -
-
-property exits
-

Returns all exits from this object, i.e. all objects at this -location having the property destination != None.

-
- -
-
-get_display_name(looker, **kwargs)[source]
-

Displays the name of the object in a viewer-aware manner.

-
-
Parameters
-

looker (TypedObject) – The object or account that is looking -at/getting inforamtion for this object.

-
-
Returns
-

name (str)

-
-
A string containing the name of the object,

including the DBREF if this user is privileged to control -said object.

-
-
-

-
-
-

Notes

-

This function could be extended to change how object names -appear to users in character, but be wary. This function -does not change an object’s keys or aliases when -searching, and is expected to produce something useful for -builders.

-
- -
-
-get_numbered_name(count, looker, **kwargs)[source]
-

Return the numbered (singular, plural) forms of this object’s key. This is by default called -by return_appearance and is used for grouping multiple same-named of this object. Note that -this will be called on every member of a group even though the plural name will be only -shown once. Also the singular display version, such as ‘an apple’, ‘a tree’ is determined -from this method.

-
-
Parameters
-
    -
  • count (int) – Number of objects of this type

  • -
  • looker (Object) – Onlooker. Not used by default.

  • -
-
-
Keyword Arguments
-

key (str) – Optional key to pluralize, if given, use this instead of the object’s key.

-
-
Returns
-

singular (str) – The singular form to display. -plural (str): The determined plural form of the key, including the count.

-
-
-
- -
-
-search(searchdata, global_search=False, use_nicks=True, typeclass=None, location=None, attribute_name=None, quiet=False, exact=False, candidates=None, nofound_string=None, multimatch_string=None, use_dbref=None)[source]
-

Returns an Object matching a search string/condition

-

Perform a standard object search in the database, handling -multiple results and lack thereof gracefully. By default, only -objects in the current location of self or its inventory are searched for.

-
-
Parameters
-
    -
  • searchdata (str or obj) –

    Primary search criterion. Will be matched -against object.key (with object.aliases second) unless -the keyword attribute_name specifies otherwise.

    -

    Special strings:

    -
      -
    • -
      #<num>: search by unique dbref. This is always

      a global search.

      -
      -
      -
    • -
    • me,self: self-reference to this object

    • -
    • -
      <num>-<string> - can be used to differentiate

      between multiple same-named matches. The exact form of this input -is given by settings.SEARCH_MULTIMATCH_REGEX.

      -
      -
      -
    • -
    -

  • -
  • global_search (bool) – Search all objects globally. This overrules ‘location’ data.

  • -
  • use_nicks (bool) – Use nickname-replace (nicktype “object”) on searchdata.

  • -
  • typeclass (str or Typeclass, or list of either) – Limit search only -to Objects with this typeclass. May be a list of typeclasses -for a broader search.

  • -
  • location (Object or list) – Specify a location or multiple locations -to search. Note that this is used to query the contents of a -location and will not match for the location itself - -if you want that, don’t set this or use candidates to specify -exactly which objects should be searched.

  • -
  • attribute_name (str) – Define which property to search. If set, no -key+alias search will be performed. This can be used -to search database fields (db_ will be automatically -prepended), and if that fails, it will try to return -objects having Attributes with this name and value -equal to searchdata. A special use is to search for -“key” here if you want to do a key-search without -including aliases.

  • -
  • quiet (bool) – don’t display default error messages - this tells the -search method that the user wants to handle all errors -themselves. It also changes the return value type, see -below.

  • -
  • exact (bool) – if unset (default) - prefers to match to beginning of -string rather than not matching at all. If set, requires -exact matching of entire string.

  • -
  • candidates (list of objects) – this is an optional custom list of objects -to search (filter) between. It is ignored if global_search -is given. If not set, this list will automatically be defined -to include the location, the contents of location and the -caller’s contents (inventory).

  • -
  • nofound_string (str) – optional custom string for not-found error message.

  • -
  • multimatch_string (str) – optional custom string for multimatch error header.

  • -
  • use_dbref (bool or None, optional) – If True, allow to enter e.g. a query “#123” -to find an object (globally) by its database-id 123. If False, the string “#123” -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.

  • -
-
-
Returns
-

Object, None or list – Will return an Object or None if quiet=False. Will return a -list with 0, 1 or more matches if quiet=True. If stacked is a positive integer, this -list may contain all stacked identical matches.

-
-
-

Notes

-

To find Accounts, use eg. evennia.account_search. If -quiet=False, error messages will be handled by -settings.SEARCH_AT_RESULT and echoed automatically (on -error, return will be None). If quiet=True, the error -messaging is assumed to be handled by the caller.

-
- -
-
-search_account(searchdata, quiet=False)[source]
-

Simple shortcut wrapper to search for accounts, not characters.

-
-
Parameters
-
    -
  • searchdata (str) – Search criterion - the key or dbref of the account -to search for. If this is “here” or “me”, search -for the account connected to this object.

  • -
  • quiet (bool) – Returns the results as a list rather than -echo eventual standard error messages. Default False.

  • -
-
-
Returns
-

result (Account, None or list)

-
-
Just what is returned depends on
-
the quiet setting:
    -
  • quiet=True: No match or multumatch auto-echoes errors -to self.msg, then returns None. The esults are passed -through settings.SEARCH_AT_RESULT and -settings.SEARCH_AT_MULTIMATCH_INPUT. If there is a -unique match, this will be returned.

  • -
  • quiet=True: No automatic error messaging is done, and -what is returned is always a list with 0, 1 or more -matching Accounts.

  • -
-
-
-
-
-

-
-
-
- -
-
-execute_cmd(raw_string, session=None, **kwargs)[source]
-

Do something as this object. This is never called normally, -it’s only used when wanting specifically to let an object be -the caller of a command. It makes use of nicks of eventual -connected accounts as well.

-
-
Parameters
-
    -
  • raw_string (string) – Raw command input

  • -
  • session (Session, optional) – Session to -return results to

  • -
-
-
Keyword Arguments
-
    -
  • keyword arguments will be added to the found command (Other) –

  • -
  • instace as variables before it executes. This is (object) –

  • -
  • by default Evennia but may be used to set flags and (unused) –

  • -
  • operating paramaters for commands at run-time. (change) –

  • -
-
-
Returns
-

defer (Deferred)

-
-
This is an asynchronous Twisted object that

will not fire until the command has actually finished -executing. To overload this one needs to attach -callback functions to it, with addCallback(function). -This function will be called with an eventual return -value from the command execution. This return is not -used at all by Evennia by default, but might be useful -for coders intending to implement some sort of nested -command structure.

-
-
-

-
-
-
- -
-
-msg(text=None, from_obj=None, session=None, options=None, **kwargs)[source]
-

Emits something to a session attached to the object.

-
-
Parameters
-
    -
  • text (str or tuple, optional) – The message to send. This -is treated internally like any send-command, so its -value can be a tuple if sending multiple arguments to -the text oob command.

  • -
  • from_obj (obj or list, optional) – object that is sending. If -given, at_msg_send will be called. This value will be -passed on to the protocol. If iterable, will execute hook -on all entities in it.

  • -
  • session (Session or list, optional) – Session or list of -Sessions to relay data to, if any. If set, will force send -to these sessions. If unset, who receives the message -depends on the MULTISESSION_MODE.

  • -
  • options (dict, optional) – Message-specific option-value -pairs. These will be applied at the protocol level.

  • -
-
-
Keyword Arguments
-

any (string or tuples) – All kwarg keys not listed above -will be treated as send-command names and their arguments -(which can be a string or a tuple).

-
-
-

Notes

-

at_msg_receive will be called on this Object. -All extra kwargs will be passed on to the protocol.

-
- -
-
-for_contents(func, exclude=None, **kwargs)[source]
-

Runs a function on every object contained within this one.

-
-
Parameters
-
    -
  • func (callable) – Function to call. This must have the -formal call sign func(obj, kwargs), where obj is the -object currently being processed and ****kwargs are -passed on from the call to for_contents.

  • -
  • exclude (list, optional) – A list of object not to call the -function on.

  • -
-
-
Keyword Arguments
-

arguments will be passed to the function for all objects. (Keyword) –

-
-
-
- -
-
-msg_contents(text=None, exclude=None, from_obj=None, mapping=None, **kwargs)[source]
-

Emits a message to all objects inside this object.

-
-
Parameters
-
    -
  • text (str or tuple) – Message to send. If a tuple, this should be -on the valid OOB outmessage form (message, {kwargs}), -where kwargs are optional data passed to the text -outputfunc.

  • -
  • exclude (list, optional) – A list of objects not to send to.

  • -
  • from_obj (Object, optional) – An object designated as the -“sender” of the message. See DefaultObject.msg() for -more info.

  • -
  • mapping (dict, optional) – A mapping of formatting keys -{“key”:<object>, “key2”:<object2>,…}. The keys -must match **{key} markers in the text if this is a string or -in the internal message if text is a tuple. These -formatting statements will be -replaced by the return of <object>.get_display_name(looker) -for every looker in contents that receives the -message. This allows for every object to potentially -get its own customized string.

  • -
-
-
Keyword Arguments
-
    -
  • arguments will be passed on to obj.msg() for all (Keyword) –

  • -
  • objects. (messaged) –

  • -
-
-
-

Notes

-

The mapping argument is required if message contains -{}-style format syntax. The keys of mapping should match -named format tokens, and its values will have their -get_display_name() function called for each object in -the room before substitution. If an item in the mapping does -not have get_display_name(), its string value will be used.

-

Example

-

Say Char is a Character object and Npc is an NPC object:

-
-
char.location.msg_contents(

“{attacker} kicks {defender}”, -mapping=dict(attacker=char, defender=npc), exclude=(char, npc))

-
-
-

This will result in everyone in the room seeing ‘Char kicks NPC’ -where everyone may potentially see different results for Char and Npc -depending on the results of char.get_display_name(looker) and -npc.get_display_name(looker) for each particular onlooker

-
- -
-
-move_to(destination, quiet=False, emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True, **kwargs)[source]
-

Moves this object to a new location.

-
-
Parameters
-
    -
  • destination (Object) – Reference to the object to move to. This -can also be an exit object, in which case the -destination property is used as destination.

  • -
  • quiet (bool) – If true, turn off the calling of the emit hooks -(announce_move_to/from etc)

  • -
  • emit_to_obj (Object) – object to receive error messages

  • -
  • use_destination (bool) – Default is for objects to use the “destination” -property of destinations as the target to move to. Turning off this -keyword allows objects to move “inside” exit objects.

  • -
  • to_none (bool) – Allow destination to be None. Note that no hooks are run when -moving to a None location. If you want to run hooks, run them manually -(and make sure they can manage None locations).

  • -
  • move_hooks (bool) – If False, turn off the calling of move-related hooks -(at_before/after_move etc) with quiet=True, this is as quiet a move -as can be done.

  • -
-
-
Keyword Arguments
-

on to announce_move_to and announce_move_from hooks. (Passed) –

-
-
Returns
-

result (bool)

-
-
True/False depending on if there were problems with the move.

This method may also return various error messages to the -emit_to_obj.

-
-
-

-
-
-

Notes

-

No access checks are done in this method, these should be handled before -calling move_to.

-

The DefaultObject hooks called (if move_hooks=True) are, in order:

-
-
    -
  1. self.at_before_move(destination) (if this returns False, move is aborted)

  2. -
  3. source_location.at_object_leave(self, destination)

  4. -
  5. self.announce_move_from(destination)

  6. -
  7. (move happens here)

  8. -
  9. self.announce_move_to(source_location)

  10. -
  11. destination.at_object_receive(self, source_location)

  12. -
  13. self.at_after_move(source_location)

  14. -
-
-
- -
-
-clear_exits()[source]
-

Destroys all of the exits and any exits pointing to this -object as a destination.

-
- -
-
-clear_contents()[source]
-

Moves all objects (accounts/things) to their home location or -to default home.

-
- -
-
-classmethod create(key, account=None, **kwargs)[source]
-

Creates a basic object with default parameters, unless otherwise -specified or extended.

-

Provides a friendlier interface to the utils.create_object() function.

-
-
Parameters
-
    -
  • key (str) – Name of the new object.

  • -
  • account (Account) – Account to attribute this object to.

  • -
-
-
Keyword Arguments
-
    -
  • description (str) – Brief description for this object.

  • -
  • ip (str) – IP address of creator (for object auditing).

  • -
-
-
Returns
-

object (Object) – A newly created object of the given typeclass. -errors (list): A list of errors in string form, if any.

-
-
-
- -
-
-copy(new_key=None, **kwargs)[source]
-

Makes an identical copy of this object, identical except for a -new dbref in the database. If you want to customize the copy -by changing some settings, use ObjectDB.object.copy_object() -directly.

-
-
Parameters
-

new_key (string) – New key/name of copied object. If new_key is not -specified, the copy will be named <old_key>_copy by default.

-
-
Returns
-

copy (Object) – A copy of this object.

-
-
-
- -
-
-at_object_post_copy(new_obj, **kwargs)[source]
-

Called by DefaultObject.copy(). Meant to be overloaded. In case there’s extra data not covered by -.copy(), this can be used to deal with it.

-
-
Parameters
-

new_obj (Object) – The new Copy of this object.

-
-
Returns
-

None

-
-
-
- -
-
-delete()[source]
-

Deletes this object. Before deletion, this method makes sure -to move all contained objects to their respective home -locations, as well as clean up all exits to/from the object.

-
-
Returns
-

noerror (bool)

-
-
Returns whether or not the delete completed

successfully or not.

-
-
-

-
-
-
- -
-
-access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False, **kwargs)[source]
-

Determines if another object has permission to access this object -in whatever way.

-
-
Parameters
-
    -
  • accessing_obj (Object) – Object trying to access this one.

  • -
  • access_type (str, optional) – Type of access sought.

  • -
  • default (bool, optional) – What to return if no lock of access_type was found.

  • -
  • no_superuser_bypass (bool, optional) – If True, don’t skip -lock check for superuser (be careful with this one).

  • -
-
-
Keyword Arguments
-

on to the at_access hook along with the result of the access check. (Passed) –

-
-
-
- -
-
-at_first_save()[source]
-

This is called by the typeclass system whenever an instance of -this class is saved for the first time. It is a generic hook -for calling the startup hooks for the various game entities. -When overloading you generally don’t overload this but -overload the hooks called by this method.

-
- -
-
-basetype_setup()[source]
-

This sets up the default properties of an Object, just before -the more general at_object_creation.

-

You normally don’t need to change this unless you change some -fundamental things like names of permission groups.

-
- -
-
-basetype_posthook_setup()[source]
-

Called once, after basetype_setup and at_object_creation. This -should generally not be overloaded unless you are redefining -how a room/exit/object works. It allows for basetype-like -setup after the object is created. An example of this is -EXITs, who need to know keys, aliases, locks etc to set up -their exit-cmdsets.

-
- -
-
-at_object_creation()[source]
-

Called once, when this object is first created. This is the -normal hook to overload for most object types.

-
- -
-
-at_object_delete()[source]
-

Called just before the database object is permanently -delete()d from the database. If this method returns False, -deletion is aborted.

-
- -
-
-at_init()[source]
-

This is always called whenever this object is initiated – -that is, whenever it its typeclass is cached from memory. This -happens on-demand first time the object is used or activated -in some way after being created but also after each server -restart or reload.

-
- -
-
-at_cmdset_get(**kwargs)[source]
-

Called just before cmdsets on this object are requested by the -command handler. If changes need to be done on the fly to the -cmdset before passing them on to the cmdhandler, this is the -place to do it. This is called also if the object currently -have no cmdsets.

-
-
Keyword Arguments
-

caller (Session, Object or Account) – The caller requesting -this cmdset.

-
-
-
- -
-
-at_pre_puppet(account, session=None, **kwargs)[source]
-

Called just before an Account connects to this object to puppet -it.

-
-
Parameters
-
    -
  • account (Account) – This is the connecting account.

  • -
  • session (Session) – Session controlling the connection.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_post_puppet(**kwargs)[source]
-

Called just after puppeting has been completed and all -Account<->Object links have been established.

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
-

Note

-

You can use self.account and self.sessions.get() to get -account and sessions at this point; the last entry in the -list from self.sessions.get() is the latest Session -puppeting this Object.

-
-
- -
-
-at_pre_unpuppet(**kwargs)[source]
-

Called just before beginning to un-connect a puppeting from -this Account.

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
-

Note

-

You can use self.account and self.sessions.get() to get -account and sessions at this point; the last entry in the -list from self.sessions.get() is the latest Session -puppeting this Object.

-
-
- -
-
-at_post_unpuppet(account, session=None, **kwargs)[source]
-

Called just after the Account successfully disconnected from -this object, severing all connections.

-
-
Parameters
-
    -
  • account (Account) – The account object that just disconnected -from this object.

  • -
  • session (Session) – Session id controlling the connection that -just disconnected.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_server_reload()[source]
-

This hook is called whenever the server is shutting down for -restart/reboot. If you want to, for example, save non-persistent -properties across a restart, this is the place to do it.

-
- -
-
-at_server_shutdown()[source]
-

This hook is called whenever the server is shutting down fully -(i.e. not for a restart).

-
- -
-
-at_access(result, accessing_obj, access_type, **kwargs)[source]
-

This is called with the result of an access call, along with -any kwargs used for that call. The return of this method does -not affect the result of the lock check. It can be used e.g. to -customize error messages in a central location or other effects -based on the access result.

-
-
Parameters
-
    -
  • result (bool) – The outcome of the access call.

  • -
  • accessing_obj (Object or Account) – The entity trying to gain access.

  • -
  • access_type (str) – The type of access that was requested.

  • -
-
-
Keyword Arguments
-
    -
  • used by default, added for possible expandability in a (Not) –

  • -
  • game.

  • -
-
-
-
- -
-
-at_before_move(destination, **kwargs)[source]
-

Called just before starting to move this object to -destination.

-
-
Parameters
-
    -
  • destination (Object) – The object we are moving to

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

shouldmove (bool) – If we should move or not.

-
-
-

Notes

-

If this method returns False/None, the move is cancelled -before it is even started.

-
- -
-
-announce_move_from(destination, msg=None, mapping=None, **kwargs)[source]
-

Called if the move is to be announced. This is -called while we are still standing in the old -location.

-
-
Parameters
-
    -
  • destination (Object) – The place we are going to.

  • -
  • msg (str, optional) – a replacement message.

  • -
  • mapping (dict, optional) – additional mapping objects.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

You can override this method and call its parent with a -message to simply change the default message. In the string, -you can use the following as mappings (between braces):

-
-

object: the object which is moving. -exit: the exit from which the object is moving (if found). -origin: the location of the object before the move. -destination: the location of the object after moving.

-
-
- -
-
-announce_move_to(source_location, msg=None, mapping=None, **kwargs)[source]
-

Called after the move if the move was not quiet. At this point -we are standing in the new location.

-
-
Parameters
-
    -
  • source_location (Object) – The place we came from

  • -
  • msg (str, optional) – the replacement message if location.

  • -
  • mapping (dict, optional) – additional mapping objects.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

You can override this method and call its parent with a -message to simply change the default message. In the string, -you can use the following as mappings (between braces):

-
-

object: the object which is moving. -exit: the exit from which the object is moving (if found). -origin: the location of the object before the move. -destination: the location of the object after moving.

-
-
- -
-
-at_after_move(source_location, **kwargs)[source]
-

Called after move has completed, regardless of quiet mode or -not. Allows changes to the object due to the location it is -now in.

-
-
Parameters
-
    -
  • source_location (Object) – Wwhere we came from. This may be None.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_object_leave(moved_obj, target_location, **kwargs)[source]
-

Called just before an object leaves from inside this object

-
-
Parameters
-
    -
  • moved_obj (Object) – The object leaving

  • -
  • target_location (Object) – Where moved_obj is going.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_object_receive(moved_obj, source_location, **kwargs)[source]
-

Called after an object has been moved into this object.

-
-
Parameters
-
    -
  • moved_obj (Object) – The object moved into this one

  • -
  • source_location (Object) – Where moved_object came from. -Note that this could be None.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_traverse(traversing_object, target_location, **kwargs)[source]
-

This hook is responsible for handling the actual traversal, -normally by calling -traversing_object.move_to(target_location). It is normally -only implemented by Exit objects. If it returns False (usually -because move_to returned False), at_after_traverse below -should not be called and instead at_failed_traverse should be -called.

-
-
Parameters
-
    -
  • traversing_object (Object) – Object traversing us.

  • -
  • target_location (Object) – Where target is going.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_after_traverse(traversing_object, source_location, **kwargs)[source]
-

Called just after an object successfully used this object to -traverse to another object (i.e. this object is a type of -Exit)

-
-
Parameters
-
    -
  • traversing_object (Object) – The object traversing us.

  • -
  • source_location (Object) – Where traversing_object came from.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

The target location should normally be available as self.destination.

-
- -
-
-at_failed_traverse(traversing_object, **kwargs)[source]
-

This is called if an object fails to traverse this object for -some reason.

-
-
Parameters
-
    -
  • traversing_object (Object) – The object that failed traversing us.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

Using the default exits, this hook will not be called if an -Attribute err_traverse is defined - this will in that case be -read for an error string instead.

-
- -
-
-at_msg_receive(text=None, from_obj=None, **kwargs)[source]
-

This hook is called whenever someone sends a message to this -object using the msg method.

-

Note that from_obj may be None if the sender did not include -itself as an argument to the obj.msg() call - so you have to -check for this. .

-

Consider this a pre-processing method before msg is passed on -to the user session. If this method returns False, the msg -will not be passed on.

-
-
Parameters
-
    -
  • text (str, optional) – The message received.

  • -
  • from_obj (any, optional) – The object sending the message.

  • -
-
-
Keyword Arguments
-

includes any keywords sent to the msg method. (This) –

-
-
Returns
-

receive (bool) – If this message should be received.

-
-
-

Notes

-

If this method returns False, the msg operation -will abort without sending the message.

-
- -
-
-at_msg_send(text=None, to_obj=None, **kwargs)[source]
-

This is a hook that is called when this object sends a -message to another object with obj.msg(text, to_obj=obj).

-
-
Parameters
-
    -
  • text (str, optional) – Text to send.

  • -
  • to_obj (any, optional) – The object to send to.

  • -
-
-
Keyword Arguments
-

passed from msg() (Keywords) –

-
-
-

Notes

-

Since this method is executed by from_obj, if no from_obj -was passed to DefaultCharacter.msg this hook will never -get called.

-
- -
-
-return_appearance(looker, **kwargs)[source]
-

This formats a description. It is the hook a ‘look’ command -should call.

-
-
Parameters
-
    -
  • looker (Object) – Object doing the looking.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_look(target, **kwargs)[source]
-

Called when this object performs a look. It allows to -customize just what this means. It will not itself -send any data.

-
-
Parameters
-
    -
  • target (Object) – The target being looked at. This is -commonly an object or the current location. It will -be checked for the “view” type access.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call. This will be passed into -return_appearance, get_display_name and at_desc but is not used -by default.

  • -
-
-
Returns
-

lookstring (str)

-
-
A ready-processed look string

potentially ready to return to the looker.

-
-
-

-
-
-
- -
-
-at_desc(looker=None, **kwargs)[source]
-

This is called whenever someone looks at this object.

-
-
Parameters
-
    -
  • looker (Object, optional) – The object requesting the description.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_before_get(getter, **kwargs)[source]
-

Called by the default get command before this object has been -picked up.

-
-
Parameters
-
    -
  • getter (Object) – The object about to get this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

shouldget (bool) – If the object should be gotten or not.

-
-
-

Notes

-

If this method returns False/None, the getting is cancelled -before it is even started.

-
- -
-
-at_get(getter, **kwargs)[source]
-

Called by the default get command when this object has been -picked up.

-
-
Parameters
-
    -
  • getter (Object) – The object getting this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

This hook cannot stop the pickup from happening. Use -permissions or the at_before_get() hook for that.

-
- -
-
-at_before_give(giver, getter, **kwargs)[source]
-

Called by the default give command before this object has been -given.

-
-
Parameters
-
    -
  • giver (Object) – The object about to give this object.

  • -
  • getter (Object) – The object about to get this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

shouldgive (bool) – If the object should be given or not.

-
-
-

Notes

-

If this method returns False/None, the giving is cancelled -before it is even started.

-
- -
-
-at_give(giver, getter, **kwargs)[source]
-

Called by the default give command when this object has been -given.

-
-
Parameters
-
    -
  • giver (Object) – The object giving this object.

  • -
  • getter (Object) – The object getting this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

This hook cannot stop the give from happening. Use -permissions or the at_before_give() hook for that.

-
- -
-
-at_before_drop(dropper, **kwargs)[source]
-

Called by the default drop command before this object has been -dropped.

-
-
Parameters
-
    -
  • dropper (Object) – The object which will drop this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

shoulddrop (bool) – If the object should be dropped or not.

-
-
-

Notes

-

If this method returns False/None, the dropping is cancelled -before it is even started.

-
- -
-
-at_drop(dropper, **kwargs)[source]
-

Called by the default drop command when this object has been -dropped.

-
-
Parameters
-
    -
  • dropper (Object) – The object which just dropped this object.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

This hook cannot stop the drop from happening. Use -permissions or the at_before_drop() hook for that.

-
- -
-
-at_before_say(message, **kwargs)[source]
-

Before the object says something.

-

This hook is by default used by the ‘say’ and ‘whisper’ -commands as used by this command it is called before the text -is said/whispered and can be used to customize the outgoing -text from the object. Returning None aborts the command.

-
-
Parameters
-

message (str) – The suggested say/whisper text spoken by self.

-
-
Keyword Arguments
-
    -
  • whisper (bool) – If True, this is a whisper rather than -a say. This is sent by the whisper command by default. -Other verbal commands could use this hook in similar -ways.

  • -
  • receivers (Object or iterable) – If set, this is the target or targets for the say/whisper.

  • -
-
-
Returns
-

message (str) – The (possibly modified) text to be spoken.

-
-
-
- -
-
-at_say(message, msg_self=None, msg_location=None, receivers=None, msg_receivers=None, **kwargs)[source]
-

Display the actual say (or whisper) of self.

-

This hook should display the actual say/whisper of the object in its -location. It should both alert the object (self) and its -location that some text is spoken. The overriding of messages or -mapping allows for simple customization of the hook without -re-writing it completely.

-
-
Parameters
-
    -
  • message (str) – The message to convey.

  • -
  • msg_self (bool or str, optional) – If boolean True, echo message to self. If a string, -return that message. If False or unset, don’t echo to self.

  • -
  • msg_location (str, optional) – The message to echo to self’s location.

  • -
  • receivers (Object or iterable, optional) – An eventual receiver or receivers of the message -(by default only used by whispers).

  • -
  • msg_receivers (str) – Specific message to pass to the receiver(s). This will parsed -with the {receiver} placeholder replaced with the given receiver.

  • -
-
-
Keyword Arguments
-
    -
  • whisper (bool) – If this is a whisper rather than a say. Kwargs -can be used by other verbal commands in a similar way.

  • -
  • mapping (dict) – Pass an additional mapping to the message.

  • -
-
-
-

Notes

-

Messages can contain {} markers. These are substituted against the values -passed in the mapping argument.

-
-

msg_self = ‘You say: “{speech}”’ -msg_location = ‘{object} says: “{speech}”’ -msg_receivers = ‘{object} whispers: “{speech}”’

-
-
-
Supported markers by default:

{self}: text to self-reference with (default ‘You’) -{speech}: the text spoken/whispered by self. -{object}: the object speaking. -{receiver}: replaced with a single receiver only for strings meant for a specific

-
-

receiver (otherwise ‘None’).

-
-
-
{all_receivers}: comma-separated list of all receivers,

if more than one, otherwise same as receiver

-
-
-

{location}: the location where object is.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.models.ObjectDB.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.models.ObjectDB.MultipleObjectsReturned

-
- -
-
-path = 'evennia.objects.objects.DefaultObject'
-
- -
-
-typename = 'DefaultObject'
-
- -
- -
-
-class evennia.objects.objects.DefaultCharacter(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

This implements an Object puppeted by a Session - that is, -a character avatar controlled by an account.

-
-
-lockstring = 'puppet:id({character_id}) or pid({account_id}) or perm(Developer) or pperm(Developer);delete:id({account_id}) or perm(Admin)'
-
- -
-
-classmethod create(key, account=None, **kwargs)[source]
-

Creates a basic Character with default parameters, unless otherwise -specified or extended.

-

Provides a friendlier interface to the utils.create_character() function.

-
-
Parameters
-
    -
  • key (str) – Name of the new Character.

  • -
  • account (obj, optional) – Account to associate this Character with. -If unset supplying None– it will -change the default lockset and skip creator attribution.

  • -
-
-
Keyword Arguments
-
    -
  • description (str) – Brief description for this object.

  • -
  • ip (str) – IP address of creator (for object auditing).

  • -
  • other kwargs will be passed into the create_object call. (All) –

  • -
-
-
Returns
-

character (Object) – A newly created Character of the given typeclass. -errors (list): A list of errors in string form, if any.

-
-
-
- -
-
-basetype_setup()[source]
-

Setup character-specific security.

-

You should normally not need to overload this, but if you do, -make sure to reproduce at least the two last commands in this -method (unless you want to fundamentally change how a -Character object works).

-
- -
-
-at_after_move(source_location, **kwargs)[source]
-

We make sure to look around after a move.

-
- -
-
-at_pre_puppet(account, session=None, **kwargs)[source]
-

Return the character from storage in None location in at_post_unpuppet. -:param account: This is the connecting account. -:type account: Account -:param session: Session controlling the connection. -:type session: Session

-
- -
-
-at_post_puppet(**kwargs)[source]
-

Called just after puppeting has been completed and all -Account<->Object links have been established.

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
-

Note

-

You can use self.account and self.sessions.get() to get -account and sessions at this point; the last entry in the -list from self.sessions.get() is the latest Session -puppeting this Object.

-
-
- -
-
-at_post_unpuppet(account, session=None, **kwargs)[source]
-

We stove away the character when the account goes ooc/logs off, -otherwise the character object will remain in the room also -after the account logged off (“headless”, so to say).

-
-
Parameters
-
    -
  • account (Account) – The account object that just disconnected -from this object.

  • -
  • session (Session) – Session controlling the connection that -just disconnected.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-property idle_time
-

Returns the idle time of the least idle session in seconds. If -no sessions are connected it returns nothing.

-
- -
-
-property connection_time
-

Returns the maximum connection time of all connected sessions -in seconds. Returns nothing if there are no sessions.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.objects.objects.DefaultCharacter'
-
- -
-
-typename = 'DefaultCharacter'
-
- -
- -
-
-class evennia.objects.objects.DefaultRoom(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

This is the base room object. It’s just like any Object except its -location is always None.

-
-
-lockstring = 'control:id({id}) or perm(Admin); delete:id({id}) or perm(Admin); edit:id({id}) or perm(Admin)'
-
- -
-
-classmethod create(key, account=None, **kwargs)[source]
-

Creates a basic Room with default parameters, unless otherwise -specified or extended.

-

Provides a friendlier interface to the utils.create_object() function.

-
-
Parameters
-
    -
  • key (str) – Name of the new Room.

  • -
  • account (obj, optional) – Account to associate this Room with. If -given, it will be given specific control/edit permissions to this -object (along with normal Admin perms). If not given, default

  • -
-
-
Keyword Arguments
-
    -
  • description (str) – Brief description for this object.

  • -
  • ip (str) – IP address of creator (for object auditing).

  • -
-
-
Returns
-

room (Object) – A newly created Room of the given typeclass. -errors (list): A list of errors in string form, if any.

-
-
-
- -
-
-basetype_setup()[source]
-

Simple room setup setting locks to make sure the room -cannot be picked up.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.objects.objects.DefaultRoom'
-
- -
-
-typename = 'DefaultRoom'
-
- -
- -
-
-class evennia.objects.objects.ExitCommand(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

This is a command that simply cause the caller to traverse -the object it is attached to.

-
-
-obj = None
-
- -
-
-func()[source]
-

Default exit traverse if no syscommand is defined.

-
- -
-
-get_extra_info(caller, **kwargs)[source]
-

Shows a bit of information on where the exit leads.

-
-
Parameters
-
    -
  • caller (Object) – The object (usually a character) that entered an ambiguous command.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
Returns
-

A string with identifying information to disambiguate the command, conventionally with a preceding space.

-
-
-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'command'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.objects.objects.DefaultExit(*args, **kwargs)[source]
-

Bases: evennia.objects.objects.DefaultObject

-

This is the base exit object - it connects a location to another. -This is done by the exit assigning a “command” on itself with the -same name as the exit object (to do this we need to remember to -re-create the command when the object is cached since it must be -created dynamically depending on what the exit is called). This -command (which has a high priority) will thus allow us to traverse -exits simply by giving the exit-object’s name on its own.

-
-
-exit_command
-

alias of ExitCommand

-
- -
-
-priority = 101
-
- -
-
-lockstring = 'control:id({id}) or perm(Admin); delete:id({id}) or perm(Admin); edit:id({id}) or perm(Admin)'
-
- -
-
-create_exit_cmdset(exidbobj)[source]
-

Helper function for creating an exit command set + command.

-

The command of this cmdset has the same name as the Exit -object and allows the exit to react when the account enter the -exit’s name, triggering the movement between rooms.

-
-
Parameters
-

exidbobj (Object) – The DefaultExit object to base the command on.

-
-
-
- -
-
-classmethod create(key, source, dest, account=None, **kwargs)[source]
-

Creates a basic Exit with default parameters, unless otherwise -specified or extended.

-

Provides a friendlier interface to the utils.create_object() function.

-
-
Parameters
-
    -
  • key (str) – Name of the new Exit, as it should appear from the -source room.

  • -
  • account (obj) – Account to associate this Exit with.

  • -
  • source (Room) – The room to create this exit in.

  • -
  • dest (Room) – The room to which this exit should go.

  • -
-
-
Keyword Arguments
-
    -
  • description (str) – Brief description for this object.

  • -
  • ip (str) – IP address of creator (for object auditing).

  • -
-
-
Returns
-

exit (Object) – A newly created Room of the given typeclass. -errors (list): A list of errors in string form, if any.

-
-
-
- -
-
-basetype_setup()[source]
-

Setup exit-security

-

You should normally not need to overload this - if you do make -sure you include all the functionality in this method.

-
- -
-
-at_cmdset_get(**kwargs)[source]
-

Called just before cmdsets on this object are requested by the -command handler. If changes need to be done on the fly to the -cmdset before passing them on to the cmdhandler, this is the -place to do it. This is called also if the object currently -has no cmdsets.

-
-
Keyword Arguments
-

force_init (bool) – If True, force a re-build of the cmdset -(for example to update aliases).

-
-
-
- -
-
-at_init()[source]
-

This is called when this objects is re-loaded from cache. When -that happens, we make sure to remove any old ExitCmdSet cmdset -(this most commonly occurs when renaming an existing exit)

-
- -
-
-at_traverse(traversing_object, target_location, **kwargs)[source]
-

This implements the actual traversal. The traverse lock has -already been checked (in the Exit command) at this point.

-
-
Parameters
-
    -
  • traversing_object (Object) – Object traversing us.

  • -
  • target_location (Object) – Where target is going.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-
- -
-
-at_failed_traverse(traversing_object, **kwargs)[source]
-

Overloads the default hook to implement a simple default error message.

-
-
Parameters
-
    -
  • traversing_object (Object) – The object that failed traversing us.

  • -
  • **kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

  • -
-
-
-

Notes

-

Using the default exits, this hook will not be called if an -Attribute err_traverse is defined - this will in that case be -read for an error string instead.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.objects.objects.DefaultObject.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.objects.objects.DefaultObject.MultipleObjectsReturned

-
- -
-
-path = 'evennia.objects.objects.DefaultExit'
-
- -
-
-typename = 'DefaultExit'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.prototypes.html b/docs/0.9.5/api/evennia.prototypes.html deleted file mode 100644 index 6c4ab5cf2e..0000000000 --- a/docs/0.9.5/api/evennia.prototypes.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - evennia.prototypes — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
- - -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.prototypes.menus.html b/docs/0.9.5/api/evennia.prototypes.menus.html deleted file mode 100644 index 3bb880fe2f..0000000000 --- a/docs/0.9.5/api/evennia.prototypes.menus.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - - evennia.prototypes.menus — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.prototypes.menus

-

OLC Prototype menu nodes

-
-
-evennia.prototypes.menus.node_validate_prototype(caller, raw_string, **kwargs)[source]
-

General node to view and validate a protototype

-
- -
-
-evennia.prototypes.menus.node_examine_entity(caller, raw_string, **kwargs)[source]
-

General node to view a text and then return to previous node. Kwargs should contain “text” for -the text to show and ‘back” pointing to the node to return to.

-
- -
-
-evennia.prototypes.menus.node_index(caller)[source]
-
- -
-
-evennia.prototypes.menus.node_prototype_key(caller)[source]
-
- -
-
-evennia.prototypes.menus.node_key(caller)[source]
-
- -
-
-evennia.prototypes.menus.node_location(caller)[source]
-
- -
-
-evennia.prototypes.menus.node_home(caller)[source]
-
- -
-
-evennia.prototypes.menus.node_destination(caller)[source]
-
- -
-
-evennia.prototypes.menus.node_prototype_desc(caller)[source]
-
- -
-
-evennia.prototypes.menus.node_apply_diff(caller, **kwargs)[source]
-

Offer options for updating objects

-
- -
-
-evennia.prototypes.menus.node_prototype_save(caller, **kwargs)[source]
-

Save prototype to disk

-
- -
-
-evennia.prototypes.menus.node_prototype_spawn(caller, **kwargs)[source]
-

Submenu for spawning the prototype

-
- -
-
-class evennia.prototypes.menus.OLCMenu(caller, menudata, startnode='start', cmdset_mergetype='Replace', cmdset_priority=1, auto_quit=True, auto_look=True, auto_help=True, cmd_on_exit='look', persistent=False, startnode_input='', session=None, debug=False, **kwargs)[source]
-

Bases: evennia.utils.evmenu.EvMenu

-

A custom EvMenu with a different formatting for the options.

-
-
-nodetext_formatter(nodetext)[source]
-

Format the node text itself.

-
- -
-
-options_formatter(optionlist)[source]
-

Split the options into two blocks - olc options and normal options

-
- -
-
-helptext_formatter(helptext)[source]
-

Show help text

-
- -
-
-display_helptext()[source]
-
- -
- -
-
-evennia.prototypes.menus.start_olc(caller, session=None, prototype=None)[source]
-

Start menu-driven olc system for prototypes.

-
-
Parameters
-
    -
  • caller (Object or Account) – The entity starting the menu.

  • -
  • session (Session, optional) – The individual session to get data.

  • -
  • prototype (dict, optional) – Given when editing an existing -prototype rather than creating a new one.

  • -
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.prototypes.protfuncs.html b/docs/0.9.5/api/evennia.prototypes.protfuncs.html deleted file mode 100644 index 831f902803..0000000000 --- a/docs/0.9.5/api/evennia.prototypes.protfuncs.html +++ /dev/null @@ -1,294 +0,0 @@ - - - - - - - - - evennia.prototypes.protfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.prototypes.protfuncs

-

Protfuncs are function-strings embedded in a prototype and allows for a builder to create a -prototype with custom logics without having access to Python. The Protfunc is parsed using the -inlinefunc parser but is fired at the moment the spawning happens, using the creating object’s -session as input.

-

In the prototype dict, the protfunc is specified as a string inside the prototype, e.g.:

-
-

{ …

-

“key”: “$funcname(arg1, arg2, …)”

-

… }

-
-

and multiple functions can be nested (no keyword args are supported). The result will be used as the -value for that prototype key for that individual spawn.

-

Available protfuncs are callables in one of the modules of settings.PROT_FUNC_MODULES. They -are specified as functions

-
-

def funcname (*args, **kwargs)

-
-

where *args are the arguments given in the prototype, and **kwargs are inserted by Evennia:

-
-
    -
  • session (Session): The Session of the entity spawning using this prototype.

  • -
  • prototype (dict): The dict this protfunc is a part of.

  • -
  • current_key (str): The active key this value belongs to in the prototype.

  • -
  • -
    testing (bool): This is set if this function is called as part of the prototype validation; if

    set, the protfunc should take care not to perform any persistent actions, such as operate on -objects or add things to the database.

    -
    -
    -
  • -
-
-

Any traceback raised by this function will be handled at the time of spawning and abort the spawn -before any object is created/updated. It must otherwise return the value to store for the specified -prototype key (this value must be possible to serialize in an Attribute).

-
-
-evennia.prototypes.protfuncs.base_random()
-

random() -> x in the interval [0, 1).

-
- -
-
-evennia.prototypes.protfuncs.random(*args, **kwargs)[source]
-

Usage: $random() -Returns a random value in the interval [0, 1)

-
- -
-
-evennia.prototypes.protfuncs.randint(*args, **kwargs)[source]
-

Usage: $randint(start, end) -Returns random integer in interval [start, end]

-
- -
-
-evennia.prototypes.protfuncs.left_justify(*args, **kwargs)[source]
-

Usage: $left_justify(<text>) -Returns <text> left-justified.

-
- -
-
-evennia.prototypes.protfuncs.right_justify(*args, **kwargs)[source]
-

Usage: $right_justify(<text>) -Returns <text> right-justified across screen width.

-
- -
-
-evennia.prototypes.protfuncs.center_justify(*args, **kwargs)[source]
-

Usage: $center_justify(<text>) -Returns <text> centered in screen width.

-
- -
-
-evennia.prototypes.protfuncs.choice(*args, **kwargs)[source]
-

Usage: $choice(val, val, val, …) -Returns one of the values randomly

-
- -
-
-evennia.prototypes.protfuncs.full_justify(*args, **kwargs)[source]
-

Usage: $full_justify(<text>) -Returns <text> filling up screen width by adding extra space.

-
- -
-
-evennia.prototypes.protfuncs.protkey(*args, **kwargs)[source]
-

Usage: $protkey(<key>) -Returns the value of another key in this prototoype. Will raise an error if

-
-

the key is not found in this prototype.

-
-
- -
-
-evennia.prototypes.protfuncs.add(*args, **kwargs)[source]
-

Usage: $add(val1, val2) -Returns the result of val1 + val2. Values must be

-
-

valid simple Python structures possible to add, -such as numbers, lists etc.

-
-
- -
-
-evennia.prototypes.protfuncs.sub(*args, **kwargs)[source]
-

Usage: $del(val1, val2) -Returns the value of val1 - val2. Values must be

-
-

valid simple Python structures possible to -subtract.

-
-
- -
-
-evennia.prototypes.protfuncs.mult(*args, **kwargs)[source]
-

Usage: $mul(val1, val2) -Returns the value of val1 * val2. The values must be

-
-

valid simple Python structures possible to -multiply, like strings and/or numbers.

-
-
- -
-
-evennia.prototypes.protfuncs.div(*args, **kwargs)[source]
-

Usage: $div(val1, val2) -Returns the value of val1 / val2. Values must be numbers and

-
-

the result is always a float.

-
-
- -
-
-evennia.prototypes.protfuncs.toint(*args, **kwargs)[source]
-

Usage: $toint(<number>) -Returns <number> as an integer.

-
- -
-
-evennia.prototypes.protfuncs.eval(*args, **kwargs)[source]
-

Usage $eval(<expression>) -Returns evaluation of a simple Python expression. The string may only consist of the following

-
-

Python literal structures: strings, numbers, tuples, lists, dicts, booleans, -and None. The strings can also contain #dbrefs. Escape embedded protfuncs as $$protfunc(..) -- those will then be evaluated after $eval.

-
-
- -
-
-evennia.prototypes.protfuncs.obj(*args, **kwargs)[source]
-

Usage $obj(<query>) -Returns one Object searched globally by key, alias or #dbref. Error if more than one.

-
- -
-
-evennia.prototypes.protfuncs.objlist(*args, **kwargs)[source]
-

Usage $objlist(<query>) -Returns list with one or more Objects searched globally by key, alias or #dbref.

-
- -
-
-evennia.prototypes.protfuncs.dbref(*args, **kwargs)[source]
-

Usage $dbref(<#dbref>) -Validate that a #dbref input is valid.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.prototypes.prototypes.html b/docs/0.9.5/api/evennia.prototypes.prototypes.html deleted file mode 100644 index 61349ad18b..0000000000 --- a/docs/0.9.5/api/evennia.prototypes.prototypes.html +++ /dev/null @@ -1,528 +0,0 @@ - - - - - - - - - evennia.prototypes.prototypes — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.prototypes.prototypes

-

Handling storage of prototypes, both database-based ones (DBPrototypes) and those defined in modules -(Read-only prototypes). Also contains utility functions, formatters and manager functions.

-
-
-exception evennia.prototypes.prototypes.PermissionError[source]
-

Bases: RuntimeError

-
- -
-
-exception evennia.prototypes.prototypes.ValidationError[source]
-

Bases: RuntimeError

-

Raised on prototype validation errors

-
- -
-
-evennia.prototypes.prototypes.homogenize_prototype(prototype, custom_keys=None)[source]
-

Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form.

-
-
Parameters
-
    -
  • prototype (dict) – Prototype.

  • -
  • custom_keys (list, optional) – Custom keys which should not be interpreted as attrs, beyond -the default reserved keys.

  • -
-
-
Returns
-

homogenized (dict)

-
-
Prototype where all non-identified keys grouped as attributes and other

homogenizations like adding missing prototype_keys and setting a default typeclass.

-
-
-

-
-
-
- -
-
-evennia.prototypes.prototypes.load_module_prototypes()[source]
-

This is called by evennia.__init__ as Evennia initializes. It’s important -to do this late so as to not interfere with evennia initialization.

-
- -
-
-class evennia.prototypes.prototypes.DbPrototype(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

This stores a single prototype, in an Attribute prototype.

-
-
-at_script_creation()[source]
-

Only called once, when script is first created.

-
- -
-
-property prototype
-

Make sure to decouple from db!

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.prototypes.prototypes.DbPrototype'
-
- -
-
-typename = 'DbPrototype'
-
- -
- -
-
-evennia.prototypes.prototypes.save_prototype(prototype)[source]
-

Create/Store a prototype persistently.

-
-
Parameters
-

prototype (dict) – The prototype to save. A prototype_key key is -required.

-
-
Returns
-

prototype (dict or None) – The prototype stored using the given kwargs, None if deleting.

-
-
Raises
-

prototypes.ValidationError – If prototype does not validate.

-
-
-
-

Note

-

No edit/spawn locks will be checked here - if this function is called the caller -is expected to have valid permissions.

-
-
- -
-
-evennia.prototypes.prototypes.create_prototype(prototype)
-

Create/Store a prototype persistently.

-
-
Parameters
-

prototype (dict) – The prototype to save. A prototype_key key is -required.

-
-
Returns
-

prototype (dict or None) – The prototype stored using the given kwargs, None if deleting.

-
-
Raises
-

prototypes.ValidationError – If prototype does not validate.

-
-
-
-

Note

-

No edit/spawn locks will be checked here - if this function is called the caller -is expected to have valid permissions.

-
-
- -
-
-evennia.prototypes.prototypes.delete_prototype(prototype_key, caller=None)[source]
-

Delete a stored prototype

-
-
Parameters
-
    -
  • key (str) – The persistent prototype to delete.

  • -
  • caller (Account or Object, optionsl) – Caller aiming to delete a prototype. -Note that no locks will be checked if**caller** is not passed.

  • -
-
-
Returns
-

success (bool) – If deletion worked or not.

-
-
Raises
-

PermissionError – If ‘edit’ lock was not passed or deletion failed for some other reason.

-
-
-
- -
-
-evennia.prototypes.prototypes.search_prototype(key=None, tags=None, require_single=False, return_iterators=False)[source]
-

Find prototypes based on key and/or tags, or all prototypes.

-
-
Keyword Arguments
-
    -
  • key (str) – An exact or partial key to query for.

  • -
  • tags (str or list) – Tag key or keys to query for. These -will always be applied with the ‘db_protototype’ -tag category.

  • -
  • require_single (bool) – If set, raise KeyError if the result -was not found or if there are multiple matches.

  • -
  • return_iterators (bool) – Optimized return for large numbers of db-prototypes. -If set, separate returns of module based prototypes and paginate -the db-prototype return.

  • -
-
-
Returns
-

matches (list)

-
-
Default return, all found prototype dicts. Empty list if

no match was found. Note that if neither key nor tags -were given, all available prototypes will be returned.

-
-
list, queryset: If return_iterators are found, this is a list of

module-based prototypes followed by a paginated queryset of -db-prototypes.

-
-
-

-
-
Raises
-

KeyError – If require_single is True and there are 0 or >1 matches.

-
-
-
-

Note

-

The available prototypes is a combination of those supplied in -PROTOTYPE_MODULES and those stored in the database. Note that if -tags are given and the prototype has no tags defined, it will not -be found as a match.

-
-
- -
-
-evennia.prototypes.prototypes.search_objects_with_prototype(prototype_key)[source]
-

Retrieve all object instances created by a given prototype.

-
-
Parameters
-

prototype_key (str) – The exact (and unique) prototype identifier to query for.

-
-
Returns
-

matches (Queryset) – All matching objects spawned from this prototype.

-
-
-
- -
-
-class evennia.prototypes.prototypes.PrototypeEvMore(caller, *args, session=None, **kwargs)[source]
-

Bases: evennia.utils.evmore.EvMore

-

Listing 1000+ prototypes can be very slow. So we customize EvMore to -display an EvTable per paginated page rather than to try creating an -EvTable for the entire dataset and then paginate it.

-
-
-__init__(caller, *args, session=None, **kwargs)[source]
-

Store some extra properties on the EvMore class

-
- -
-
-init_pages(inp)[source]
-

This will be initialized with a tuple (mod_prototype_list, paginated_db_query) -and we must handle these separately since they cannot be paginated in the same -way. We will build the prototypes so that the db-prototypes come first (they -are likely the most volatile), followed by the mod-prototypes.

-
- -
-
-prototype_paginator(pageno)[source]
-

The listing is separated in db/mod prototypes, so we need to figure out which -one to pick based on the page number. Also, pageno starts from 0.

-
- -
-
-page_formatter(page)[source]
-

Input is a queryset page from django.Paginator

-
- -
- -
-
-evennia.prototypes.prototypes.list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_edit=True, session=None)[source]
-

Collate a list of found prototypes based on search criteria and access.

-
-
Parameters
-
    -
  • caller (Account or Object) – The object requesting the list.

  • -
  • key (str, optional) – Exact or partial prototype key to query for.

  • -
  • tags (str or list, optional) – Tag key or keys to query for.

  • -
  • show_non_use (bool, optional) – Show also prototypes the caller may not use.

  • -
  • show_non_edit (bool, optional) – Show also prototypes the caller may not edit.

  • -
  • session (Session, optional) – If given, this is used for display formatting.

  • -
-
-
Returns
-

PrototypeEvMore – An EvMore subclass optimized for prototype listings. -None: If no matches were found. In this case the caller has already been notified.

-
-
-
- -
-
-evennia.prototypes.prototypes.validate_prototype(prototype, protkey=None, protparents=None, is_prototype_base=True, strict=True, _flags=None)[source]
-

Run validation on a prototype, checking for inifinite regress.

-
-
Parameters
-
    -
  • prototype (dict) – Prototype to validate.

  • -
  • protkey (str, optional) – The name of the prototype definition. If not given, the prototype -dict needs to have the prototype_key field set.

  • -
  • protpartents (dict, optional) – The available prototype parent library. If -note given this will be determined from settings/database.

  • -
  • is_prototype_base (bool, optional) – We are trying to create a new object based on this -object. This means we can’t allow ‘mixin’-style prototypes without typeclass/parent -etc.

  • -
  • strict (bool, optional) – If unset, don’t require needed keys, only check against infinite -recursion etc.

  • -
  • _flags (dict, optional) – Internal work dict that should not be set externally.

  • -
-
-
Raises
-
    -
  • RuntimeError – If prototype has invalid structure.

  • -
  • RuntimeWarning – If prototype has issues that would make it unsuitable to build an object -with (it may still be useful as a mix-in prototype).

  • -
-
-
-
- -
-
-evennia.prototypes.prototypes.protfunc_parser(value, available_functions=None, testing=False, stacktrace=False, **kwargs)[source]
-

Parse a prototype value string for a protfunc and process it.

-

Available protfuncs are specified as callables in one of the modules of -settings.PROTFUNC_MODULES, or specified on the command line.

-
-
Parameters
-
    -
  • value (any) – The value to test for a parseable protfunc. Only strings will be parsed for -protfuncs, all other types are returned as-is.

  • -
  • available_functions (dict, optional) – Mapping of name:protfunction to use for this parsing. -If not set, use default sources.

  • -
  • testing (bool, optional) – Passed to protfunc. If in a testing mode, some protfuncs may -behave differently.

  • -
  • stacktrace (bool, optional) – If set, print the stack parsing process of the protfunc-parser.

  • -
-
-
Keyword Arguments
-
    -
  • session (Session) – Passed to protfunc. Session of the entity spawning the prototype.

  • -
  • protototype (dict) – Passed to protfunc. The dict this protfunc is a part of.

  • -
  • current_key (str) – Passed to protfunc. The key in the prototype that will hold this value.

  • -
  • any (any) – Passed on to the protfunc.

  • -
-
-
Returns
-

testresult (tuple)

-
-
If testing is set, returns a tuple (error, result) where error is

either None or a string detailing the error from protfunc_parser or seen when trying to -run literal_eval on the parsed string.

-
-
any (any): A structure to replace the string on the prototype level. If this is a

callable or a (callable, (args,)) structure, it will be executed as if one had supplied -it to the prototype directly. This structure is also passed through literal_eval so one -can get actual Python primitives out of it (not just strings). It will also identify -eventual object #dbrefs in the output from the protfunc.

-
-
-

-
-
-
- -
-
-evennia.prototypes.prototypes.format_available_protfuncs()[source]
-

Get all protfuncs in a pretty-formatted form.

-
-
Parameters
-

clr (str, optional) – What coloration tag to use.

-
-
-
- -
-
-evennia.prototypes.prototypes.prototype_to_str(prototype)[source]
-

Format a prototype to a nice string representation.

-
-
Parameters
-

prototype (dict) – The prototype.

-
-
-
- -
-
-evennia.prototypes.prototypes.check_permission(prototype_key, action, default=True)[source]
-

Helper function to check access to actions on given prototype.

-
-
Parameters
-
    -
  • prototype_key (str) – The prototype to affect.

  • -
  • action (str) – One of “spawn” or “edit”.

  • -
  • default (str) – If action is unknown or prototype has no locks

  • -
-
-
Returns
-

passes (bool) – If permission for action is granted or not.

-
-
-
- -
-
-evennia.prototypes.prototypes.init_spawn_value(value, validator=None)[source]
-

Analyze the prototype value and produce a value useful at the point of spawning.

-
-
Parameters
-

value (any) –

This can be: -callable - will be called as callable() -(callable, (args,)) - will be called as callable(*args) -other - will be assigned depending on the variable type -validator (callable, optional): If given, this will be called with the value to

-
-

check and guarantee the outcome is of a given type.

-
-

-
-
Returns
-

any (any) – The (potentially pre-processed value to use for this prototype key)

-
-
-
- -
-
-evennia.prototypes.prototypes.value_to_obj_or_any(value)[source]
-

Convert value(s) to Object if possible, otherwise keep original value

-
- -
-
-evennia.prototypes.prototypes.value_to_obj(value, force=True)[source]
-

Always convert value(s) to Object, or None

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.prototypes.spawner.html b/docs/0.9.5/api/evennia.prototypes.spawner.html deleted file mode 100644 index b1e449a59b..0000000000 --- a/docs/0.9.5/api/evennia.prototypes.spawner.html +++ /dev/null @@ -1,546 +0,0 @@ - - - - - - - - - evennia.prototypes.spawner — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.prototypes.spawner

-

Spawner

-

The spawner takes input files containing object definitions in -dictionary forms. These use a prototype architecture to define -unique objects without having to make a Typeclass for each.

-

There main function is spawn(*prototype), where the prototype -is a dictionary like this:

-
from evennia.prototypes import prototypes, spawner
-
-prot = {
- "prototype_key": "goblin",
- "typeclass": "types.objects.Monster",
- "key": "goblin grunt",
- "health": lambda: randint(20,30),
- "resists": ["cold", "poison"],
- "attacks": ["fists"],
- "weaknesses": ["fire", "light"]
- "tags": ["mob", "evil", ('greenskin','mob')]
- "attrs": [("weapon", "sword")]
-}
-# spawn something with the prototype
-goblin = spawner.spawn(prot)
-
-# make this into a db-saved prototype (optional)
-prot = prototypes.create_prototype(prot)
-
-
-
-
Possible keywords are:
-
prototype_key (str): name of this prototype. This is used when storing prototypes and should

be unique. This should always be defined but for prototypes defined in modules, the -variable holding the prototype dict will become the prototype_key if it’s not explicitly -given.

-
-
-

prototype_desc (str, optional): describes prototype in listings -prototype_locks (str, optional): locks for restricting access to this prototype. Locktypes

-
-

supported are ‘edit’ and ‘use’.

-
-
-
prototype_tags(list, optional): List of tags or tuples (tag, category) used to group prototype

in listings

-
-
prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent prototype, or

a list of parents, for multiple left-to-right inheritance.

-
-
-

prototype: Deprecated. Same meaning as ‘parent’.

-
-
typeclass (str or callable, optional): if not set, will use typeclass of parent prototype or use

settings.BASE_OBJECT_TYPECLASS

-
-
key (str or callable, optional): the name of the spawned object. If not given this will set to a

random hash

-
-
-

location (obj, str or callable, optional): location of the object - a valid object or #dbref -home (obj, str or callable, optional): valid object or #dbref -destination (obj, str or callable, optional): only valid for exits (object or #dbref)

-

permissions (str, list or callable, optional): which permissions for spawned object to have -locks (str or callable, optional): lock-string for the spawned object -aliases (str, list or callable, optional): Aliases for the spawned object -exec (str or callable, optional): this is a string of python code to execute or a list of such

-
-

codes. This can be used e.g. to trigger custom handlers on the object. The execution -namespace contains ‘evennia’ for the library and ‘obj’. All default spawn commands limit -this functionality to Developer/superusers. Usually it’s better to use callables or -prototypefuncs instead of this.

-
-
-
tags (str, tuple, list or callable, optional): string or list of strings or tuples

(tagstr, category). Plain strings will be result in tags with no category (default tags).

-
-
attrs (tuple, list or callable, optional): tuple or list of tuples of Attributes to add. This

form allows more complex Attributes to be set. Tuples at least specify (key, value) -but can also specify up to (key, value, category, lockstring). If you want to specify a -lockstring but not a category, set the category to None.

-
-
-

ndb_<name> (any): value of a nattribute (ndb_ is stripped) - this is of limited use. -other (any): any other name is interpreted as the key of an Attribute with

-
-

its value. Such Attributes have no categories.

-
-
-
-

Each value can also be a callable that takes no arguments. It should -return the value to enter into the field and will be called every time -the prototype is used to spawn an object. Note, if you want to store -a callable in an Attribute, embed it in a tuple to the args keyword.

-

By specifying the “prototype_parent” key, the prototype becomes a child of -the given prototype, inheritng all prototype slots it does not explicitly -define itself, while overloading those that it does specify.

-
import random
-
-
-{
- "prototype_key": "goblin_wizard",
- "prototype_parent": "GOBLIN",
- "key": "goblin wizard",
- "spells": ["fire ball", "lighting bolt"]
- }
-
-GOBLIN_ARCHER = {
- "prototype_parent": "GOBLIN",
- "key": "goblin archer",
- "attack_skill": (random, (5, 10))"
- "attacks": ["short bow"]
-}
-
-
-

One can also have multiple prototypes. These are inherited from the -left, with the ones further to the right taking precedence.

-
ARCHWIZARD = {
- "attack": ["archwizard staff", "eye of doom"]
-
-GOBLIN_ARCHWIZARD = {
- "key" : "goblin archwizard"
- "prototype_parent": ("GOBLIN_WIZARD", "ARCHWIZARD"),
-}
-
-
-

The goblin archwizard will have some different attacks, but will -otherwise have the same spells as a goblin wizard who in turn shares -many traits with a normal goblin.

-

Storage mechanism:

-

This sets up a central storage for prototypes. The idea is to make these -available in a repository for buildiers to use. Each prototype is stored -in a Script so that it can be tagged for quick sorting/finding and locked for limiting -access.

-

This system also takes into consideration prototypes defined and stored in modules. -Such prototypes are considered ‘read-only’ to the system and can only be modified -in code. To replace a default prototype, add the same-name prototype in a -custom module read later in the settings.PROTOTYPE_MODULES list. To remove a default -prototype, override its name with an empty dict.

-
-
-class evennia.prototypes.spawner.Unset[source]
-

Bases: object

-

Helper class representing a non-set diff element.

-
- -
-
-evennia.prototypes.spawner.flatten_prototype(prototype, validate=False)[source]
-

Produce a ‘flattened’ prototype, where all prototype parents in the inheritance tree have been -merged into a final prototype.

-
-
Parameters
-
    -
  • prototype (dict) – Prototype to flatten. Its prototype_parent field will be parsed.

  • -
  • validate (bool, optional) – Validate for valid keys etc.

  • -
-
-
Returns
-

flattened (dict) – The final, flattened prototype.

-
-
-
- -
-
-evennia.prototypes.spawner.prototype_from_object(obj)[source]
-

Guess a minimal prototype from an existing object.

-
-
Parameters
-

obj (Object) – An object to analyze.

-
-
Returns
-

prototype (dict) – A prototype estimating the current state of the object.

-
-
-
- -
-
-evennia.prototypes.spawner.prototype_diff(prototype1, prototype2, maxdepth=2, homogenize=False, implicit_keep=False)[source]
-

A ‘detailed’ diff specifies differences down to individual sub-sections -of the prototype, like individual attributes, permissions etc. It is used -by the menu to allow a user to customize what should be kept.

-
-
Parameters
-
    -
  • prototype1 (dict) – Original prototype.

  • -
  • prototype2 (dict) – Comparison prototype.

  • -
  • maxdepth (int, optional) – The maximum depth into the diff we go before treating the elements -of iterables as individual entities to compare. This is important since a single -attr/tag (for example) are represented by a tuple.

  • -
  • homogenize (bool, optional) – Auto-homogenize both prototypes for the best comparison. -This is most useful for displaying.

  • -
  • implicit_keep (bool, optional) – If set, the resulting diff will assume KEEP unless the new -prototype explicitly change them. That is, if a key exists in prototype1 and -not in prototype2, it will not be REMOVEd but set to KEEP instead. This is particularly -useful for auto-generated prototypes when updating objects.

  • -
-
-
Returns
-

diff (dict)

-
-
A structure detailing how to convert prototype1 to prototype2. All

nested structures are dicts with keys matching either the prototype’s matching -key or the first element in the tuple describing the prototype value (so for -a tag tuple (tagname, category) the second-level key in the diff would be tagname). -The the bottom level of the diff consist of tuples (old, new, instruction), where -instruction can be one of “REMOVE”, “ADD”, “UPDATE” or “KEEP”.

-
-
-

-
-
-
- -
-
-evennia.prototypes.spawner.flatten_diff(diff)[source]
-

For spawning, a ‘detailed’ diff is not necessary, rather we just want instructions on how to -handle each root key.

-
-
Parameters
-

diff (dict) – Diff produced by prototype_diff and -possibly modified by the user. Note that also a pre-flattened diff will come out -unchanged by this function.

-
-
Returns
-

flattened_diff (dict)

-
-
A flat structure detailing how to operate on each

root component of the prototype.

-
-
-

-
-
-

Notes

-
-
The flattened diff has the following possible instructions:

UPDATE, REPLACE, REMOVE

-
-
-

Many of the detailed diff’s values can hold nested structures with their own -individual instructions. A detailed diff can have the following instructions:

-
-

REMOVE, ADD, UPDATE, KEEP

-
-
-
Here’s how they are translated:
    -
  • All REMOVE -> REMOVE

  • -
  • All ADD|UPDATE -> UPDATE

  • -
  • All KEEP -> KEEP

  • -
  • Mix KEEP, UPDATE, ADD -> UPDATE

  • -
  • Mix REMOVE, KEEP, UPDATE, ADD -> REPLACE

  • -
-
-
-
- -
-
-evennia.prototypes.spawner.prototype_diff_from_object(prototype, obj, implicit_keep=True)[source]
-

Get a simple diff for a prototype compared to an object which may or may not already have a -prototype (or has one but changed locally). For more complex migratations a manual diff may be -needed.

-
-
Parameters
-
    -
  • prototype (dict) – New prototype.

  • -
  • obj (Object) – Object to compare prototype against.

  • -
-
-
Returns
-

diff (dict) – Mapping for every prototype key: {“keyname”: “REMOVE|UPDATE|KEEP”, …} -obj_prototype (dict): The prototype calculated for the given object. The diff is how to

-
-

convert this prototype into the new prototype.

-
-
-
implicit_keep (bool, optional): This is usually what one wants for object updating. When

set, this means the prototype diff will assume KEEP on differences -between the object-generated prototype and that which is not explicitly set in the -new prototype. This means e.g. that even though the object has a location, and the -prototype does not specify the location, it will not be unset.

-
-
-

-
-
-

Notes

-

The diff is on the following form:

-
-
-
{“key”: (old, new, “KEEP|REPLACE|UPDATE|REMOVE”),
-
“attrs”: {“attrkey”: (old, new, “KEEP|REPLACE|UPDATE|REMOVE”),

“attrkey”: (old, new, “KEEP|REPLACE|UPDATE|REMOVE”), …},

-
-
-

“aliases”: {“aliasname”: (old, new, “KEEP…”, …}, -… }

-
-
-
-
- -
-
-evennia.prototypes.spawner.format_diff(diff, minimal=True)[source]
-

Reformat a diff for presentation. This is a shortened version -of the olc _format_diff_text_and_options without the options.

-
-
Parameters
-
    -
  • diff (dict) – A diff as produced by prototype_diff.

  • -
  • minimal (bool, optional) – Only show changes (remove KEEPs)

  • -
-
-
Returns
-

texts (str) – The formatted text.

-
-
-
- -
-
-evennia.prototypes.spawner.batch_update_objects_with_prototype(prototype, diff=None, objects=None, exact=False)[source]
-

Update existing objects with the latest version of the prototype.

-
-
Parameters
-
    -
  • prototype (str or dict) – Either the prototype_key to use or the -prototype dict itself.

  • -
  • diff (dict, optional) – This a diff structure that describes how to update the protototype. -If not given this will be constructed from the first object found.

  • -
  • objects (list, optional) – List of objects to update. If not given, query for these -objects using the prototype’s prototype_key.

  • -
  • exact (bool, optional) – By default (False), keys not explicitly in the prototype will -not be applied to the object, but will be retained as-is. This is usually what is -expected - for example, one usually do not want to remove the object’s location even -if it’s not set in the prototype. With exact=True, all un-specified properties of the -objects will be removed if they exist. This will lead to a more accurate 1:1 correlation -between the object and the prototype but is usually impractical.

  • -
-
-
Returns
-

changed (int) – The number of objects that had changes applied to them.

-
-
-
- -
-
-evennia.prototypes.spawner.batch_create_object(*objparams)[source]
-

This is a cut-down version of the create_object() function, -optimized for speed. It does NOT check and convert various input -so make sure the spawned Typeclass works before using this!

-
-
Parameters
-

objsparams (tuple) –

Each paremter tuple will create one object instance using the parameters -within. -The parameters should be given in the following order:

-
-
    -
  • create_kwargs (dict): For use as new_obj = ObjectDB(**create_kwargs).

  • -
  • permissions (str): Permission string used with new_obj.batch_add(permission).

  • -
  • lockstring (str): Lockstring used with new_obj.locks.add(lockstring).

  • -
  • -
    aliases (list): A list of alias strings for

    adding with new_object.aliases.batch_add(*aliases).

    -
    -
    -
  • -
  • -
    nattributes (list): list of tuples (key, value) to be loop-added to

    add with new_obj.nattributes.add(*tuple).

    -
    -
    -
  • -
  • -
    attributes (list): list of tuples (key, value[,category[,lockstring]]) for

    adding with new_obj.attributes.batch_add(*attributes).

    -
    -
    -
  • -
  • -
    tags (list): list of tuples (key, category) for adding

    with new_obj.tags.batch_add(*tags).

    -
    -
    -
  • -
  • -
    execs (list): Code strings to execute together with the creation
    -
    of each object. They will be executed with evennia and obj

    (the newly created object) available in the namespace. Execution -will happend after all other properties have been assigned and -is intended for calling custom handlers etc.

    -
    -
    -
    -
    -
  • -
-
-

-
-
Returns
-

objects (list) – A list of created objects

-
-
-

Notes

-

The exec list will execute arbitrary python code so don’t allow this to be available to -unprivileged users!

-
- -
-
-evennia.prototypes.spawner.spawn(*prototypes, **kwargs)[source]
-

Spawn a number of prototyped objects.

-
-
Parameters
-

prototypes (str or dict) – Each argument should either be a -prototype_key (will be used to find the prototype) or a full prototype -dictionary. These will be batched-spawned as one object each.

-
-
Keyword Arguments
-
    -
  • prototype_modules (str or list) – A python-path to a prototype -module, or a list of such paths. These will be used to build -the global protparents dictionary accessible by the input -prototypes. If not given, it will instead look for modules -defined by settings.PROTOTYPE_MODULES.

  • -
  • prototype_parents (dict) – A dictionary holding a custom -prototype-parent dictionary. Will overload same-named -prototypes from prototype_modules.

  • -
  • return_parents (bool) – Return a dict of the entire prototype-parent tree -available to this prototype (no object creation happens). This is a -merged result between the globally found protparents and whatever -custom prototype_parents are given to this function.

  • -
  • only_validate (bool) – Only run validation of prototype/parents -(no object creation) and return the create-kwargs.

  • -
-
-
Returns
-

object (Object, dict or list)

-
-
Spawned object(s). If only_validate is given, return

a list of the creation kwargs to build the object(s) without actually creating it. If -return_parents is set, instead return dict of prototype parents.

-
-
-

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.admin.html b/docs/0.9.5/api/evennia.scripts.admin.html deleted file mode 100644 index f20c6f4690..0000000000 --- a/docs/0.9.5/api/evennia.scripts.admin.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - - - evennia.scripts.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts.admin

-
-
-class evennia.scripts.admin.ScriptTagInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.TagInline

-

Inline script tags.

-
-
-model
-

alias of evennia.scripts.models.ScriptDB_db_tags

-
- -
-
-related_field = 'scriptdb'
-
- -
-
-property media
-
- -
- -
-
-class evennia.scripts.admin.ScriptAttributeInline(parent_model, admin_site)[source]
-

Bases: evennia.typeclasses.admin.AttributeInline

-

Inline attribute tags.

-
-
-model
-

alias of evennia.scripts.models.ScriptDB_db_attributes

-
- -
-
-related_field = 'scriptdb'
-
- -
-
-property media
-
- -
- -
-
-class evennia.scripts.admin.ScriptDBAdmin(model, admin_site)[source]
-

Bases: django.contrib.admin.options.ModelAdmin

-

Displaying the main Script page.

-
-
-list_display = ('id', 'db_key', 'db_typeclass_path', 'db_obj', 'db_interval', 'db_repeats', 'db_persistent')
-
- -
- -
- -
-
-ordering = ['db_obj', 'db_typeclass_path']
-
- -
-
-search_fields = ['^db_key', 'db_typeclass_path']
-
- -
-
-save_as = True
-
- -
-
-save_on_top = True
-
- -
- -
- -
-
-raw_id_fields = ('db_obj',)
-
- -
-
-fieldsets = ((None, {'fields': (('db_key', 'db_typeclass_path'), 'db_interval', 'db_repeats', 'db_start_delay', 'db_persistent', 'db_obj')}),)
-
- -
-
-inlines = [<class 'evennia.scripts.admin.ScriptTagInline'>, <class 'evennia.scripts.admin.ScriptAttributeInline'>]
-
- -
-
-save_model(request, obj, form, change)[source]
-

Model-save hook.

-
-
Parameters
-
    -
  • request (Request) – Incoming request.

  • -
  • obj (Object) – Database object.

  • -
  • form (Form) – Form instance.

  • -
  • change (bool) – If this is a change or a new object.

  • -
-
-
-
- -
-
-property media
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.html b/docs/0.9.5/api/evennia.scripts.html deleted file mode 100644 index 96fd00e587..0000000000 --- a/docs/0.9.5/api/evennia.scripts.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - evennia.scripts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts

-

This sub-package holds the Scripts system. Scripts are database -entities that can store data both in connection to Objects and Accounts -or globally. They may also have a timer-component to execute various -timed effects.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.manager.html b/docs/0.9.5/api/evennia.scripts.manager.html deleted file mode 100644 index 84e83bb6cf..0000000000 --- a/docs/0.9.5/api/evennia.scripts.manager.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - - - - - evennia.scripts.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts.manager

-

The custom manager for Scripts.

-
-
-class evennia.scripts.manager.ScriptManager(*args, **kwargs)[source]
-

Bases: evennia.scripts.manager.ScriptDBManager, evennia.typeclasses.managers.TypeclassManager

-
- -
-
-class evennia.scripts.manager.ScriptDBManager(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.managers.TypedObjectManager

-

This Scriptmanager implements methods for searching -and manipulating Scripts directly from the database.

-

Evennia-specific search methods (will return Typeclasses or -lists of Typeclasses, whereas Django-general methods will return -Querysets or database objects).

-

dbref (converter) -get_id (or dbref_search) -get_dbref_range -object_totals -typeclass_search -get_all_scripts_on_obj -get_all_scripts -delete_script -remove_non_persistent -validate -script_search (equivalent to evennia.search_script) -copy_script

-
-
-get_all_scripts_on_obj(obj, key=None)[source]
-

Find all Scripts related to a particular object.

-
-
Parameters
-
    -
  • obj (Object) – Object whose Scripts we are looking for.

  • -
  • key (str, optional) – Script identifier - can be given as a -dbref or name string. If given, only scripts matching the -key on the object will be returned.

  • -
-
-
Returns
-

matches (list) – Matching scripts.

-
-
-
- -
-
-get_all_scripts(key=None)[source]
-

Get all scripts in the database.

-
-
Parameters
-

key (str or int, optional) – Restrict result to only those -with matching key or dbref.

-
-
Returns
-

scripts (list) – All scripts found, or those matching key.

-
-
-
- -
-
-delete_script(dbref)[source]
-

This stops and deletes a specific script directly from the -script database.

-
-
Parameters
-

dbref (int) – Database unique id.

-
-
-

Notes

-

This might be needed for global scripts not tied to a -specific game object

-
- -
-
-remove_non_persistent(obj=None)[source]
-

This cleans up the script database of all non-persistent -scripts. It is called every time the server restarts.

-
-
Parameters
-

obj (Object, optional) – Only remove non-persistent scripts -assigned to this object.

-
-
-
- -
-
-validate(scripts=None, obj=None, key=None, dbref=None, init_mode=None)[source]
-

This will step through the script database and make sure -all objects run scripts that are still valid in the context -they are in. This is called by the game engine at regular -intervals but can also be initiated by player scripts.

-

Only one of the arguments are supposed to be supplied -at a time, since they are exclusive to each other.

-
-
Parameters
-
    -
  • scripts (list, optional) – A list of script objects to -validate.

  • -
  • obj (Object, optional) – Validate only scripts defined on -this object.

  • -
  • key (str) – Validate only scripts with this key.

  • -
  • dbref (int) – Validate only the single script with this -particular id.

  • -
  • init_mode (str, optional) – This is used during server -upstart and can have three values: -- None (no init mode). Called during run. -- “reset” - server reboot. Kill non-persistent scripts -- “reload” - server reload. Keep non-persistent scripts.

  • -
-
-
Returns
-

nr_started, nr_stopped (tuple)

-
-
Statistics on how many objects

where started and stopped.

-
-
-

-
-
-

Notes

-

This method also makes sure start any scripts it validates -which should be harmless, since already-active scripts have -the property ‘is_running’ set and will be skipped.

-
- -
-
-search_script(ostring, obj=None, only_timed=False, typeclass=None)[source]
-

Search for a particular script.

-
-
Parameters
-
    -
  • ostring (str) – Search criterion - a script dbef or key.

  • -
  • obj (Object, optional) – Limit search to scripts defined on -this object

  • -
  • only_timed (bool) – Limit search only to scripts that run -on a timer.

  • -
  • typeclass (class or str) – Typeclass or path to typeclass.

  • -
-
-
-
- -
- -

Search for a particular script.

-
-
Parameters
-
    -
  • ostring (str) – Search criterion - a script dbef or key.

  • -
  • obj (Object, optional) – Limit search to scripts defined on -this object

  • -
  • only_timed (bool) – Limit search only to scripts that run -on a timer.

  • -
  • typeclass (class or str) – Typeclass or path to typeclass.

  • -
-
-
-
- -
-
-copy_script(original_script, new_key=None, new_obj=None, new_locks=None)[source]
-

Make an identical copy of the original_script.

-
-
Parameters
-
    -
  • original_script (Script) – The Script to copy.

  • -
  • new_key (str, optional) – Rename the copy.

  • -
  • new_obj (Object, optional) – Place copy on different Object.

  • -
  • new_locks (str, optional) – Give copy different locks from -the original.

  • -
-
-
Returns
-

script_copy (Script)

-
-
A new Script instance, copied from

the original.

-
-
-

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.models.html b/docs/0.9.5/api/evennia.scripts.models.html deleted file mode 100644 index 81bc93ebec..0000000000 --- a/docs/0.9.5/api/evennia.scripts.models.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - - - - evennia.scripts.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts.models

-

Scripts are entities that perform some sort of action, either only -once or repeatedly. They can be directly linked to a particular -Evennia Object or be stand-alonw (in the latter case it is considered -a ‘global’ script). Scripts can indicate both actions related to the -game world as well as pure behind-the-scenes events and effects. -Everything that has a time component in the game (i.e. is not -hard-coded at startup or directly created/controlled by players) is -handled by Scripts.

-

Scripts have to check for themselves that they should be applied at a -particular moment of time; this is handled by the is_valid() hook. -Scripts can also implement at_start and at_end hooks for preparing and -cleaning whatever effect they have had on the game object.

-

Common examples of uses of Scripts:

-
    -
  • Load the default cmdset to the account object’s cmdhandler -when logging in.

  • -
  • Switch to a different state, such as entering a text editor, -start combat or enter a dark room.

  • -
  • Merge a new cmdset with the default one for changing which -commands are available at a particular time

  • -
  • Give the account/object a time-limited bonus/effect

  • -
-
-
-class evennia.scripts.models.ScriptDB(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.models.TypedObject

-

The Script database representation.

-
-
The TypedObject supplies the following (inherited) properties:

key - main name -name - alias for key -typeclass_path - the path to the decorating typeclass -typeclass - auto-linked typeclass -date_created - time stamp of object creation -permissions - perm strings -dbref - #id of object -db - persistent attribute storage -ndb - non-persistent attribute storage

-
-
The ScriptDB adds the following properties:

desc - optional description of script -obj - the object the script is linked to, if any -account - the account the script is linked to (exclusive with obj) -interval - how often script should run -start_delay - if the script should start repeating right away -repeats - how many times the script should repeat -persistent - if script should survive a server reboot -is_active - bool if script is currently running

-
-
-
-
-db_desc
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_obj
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-db_account
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-db_interval
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_start_delay
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_repeats
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_persistent
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_is_active
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-objects = <evennia.scripts.manager.ScriptDBManager object>
-
- -
-
-property obj
-

Property wrapper that homogenizes access to either the -db_account or db_obj field, using the same object property -name.

-
- -
-
-property object
-

Property wrapper that homogenizes access to either the -db_account or db_obj field, using the same object property -name.

-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-property account
-

A wrapper for getting database field db_account.

-
- -
-
-db_account_id
-
- -
-
-db_attributes
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_obj_id
-
- -
-
-db_tags
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-property desc
-

A wrapper for getting database field db_desc.

-
- -
-
-get_next_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=True, **kwargs)
-
- -
-
-get_previous_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=False, **kwargs)
-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-property interval
-

A wrapper for getting database field db_interval.

-
- -
-
-property is_active
-

A wrapper for getting database field db_is_active.

-
- -
-
-path = 'evennia.scripts.models.ScriptDB'
-
- -
-
-property persistent
-

A wrapper for getting database field db_persistent.

-
- -
-
-receiver_script_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-property repeats
-

A wrapper for getting database field db_repeats.

-
- -
-
-sender_script_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-property start_delay
-

A wrapper for getting database field db_start_delay.

-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.monitorhandler.html b/docs/0.9.5/api/evennia.scripts.monitorhandler.html deleted file mode 100644 index ac6d4c4253..0000000000 --- a/docs/0.9.5/api/evennia.scripts.monitorhandler.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - - - - evennia.scripts.monitorhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts.monitorhandler

-

Monitors - catch changes to model fields and Attributes.

-

The MONITOR_HANDLER singleton from this module offers the following -functionality:

-
    -
  • -
    Field-monitor - track a object’s specific database field and perform

    an action whenever that field changes for whatever reason.

    -
    -
    -
  • -
  • -
    Attribute-monitor tracks an object’s specific Attribute and perform

    an action whenever that Attribute changes for whatever reason.

    -
    -
    -
  • -
-
-
-class evennia.scripts.monitorhandler.MonitorHandler[source]
-

Bases: object

-

This is a resource singleton that allows for registering -callbacks for when a field or Attribute is updated (saved).

-
-
-__init__()[source]
-

Initialize the handler.

-
- -
-
-save()[source]
-

Store our monitors to the database. This is called -by the server process.

-

Since dbserialize can’t handle defaultdicts, we convert to an -intermediary save format ((obj,fieldname, idstring, callback, kwargs), …)

-
- -
-
-restore(server_reload=True)[source]
-

Restore our monitors after a reload. This is called -by the server process.

-
-
Parameters
-

server_reload (bool, optional) – If this is False, it means -the server went through a cold reboot and all -non-persistent tickers must be killed.

-
-
-
- -
-
-at_update(obj, fieldname)[source]
-

Called by the field/attribute as it saves.

-
- -
-
-add(obj, fieldname, callback, idstring='', persistent=False, **kwargs)[source]
-

Add monitoring to a given field or Attribute. A field must -be specified with the full db_* name or it will be assumed -to be an Attribute (so db_key, not just key).

-
-
Parameters
-
    -
  • obj (Typeclassed Entity) – The entity on which to monitor a -field or Attribute.

  • -
  • fieldname (str) – Name of field (db_*) or Attribute to monitor.

  • -
  • callback (callable) – A callable on the form **callable(**kwargs), -where kwargs holds keys fieldname and obj.

  • -
  • idstring (str, optional) – An id to separate this monitor from other monitors -of the same field and object.

  • -
  • persistent (bool, optional) – If False, the monitor will survive -a server reload but not a cold restart. This is default.

  • -
-
-
Keyword Arguments
-
    -
  • session (Session) – If this keyword is given, the monitorhandler will -correctly analyze it and remove the monitor if after a reload/reboot -the session is no longer valid.

  • -
  • any (any) – Any other kwargs are passed on to the callback. Remember that -all kwargs must be possible to pickle!

  • -
-
-
-
- -
-
-remove(obj, fieldname, idstring='')[source]
-

Remove a monitor.

-
- -
-
-clear()[source]
-

Delete all monitors.

-
- -
-
-all(obj=None)[source]
-

List all monitors or all monitors of a given object.

-
-
Parameters
-

obj (Object) – The object on which to list all monitors.

-
-
Returns
-

monitors (list) – The handled monitors.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.scripthandler.html b/docs/0.9.5/api/evennia.scripts.scripthandler.html deleted file mode 100644 index b3b8e84ab6..0000000000 --- a/docs/0.9.5/api/evennia.scripts.scripthandler.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - - evennia.scripts.scripthandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts.scripthandler

-

The script handler makes sure to check through all stored scripts to -make sure they are still relevant. A scripthandler is automatically -added to all game objects. You access it through the property -scripts on the game object.

-
-
-class evennia.scripts.scripthandler.ScriptHandler(obj)[source]
-

Bases: object

-

Implements the handler. This sits on each game object.

-
-
-__init__(obj)[source]
-

Set up internal state.

-
-
Parameters
-

obj (Object) – A reference to the object this handler is -attached to.

-
-
-
- -
-
-add(scriptclass, key=None, autostart=True)[source]
-

Add a script to this object.

-
-
Parameters
-
    -
  • scriptclass (Scriptclass, Script or str) – Either a class -object inheriting from DefaultScript, an instantiated -script object or a python path to such a class object.

  • -
  • key (str, optional) – Identifier for the script (often set -in script definition and listings)

  • -
  • autostart (bool, optional) – Start the script upon adding it.

  • -
-
-
-
- -
-
-start(key)[source]
-

Find scripts and force-start them

-
-
Parameters
-

key (str) – The script’s key or dbref.

-
-
Returns
-

nr_started (int) – The number of started scripts found.

-
-
-
- -
-
-get(key)[source]
-

Search scripts on this object.

-
-
Parameters
-

key (str) – Search criterion, the script’s key or dbref.

-
-
Returns
-

scripts (list) – The found scripts matching key.

-
-
-
- -
-
-delete(key=None)[source]
-

Forcibly delete a script from this object.

-
-
Parameters
-

key (str, optional) – A script key or the path to a script (in the -latter case all scripts with this path will be deleted!) -If no key is given, delete all scripts on the object!

-
-
-
- -
-
-stop(key=None)
-

Forcibly delete a script from this object.

-
-
Parameters
-

key (str, optional) – A script key or the path to a script (in the -latter case all scripts with this path will be deleted!) -If no key is given, delete all scripts on the object!

-
-
-
- -
-
-all()[source]
-

Get all scripts stored in this handler.

-
- -
-
-validate(init_mode=False)[source]
-

Runs a validation on this object’s scripts only. This should -be called regularly to crank the wheels.

-
-
Parameters
-

init_mode (str, optional) –

    -
  • This is used during server

  • -
-

upstart and can have three values: -- False (no init mode). Called during run. -- “reset” - server reboot. Kill non-persistent scripts -- “reload” - server reload. Keep non-persistent scripts.

-

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.scripts.html b/docs/0.9.5/api/evennia.scripts.scripts.html deleted file mode 100644 index 53a29de436..0000000000 --- a/docs/0.9.5/api/evennia.scripts.scripts.html +++ /dev/null @@ -1,463 +0,0 @@ - - - - - - - - - evennia.scripts.scripts — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts.scripts

-

This module defines Scripts, out-of-character entities that can store -data both on themselves and on other objects while also having the -ability to run timers.

-
-
-class evennia.scripts.scripts.DefaultScript(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.ScriptBase

-

This is the base TypeClass for all Scripts. Scripts describe -events, timers and states in game, they can have a time component -or describe a state that changes under certain conditions.

-
-
-classmethod create(key, **kwargs)[source]
-

Provides a passthrough interface to the utils.create_script() function.

-
-
Parameters
-

key (str) – Name of the new object.

-
-
Returns
-

object (Object) – A newly created object of the given typeclass. -errors (list): A list of errors in string form, if any.

-
-
-
- -
-
-at_script_creation()[source]
-

Only called once, when script is first created.

-
- -
-
-time_until_next_repeat()[source]
-

Get time until the script fires it at_repeat hook again.

-
-
Returns
-

next (int)

-
-
Time in seconds until the script runs again.

If not a timed script, return None.

-
-
-

-
-
-

Notes

-

This hook is not used in any way by the script’s stepping -system; it’s only here for the user to be able to check in -on their scripts and when they will next be run.

-
- -
-
-remaining_repeats()[source]
-

Get the number of returning repeats for limited Scripts.

-
-
Returns
-

remaining (int or None) –

-
-
The number of repeats

remaining until the Script stops. Returns None -if it has unlimited repeats.

-
-
-

-
-
-
- -
-
-at_idmapper_flush()[source]
-

If we’re flushing this object, make sure the LoopingCall is gone too

-
- -
-
-start(force_restart=False)[source]
-

Called every time the script is started (for persistent -scripts, this is usually once every server start)

-
-
Parameters
-

force_restart (bool, optional) – Normally an already -started script will not be started again. if -force_restart=True, the script will always restart -the script, regardless of if it has started before.

-
-
Returns
-

result (int)

-
-
0 or 1 depending on if the script successfully

started or not. Used in counting.

-
-
-

-
-
-
- -
-
-stop(kill=False)[source]
-

Called to stop the script from running. This also deletes the -script.

-
-
Parameters
-

kill (bool, optional) –

    -
  • Stop the script without

  • -
-

calling any relevant script hooks.

-

-
-
Returns
-

result (int)

-
-
0 if the script failed to stop, 1 otherwise.

Used in counting.

-
-
-

-
-
-
- -
-
-pause(manual_pause=True)[source]
-

This stops a running script and stores its active state. -It WILL NOT call the at_stop() hook.

-
- -
-
-unpause(manual_unpause=True)[source]
-

Restart a paused script. This WILL call the at_start() hook.

-
-
Parameters
-

manual_unpause (bool, optional) – This is False if unpause is -called by the server reload/reset mechanism.

-
-
Returns
-

result (bool) – True if unpause was triggered, False otherwise.

-
-
Raises
-

RuntimeError – If trying to automatically resart this script -(usually after a reset/reload), but it was manually paused, -and so should not the auto-unpaused.

-
-
-
- -
-
-restart(interval=None, repeats=None, start_delay=None)[source]
-

Restarts an already existing/running Script from the -beginning, optionally using different settings. This will -first call the stop hooks, and then the start hooks again. -:param interval: Allows for changing the interval

-
-

of the Script. Given in seconds. if None, will use the already stored interval.

-
-
-
Parameters
-
    -
  • repeats (int, optional) – The number of repeats. If unset, will -use the previous setting.

  • -
  • start_delay (bool, optional) – If we should wait interval seconds -before starting or not. If None, re-use the previous setting.

  • -
-
-
-
- -
-
-reset_callcount(value=0)[source]
-

Reset the count of the number of calls done.

-
-
Parameters
-

value (int, optional) – The repeat value to reset to. Default -is to set it all the way back to 0.

-
-
-

Notes

-

This is only useful if repeats != 0.

-
- -
-
-force_repeat()[source]
-

Fire a premature triggering of the script callback. This -will reset the timer and count down repeats as if the script -had fired normally.

-
- -
-
-is_valid()[source]
-

Is called to check if the script is valid to run at this time. -Should return a boolean. The method is assumed to collect all -needed information from its related self.obj.

-
- -
-
-at_start(**kwargs)[source]
-

Called whenever the script is started, which for persistent -scripts is at least once every server start. It will also be -called when starting again after a pause (such as after a -server reload)

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
- -
-
-at_repeat(**kwargs)[source]
-

Called repeatedly if this Script is set to repeat regularly.

-
-
Parameters
-

**kwargs (dict) – Arbitrary, optional arguments for users -overriding the call (unused by default).

-
-
-
- -
-
-at_stop(**kwargs)[source]
-

Called whenever when it’s time for this script to stop (either -because is_valid returned False or it runs out of iterations)

-
-
Args
-
**kwargs (dict): Arbitrary, optional arguments for users

overriding the call (unused by default).

-
-
-
-
-
- -
-
-at_server_reload()[source]
-

This hook is called whenever the server is shutting down for -restart/reboot. If you want to, for example, save -non-persistent properties across a restart, this is the place -to do it.

-
- -
-
-at_server_shutdown()[source]
-

This hook is called whenever the server is shutting down fully -(i.e. not for a restart).

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.ScriptBase.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.ScriptBase.MultipleObjectsReturned

-
- -
-
-path = 'evennia.scripts.scripts.DefaultScript'
-
- -
-
-typename = 'DefaultScript'
-
- -
- -
-
-class evennia.scripts.scripts.DoNothing(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

A script that does nothing. Used as default fallback.

-
-
-at_script_creation()[source]
-

Setup the script

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.scripts.scripts.DoNothing'
-
- -
-
-typename = 'DoNothing'
-
- -
- -
-
-class evennia.scripts.scripts.Store(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

Simple storage script

-
-
-at_script_creation()[source]
-

Setup the script

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.scripts.scripts.Store'
-
- -
-
-typename = 'Store'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.taskhandler.html b/docs/0.9.5/api/evennia.scripts.taskhandler.html deleted file mode 100644 index 1e20357f46..0000000000 --- a/docs/0.9.5/api/evennia.scripts.taskhandler.html +++ /dev/null @@ -1,592 +0,0 @@ - - - - - - - - - evennia.scripts.taskhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts.taskhandler

-

Module containing the task handler for Evennia deferred tasks, persistent or not.

-
-
-evennia.scripts.taskhandler.handle_error(*args, **kwargs)[source]
-

Handle errors within deferred objects.

-
- -
-
-class evennia.scripts.taskhandler.TaskHandlerTask(task_id)[source]
-

Bases: object

-

An object to represent a single TaskHandler task.

-
-
Instance Attributes:

task_id (int): the global id for this task -deferred (deferred): a reference to this task’s deferred

-
-
Property Attributes:

paused (bool): check if the deferred instance of a task has been paused. -called(self): A task attribute to check if the deferred instance of a task has been called.

-
-
-
-
-pause()[source]
-

Pause the callback of a task.

-
- -
-
-unpause()[source]
-

Process all callbacks made since pause() was called.

-
- -
-
-do_task()[source]
-

Execute the task (call its callback).

-
- -
-
-call()[source]
-

Call the callback of this task.

-
- -
-
-remove()[source]
-

Remove a task without executing it.

-
- -
-
-cancel()[source]
-

Stop a task from automatically executing.

-
- -
-
-active()[source]
-

Check if a task is active (has not been called yet).

-
- -
-
-exists()[source]
-

Check if a task exists.

-
- -
-
-get_id()[source]
-

Returns the global id for this task. For use with

-
- -
-
-__init__(task_id)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-get_deferred()[source]
-

Return the instance of the deferred the task id is using.

-
-
Returns
-

bool or deferred

-
-
An instance of a deferred or False if there is no task with the id.

None is returned if there is no deferred affiliated with this id.

-
-
-

-
-
-
- -
-
-pause()[source]
-

Pause the callback of a task. -To resume use TaskHandlerTask.unpause

-
- -
-
-unpause()[source]
-

Unpause a task, run the task if it has passed delay time.

-
- -
-
-property paused
-

A task attribute to check if the deferred instance of a task has been paused.

-

This exists to mock usage of a twisted deferred object.

-
-
Returns
-

bool or None

-
-
True if the task was properly paused. None if the task does not have

a deferred instance.

-
-
-

-
-
-
- -
-
-do_task()[source]
-

Execute the task (call its callback). -If calling before timedelay, cancel the deferred instance affliated to this task. -Remove the task from the dictionary of current tasks on a successful -callback.

-
-
Returns
-

bool or any – Set to False if the task does not exist in task -handler. Otherwise it will be the return of the task’s callback.

-
-
-
- -
-
-call()[source]
-

Call the callback of a task. -Leave the task unaffected otherwise. -This does not use the task’s deferred instance. -The only requirement is that the task exist in task handler.

-
-
Returns
-

bool or any – Set to False if the task does not exist in task -handler. Otherwise it will be the return of the task’s callback.

-
-
-
- -
-
-remove()[source]
-

Remove a task without executing it. -Deletes the instance of the task’s deferred.

-
-
Parameters
-

task_id (int) – an existing task ID.

-
-
Returns
-

bool – True if the removal completed successfully.

-
-
-
- -
-
-cancel()[source]
-

Stop a task from automatically executing. -This will not remove the task.

-
-
Returns
-

bool

-
-
True if the cancel completed successfully.

False if the cancel did not complete successfully.

-
-
-

-
-
-
- -
-
-active()[source]
-

Check if a task is active (has not been called yet).

-
-
Returns
-

bool

-
-
True if a task is active (has not been called yet). False if

it is not (has been called) or if the task does not exist.

-
-
-

-
-
-
- -
-
-property called
-

A task attribute to check if the deferred instance of a task has been called.

-

This exists to mock usage of a twisted deferred object. -It will not set to True if Task.call has been called. This only happens if -task’s deferred instance calls the callback.

-
-
Returns
-

bool

-
-
True if the deferred instance of this task has called the callback.

False if the deferred instnace of this task has not called the callback.

-
-
-

-
-
-
- -
-
-exists()[source]
-

Check if a task exists. -Most task handler methods check for existence for you.

-
-
Returns
-

bool – True the task exists False if it does not.

-
-
-
- -
-
-get_id()[source]
-

Returns the global id for this task. For use with -evennia.scripts.taskhandler.TASK_HANDLER.

-
-
Returns
-

task_id (int) – global task id for this task.

-
-
-
- -
- -
-
-class evennia.scripts.taskhandler.TaskHandler[source]
-

Bases: object

-

A light singleton wrapper allowing to access permanent tasks.

-

When utils.delay is called, the task handler is used to create -the task.

-

Task handler will automatically remove uncalled but canceled from task -handler. By default this will not occur until a canceled task -has been uncalled for 60 second after the time it should have been called. -To adjust this time use TASK_HANDLER.stale_timeout. If stale_timeout is 0 -stale tasks will not be automatically removed. -This is not done on a timer. I is done as new tasks are added or the load method is called.

-
-
-__init__()[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-load()[source]
-

Load from the ServerConfig.

-

This should be automatically called when Evennia starts. -It populates self.tasks according to the ServerConfig.

-
- -
-
-clean_stale_tasks()[source]
-

remove uncalled but canceled from task handler.

-

By default this will not occur until a canceled task -has been uncalled for 60 second after the time it should have been called. -To adjust this time use TASK_HANDLER.stale_timeout.

-
- -
-
-save()[source]
-

Save the tasks in ServerConfig.

-
- -
-
-add(timedelay, callback, *args, **kwargs)[source]
-

Add a new task.

-

If the persistent kwarg is truthy: -The callback, args and values for kwarg will be serialized. Type -and attribute errors during the serialization will be logged, -but will not throw exceptions. -For persistent tasks do not use memory references in the callback -function or arguments. After a restart those memory references are no -longer accurate.

-
-
Parameters
-
    -
  • timedelay (int or float) – time in seconds before calling the callback.

  • -
  • callback (function or instance method) – the callback itself

  • -
  • any (any) – any additional positional arguments to send to the callback

  • -
  • *args – positional arguments to pass to callback.

  • -
  • **kwargs

    keyword arguments to pass to callback. -- persistent (bool, optional): persist the task (stores it).

    -
    -

    Persistent key and value is removed from kwargs it will -not be passed to callback.

    -
    -

  • -
-
-
Returns
-

TaskHandlerTask

-
-
An object to represent a task.

Reference evennia.scripts.taskhandler.TaskHandlerTask for complete details.

-
-
-

-
-
-
- -
-
-exists(task_id)[source]
-

Check if a task exists. -Most task handler methods check for existence for you.

-
-
Parameters
-

task_id (int) – an existing task ID.

-
-
Returns
-

bool – True the task exists False if it does not.

-
-
-
- -
-
-active(task_id)[source]
-

Check if a task is active (has not been called yet).

-
-
Parameters
-

task_id (int) – an existing task ID.

-
-
Returns
-

bool

-
-
True if a task is active (has not been called yet). False if

it is not (has been called) or if the task does not exist.

-
-
-

-
-
-
- -
-
-cancel(task_id)[source]
-

Stop a task from automatically executing. -This will not remove the task.

-
-
Parameters
-

task_id (int) – an existing task ID.

-
-
Returns
-

bool

-
-
True if the cancel completed successfully.

False if the cancel did not complete successfully.

-
-
-

-
-
-
- -
-
-remove(task_id)[source]
-

Remove a task without executing it. -Deletes the instance of the task’s deferred.

-
-
Parameters
-

task_id (int) – an existing task ID.

-
-
Returns
-

bool – True if the removal completed successfully.

-
-
-
- -
-
-clear(save=True, cancel=True)[source]
-

clear all tasks. -By default tasks are canceled and removed from the database also.

-
-
Parameters
-
    -
  • save=True (bool) – Should changes to persistent tasks be saved to database.

  • -
  • cancel=True (bool) – Cancel scheduled tasks before removing it from task handler.

  • -
-
-
Returns
-

True (bool) – if the removal completed successfully.

-
-
-
- -
-
-call_task(task_id)[source]
-

Call the callback of a task. -Leave the task unaffected otherwise. -This does not use the task’s deferred instance. -The only requirement is that the task exist in task handler.

-
-
Parameters
-

task_id (int) – an existing task ID.

-
-
Returns
-

bool or any – Set to False if the task does not exist in task -handler. Otherwise it will be the return of the task’s callback.

-
-
-
- -
-
-do_task(task_id)[source]
-

Execute the task (call its callback). -If calling before timedelay cancel the deferred instance affliated to this task. -Remove the task from the dictionary of current tasks on a successful -callback.

-
-
Parameters
-

task_id (int) – a valid task ID.

-
-
Returns
-

bool or any – Set to False if the task does not exist in task -handler. Otherwise it will be the return of the task’s callback.

-
-
-
- -
-
-get_deferred(task_id)[source]
-

Return the instance of the deferred the task id is using.

-
-
Parameters
-

task_id (int) – a valid task ID.

-
-
Returns
-

bool or deferred

-
-
An instance of a deferred or False if there is no task with the id.

None is returned if there is no deferred affiliated with this id.

-
-
-

-
-
-
- -
-
-create_delays()[source]
-

Create the delayed tasks for the persistent tasks. -This method should be automatically called when Evennia starts.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.scripts.tickerhandler.html b/docs/0.9.5/api/evennia.scripts.tickerhandler.html deleted file mode 100644 index 64d6df6560..0000000000 --- a/docs/0.9.5/api/evennia.scripts.tickerhandler.html +++ /dev/null @@ -1,456 +0,0 @@ - - - - - - - - - evennia.scripts.tickerhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.scripts.tickerhandler

-

TickerHandler

-

This implements an efficient Ticker which uses a subscription -model to ‘tick’ subscribed objects at regular intervals.

-

The ticker mechanism is used by importing and accessing -the instantiated TICKER_HANDLER instance in this module. This -instance is run by the server; it will save its status across -server reloads and be started automaticall on boot.

-

Example:

-
from evennia.scripts.tickerhandler import TICKER_HANDLER
-
-# call tick myobj.at_tick(*args, **kwargs) every 15 seconds
-TICKER_HANDLER.add(15, myobj.at_tick, *args, **kwargs)
-
-
-

You supply the interval to tick and a callable to call regularly with -any extra args/kwargs. The callable should either be a stand-alone -function in a module or the method on a typeclassed entity (that -is, on an object that can be safely and stably returned from the -database). Functions that are dynamically created or sits on -in-memory objects cannot be used by the tickerhandler (there is no way -to reference them safely across reboots and saves).

-

The handler will transparently set -up and add new timers behind the scenes to tick at given intervals, -using a TickerPool - all callables with the same interval will share -the interval ticker.

-

To remove:

-
TICKER_HANDLER.remove(15, myobj.at_tick)
-
-
-

Both interval and callable must be given since a single object can be subscribed -to many different tickers at the same time. You can also supply idstring -as an identifying string if you ever want to tick the callable at the same interval -but with different arguments (args/kwargs are not used for identifying the ticker). There -is also persistent=False if you don’t want to make a ticker that don’t survive a reload. -If either or both idstring or persistent has been changed from their defaults, they -must be supplied to the TICKER_HANDLER.remove call to properly identify the ticker -to remove.

-

The TickerHandler’s functionality can be overloaded by modifying the -Ticker class and then changing TickerPool and TickerHandler to use the -custom classes

-
class MyTicker(Ticker):
-    # [doing custom stuff]
-
-class MyTickerPool(TickerPool):
-    ticker_class = MyTicker
-class MyTickerHandler(TickerHandler):
-    ticker_pool_class = MyTickerPool
-
-
-

If one wants to duplicate TICKER_HANDLER’s auto-saving feature in -a custom handler one can make a custom AT_STARTSTOP_MODULE entry to -call the handler’s save() and restore() methods when the server reboots.

-
-
-class evennia.scripts.tickerhandler.Ticker(interval)[source]
-

Bases: object

-

Represents a repeatedly running task that calls -hooks repeatedly. Overload _callback to change the -way it operates.

-
-
-__init__(interval)[source]
-

Set up the ticker

-
-
Parameters
-

interval (int) – The stepping interval.

-
-
-
- -
-
-validate(start_delay=None)[source]
-

Start/stop the task depending on how many subscribers we have -using it.

-
-
Parameters
-

start_delay (int) – Time to way before starting.

-
-
-
- -
-
-add(store_key, *args, **kwargs)[source]
-

Sign up a subscriber to this ticker. -:param store_key: Unique storage hash for this ticker subscription. -:type store_key: str -:param args: Arguments to call the hook method with. -:type args: any, optional

-
-
Keyword Arguments
-

_start_delay (int) – If set, this will be -used to delay the start of the trigger instead of -interval.

-
-
-
- -
-
-remove(store_key)[source]
-

Unsubscribe object from this ticker

-
-
Parameters
-

store_key (str) – Unique store key.

-
-
-
- -
-
-stop()[source]
-

Kill the Task, regardless of subscriptions.

-
- -
- -
-
-class evennia.scripts.tickerhandler.TickerPool[source]
-

Bases: object

-

This maintains a pool of -evennia.scripts.scripts.ExtendedLoopingCall tasks for calling -subscribed objects at given times.

-
-
-ticker_class
-

alias of Ticker

-
- -
-
-__init__()[source]
-

Initialize the pool.

-
- -
-
-add(store_key, *args, **kwargs)[source]
-

Add new ticker subscriber.

-
-
Parameters
-
    -
  • store_key (str) – Unique storage hash.

  • -
  • args (any, optional) – Arguments to send to the hook method.

  • -
-
-
-
- -
-
-remove(store_key)[source]
-

Remove subscription from pool.

-
-
Parameters
-

store_key (str) – Unique storage hash to remove

-
-
-
- -
-
-stop(interval=None)[source]
-

Stop all scripts in pool. This is done at server reload since -restoring the pool will automatically re-populate the pool.

-
-
Parameters
-

interval (int, optional) – Only stop tickers with this -interval.

-
-
-
- -
- -
-
-class evennia.scripts.tickerhandler.TickerHandler(save_name='ticker_storage')[source]
-

Bases: object

-

The Tickerhandler maintains a pool of tasks for subscribing -objects to various tick rates. The pool maintains creation -instructions and and re-applies them at a server restart.

-
-
-ticker_pool_class
-

alias of TickerPool

-
- -
-
-__init__(save_name='ticker_storage')[source]
-

Initialize handler

-
-
save_name (str, optional): The name of the ServerConfig

instance to store the handler state persistently.

-
-
-
- -
-
-save()[source]
-

Save ticker_storage as a serialized string into a temporary -ServerConf field. Whereas saving is done on the fly, if called -by server when it shuts down, the current timer of each ticker -will be saved so it can start over from that point.

-
- -
-
-restore(server_reload=True)[source]
-

Restore ticker_storage from database and re-initialize the -handler from storage. This is triggered by the server at -restart.

-
-
Parameters
-

server_reload (bool, optional) – If this is False, it means -the server went through a cold reboot and all -non-persistent tickers must be killed.

-
-
-
- -
-
-add(interval=60, callback=None, idstring='', persistent=True, *args, **kwargs)[source]
-

Add subscription to tickerhandler

-
-
Parameters
-
    -
  • interval (int, optional) – Interval in seconds between calling -callable(*args, **kwargs)

  • -
  • callable (callable function or method, optional) – This -should either be a stand-alone function or a method on a -typeclassed entity (that is, one that can be saved to the -database).

  • -
  • idstring (str, optional) – Identifier for separating -this ticker-subscription from others with the same -interval. Allows for managing multiple calls with -the same time interval and callback.

  • -
  • persistent (bool, optional) – A ticker will always survive -a server reload. If this is unset, the ticker will be -deleted by a server shutdown.

  • -
  • args (optional) – These will be passed into the -callback every time it is called. This must be data possible -to pickle!

  • -
  • kwargs (optional) – These will be passed into the -callback every time it is called. This must be data possible -to pickle!

  • -
-
-
Returns
-

store_key (tuple)

-
-
The immutable store-key for this ticker. This can

be stored and passed into .remove(store_key=store_key) later to -easily stop this ticker later.

-
-
-

-
-
-

Notes

-

The callback will be identified by type and stored either as -as combination of serialized database object + methodname or -as a python-path to the module + funcname. These strings will -be combined iwth interval and idstring to define a -unique storage key for saving. These must thus all be supplied -when wanting to modify/remove the ticker later.

-
- -
-
-remove(interval=60, callback=None, idstring='', persistent=True, store_key=None)[source]
-

Remove ticker subscription from handler.

-
-
Parameters
-
    -
  • interval (int, optional) – Interval of ticker to remove.

  • -
  • callback (callable function or method) – Either a function or -the method of a typeclassed object.

  • -
  • idstring (str, optional) – Identifier id of ticker to remove.

  • -
  • persistent (bool, optional) – Whether this ticker is persistent or not.

  • -
  • store_key (str, optional) – If given, all other kwargs are ignored and only -this is used to identify the ticker.

  • -
-
-
Raises
-

KeyError – If no matching ticker was found to remove.

-
-
-

Notes

-

The store-key is normally built from the interval/callback/idstring/persistent values; -but if the store_key is explicitly given, this is used instead.

-
- -
-
-clear(interval=None)[source]
-

Stop/remove tickers from handler.

-
-
Parameters
-

interval (int) – Only stop tickers with this interval.

-
-
-

Notes

-

This is the only supported way to kill tickers related to -non-db objects.

-
- -
-
-all(interval=None)[source]
-

Get all subscriptions.

-
-
Parameters
-

interval (int) – Limit match to tickers with this interval.

-
-
Returns
-

tickers (list)

-
-
If interval was given, this is a list of

tickers using that interval.

-
-
tickerpool_layout (dict): If interval was not given,

this is a dict {interval1: [ticker1, ticker2, …], …}

-
-
-

-
-
-
- -
-
-all_display()[source]
-

Get all tickers on an easily displayable form.

-
-
Returns
-

tickers (dict) – A list of all storekeys

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.admin.html b/docs/0.9.5/api/evennia.server.admin.html deleted file mode 100644 index 5dffec27b0..0000000000 --- a/docs/0.9.5/api/evennia.server.admin.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - - evennia.server.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.admin

-
-
-class evennia.server.admin.ServerConfigAdmin(model, admin_site)[source]
-

Bases: django.contrib.admin.options.ModelAdmin

-

Custom admin for server configs

-
-
-list_display = ('db_key', 'db_value')
-
- -
- -
- -
-
-ordering = ['db_key', 'db_value']
-
- -
-
-search_fields = ['db_key']
-
- -
-
-save_as = True
-
- -
-
-save_on_top = True
-
- -
- -
- -
-
-property media
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.amp_client.html b/docs/0.9.5/api/evennia.server.amp_client.html deleted file mode 100644 index 8728c45d22..0000000000 --- a/docs/0.9.5/api/evennia.server.amp_client.html +++ /dev/null @@ -1,294 +0,0 @@ - - - - - - - - - evennia.server.amp_client — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.amp_client

-

The Evennia Server service acts as an AMP-client when talking to the -Portal. This module sets up the Client-side communication.

-
-
-class evennia.server.amp_client.AMPClientFactory(server)[source]
-

Bases: twisted.internet.protocol.ReconnectingClientFactory

-

This factory creates an instance of an AMP client connection. This handles communication from -the be the Evennia ‘Server’ service to the ‘Portal’. The client will try to auto-reconnect on a -connection error.

-
-
-initialDelay = 1
-
- -
-
-factor = 1.5
-
- -
-
-noisy = False
-
- -
-
-__init__(server)[source]
-

Initializes the client factory.

-
-
Parameters
-

server (server) – server instance.

-
-
-
- -
-
-maxDelay = 1
-
- -
-
-startedConnecting(connector)[source]
-

Called when starting to try to connect to the Portal AMP server.

-
-
Parameters
-

connector (Connector) – Twisted Connector instance representing -this connection.

-
-
-
- -
-
-buildProtocol(addr)[source]
-

Creates an AMPProtocol instance when connecting to the AMP server.

-
-
Parameters
-

addr (str) – Connection address. Not used.

-
-
-
- -
-
-clientConnectionLost(connector, reason)[source]
-

Called when the AMP connection to the MUD server is lost.

-
-
Parameters
-
    -
  • connector (Connector) – Twisted Connector instance representing -this connection.

  • -
  • reason (str) – Eventual text describing why connection was lost.

  • -
-
-
-
- -
-
-clientConnectionFailed(connector, reason)[source]
-

Called when an AMP connection attempt to the MUD server fails.

-
-
Parameters
-
    -
  • connector (Connector) – Twisted Connector instance representing -this connection.

  • -
  • reason (str) – Eventual text describing why connection failed.

  • -
-
-
-
- -
- -
-
-class evennia.server.amp_client.AMPServerClientProtocol(*args, **kwargs)[source]
-

Bases: evennia.server.portal.amp.AMPMultiConnectionProtocol

-

This protocol describes the Server service (acting as an AMP-client)’s communication with the -Portal (which acts as the AMP-server)

-
-
-connectionMade()[source]
-

Called when a new connection is established.

-
- -
-
-data_to_portal(command, sessid, **kwargs)[source]
-

Send data across the wire to the Portal

-
-
Parameters
-
    -
  • command (AMP Command) – A protocol send command.

  • -
  • sessid (int) – A unique Session id.

  • -
  • kwargs (any) – Any data to pickle into the command.

  • -
-
-
Returns
-

deferred (deferred or None) – A deferred with an errback.

-
-
-

Notes

-

Data will be sent across the wire pickled as a tuple -(sessid, kwargs).

-
- -
-
-send_MsgServer2Portal(session, **kwargs)[source]
-
-
Access method - executed on the Server for sending data

to Portal.

-
-
-
-
Parameters
-
    -
  • session (Session) – Unique Session.

  • -
  • kwargs (any, optiona) – Extra data.

  • -
-
-
-
- -
-
-send_AdminServer2Portal(session, operation='', **kwargs)[source]
-

Administrative access method called by the Server to send an -instruction to the Portal.

-
-
Parameters
-
    -
  • session (Session) – Session.

  • -
  • operation (char, optional) – Identifier for the server -operation, as defined by the global variables in -evennia/server/amp.py.

  • -
  • kwargs (dict, optional) – Data going into the adminstrative.

  • -
-
-
-
- -
-
-server_receive_status(question)[source]
-
- -
-
-server_receive_msgportal2server()
-

Helper decorator

-
- -
-
-server_receive_adminportal2server()
-

Helper decorator

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.connection_wizard.html b/docs/0.9.5/api/evennia.server.connection_wizard.html deleted file mode 100644 index 9996f6a517..0000000000 --- a/docs/0.9.5/api/evennia.server.connection_wizard.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - - evennia.server.connection_wizard — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.connection_wizard

-

Link Evennia to external resources (wizard plugin for evennia_launcher)

-
-
-class evennia.server.connection_wizard.ConnectionWizard[source]
-

Bases: object

-
-
-__init__()[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-display(text)[source]
-

Show text

-
- -
-
-ask_continue()[source]
-

‘Press return to continue’-prompt

-
- -
-
-ask_node(options, prompt='Enter choice: ', default=None)[source]
-

Retrieve options and jump to different menu nodes

-
-
Parameters
-
    -
  • options (dict) – Node options on the form {key: (desc, callback), }

  • -
  • prompt (str, optional) – Question to ask

  • -
  • default (str, optional) – Default value to use if user hits return.

  • -
-
-
-
- -
-
-ask_yesno(prompt, default='yes')[source]
-

Ask a yes/no question inline.

-
-
Keyword Arguments
-
    -
  • prompt (str) – The prompt to ask.

  • -
  • default (str) – “yes” or “no”, used if pressing return.

  • -
-
-
Returns
-

reply (str) – Either ‘yes’ or ‘no’.

-
-
-
- -
-
-ask_choice(prompt=' > ', options=None, default=None)[source]
-

Ask multiple-choice question, get response inline.

-
-
Keyword Arguments
-
    -
  • prompt (str) – Input prompt.

  • -
  • options (list) – List of options. Will be indexable by sequence number 1…

  • -
  • default (int) – The list index+1 of the default choice, if any

  • -
-
-
Returns
-

reply (str) – The answered reply.

-
-
-
- -
-
-ask_input(prompt=' > ', default=None, validator=None)[source]
-

Get arbitrary input inline.

-
-
Keyword Arguments
-
    -
  • prompt (str) – The display prompt.

  • -
  • default (str) – If empty input, use this.

  • -
  • validator (callable) – If given, the input will be passed -into this callable. It should return True unless validation -fails (and is expected to echo why if so).

  • -
-
-
Returns
-

inp (str) – The input given, or default.

-
-
-
- -
- -
-
-evennia.server.connection_wizard.node_start(wizard)[source]
-
- -
-
-evennia.server.connection_wizard.node_game_index_start(wizard, **kwargs)[source]
-
- -
-
-evennia.server.connection_wizard.node_game_index_fields(wizard, status=None)[source]
-
- -
-
-evennia.server.connection_wizard.node_mssp_start(wizard)[source]
-
- -
-
-evennia.server.connection_wizard.node_view_and_apply_settings(wizard)[source]
-

Inspect and save the data gathered in the other nodes

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.deprecations.html b/docs/0.9.5/api/evennia.server.deprecations.html deleted file mode 100644 index b722e3c943..0000000000 --- a/docs/0.9.5/api/evennia.server.deprecations.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - evennia.server.deprecations — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.deprecations

-

This module contains historical deprecations that the Evennia launcher -checks for.

-

These all print to the terminal.

-
-
-evennia.server.deprecations.check_errors(settings)[source]
-

Check for deprecations that are critical errors and should stop -the launcher.

-
-
Parameters
-

settings (Settings) – The Django settings file

-
-
Raises
-

DeprecationWarning if a critical deprecation is found.

-
-
-
- -
-
-evennia.server.deprecations.check_warnings(settings)[source]
-

Check conditions and deprecations that should produce warnings but which -does not stop launch.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.evennia_launcher.html b/docs/0.9.5/api/evennia.server.evennia_launcher.html deleted file mode 100644 index fbd95d2e74..0000000000 --- a/docs/0.9.5/api/evennia.server.evennia_launcher.html +++ /dev/null @@ -1,611 +0,0 @@ - - - - - - - - - evennia.server.evennia_launcher — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.evennia_launcher

-

Evennia launcher program

-

This is the start point for running Evennia.

-

Sets the appropriate environmental variables for managing an Evennia game. It will start and connect -to the Portal, through which the Server is also controlled. This pprogram

-

Run the script with the -h flag to see usage information.

-
-
-class evennia.server.evennia_launcher.MsgStatus(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Ping between AMP services

-
-
-key = 'MsgStatus'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'status', <twisted.protocols.amp.String object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'status', <twisted.protocols.amp.String object>)]
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'MsgStatus'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.evennia_launcher.MsgLauncher2Portal(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Message Launcher -> Portal

-
-
-key = 'MsgLauncher2Portal'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'operation', <twisted.protocols.amp.String object>), (b'arguments', <twisted.protocols.amp.String object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = []
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'MsgLauncher2Portal'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.evennia_launcher.AMPLauncherProtocol[source]
-

Bases: twisted.protocols.amp.AMP

-

Defines callbacks to the launcher

-
-
-__init__()[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-wait_for_status(callback)[source]
-

Register a waiter for a status return.

-
- -
-
-receive_status_from_portal(status)[source]
-

Get a status signal from portal - fire next queued -callback

-
- -
- -
-
-evennia.server.evennia_launcher.send_instruction(operation, arguments, callback=None, errback=None)[source]
-

Send instruction and handle the response.

-
- -
-
-evennia.server.evennia_launcher.query_status(callback=None)[source]
-

Send status ping to portal

-
- -
-
-evennia.server.evennia_launcher.wait_for_status_reply(callback)[source]
-

Wait for an explicit STATUS signal to be sent back from Evennia.

-
- -
-
-evennia.server.evennia_launcher.wait_for_status(portal_running=True, server_running=True, callback=None, errback=None, rate=0.5, retries=20)[source]
-

Repeat the status ping until the desired state combination is achieved.

-
-
Parameters
-
    -
  • portal_running (bool or None) – Desired portal run-state. If None, any state -is accepted.

  • -
  • server_running (bool or None) – Desired server run-state. If None, any state -is accepted. The portal must be running.

  • -
  • callback (callable) – Will be called with portal_state, server_state when -condition is fulfilled.

  • -
  • errback (callable) – Will be called with portal_state, server_state if the -request is timed out.

  • -
  • rate (float) – How often to retry.

  • -
  • retries (int) – How many times to retry before timing out and calling errback.

  • -
-
-
-
- -
-
-evennia.server.evennia_launcher.collectstatic()[source]
-

Run the collectstatic django command

-
- -
-
-evennia.server.evennia_launcher.start_evennia(pprofiler=False, sprofiler=False)[source]
-

This will start Evennia anew by launching the Evennia Portal (which in turn -will start the Server)

-
- -
-
-evennia.server.evennia_launcher.reload_evennia(sprofiler=False, reset=False)[source]
-

This will instruct the Portal to reboot the Server component. We -do this manually by telling the server to shutdown (in reload mode) -and wait for the portal to report back, at which point we start the -server again. This way we control the process exactly.

-
- -
-
-evennia.server.evennia_launcher.stop_evennia()[source]
-

This instructs the Portal to stop the Server and then itself.

-
- -
-
-evennia.server.evennia_launcher.reboot_evennia(pprofiler=False, sprofiler=False)[source]
-

This is essentially an evennia stop && evennia start except we make sure -the system has successfully shut down before starting it again.

-

If evennia was not running, start it.

-
- -
-
-evennia.server.evennia_launcher.start_only_server()[source]
-

Tell portal to start server (debug)

-
- -
-
-evennia.server.evennia_launcher.start_server_interactive()[source]
-

Start the Server under control of the launcher process (foreground)

-
- -
-
-evennia.server.evennia_launcher.start_portal_interactive()[source]
-

Start the Portal under control of the launcher process (foreground)

-

Notes

-

In a normal start, the launcher waits for the Portal to start, then -tells it to start the Server. Since we can’t do this here, we instead -start the Server first and then starts the Portal - the Server will -auto-reconnect to the Portal. To allow the Server to be reloaded, this -relies on a fixed server server-cmdline stored as a fallback on the -portal application in evennia/server/portal/portal.py.

-
- -
-
-evennia.server.evennia_launcher.stop_server_only(when_stopped=None, interactive=False)[source]
-

Only stop the Server-component of Evennia (this is not useful except for debug)

-
-
Parameters
-
    -
  • when_stopped (callable) – This will be called with no arguments when Server has stopped (or -if it had already stopped when this is called).

  • -
  • interactive (bool, optional) – Set if this is called as part of the interactive reload -mechanism.

  • -
-
-
-
- -
-
-evennia.server.evennia_launcher.query_info()[source]
-

Display the info strings from the running Evennia

-
- -
-
-evennia.server.evennia_launcher.tail_log_files(filename1, filename2, start_lines1=20, start_lines2=20, rate=1)[source]
-

Tail two logfiles interactively, combining their output to stdout

-

When first starting, this will display the tail of the log files. After -that it will poll the log files repeatedly and display changes.

-
-
Parameters
-
    -
  • filename1 (str) – Path to first log file.

  • -
  • filename2 (str) – Path to second log file.

  • -
  • start_lines1 (int) – How many lines to show from existing first log.

  • -
  • start_lines2 (int) – How many lines to show from existing second log.

  • -
  • rate (int, optional) – How often to poll the log file.

  • -
-
-
-
- -
-
-evennia.server.evennia_launcher.evennia_version()[source]
-

Get the Evennia version info from the main package.

-
- -
-
-evennia.server.evennia_launcher.check_main_evennia_dependencies()[source]
-

Checks and imports the Evennia dependencies. This must be done -already before the paths are set up.

-
-
Returns
-

not_error (bool) – True if no dependency error was found.

-
-
-
- -
-
-evennia.server.evennia_launcher.set_gamedir(path)[source]
-

Set GAMEDIR based on path, by figuring out where the setting file -is inside the directory tree. This allows for running the launcher -from elsewhere than the top of the gamedir folder.

-
- -
-
-evennia.server.evennia_launcher.create_secret_key()[source]
-

Randomly create the secret key for the settings file

-
- -
-
-evennia.server.evennia_launcher.create_settings_file(init=True, secret_settings=False)[source]
-

Uses the template settings file to build a working settings file.

-
-
Parameters
-
    -
  • init (bool) – This is part of the normal evennia –init -operation. If false, this function will copy a fresh -template file in (asking if it already exists).

  • -
  • secret_settings (bool, optional) – If False, create settings.py, otherwise -create the secret_settings.py file.

  • -
-
-
-
- -
-
-evennia.server.evennia_launcher.create_game_directory(dirname)[source]
-

Initialize a new game directory named dirname -at the current path. This means copying the -template directory from evennia’s root.

-
-
Parameters
-

dirname (str) – The directory name to create.

-
-
-
- -
-
-evennia.server.evennia_launcher.create_superuser()[source]
-

Create the superuser account

-
- -
-
-evennia.server.evennia_launcher.check_database(always_return=False)[source]
-

Check so the database exists.

-
-
Parameters
-

always_return (bool, optional) – If set, will always return True/False -also on critical errors. No output will be printed.

-
-
Returns
-

exists (bool)True if the database exists, otherwise False.

-
-
-
- -
-
-evennia.server.evennia_launcher.getenv()[source]
-

Get current environment and add PYTHONPATH.

-
-
Returns
-

env (dict) – Environment global dict.

-
-
-
- -
-
-evennia.server.evennia_launcher.get_pid(pidfile, default=None)[source]
-

Get the PID (Process ID) by trying to access an PID file.

-
-
Parameters
-
    -
  • pidfile (str) – The path of the pid file.

  • -
  • default (int, optional) – What to return if file does not exist.

  • -
-
-
Returns
-

pid (str) – The process id or default.

-
-
-
- -
-
-evennia.server.evennia_launcher.del_pid(pidfile)[source]
-

The pidfile should normally be removed after a process has -finished, but when sending certain signals they remain, so we need -to clean them manually.

-
-
Parameters
-

pidfile (str) – The path of the pid file.

-
-
-
- -
-
-evennia.server.evennia_launcher.kill(pidfile, component='Server', callback=None, errback=None, killsignal=<Signals.SIGINT: 2>)[source]
-

Send a kill signal to a process based on PID. A customized -success/error message will be returned. If clean=True, the system -will attempt to manually remove the pid file. On Windows, no arguments -are useful since Windows has no ability to direct signals except to all -children of a console.

-
-
Parameters
-
    -
  • pidfile (str) – The path of the pidfile to get the PID from. This is ignored -on Windows.

  • -
  • component (str, optional) – Usually one of ‘Server’ or ‘Portal’. This is -ignored on Windows.

  • -
  • errback (callable, optional) – Called if signal failed to send. This -is ignored on Windows.

  • -
  • callback (callable, optional) – Called if kill signal was sent successfully. -This is ignored on Windows.

  • -
  • killsignal (int, optional) – Signal identifier for signal to send. This is -ignored on Windows.

  • -
-
-
-
- -
-
-evennia.server.evennia_launcher.show_version_info(about=False)[source]
-

Display version info.

-
-
Parameters
-

about (bool) – Include ABOUT info as well as version numbers.

-
-
Returns
-

version_info (str) – A complete version info string.

-
-
-
- -
-
-evennia.server.evennia_launcher.error_check_python_modules(show_warnings=False)[source]
-

Import settings modules in settings. This will raise exceptions on -pure python-syntax issues which are hard to catch gracefully with -exceptions in the engine (since they are formatting errors in the -python source files themselves). Best they fail already here -before we get any further.

-
-
Keyword Arguments
-

show_warnings (bool) – If non-fatal warning messages should be shown.

-
-
-
- -
-
-evennia.server.evennia_launcher.init_game_directory(path, check_db=True, need_gamedir=True)[source]
-

Try to analyze the given path to find settings.py - this defines -the game directory and also sets PYTHONPATH as well as the django -path.

-
-
Parameters
-
    -
  • path (str) – Path to new game directory, including its name.

  • -
  • check_db (bool, optional) – Check if the databae exists.

  • -
  • need_gamedir (bool, optional) – set to False if Evennia doesn’t require to -be run in a valid game directory.

  • -
-
-
-
- -
-
-evennia.server.evennia_launcher.run_dummyrunner(number_of_dummies)[source]
-

Start an instance of the dummyrunner

-
-
Parameters
-

number_of_dummies (int) – The number of dummy accounts to start.

-
-
-

Notes

-

The dummy accounts’ behavior can be customized by adding a -dummyrunner_settings.py config file in the game’s conf/ -directory.

-
- -
-
-evennia.server.evennia_launcher.run_connect_wizard()[source]
-

Run the linking wizard, for adding new external connections.

-
- -
-
-evennia.server.evennia_launcher.list_settings(keys)[source]
-

Display the server settings. We only display the Evennia specific -settings here. The result will be printed to the terminal.

-
-
Parameters
-

keys (str or list) – Setting key or keys to inspect.

-
-
-
- -
-
-evennia.server.evennia_launcher.run_menu()[source]
-

This launches an interactive menu.

-
- -
-
-evennia.server.evennia_launcher.main()[source]
-

Run the evennia launcher main program.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.game_index_client.client.html b/docs/0.9.5/api/evennia.server.game_index_client.client.html deleted file mode 100644 index 42b5444a45..0000000000 --- a/docs/0.9.5/api/evennia.server.game_index_client.client.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - - - - evennia.server.game_index_client.client — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.game_index_client.client

-

The client for sending data to the Evennia Game Index

-
-
-class evennia.server.game_index_client.client.EvenniaGameIndexClient(on_bad_request=None)[source]
-

Bases: object

-

This client class is used for gathering and sending game details to the -Evennia Game Index. Since EGI is in the early goings, this isn’t -incredibly configurable as far as to what is being sent.

-
-
-__init__(on_bad_request=None)[source]
-
-
Parameters
-

on_bad_request – Optional callable to trigger when a bad request -was sent. This is almost always going to be due to bad config.

-
-
-
- -
-
-send_game_details()[source]
-

This is where the magic happens. Send details about the game to the -Evennia Game Index.

-
- -
-
-handle_egd_response(response)[source]
-
- -
- -
-
-class evennia.server.game_index_client.client.SimpleResponseReceiver(status_code, d)[source]
-

Bases: twisted.internet.protocol.Protocol

-

Used for pulling the response body out of an HTTP response.

-
-
-__init__(status_code, d)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-dataReceived(data)[source]
-

Called whenever data is received.

-

Use this method to translate to a higher-level message. Usually, some -callback will be made upon the receipt of each complete protocol -message.

-
-
@param data: a string of indeterminate length. Please keep in mind

that you will probably need to buffer some data, as partial -(or multiple) protocol messages may be received! I recommend -that unit tests for protocols call through to this method with -differing chunk sizes, down to one byte at a time.

-
-
-
- -
-
-connectionLost(reason=<twisted.python.failure.Failure twisted.internet.error.ConnectionDone: Connection was closed cleanly.>)[source]
-

Called when the connection is shut down.

-

Clear any circular references here, and any external references -to this Protocol. The connection has been closed.

-

@type reason: L{twisted.python.failure.Failure}

-
- -
- -
-
-class evennia.server.game_index_client.client.StringProducer(body)[source]
-

Bases: object

-

Used for feeding a request body to the tx HTTP client.

-
-
-__init__(body)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-startProducing(consumer)[source]
-
- -
-
-pauseProducing()[source]
-
- -
-
-stopProducing()[source]
-
- -
- -
-
-class evennia.server.game_index_client.client.QuietHTTP11ClientFactory(quiescentCallback, metadata)[source]
-

Bases: twisted.web.client._HTTP11ClientFactory

-

Silences the obnoxious factory start/stop messages in the default client.

-
-
-noisy = False
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.game_index_client.html b/docs/0.9.5/api/evennia.server.game_index_client.html deleted file mode 100644 index 76f144f5cf..0000000000 --- a/docs/0.9.5/api/evennia.server.game_index_client.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - evennia.server.game_index_client — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.game_index_client

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.game_index_client.service.html b/docs/0.9.5/api/evennia.server.game_index_client.service.html deleted file mode 100644 index 37cbd6aeeb..0000000000 --- a/docs/0.9.5/api/evennia.server.game_index_client.service.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - evennia.server.game_index_client.service — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.game_index_client.service

-

Service for integrating the Evennia Game Index client into Evennia.

-
-
-class evennia.server.game_index_client.service.EvenniaGameIndexService[source]
-

Bases: twisted.application.service.Service

-

Twisted Service that contains a LoopingCall for regularly sending game details -to the Evennia Game Index.

-
-
-name = 'GameIndexClient'
-
- -
-
-__init__()[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-startService()[source]
-
- -
-
-stopService()[source]
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.html b/docs/0.9.5/api/evennia.server.html deleted file mode 100644 index 612bf5d285..0000000000 --- a/docs/0.9.5/api/evennia.server.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - - evennia.server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server

-

This sub-package holds the Server and Portal programs - the “core” of -Evennia. It also contains the SessionHandler that manages all -connected users as well as defines all the connection protocols used -to connect to the game.

- - -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.initial_setup.html b/docs/0.9.5/api/evennia.server.initial_setup.html deleted file mode 100644 index e9a5cad34e..0000000000 --- a/docs/0.9.5/api/evennia.server.initial_setup.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - evennia.server.initial_setup — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.initial_setup

-

This module handles initial database propagation, which is only run the first -time the game starts. It will create some default channels, objects, and -other things.

-

Everything starts at handle_setup()

-
-
-evennia.server.initial_setup.get_god_account()[source]
-

Creates the god user and don’t take no for an answer.

-
- -
-
-evennia.server.initial_setup.create_objects()[source]
-

Creates the #1 account and Limbo room.

-
- -
-
-evennia.server.initial_setup.create_channels()[source]
-

Creates some sensible default channels.

-
- -
-
-evennia.server.initial_setup.at_initial_setup()[source]
-

Custom hook for users to overload some or all parts of the initial -setup. Called very last in the sequence. It tries to import and -srun a module settings.AT_INITIAL_SETUP_HOOK_MODULE and will fail -silently if this does not exist or fails to load.

-
- -
-
-evennia.server.initial_setup.collectstatic()[source]
-

Run collectstatic to make sure all web assets are loaded.

-
- -
-
-evennia.server.initial_setup.reset_server()[source]
-

We end the initialization by resetting the server. This makes sure -the first login is the same as all the following ones, -particularly it cleans all caches for the special objects. It -also checks so the warm-reset mechanism works as it should.

-
- -
-
-evennia.server.initial_setup.handle_setup(last_step)[source]
-

Main logic for the module. It allows for restarting the -initialization at any point if one of the modules should crash.

-
-
Parameters
-

last_step (int) – The last stored successful step, for starting -over on errors. If < 0, initialization has finished and no -steps need to be redone.

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.inputfuncs.html b/docs/0.9.5/api/evennia.server.inputfuncs.html deleted file mode 100644 index 7fca04571e..0000000000 --- a/docs/0.9.5/api/evennia.server.inputfuncs.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - - - evennia.server.inputfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.inputfuncs

-

Functions for processing input commands.

-

All global functions in this module whose name does not start with “_” -is considered an inputfunc. Each function must have the following -callsign (where inputfunc name is always lower-case, no matter what the -OOB input name looked like):

-
-

inputfunc(session, *args, **kwargs)

-
-

Where “options” is always one of the kwargs, containing eventual -protocol-options. -There is one special function, the “default” function, which is called -on a no-match. It has this callsign:

-
-

default(session, cmdname, *args, **kwargs)

-
-

Evennia knows which modules to use for inputfuncs by -settings.INPUT_FUNC_MODULES.

-
-
-evennia.server.inputfuncs.text(session, *args, **kwargs)[source]
-

Main text input from the client. This will execute a command -string on the server.

-
-
Parameters
-
    -
  • session (Session) – The active Session to receive the input.

  • -
  • text (str) – First arg is used as text-command input. Other -arguments are ignored.

  • -
-
-
-
- -
-
-evennia.server.inputfuncs.bot_data_in(session, *args, **kwargs)[source]
-

Text input from the IRC and RSS bots. -This will trigger the execute_cmd method on the bots in-game counterpart.

-
-
Parameters
-
    -
  • session (Session) – The active Session to receive the input.

  • -
  • text (str) – First arg is text input. Other arguments are ignored.

  • -
-
-
-
- -
-
-evennia.server.inputfuncs.echo(session, *args, **kwargs)[source]
-

Echo test function

-
- -
-
-evennia.server.inputfuncs.default(session, cmdname, *args, **kwargs)[source]
-

Default catch-function. This is like all other input functions except -it will get cmdname as the first argument.

-
- -
-
-evennia.server.inputfuncs.client_options(session, *args, **kwargs)[source]
-

This allows the client an OOB way to inform us about its name and capabilities. -This will be integrated into the session settings

-
-
Keyword Arguments
-
    -
  • get (bool) – If this is true, return the settings as a dict -(ignore all other kwargs).

  • -
  • client (str) – A client identifier, like “mushclient”.

  • -
  • version (str) – A client version

  • -
  • ansi (bool) – Supports ansi colors

  • -
  • xterm256 (bool) – Supports xterm256 colors or not

  • -
  • mxp (bool) – Supports MXP or not

  • -
  • utf-8 (bool) – Supports UTF-8 or not

  • -
  • screenreader (bool) – Screen-reader mode on/off

  • -
  • mccp (bool) – MCCP compression on/off

  • -
  • screenheight (int) – Screen height in lines

  • -
  • screenwidth (int) – Screen width in characters

  • -
  • inputdebug (bool) – Debug input functions

  • -
  • nocolor (bool) – Strip color

  • -
  • raw (bool) – Turn off parsing

  • -
-
-
-
- -
-
-evennia.server.inputfuncs.get_client_options(session, *args, **kwargs)[source]
-

Alias wrapper for getting options.

-
- -
-
-evennia.server.inputfuncs.get_inputfuncs(session, *args, **kwargs)[source]
-

Get the keys of all available inputfuncs. Note that we don’t get -it from this module alone since multiple modules could be added. -So we get it from the sessionhandler.

-
- -
-
-evennia.server.inputfuncs.login(session, *args, **kwargs)[source]
-

Peform a login. This only works if session is currently not logged -in. This will also automatically throttle too quick attempts.

-
-
Keyword Arguments
-
    -
  • name (str) – Account name

  • -
  • password (str) – Plain-text password

  • -
-
-
-
- -
-
-evennia.server.inputfuncs.get_value(session, *args, **kwargs)[source]
-

Return the value of a given attribute or db_property on the -session’s current account or character.

-
-
Keyword Arguments
-

name (str) – Name of info value to return. Only names -in the _gettable dictionary earlier in this module -are accepted.

-
-
-
- -
-
-evennia.server.inputfuncs.repeat(session, *args, **kwargs)[source]
-

Call a named function repeatedly. Note that -this is meant as an example of limiting the number of -possible call functions.

-
-
Keyword Arguments
-
    -
  • callback (str) – The function to call. Only functions -from the _repeatable dictionary earlier in this -module are available.

  • -
  • interval (int) –

    How often to call function (s). -Defaults to once every 60 seconds with a minimum

    -
    -

    of 5 seconds.

    -
    -

  • -
  • stop (bool) – Stop a previously assigned ticker with -the above settings.

  • -
-
-
-
- -
-
-evennia.server.inputfuncs.unrepeat(session, *args, **kwargs)[source]
-

Wrapper for OOB use

-
- -
-
-evennia.server.inputfuncs.monitor(session, *args, **kwargs)[source]
-

Adds monitoring to a given property or Attribute.

-
-
Keyword Arguments
-
    -
  • name (str) – The name of the property or Attribute -to report. No db_* prefix is needed. Only names -in the _monitorable dict earlier in this module -are accepted.

  • -
  • stop (bool) – Stop monitoring the above name.

  • -
  • outputfunc_name (str, optional) – Change the name of -the outputfunc name. This is used e.g. by MSDP which -has its own specific output format.

  • -
-
-
-
- -
-
-evennia.server.inputfuncs.unmonitor(session, *args, **kwargs)[source]
-

Wrapper for turning off monitoring

-
- -
-
-evennia.server.inputfuncs.monitored(session, *args, **kwargs)[source]
-

Report on what is being monitored

-
- -
-
-evennia.server.inputfuncs.webclient_options(session, *args, **kwargs)[source]
-

Handles retrieving and changing of options related to the webclient.

-

If kwargs is empty (or contains just a “cmdid”), the saved options will be -sent back to the session. -A monitor handler will be created to inform the client of any future options -that changes.

-

If kwargs is not empty, the key/values stored in there will be persisted -to the account object.

-
-
Keyword Arguments
-

name> (<option) – an option to save

-
-
-
- -
-
-evennia.server.inputfuncs.hello(session, *args, **kwargs)
-

This allows the client an OOB way to inform us about its name and capabilities. -This will be integrated into the session settings

-
-
Keyword Arguments
-
    -
  • get (bool) – If this is true, return the settings as a dict -(ignore all other kwargs).

  • -
  • client (str) – A client identifier, like “mushclient”.

  • -
  • version (str) – A client version

  • -
  • ansi (bool) – Supports ansi colors

  • -
  • xterm256 (bool) – Supports xterm256 colors or not

  • -
  • mxp (bool) – Supports MXP or not

  • -
  • utf-8 (bool) – Supports UTF-8 or not

  • -
  • screenreader (bool) – Screen-reader mode on/off

  • -
  • mccp (bool) – MCCP compression on/off

  • -
  • screenheight (int) – Screen height in lines

  • -
  • screenwidth (int) – Screen width in characters

  • -
  • inputdebug (bool) – Debug input functions

  • -
  • nocolor (bool) – Strip color

  • -
  • raw (bool) – Turn off parsing

  • -
-
-
-
- -
-
-evennia.server.inputfuncs.supports_set(session, *args, **kwargs)
-

This allows the client an OOB way to inform us about its name and capabilities. -This will be integrated into the session settings

-
-
Keyword Arguments
-
    -
  • get (bool) – If this is true, return the settings as a dict -(ignore all other kwargs).

  • -
  • client (str) – A client identifier, like “mushclient”.

  • -
  • version (str) – A client version

  • -
  • ansi (bool) – Supports ansi colors

  • -
  • xterm256 (bool) – Supports xterm256 colors or not

  • -
  • mxp (bool) – Supports MXP or not

  • -
  • utf-8 (bool) – Supports UTF-8 or not

  • -
  • screenreader (bool) – Screen-reader mode on/off

  • -
  • mccp (bool) – MCCP compression on/off

  • -
  • screenheight (int) – Screen height in lines

  • -
  • screenwidth (int) – Screen width in characters

  • -
  • inputdebug (bool) – Debug input functions

  • -
  • nocolor (bool) – Strip color

  • -
  • raw (bool) – Turn off parsing

  • -
-
-
-
- -
-
-evennia.server.inputfuncs.msdp_list(session, *args, **kwargs)[source]
-

MSDP LIST command

-
- -
-
-evennia.server.inputfuncs.msdp_report(session, *args, **kwargs)[source]
-

MSDP REPORT command

-
- -
-
-evennia.server.inputfuncs.msdp_unreport(session, *args, **kwargs)[source]
-

MSDP UNREPORT command

-
- -
-
-evennia.server.inputfuncs.msdp_send(session, *args, **kwargs)[source]
-

MSDP SEND command

-
- -
-
-evennia.server.inputfuncs.external_discord_hello(session, *args, **kwargs)
-

Dummy used to swallow missing-inputfunc errors for -common clients.

-
- -
-
-evennia.server.inputfuncs.client_gui(session, *args, **kwargs)
-

Dummy used to swallow missing-inputfunc errors for -common clients.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.manager.html b/docs/0.9.5/api/evennia.server.manager.html deleted file mode 100644 index dd0f93e26e..0000000000 --- a/docs/0.9.5/api/evennia.server.manager.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - evennia.server.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.manager

-

Custom manager for ServerConfig objects.

-
-
-class evennia.server.manager.ServerConfigManager(*args, **kwargs)[source]
-

Bases: django.db.models.manager.Manager

-

This ServerConfigManager implements methods for searching and -manipulating ServerConfigs directly from the database.

-

These methods will all return database objects (or QuerySets) -directly.

-

ServerConfigs are used to store certain persistent settings for -the server at run-time.

-
-
-conf(key=None, value=None, delete=False, default=None)[source]
-

Add, retrieve and manipulate config values.

-
-
Parameters
-
    -
  • key (str, optional) – Name of config.

  • -
  • value (str, optional) – Data to store in this config value.

  • -
  • delete (bool, optional) – If True, delete config with key.

  • -
  • default (str, optional) – Use when retrieving a config value -by a key that does not exist.

  • -
-
-
Returns
-

all (list) – If key was not given - all stored config values. -value (str): If key was given, this is the stored value, or

-
-

default if no matching key was found.

-
-

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.models.html b/docs/0.9.5/api/evennia.server.models.html deleted file mode 100644 index 131c55b02c..0000000000 --- a/docs/0.9.5/api/evennia.server.models.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - evennia.server.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.models

-

Server Configuration flags

-

This holds persistent server configuration flags.

-

Config values should usually be set through the -manager’s conf() method.

-
-
-class evennia.server.models.ServerConfig(*args, **kwargs)[source]
-

Bases: evennia.utils.idmapper.models.WeakSharedMemoryModel

-

On-the fly storage of global settings.

-

Properties defined on ServerConfig:

-
-
    -
  • key: Main identifier

  • -
  • value: Value stored in key. This is a pickled storage.

  • -
-
-
-
-db_key
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_value
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-objects = <evennia.server.manager.ServerConfigManager object>
-
- -
-
-property key
-

Getter. Allows for value = self.key

-
- -
-
-property value
-

Getter. Allows for value = self.value

-
- -
-
-store(key, value)[source]
-

Wrap the storage.

-
-
Parameters
-
    -
  • key (str) – The name of this store.

  • -
  • value (str) – The data to store with this key.

  • -
-
-
-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-path = 'evennia.server.models.ServerConfig'
-
- -
-
-typename = 'WeakSharedMemoryModelBase'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.amp.html b/docs/0.9.5/api/evennia.server.portal.amp.html deleted file mode 100644 index c9e691b95f..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.amp.html +++ /dev/null @@ -1,587 +0,0 @@ - - - - - - - - - evennia.server.portal.amp — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.amp

-

The AMP (Asynchronous Message Protocol)-communication commands and constants used by Evennia.

-

This module acts as a central place for AMP-servers and -clients to get commands to use.

-
-
-evennia.server.portal.amp.dumps(data)[source]
-
- -
-
-evennia.server.portal.amp.loads(data)[source]
-
- -
-
-class evennia.server.portal.amp.Compressed(optional=False)[source]
-

Bases: twisted.protocols.amp.String

-

This is a custom AMP command Argument that both handles too-long -sends as well as uses zlib for compression across the wire. The -batch-grouping of too-long sends is borrowed from the “mediumbox” -recipy at twisted-hacks’s ~glyph/+junk/amphacks/mediumbox.

-
-
-fromBox(name, strings, objects, proto)[source]
-

Converts from box string representation to python. We read back too-long batched data and -put it back together here.

-
- -
-
-toBox(name, strings, objects, proto)[source]
-

Convert from python object to string box representation. -we break up too-long data snippets into multiple batches here.

-
- -
-
-toString(inObject)[source]
-

Convert to send as a bytestring on the wire, with compression.

-

Note: In Py3 this is really a byte stream.

-
- -
-
-fromString(inString)[source]
-

Convert (decompress) from the string-representation on the wire to Python.

-
- -
- -
-
-class evennia.server.portal.amp.MsgLauncher2Portal(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Message Launcher -> Portal

-
-
-key = 'MsgLauncher2Portal'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'operation', <twisted.protocols.amp.String object>), (b'arguments', <twisted.protocols.amp.String object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = []
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'MsgLauncher2Portal'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.portal.amp.MsgPortal2Server(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Message Portal -> Server

-
-
-key = b'MsgPortal2Server'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'packed_data', <evennia.server.portal.amp.Compressed object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = []
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'MsgPortal2Server'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.portal.amp.MsgServer2Portal(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Message Server -> Portal

-
-
-key = 'MsgServer2Portal'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'packed_data', <evennia.server.portal.amp.Compressed object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = []
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'MsgServer2Portal'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.portal.amp.AdminPortal2Server(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Administration Portal -> Server

-

Sent when the portal needs to perform admin operations on the -server, such as when a new session connects or resyncs

-
-
-key = 'AdminPortal2Server'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'packed_data', <evennia.server.portal.amp.Compressed object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = []
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'AdminPortal2Server'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.portal.amp.AdminServer2Portal(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Administration Server -> Portal

-

Sent when the server needs to perform admin operations on the -portal.

-
-
-key = 'AdminServer2Portal'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'packed_data', <evennia.server.portal.amp.Compressed object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = []
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'AdminServer2Portal'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.portal.amp.MsgStatus(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Check Status between AMP services

-
-
-key = 'MsgStatus'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'status', <twisted.protocols.amp.String object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'status', <twisted.protocols.amp.String object>)]
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'MsgStatus'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.portal.amp.FunctionCall(**kw)[source]
-

Bases: twisted.protocols.amp.Command

-

Bidirectional Server <-> Portal

-

Sent when either process needs to call an arbitrary function in -the other. This does not use the batch-send functionality.

-
-
-key = 'FunctionCall'
-
- -
-
-arguments: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'module', <twisted.protocols.amp.String object>), (b'function', <twisted.protocols.amp.String object>), (b'args', <twisted.protocols.amp.String object>), (b'kwargs', <twisted.protocols.amp.String object>)]
-
- -
-
-errors: Dict[Type[Exception], bytes] = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-response: List[Tuple[bytes, twisted.protocols.amp.Argument]] = [(b'result', <twisted.protocols.amp.String object>)]
-
- -
-
-allErrors = {<class 'Exception'>: b'EXCEPTION'}
-
- -
-
-commandName = b'FunctionCall'
-
- -
-
-reverseErrors = {b'EXCEPTION': <class 'Exception'>}
-
- -
- -
-
-class evennia.server.portal.amp.AMPMultiConnectionProtocol(*args, **kwargs)[source]
-

Bases: twisted.protocols.amp.AMP

-

AMP protocol that safely handle multiple connections to the same -server without dropping old ones - new clients will receive -all server returns (broadcast). Will also correctly handle -erroneous HTTP requests on the port and return a HTTP error response.

-
-
-__init__(*args, **kwargs)[source]
-

Initialize protocol with some things that need to be in place -already before connecting both on portal and server.

-
- -
-
-dataReceived(data)[source]
-

Handle non-AMP messages, such as HTTP communication.

-
- -
-
-makeConnection(transport)[source]
-

Swallow connection log message here. Copied from original -in the amp protocol.

-
- -
-
-connectionMade()[source]
-

This is called when an AMP connection is (re-)established. AMP calls it on both sides.

-
- -
-
-connectionLost(reason)[source]
-

We swallow connection errors here. The reason is that during a -normal reload/shutdown there will almost always be cases where -either the portal or server shuts down before a message has -returned its (empty) return, triggering a connectionLost error -that is irrelevant. If a true connection error happens, the -portal will continuously try to reconnect, showing the problem -that way.

-
- -
-
-errback(e, info)[source]
-

Error callback. -Handles errors to avoid dropping connections on server tracebacks.

-
-
Parameters
-
    -
  • e (Failure) – Deferred error instance.

  • -
  • info (str) – Error string.

  • -
-
-
-
- -
-
-data_in(packed_data)[source]
-

Process incoming packed data.

-
-
Parameters
-

packed_data (bytes) – Pickled data.

-
-
Returns
-

unpaced_data (any) – Unpickled package

-
-
-
- -
-
-broadcast(command, sessid, **kwargs)[source]
-

Send data across the wire to all connections.

-
-
Parameters
-
    -
  • command (AMP Command) – A protocol send command.

  • -
  • sessid (int) – A unique Session id.

  • -
-
-
Returns
-

deferred (deferred or None) – A deferred with an errback.

-
-
-

Notes

-

Data will be sent across the wire pickled as a tuple -(sessid, kwargs).

-
- -
-
-send_FunctionCall(modulepath, functionname, *args, **kwargs)[source]
-

Access method called by either process. This will call an arbitrary -function on the other process (On Portal if calling from Server and -vice versa).

-
-
Inputs:

modulepath (str) - python path to module holding function to call -functionname (str) - name of function in given module -*args, **kwargs will be used as arguments/keyword args for the

-
-

remote function call

-
-
-
-
-
Returns
-

A deferred that fires with the return value of the remote -function call

-
-
-
- -
-
-receive_functioncall()[source]
-

Helper decorator

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.amp_server.html b/docs/0.9.5/api/evennia.server.portal.amp_server.html deleted file mode 100644 index 0f1eed2f5b..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.amp_server.html +++ /dev/null @@ -1,335 +0,0 @@ - - - - - - - - - evennia.server.portal.amp_server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.amp_server

-

The Evennia Portal service acts as an AMP-server, handling AMP -communication to the AMP clients connecting to it (by default -these are the Evennia Server and the evennia launcher).

-
-
-evennia.server.portal.amp_server.getenv()[source]
-

Get current environment and add PYTHONPATH.

-
-
Returns
-

env (dict) – Environment global dict.

-
-
-
- -
-
-class evennia.server.portal.amp_server.AMPServerFactory(portal)[source]
-

Bases: twisted.internet.protocol.ServerFactory

-

This factory creates AMP Server connection. This acts as the ‘Portal’-side communication to the -‘Server’ process.

-
-
-noisy = False
-
- -
-
-logPrefix()[source]
-

How this is named in logs

-
- -
-
-__init__(portal)[source]
-

Initialize the factory. This is called as the Portal service starts.

-
-
Parameters
-
    -
  • portal (Portal) – The Evennia Portal service instance.

  • -
  • protocol (Protocol) – The protocol the factory creates -instances of.

  • -
-
-
-
- -
-
-buildProtocol(addr)[source]
-

Start a new connection, and store it on the service object.

-
-
Parameters
-

addr (str) – Connection address. Not used.

-
-
Returns
-

protocol (Protocol) – The created protocol.

-
-
-
- -
- -
-
-class evennia.server.portal.amp_server.AMPServerProtocol(*args, **kwargs)[source]
-

Bases: evennia.server.portal.amp.AMPMultiConnectionProtocol

-

Protocol subclass for the AMP-server run by the Portal.

-
-
-connectionLost(reason)[source]
-

Set up a simple callback mechanism to let the amp-server wait for a connection to close.

-
- -
-
-get_status()[source]
-

Return status for the Evennia infrastructure.

-
-
Returns
-

status (tuple)

-
-
The portal/server status and pids

(portal_live, server_live, portal_PID, server_PID).

-
-
-

-
-
-
- -
-
-data_to_server(command, sessid, **kwargs)[source]
-

Send data across the wire to the Server.

-
-
Parameters
-
    -
  • command (AMP Command) – A protocol send command.

  • -
  • sessid (int) – A unique Session id.

  • -
  • kwargs (any) – Data to send. This will be pickled.

  • -
-
-
Returns
-

deferred (deferred or None) – A deferred with an errback.

-
-
-

Notes

-

Data will be sent across the wire pickled as a tuple -(sessid, kwargs).

-
- -
-
-start_server(server_twistd_cmd)[source]
-

(Re-)Launch the Evennia server.

-
-
Parameters
-

server_twisted_cmd (list) – The server start instruction -to pass to POpen to start the server.

-
-
-
- -
-
-wait_for_disconnect(callback, *args, **kwargs)[source]
-

Add a callback for when this connection is lost.

-
-
Parameters
-

callback (callable) – Will be called with *args, **kwargs -once this protocol is disconnected.

-
-
-
- -
-
-wait_for_server_connect(callback, *args, **kwargs)[source]
-

Add a callback for when the Server is sure to have connected.

-
-
Parameters
-

callback (callable) – Will be called with *args, **kwargs -once the Server handshake with Portal is complete.

-
-
-
- -
-
-stop_server(mode='shutdown')[source]
-

Shut down server in one or more modes.

-
-
Parameters
-

mode (str) – One of ‘shutdown’, ‘reload’ or ‘reset’.

-
-
-
- -
-
-send_Status2Launcher()[source]
-

Send a status stanza to the launcher.

-
- -
-
-send_MsgPortal2Server(session, **kwargs)[source]
-

Access method called by the Portal and executed on the Portal.

-
-
Parameters
-
    -
  • session (session) – Session

  • -
  • kwargs (any, optional) – Optional data.

  • -
-
-
Returns
-

deferred (Deferred) – Asynchronous return.

-
-
-
- -
-
-send_AdminPortal2Server(session, operation='', **kwargs)[source]
-

Send Admin instructions from the Portal to the Server. -Executed on the Portal.

-
-
Parameters
-
    -
  • session (Session) – Session.

  • -
  • operation (char, optional) – Identifier for the server operation, as defined by the -global variables in evennia/server/amp.py.

  • -
  • data (str or dict, optional) – Data used in the administrative operation.

  • -
-
-
-
- -
-
-portal_receive_status()
-

Helper decorator

-
- -
-
-portal_receive_launcher2portal()
-

Helper decorator

-
- -
-
-portal_receive_server2portal()
-

Helper decorator

-
- -
-
-portal_receive_adminserver2portal()
-

Helper decorator

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.grapevine.html b/docs/0.9.5/api/evennia.server.portal.grapevine.html deleted file mode 100644 index 9bdf2b7519..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.grapevine.html +++ /dev/null @@ -1,332 +0,0 @@ - - - - - - - - - evennia.server.portal.grapevine — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.grapevine

-

Grapevine network connection

-

This is an implementation of the Grapevine Websocket protocol v 1.0.0 as -outlined here: https://grapevine.haus/docs

-

This will allow the linked game to transfer status as well as connects -the grapevine client to in-game channels.

-
-
-class evennia.server.portal.grapevine.RestartingWebsocketServerFactory(sessionhandler, *args, **kwargs)[source]
-

Bases: autobahn.twisted.websocket.WebSocketClientFactory, twisted.internet.protocol.ReconnectingClientFactory

-

A variant of the websocket-factory that auto-reconnects.

-
-
-initialDelay = 1
-
- -
-
-factor = 1.5
-
- -
-
-maxDelay = 60
-
- -
-
-__init__(sessionhandler, *args, **kwargs)[source]
-

In addition to all arguments to the constructor of -:func:autobahn.websocket.interfaces.IWebSocketClientChannelFactory, -you can supply a **reactor** keyword argument to specify the -Twisted reactor to be used.

-
- -
-
-buildProtocol(addr)[source]
-

Build new instance of protocol

-
-
Parameters
-

addr (str) – Not used, using factory/settings data

-
-
-
- -
-
-startedConnecting(connector)[source]
-

Tracks reconnections for debugging.

-
-
Parameters
-

connector (Connector) – Represents the connection.

-
-
-
- -
-
-clientConnectionFailed(connector, reason)[source]
-

Called when Client failed to connect.

-
-
Parameters
-
    -
  • connector (Connection) – Represents the connection.

  • -
  • reason (str) – The reason for the failure.

  • -
-
-
-
- -
-
-clientConnectionLost(connector, reason)[source]
-

Called when Client loses connection.

-
-
Parameters
-
    -
  • connector (Connection) – Represents the connection.

  • -
  • reason (str) – The reason for the failure.

  • -
-
-
-
- -
-
-reconnect()[source]
-

Force a reconnection of the bot protocol. This requires -de-registering the session and then reattaching a new one, -otherwise you end up with an ever growing number of bot -sessions.

-
- -
-
-start()[source]
-

Connect protocol to remote server

-
- -
- -
-
-class evennia.server.portal.grapevine.GrapevineClient[source]
-

Bases: autobahn.twisted.websocket.WebSocketClientProtocol, evennia.server.session.Session

-

Implements the grapevine client

-
-
-__init__()[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-at_login()[source]
-
- -
-
-onOpen()[source]
-

Called when connection is established.

-
- -
-
-onMessage(payload, isBinary)[source]
-

Callback fired when a complete WebSocket message was received.

-
-
Parameters
-
    -
  • payload (bytes) – The WebSocket message received.

  • -
  • isBinary (bool) – Flag indicating whether payload is binary or -UTF-8 encoded text.

  • -
-
-
-
- -
-
-onClose(wasClean, code=None, reason=None)[source]
-

This is executed when the connection is lost for whatever -reason. it can also be called directly, from the disconnect -method.

-
-
Parameters
-
    -
  • wasClean (bool) – **True** if the WebSocket was closed cleanly.

  • -
  • code (int or None) – Close status as sent by the WebSocket peer.

  • -
  • reason (str or None) – Close reason as sent by the WebSocket peer.

  • -
-
-
-
- -
-
-disconnect(reason=None)[source]
-

Generic hook for the engine to call in order to -disconnect this protocol.

-
-
Parameters
-

reason (str or None) – Motivation for the disconnection.

-
-
-
- -
-
-send_authenticate(*args, **kwargs)[source]
-

Send grapevine authentication. This should be send immediately upon connection.

-
- -
-
-send_heartbeat(*args, **kwargs)[source]
-

Send heartbeat to remote grapevine server.

-
- -
-
-send_subscribe(channelname, *args, **kwargs)[source]
-

Subscribe to new grapevine channel

-

Use with session.msg(subscribe=”channelname”)

-
- -
-
-send_unsubscribe(channelname, *args, **kwargs)[source]
-

Un-subscribe to a grapevine channel

-

Use with session.msg(unsubscribe=”channelname”)

-
- -
-
-send_channel(text, channel, sender, *args, **kwargs)[source]
-

Send text type Evennia -> grapevine

-

This is the channels/send message type

-

Use with session.msg(channel=(message, channel, sender))

-
- -
-
-send_default(*args, **kwargs)[source]
-

Ignore other outputfuncs

-
- -
-
-data_in(data, **kwargs)[source]
-

Send data grapevine -> Evennia

-
-
Keyword Arguments
-

data (dict) – Converted json data.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.html b/docs/0.9.5/api/evennia.server.portal.html deleted file mode 100644 index f182d403a8..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - evennia.server.portal — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.irc.html b/docs/0.9.5/api/evennia.server.portal.irc.html deleted file mode 100644 index dc903397da..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.irc.html +++ /dev/null @@ -1,448 +0,0 @@ - - - - - - - - - evennia.server.portal.irc — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.irc

-

This connects to an IRC network/channel and launches an ‘bot’ onto it. -The bot then pipes what is being said between the IRC channel and one or -more Evennia channels.

-
-
-evennia.server.portal.irc.parse_ansi_to_irc(string)[source]
-

Parse |-type syntax and replace with IRC color markers

-
-
Parameters
-

string (str) – String to parse for ANSI colors.

-
-
Returns
-

parsed_string (str) – String with replaced ANSI colors.

-
-
-
- -
-
-evennia.server.portal.irc.parse_irc_to_ansi(string)[source]
-

Parse IRC mIRC color syntax and replace with Evennia ANSI color markers

-
-
Parameters
-

string (str) – String to parse for IRC colors.

-
-
Returns
-

parsed_string (str) – String with replaced IRC colors.

-
-
-
- -
-
-class evennia.server.portal.irc.IRCBot[source]
-

Bases: twisted.words.protocols.irc.IRCClient, evennia.server.session.Session

-

An IRC bot that tracks activity in a channel as well -as sends text to it when prompted

-
-
-lineRate = 1
-
- -
-
-nickname = None
-
- -
-
-logger = None
-
- -
-
-factory: Optional[twisted.internet.protocol.Factory] = None
-
- -
-
-channel = None
-
- -
-
-sourceURL = 'http://code.evennia.com'
-
- -
-
-signedOn()[source]
-

This is called when we successfully connect to the network. We -make sure to now register with the game as a full session.

-
- -
-
-disconnect(reason='')[source]
-

Called by sessionhandler to disconnect this protocol.

-
-
Parameters
-

reason (str) – Motivation for the disconnect.

-
-
-
- -
-
-at_login()[source]
-
- -
-
-privmsg(user, channel, msg)[source]
-

Called when the connected channel receives a message.

-
-
Parameters
-
    -
  • user (str) – User name sending the message.

  • -
  • channel (str) – Channel name seeing the message.

  • -
  • msg (str) – The message arriving from channel.

  • -
-
-
-
- -
-
-action(user, channel, msg)[source]
-

Called when an action is detected in channel.

-
-
Parameters
-
    -
  • user (str) – User name sending the message.

  • -
  • channel (str) – Channel name seeing the message.

  • -
  • msg (str) – The message arriving from channel.

  • -
-
-
-
- -
-
-get_nicklist()[source]
-

Retrieve name list from the channel. The return -is handled by the catch methods below.

-
- -
-
-irc_RPL_NAMREPLY(prefix, params)[source]
-

“Handles IRC NAME request returns (nicklist)

-
- -
-
-irc_RPL_ENDOFNAMES(prefix, params)[source]
-

Called when the nicklist has finished being returned.

-
- -
-
-pong(user, time)[source]
-

Called with the return timing from a PING.

-
-
Parameters
-
    -
  • user (str) – Name of user

  • -
  • time (float) – Ping time in secs.

  • -
-
-
-
- -
-
-data_in(text=None, **kwargs)[source]
-

Data IRC -> Server.

-
-
Keyword Arguments
-
    -
  • text (str) – Ingoing text.

  • -
  • kwargs (any) – Other data from protocol.

  • -
-
-
-
- -
-
-send_channel(*args, **kwargs)[source]
-

Send channel text to IRC channel (visible to all). Note that -we don’t handle the “text” send (it’s rerouted to send_default -which does nothing) - this is because the IRC bot is a normal -session and would otherwise report anything that happens to it -to the IRC channel (such as it seeing server reload messages).

-
-
Parameters
-

text (str) – Outgoing text

-
-
-
- -
-
-send_privmsg(*args, **kwargs)[source]
-

Send message only to specific user.

-
-
Parameters
-

text (str) – Outgoing text.

-
-
Keyword Arguments
-

user (str) – the nick to send -privately to.

-
-
-
- -
-
-send_request_nicklist(*args, **kwargs)[source]
-

Send a request for the channel nicklist. The return (handled -by self.irc_RPL_ENDOFNAMES) will be sent back as a message -with type **nicklist’.

-
- -
-
-send_ping(*args, **kwargs)[source]
-

Send a ping. The return (handled by self.pong) will be sent -back as a message of type ‘ping’.

-
- -
-
-send_reconnect(*args, **kwargs)[source]
-

The server instructs us to rebuild the connection by force, -probably because the client silently lost connection.

-
- -
-
-send_default(*args, **kwargs)[source]
-

Ignore other types of sends.

-
- -
- -
-
-class evennia.server.portal.irc.IRCBotFactory(sessionhandler, uid=None, botname=None, channel=None, network=None, port=None, ssl=None)[source]
-

Bases: twisted.internet.protocol.ReconnectingClientFactory

-

Creates instances of IRCBot, connecting with a staggered -increase in delay

-
-
-initialDelay = 1
-
- -
-
-factor = 1.5
-
- -
-
-maxDelay = 60
-
- -
-
-__init__(sessionhandler, uid=None, botname=None, channel=None, network=None, port=None, ssl=None)[source]
-

Storing some important protocol properties.

-
-
Parameters
-

sessionhandler (SessionHandler) – Reference to the main Sessionhandler.

-
-
Keyword Arguments
-
    -
  • uid (int) – Bot user id.

  • -
  • botname (str) – Bot name (seen in IRC channel).

  • -
  • channel (str) – IRC channel to connect to.

  • -
  • network (str) – Network address to connect to.

  • -
  • port (str) – Port of the network.

  • -
  • ssl (bool) – Indicates SSL connection.

  • -
-
-
-
- -
-
-buildProtocol(addr)[source]
-

Build the protocol and assign it some properties.

-
-
Parameters
-

addr (str) – Not used; using factory data.

-
-
-
- -
-
-startedConnecting(connector)[source]
-

Tracks reconnections for debugging.

-
-
Parameters
-

connector (Connector) – Represents the connection.

-
-
-
- -
-
-clientConnectionFailed(connector, reason)[source]
-

Called when Client failed to connect.

-
-
Parameters
-
    -
  • connector (Connection) – Represents the connection.

  • -
  • reason (str) – The reason for the failure.

  • -
-
-
-
- -
-
-clientConnectionLost(connector, reason)[source]
-

Called when Client loses connection.

-
-
Parameters
-
    -
  • connector (Connection) – Represents the connection.

  • -
  • reason (str) – The reason for the failure.

  • -
-
-
-
- -
-
-reconnect()[source]
-

Force a reconnection of the bot protocol. This requires -de-registering the session and then reattaching a new one, -otherwise you end up with an ever growing number of bot -sessions.

-
- -
-
-start()[source]
-

Connect session to sessionhandler.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.mccp.html b/docs/0.9.5/api/evennia.server.portal.mccp.html deleted file mode 100644 index d4a0bbca71..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.mccp.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - evennia.server.portal.mccp — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.mccp

-

MCCP - Mud Client Compression Protocol

-

This implements the MCCP v2 telnet protocol as per -http://tintin.sourceforge.net/mccp/. MCCP allows for the server to -compress data when sending to supporting clients, reducing bandwidth -by 70-90%.. The compression is done using Python’s builtin zlib -library. If the client doesn’t support MCCP, server sends uncompressed -as normal. Note: On modern hardware you are not likely to notice the -effect of MCCP unless you have extremely heavy traffic or sits on a -terribly slow connection.

-

This protocol is implemented by the telnet protocol importing -mccp_compress and calling it from its write methods.

-
-
-evennia.server.portal.mccp.mccp_compress(protocol, data)[source]
-

Handles zlib compression, if applicable.

-
-
Parameters
-

data (str) – Incoming data to compress.

-
-
Returns
-

stream (binary) – Zlib-compressed data.

-
-
-
- -
-
-class evennia.server.portal.mccp.Mccp(protocol)[source]
-

Bases: object

-

Implements the MCCP protocol. Add this to a -variable on the telnet protocol to set it up.

-
-
-__init__(protocol)[source]
-

initialize MCCP by storing protocol on -ourselves and calling the client to see if -it supports MCCP. Sets callbacks to -start zlib compression in that case.

-
-
Parameters
-

protocol (Protocol) – The active protocol instance.

-
-
-
- -
-
-no_mccp(option)[source]
-

Called if client doesn’t support mccp or chooses to turn it off.

-
-
Parameters
-

option (Option) – Option dict (not used).

-
-
-
- -
-
-do_mccp(option)[source]
-

The client supports MCCP. Set things up by -creating a zlib compression stream.

-
-
Parameters
-

option (Option) – Option dict (not used).

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.mssp.html b/docs/0.9.5/api/evennia.server.portal.mssp.html deleted file mode 100644 index 23c118ce25..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.mssp.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - evennia.server.portal.mssp — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.mssp

-

MSSP - Mud Server Status Protocol

-

This implements the MSSP telnet protocol as per -http://tintin.sourceforge.net/mssp/. MSSP allows web portals and -listings to have their crawlers find the mud and automatically -extract relevant information about it, such as genre, how many -active players and so on.

-
-
-class evennia.server.portal.mssp.Mssp(protocol)[source]
-

Bases: object

-

Implements the MSSP protocol. Add this to a variable on the telnet -protocol to set it up.

-
-
-__init__(protocol)[source]
-

initialize MSSP by storing protocol on ourselves and calling -the client to see if it supports MSSP.

-
-
Parameters
-

protocol (Protocol) – The active protocol instance.

-
-
-
- -
-
-get_player_count()[source]
-

Get number of logged-in players.

-
-
Returns
-

count (int) – The number of players in the MUD.

-
-
-
- -
-
-get_uptime()[source]
-

Get how long the portal has been online (reloads are not counted).

-
-
Returns
-

uptime (int) – Number of seconds of uptime.

-
-
-
- -
-
-no_mssp(option)[source]
-

Called when mssp is not requested. This is the normal -operation.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-do_mssp(option)[source]
-

Negotiate all the information.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.mxp.html b/docs/0.9.5/api/evennia.server.portal.mxp.html deleted file mode 100644 index fc5761aab2..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.mxp.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - - - evennia.server.portal.mxp — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.mxp

-

MXP - Mud eXtension Protocol.

-

Partial implementation of the MXP protocol. -The MXP protocol allows more advanced formatting options for telnet clients -that supports it (mudlet, zmud, mushclient are a few)

-

This only implements the SEND tag.

-

More information can be found on the following links: -http://www.zuggsoft.com/zmud/mxp.htm -http://www.mushclient.com/mushclient/mxp.htm -http://www.gammon.com.au/mushclient/addingservermxp.htm

-
-
-evennia.server.portal.mxp.mxp_parse(text)[source]
-

Replaces links to the correct format for MXP.

-
-
Parameters
-

text (str) – The text to parse.

-
-
Returns
-

parsed (str) – The parsed text.

-
-
-
- -
-
-class evennia.server.portal.mxp.Mxp(protocol)[source]
-

Bases: object

-

Implements the MXP protocol.

-
-
-__init__(protocol)[source]
-

Initializes the protocol by checking if the client supports it.

-
-
Parameters
-

protocol (Protocol) – The active protocol instance.

-
-
-
- -
-
-no_mxp(option)[source]
-

Called when the Client reports to not support MXP.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-do_mxp(option)[source]
-

Called when the Client reports to support MXP.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.naws.html b/docs/0.9.5/api/evennia.server.portal.naws.html deleted file mode 100644 index 63d59df04f..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.naws.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - - - evennia.server.portal.naws — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.naws

-

NAWS - Negotiate About Window Size

-

This implements the NAWS telnet option as per -https://www.ietf.org/rfc/rfc1073.txt

-

NAWS allows telnet clients to report their current window size to the -client and update it when the size changes

-
-
-class evennia.server.portal.naws.Naws(protocol)[source]
-

Bases: object

-

Implements the NAWS protocol. Add this to a variable on the telnet -protocol to set it up.

-
-
-__init__(protocol)[source]
-

initialize NAWS by storing protocol on ourselves and calling -the client to see if it supports NAWS.

-
-
Parameters
-

protocol (Protocol) – The active protocol instance.

-
-
-
- -
-
-no_naws(option)[source]
-

Called when client is not reporting NAWS. This is the normal -operation.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-do_naws(option)[source]
-

Client wants to negotiate all the NAWS information.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-negotiate_sizes(options)[source]
-

Step through the NAWS handshake.

-
-
Parameters
-

option (list) – The incoming NAWS options.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.portal.html b/docs/0.9.5/api/evennia.server.portal.portal.html deleted file mode 100644 index aa0c0fd5f0..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.portal.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - evennia.server.portal.portal — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.portal

-

This module implements the main Evennia server process, the core of -the game engine.

-

This module should be started with the ‘twistd’ executable since it -sets up all the networking features. (this is done automatically -by game/evennia.py).

-
-
-class evennia.server.portal.portal.Portal(application)[source]
-

Bases: object

-

The main Portal server handler. This object sets up the database -and tracks and interlinks all the twisted network services that -make up Portal.

-
-
-__init__(application)[source]
-

Setup the server.

-
-
Parameters
-

application (Application) – An instantiated Twisted application

-
-
-
- -
-
-get_info_dict()[source]
-

Return the Portal info, for display.

-
- -
-
-shutdown(_reactor_stopping=False, _stop_server=False)[source]
-

Shuts down the server from inside it.

-
-
Parameters
-
    -
  • _reactor_stopping (bool, optional) – This is set if server -is already in the process of shutting down; in this case -we don’t need to stop it again.

  • -
  • _stop_server (bool, optional) – Only used in portal-interactive mode; -makes sure to stop the Server cleanly.

  • -
-
-
-

Note that restarting (regardless of the setting) will not work -if the Portal is currently running in daemon mode. In that -case it always needs to be restarted manually.

-
- -
- -
-
-class evennia.server.portal.portal.Websocket(*args, **kwargs)[source]
-

Bases: autobahn.twisted.websocket.WebSocketServerFactory

-

Only here for better naming in logs

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.portalsessionhandler.html b/docs/0.9.5/api/evennia.server.portal.portalsessionhandler.html deleted file mode 100644 index 967911b5dc..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.portalsessionhandler.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - evennia.server.portal.portalsessionhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.portalsessionhandler

-

Sessionhandler for portal sessions

-
-
-class evennia.server.portal.portalsessionhandler.PortalSessionHandler(*args, **kwargs)[source]
-

Bases: evennia.server.sessionhandler.SessionHandler

-

This object holds the sessions connected to the portal at any time. -It is synced with the server’s equivalent SessionHandler over the AMP -connection.

-

Sessions register with the handler using the connect() method. This -will assign a new unique sessionid to the session and send that sessid -to the server using the AMP connection.

-
-
-__init__(*args, **kwargs)[source]
-

Init the handler

-
- -
-
-at_server_connection()[source]
-

Called when the Portal establishes connection with the Server. -At this point, the AMP connection is already established.

-
- -
-
-connect(session)[source]
-

Called by protocol at first connect. This adds a not-yet -authenticated session using an ever-increasing counter for -sessid.

-
-
Parameters
-

session (PortalSession) – The Session connecting.

-
-
-

Notes

-

We implement a throttling mechanism here to limit the speed at -which new connections are accepted - this is both a stop -against DoS attacks as well as helps using the Dummyrunner -tester with a large number of connector dummies.

-
- -
-
-sync(session)[source]
-

Called by the protocol of an already connected session. This -can be used to sync the session info in a delayed manner, such -as when negotiation and handshakes are delayed.

-
-
Parameters
-

session (PortalSession) – Session to sync.

-
-
-
- -
-
-disconnect(session)[source]
-

Called from portal when the connection is closed from the -portal side.

-
-
Parameters
-
    -
  • session (PortalSession) – Session to disconnect.

  • -
  • delete (bool, optional) – Delete the session from -the handler. Only time to not do this is when -this is called from a loop, such as from -self.disconnect_all().

  • -
-
-
-
- -
-
-disconnect_all()[source]
-

Disconnect all sessions, informing the Server.

-
- -
-
-server_connect(protocol_path='', config={})[source]
-

Called by server to force the initialization of a new protocol -instance. Server wants this instance to get a unique sessid and to be -connected back as normal. This is used to initiate irc/rss etc -connections.

-
-
Parameters
-
    -
  • protocol_path (str) – Full python path to the class factory -for the protocol used, eg -‘evennia.server.portal.irc.IRCClientFactory’

  • -
  • config (dict) – Dictionary of configuration options, fed as -**kwarg to protocol class __init__ method.

  • -
-
-
Raises
-

RuntimeError – If The correct factory class is not found.

-
-
-

Notes

-

The called protocol class must have a method start() -that calls the portalsession.connect() as a normal protocol.

-
- -
-
-server_disconnect(session, reason='')[source]
-

Called by server to force a disconnect by sessid.

-
-
Parameters
-
    -
  • session (portalsession) – Session to disconnect.

  • -
  • reason (str, optional) – Motivation for disconnect.

  • -
-
-
-
- -
-
-server_disconnect_all(reason='')[source]
-

Called by server when forcing a clean disconnect for everyone.

-
-
Parameters
-

reason (str, optional) – Motivation for disconnect.

-
-
-
- -
-
-server_logged_in(session, data)[source]
-

The server tells us that the session has been authenticated. -Update it. Called by the Server.

-
-
Parameters
-
    -
  • session (Session) – Session logging in.

  • -
  • data (dict) – The session sync data.

  • -
-
-
-
- -
-
-server_session_sync(serversessions, clean=True)[source]
-

Server wants to save data to the portal, maybe because it’s -about to shut down. We don’t overwrite any sessions here, just -update them in-place.

-
-
Parameters
-
    -
  • serversessions (dict) –

    This is a dictionary

    -

    {sessid:{property:value},…} describing -the properties to sync on all sessions.

    -

  • -
  • clean (bool) – If True, remove any Portal sessions that are -not included in serversessions.

  • -
-
-
-
- -
-
-count_loggedin(include_unloggedin=False)[source]
-

Count loggedin connections, alternatively count all connections.

-
-
Parameters
-
    -
  • include_unloggedin (bool) – Also count sessions that have

  • -
  • yet authenticated. (not) –

  • -
-
-
Returns
-

count (int) – Number of sessions.

-
-
-
- -
-
-sessions_from_csessid(csessid)[source]
-

Given a session id, retrieve the session (this is primarily -intended to be called by web clients)

-
-
Parameters
-

csessid (int) – Session id.

-
-
Returns
-

session (list) – The matching session, if found.

-
-
-
- -
-
-announce_all(message)[source]
-

Send message to all connected sessions.

-
-
Parameters
-

message (str) – Message to relay.

-
-
-

Notes

-

This will create an on-the fly text-type -send command.

-
- -
-
-data_in(session, **kwargs)[source]
-

Called by portal sessions for relaying data coming -in from the protocol to the server.

-
-
Parameters
-

session (PortalSession) – Session receiving data.

-
-
Keyword Arguments
-

kwargs (any) – Other data from protocol.

-
-
-

Notes

-

Data is serialized before passed on.

-
- -
-
-data_out(session, **kwargs)[source]
-

Called by server for having the portal relay messages and data -to the correct session protocol.

-
-
Parameters
-

session (Session) – Session sending data.

-
-
Keyword Arguments
-

kwargs (any) – Each key is a command instruction to the -protocol on the form key = [[args],{kwargs}]. This will -call a method send_<key> on the protocol. If no such -method exixts, it sends the data to a method send_default.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.rss.html b/docs/0.9.5/api/evennia.server.portal.rss.html deleted file mode 100644 index 30aaa63b64..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.rss.html +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - evennia.server.portal.rss — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.rss

-

RSS parser for Evennia

-

This connects an RSS feed to an in-game Evennia channel, sending messages -to the channel whenever the feed updates.

-
-
-class evennia.server.portal.rss.RSSReader(factory, url, rate)[source]
-

Bases: evennia.server.session.Session

-

A simple RSS reader using the feedparser module.

-
-
-__init__(factory, url, rate)[source]
-

Initialize the reader.

-
-
Parameters
-
    -
  • factory (RSSFactory) – The protocol factory.

  • -
  • url (str) – The RSS url.

  • -
  • rate (int) – The seconds between RSS lookups.

  • -
-
-
-
- -
-
-get_new()[source]
-

Returns list of new items.

-
- -
-
-disconnect(reason=None)[source]
-

Disconnect from feed.

-
-
Parameters
-

reason (str, optional) – Motivation for the disconnect.

-
-
-
- -
-
-data_in(text=None, **kwargs)[source]
-

Data RSS -> Evennia.

-
-
Keyword Arguments
-
    -
  • text (str) – Incoming text

  • -
  • kwargs (any) – Options from protocol.

  • -
-
-
-
- -
-
-update(init=False)[source]
-

Request the latest version of feed.

-
-
Parameters
-

init (bool, optional) – If this is an initialization call -or not (during init, all entries are conidered new).

-
-
-

Notes

-

This call is done in a separate thread to avoid blocking -on slow connections.

-
- -
- -
-
-class evennia.server.portal.rss.RSSBotFactory(sessionhandler, uid=None, url=None, rate=None)[source]
-

Bases: object

-

Initializes new bots.

-
-
-__init__(sessionhandler, uid=None, url=None, rate=None)[source]
-

Initialize the bot.

-
-
Parameters
-
    -
  • sessionhandler (PortalSessionHandler) – The main sessionhandler object.

  • -
  • uid (int) – User id for the bot.

  • -
  • url (str) – The RSS URL.

  • -
  • rate (int) – How often for the RSS to request the latest RSS entries.

  • -
-
-
-
- -
-
-start()[source]
-

Called by portalsessionhandler. Starts the bot.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.ssh.html b/docs/0.9.5/api/evennia.server.portal.ssh.html deleted file mode 100644 index c160034c2a..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.ssh.html +++ /dev/null @@ -1,433 +0,0 @@ - - - - - - - - - evennia.server.portal.ssh — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.ssh

-

This module implements the ssh (Secure SHell) protocol for encrypted -connections.

-

This depends on a generic session module that implements the actual -login procedure of the game, tracks sessions etc.

-

Using standard ssh client,

-
-
-class evennia.server.portal.ssh.SSHServerFactory[source]
-

Bases: twisted.internet.protocol.ServerFactory

-

This is only to name this better in logs

-
-
-noisy = False
-
- -
-
-logPrefix()[source]
-

Describe this factory for log messages.

-
- -
- -
-
-class evennia.server.portal.ssh.SshProtocol(starttuple)[source]
-

Bases: twisted.conch.manhole.Manhole, evennia.server.session.Session

-

Each account connecting over ssh gets this protocol assigned to -them. All communication between game and account goes through -here.

-
-
-noisy = False
-
- -
-
-__init__(starttuple)[source]
-

For setting up the account. If account is not None then we’ll -login automatically.

-
-
Parameters
-

starttuple (tuple) – A (account, factory) tuple.

-
-
-
- -
-
-terminalSize(width, height)[source]
-

Initialize the terminal and connect to the new session.

-
-
Parameters
-
    -
  • width (int) – Width of terminal.

  • -
  • height (int) – Height of terminal.

  • -
-
-
-
- -
-
-connectionMade()[source]
-

This is called when the connection is first established.

-
- -
-
-handle_INT()[source]
-

Handle ^C as an interrupt keystroke by resetting the current -input variables to their initial state.

-
- -
-
-handle_EOF()[source]
-

Handles EOF generally used to exit.

-
- -
-
-handle_FF()[source]
-

Handle a ‘form feed’ byte - generally used to request a screen -refresh/redraw.

-
- -
-
-handle_QUIT()[source]
-

Quit, end, and lose the connection.

-
- -
-
-connectionLost(reason=None)[source]
-

This is executed when the connection is lost for whatever -reason. It can also be called directly, from the disconnect -method.

-
-
Parameters
-

reason (str) – Motivation for loosing connection.

-
-
-
- -
-
-getClientAddress()[source]
-

Get client address.

-
-
Returns
-

address_and_port (tuple)

-
-
The client’s address and port in

a tuple. For example (‘127.0.0.1’, 41917).

-
-
-

-
-
-
- -
-
-lineReceived(string)[source]
-

Communication User -> Evennia. Any line return indicates a -command for the purpose of the MUD. So we take the user input -and pass it on to the game engine.

-
-
Parameters
-

string (str) – Input text.

-
-
-
- -
-
-sendLine(string)[source]
-

Communication Evennia -> User. Any string sent should -already have been properly formatted and processed before -reaching this point.

-
-
Parameters
-

string (str) – Output text.

-
-
-
- -
-
-at_login()[source]
-

Called when this session gets authenticated by the server.

-
- -
-
-disconnect(reason='Connection closed. Goodbye for now.')[source]
-

Disconnect from server.

-
-
Parameters
-

reason (str) – Motivation for disconnect.

-
-
-
- -
-
-data_out(**kwargs)[source]
-

Data Evennia -> User

-
-
Keyword Arguments
-

kwargs (any) – Options to the protocol.

-
-
-
- -
-
-send_text(*args, **kwargs)[source]
-

Send text data. This is an in-band telnet operation.

-
-
Parameters
-

text (str) – The first argument is always the text string to send. No other arguments -are considered.

-
-
Keyword Arguments
-

options (dict) –

Send-option flags:

-
    -
  • mxp: Enforce MXP link support.

  • -
  • ansi: Enforce no ANSI colors.

  • -
  • xterm256: Enforce xterm256 colors, regardless of TTYPE setting.

  • -
  • nocolor: Strip all colors.

  • -
  • raw: Pass string through without any ansi processing -(i.e. include Evennia ansi markers but do not -convert them into ansi tokens)

  • -
  • echo: Turn on/off line echo on the client. Turn -off line echo for client, for example for password. -Note that it must be actively turned back on again!

  • -
-

-
-
-
- -
-
-send_prompt(*args, **kwargs)[source]
-
- -
-
-send_default(*args, **kwargs)[source]
-
- -
- -
-
-class evennia.server.portal.ssh.ExtraInfoAuthServer[source]
-

Bases: twisted.conch.ssh.userauth.SSHUserAuthServer

-
-
-noisy = False
-
- -
-
-auth_password(packet)[source]
-

Password authentication.

-

Used mostly for setting up the transport so we can query -username and password later.

-
-
Parameters
-

packet (Packet) – Auth packet.

-
-
-
- -
- -
-
-class evennia.server.portal.ssh.AccountDBPasswordChecker(factory)[source]
-

Bases: object

-

Checks the django db for the correct credentials for -username/password otherwise it returns the account or None which is -useful for the Realm.

-
-
-noisy = False
-
- -
-
-credentialInterfaces = (<InterfaceClass twisted.cred.credentials.IUsernamePassword>,)
-
- -
-
-__init__(factory)[source]
-

Initialize the factory.

-
-
Parameters
-

factory (SSHFactory) – Checker factory.

-
-
-
- -
-
-requestAvatarId(c)[source]
-

Generic credentials.

-
- -
- -
-
-class evennia.server.portal.ssh.PassAvatarIdTerminalRealm(transportFactory=None)[source]
-

Bases: twisted.conch.manhole_ssh.TerminalRealm

-

Returns an avatar that passes the avatarId through to the -protocol. This is probably not the best way to do it.

-
-
-noisy = False
-
- -
- -
-
-class evennia.server.portal.ssh.TerminalSessionTransport_getPeer(proto, chainedProtocol, avatar, width, height)[source]
-

Bases: object

-

Taken from twisted’s TerminalSessionTransport which doesn’t -provide getPeer to the transport. This one does.

-
-
-noisy = False
-
- -
-
-__init__(proto, chainedProtocol, avatar, width, height)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
- -
-
-evennia.server.portal.ssh.getKeyPair(pubkeyfile, privkeyfile)[source]
-

This function looks for RSA keypair files in the current directory. If they -do not exist, the keypair is created.

-
- -
-
-evennia.server.portal.ssh.makeFactory(configdict)[source]
-

Creates the ssh server factory.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.ssl.html b/docs/0.9.5/api/evennia.server.portal.ssl.html deleted file mode 100644 index a3ad0d519d..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.ssl.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - - evennia.server.portal.ssl — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.ssl

-

This is a simple context factory for auto-creating -SSL keys and certificates.

-
-
-class evennia.server.portal.ssl.SSLProtocol(*args, **kwargs)[source]
-

Bases: evennia.server.portal.telnet.TelnetProtocol

-

Communication is the same as telnet, except data transfer -is done with encryption.

-
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
- -
-
-evennia.server.portal.ssl.verify_SSL_key_and_cert(keyfile, certfile)[source]
-

This function looks for RSA key and certificate in the current -directory. If files ssl.key and ssl.cert does not exist, they -are created.

-
- -
-
-evennia.server.portal.ssl.getSSLContext()[source]
-

This is called by the portal when creating the SSL context -server-side.

-
-
Returns
-

ssl_context (tuple)

-
-
A key and certificate that is either

existing previously or or created on the fly.

-
-
-

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.suppress_ga.html b/docs/0.9.5/api/evennia.server.portal.suppress_ga.html deleted file mode 100644 index 3f95439e10..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.suppress_ga.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - - - - evennia.server.portal.suppress_ga — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.suppress_ga

-

SUPPRESS-GO-AHEAD

-

This supports suppressing or activating Evennia -the GO-AHEAD telnet operation after every server reply. -If the client sends no explicit DONT SUPRESS GO-AHEAD, -Evennia will default to supressing it since many clients -will fail to use it and has no knowledge of this standard.

-

It is set as the NOGOAHEAD protocol_flag option.

-

http://www.faqs.org/rfcs/rfc858.html

-
-
-class evennia.server.portal.suppress_ga.SuppressGA(protocol)[source]
-

Bases: object

-

Implements the SUPRESS-GO-AHEAD protocol. Add this to a variable on the telnet -protocol to set it up.

-
-
-__init__(protocol)[source]
-

Initialize suppression of GO-AHEADs.

-
-
Parameters
-

protocol (Protocol) – The active protocol instance.

-
-
-
- -
-
-wont_suppress_ga(option)[source]
-

Called when client requests to not suppress GA.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-will_suppress_ga(option)[source]
-

Client will suppress GA

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.telnet.html b/docs/0.9.5/api/evennia.server.portal.telnet.html deleted file mode 100644 index 824058c18f..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.telnet.html +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - - - evennia.server.portal.telnet — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.telnet

-

This module implements the telnet protocol.

-

This depends on a generic session module that implements -the actual login procedure of the game, tracks -sessions etc.

-
-
-class evennia.server.portal.telnet.TelnetServerFactory[source]
-

Bases: twisted.internet.protocol.ServerFactory

-

This is only to name this better in logs

-
-
-noisy = False
-
- -
-
-logPrefix()[source]
-

Describe this factory for log messages.

-
- -
- -
-
-class evennia.server.portal.telnet.TelnetProtocol(*args, **kwargs)[source]
-

Bases: twisted.conch.telnet.Telnet, twisted.conch.telnet.StatefulTelnetProtocol, evennia.server.session.Session

-

Each player connecting over telnet (ie using most traditional mud -clients) gets a telnet protocol instance assigned to them. All -communication between game and player goes through here.

-
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-dataReceived(data)[source]
-

Unused by default, but a good place to put debug printouts -of incoming data.

-
- -
-
-connectionMade()[source]
-

This is called when the connection is first established.

-
- -
-
-toggle_nop_keepalive()[source]
-

Allow to toggle the NOP keepalive for those sad clients that -can’t even handle a NOP instruction. This is turned off by the -protocol_flag NOPKEEPALIVE (settable e.g. by the default -@option command).

-
- -
-
-handshake_done(timeout=False)[source]
-

This is called by all telnet extensions once they are finished. -When all have reported, a sync with the server is performed. -The system will force-call this sync after a small time to handle -clients that don’t reply to handshakes at all.

-
- -
-
-at_login()[source]
-

Called when this session gets authenticated by the server.

-
- -
-
-enableRemote(option)[source]
-

This sets up the remote-activated options we allow for this protocol.

-
-
Parameters
-

option (char) – The telnet option to enable.

-
-
Returns
-

enable (bool) – If this option should be enabled.

-
-
-
- -
-
-disableRemote(option)[source]
-

Signal a programming error by raising an exception.

-

L{enableRemote} must return true for the given value of C{option} in -order for this method to be called. If a subclass of L{Telnet} -overrides enableRemote to allow certain options to be enabled, it must -also override disableRemote tto disable those options.

-

@raise NotImplementedError: Always raised.

-
- -
-
-enableLocal(option)[source]
-

Call to allow the activation of options for this protocol

-
-
Parameters
-

option (char) – The telnet option to enable locally.

-
-
Returns
-

enable (bool) – If this option should be enabled.

-
-
-
- -
-
-disableLocal(option)[source]
-

Disable a given option locally.

-
-
Parameters
-

option (char) – The telnet option to disable locally.

-
-
-
- -
-
-connectionLost(reason)[source]
-

this is executed when the connection is lost for whatever -reason. it can also be called directly, from the disconnect -method

-
-
Parameters
-

reason (str) – Motivation for losing connection.

-
-
-
- -
-
-applicationDataReceived(data)[source]
-

Telnet method called when non-telnet-command data is coming in -over the telnet connection. We pass it on to the game engine -directly.

-
-
Parameters
-

data (str) – Incoming data.

-
-
-
- -
-
-sendLine(line)[source]
-

Hook overloading the one used by linereceiver.

-
-
Parameters
-

line (str) – Line to send.

-
-
-
- -
-
-disconnect(reason='')[source]
-

generic hook for the engine to call in order to -disconnect this protocol.

-
-
Parameters
-

reason (str, optional) – Reason for disconnecting.

-
-
-
- -
-
-data_in(**kwargs)[source]
-

Data User -> Evennia

-
-
Keyword Arguments
-

kwargs (any) – Options from the protocol.

-
-
-
- -
-
-data_out(**kwargs)[source]
-

Data Evennia -> User

-
-
Keyword Arguments
-

kwargs (any) – Options to the protocol

-
-
-
- -
-
-send_text(*args, **kwargs)[source]
-

Send text data. This is an in-band telnet operation.

-
-
Parameters
-

text (str) – The first argument is always the text string to send. No other arguments -are considered.

-
-
Keyword Arguments
-

options (dict) –

Send-option flags:

-
    -
  • mxp: Enforce MXP link support.

  • -
  • ansi: Enforce no ANSI colors.

  • -
  • xterm256: Enforce xterm256 colors, regardless of TTYPE.

  • -
  • noxterm256: Enforce no xterm256 color support, regardless of TTYPE.

  • -
  • nocolor: Strip all Color, regardless of ansi/xterm256 setting.

  • -
  • raw: Pass string through without any ansi processing -(i.e. include Evennia ansi markers but do not -convert them into ansi tokens)

  • -
  • echo: Turn on/off line echo on the client. Turn -off line echo for client, for example for password. -Note that it must be actively turned back on again!

  • -
-

-
-
-
- -
-
-send_prompt(*args, **kwargs)[source]
-

Send a prompt - a text without a line end. See send_text for argument options.

-
- -
-
-send_default(cmdname, *args, **kwargs)[source]
-

Send other oob data

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.telnet_oob.html b/docs/0.9.5/api/evennia.server.portal.telnet_oob.html deleted file mode 100644 index a36bcd802c..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.telnet_oob.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - - - - - - evennia.server.portal.telnet_oob — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.telnet_oob

-

Telnet OOB (Out of band communication)

-

OOB protocols allow for asynchronous communication between Evennia and -compliant telnet clients. The “text” type of send command will always -be sent “in-band”, appearing in the client’s main text output. OOB -commands, by contrast, can have many forms and it is up to the client -how and if they are handled. Examples of OOB instructions could be to -instruct the client to play sounds or to update a graphical health -bar.

-

> Note that in Evennia’s Web client, all send commands are “OOB commands”, -(including the “text” one), there is no equivalence to MSDP/GMCP for the -webclient since it doesn’t need it.

-

This implements the following telnet OOB communication protocols:

- -

Following the lead of KaVir’s protocol snippet, we first check if client -supports MSDP and if not, we fallback to GMCP with a MSDP header where -applicable.

-
-
-
-class evennia.server.portal.telnet_oob.TelnetOOB(protocol)[source]
-

Bases: object

-

Implements the MSDP and GMCP protocols.

-
-
-__init__(protocol)[source]
-

Initiates by storing the protocol on itself and trying to -determine if the client supports MSDP.

-
-
Parameters
-

protocol (Protocol) – The active protocol.

-
-
-
- -
-
-no_msdp(option)[source]
-

Client reports No msdp supported or wanted.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-do_msdp(option)[source]
-

Client reports that it supports msdp.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-no_gmcp(option)[source]
-

If this is reached, it means neither MSDP nor GMCP is -supported.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-do_gmcp(option)[source]
-

Called when client confirms that it can do MSDP or GMCP.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-encode_msdp(cmdname, *args, **kwargs)[source]
-

Encode into a valid MSDP command.

-
-
Parameters
-
    -
  • cmdname (str) – Name of send instruction.

  • -
  • args (any) – Arguments to OOB command.

  • -
  • kwargs (any) – Arguments to OOB command.

  • -
-
-
-

Notes

-

The output of this encoding will be -MSDP structures on these forms:

-
[cmdname, [], {}]          -> VAR cmdname VAL ""
-[cmdname, [arg], {}]       -> VAR cmdname VAL arg
-[cmdname, [args],{}]       -> VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE
-[cmdname, [], {kwargs}]    -> VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE
-[cmdname, [args], {kwargs}] -> VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE
-                               VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE
-
-
-

Further nesting is not supported, so if an array argument consists -of an array (for example), that array will be json-converted to a -string.

-
- -
-
-encode_gmcp(cmdname, *args, **kwargs)[source]
-

Encode into GMCP messages.

-
-
Parameters
-
    -
  • cmdname (str) – GMCP OOB command name.

  • -
  • args (any) – Arguments to OOB command.

  • -
  • kwargs (any) – Arguments to OOB command.

  • -
-
-
-

Notes

-

GMCP messages will be outgoing on the following -form (the non-JSON cmdname at the start is what -IRE games use, supposedly, and what clients appear -to have adopted). A cmdname without Package will end -up in the Core package, while Core package names will -be stripped on the Evennia side.

-
[cmd.name, [], {}]          -> Cmd.Name
-[cmd.name, [arg], {}]       -> Cmd.Name arg
-[cmd.name, [args],{}]       -> Cmd.Name [args]
-[cmd.name, [], {kwargs}]    -> Cmd.Name {kwargs}
-[cmdname, [args, {kwargs}] -> Core.Cmdname [[args],{kwargs}]
-
-
-

Notes

-

There are also a few default mappings between evennia outputcmds and -GMCP:

-
client_options -> Core.Supports.Get
-get_inputfuncs -> Core.Commands.Get
-get_value      -> Char.Value.Get
-repeat         -> Char.Repeat.Update
-monitor        -> Char.Monitor.Update
-
-
-
- -
-
-decode_msdp(data)[source]
-

Decodes incoming MSDP data.

-
-
Parameters
-

data (str or list) – MSDP data.

-
-
-

Notes

-

Clients should always send MSDP data on -one of the following forms:

-
cmdname ''          -> [cmdname, [], {}]
-cmdname val         -> [cmdname, [val], {}]
-cmdname array       -> [cmdname, [array], {}]
-cmdname table       -> [cmdname, [], {table}]
-cmdname array cmdname table -> [cmdname, [array], {table}]
-
-
-

Observe that all MSDP_VARS are used to identify cmdnames, -so if there are multiple arrays with the same cmdname -given, they will be merged into one argument array, same -for tables. Different MSDP_VARS (outside tables) will be -identified as separate cmdnames.

-
- -
-
-decode_gmcp(data)[source]
-

Decodes incoming GMCP data on the form ‘varname <structure>’.

-
-
Parameters
-

data (str or list) – GMCP data.

-
-
-

Notes

-

Clients send data on the form “Module.Submodule.Cmdname <structure>”. -We assume the structure is valid JSON.

-

The following is parsed into Evennia’s formal structure:

-
Core.Name                         -> [name, [], {}]
-Core.Name string                  -> [name, [string], {}]
-Core.Name [arg, arg,...]          -> [name, [args], {}]
-Core.Name {key:arg, key:arg, ...} -> [name, [], {kwargs}]
-Core.Name [[args], {kwargs}]      -> [name, [args], {kwargs}]
-
-
-
- -
-
-data_out(cmdname, *args, **kwargs)[source]
-

Return a MSDP- or GMCP-valid subnegotiation across the protocol.

-
-
Parameters
-
    -
  • cmdname (str) – OOB-command name.

  • -
  • args (any) – Arguments to OOB command.

  • -
  • kwargs (any) – Arguments to OOB command.

  • -
-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.telnet_ssl.html b/docs/0.9.5/api/evennia.server.portal.telnet_ssl.html deleted file mode 100644 index 3ab7a3db83..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.telnet_ssl.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - evennia.server.portal.telnet_ssl — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.telnet_ssl

-

This allows for running the telnet communication over an encrypted SSL tunnel. To use it, requires a -client supporting Telnet SSL.

-

The protocol will try to automatically create the private key and certificate on the server side -when starting and will warn if this was not possible. These will appear as files ssl.key and -ssl.cert in mygame/server/.

-
-
-class evennia.server.portal.telnet_ssl.SSLProtocol(*args, **kwargs)[source]
-

Bases: evennia.server.portal.telnet.TelnetProtocol

-

Communication is the same as telnet, except data transfer -is done with encryption set up by the portal at start time.

-
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
- -
-
-evennia.server.portal.telnet_ssl.verify_or_create_SSL_key_and_cert(keyfile, certfile)[source]
-

Verify or create new key/certificate files.

-
-
Parameters
-
    -
  • keyfile (str) – Path to ssl.key file.

  • -
  • certfile (str) – Parth to ssl.cert file.

  • -
-
-
-

Notes

-

If files don’t already exist, they are created.

-
- -
-
-evennia.server.portal.telnet_ssl.getSSLContext()[source]
-

This is called by the portal when creating the SSL context -server-side.

-
-
Returns
-

ssl_context (tuple)

-
-
A key and certificate that is either

existing previously or created on the fly.

-
-
-

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.tests.html b/docs/0.9.5/api/evennia.server.portal.tests.html deleted file mode 100644 index b9d5668cad..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.tests.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - evennia.server.portal.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.tests

-
-
-class evennia.server.portal.tests.TestAMPServer(methodName='runTest')[source]
-

Bases: twisted.trial._asynctest.TestCase

-

Test AMP communication

-
-
-setUp()[source]
-

Hook method for setting up the test fixture before exercising it.

-
- -
-
-test_amp_out()[source]
-
- -
-
-test_amp_in()[source]
-
- -
-
-test_large_msg()[source]
-

Send message larger than AMP_MAXLEN - should be split into several

-
- -
- -
-
-class evennia.server.portal.tests.TestIRC(methodName='runTest')[source]
-

Bases: django.test.testcases.TestCase

-
-
-test_plain_ansi()[source]
-

Test that printable characters do not get mangled.

-
- -
-
-test_bold()[source]
-
- -
-
-test_italic()[source]
-
- -
-
-test_colors()[source]
-
- -
-
-test_identity()[source]
-

Test that the composition of the function and -its inverse gives the correct string.

-
- -
- -
-
-class evennia.server.portal.tests.TestTelnet(methodName='runTest')[source]
-

Bases: twisted.trial._asynctest.TestCase

-
-
-setUp()[source]
-

Hook method for setting up the test fixture before exercising it.

-
- -
-
-test_mudlet_ttype()[source]
-
- -
- -
-
-class evennia.server.portal.tests.TestWebSocket(methodName='runTest')[source]
-

Bases: evennia.utils.test_resources.EvenniaTest

-
-
-setUp()[source]
-

Sets up testing environment

-
- -
-
-tearDown()[source]
-

Hook method for deconstructing the test fixture after testing it.

-
- -
-
-test_data_in()[source]
-
- -
-
-test_data_out()[source]
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.ttype.html b/docs/0.9.5/api/evennia.server.portal.ttype.html deleted file mode 100644 index df044980d0..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.ttype.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - evennia.server.portal.ttype — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.ttype

-

TTYPE (MTTS) - Mud Terminal Type Standard

-

This module implements the TTYPE telnet protocol as per -http://tintin.sourceforge.net/mtts/. It allows the server to ask the -client about its capabilities. If the client also supports TTYPE, it -will return with information such as its name, if it supports colour -etc. If the client does not support TTYPE, this will be ignored.

-

All data will be stored on the protocol’s protocol_flags dictionary, -under the ‘TTYPE’ key.

-
-
-class evennia.server.portal.ttype.Ttype(protocol)[source]
-

Bases: object

-

Handles ttype negotiations. Called and initiated by the -telnet protocol.

-
-
-__init__(protocol)[source]
-

Initialize ttype by storing protocol on ourselves and calling -the client to see if it supporst ttype.

-
-
Parameters
-

protocol (Protocol) – The protocol instance.

-
-
-

Notes

-

The self.ttype_step indicates how far in the data -retrieval we’ve gotten.

-
- -
-
-wont_ttype(option)[source]
-

Callback if ttype is not supported by client.

-
-
Parameters
-

option (Option) – Not used.

-
-
-
- -
-
-will_ttype(option)[source]
-

Handles negotiation of the ttype protocol once the client has -confirmed that it will respond with the ttype protocol.

-
-
Parameters
-

option (Option) – Not used.

-
-
-

Notes

-

The negotiation proceeds in several steps, each returning a -certain piece of information about the client. All data is -stored on protocol.protocol_flags under the TTYPE key.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.webclient.html b/docs/0.9.5/api/evennia.server.portal.webclient.html deleted file mode 100644 index e721e92436..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.webclient.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - - evennia.server.portal.webclient — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.webclient

-

Webclient based on websockets.

-

This implements a webclient with WebSockets (http://en.wikipedia.org/wiki/WebSocket) -by use of the autobahn-python package’s implementation (https://github.com/crossbario/autobahn-python). -It is used together with evennia/web/media/javascript/evennia_websocket_webclient.js.

-

All data coming into the webclient is in the form of valid JSON on the form

-

[“inputfunc_name”, [args], {kwarg}]

-

which represents an “inputfunc” to be called on the Evennia side with args, **kwargs. -The most common inputfunc is “text”, which takes just the text input -from the command line and interprets it as an Evennia Command: **[“text”, [“look”], {}]*

-
-
-class evennia.server.portal.webclient.WebSocketClient(*args, **kwargs)[source]
-

Bases: autobahn.twisted.websocket.WebSocketServerProtocol, evennia.server.session.Session

-

Implements the server-side of the Websocket connection.

-
-
-nonce = None
-
- -
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-get_client_session()[source]
-

Get the Client browser session (used for auto-login based on browser session)

-
-
Returns
-

csession (ClientSession)

-
-
This is a django-specific internal representation

of the browser session.

-
-
-

-
-
-
- -
-
-onOpen()[source]
-

This is called when the WebSocket connection is fully established.

-
- -
-
-disconnect(reason=None)[source]
-

Generic hook for the engine to call in order to -disconnect this protocol.

-
-
Parameters
-

reason (str or None) – Motivation for the disconnection.

-
-
-
- -
-
-onClose(wasClean, code=None, reason=None)[source]
-

This is executed when the connection is lost for whatever -reason. it can also be called directly, from the disconnect -method.

-
-
Parameters
-
    -
  • wasClean (bool) – **True** if the WebSocket was closed cleanly.

  • -
  • code (int or None) – Close status as sent by the WebSocket peer.

  • -
  • reason (str or None) – Close reason as sent by the WebSocket peer.

  • -
-
-
-
- -
-
-onMessage(payload, isBinary)[source]
-

Callback fired when a complete WebSocket message was received.

-
-
Parameters
-
    -
  • payload (bytes) – The WebSocket message received.

  • -
  • isBinary (bool) – Flag indicating whether payload is binary or -UTF-8 encoded text.

  • -
-
-
-
- -
-
-sendLine(line)[source]
-

Send data to client.

-
-
Parameters
-

line (str) – Text to send.

-
-
-
- -
-
-at_login()[source]
-
- -
-
-data_in(**kwargs)[source]
-

Data User > Evennia.

-
-
Parameters
-
    -
  • text (str) – Incoming text.

  • -
  • kwargs (any) – Options from protocol.

  • -
-
-
-

Notes

-

At initilization, the client will send the special -‘csessid’ command to identify its browser session hash -with the Evennia side.

-

The websocket client will also pass ‘websocket_close’ command -to report that the client has been closed and that the -session should be disconnected.

-

Both those commands are parsed and extracted already at -this point.

-
- -
-
-send_text(*args, **kwargs)[source]
-

Send text data. This will pre-process the text for -color-replacement, conversion to html etc.

-
-
Parameters
-

text (str) – Text to send.

-
-
Keyword Arguments
-

options (dict) – Options-dict with the following keys understood: -- raw (bool): No parsing at all (leave ansi-to-html markers unparsed). -- nocolor (bool): Clean out all color. -- screenreader (bool): Use Screenreader mode. -- send_prompt (bool): Send a prompt with parsed html

-
-
-
- -
-
-send_prompt(*args, **kwargs)[source]
-
- -
-
-send_default(cmdname, *args, **kwargs)[source]
-

Data Evennia -> User.

-
-
Parameters
-
    -
  • cmdname (str) – The first argument will always be the oob cmd name.

  • -
  • *args (any) – Remaining args will be arguments for cmd.

  • -
-
-
Keyword Arguments
-

options (dict) – These are ignored for oob commands. Use command -arguments (which can hold dicts) to send instructions to the -client instead.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.portal.webclient_ajax.html b/docs/0.9.5/api/evennia.server.portal.webclient_ajax.html deleted file mode 100644 index afb06bbc5a..0000000000 --- a/docs/0.9.5/api/evennia.server.portal.webclient_ajax.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - - - - - - evennia.server.portal.webclient_ajax — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.portal.webclient_ajax

-

AJAX/COMET fallback webclient

-

The AJAX/COMET web client consists of two components running on -twisted and django. They are both a part of the Evennia website url -tree (so the testing website might be located on -http://localhost:4001/, whereas the webclient can be found on -http://localhost:4001/webclient.)

-
-
/webclient - this url is handled through django’s template

system and serves the html page for the client -itself along with its javascript chat program.

-
-
/webclientdata - this url is called by the ajax chat using

POST requests (long-polling when necessary) -The WebClient resource in this module will -handle these requests and act as a gateway -to sessions connected over the webclient.

-
-
-
-
-class evennia.server.portal.webclient_ajax.LazyEncoder(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)[source]
-

Bases: json.encoder.JSONEncoder

-
-
-default(obj)[source]
-

Implement this method in a subclass such that it returns -a serializable object for **o**, or calls the base implementation -(to raise a **TypeError**).

-

For example, to support arbitrary iterators, you could -implement default like this:

-
def default(self, o):
-    try:
-        iterable = iter(o)
-    except TypeError:
-        pass
-    else:
-        return list(iterable)
-    # Let the base class default method raise the TypeError
-    return JSONEncoder.default(self, o)
-
-
-
- -
- -
-
-evennia.server.portal.webclient_ajax.jsonify(obj)[source]
-
- -
-
-class evennia.server.portal.webclient_ajax.AjaxWebClient[source]
-

Bases: twisted.web.resource.Resource

-

An ajax/comet long-polling transport

-
-
-isLeaf = True
-
- -
-
-allowedMethods = ('POST',)
-
- -
-
-__init__()[source]
-

Initialize.

-
- -
-
-get_client_sessid(request)[source]
-

Helper to get the client session id out of the request.

-
-
Parameters
-

request (Request) – Incoming request object.

-
-
Returns
-

csessid (int) – The client-session id.

-
-
-
- -
-
-at_login()[source]
-

Called when this session gets authenticated by the server.

-
- -
-
-lineSend(csessid, data)[source]
-

This adds the data to the buffer and/or sends it to the client -as soon as possible.

-
-
Parameters
-
    -
  • csessid (int) – Session id.

  • -
  • data (list) – A send structure [cmdname, [args], {kwargs}].

  • -
-
-
-
- -
-
-client_disconnect(csessid)[source]
-

Disconnect session with given csessid.

-
-
Parameters
-

csessid (int) – Session id.

-
-
-
- -
-
-mode_init(request)[source]
-

This is called by render_POST when the client requests an init -mode operation (at startup)

-
-
Parameters
-

request (Request) – Incoming request.

-
-
-
- -
-
-mode_keepalive(request)[source]
-

This is called by render_POST when the -client is replying to the keepalive.

-
- -
-
-mode_input(request)[source]
-

This is called by render_POST when the client -is sending data to the server.

-
-
Parameters
-

request (Request) – Incoming request.

-
-
-
- -
-
-mode_receive(request)[source]
-

This is called by render_POST when the client is telling us -that it is ready to receive data as soon as it is available. -This is the basis of a long-polling (comet) mechanism: the -server will wait to reply until data is available.

-
-
Parameters
-

request (Request) – Incoming request.

-
-
-
- -
-
-mode_close(request)[source]
-

This is called by render_POST when the client is signalling -that it is about to be closed.

-
-
Parameters
-

request (Request) – Incoming request.

-
-
-
- -
-
-render_POST(request)[source]
-

This function is what Twisted calls with POST requests coming -in from the ajax client. The requests should be tagged with -different modes depending on what needs to be done, such as -initializing or sending/receving data through the request. It -uses a long-polling mechanism to avoid sending data unless -there is actual data available.

-
-
Parameters
-

request (Request) – Incoming request.

-
-
-
- -
- -
-
-class evennia.server.portal.webclient_ajax.AjaxWebClientSession(*args, **kwargs)[source]
-

Bases: evennia.server.session.Session

-

This represents a session running in an AjaxWebclient.

-
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-get_client_session()[source]
-

Get the Client browser session (used for auto-login based on browser session)

-
-
Returns
-

csession (ClientSession)

-
-
This is a django-specific internal representation

of the browser session.

-
-
-

-
-
-
- -
-
-disconnect(reason='Server disconnected.')[source]
-

Disconnect from server.

-
-
Parameters
-

reason (str) – Motivation for the disconnect.

-
-
-
- -
-
-at_login()[source]
-
- -
-
-data_in(**kwargs)[source]
-

Data User -> Evennia

-
-
Keyword Arguments
-

kwargs (any) – Incoming data.

-
-
-
- -
-
-data_out(**kwargs)[source]
-

Data Evennia -> User

-
-
Keyword Arguments
-

kwargs (any) – Options to the protocol

-
-
-
- -
-
-send_text(*args, **kwargs)[source]
-

Send text data. This will pre-process the text for -color-replacement, conversion to html etc.

-
-
Parameters
-

text (str) – Text to send.

-
-
Keyword Arguments
-

options (dict) – Options-dict with the following keys understood: -- raw (bool): No parsing at all (leave ansi-to-html markers unparsed). -- nocolor (bool): Remove all color. -- screenreader (bool): Use Screenreader mode. -- send_prompt (bool): Send a prompt with parsed html

-
-
-
- -
-
-send_prompt(*args, **kwargs)[source]
-
- -
-
-send_default(cmdname, *args, **kwargs)[source]
-

Data Evennia -> User.

-
-
Parameters
-
    -
  • cmdname (str) – The first argument will always be the oob cmd name.

  • -
  • *args (any) – Remaining args will be arguments for cmd.

  • -
-
-
Keyword Arguments
-

options (dict) – These are ignored for oob commands. Use command -arguments (which can hold dicts) to send instructions to the -client instead.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.profiling.dummyrunner.html b/docs/0.9.5/api/evennia.server.profiling.dummyrunner.html deleted file mode 100644 index 57a8bc166e..0000000000 --- a/docs/0.9.5/api/evennia.server.profiling.dummyrunner.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - evennia.server.profiling.dummyrunner — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.profiling.dummyrunner

-

Dummy client runner

-

This module implements a stand-alone launcher for stress-testing -an Evennia game. It will launch any number of fake clients. These -clients will log into the server and start doing random operations. -Customizing and weighing these operations differently depends on -which type of game is tested. The module contains a testing module -for plain Evennia.

-

Please note that you shouldn’t run this on a production server! -Launch the program without any arguments or options to see a -full step-by-step setup help.

-

Basically (for testing default Evennia):

-
-
    -
  • Use an empty/testing database.

  • -
  • set PERMISSION_ACCOUNT_DEFAULT = “Builder”

  • -
  • start server, eventually with profiling active

  • -
  • launch this client runner

  • -
-
-

If you want to customize the runner’s client actions -(because you changed the cmdset or needs to better -match your use cases or add more actions), you can -change which actions by adding a path to

-
-

DUMMYRUNNER_ACTIONS_MODULE = <path.to.your.module>

-
-

in your settings. See utils.dummyrunner_actions.py -for instructions on how to define this module.

-
-
-evennia.server.profiling.dummyrunner.idcounter()[source]
-

Makes unique ids.

-
-
Returns
-

count (int) – A globally unique counter.

-
-
-
- -
-
-evennia.server.profiling.dummyrunner.gidcounter()[source]
-

Makes globally unique ids.

-
-
Returns
-

count (int); A globally unique counter.

-
-
-
- -
-
-evennia.server.profiling.dummyrunner.makeiter(obj)[source]
-

Makes everything iterable.

-
-
Parameters
-

obj (any) – Object to turn iterable.

-
-
Returns
-

iterable (iterable) – An iterable object.

-
-
-
- -
-
-class evennia.server.profiling.dummyrunner.DummyClient[source]
-

Bases: twisted.conch.telnet.StatefulTelnetProtocol

-

Handles connection to a running Evennia server, -mimicking a real account by sending commands on -a timer.

-
-
-connectionMade()[source]
-

Called when connection is first established.

-
- -
-
-dataReceived(data)[source]
-

Called when data comes in over the protocol. We wait to start -stepping until the server actually responds

-
-
Parameters
-

data (str) – Incoming data.

-
-
-
- -
-
-connectionLost(reason)[source]
-

Called when loosing the connection.

-
-
Parameters
-

reason (str) – Reason for loosing connection.

-
-
-
- -
-
-error(err)[source]
-

Error callback.

-
-
Parameters
-

err (Failure) – Error instance.

-
-
-
- -
-
-counter()[source]
-

Produces a unique id, also between clients.

-
-
Returns
-

counter (int) – A unique counter.

-
-
-
- -
-
-logout()[source]
-

Causes the client to log out of the server. Triggered by ctrl-c signal.

-
- -
-
-step()[source]
-

Perform a step. This is called repeatedly by the runner and -causes the client to issue commands to the server. This holds -all “intelligence” of the dummy client.

-
- -
- -
-
-class evennia.server.profiling.dummyrunner.DummyFactory(actions)[source]
-

Bases: twisted.internet.protocol.ClientFactory

-
-
-protocol
-

alias of DummyClient

-
- -
-
-__init__(actions)[source]
-

Setup the factory base (shared by all clients)

-
- -
- -
-
-evennia.server.profiling.dummyrunner.start_all_dummy_clients(nclients)[source]
-

Initialize all clients, connect them and start to step them

-
-
Parameters
-

nclients (int) – Number of dummy clients to connect.

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.profiling.dummyrunner_settings.html b/docs/0.9.5/api/evennia.server.profiling.dummyrunner_settings.html deleted file mode 100644 index 0eb97c3a25..0000000000 --- a/docs/0.9.5/api/evennia.server.profiling.dummyrunner_settings.html +++ /dev/null @@ -1,245 +0,0 @@ - - - - - - - - - evennia.server.profiling.dummyrunner_settings — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.profiling.dummyrunner_settings

-

Settings and actions for the dummyrunner

-

This module defines dummyrunner settings and sets up -the actions available to dummy accounts.

-

The settings are global variables:

-

TIMESTEP - time in seconds between each ‘tick’ -CHANCE_OF_ACTION - chance 0-1 of action happening -CHANCE_OF_LOGIN - chance 0-1 of login happening -TELNET_PORT - port to use, defaults to settings.TELNET_PORT -ACTIONS - see below

-

ACTIONS is a tuple

-

(login_func, logout_func, (0.3, func1), (0.1, func2) … )

-

where the first entry is the function to call on first connect, with a -chance of occurring given by CHANCE_OF_LOGIN. This function is usually -responsible for logging in the account. The second entry is always -called when the dummyrunner disconnects from the server and should -thus issue a logout command. The other entries are tuples (chance, -func). They are picked randomly, their commonality based on the -cumulative chance given (the chance is normalized between all options -so if will still work also if the given chances don’t add up to 1). -Since each function can return a list of game-command strings, each -function may result in multiple operations.

-

An action-function is called with a “client” argument which is a -reference to the dummy client currently performing the action. It -returns a string or a list of command strings to execute. Use the -client object for optionally saving data between actions.

-

The client object has the following relevant properties and methods:

-
    -
  • -
    key - an optional client key. This is only used for dummyrunner output.

    Default is “Dummy-<cid>”

    -
    -
    -
  • -
  • cid - client id

  • -
  • gid - globally unique id, hashed with time stamp

  • -
  • istep - the current step

  • -
  • exits - an empty list. Can be used to store exit names

  • -
  • objs - an empty list. Can be used to store object names

  • -
  • -
    counter() - returns a unique increasing id, hashed with time stamp

    to make it unique also between dummyrunner instances.

    -
    -
    -
  • -
-

The return should either be a single command string or a tuple of -command strings. This list of commands will always be executed every -TIMESTEP with a chance given by CHANCE_OF_ACTION by in the order given -(no randomness) and allows for setting up a more complex chain of -commands (such as creating an account and logging in).

-

-
-
-evennia.server.profiling.dummyrunner_settings.c_login(client)[source]
-

logins to the game

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_login_nodig(client)[source]
-

logins, don’t dig its own room

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_logout(client)[source]
-

logouts of the game

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_looks(client)[source]
-

looks at various objects

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_examines(client)[source]
-

examines various objects

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_idles(client)[source]
-

idles

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_help(client)[source]
-

reads help files

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_digs(client)[source]
-

digs a new room, storing exit names on client

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_creates_obj(client)[source]
-

creates normal objects, storing their name on client

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_creates_button(client)[source]
-

creates example button, storing name on client

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_socialize(client)[source]
-

socializechats on channel

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_moves(client)[source]
-

moves to a previously created room, using the stored exits

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_moves_n(client)[source]
-

move through north exit if available

-
- -
-
-evennia.server.profiling.dummyrunner_settings.c_moves_s(client)[source]
-

move through south exit if available

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.profiling.html b/docs/0.9.5/api/evennia.server.profiling.html deleted file mode 100644 index 504f4fd9c8..0000000000 --- a/docs/0.9.5/api/evennia.server.profiling.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - evennia.server.profiling — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.profiling.memplot.html b/docs/0.9.5/api/evennia.server.profiling.memplot.html deleted file mode 100644 index 122ddb2734..0000000000 --- a/docs/0.9.5/api/evennia.server.profiling.memplot.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - evennia.server.profiling.memplot — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.profiling.memplot

-

Script that saves memory and idmapper data over time.

-

Data will be saved to game/logs/memoryusage.log. Note that -the script will append to this file if it already exists.

-

Call this module directly to plot the log (requires matplotlib and numpy).

-
-
-class evennia.server.profiling.memplot.Memplot(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

Describes a memory plotting action.

-
-
-at_script_creation()[source]
-

Called at script creation

-
- -
-
-at_repeat()[source]
-

Regularly save memory statistics.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.server.profiling.memplot.Memplot'
-
- -
-
-typename = 'Memplot'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.profiling.settings_mixin.html b/docs/0.9.5/api/evennia.server.profiling.settings_mixin.html deleted file mode 100644 index bad5bb4316..0000000000 --- a/docs/0.9.5/api/evennia.server.profiling.settings_mixin.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - evennia.server.profiling.settings_mixin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.profiling.settings_mixin

-

Dummyrunner mixin. Add this at the end of the settings file before -running dummyrunner, like this:

-
-

from evennia.server.profiling.settings_mixin import *

-
-

Note that these mixin-settings are not suitable for production -servers!

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.profiling.test_queries.html b/docs/0.9.5/api/evennia.server.profiling.test_queries.html deleted file mode 100644 index 44ca29d9a1..0000000000 --- a/docs/0.9.5/api/evennia.server.profiling.test_queries.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - evennia.server.profiling.test_queries — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.profiling.test_queries

-

This is a little routine for viewing the sql queries that are executed by a given -query as well as count them for optimization testing.

-
-
-evennia.server.profiling.test_queries.count_queries(exec_string, setup_string)[source]
-

Display queries done by exec_string. Use setup_string -to setup the environment to test.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.profiling.tests.html b/docs/0.9.5/api/evennia.server.profiling.tests.html deleted file mode 100644 index 40e925b480..0000000000 --- a/docs/0.9.5/api/evennia.server.profiling.tests.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - evennia.server.profiling.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.profiling.tests

-
-
-class evennia.server.profiling.tests.TestDummyrunnerSettings(methodName='runTest')[source]
-

Bases: django.test.testcases.TestCase

-
-
-setUp()[source]
-

Hook method for setting up the test fixture before exercising it.

-
- -
-
-clear_client_lists()[source]
-
- -
-
-test_c_login()[source]
-
- -
-
-test_c_login_no_dig()[source]
-
- -
-
-test_c_logout()[source]
-
- -
-
-perception_method_tests(func, verb, alone_suffix='')[source]
-
- -
-
-test_c_looks()[source]
-
- -
-
-test_c_examines()[source]
-
- -
-
-test_idles()[source]
-
- -
-
-test_c_help()[source]
-
- -
-
-test_c_digs()[source]
-
- -
-
-test_c_creates_obj()[source]
-
- -
-
-test_c_creates_button()[source]
-
- -
-
-test_c_socialize()[source]
-
- -
-
-test_c_moves()[source]
-
- -
-
-test_c_move_n()[source]
-
- -
-
-test_c_move_s()[source]
-
- -
- -
-
-class evennia.server.profiling.tests.TestMemPlot(methodName='runTest')[source]
-

Bases: django.test.testcases.TestCase

-
-
-test_memplot(mock_time, mocked_open, mocked_os, mocked_idmapper)[source]
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.profiling.timetrace.html b/docs/0.9.5/api/evennia.server.profiling.timetrace.html deleted file mode 100644 index f6f1fc4324..0000000000 --- a/docs/0.9.5/api/evennia.server.profiling.timetrace.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - evennia.server.profiling.timetrace — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.profiling.timetrace

-

Trace a message through the messaging system

-
-
-evennia.server.profiling.timetrace.timetrace(message, idstring, tracemessage='TEST_MESSAGE', final=False)[source]
-

Trace a message with time stamps.

-
-
Parameters
-
    -
  • message (str) – The actual message coming through

  • -
  • idstring (str) – An identifier string specifying where this trace is happening.

  • -
  • tracemessage (str) – The start of the message to tag. -This message will get attached time stamp.

  • -
  • final (bool) – This is the final leg in the path - include total time in message

  • -
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.server.html b/docs/0.9.5/api/evennia.server.server.html deleted file mode 100644 index a2bac9a82b..0000000000 --- a/docs/0.9.5/api/evennia.server.server.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - - - evennia.server.server — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.server

-

This module implements the main Evennia server process, the core of -the game engine.

-

This module should be started with the ‘twistd’ executable since it -sets up all the networking features. (this is done automatically -by evennia/server/server_runner.py).

-
-
-class evennia.server.server.Evennia(application)[source]
-

Bases: object

-

The main Evennia server handler. This object sets up the database and -tracks and interlinks all the twisted network services that make up -evennia.

-
-
-__init__(application)[source]
-

Setup the server.

-

application - an instantiated Twisted application

-
- -
-
-sqlite3_prep()[source]
-

Optimize some SQLite stuff at startup since we -can’t save it to the database.

-
- -
-
-update_defaults()[source]
-

We make sure to store the most important object defaults here, so -we can catch if they change and update them on-objects automatically. -This allows for changing default cmdset locations and default -typeclasses in the settings file and have them auto-update all -already existing objects.

-
- -
-
-run_initial_setup()[source]
-

This is triggered by the amp protocol when the connection -to the portal has been established. -This attempts to run the initial_setup script of the server. -It returns if this is not the first time the server starts. -Once finished the last_initial_setup_step is set to -1.

-
- -
-
-run_init_hooks(mode)[source]
-

Called by the amp client once receiving sync back from Portal

-
-
Parameters
-

mode (str) – One of shutdown, reload or reset

-
-
-
- -
-
-shutdown(mode='reload', _reactor_stopping=False)[source]
-

Shuts down the server from inside it.

-
-
Keyword Arguments
-
    -
  • mode (str) – Sets the server restart mode:

  • -
  • 'reload' (-) – server restarts, no “persistent” scripts -are stopped, at_reload hooks called.

  • -
  • 'reset' - server restarts, non-persistent scripts stopped, (-) – at_shutdown hooks called but sessions will not -be disconnected.

  • -
  • - like reset, but server will not auto-restart. (-'shutdown') –

  • -
  • _reactor_stopping – This is set if server is stopped by a kill -command OR this method was already called -once - in both cases the reactor is dead/stopping already.

  • -
-
-
-
- -
-
-get_info_dict()[source]
-

Return the server info, for display.

-
- -
-
-at_server_start()[source]
-

This is called every time the server starts up, regardless of -how it was shut down.

-
- -
-
-at_server_stop()[source]
-

This is called just before a server is shut down, regardless -of it is fore a reload, reset or shutdown.

-
- -
-
-at_server_reload_start()[source]
-

This is called only when server starts back up after a reload.

-
- -
-
-at_post_portal_sync(mode)[source]
-

This is called just after the portal has finished syncing back data to the server -after reconnecting.

-
-
Parameters
-

mode (str) – One of reload, reset or shutdown.

-
-
-
- -
-
-at_server_reload_stop()[source]
-

This is called only time the server stops before a reload.

-
- -
-
-at_server_cold_start()[source]
-

This is called only when the server starts “cold”, i.e. after a -shutdown or a reset.

-
- -
-
-at_server_cold_stop()[source]
-

This is called only when the server goes down due to a shutdown or reset.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.serversession.html b/docs/0.9.5/api/evennia.server.serversession.html deleted file mode 100644 index 88bc8d6e1f..0000000000 --- a/docs/0.9.5/api/evennia.server.serversession.html +++ /dev/null @@ -1,535 +0,0 @@ - - - - - - - - - evennia.server.serversession — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.serversession

-

This defines a the Server’s generic session object. This object represents -a connection to the outside world but don’t know any details about how the -connection actually happens (so it’s the same for telnet, web, ssh etc).

-

It is stored on the Server side (as opposed to protocol-specific sessions which -are stored on the Portal side)

-
-
-class evennia.server.serversession.NDbHolder(obj, name, manager_name='attributes')[source]
-

Bases: object

-

Holder for allowing property access of attributes

-
-
-__init__(obj, name, manager_name='attributes')[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-get_all()[source]
-
- -
-
-property all
-
- -
- -
-
-class evennia.server.serversession.NAttributeHandler(obj)[source]
-

Bases: object

-

NAttributeHandler version without recache protection. -This stand-alone handler manages non-database saving. -It is similar to AttributeHandler and is used -by the .ndb handler in the same way as .db does -for the AttributeHandler.

-
-
-__init__(obj)[source]
-

Initialized on the object

-
- -
-
-has(key)[source]
-

Check if object has this attribute or not.

-
-
Parameters
-

key (str) – The Nattribute key to check.

-
-
Returns
-

has_nattribute (bool) – If Nattribute is set or not.

-
-
-
- -
-
-get(key, default=None)[source]
-

Get the named key value.

-
-
Parameters
-

key (str) – The Nattribute key to get.

-
-
Returns
-

the value of the Nattribute.

-
-
-
- -
-
-add(key, value)[source]
-

Add new key and value.

-
-
Parameters
-
    -
  • key (str) – The name of Nattribute to add.

  • -
  • value (any) – The value to store.

  • -
-
-
-
- -
-
-remove(key)[source]
-

Remove Nattribute from storage.

-
-
Parameters
-

key (str) – The name of the Nattribute to remove.

-
-
-
- -
-
-clear()[source]
-

Remove all NAttributes from handler.

-
- -
-
-all(return_tuples=False)[source]
-

List the contents of the handler.

-
-
Parameters
-

return_tuples (bool, optional) – Defines if the Nattributes -are returns as a list of keys or as a list of (key, value).

-
-
Returns
-

nattributes (list)

-
-
A list of keys [key, key, …] or a

list of tuples [(key, value), …] depending on the -setting of return_tuples.

-
-
-

-
-
-
- -
- -
-
-class evennia.server.serversession.ServerSession[source]
-

Bases: evennia.server.session.Session

-

This class represents an account’s session and is a template for -individual protocols to communicate with Evennia.

-

Each account gets a session assigned to them whenever they connect -to the game server. All communication between game and account goes -through their session.

-
-
-__init__()[source]
-

Initiate to avoid AttributeErrors down the line

-
- -
-
-property cmdset_storage
-
- -
-
-at_sync()[source]
-

This is called whenever a session has been resynced with the -portal. At this point all relevant attributes have already -been set and self.account been assigned (if applicable).

-

Since this is often called after a server restart we need to -set up the session as it was.

-
- -
-
-at_login(account)[source]
-

Hook called by sessionhandler when the session becomes authenticated.

-
-
Parameters
-

account (Account) – The account associated with the session.

-
-
-
- -
-
-at_disconnect(reason=None)[source]
-

Hook called by sessionhandler when disconnecting this session.

-
- -
-
-get_account()[source]
-

Get the account associated with this session

-
-
Returns
-

account (Account) – The associated Account.

-
-
-
- -
-
-get_puppet()[source]
-

Get the in-game character associated with this session.

-
-
Returns
-

puppet (Object) – The puppeted object, if any.

-
-
-
- -
-
-get_character()
-

Get the in-game character associated with this session.

-
-
Returns
-

puppet (Object) – The puppeted object, if any.

-
-
-
- -
-
-get_puppet_or_account()[source]
-

Get puppet or account.

-
-
Returns
-

controller (Object or Account)

-
-
The puppet if one exists,

otherwise return the account.

-
-
-

-
-
-
- -
-
-log(message, channel=True)[source]
-

Emits session info to the appropriate outputs and info channels.

-
-
Parameters
-
    -
  • message (str) – The message to log.

  • -
  • channel (bool, optional) – Log to the CHANNEL_CONNECTINFO channel -in addition to the server log.

  • -
-
-
-
- -
-
-get_client_size()[source]
-

Return eventual eventual width and height reported by the -client. Note that this currently only deals with a single -client window (windowID==0) as in a traditional telnet session.

-
- -
-
-update_session_counters(idle=False)[source]
-

Hit this when the user enters a command in order to update -idle timers and command counters.

-
- -
-
-update_flags(**kwargs)[source]
-

Update the protocol_flags and sync them with Portal.

-
-
Keyword Arguments
-

any – A key:value pair to set in the -protocol_flags dictionary.

-
-
-

Notes

-

Since protocols can vary, no checking is done -as to the existene of the flag or not. The input -data should have been validated before this call.

-
- -
-
-data_out(**kwargs)[source]
-

Sending data from Evennia->Client

-
-
Keyword Arguments
-
    -
  • text (str or tuple) –

  • -
  • any (str or tuple) – Send-commands identified -by their keys. Or “options”, carrying options -for the protocol(s).

  • -
-
-
-
- -
-
-data_in(**kwargs)[source]
-

Receiving data from the client, sending it off to -the respective inputfuncs.

-
-
Keyword Arguments
-

any – Incoming data from protocol on -the form {“commandname”: ((args), {kwargs}),…}

-
-
-

Notes

-

This method is here in order to give the user -a single place to catch and possibly process all incoming data from -the client. It should usually always end by sending -this data off to self.sessionhandler.call_inputfuncs(self, **kwargs).

-
- -
-
-msg(text=None, **kwargs)[source]
-

Wrapper to mimic msg() functionality of Objects and Accounts.

-
-
Parameters
-
    -
  • text (str) – String input.

  • -
  • kwargs (str or tuple) – Send-commands identified -by their keys. Or “options”, carrying options -for the protocol(s).

  • -
-
-
-
- -
-
-execute_cmd(raw_string, session=None, **kwargs)[source]
-

Do something as this object. This method is normally never -called directly, instead incoming command instructions are -sent to the appropriate inputfunc already at the sessionhandler -level. This method allows Python code to inject commands into -this stream, and will lead to the text inputfunc be called.

-
-
Parameters
-
    -
  • raw_string (string) – Raw command input

  • -
  • session (Session) – This is here to make API consistent with -Account/Object.execute_cmd. If given, data is passed to -that Session, otherwise use self.

  • -
-
-
Keyword Arguments
-
    -
  • keyword arguments will be added to the found command (Other) –

  • -
  • instace as variables before it executes. This is (object) –

  • -
  • by default Evennia but may be used to set flags and (unused) –

  • -
  • operating paramaters for commands at run-time. (change) –

  • -
-
-
-
- -
-
-at_cmdset_get(**kwargs)[source]
-

A dummy hook all objects with cmdsets need to have

-
- -
-
-nattributes[source]
-
- -
-
-attributes[source]
-
- -
-
-ndb_get()[source]
-

A non-persistent store (ndb: NonDataBase). Everything stored -to this is guaranteed to be cleared when a server is shutdown. -Syntax is same as for the _get_db_holder() method and -property, e.g. obj.ndb.attr = value etc.

-
- -
-
-ndb_set(value)[source]
-

Stop accidentally replacing the db object

-
-
Parameters
-

value (any) – A value to store in the ndb.

-
-
-
- -
-
-ndb_del()[source]
-

Stop accidental deletion.

-
- -
-
-property ndb
-

NonDataBase). Everything stored -to this is guaranteed to be cleared when a server is shutdown. -Syntax is same as for the _get_db_holder() method and -property, e.g. obj.ndb.attr = value etc.

-
-
Type
-

A non-persistent store (ndb

-
-
-
- -
-
-property db
-

NonDataBase). Everything stored -to this is guaranteed to be cleared when a server is shutdown. -Syntax is same as for the _get_db_holder() method and -property, e.g. obj.ndb.attr = value etc.

-
-
Type
-

A non-persistent store (ndb

-
-
-
- -
-
-access(*args, **kwargs)[source]
-

Dummy method to mimic the logged-in API.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.session.html b/docs/0.9.5/api/evennia.server.session.html deleted file mode 100644 index e18dd60ecf..0000000000 --- a/docs/0.9.5/api/evennia.server.session.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - - evennia.server.session — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.session

-

This module defines a generic session class. All connection instances -(both on Portal and Server side) should inherit from this class.

-
-
-class evennia.server.session.Session[source]
-

Bases: object

-

This class represents a player’s session and is a template for -both portal- and server-side sessions.

-

Each connection will see two session instances created:

-
-
    -
  1. A Portal session. This is customized for the respective connection -protocols that Evennia supports, like Telnet, SSH etc. The Portal -session must call init_session() as part of its initialization. The -respective hook methods should be connected to the methods unique -for the respective protocol so that there is a unified interface -to Evennia.

  2. -
  3. A Server session. This is the same for all connected accounts, -regardless of how they connect.

  4. -
-
-

The Portal and Server have their own respective sessionhandlers. These -are synced whenever new connections happen or the Server restarts etc, -which means much of the same information must be stored in both places -e.g. the portal can re-sync with the server when the server reboots.

-
-
-init_session(protocol_key, address, sessionhandler)[source]
-

Initialize the Session. This should be called by the protocol when -a new session is established.

-
-
Parameters
-
    -
  • protocol_key (str) – By default, one of ‘telnet’, ‘telnet/ssl’, ‘ssh’, -‘webclient/websocket’ or ‘webclient/ajax’.

  • -
  • address (str) – Client address.

  • -
  • sessionhandler (SessionHandler) – Reference to the -main sessionhandler instance.

  • -
-
-
-
- -
-
-get_sync_data()[source]
-

Get all data relevant to sync the session.

-
-
Parameters
-

syncdata (dict) – All syncdata values, based on -the keys given by self._attrs_to_sync.

-
-
-
- -
-
-load_sync_data(sessdata)[source]
-

Takes a session dictionary, as created by get_sync_data, and -loads it into the correct properties of the session.

-
-
Parameters
-

sessdata (dict) – Session data dictionary.

-
-
-
- -
-
-at_sync()[source]
-

Called after a session has been fully synced (including -secondary operations such as setting self.account based -on uid etc).

-
- -
-
-disconnect(reason=None)[source]
-

generic hook called from the outside to disconnect this session -should be connected to the protocols actual disconnect mechanism.

-
-
Parameters
-

reason (str) – Eventual text motivating the disconnect.

-
-
-
- -
-
-data_out(**kwargs)[source]
-

Generic hook for sending data out through the protocol. Server -protocols can use this right away. Portal sessions -should overload this to format/handle the outgoing data as needed.

-
-
Keyword Arguments
-

kwargs (any) – Other data to the protocol.

-
-
-
- -
-
-data_in(**kwargs)[source]
-

Hook for protocols to send incoming data to the engine.

-
-
Keyword Arguments
-

kwargs (any) – Other data from the protocol.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.sessionhandler.html b/docs/0.9.5/api/evennia.server.sessionhandler.html deleted file mode 100644 index b803173cbb..0000000000 --- a/docs/0.9.5/api/evennia.server.sessionhandler.html +++ /dev/null @@ -1,645 +0,0 @@ - - - - - - - - - evennia.server.sessionhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.sessionhandler

-

This module defines handlers for storing sessions when handles -sessions of users connecting to the server.

-

There are two similar but separate stores of sessions:

-
-
    -
  • -
    ServerSessionHandler - this stores generic game sessions

    for the game. These sessions has no knowledge about -how they are connected to the world.

    -
    -
    -
  • -
  • -
    PortalSessionHandler - this stores sessions created by

    twisted protocols. These are dumb connectors that -handle network communication but holds no game info.

    -
    -
    -
  • -
-
-
-
-class evennia.server.sessionhandler.DummySession[source]
-

Bases: object

-
-
-sessid = 0
-
- -
- -
-
-evennia.server.sessionhandler.delayed_import()[source]
-

Helper method for delayed import of all needed entities.

-
- -
-
-class evennia.server.sessionhandler.SessionHandler[source]
-

Bases: dict

-

This handler holds a stack of sessions.

-
-
-get(key, default=None)[source]
-

Clean out None-sessions automatically.

-
- -
-
-get_sessions(include_unloggedin=False)[source]
-

Returns the connected session objects.

-
-
Parameters
-

include_unloggedin (bool, optional) – Also list Sessions -that have not yet authenticated.

-
-
Returns
-

sessions (list) – A list of Session objects.

-
-
-
- -
-
-get_all_sync_data()[source]
-

Create a dictionary of sessdata dicts representing all -sessions in store.

-
-
Returns
-

syncdata (dict) – A dict of sync data.

-
-
-
- -
-
-clean_senddata(session, kwargs)[source]
-

Clean up data for sending across the AMP wire. Also apply INLINEFUNCS.

-
-
Parameters
-
    -
  • session (Session) – The relevant session instance.

  • -
  • kwargs (dict) –

    send-instruction, with the keyword itself being the name -of the instruction (like “text”). Suitable values for each -keyword are:

    -
    arg                ->  [[arg], {}]
    -[args]             ->  [[args], {}]
    -{kwargs}           ->  [[], {kwargs}]
    -[args, {kwargs}]   ->  [[arg], {kwargs}]
    -[[args], {kwargs}] ->  [[args], {kwargs}]
    -
    -
    -

  • -
-
-
Returns
-

kwargs (dict)

-
-
A cleaned dictionary of cmdname:[[args],{kwargs}] pairs,

where the keys, args and kwargs have all been converted to -send-safe entities (strings or numbers), and inlinefuncs have been -applied.

-
-
-

-
-
-
- -
- -
-
-class evennia.server.sessionhandler.ServerSessionHandler(*args, **kwargs)[source]
-

Bases: evennia.server.sessionhandler.SessionHandler

-

This object holds the stack of sessions active in the game at -any time.

-

A session register with the handler in two steps, first by -registering itself with the connect() method. This indicates an -non-authenticated session. Whenever the session is authenticated -the session together with the related account is sent to the login() -method.

-
-
-__init__(*args, **kwargs)[source]
-

Init the handler.

-
- -
-
-portal_connect(portalsessiondata)[source]
-

Called by Portal when a new session has connected. -Creates a new, unlogged-in game session.

-
-
Parameters
-

portalsessiondata (dict) – a dictionary of all property:value -keys defining the session and which is marked to be -synced.

-
-
-
- -
-
-portal_session_sync(portalsessiondata)[source]
-

Called by Portal when it wants to update a single session (e.g. -because of all negotiation protocols have finally replied)

-
-
Parameters
-

portalsessiondata (dict) – a dictionary of all property:value -keys defining the session and which is marked to be -synced.

-
-
-
- -
-
-portal_sessions_sync(portalsessionsdata)[source]
-

Syncing all session ids of the portal with the ones of the -server. This is instantiated by the portal when reconnecting.

-
-
Parameters
-

portalsessionsdata (dict) – A dictionary -{sessid: {property:value},…} defining each session and -the properties in it which should be synced.

-
-
-
- -
-
-portal_disconnect(session)[source]
-

Called from Portal when Portal session closed from the portal -side. There is no message to report in this case.

-
-
Parameters
-

session (Session) – The Session to disconnect

-
-
-
- -
-
-portal_disconnect_all()[source]
-

Called from Portal when Portal is closing down. All -Sessions should die. The Portal should not be informed.

-
- -
-
-start_bot_session(protocol_path, configdict)[source]
-

This method allows the server-side to force the Portal to -create a new bot session.

-
-
Parameters
-
    -
  • protocol_path (str) – The full python path to the bot’s -class.

  • -
  • configdict (dict) – This dict will be used to configure -the bot (this depends on the bot protocol).

  • -
-
-
-

Examples

-
-
start_bot_session(“evennia.server.portal.irc.IRCClient”,
-
{“uid”:1, “botname”:”evbot”, “channel”:”#evennia”,

“network:”irc.freenode.net”, “port”: 6667})

-
-
-
-
-

Notes

-

The new session will use the supplied account-bot uid to -initiate an already logged-in connection. The Portal will -treat this as a normal connection and henceforth so will -the Server.

-
- -
-
-portal_restart_server()[source]
-

Called by server when reloading. We tell the portal to start a new server instance.

-
- -
-
-portal_reset_server()[source]
-

Called by server when reloading. We tell the portal to start a new server instance.

-
- -
-
-portal_shutdown()[source]
-

Called by server when it’s time to shut down (the portal will shut us down and then shut -itself down)

-
- -
-
-login(session, account, force=False, testmode=False)[source]
-

Log in the previously unloggedin session and the account we by -now should know is connected to it. After this point we assume -the session to be logged in one way or another.

-
-
Parameters
-
    -
  • session (Session) – The Session to authenticate.

  • -
  • account (Account) – The Account identified as associated with this Session.

  • -
  • force (bool) – Login also if the session thinks it’s already logged in -(this can happen for auto-authenticating protocols)

  • -
  • testmode (bool, optional) – This is used by unittesting for -faking login without any AMP being actually active.

  • -
-
-
-
- -
-
-disconnect(session, reason='', sync_portal=True)[source]
-

Called from server side to remove session and inform portal -of this fact.

-
-
Parameters
-
    -
  • session (Session) – The Session to disconnect.

  • -
  • reason (str, optional) – A motivation for the disconnect.

  • -
  • sync_portal (bool, optional) – Sync the disconnect to -Portal side. This should be done unless this was -called by self.portal_disconnect().

  • -
-
-
-
- -
-
-all_sessions_portal_sync()[source]
-

This is called by the server when it reboots. It syncs all session data -to the portal. Returns a deferred!

-
- -
-
-session_portal_sync(session)[source]
-

This is called by the server when it wants to sync a single session -with the Portal for whatever reason. Returns a deferred!

-
- -
-
-session_portal_partial_sync(session_data)[source]
-

Call to make a partial update of the session, such as only a particular property.

-
-
Parameters
-

session_data (dict) – Store {sessid: {property:value}, …} defining one or -more sessions in detail.

-
-
-
- -
-
-disconnect_all_sessions(reason='You have been disconnected.')[source]
-

Cleanly disconnect all of the connected sessions.

-
-
Parameters
-

reason (str, optional) – The reason for the disconnection.

-
-
-
- -
-
-disconnect_duplicate_sessions(curr_session, reason='Logged in from elsewhere. Disconnecting.')[source]
-

Disconnects any existing sessions with the same user.

-
-
Parameters
-
    -
  • curr_session (Session) – Disconnect all Sessions matching this one.

  • -
  • reason (str, optional) – A motivation for disconnecting.

  • -
-
-
-
- -
-
-validate_sessions()[source]
-

Check all currently connected sessions (logged in and not) and -see if any are dead or idle.

-
- -
-
-account_count()[source]
-

Get the number of connected accounts (not sessions since a -account may have more than one session depending on settings). -Only logged-in accounts are counted here.

-
-
Returns
-

naccount (int) – Number of connected accounts

-
-
-
- -
-
-all_connected_accounts()[source]
-

Get a unique list of connected and logged-in Accounts.

-
-
Returns
-

accounts (list)

-
-
All conected Accounts (which may be fewer than the

amount of Sessions due to multi-playing).

-
-
-

-
-
-
- -
-
-session_from_sessid(sessid)[source]
-

Get session based on sessid, or None if not found

-
-
Parameters
-

sessid (int or list) – Session id(s).

-
-
Returns
-

sessions (Session or list)

-
-
Session(s) found. This

is a list if input was a list.

-
-
-

-
-
-
- -
-
-session_from_account(account, sessid)[source]
-

Given an account and a session id, return the actual session -object.

-
-
Parameters
-
    -
  • account (Account) – The Account to get the Session from.

  • -
  • sessid (int or list) – Session id(s).

  • -
-
-
Returns
-

sessions (Session or list) – Session(s) found.

-
-
-
- -
-
-sessions_from_account(account)[source]
-

Given an account, return all matching sessions.

-
-
Parameters
-

account (Account) – Account to get sessions from.

-
-
Returns
-

sessions (list) – All Sessions associated with this account.

-
-
-
- -
-
-sessions_from_puppet(puppet)[source]
-

Given a puppeted object, return all controlling sessions.

-
-
Parameters
-

puppet (Object) – Object puppeted

-
-
-
-
Returns.
-
sessions (Session or list): Can be more than one of Object is controlled by

more than one Session (MULTISESSION_MODE > 1).

-
-
-
-
-
- -
-
-sessions_from_character(puppet)
-

Given a puppeted object, return all controlling sessions.

-
-
Parameters
-

puppet (Object) – Object puppeted

-
-
-
-
Returns.
-
sessions (Session or list): Can be more than one of Object is controlled by

more than one Session (MULTISESSION_MODE > 1).

-
-
-
-
-
- -
-
-sessions_from_csessid(csessid)[source]
-

Given a client identification hash (for session types that offer them) -return all sessions with a matching hash.

-
-
Parameters
-

csessid (str) – The session hash.

-
-
Returns
-

sessions (list) – The sessions with matching .csessid, if any.

-
-
-
- -
-
-announce_all(message)[source]
-

Send message to all connected sessions

-
-
Parameters
-

message (str) – Message to send.

-
-
-
- -
-
-data_out(session, **kwargs)[source]
-

Sending data Server -> Portal

-
-
Parameters
-
    -
  • session (Session) – Session to relay to.

  • -
  • text (str, optional) – text data to return

  • -
-
-
-

Notes

-

The outdata will be scrubbed for sending across -the wire here.

-
- -
-
-get_inputfuncs()[source]
-

Get all registered inputfuncs (access function)

-
-
Returns
-

inputfuncs (dict) – A dict of {key:inputfunc,…}

-
-
-
- -
-
-data_in(session, **kwargs)[source]
-

We let the data take a “detour” to session.data_in -so the user can override and see it all in one place. -That method is responsible to in turn always call -this class’ sessionhandler.call_inputfunc with the -(possibly processed) data.

-
- -
-
-call_inputfuncs(session, **kwargs)[source]
-

Split incoming data into its inputfunc counterparts. -This should be called by the serversession.data_in -as sessionhandler.call_inputfunc(self, **kwargs).

-

We also intercept OOB communication here.

-
-
Parameters
-

sessions (Session) – Session.

-
-
Keyword Arguments
-

kwargs (any) – Incoming data from protocol on -the form {“commandname”: ((args), {kwargs}),…}

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.signals.html b/docs/0.9.5/api/evennia.server.signals.html deleted file mode 100644 index 9cc3fe4bd7..0000000000 --- a/docs/0.9.5/api/evennia.server.signals.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - evennia.server.signals — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.signals

-

This module brings Django Signals into Evennia. These are events that -can be subscribed to by importing a given Signal and using the -following code.

-
THIS_SIGNAL.connect(callback, sender_object**)
-
-
-

When other code calls THIS_SIGNAL.send(sender, **kwargs), the callback -will be triggered.

-

Callbacks must be in the following format:

-
def my_callback(sender, **kwargs):
-    ...
-
-
-

This is used on top of hooks to make certain features easier to -add to contribs without necessitating a full takeover of hooks -that may be in high demand.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.throttle.html b/docs/0.9.5/api/evennia.server.throttle.html deleted file mode 100644 index 95c2202b48..0000000000 --- a/docs/0.9.5/api/evennia.server.throttle.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - - - - evennia.server.throttle — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.throttle

-
-
-class evennia.server.throttle.Throttle(**kwargs)[source]
-

Bases: object

-

Keeps a running count of failed actions per IP address.

-

Available methods indicate whether or not the number of failures exceeds a -particular threshold.

-

This version of the throttle is usable by both the terminal server as well -as the web server, imposes limits on memory consumption by using deques -with length limits instead of open-ended lists, and removes sparse keys when -no recent failures have been recorded.

-
-
-error_msg = 'Too many failed attempts; you must wait a few minutes before trying again.'
-
- -
-
-__init__(**kwargs)[source]
-

Allows setting of throttle parameters.

-
-
Keyword Arguments
-
    -
  • limit (int) – Max number of failures before imposing limiter

  • -
  • timeout (int) – number of timeout seconds after -max number of tries has been reached.

  • -
  • cache_size (int) – Max number of attempts to record per IP within a -rolling window; this is NOT the same as the limit after which -the throttle is imposed!

  • -
-
-
-
- -
-
-get(ip=None)[source]
-

Convenience function that returns the storage table, or part of.

-
-
Parameters
-

ip (str, optional) – IP address of requestor

-
-
Returns
-

storage (dict)

-
-
When no IP is provided, returns a dict of all

current IPs being tracked and the timestamps of their recent -failures.

-
-
timestamps (deque): When an IP is provided, returns a deque of

timestamps of recent failures only for that IP.

-
-
-

-
-
-
- -
-
-update(ip, failmsg='Exceeded threshold.')[source]
-

Store the time of the latest failure.

-
-
Parameters
-
    -
  • ip (str) – IP address of requestor

  • -
  • failmsg (str, optional) – Message to display in logs upon activation -of throttle.

  • -
-
-
Returns
-

None

-
-
-
- -
-
-check(ip)[source]
-

This will check the session’s address against the -storage dictionary to check they haven’t spammed too many -fails recently.

-
-
Parameters
-

ip (str) – IP address of requestor

-
-
Returns
-

throttled (bool)

-
-
True if throttling is active,

False otherwise.

-
-
-

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.validators.html b/docs/0.9.5/api/evennia.server.validators.html deleted file mode 100644 index 4b61824ed0..0000000000 --- a/docs/0.9.5/api/evennia.server.validators.html +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - - - - evennia.server.validators — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.validators

-
-
-class evennia.server.validators.EvenniaUsernameAvailabilityValidator[source]
-

Bases: object

-

Checks to make sure a given username is not taken or otherwise reserved.

-
- -
-
-class evennia.server.validators.EvenniaPasswordValidator(regex="^[\\w. @+\\-',]+$", policy="Password should contain a mix of letters, spaces, digits and @/./+/-/_/'/, only.")[source]
-

Bases: object

-
-
-__init__(regex="^[\\w. @+\\-',]+$", policy="Password should contain a mix of letters, spaces, digits and @/./+/-/_/'/, only.")[source]
-

Constructs a standard Django password validator.

-
-
Parameters
-
    -
  • regex (str) – Regex pattern of valid characters to allow.

  • -
  • policy (str) – Brief explanation of what the defined regex permits.

  • -
-
-
-
- -
-
-validate(password, user=None)[source]
-

Validates a password string to make sure it meets predefined Evennia -acceptable character policy.

-
-
Parameters
-
    -
  • password (str) – Password to validate

  • -
  • user (None) – Unused argument but required by Django

  • -
-
-
Returns
-

None (None)

-
-
None if password successfully validated,

raises ValidationError otherwise.

-
-
-

-
-
-
- -
-
-get_help_text()[source]
-

Returns a user-facing explanation of the password policy defined -by this validator.

-
-
Returns
-

text (str) – Explanation of password policy.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.server.webserver.html b/docs/0.9.5/api/evennia.server.webserver.html deleted file mode 100644 index a88f151b43..0000000000 --- a/docs/0.9.5/api/evennia.server.webserver.html +++ /dev/null @@ -1,332 +0,0 @@ - - - - - - - - - evennia.server.webserver — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.server.webserver

-

This implements resources for Twisted webservers using the WSGI -interface of Django. This alleviates the need of running e.g. an -Apache server to serve Evennia’s web presence (although you could do -that too if desired).

-

The actual servers are started inside server.py as part of the Evennia -application.

-

(Lots of thanks to http://github.com/clemesha/twisted-wsgi-django for -a great example/aid on how to do this.)

-
-
-class evennia.server.webserver.LockableThreadPool(*args, **kwargs)[source]
-

Bases: twisted.python.threadpool.ThreadPool

-

Threadpool that can be locked from accepting new requests.

-
-
-__init__(*args, **kwargs)[source]
-

Create a new threadpool.

-

@param minthreads: minimum number of threads in the pool -@type minthreads: L{int}

-

@param maxthreads: maximum number of threads in the pool -@type maxthreads: L{int}

-

@param name: The name to give this threadpool; visible in log messages. -@type name: native L{str}

-
- -
-
-lock()[source]
-
- -
-
-callInThread(func, *args, **kwargs)[source]
-

called in the main reactor thread. Makes sure the pool -is not locked before continuing.

-
- -
- -
-
-class evennia.server.webserver.HTTPChannelWithXForwardedFor[source]
-

Bases: twisted.web.http.HTTPChannel

-

HTTP xforward class

-
-
-allHeadersReceived()[source]
-

Check to see if this is a reverse proxied connection.

-
- -
- -
-
-class evennia.server.webserver.EvenniaReverseProxyResource(host, port, path, reactor=<twisted.internet.epollreactor.EPollReactor object>)[source]
-

Bases: twisted.web.proxy.ReverseProxyResource

-
-
-getChild(path, request)[source]
-

Create and return a proxy resource with the same proxy configuration -as this one, except that its path also contains the segment given by -path at the end.

-
-
Parameters
-
    -
  • path (str) – Url path.

  • -
  • request (Request object) – Incoming request.

  • -
-
-
Returns
-

resource (EvenniaReverseProxyResource) – A proxy resource.

-
-
-
- -
-
-render(request)[source]
-

Render a request by forwarding it to the proxied server.

-
-
Parameters
-

request (Request) – Incoming request.

-
-
Returns
-

not_done (char) – Indicator to note request not yet finished.

-
-
-
- -
- -
-
-class evennia.server.webserver.DjangoWebRoot(pool)[source]
-

Bases: twisted.web.resource.Resource

-

This creates a web root (/) that Django -understands by tweaking the way -child instances are recognized.

-
-
-__init__(pool)[source]
-

Setup the django+twisted resource.

-
-
Parameters
-

pool (ThreadPool) – The twisted threadpool.

-
-
-
- -
-
-empty_threadpool()[source]
-

Converts our _pending_requests list of deferreds into a DeferredList

-
-
Returns
-

deflist (DeferredList) – Contains all deferreds of pending requests.

-
-
-
- -
-
-getChild(path, request)[source]
-

To make things work we nudge the url tree to make this the -root.

-
-
Parameters
-
    -
  • path (str) – Url path.

  • -
  • request (Request object) – Incoming request.

  • -
-
-
-

Notes

-

We make sure to save the request queue so -that we can safely kill the threadpool -on a server reload.

-
- -
- -
-
-class evennia.server.webserver.Website(resource, requestFactory=None, *args, **kwargs)[source]
-

Bases: twisted.web.server.Site

-

This class will only log http requests if settings.DEBUG is True.

-
-
-noisy = False
-
- -
-
-logPrefix()[source]
-

How to be named in logs

-
- -
-
-log(request)[source]
-

Conditional logging

-
- -
- -
-
-class evennia.server.webserver.WSGIWebServer(pool, *args, **kwargs)[source]
-

Bases: twisted.application.internet.TCPServer

-

This is a WSGI webserver. It makes sure to start -the threadpool after the service itself started, -so as to register correctly with the twisted daemon.

-

call with WSGIWebServer(threadpool, port, wsgi_resource)

-
-
-__init__(pool, *args, **kwargs)[source]
-

This just stores the threadpool.

-
-
Parameters
-
    -
  • pool (ThreadPool) – The twisted threadpool.

  • -
  • args (any) – Passed on to the TCPServer.

  • -
  • kwargs (any) – Passed on to the TCPServer.

  • -
-
-
-
- -
-
-startService()[source]
-

Start the pool after the service starts.

-
- -
-
-stopService()[source]
-

Safely stop the pool after the service stops.

-
- -
- -
-
-class evennia.server.webserver.PrivateStaticRoot(path, defaultType='text/html', ignoredExts=(), registry=None, allowExt=0)[source]
-

Bases: twisted.web.static.File

-

This overrides the default static file resource so as to not make the -directory listings public (that is, if you go to /media or /static you -won’t see an index of all static/media files on the server).

-
-
-directoryListing()[source]
-

Return a resource that generates an HTML listing of the -directory this path represents.

-

@return: A resource that renders the directory to HTML. -@rtype: L{DirectoryLister}

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.settings_default.html b/docs/0.9.5/api/evennia.settings_default.html deleted file mode 100644 index 44ef6ba345..0000000000 --- a/docs/0.9.5/api/evennia.settings_default.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - evennia.settings_default — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.settings_default

-

Master configuration file for Evennia.

-

NOTE: NO MODIFICATIONS SHOULD BE MADE TO THIS FILE!

-

All settings changes should be done by copy-pasting the variable and -its value to <gamedir>/server/conf/settings.py.

-

Hint: Don’t copy&paste over more from this file than you actually want -to change. Anything you don’t copy&paste will thus retain its default -value - which may change as Evennia is developed. This way you can -always be sure of what you have changed and what is default behaviour.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.typeclasses.admin.html b/docs/0.9.5/api/evennia.typeclasses.admin.html deleted file mode 100644 index 30c20917cb..0000000000 --- a/docs/0.9.5/api/evennia.typeclasses.admin.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - evennia.typeclasses.admin — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.typeclasses.admin

-
-
-class evennia.typeclasses.admin.TagAdmin(model, admin_site)[source]
-

Bases: django.contrib.admin.options.ModelAdmin

-

A django Admin wrapper for Tags.

-
-
-search_fields = ('db_key', 'db_category', 'db_tagtype')
-
- -
-
-list_display = ('db_key', 'db_category', 'db_tagtype', 'db_data')
-
- -
-
-fields = ('db_key', 'db_category', 'db_tagtype', 'db_data')
-
- -
-
-list_filter = ('db_tagtype',)
-
- -
-
-property media
-
- -
- -
-
-class evennia.typeclasses.admin.TagForm(*args, **kwargs)[source]
-

Bases: django.forms.models.ModelForm

-

This form overrides the base behavior of the ModelForm that would be used for a -Tag-through-model. Since the through-models only have access to the foreignkeys of the Tag and -the Object that they’re attached to, we need to spoof the behavior of it being a form that would -correspond to its tag, or the creation of a tag. Instead of being saved, we’ll call to the -Object’s handler, which will handle the creation, change, or deletion of a tag for us, as well -as updating the handler’s cache so that all changes are instantly updated in-game.

-
-
-class Meta[source]
-

Bases: object

-
-
-fields = ('tag_key', 'tag_category', 'tag_data', 'tag_type')
-
- -
- -
-
-__init__(*args, **kwargs)[source]
-

If we have a tag, then we’ll prepopulate our instance with the fields we’d expect it -to have based on the tag. tag_key, tag_category, tag_type, and tag_data all refer to -the corresponding tag fields. The initial data of the form fields will similarly be -populated.

-
- -
-
-save(commit=True)[source]
-

One thing we want to do here is the or None checks, because forms are saved with an empty -string rather than null from forms, usually, and the Handlers may handle empty strings -differently than None objects. So for consistency with how things are handled in game, -we’ll try to make sure that empty form fields will be None, rather than ‘’.

-
- -
-
-base_fields = {'tag_category': <django.forms.fields.CharField object>, 'tag_data': <django.forms.fields.CharField object>, 'tag_key': <django.forms.fields.CharField object>, 'tag_type': <django.forms.fields.CharField object>}
-
- -
-
-declared_fields = {'tag_category': <django.forms.fields.CharField object>, 'tag_data': <django.forms.fields.CharField object>, 'tag_key': <django.forms.fields.CharField object>, 'tag_type': <django.forms.fields.CharField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.typeclasses.admin.TagFormSet(data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None, **kwargs)[source]
-

Bases: django.forms.models.BaseInlineFormSet

-

The Formset handles all the inline forms that are grouped together on the change page of the -corresponding object. All the tags will appear here, and we’ll save them by overriding the -formset’s save method. The forms will similarly spoof their save methods to return an instance -which hasn’t been saved to the database, but have the relevant fields filled out based on the -contents of the cleaned form. We’ll then use that to call to the handler of the corresponding -Object, where the handler is an AliasHandler, PermissionsHandler, or TagHandler, based on the -type of tag.

-
-
-save(commit=True)[source]
-

Save model instances for every form, adding and changing instances -as necessary, and return the list of instances.

-
- -
- -
-
-class evennia.typeclasses.admin.TagInline(parent_model, admin_site)[source]
-

Bases: django.contrib.admin.options.TabularInline

-

A handler for inline Tags. This class should be subclassed in the admin of your models, -and the ‘model’ and ‘related_field’ class attributes must be set. model should be the -through model (ObjectDB_db_tag’, for example), while related field should be the name -of the field on that through model which points to the model being used: ‘objectdb’, -‘msg’, ‘accountdb’, etc.

-
-
-model = None
-
- -
-
-form
-

alias of TagForm

-
- -
-
-formset
-

alias of TagFormSet

-
- -
-
-related_field = None
-
- -
-
-extra = 0
-
- -
-
-get_formset(request, obj=None, **kwargs)[source]
-

get_formset has to return a class, but we need to make the class that we return -know about the related_field that we’ll use. Returning the class itself rather than -a proxy isn’t threadsafe, since it’d be the base class and would change if multiple -people used the admin at the same time

-
- -
-
-property media
-
- -
- -
-
-class evennia.typeclasses.admin.AttributeForm(*args, **kwargs)[source]
-

Bases: django.forms.models.ModelForm

-

This form overrides the base behavior of the ModelForm that would be used for a Attribute-through-model. -Since the through-models only have access to the foreignkeys of the Attribute and the Object that they’re -attached to, we need to spoof the behavior of it being a form that would correspond to its Attribute, -or the creation of an Attribute. Instead of being saved, we’ll call to the Object’s handler, which will handle -the creation, change, or deletion of an Attribute for us, as well as updating the handler’s cache so that all -changes are instantly updated in-game.

-
-
-class Meta[source]
-

Bases: object

-
-
-fields = ('attr_key', 'attr_value', 'attr_category', 'attr_lockstring', 'attr_type')
-
- -
- -
-
-__init__(*args, **kwargs)[source]
-

If we have an Attribute, then we’ll prepopulate our instance with the fields we’d expect it -to have based on the Attribute. attr_key, attr_category, attr_value, attr_type, -and attr_lockstring all refer to the corresponding Attribute fields. The initial data of the form fields will -similarly be populated.

-
- -
-
-save(commit=True)[source]
-

One thing we want to do here is the or None checks, because forms are saved with an empty -string rather than null from forms, usually, and the Handlers may handle empty strings -differently than None objects. So for consistency with how things are handled in game, -we’ll try to make sure that empty form fields will be None, rather than ‘’.

-
- -
-
-clean_attr_value()[source]
-

Prevent certain data-types from being cleaned due to literal_eval -failing on them. Otherwise they will be turned into str.

-
- -
-
-base_fields = {'attr_category': <django.forms.fields.CharField object>, 'attr_key': <django.forms.fields.CharField object>, 'attr_lockstring': <django.forms.fields.CharField object>, 'attr_type': <django.forms.fields.CharField object>, 'attr_value': <evennia.utils.picklefield.PickledFormField object>}
-
- -
-
-declared_fields = {'attr_category': <django.forms.fields.CharField object>, 'attr_key': <django.forms.fields.CharField object>, 'attr_lockstring': <django.forms.fields.CharField object>, 'attr_type': <django.forms.fields.CharField object>, 'attr_value': <evennia.utils.picklefield.PickledFormField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.typeclasses.admin.AttributeFormSet(data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None, **kwargs)[source]
-

Bases: django.forms.models.BaseInlineFormSet

-

Attribute version of TagFormSet, as above.

-
-
-save(commit=True)[source]
-

Save model instances for every form, adding and changing instances -as necessary, and return the list of instances.

-
- -
- -
-
-class evennia.typeclasses.admin.AttributeInline(parent_model, admin_site)[source]
-

Bases: django.contrib.admin.options.TabularInline

-

A handler for inline Attributes. This class should be subclassed in the admin of your models, -and the ‘model’ and ‘related_field’ class attributes must be set. model should be the -through model (ObjectDB_db_tag’, for example), while related field should be the name -of the field on that through model which points to the model being used: ‘objectdb’, -‘msg’, ‘accountdb’, etc.

-
-
-model = None
-
- -
-
-form
-

alias of AttributeForm

-
- -
-
-formset
-

alias of AttributeFormSet

-
- -
-
-related_field = None
-
- -
-
-extra = 0
-
- -
-
-get_formset(request, obj=None, **kwargs)[source]
-

get_formset has to return a class, but we need to make the class that we return -know about the related_field that we’ll use. Returning the class itself rather than -a proxy isn’t threadsafe, since it’d be the base class and would change if multiple -people used the admin at the same time

-
- -
-
-property media
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.typeclasses.attributes.html b/docs/0.9.5/api/evennia.typeclasses.attributes.html deleted file mode 100644 index 8cb4247e3f..0000000000 --- a/docs/0.9.5/api/evennia.typeclasses.attributes.html +++ /dev/null @@ -1,896 +0,0 @@ - - - - - - - - - evennia.typeclasses.attributes — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.typeclasses.attributes

-

Attributes are arbitrary data stored on objects. Attributes supports -both pure-string values and pickled arbitrary data.

-

Attributes are also used to implement Nicks. This module also contains -the Attribute- and NickHandlers as well as the NAttributeHandler, -which is a non-db version of Attributes.

-
-
-class evennia.typeclasses.attributes.Attribute(*args, **kwargs)[source]
-

Bases: evennia.utils.idmapper.models.SharedMemoryModel

-

Attributes are things that are specific to different types of objects. For -example, a drink container needs to store its fill level, whereas an exit -needs to store its open/closed/locked/unlocked state. These are done via -attributes, rather than making different classes for each object type and -storing them directly. The added benefit is that we can add/remove -attributes on the fly as we like.

-
-
The Attribute class defines the following properties:
    -
  • key (str): Primary identifier.

  • -
  • lock_storage (str): Perm strings.

  • -
  • -
    model (str): A string defining the model this is connected to. This

    is a natural_key, like “objects.objectdb”

    -
    -
    -
  • -
  • date_created (datetime): When the attribute was created.

  • -
  • -
    value (any): The data stored in the attribute, in pickled form

    using wrappers to be able to store/retrieve models.

    -
    -
    -
  • -
  • -
    strvalue (str): String-only data. This data is not pickled and

    is thus faster to search for in the database.

    -
    -
    -
  • -
  • -
    category (str): Optional character string for grouping the

    Attribute.

    -
    -
    -
  • -
-
-
-
-
-db_key
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_value
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_strvalue
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_category
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_lock_storage
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_model
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_attrtype
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_date_created
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-locks[source]
-
- -
-
-property key
-
- -
-
-property strvalue
-
- -
-
-property category
-
- -
-
-property model
-
- -
-
-property attrtype
-
- -
-
-property date_created
-
- -
-
-property lock_storage
-
- -
-
-property value
-

Getter. Allows for value = self.value. -We cannot cache here since it makes certain cases (such -as storing a dbobj which is then deleted elsewhere) out-of-sync. -The overhead of unpickling seems hard to avoid.

-
- -
-
-access(accessing_obj, access_type='attrread', default=False, **kwargs)[source]
-

Determines if another object has permission to access.

-
-
Parameters
-
    -
  • accessing_obj (object) – Entity trying to access this one.

  • -
  • access_type (str, optional) – Type of access sought, see -the lock documentation.

  • -
  • default (bool, optional) – What result to return if no lock -of access_type was found. The default, False, means a lockdown -policy, only allowing explicit access.

  • -
  • kwargs (any, optional) – Not used; here to make the API consistent with -other access calls.

  • -
-
-
Returns
-

result (bool) – If the lock was passed or not.

-
-
-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-accountdb_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-channeldb_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-get_next_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=True, **kwargs)
-
- -
-
-get_previous_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=False, **kwargs)
-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-objectdb_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-path = 'evennia.typeclasses.attributes.Attribute'
-
- -
-
-scriptdb_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
-
-class evennia.typeclasses.attributes.AttributeHandler(obj)[source]
-

Bases: object

-

Handler for adding Attributes to the object.

-
-
-__init__(obj)[source]
-

Initialize handler.

-
- -
-
-reset_cache()[source]
-

Reset cache from the outside.

-
- -
-
-has(key=None, category=None)[source]
-

Checks if the given Attribute (or list of Attributes) exists on -the object.

-
-
Parameters
-
    -
  • key (str or iterable) – The Attribute key or keys to check for. -If None, search by category.

  • -
  • category (str or None) – Limit the check to Attributes with this -category (note, that None is the default category).

  • -
-
-
Returns
-

has_attribute (bool or list)

-
-
If the Attribute exists on

this object or not. If key was given as an iterable then -the return is a list of booleans.

-
-
-

-
-
-
- -
-
-get(key=None, default=None, category=None, return_obj=False, strattr=False, raise_exception=False, accessing_obj=None, default_access=True, return_list=False)[source]
-

Get the Attribute.

-
-
Parameters
-
    -
  • key (str or list, optional) – the attribute identifier or -multiple attributes to get. if a list of keys, the -method will return a list.

  • -
  • category (str, optional) – the category within which to -retrieve attribute(s).

  • -
  • default (any, optional) – The value to return if an -Attribute was not defined. If set, it will be returned in -a one-item list.

  • -
  • return_obj (bool, optional) – If set, the return is not the value of the -Attribute but the Attribute object itself.

  • -
  • strattr (bool, optional) – Return the strvalue field of -the Attribute rather than the usual value, this is a -string-only value for quick database searches.

  • -
  • raise_exception (bool, optional) – When an Attribute is not -found, the return from this is usually default. If this -is set, an exception is raised instead.

  • -
  • accessing_obj (object, optional) – If set, an attrread -permission lock will be checked before returning each -looked-after Attribute.

  • -
  • default_access (bool, optional) – If no attrread lock is set on -object, this determines if the lock should then be passed or not.

  • -
  • return_list (bool, optional) –

  • -
-
-
Returns
-

result (any or list)

-
-
One or more matches for keys and/or

categories. Each match will be the value of the found Attribute(s) -unless return_obj is True, at which point it will be the -attribute object itself or None. If return_list is True, this -will always be a list, regardless of the number of elements.

-
-
-

-
-
Raises
-

AttributeError – If raise_exception is set and no matching Attribute -was found matching key.

-
-
-
- -
-
-add(key, value, category=None, lockstring='', strattr=False, accessing_obj=None, default_access=True)[source]
-

Add attribute to object, with optional lockstring.

-
-
Parameters
-
    -
  • key (str) – An Attribute name to add.

  • -
  • value (any or str) – The value of the Attribute. If -strattr keyword is set, this must be a string.

  • -
  • category (str, optional) – The category for the Attribute. -The default None is the normal category used.

  • -
  • lockstring (str, optional) – A lock string limiting access -to the attribute.

  • -
  • strattr (bool, optional) – Make this a string-only Attribute. -This is only ever useful for optimization purposes.

  • -
  • accessing_obj (object, optional) – An entity to check for -the attrcreate access-type. If not passing, this method -will be exited.

  • -
  • default_access (bool, optional) – What access to grant if -accessing_obj is given but no lock of the type -attrcreate is defined on the Attribute in question.

  • -
-
-
-
- -
-
-batch_add(*args, **kwargs)[source]
-

Batch-version of add(). This is more efficient than -repeat-calling add when having many Attributes to add.

-
-
Parameters
-

*args (tuple) –

Each argument should be a tuples (can be of varying -length) representing the Attribute to add to this object. -Supported tuples are

-
-
    -
  • (key, value)

  • -
  • (key, value, category)

  • -
  • (key, value, category, lockstring)

  • -
  • (key, value, category, lockstring, default_access)

  • -
-
-

-
-
Keyword Arguments
-

strattr (bool) – If True, value must be a string. This -will save the value without pickling which is less -flexible but faster to search (not often used except -internally).

-
-
Raises
-

RuntimeError – If trying to pass a non-iterable as argument.

-
-
-

Notes

-

The indata tuple order matters, so if you want a lockstring -but no category, set the category to None. This method -does not have the ability to check editing permissions like -normal .add does, and is mainly used internally. It does not -use the normal self.add but apply the Attributes directly -to the database.

-
- -
-
-remove(key=None, raise_exception=False, category=None, accessing_obj=None, default_access=True)[source]
-

Remove attribute or a list of attributes from object.

-
-
Parameters
-
    -
  • key (str or list, optional) – An Attribute key to remove or a list of keys. If -multiple keys, they must all be of the same category. If None and -category is not given, remove all Attributes.

  • -
  • raise_exception (bool, optional) – If set, not finding the -Attribute to delete will raise an exception instead of -just quietly failing.

  • -
  • category (str, optional) – The category within which to -remove the Attribute.

  • -
  • accessing_obj (object, optional) – An object to check -against the attredit lock. If not given, the check will -be skipped.

  • -
  • default_access (bool, optional) – The fallback access to -grant if accessing_obj is given but there is no -attredit lock set on the Attribute in question.

  • -
-
-
Raises
-

AttributeError – If raise_exception is set and no matching Attribute -was found matching key.

-
-
-

Notes

-

If neither key nor category is given, this acts as clear().

-
- -
-
-clear(category=None, accessing_obj=None, default_access=True)[source]
-

Remove all Attributes on this object.

-
-
Parameters
-
    -
  • category (str, optional) – If given, clear only Attributes -of this category.

  • -
  • accessing_obj (object, optional) – If given, check the -attredit lock on each Attribute before continuing.

  • -
  • default_access (bool, optional) – Use this permission as -fallback if access_obj is given but there is no lock of -type attredit on the Attribute in question.

  • -
-
-
-
- -
-
-all(accessing_obj=None, default_access=True)[source]
-

Return all Attribute objects on this object, regardless of category.

-
-
Parameters
-
    -
  • accessing_obj (object, optional) – Check the attrread -lock on each attribute before returning them. If not -given, this check is skipped.

  • -
  • default_access (bool, optional) – Use this permission as a -fallback if accessing_obj is given but one or more -Attributes has no lock of type attrread defined on them.

  • -
-
-
Returns
-

Attributes (list)

-
-
All the Attribute objects (note: Not

their values!) in the handler.

-
-
-

-
-
-
- -
- -
-
-exception evennia.typeclasses.attributes.NickTemplateInvalid[source]
-

Bases: ValueError

-
- -
-
-evennia.typeclasses.attributes.initialize_nick_templates(in_template, out_template)[source]
-

Initialize the nick templates for matching and remapping a string.

-
-
Parameters
-
    -
  • in_template (str) – The template to be used for nick recognition.

  • -
  • out_template (str) – The template to be used to replace the string -matched by the in_template.

  • -
-
-
Returns
-

(regex, str)

-
-
Regex to match against strings and a template

Template with markers {arg1}, {arg2}, etc for -replacement using the standard .format method.

-
-
-

-
-
Raises
-

attributes.NickTemplateInvalid – If the in/out template does not have a matching -number of $args.

-
-
-
- -
-
-evennia.typeclasses.attributes.parse_nick_template(string, template_regex, outtemplate)[source]
-

Parse a text using a template and map it to another template

-
-
Parameters
-
    -
  • string (str) – The input string to processj

  • -
  • template_regex (regex) – A template regex created with -initialize_nick_template.

  • -
  • outtemplate (str) – The template to which to map the matches -produced by the template_regex. This should have $1, $2, -etc to match the regex.

  • -
-
-
-
- -
-
-class evennia.typeclasses.attributes.NickHandler(*args, **kwargs)[source]
-

Bases: evennia.typeclasses.attributes.AttributeHandler

-

Handles the addition and removal of Nicks. Nicks are special -versions of Attributes with an _attrtype hardcoded to nick. -They also always use the strvalue fields for their data.

-
-
-__init__(*args, **kwargs)[source]
-

Initialize handler.

-
- -
-
-has(key, category='inputline')[source]
-
-
Parameters
-
    -
  • key (str or iterable) – The Nick key or keys to check for.

  • -
  • category (str) – Limit the check to Nicks with this -category (note, that None is the default category).

  • -
-
-
Returns
-

has_nick (bool or list)

-
-
If the Nick exists on this object

or not. If key was given as an iterable then the return -is a list of booleans.

-
-
-

-
-
-
- -
-
-get(key=None, category='inputline', return_tuple=False, **kwargs)[source]
-

Get the replacement value matching the given key and category

-
-
Parameters
-
    -
  • key (str or list, optional) – the attribute identifier or -multiple attributes to get. if a list of keys, the -method will return a list.

  • -
  • category (str, optional) – the category within which to -retrieve the nick. The “inputline” means replacing data -sent by the user.

  • -
  • return_tuple (bool, optional) – return the full nick tuple rather -than just the replacement. For non-template nicks this is just -a string.

  • -
  • kwargs (any, optional) – These are passed on to AttributeHandler.get.

  • -
-
-
-
- -
-
-add(key, replacement, category='inputline', **kwargs)[source]
-

Add a new nick.

-
-
Parameters
-
    -
  • key (str) – A key (or template) for the nick to match for.

  • -
  • replacement (str) – The string (or template) to replace key with (the “nickname”).

  • -
  • category (str, optional) – the category within which to -retrieve the nick. The “inputline” means replacing data -sent by the user.

  • -
  • kwargs (any, optional) – These are passed on to AttributeHandler.get.

  • -
-
-
-
- -
-
-remove(key, category='inputline', **kwargs)[source]
-

Remove Nick with matching category.

-
-
Parameters
-
    -
  • key (str) – A key for the nick to match for.

  • -
  • category (str, optional) – the category within which to -removethe nick. The “inputline” means replacing data -sent by the user.

  • -
  • kwargs (any, optional) – These are passed on to AttributeHandler.get.

  • -
-
-
-
- -
-
-nickreplace(raw_string, categories='inputline', 'channel', include_account=True)[source]
-

Apply nick replacement of entries in raw_string with nick replacement.

-
-
Parameters
-
    -
  • raw_string (str) – The string in which to perform nick -replacement.

  • -
  • categories (tuple, optional) – Replacement categories in -which to perform the replacement, such as “inputline”, -“channel” etc.

  • -
  • include_account (bool, optional) – Also include replacement -with nicks stored on the Account level.

  • -
  • kwargs (any, optional) – Not used.

  • -
-
-
Returns
-

string (str)

-
-
A string with matching keys replaced with

their nick equivalents.

-
-
-

-
-
-
- -
- -
-
-class evennia.typeclasses.attributes.NAttributeHandler(obj)[source]
-

Bases: object

-

This stand-alone handler manages non-database saving. -It is similar to AttributeHandler and is used -by the .ndb handler in the same way as .db does -for the AttributeHandler.

-
-
-__init__(obj)[source]
-

Initialized on the object

-
- -
-
-has(key)[source]
-

Check if object has this attribute or not.

-
-
Parameters
-

key (str) – The Nattribute key to check.

-
-
Returns
-

has_nattribute (bool) – If Nattribute is set or not.

-
-
-
- -
-
-get(key)[source]
-

Get the named key value.

-
-
Parameters
-

key (str) – The Nattribute key to get.

-
-
Returns
-

the value of the Nattribute.

-
-
-
- -
-
-add(key, value)[source]
-

Add new key and value.

-
-
Parameters
-
    -
  • key (str) – The name of Nattribute to add.

  • -
  • value (any) – The value to store.

  • -
-
-
-
- -
-
-remove(key)[source]
-

Remove Nattribute from storage.

-
-
Parameters
-

key (str) – The name of the Nattribute to remove.

-
-
-
- -
-
-clear()[source]
-

Remove all NAttributes from handler.

-
- -
-
-all(return_tuples=False)[source]
-

List the contents of the handler.

-
-
Parameters
-

return_tuples (bool, optional) – Defines if the Nattributes -are returns as a list of keys or as a list of (key, value).

-
-
Returns
-

nattributes (list)

-
-
A list of keys [key, key, …] or a

list of tuples [(key, value), …] depending on the -setting of return_tuples.

-
-
-

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.typeclasses.html b/docs/0.9.5/api/evennia.typeclasses.html deleted file mode 100644 index d6689d651b..0000000000 --- a/docs/0.9.5/api/evennia.typeclasses.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - evennia.typeclasses — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.typeclasses

-

This sub-package defines the typeclass-system, a way to wrap database -access into almost-normal Python classes. Using typeclasses one can -work in normal Python while having the luxury of persistent data -storage at every turn. ObjectDB, ChannelDB, AccountDB and ScriptDB all -inherit from the models in this package. Here is also were the -Attribute and Tag models are defined along with their handlers.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.typeclasses.managers.html b/docs/0.9.5/api/evennia.typeclasses.managers.html deleted file mode 100644 index 7f9e511477..0000000000 --- a/docs/0.9.5/api/evennia.typeclasses.managers.html +++ /dev/null @@ -1,521 +0,0 @@ - - - - - - - - - evennia.typeclasses.managers — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.typeclasses.managers

-

This implements the common managers that are used by the -abstract models in dbobjects.py (and which are thus shared by -all Attributes and TypedObjects).

-
-
-class evennia.typeclasses.managers.TypedObjectManager(*args, **kwargs)[source]
-

Bases: evennia.utils.idmapper.manager.SharedMemoryManager

-

Common ObjectManager for all dbobjects.

-
-
-get_attribute(key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None, **kwargs)[source]
-

Return Attribute objects by key, by category, by value, by -strvalue, by object (it is stored on) or with a combination of -those criteria.

-
-
Parameters
-
    -
  • key (str, optional) – The attribute’s key to search for.

  • -
  • category (str, optional) – The category of the attribute(s) -to search for.

  • -
  • value (str, optional) – The attribute value to search for. -Note that this is not a very efficient operation since it -will query for a pickled entity. Mutually exclusive to -strvalue.

  • -
  • strvalue (str, optional) – The str-value to search for. -Most Attributes will not have strvalue set. This is -mutually exclusive to the value keyword and will take -precedence if given.

  • -
  • obj (Object, optional) – On which object the Attribute to -search for is.

  • -
  • attrtype (str, optional) – An attribute-type to search for. -By default this is either None (normal Attributes) or -“nick”.

  • -
  • kwargs (any) – Currently unused. Reserved for future use.

  • -
-
-
Returns
-

attributes (list) – The matching Attributes.

-
-
-
- -
-
-get_nick(key=None, category=None, value=None, strvalue=None, obj=None)[source]
-

Get a nick, in parallel to get_attribute.

-
-
Parameters
-
    -
  • key (str, optional) – The nicks’s key to search for

  • -
  • category (str, optional) – The category of the nicks(s) to search for.

  • -
  • value (str, optional) – The attribute value to search for. Note that this -is not a very efficient operation since it will query for a pickled -entity. Mutually exclusive to strvalue.

  • -
  • strvalue (str, optional) – The str-value to search for. Most Attributes -will not have strvalue set. This is mutually exclusive to the value -keyword and will take precedence if given.

  • -
  • obj (Object, optional) – On which object the Attribute to search for is.

  • -
-
-
Returns
-

nicks (list) – The matching Nicks.

-
-
-
- -
-
-get_by_attribute(key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs)[source]
-

Return objects having attributes with the given key, category, -value, strvalue or combination of those criteria.

-
-
Parameters
-
    -
  • key (str, optional) – The attribute’s key to search for

  • -
  • category (str, optional) – The category of the attribute -to search for.

  • -
  • value (str, optional) – The attribute value to search for. -Note that this is not a very efficient operation since it -will query for a pickled entity. Mutually exclusive to -strvalue.

  • -
  • strvalue (str, optional) – The str-value to search for. -Most Attributes will not have strvalue set. This is -mutually exclusive to the value keyword and will take -precedence if given.

  • -
  • attrype (str, optional) – An attribute-type to search for. -By default this is either None (normal Attributes) or -“nick”.

  • -
  • kwargs (any) – Currently unused. Reserved for future use.

  • -
-
-
Returns
-

obj (list) – Objects having the matching Attributes.

-
-
-
- -
-
-get_by_nick(key=None, nick=None, category='inputline')[source]
-

Get object based on its key or nick.

-
-
Parameters
-
    -
  • key (str, optional) – The attribute’s key to search for

  • -
  • nick (str, optional) – The nickname to search for

  • -
  • category (str, optional) – The category of the nick -to search for.

  • -
-
-
Returns
-

obj (list) – Objects having the matching Nicks.

-
-
-
- -
-
-get_tag(key=None, category=None, obj=None, tagtype=None, global_search=False)[source]
-

Return Tag objects by key, by category, by object (it is -stored on) or with a combination of those criteria.

-
-
Parameters
-
    -
  • key (str, optional) – The Tag’s key to search for

  • -
  • category (str, optional) – The Tag of the attribute(s) -to search for.

  • -
  • obj (Object, optional) – On which object the Tag to -search for is.

  • -
  • tagtype (str, optional) – One of None (normal tags), -“alias” or “permission”

  • -
  • global_search (bool, optional) – Include all possible tags, -not just tags on this object

  • -
-
-
Returns
-

tag (list) – The matching Tags.

-
-
-
- -
-
-get_permission(key=None, category=None, obj=None)[source]
-

Get a permission from the database.

-
-
Parameters
-
    -
  • key (str, optional) – The permission’s identifier.

  • -
  • category (str, optional) – The permission’s category.

  • -
  • obj (object, optional) – The object on which this Tag is set.

  • -
-
-
Returns
-

permission (list) – Permission objects.

-
-
-
- -
-
-get_alias(key=None, category=None, obj=None)[source]
-

Get an alias from the database.

-
-
Parameters
-
    -
  • key (str, optional) – The permission’s identifier.

  • -
  • category (str, optional) – The permission’s category.

  • -
  • obj (object, optional) – The object on which this Tag is set.

  • -
-
-
Returns
-

alias (list) – Alias objects.

-
-
-
- -
-
-get_by_tag(key=None, category=None, tagtype=None, **kwargs)[source]
-

Return objects having tags with a given key or category or combination of the two. -Also accepts multiple tags/category/tagtype

-
-
Parameters
-
    -
  • key (str or list, optional) – Tag key or list of keys. Not case sensitive.

  • -
  • category (str or list, optional) – Tag category. Not case sensitive. -If key is a list, a single category can either apply to all -keys in that list or this must be a list matching the key -list element by element. If no key is given, all objects with -tags of this category are returned.

  • -
  • tagtype (str, optional) – ‘type’ of Tag, by default -this is either None (a normal Tag), alias or -permission. This always apply to all queried tags.

  • -
-
-
Keyword Arguments
-

match (str) – “all” (default) or “any”; determines whether the -target object must be tagged with ALL of the provided -tags/categories or ANY single one. ANY will perform a weighted -sort, so objects with more tag matches will outrank those with -fewer tag matches.

-
-
Returns
-

objects (list) – Objects with matching tag.

-
-
Raises
-

IndexError – If key and category are both lists and category is shorter -than key.

-
-
-
- -
-
-get_by_permission(key=None, category=None)[source]
-

Return objects having permissions with a given key or category or -combination of the two.

-
-
Parameters
-
    -
  • key (str, optional) – Permissions key. Not case sensitive.

  • -
  • category (str, optional) – Permission category. Not case sensitive.

  • -
-
-
Returns
-

objects (list) – Objects with matching permission.

-
-
-
- -
-
-get_by_alias(key=None, category=None)[source]
-

Return objects having aliases with a given key or category or -combination of the two.

-
-
Parameters
-
    -
  • key (str, optional) – Alias key. Not case sensitive.

  • -
  • category (str, optional) – Alias category. Not case sensitive.

  • -
-
-
Returns
-

objects (list) – Objects with matching alias.

-
-
-
- -
-
-create_tag(key=None, category=None, data=None, tagtype=None)[source]
-

Create a new Tag of the base type associated with this -object. This makes sure to create case-insensitive tags. -If the exact same tag configuration (key+category+tagtype+dbmodel) -exists on the model, a new tag will not be created, but an old -one returned.

-
-
Parameters
-
    -
  • key (str, optional) – Tag key. Not case sensitive.

  • -
  • category (str, optional) – Tag category. Not case sensitive.

  • -
  • data (str, optional) – Extra information about the tag.

  • -
  • tagtype (str or None, optional) – ‘type’ of Tag, by default -this is either None (a normal Tag), alias or -permission.

  • -
-
-
-

Notes

-

The data field is not part of the uniqueness of the tag: -Setting data on an existing tag will overwrite the old -data field. It is intended only as a way to carry -information about the tag (like a help text), not to carry -any information about the tagged objects themselves.

-
- -
-
-dbref(dbref, reqhash=True)[source]
-

Determing if input is a valid dbref.

-
-
Parameters
-
    -
  • dbref (str or int) – A possible dbref.

  • -
  • reqhash (bool, optional) – If the “#” is required for this -to be considered a valid hash.

  • -
-
-
Returns
-

dbref (int or None) – The integer part of the dbref.

-
-
-

Notes

-

Valid forms of dbref (database reference number) are -either a string ‘#N’ or an integer N.

-
- -
-
-get_id(dbref)[source]
-

Find object with given dbref.

-
-
Parameters
-

dbref (str or int) – The id to search for.

-
-
Returns
-

object (TypedObject) – The matched object.

-
-
-
- -
- -

Alias to get_id.

-
-
Parameters
-

dbref (str or int) – The id to search for.

-
-
Returns
-

object (TypedObject) – The matched object.

-
-
-
- -
-
-get_dbref_range(min_dbref=None, max_dbref=None)[source]
-

Get objects within a certain range of dbrefs.

-
-
Parameters
-
    -
  • min_dbref (int) – Start of dbref range.

  • -
  • max_dbref (int) – End of dbref range (inclusive)

  • -
-
-
Returns
-

objects (list)

-
-
TypedObjects with dbrefs within

the given dbref ranges.

-
-
-

-
-
-
- -
-
-get_typeclass_totals(*args, **kwargs) → object[source]
-

Returns a queryset of typeclass composition statistics.

-
-
Returns
-

qs (Queryset)

-
-
A queryset of dicts containing the typeclass (name),

the count of objects with that typeclass and a float representing -the percentage of objects associated with the typeclass.

-
-
-

-
-
-
- -
-
-object_totals()[source]
-

Get info about database statistics.

-
-
Returns
-

census (dict)

-
-
A dictionary {typeclass_path: number, …} with

all the typeclasses active in-game as well as the number -of such objects defined (i.e. the number of database -object having that typeclass set on themselves).

-
-
-

-
-
-
- -
- -

Searches through all objects returning those which has a -certain typeclass. If location is set, limit search to objects -in that location.

-
-
Parameters
-
    -
  • typeclass (str or class) – A typeclass class or a python path to a typeclass.

  • -
  • include_children (bool, optional) – Return objects with -given typeclass and all children inheriting from this -typeclass. Mutuall exclusive to include_parents.

  • -
  • include_parents (bool, optional) – Return objects with -given typeclass and all parents to this typeclass. -Mutually exclusive to include_children.

  • -
-
-
Returns
-

objects (list) – The objects found with the given typeclasses.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.typeclasses.models.html b/docs/0.9.5/api/evennia.typeclasses.models.html deleted file mode 100644 index 907bb4b41c..0000000000 --- a/docs/0.9.5/api/evennia.typeclasses.models.html +++ /dev/null @@ -1,795 +0,0 @@ - - - - - - - - - evennia.typeclasses.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.typeclasses.models

-

This is the abstract django models for many of the database objects -in Evennia. A django abstract (obs, not the same as a Python metaclass!) is -a model which is not actually created in the database, but which only exists -for other models to inherit from, to avoid code duplication. Any model can -import and inherit from these classes.

-

Attributes are database objects stored on other objects. The implementing -class needs to supply a ForeignKey field attr_object pointing to the kind -of object being mapped. Attributes storing iterables actually store special -types of iterables named PackedList/PackedDict respectively. These make -sure to save changes to them to database - this is criticial in order to -allow for obj.db.mylist[2] = data. Also, all dbobjects are saved as -dbrefs but are also aggressively cached.

-

TypedObjects are objects ‘decorated’ with a typeclass - that is, the typeclass -(which is a normal Python class implementing some special tricks with its -get/set attribute methods, allows for the creation of all sorts of different -objects all with the same database object underneath. Usually attributes are -used to permanently store things not hard-coded as field on the database object. -The admin should usually not have to deal directly with the database object -layer.

-

This module also contains the Managers for the respective models; inherit from -these to create custom managers.

-
-
-
-class evennia.typeclasses.models.TypedObject(*args, **kwargs)[source]
-

Bases: evennia.utils.idmapper.models.SharedMemoryModel

-

Abstract Django model.

-

This is the basis for a typed object. It also contains all the -mechanics for managing connected attributes.

-
-
The TypedObject has the following properties:

key - main name -name - alias for key -typeclass_path - the path to the decorating typeclass -typeclass - auto-linked typeclass -date_created - time stamp of object creation -permissions - perm strings -dbref - #id of object -db - persistent attribute storage -ndb - non-persistent attribute storage

-
-
-
-
-db_key
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_typeclass_path
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_date_created
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_lock_storage
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_attributes
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-db_tags
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-objects
-
- -
-
-set_class_from_typeclass(typeclass_path=None)[source]
-
- -
-
-__init__(*args, **kwargs)[source]
-

The __init__ method of typeclasses is the core operational -code of the typeclass system, where it dynamically re-applies -a class based on the db_typeclass_path database field rather -than use the one in the model.

-
-
Parameters
-
    -
  • *args – Passed through to parent.

  • -
  • **kwargs – Passed through to parent.

  • -
-
-
-

Notes

-

The loading mechanism will attempt the following steps:

-
    -
  1. Attempt to load typeclass given on command line

  2. -
  3. Attempt to load typeclass stored in db_typeclass_path

  4. -
  5. Attempt to load __settingsclasspath__, which is by the -default classes defined to be the respective user-set -base typeclass settings, like BASE_OBJECT_TYPECLASS.

  6. -
  7. Attempt to load __defaultclasspath__, which is the -base classes in the library, like DefaultObject etc.

  8. -
  9. If everything else fails, use the database model.

  10. -
-

Normal operation is to load successfully at either step 1 -or 2 depending on how the class was called. Tracebacks -will be logged for every step the loader must take beyond -2.

-
- -
-
-attributes[source]
-
- -
-
-locks[source]
-
- -
-
-tags[source]
-
- -
-
-aliases[source]
-
- -
-
-permissions[source]
-
- -
-
-nattributes[source]
-
- -
-
-class Meta[source]
-

Bases: object

-

Django setup info.

-
-
-abstract = False
-
- -
-
-verbose_name = 'Evennia Database Object'
-
- -
-
-ordering = ['-db_date_created', 'id', 'db_typeclass_path', 'db_key']
-
- -
- -
-
-property name
-
- -
-
-property key
-
- -
-
-property dbid
-

Caches and returns the unique id of the object. -Use this instead of self.id, which is not cached.

-
- -
-
-property dbref
-

Returns the object’s dbref on the form #NN.

-
- -
-
-at_idmapper_flush()[source]
-

This is called when the idmapper cache is flushed and -allows customized actions when this happens.

-
-
Returns
-

do_flush (bool)

-
-
If True, flush this object as normal. If

False, don’t flush and expect this object to handle -the flushing on its own.

-
-
-

-
-
-

Notes

-

The default implementation relies on being able to clear -Django’s Foreignkey cache on objects not affected by the -flush (notably objects with an NAttribute stored). We rely -on this cache being stored on the format “_<fieldname>_cache”. -If Django were to change this name internally, we need to -update here (unlikely, but marking just in case).

-
- -
-
-is_typeclass(typeclass, exact=False)[source]
-

Returns true if this object has this type OR has a typeclass -which is an subclass of the given typeclass. This operates on -the actually loaded typeclass (this is important since a -failing typeclass may instead have its default currently -loaded) typeclass - can be a class object or the python path -to such an object to match against.

-
-
Parameters
-
    -
  • typeclass (str or class) – A class or the full python path -to the class to check.

  • -
  • exact (bool, optional) – Returns true only if the object’s -type is exactly this typeclass, ignoring parents.

  • -
-
-
Returns
-

is_typeclass (bool)

-
-
If this typeclass matches the given

typeclass.

-
-
-

-
-
-
- -
-
-swap_typeclass(new_typeclass, clean_attributes=False, run_start_hooks='all', no_default=True, clean_cmdsets=False)[source]
-

This performs an in-situ swap of the typeclass. This means -that in-game, this object will suddenly be something else. -Account will not be affected. To ‘move’ an account to a different -object entirely (while retaining this object’s type), use -self.account.swap_object().

-

Note that this might be an error prone operation if the -old/new typeclass was heavily customized - your code -might expect one and not the other, so be careful to -bug test your code if using this feature! Often its easiest -to create a new object and just swap the account over to -that one instead.

-
-
Parameters
-
    -
  • new_typeclass (str or classobj) – Type to switch to.

  • -
  • clean_attributes (bool or list, optional) – Will delete all -attributes stored on this object (but not any of the -database fields such as name or location). You can’t get -attributes back, but this is often the safest bet to make -sure nothing in the new typeclass clashes with the old -one. If you supply a list, only those named attributes -will be cleared.

  • -
  • run_start_hooks (str or None, optional) – This is either None, -to not run any hooks, “all” to run all hooks defined by -at_first_start, or a string giving the name of the hook -to run (for example ‘at_object_creation’). This will -always be called without arguments.

  • -
  • no_default (bool, optiona) – If set, the swapper will not -allow for swapping to a default typeclass in case the -given one fails for some reason. Instead the old one will -be preserved.

  • -
  • clean_cmdsets (bool, optional) – Delete all cmdsets on the object.

  • -
-
-
-
- -
-
-access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False, **kwargs)[source]
-

Determines if another object has permission to access this one.

-
-
Parameters
-
    -
  • accessing_obj (str) – Object trying to access this one.

  • -
  • access_type (str, optional) – Type of access sought.

  • -
  • default (bool, optional) – What to return if no lock of -access_type was found

  • -
  • no_superuser_bypass (bool, optional) – Turn off the -superuser lock bypass (be careful with this one).

  • -
-
-
Keyword Arguments
-

kwargs (any) – Ignored, but is there to make the api -consistent with the object-typeclass method access, which -use it to feed to its hook methods.

-
-
-
- -
-
-check_permstring(permstring)[source]
-

This explicitly checks if we hold particular permission -without involving any locks.

-
-
Parameters
-

permstring (str) – The permission string to check against.

-
-
Returns
-

result (bool) – If the permstring is passed or not.

-
-
-
- -
-
-delete()[source]
-

Cleaning up handlers on the typeclass level

-
- -
-
-property db
-

Attribute handler wrapper. Allows for the syntax

-
obj.db.attrname = value
-  and
-value = obj.db.attrname
-  and
-del obj.db.attrname
-  and
-all_attr = obj.db.all()
-
-
-

(unless there is an attribute named ‘all’, in which case that will be -returned instead).

-
- -
-
-property ndb
-

A non-attr_obj store (NonDataBase). Everything stored to this is -guaranteed to be cleared when a server is shutdown. Syntax is same as -for the .db property, e.g.

-
obj.ndb.attrname = value
-  and
-value = obj.ndb.attrname
-  and
-del obj.ndb.attrname
-  and
-all_attr = obj.ndb.all()
-
-
-

What makes this preferable over just assigning properties directly on -the object is that Evennia can track caching for these properties and -for example avoid wiping objects with set .ndb data on cache flushes.

-
- -
-
-get_display_name(looker, **kwargs)[source]
-

Displays the name of the object in a viewer-aware manner.

-
-
Parameters
-

looker (TypedObject, optional) – The object or account that is looking -at/getting inforamtion for this object. If not given, some -‘safe’ minimum level should be returned.

-
-
Returns
-

name (str)

-
-
A string containing the name of the object,

including the DBREF if this user is privileged to control -said object.

-
-
-

-
-
-

Notes

-

This function could be extended to change how object names -appear to users in character, but be wary. This function -does not change an object’s keys or aliases when -searching, and is expected to produce something useful for -builders.

-
- -
-
-get_extra_info(looker, **kwargs)[source]
-

Used when an object is in a list of ambiguous objects as an -additional information tag.

-

For instance, if you had potions which could have varying -levels of liquid left in them, you might want to display how -many drinks are left in each when selecting which to drop, but -not in your normal inventory listing.

-
-
Parameters
-

looker (TypedObject) – The object or account that is looking -at/getting information for this object.

-
-
Returns
-

info (str)

-
-
A string with disambiguating information,

conventionally with a leading space.

-
-
-

-
-
-
- -
-
-at_rename(oldname, newname)[source]
-

This Hook is called by @name on a successful rename.

-
-
Parameters
-
    -
  • oldname (str) – The instance’s original name.

  • -
  • newname (str) – The new name for the instance.

  • -
-
-
-
- -
-
-web_get_admin_url()[source]
-

Returns the URI path for the Django Admin page for this object.

-

ex. Account#1 = ‘/admin/accounts/accountdb/1/change/’

-
-
Returns
-

path (str) – URI path to Django Admin page for object.

-
-
-
- -
-
-classmethod web_get_create_url()[source]
-

Returns the URI path for a View that allows users to create new -instances of this object.

-
-
Returns
-

path (str) – URI path to object creation page, if defined.

-
-
-

Examples

-
Chargen = '/characters/create/'
-
-
-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-create’ would be referenced by this method.

-
url(r'characters/create/', ChargenView.as_view(), name='character-create')
-
-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

Notes

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can create new objects is the -developer’s responsibility.

-
- -
-
-web_get_detail_url()[source]
-

Returns the URI path for a View that allows users to view details for -this object.

-
-
Returns
-

path (str) – URI path to object detail page, if defined.

-
-
-

Examples

-
Oscar (Character) = '/characters/oscar/1/'
-
-
-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-detail’ would be referenced by this method.

-
CharDetailView.as_view(), name='character-detail')
-
-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

Notes

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can view this object is the -developer’s responsibility.

-
- -
-
-web_get_puppet_url()[source]
-

Returns the URI path for a View that allows users to puppet a specific -object.

-
-
Returns
-

path (str) – URI path to object puppet page, if defined.

-
-
-

Examples

-
Oscar (Character) = '/characters/oscar/1/puppet/'
-
-
-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-puppet’ would be referenced by this method.

-
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/puppet/$',
-    CharPuppetView.as_view(), name='character-puppet')
-
-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

Notes

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can view this object is the developer’s -responsibility.

-
- -
-
-web_get_update_url()[source]
-

Returns the URI path for a View that allows users to update this -object.

-
-
Returns
-

path (str) – URI path to object update page, if defined.

-
-
-

Examples

-
Oscar (Character) = '/characters/oscar/1/change/'
-
-
-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-update’ would be referenced by this method.

-
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
-    CharUpdateView.as_view(), name='character-update')
-
-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

Notes

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can modify objects is the developer’s -responsibility.

-
- -
-
-web_get_delete_url()[source]
-

Returns the URI path for a View that allows users to delete this object.

-
-
Returns
-

path (str) – URI path to object deletion page, if defined.

-
-
-

Examples

-
Oscar (Character) = '/characters/oscar/1/delete/'
-
-
-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-detail’ would be referenced by this method.

-
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$',
-    CharDeleteView.as_view(), name='character-delete')
-
-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

Notes

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can delete this object is the developer’s -responsibility.

-
- -
-
-property date_created
-

A wrapper for getting database field db_date_created.

-
- -
-
-get_absolute_url()
-

Returns the URI path for a View that allows users to view details for -this object.

-
-
Returns
-

path (str) – URI path to object detail page, if defined.

-
-
-

Examples

-
Oscar (Character) = '/characters/oscar/1/'
-
-
-

For this to work, the developer must have defined a named view somewhere -in urls.py that follows the format ‘modelname-action’, so in this case -a named view of ‘character-detail’ would be referenced by this method.

-
CharDetailView.as_view(), name='character-detail')
-
-
-

If no View has been created and defined in urls.py, returns an -HTML anchor.

-

Notes

-

This method is naive and simply returns a path. Securing access to -the actual view and limiting who can view this object is the -developer’s responsibility.

-
- -
-
-get_next_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=True, **kwargs)
-
- -
-
-get_previous_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=False, **kwargs)
-
- -
-
-property lock_storage
-

A wrapper for getting database field db_lock_storage.

-
- -
-
-path = 'evennia.typeclasses.models.TypedObject'
-
- -
-
-property typeclass_path
-

A wrapper for getting database field db_typeclass_path.

-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.typeclasses.tags.html b/docs/0.9.5/api/evennia.typeclasses.tags.html deleted file mode 100644 index cd932ea095..0000000000 --- a/docs/0.9.5/api/evennia.typeclasses.tags.html +++ /dev/null @@ -1,464 +0,0 @@ - - - - - - - - - evennia.typeclasses.tags — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.typeclasses.tags

-

Tags are entities that are attached to objects in the same way as -Attributes. But contrary to Attributes, which are unique to an -individual object, a single Tag can be attached to any number of -objects at the same time.

-

Tags are used for tagging, obviously, but the data structure is also -used for storing Aliases and Permissions. This module contains the -respective handlers.

-
-
-class evennia.typeclasses.tags.Tag(*args, **kwargs)[source]
-

Bases: django.db.models.base.Model

-

Tags are quick markers for objects in-game. An typeobject can have -any number of tags, stored via its db_tags property. Tagging -similar objects will make it easier to quickly locate the group -later (such as when implementing zones). The main advantage of -tagging as opposed to using tags is speed; a tag is very -limited in what data it can hold, and the tag key+category is -indexed for efficient lookup in the database. Tags are shared -between objects - a new tag is only created if the key+category -combination did not previously exist, making them unsuitable for -storing object-related data (for this a regular Attribute should be -used).

-

The ‘db_data’ field is intended as a documentation field for the -tag itself, such as to document what this tag+category stands for -and display that in a web interface or similar.

-

The main default use for Tags is to implement Aliases for objects. -this uses the ‘aliases’ tag category, which is also checked by the -default search functions of Evennia to allow quick searches by alias.

-
-
-db_key
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_category
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_data
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_model
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-db_tagtype
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-accountdb_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-channeldb_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-helpentry_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-msg_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-objectdb_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-objects = <django.db.models.manager.Manager object>
-
- -
-
-scriptdb_set
-

Accessor to the related objects manager on the forward and reverse sides of -a many-to-many relation.

-

In the example:

-
class Pizza(Model):
-    toppings = ManyToManyField(Topping, related_name='pizzas')
-
-
-

**Pizza.toppings** and **Topping.pizzas** are **ManyToManyDescriptor** -instances.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
- -
-
-class evennia.typeclasses.tags.TagHandler(obj)[source]
-

Bases: object

-

Generic tag-handler. Accessed via TypedObject.tags.

-
-
-__init__(obj)[source]
-

Tags are stored internally in the TypedObject.db_tags m2m -field with an tag.db_model based on the obj the taghandler is -stored on and with a tagtype given by self.handlertype

-
-
Parameters
-

obj (object) – The object on which the handler is set.

-
-
-
- -
-
-reset_cache()[source]
-

Reset the cache from the outside.

-
- -
-
-add(tag=None, category=None, data=None)[source]
-

Add a new tag to the handler.

-
-
Parameters
-
    -
  • tag (str or list) – The name of the tag to add. If a list, -add several Tags.

  • -
  • category (str, optional) – Category of Tag. None is the default category.

  • -
  • data (str, optional) – Info text about the tag(s) added. -This can not be used to store object-unique info but only -eventual info about the tag itself.

  • -
-
-
-

Notes

-

If the tag + category combination matches an already -existing Tag object, this will be re-used and no new Tag -will be created.

-
- -
-
-get(key=None, default=None, category=None, return_tagobj=False, return_list=False)[source]
-

Get the tag for the given key, category or combination of the two.

-
-
Parameters
-
    -
  • key (str or list, optional) – The tag or tags to retrieve.

  • -
  • default (any, optional) – The value to return in case of no match.

  • -
  • category (str, optional) – The Tag category to limit the -request to. Note that None is the valid, default -category. If no key is given, all tags of this category will be -returned.

  • -
  • return_tagobj (bool, optional) – Return the Tag object itself -instead of a string representation of the Tag.

  • -
  • return_list (bool, optional) – Always return a list, regardless -of number of matches.

  • -
-
-
Returns
-

tags (list)

-
-
The matches, either string

representations of the tags or the Tag objects themselves -depending on return_tagobj. If ‘default’ is set, this -will be a list with the default value as its only element.

-
-
-

-
-
-
- -
-
-remove(key=None, category=None)[source]
-

Remove a tag from the handler based ond key and/or category.

-
-
Parameters
-
    -
  • key (str or list, optional) – The tag or tags to retrieve.

  • -
  • category (str, optional) – The Tag category to limit the -request to. Note that None is the valid, default -category

  • -
-
-
-

Notes

-

If neither key nor category is specified, this acts -as .clear().

-
- -
-
-clear(category=None)[source]
-

Remove all tags from the handler.

-
-
Parameters
-

category (str, optional) – The Tag category to limit the -request to. Note that None is the valid, default -category.

-
-
-
- -
-
-all(return_key_and_category=False, return_objs=False)[source]
-

Get all tags in this handler, regardless of category.

-
-
Parameters
-
    -
  • return_key_and_category (bool, optional) – Return a list of -tuples [(key, category), …].

  • -
  • return_objs (bool, optional) – Return tag objects.

  • -
-
-
Returns
-

tags (list)

-
-
A list of tag keys [tagkey, tagkey, …] or

a list of tuples [(key, category), …] if -return_key_and_category is set.

-
-
-

-
-
-
- -
-
-batch_add(*args)[source]
-

Batch-add tags from a list of tuples.

-
-
Parameters
-

*args (tuple or str) – Each argument should be a tagstr keys or tuple (keystr, category) or -(keystr, category, data). It’s possible to mix input types.

-
-
-

Notes

-

This will generate a mimimal number of self.add calls, -based on the number of categories involved (including -None) (data is not unique and may be overwritten by the content -of a latter tuple with the same category).

-
- -
- -
-
-class evennia.typeclasses.tags.AliasHandler(obj)[source]
-

Bases: evennia.typeclasses.tags.TagHandler

-

A handler for the Alias Tag type.

-
- -
-
-class evennia.typeclasses.tags.PermissionHandler(obj)[source]
-

Bases: evennia.typeclasses.tags.TagHandler

-

A handler for the Permission Tag type.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.ansi.html b/docs/0.9.5/api/evennia.utils.ansi.html deleted file mode 100644 index 73b923a26d..0000000000 --- a/docs/0.9.5/api/evennia.utils.ansi.html +++ /dev/null @@ -1,915 +0,0 @@ - - - - - - - - - evennia.utils.ansi — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.ansi

-

ANSI - Gives colour to text.

-

Use the codes defined in ANSIPARSER in your text to apply colour to text -according to the ANSI standard.

-

Examples:

-
"This is |rRed text|n and this is normal again."
-
-
-

Mostly you should not need to call parse_ansi() explicitly; it is run by -Evennia just before returning data to/from the user. Depreciated example forms -are available by extending the ansi mapping.

-
-
-class evennia.utils.ansi.ANSIParser[source]
-

Bases: object

-

A class that parses ANSI markup to ANSI command sequences.

-

We also allow to escape colour codes by prepending with an extra |.

-
-
-ansi_map = [('|n', '\x1b[0m'), ('|/', '\r\n'), ('|-', '\t'), ('|>', ' '), ('|_', ' '), ('|*', '\x1b[7m'), ('|^', '\x1b[5m'), ('|u', '\x1b[4m'), ('|r', '\x1b[1m\x1b[31m'), ('|g', '\x1b[1m\x1b[32m'), ('|y', '\x1b[1m\x1b[33m'), ('|b', '\x1b[1m\x1b[34m'), ('|m', '\x1b[1m\x1b[35m'), ('|c', '\x1b[1m\x1b[36m'), ('|w', '\x1b[1m\x1b[37m'), ('|x', '\x1b[1m\x1b[30m'), ('|R', '\x1b[22m\x1b[31m'), ('|G', '\x1b[22m\x1b[32m'), ('|Y', '\x1b[22m\x1b[33m'), ('|B', '\x1b[22m\x1b[34m'), ('|M', '\x1b[22m\x1b[35m'), ('|C', '\x1b[22m\x1b[36m'), ('|W', '\x1b[22m\x1b[37m'), ('|X', '\x1b[22m\x1b[30m'), ('|h', '\x1b[1m'), ('|H', '\x1b[22m'), ('|!R', '\x1b[31m'), ('|!G', '\x1b[32m'), ('|!Y', '\x1b[33m'), ('|!B', '\x1b[34m'), ('|!M', '\x1b[35m'), ('|!C', '\x1b[36m'), ('|!W', '\x1b[37m'), ('|!X', '\x1b[30m'), ('|[R', '\x1b[41m'), ('|[G', '\x1b[42m'), ('|[Y', '\x1b[43m'), ('|[B', '\x1b[44m'), ('|[M', '\x1b[45m'), ('|[C', '\x1b[46m'), ('|[W', '\x1b[47m'), ('|[X', '\x1b[40m')]
-
- -
-
-ansi_xterm256_bright_bg_map = [('|[r', '|[500'), ('|[g', '|[050'), ('|[y', '|[550'), ('|[b', '|[005'), ('|[m', '|[505'), ('|[c', '|[055'), ('|[w', '|[555'), ('|[x', '|[222')]
-
- -
-
-xterm256_fg = ['\\|([0-5])([0-5])([0-5])']
-
- -
-
-xterm256_bg = ['\\|\\[([0-5])([0-5])([0-5])']
-
- -
-
-xterm256_gfg = ['\\|=([a-z])']
-
- -
-
-xterm256_gbg = ['\\|\\[=([a-z])']
-
- -
-
-mxp_re = '\\|lc(.*?)\\|lt(.*?)\\|le'
-
- -
-
-brightbg_sub = re.compile('(?<!\\|)\\|\\[r|(?<!\\|)\\|\\[g|(?<!\\|)\\|\\[y|(?<!\\|)\\|\\[b|(?<!\\|)\\|\\[m|(?<!\\|)\\|\\[c|(?<!\\|)\\|\\[w|(?<!\\|)\\|\\[x', re.DOTALL)
-
- -
-
-xterm256_fg_sub = re.compile('\\|([0-5])([0-5])([0-5])', re.DOTALL)
-
- -
-
-xterm256_bg_sub = re.compile('\\|\\[([0-5])([0-5])([0-5])', re.DOTALL)
-
- -
-
-xterm256_gfg_sub = re.compile('\\|=([a-z])', re.DOTALL)
-
- -
-
-xterm256_gbg_sub = re.compile('\\|\\[=([a-z])', re.DOTALL)
-
- -
-
-ansi_sub = re.compile('\\|n|\\|/|\\|\\-|\\|>|\\|_|\\|\\*|\\|\\^|\\|u|\\|r|\\|g|\\|y|\\|b|\\|m|\\|c|\\|w|\\|x|\\|R|\\|G|\\|Y|\\|B|\\|M|\\|C|\\|W|\\|X|\\|h|\\|H|\\|!R|\\|!G|\\|!Y|\\|!B|\\|!M|\\|!C|\\|!W|\\|!X|\\|\\[R|\\|\\[G, re.DOTALL)
-
- -
-
-mxp_sub = re.compile('\\|lc(.*?)\\|lt(.*?)\\|le', re.DOTALL)
-
- -
-
-ansi_map_dict = {'|!B': '\x1b[34m', '|!C': '\x1b[36m', '|!G': '\x1b[32m', '|!M': '\x1b[35m', '|!R': '\x1b[31m', '|!W': '\x1b[37m', '|!X': '\x1b[30m', '|!Y': '\x1b[33m', '|*': '\x1b[7m', '|-': '\t', '|/': '\r\n', '|>': ' ', '|B': '\x1b[22m\x1b[34m', '|C': '\x1b[22m\x1b[36m', '|G': '\x1b[22m\x1b[32m', '|H': '\x1b[22m', '|M': '\x1b[22m\x1b[35m', '|R': '\x1b[22m\x1b[31m', '|W': '\x1b[22m\x1b[37m', '|X': '\x1b[22m\x1b[30m', '|Y': '\x1b[22m\x1b[33m', '|[B': '\x1b[44m', '|[C': '\x1b[46m', '|[G': '\x1b[42m', '|[M': '\x1b[45m', '|[R': '\x1b[41m', '|[W': '\x1b[47m', '|[X': '\x1b[40m', '|[Y': '\x1b[43m', '|^': '\x1b[5m', '|_': ' ', '|b': '\x1b[1m\x1b[34m', '|c': '\x1b[1m\x1b[36m', '|g': '\x1b[1m\x1b[32m', '|h': '\x1b[1m', '|m': '\x1b[1m\x1b[35m', '|n': '\x1b[0m', '|r': '\x1b[1m\x1b[31m', '|u': '\x1b[4m', '|w': '\x1b[1m\x1b[37m', '|x': '\x1b[1m\x1b[30m', '|y': '\x1b[1m\x1b[33m'}
-
- -
-
-ansi_xterm256_bright_bg_map_dict = {'|[b': '|[005', '|[c': '|[055', '|[g': '|[050', '|[m': '|[505', '|[r': '|[500', '|[w': '|[555', '|[x': '|[222', '|[y': '|[550'}
-
- -
-
-ansi_re = '\\033\\[[0-9;]+m'
-
- -
-
-ansi_regex = re.compile('\\033\\[[0-9;]+m')
-
- -
-
-ansi_escapes = re.compile('({{|\\\\|\\|\\|)', re.DOTALL)
-
- -
-
-sub_ansi(ansimatch)[source]
-

Replacer used by re.sub to replace ANSI -markers with correct ANSI sequences

-
-
Parameters
-

ansimatch (re.matchobject) – The match.

-
-
Returns
-

processed (str) – The processed match string.

-
-
-
- -
-
-sub_brightbg(ansimatch)[source]
-

Replacer used by re.sub to replace ANSI -bright background markers with Xterm256 replacement

-
-
Parameters
-

ansimatch (re.matchobject) – The match.

-
-
Returns
-

processed (str) – The processed match string.

-
-
-
- -
-
-sub_xterm256(rgbmatch, use_xterm256=False, color_type='fg')[source]
-

This is a replacer method called by re.sub with the matched -tag. It must return the correct ansi sequence.

-

It checks self.do_xterm256 to determine if conversion -to standard ANSI should be done or not.

-
-
Parameters
-
    -
  • rgbmatch (re.matchobject) – The match.

  • -
  • use_xterm256 (bool, optional) – Don’t convert 256-colors to 16.

  • -
  • color_type (str) – One of ‘fg’, ‘bg’, ‘gfg’, ‘gbg’.

  • -
-
-
Returns
-

processed (str) – The processed match string.

-
-
-
- -
-
-strip_raw_codes(string)[source]
-

Strips raw ANSI codes from a string.

-
-
Parameters
-

string (str) – The string to strip.

-
-
Returns
-

string (str) – The processed string.

-
-
-
- -
-
-strip_mxp(string)[source]
-

Strips all MXP codes from a string.

-
-
Parameters
-

string (str) – The string to strip.

-
-
Returns
-

string (str) – The processed string.

-
-
-
- -
-
-parse_ansi(string, strip_ansi=False, xterm256=False, mxp=False)[source]
-

Parses a string, subbing color codes according to the stored -mapping.

-
-
Parameters
-
    -
  • string (str) – The string to parse.

  • -
  • strip_ansi (boolean, optional) – Strip all found ansi markup.

  • -
  • xterm256 (boolean, optional) – If actually using xterm256 or if -these values should be converted to 16-color ANSI.

  • -
  • mxp (boolean, optional) – Parse MXP commands in string.

  • -
-
-
Returns
-

string (str) – The parsed string.

-
-
-
- -
- -
-
-evennia.utils.ansi.parse_ansi(string, strip_ansi=False, parser=<evennia.utils.ansi.ANSIParser object>, xterm256=False, mxp=False)[source]
-

Parses a string, subbing color codes as needed.

-
-
Parameters
-
    -
  • string (str) – The string to parse.

  • -
  • strip_ansi (bool, optional) – Strip all ANSI sequences.

  • -
  • parser (ansi.AnsiParser, optional) – A parser instance to use.

  • -
  • xterm256 (bool, optional) – Support xterm256 or not.

  • -
  • mxp (bool, optional) – Support MXP markup or not.

  • -
-
-
Returns
-

string (str) – The parsed string.

-
-
-
- -
-
-evennia.utils.ansi.strip_ansi(string, parser=<evennia.utils.ansi.ANSIParser object>)[source]
-

Strip all ansi from the string. This handles the Evennia-specific -markup.

-
-
Parameters
-
    -
  • string (str) – The string to strip.

  • -
  • parser (ansi.AnsiParser, optional) – The parser to use.

  • -
-
-
Returns
-

string (str) – The stripped string.

-
-
-
- -
-
-evennia.utils.ansi.strip_raw_ansi(string, parser=<evennia.utils.ansi.ANSIParser object>)[source]
-

Remove raw ansi codes from string. This assumes pure -ANSI-bytecodes in the string.

-
-
Parameters
-
    -
  • string (str) – The string to parse.

  • -
  • parser (bool, optional) – The parser to use.

  • -
-
-
Returns
-

string (str) – the stripped string.

-
-
-
- -
-
-evennia.utils.ansi.raw(string)[source]
-

Escapes a string into a form which won’t be colorized by the ansi -parser.

-
-
Returns
-

string (str) – The raw, escaped string.

-
-
-
- -
-
-class evennia.utils.ansi.ANSIMeta(*args, **kwargs)[source]
-

Bases: type

-

Many functions on ANSIString are just light wrappers around the string -base class. We apply them here, as part of the classes construction.

-
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
- -
-
-class evennia.utils.ansi.ANSIString(*args, **kwargs)[source]
-

Bases: str

-

Unicode-like object that is aware of ANSI codes.

-

This class can be used nearly identically to strings, in that it will -report string length, handle slices, etc, much like a string object -would. The methods should be used identically as string methods are.

-

There is at least one exception to this (and there may be more, though -they have not come up yet). When using ‘’.join() or u’’.join() on an -ANSIString, color information will get lost. You must use -ANSIString(‘’).join() to preserve color information.

-

This implementation isn’t perfectly clean, as it doesn’t really have an -understanding of what the codes mean in order to eliminate -redundant characters– though cleaning up the strings might end up being -inefficient and slow without some C code when dealing with larger values. -Such enhancements could be made as an enhancement to ANSI_PARSER -if needed, however.

-

If one is going to use ANSIString, one should generally avoid converting -away from it until one is about to send information on the wire. This is -because escape sequences in the string may otherwise already be decoded, -and taken literally the second time around.

-
-
-re_format = re.compile('(?i)(?P<just>(?P<fill>.)?(?P<align>\\<|\\>|\\=|\\^))?(?P<sign>\\+|\\-| )?(?P<alt>\\#)?(?P<zero>0)?(?P<width>\\d+)?(?P<grouping>\\_|\\,)?(?:\\.(?P<precision>\\d+))?(?P<type>b|c|d|e|E|f|F|g|G|n|o|s|x|X, re.IGNORECASE)
-
- -
-
-__init__(*_, **kwargs)[source]
-

When the ANSIString is first initialized, a few internal variables -have to be set.

-

The first is the parser. It is possible to replace Evennia’s standard -ANSI parser with one of your own syntax if you wish, so long as it -implements the same interface.

-

The second is the _raw_string. This is the original “dumb” string -with ansi escapes that ANSIString represents.

-

The third thing to set is the _clean_string. This is a string that is -devoid of all ANSI Escapes.

-

Finally, _code_indexes and _char_indexes are defined. These are lookup -tables for which characters in the raw string are related to ANSI -escapes, and which are for the readable text.

-
- -
-
-clean()[source]
-

Return a string object without the ANSI escapes.

-
-
Returns
-

clean_string (str) – A unicode object with no ANSI escapes.

-
-
-
- -
-
-raw()[source]
-

Return a string object with the ANSI escapes.

-
-
Returns
-

raw (str) – A unicode object with the raw ANSI escape sequences.

-
-
-
- -
-
-partition(sep, reverse=False)[source]
-

Splits once into three sections (with the separator being the middle section)

-

We use the same techniques we used in split() to make sure each are -colored.

-
-
Parameters
-
    -
  • sep (str) – The separator to split the string on.

  • -
  • reverse (boolean) – Whether to split the string on the last -occurrence of the separator rather than the first.

  • -
-
-
Returns
-

ANSIString – The part of the string before the separator -ANSIString: The separator itself -ANSIString: The part of the string after the separator.

-
-
-
- -
-
-split(by=None, maxsplit=- 1)[source]
-

Splits a string based on a separator.

-

Stolen from PyPy’s pure Python string implementation, tweaked for -ANSIString.

-

PyPy is distributed under the MIT licence. -http://opensource.org/licenses/MIT

-
-
Parameters
-
    -
  • by (str) – A string to search for which will be used to split -the string. For instance, ‘,’ for ‘Hello,world’ would -result in [‘Hello’, ‘world’]

  • -
  • maxsplit (int) – The maximum number of times to split the string. -For example, a maxsplit of 2 with a by of ‘,’ on the string -‘Hello,world,test,string’ would result in -[‘Hello’, ‘world’, ‘test,string’]

  • -
-
-
Returns
-

result (list of ANSIStrings)

-
-
A list of ANSIStrings derived from

this string.

-
-
-

-
-
-
- -
-
-rsplit(by=None, maxsplit=- 1)[source]
-

Like split, but starts from the end of the string rather than the -beginning.

-

Stolen from PyPy’s pure Python string implementation, tweaked for -ANSIString.

-

PyPy is distributed under the MIT licence. -http://opensource.org/licenses/MIT

-
-
Parameters
-
    -
  • by (str) – A string to search for which will be used to split -the string. For instance, ‘,’ for ‘Hello,world’ would -result in [‘Hello’, ‘world’]

  • -
  • maxsplit (int) – The maximum number of times to split the string. -For example, a maxsplit of 2 with a by of ‘,’ on the string -‘Hello,world,test,string’ would result in -[‘Hello,world’, ‘test’, ‘string’]

  • -
-
-
Returns
-

result (list of ANSIStrings)

-
-
A list of ANSIStrings derived from

this string.

-
-
-

-
-
-
- -
-
-strip(chars=None)[source]
-

Strip from both ends, taking ANSI markers into account.

-
-
Parameters
-

chars (str, optional) – A string containing individual characters -to strip off of both ends of the string. By default, any blank -spaces are trimmed.

-
-
Returns
-

result (ANSIString)

-
-
A new ANSIString with the ends trimmed of the

relevant characters.

-
-
-

-
-
-
- -
-
-lstrip(chars=None)[source]
-

Strip from the left, taking ANSI markers into account.

-
-
Parameters
-

chars (str, optional) – A string containing individual characters -to strip off of the left end of the string. By default, any -blank spaces are trimmed.

-
-
Returns
-

result (ANSIString)

-
-
A new ANSIString with the left end trimmed of

the relevant characters.

-
-
-

-
-
-
- -
-
-capitalize(*args, **kwargs)
-

Return a capitalized version of the string.

-

More specifically, make the first character have upper case and the rest lower -case.

-
- -
-
-count(sub[, start[, end]]) → int
-

Return the number of non-overlapping occurrences of substring sub in -string S[start:end]. Optional arguments start and end are -interpreted as in slice notation.

-
- -
-
-decode(*args, **kwargs)
-
- -
-
-encode(*args, **kwargs)
-

Encode the string using the codec registered for encoding.

-
-
encoding

The encoding in which to encode the string.

-
-
errors

The error handling scheme to use for encoding errors. -The default is ‘strict’ meaning that encoding errors raise a -UnicodeEncodeError. Other possible values are ‘ignore’, ‘replace’ and -‘xmlcharrefreplace’ as well as any other name registered with -codecs.register_error that can handle UnicodeEncodeErrors.

-
-
-
- -
-
-endswith(suffix[, start[, end]]) → bool
-

Return True if S ends with the specified suffix, False otherwise. -With optional start, test S beginning at that position. -With optional end, stop comparing S at that position. -suffix can also be a tuple of strings to try.

-
- -
-
-expandtabs(*args, **kwargs)
-

Return a copy where all tab characters are expanded using spaces.

-

If tabsize is not given, a tab size of 8 characters is assumed.

-
- -
-
-find(sub[, start[, end]]) → int
-

Return the lowest index in S where substring sub is found, -such that sub is contained within S[start:end]. Optional -arguments start and end are interpreted as in slice notation.

-

Return -1 on failure.

-
- -
-
-format(*args, **kwargs) → str
-

Return a formatted version of S, using substitutions from args and kwargs. -The substitutions are identified by braces (‘{’ and ‘}’).

-
- -
-
-index(sub[, start[, end]]) → int
-

Return the lowest index in S where substring sub is found, -such that sub is contained within S[start:end]. Optional -arguments start and end are interpreted as in slice notation.

-

Raises ValueError when the substring is not found.

-
- -
-
-isalnum(*args, **kwargs)
-

Return True if the string is an alpha-numeric string, False otherwise.

-

A string is alpha-numeric if all characters in the string are alpha-numeric and -there is at least one character in the string.

-
- -
-
-isalpha(*args, **kwargs)
-

Return True if the string is an alphabetic string, False otherwise.

-

A string is alphabetic if all characters in the string are alphabetic and there -is at least one character in the string.

-
- -
-
-isdigit(*args, **kwargs)
-

Return True if the string is a digit string, False otherwise.

-

A string is a digit string if all characters in the string are digits and there -is at least one character in the string.

-
- -
-
-islower(*args, **kwargs)
-

Return True if the string is a lowercase string, False otherwise.

-

A string is lowercase if all cased characters in the string are lowercase and -there is at least one cased character in the string.

-
- -
-
-isspace(*args, **kwargs)
-

Return True if the string is a whitespace string, False otherwise.

-

A string is whitespace if all characters in the string are whitespace and there -is at least one character in the string.

-
- -
-
-istitle(*args, **kwargs)
-

Return True if the string is a title-cased string, False otherwise.

-

In a title-cased string, upper- and title-case characters may only -follow uncased characters and lowercase characters only cased ones.

-
- -
-
-isupper(*args, **kwargs)
-

Return True if the string is an uppercase string, False otherwise.

-

A string is uppercase if all cased characters in the string are uppercase and -there is at least one cased character in the string.

-
- -
-
-lower(*args, **kwargs)
-

Return a copy of the string converted to lowercase.

-
- -
-
-replace(*args, **kwargs)
-

Return a copy with all occurrences of substring old replaced by new.

-
-
-
count

Maximum number of occurrences to replace. --1 (the default value) means replace all occurrences.

-
-
-
-

If the optional argument count is given, only the first count occurrences are -replaced.

-
- -
-
-rfind(sub[, start[, end]]) → int
-

Return the highest index in S where substring sub is found, -such that sub is contained within S[start:end]. Optional -arguments start and end are interpreted as in slice notation.

-

Return -1 on failure.

-
- -
-
-rindex(sub[, start[, end]]) → int
-

Return the highest index in S where substring sub is found, -such that sub is contained within S[start:end]. Optional -arguments start and end are interpreted as in slice notation.

-

Raises ValueError when the substring is not found.

-
- -
-
-rstrip(chars=None)[source]
-

Strip from the right, taking ANSI markers into account.

-
-
Parameters
-

chars (str, optional) – A string containing individual characters -to strip off of the right end of the string. By default, any -blank spaces are trimmed.

-
-
Returns
-

result (ANSIString)

-
-
A new ANSIString with the right end trimmed of

the relevant characters.

-
-
-

-
-
-
- -
-
-startswith(prefix[, start[, end]]) → bool
-

Return True if S starts with the specified prefix, False otherwise. -With optional start, test S beginning at that position. -With optional end, stop comparing S at that position. -prefix can also be a tuple of strings to try.

-
- -
-
-swapcase(*args, **kwargs)
-

Convert uppercase characters to lowercase and lowercase characters to uppercase.

-
- -
-
-translate(*args, **kwargs)
-

Replace each character in the string using the given translation table.

-
-
-
table

Translation table, which must be a mapping of Unicode ordinals to -Unicode ordinals, strings, or None.

-
-
-
-

The table must implement lookup/indexing via __getitem__, for instance a -dictionary or list. If this operation raises LookupError, the character is -left untouched. Characters mapped to None are deleted.

-
- -
-
-upper(*args, **kwargs)
-

Return a copy of the string converted to uppercase.

-
- -
-
-join(iterable)[source]
-

Joins together strings in an iterable, using this string between each -one.

-

NOTE: This should always be used for joining strings when ANSIStrings -are involved. Otherwise color information will be discarded by python, -due to details in the C implementation of strings.

-
-
Parameters
-

iterable (list of strings) – A list of strings to join together

-
-
Returns
-

ANSIString

-
-
A single string with all of the iterable’s

contents concatenated, with this string between each.

-
-
-

-
-
-

Examples

-
>>> ANSIString(', ').join(['up', 'right', 'left', 'down'])
-ANSIString('up, right, left, down')
-
-
-
- -
-
-center(width, fillchar, _difference)[source]
-

Center some text with some spaces padding both sides.

-
-
Parameters
-
    -
  • width (int) – The target width of the output string.

  • -
  • fillchar (str) – A single character string to pad the output string -with.

  • -
-
-
Returns
-

result (ANSIString) – A string padded on both ends with fillchar.

-
-
-
- -
-
-ljust(width, fillchar, _difference)[source]
-

Left justify some text.

-
-
Parameters
-
    -
  • width (int) – The target width of the output string.

  • -
  • fillchar (str) – A single character string to pad the output string -with.

  • -
-
-
Returns
-

result (ANSIString) – A string padded on the right with fillchar.

-
-
-
- -
-
-rjust(width, fillchar, _difference)[source]
-

Right justify some text.

-
-
Parameters
-
    -
  • width (int) – The target width of the output string.

  • -
  • fillchar (str) – A single character string to pad the output string -with.

  • -
-
-
Returns
-

result (ANSIString) – A string padded on the left with fillchar.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.batchprocessors.html b/docs/0.9.5/api/evennia.utils.batchprocessors.html deleted file mode 100644 index b628158ac9..0000000000 --- a/docs/0.9.5/api/evennia.utils.batchprocessors.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - evennia.utils.batchprocessors — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.batchprocessors

-

This module contains the core methods for the Batch-command- and -Batch-code-processors respectively. In short, these are two different -ways to build a game world using a normal text-editor without having -to do so ‘on the fly’ in-game. They also serve as an automatic backup -so you can quickly recreate a world also after a server reset. The -functions in this module is meant to form the backbone of a system -called and accessed through game commands.

-

The Batch-command processor is the simplest. It simply runs a list of -in-game commands in sequence by reading them from a text file. The -advantage of this is that the builder only need to remember the normal -in-game commands. They are also executing with full permission checks -etc, making it relatively safe for builders to use. The drawback is -that in-game there is really a builder-character walking around -building things, and it can be important to create rooms and objects -in the right order, so the character can move between them. Also -objects that affects players (such as mobs, dark rooms etc) will -affect the building character too, requiring extra care to turn -off/on.

-

The Batch-code processor is a more advanced system that accepts full -Python code, executing in chunks. The advantage of this is much more -power; practically anything imaginable can be coded and handled using -the batch-code processor. There is no in-game character that moves and -that can be affected by what is being built - the database is -populated on the fly. The drawback is safety and entry threshold - the -code is executed as would any server code, without mud-specific -permission-checks, and you have full access to modifying objects -etc. You also need to know Python and Evennia’s API. Hence it’s -recommended that the batch-code processor is limited only to -superusers or highly trusted staff.

-
-

Batch-Command processor file syntax

-

The batch-command processor accepts ‘batchcommand files’ e.g -batch.ev, containing a sequence of valid Evennia commands in a -simple format. The engine runs each command in sequence, as if they -had been run at the game prompt.

-

Each Evennia command must be delimited by a line comment to mark its -end. This way entire game worlds can be created and planned offline; it is -especially useful in order to create long room descriptions where a -real offline text editor is often much better than any online text -editor or prompt.

-

There is only one batchcommand-specific entry to use in a batch-command -files (all others are just like in-game commands):

-
    -
  • #INSERT path.batchcmdfile - this as the first entry on a line will -import and run a batch.ev file in this position, as if it was -written in this file.

  • -
-

Example of batch.ev file:

-
# batch file
-# all lines starting with # are comments; they also indicate
-# that a command definition is over.
-
-@create box
-
-# this comment ends the @create command.
-
-@set box/desc = A large box.
-
-Inside are some scattered piles of clothing.
-
-
-It seems the bottom of the box is a bit loose.
-
-# Again, this comment indicates the @set command is over. Note how
-# the description could be freely added. Excess whitespace on a line
-# is ignored.  An empty line in the command definition is parsed as a
-
-# (so two empty lines becomes a new paragraph).
-
-@teleport #221
-
-# (Assuming #221 is a warehouse or something.)
-# (remember, this comment ends the @teleport command! Don'f forget it)
-
-# Example of importing another file at this point.
-#INSERT examples.batch
-
-@drop box
-
-# Done, the box is in the warehouse! (this last comment is not necessary to
-# close the @drop command since it's the end of the file)
-
-
-

An example batch file is contrib/examples/batch_example.ev.

-
-
-

Batch-Code processor file syntax

-

The Batch-code processor accepts full python modules (e.g. batch.py) -that looks identical to normal Python files. The difference from -importing and running any Python module is that the batch-code module -is loaded as a file and executed directly, so changes to the file will -apply immediately without a server @reload.

-

Optionally, one can add some special commented tokens to split the -execution of the code for the benefit of the batchprocessor’s -interactive- and debug-modes. This allows to conveniently step through -the code and re-run sections of it easily during development.

-

Code blocks are marked by commented tokens alone on a line:

-
    -
  • #HEADER - This denotes code that should be pasted at the top of all -other code. Multiple HEADER statements - regardless of where -it exists in the file - is the same as one big block. -Observe that changes to variables made in one block is not -preserved between blocks!

  • -
  • #CODE - This designates a code block that will be executed like a -stand-alone piece of code together with any HEADER(s) -defined. It is mainly used as a way to mark stop points for -the interactive mode of the batchprocessor. If no CODE block -is defined in the module, the entire module (including HEADERS) -is assumed to be a CODE block.

  • -
  • #INSERT path.filename - This imports another batch_code.py file and -runs it in the given position. The inserted file will retain -its own HEADERs which will not be mixed with the headers of -this file.

  • -
-

Importing works as normal. The following variables are automatically -made available in the script namespace.

-
    -
  • caller - The object executing the batchscript

  • -
  • DEBUG - This is a boolean marking if the batchprocessor is running -in debug mode. It can be checked to e.g. delete created objects -when running a CODE block multiple times during testing. -(avoids creating a slew of same-named db objects)

  • -
-

Example batch.py file:

-
#HEADER
-
-from django.conf import settings
-from evennia.utils import create
-from types import basetypes
-
-GOLD = 10
-
-#CODE
-
-obj = create.create_object(basetypes.Object)
-obj2 = create.create_object(basetypes.Object)
-obj.location = caller.location
-obj.db.gold = GOLD
-caller.msg("The object was created!")
-
-if DEBUG:
-    obj.delete()
-    obj2.delete()
-
-#INSERT another_batch_file
-
-#CODE
-
-script = create.create_script()
-
-
-
-
-
-evennia.utils.batchprocessors.read_batchfile(pythonpath, file_ending='.py')[source]
-

This reads the contents of a batch-file. Filename is considered -to be a python path to a batch file relative the directory -specified in settings.py.

-

file_ending specify which batchfile ending should be assumed (.ev -or .py). The ending should not be included in the python path.

-
-
Parameters
-
    -
  • pythonpath (str) – A dot-python path to a file.

  • -
  • file_ending (str) – The file ending of this file (.ev or .py)

  • -
-
-
Returns
-

str – The text content of the batch file.

-
-
Raises
-

IOError – If problems reading file.

-
-
-
- -
-
-class evennia.utils.batchprocessors.BatchCommandProcessor[source]
-

Bases: object

-

This class implements a batch-command processor.

-
-
-parse_file(pythonpath)[source]
-

This parses the lines of a batchfile according to the following -rules:

-
    -
  1. # at the beginning of a line marks the end of the command before -it. It is also a comment and any number of # can exist on -subsequent lines (but not inside comments).

  2. -
  3. #INSERT at the beginning of a line imports another -batch-cmd file file and pastes it into the batch file as if -it was written there.

  4. -
  5. Commands are placed alone at the beginning of a line and their -arguments are considered to be everything following (on any -number of lines) until the next comment line beginning with #.

  6. -
  7. Newlines are ignored in command definitions

  8. -
  9. A completely empty line in a command line definition is condered -a newline (so two empty lines is a paragraph).

  10. -
  11. Excess spaces and indents inside arguments are stripped.

  12. -
-
- -
- -
-
-evennia.utils.batchprocessors.tb_filename(tb)[source]
-

Helper to get filename from traceback

-
- -
-
-evennia.utils.batchprocessors.tb_iter(tb)[source]
-

Traceback iterator.

-
- -
-
-class evennia.utils.batchprocessors.BatchCodeProcessor[source]
-

Bases: object

-

This implements a batch-code processor

-
-
-parse_file(pythonpath)[source]
-

This parses the lines of a batchfile according to the following -rules:

-
-
Parameters
-

pythonpath (str) – The dot-python path to the file.

-
-
Returns
-

codeblocks (list)

-
-
A list of all #CODE blocks, each with

prepended #HEADER data. If no #CODE blocks were found, -this will be a list of one element.

-
-
-

-
-
-

Notes

-
    -
  1. -
    Code before a #CODE/HEADER block are considered part of

    the first code/header block or is the ONLY block if no -#CODE/HEADER blocks are defined.

    -
    -
    -
  2. -
  3. Lines starting with #HEADER starts a header block (ends other blocks)

  4. -
  5. Lines starting with #CODE begins a code block (ends other blocks)

  6. -
  7. Lines starting with #INSERT are on form #INSERT filename. Code from -this file are processed with their headers separately before -being inserted at the point of the #INSERT.

  8. -
  9. Code after the last block is considered part of the last header/code -block

  10. -
-
- -
-
-code_exec(code, extra_environ=None, debug=False)[source]
-

Execute a single code block, including imports and appending -global vars.

-
-
Parameters
-
    -
  • code (str) – Code to run.

  • -
  • extra_environ (dict) – Environment variables to run with code.

  • -
  • debug (bool, optional) – Set the DEBUG variable in the execution -namespace.

  • -
-
-
Returns
-

err (str or None) – An error code or None (ok).

-
-
-
- -
- -
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.containers.html b/docs/0.9.5/api/evennia.utils.containers.html deleted file mode 100644 index 3cd3aed731..0000000000 --- a/docs/0.9.5/api/evennia.utils.containers.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - - - - evennia.utils.containers — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.containers

-

Containers

-

Containers are storage classes usually initialized from a setting. They -represent Singletons and acts as a convenient place to find resources ( -available as properties on the singleton)

-

evennia.GLOBAL_SCRIPTS -evennia.OPTION_CLASSES

-
-
-class evennia.utils.containers.Container[source]
-

Bases: object

-

Base container class. A container is simply a storage object whose -properties can be acquired as a property on it. This is generally -considered a read-only affair.

-

The container is initialized by a list of modules containing callables.

-
-
-storage_modules = []
-
- -
-
-__init__()[source]
-

Read data from module.

-
- -
-
-load_data()[source]
-

Delayed import to avoid eventual circular imports from inside -the storage modules.

-
- -
-
-get(key, default=None)[source]
-

Retrive data by key (in case of not knowing it beforehand).

-
-
Parameters
-
    -
  • key (str) – The name of the script.

  • -
  • default (any, optional) – Value to return if key is not found.

  • -
-
-
Returns
-

any (any) – The data loaded on this container.

-
-
-
- -
-
-all()[source]
-

Get all stored data

-
-
Returns
-

scripts (list) – All global script objects stored on the container.

-
-
-
- -
- -
-
-class evennia.utils.containers.OptionContainer[source]
-

Bases: evennia.utils.containers.Container

-

Loads and stores the final list of OPTION CLASSES.

-

Can access these as properties or dictionary-contents.

-
-
-storage_modules = ['evennia.utils.optionclasses']
-
- -
- -
-
-class evennia.utils.containers.GlobalScriptContainer[source]
-

Bases: evennia.utils.containers.Container

-

Simple Handler object loaded by the Evennia API to contain and manage a -game’s Global Scripts. This will list global Scripts created on their own -but will also auto-(re)create scripts defined in settings.GLOBAL_SCRIPTS.

-

Example

-

import evennia -evennia.GLOBAL_SCRIPTS.scriptname

-
-

Note

-

This does not use much of the BaseContainer since it’s not loading -callables from settings but a custom dict of tuples.

-
-
-
-__init__()[source]
-

Note: We must delay loading of typeclasses since this module may get -initialized before Scripts are actually initialized.

-
- -
-
-start()[source]
-

Called last in evennia.__init__ to initialize the container late -(after script typeclasses have finished loading).

-

We include all global scripts in the handler and -make sure to auto-load time-based scripts.

-
- -
-
-load_data()[source]
-

This delayed import avoids trying to load Scripts before they are -initialized.

-
- -
-
-get(key, default=None)[source]
-

Retrive data by key (in case of not knowing it beforehand). Any -scripts that are in settings.GLOBAL_SCRIPTS that are not found -will be recreated on-demand.

-
-
Parameters
-
    -
  • key (str) – The name of the script.

  • -
  • default (any, optional) – Value to return if key is not found -at all on this container (i.e it cannot be loaded at all).

  • -
-
-
Returns
-

any (any) – The data loaded on this container.

-
-
-
- -
-
-all()[source]
-

Get all global scripts. Note that this will not auto-start -scripts defined in settings.

-
-
Returns
-

scripts (list) – All global script objects stored on the container.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.create.html b/docs/0.9.5/api/evennia.utils.create.html deleted file mode 100644 index 4f21d3b6fe..0000000000 --- a/docs/0.9.5/api/evennia.utils.create.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - evennia.utils.create — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.create

-

This module gathers all the essential database-creation -functions for the game engine’s various object types.

-

Only objects created ‘stand-alone’ are in here, e.g. object Attributes -are always created directly through their respective objects.

-

Each creation_* function also has an alias named for the entity being -created, such as create_object() and object(). This is for -consistency with the utils.search module and allows you to do the -shorter “create.object()”.

-

The respective object managers hold more methods for manipulating and -searching objects already existing in the database.

-
-
Models covered:

Objects -Scripts -Help -Message -Channel -Accounts

-
-
-
-
-evennia.utils.create.create_object(typeclass=None, key=None, location=None, home=None, permissions=None, locks=None, aliases=None, tags=None, destination=None, report_to=None, nohome=False, attributes=None, nattributes=None)[source]
-

Create a new in-game object.

-
-
Keyword Arguments
-
    -
  • typeclass (class or str) – Class or python path to a typeclass.

  • -
  • key (str) – Name of the new object. If not set, a name of -#dbref will be set.

  • -
  • home (Object or str) – Obj or #dbref to use as the object’s -home location.

  • -
  • permissions (list) – A list of permission strings or tuples (permstring, category).

  • -
  • locks (str) – one or more lockstrings, separated by semicolons.

  • -
  • aliases (list) – A list of alternative keys or tuples (aliasstring, category).

  • -
  • tags (list) – List of tag keys or tuples (tagkey, category) or (tagkey, category, data).

  • -
  • destination (Object or str) – Obj or #dbref to use as an Exit’s -target.

  • -
  • report_to (Object) – The object to return error messages to.

  • -
  • nohome (bool) – This allows the creation of objects without a -default home location; only used when creating the default -location itself or during unittests.

  • -
  • attributes (list) – Tuples on the form (key, value) or (key, value, category), -(key, value, lockstring) or (key, value, lockstring, default_access). -to set as Attributes on the new object.

  • -
  • nattributes (list) – Non-persistent tuples on the form (key, value). Note that -adding this rarely makes sense since this data will not survive a reload.

  • -
-
-
Returns
-

object (Object) – A newly created object of the given typeclass.

-
-
Raises
-
-
-
-
- -
-
-evennia.utils.create.create_script(typeclass=None, key=None, obj=None, account=None, locks=None, interval=None, start_delay=None, repeats=None, persistent=None, autostart=True, report_to=None, desc=None, tags=None, attributes=None)[source]
-

Create a new script. All scripts are a combination of a database -object that communicates with the database, and an typeclass that -‘decorates’ the database object into being different types of -scripts. It’s behaviour is similar to the game objects except -scripts has a time component and are more limited in scope.

-
-
Keyword Arguments
-
    -
  • typeclass (class or str) – Class or python path to a typeclass.

  • -
  • key (str) – Name of the new object. If not set, a name of -#dbref will be set.

  • -
  • obj (Object) – The entity on which this Script sits. If this -is None, we are creating a “global” script.

  • -
  • account (Account) – The account on which this Script sits. It is -exclusiv to obj.

  • -
  • locks (str) – one or more lockstrings, separated by semicolons.

  • -
  • interval (int) – The triggering interval for this Script, in -seconds. If unset, the Script will not have a timing -component.

  • -
  • start_delay (bool) – If True, will wait interval seconds -before triggering the first time.

  • -
  • repeats (int) – The number of times to trigger before stopping. -If unset, will repeat indefinitely.

  • -
  • persistent (bool) – If this Script survives a server shutdown -or not (all Scripts will survive a reload).

  • -
  • autostart (bool) – If this Script will start immediately when -created or if the start method must be called explicitly.

  • -
  • report_to (Object) – The object to return error messages to.

  • -
  • desc (str) – Optional description of script

  • -
  • tags (list) – List of tags or tuples (tag, category).

  • -
  • attributes (list) – List of tuples (key, value), (key, value, category), -(key, value, category, lockstring) or -(key, value, category, lockstring, default_access).

  • -
-
-
Returns
-

script (obj) – An instance of the script created

-
-
-

See evennia.scripts.manager for methods to manipulate existing -scripts in the database.

-
- -
-
-evennia.utils.create.create_help_entry(key, entrytext, category='General', locks=None, aliases=None, tags=None)[source]
-

Create a static help entry in the help database. Note that Command -help entries are dynamic and directly taken from the __doc__ -entries of the command. The database-stored help entries are -intended for more general help on the game, more extensive info, -in-game setting information and so on.

-
-
Parameters
-
    -
  • key (str) – The name of the help entry.

  • -
  • entrytext (str) – The body of te help entry

  • -
  • category (str, optional) – The help category of the entry.

  • -
  • locks (str, optional) – A lockstring to restrict access.

  • -
  • aliases (list of str, optional) – List of alternative (likely shorter) keynames.

  • -
  • tags (lst, optional) – List of tags or tuples (tag, category).

  • -
-
-
Returns
-

help (HelpEntry) – A newly created help entry.

-
-
-
- -
-
-evennia.utils.create.create_message(senderobj, message, channels=None, receivers=None, locks=None, tags=None, header=None)[source]
-

Create a new communication Msg. Msgs represent a unit of -database-persistent communication between entites.

-
-
Parameters
-
    -
  • senderobj (Object or Account) – The entity sending the Msg.

  • -
  • message (str) – Text with the message. Eventual headers, titles -etc should all be included in this text string. Formatting -will be retained.

  • -
  • channels (Channel, key or list) – A channel or a list of channels to -send to. The channels may be actual channel objects or their -unique key strings.

  • -
  • receivers (Object, Account, str or list) – An Account/Object to send -to, or a list of them. May be Account objects or accountnames.

  • -
  • locks (str) – Lock definition string.

  • -
  • tags (list) – A list of tags or tuples (tag, category).

  • -
  • header (str) – Mime-type or other optional information for the message

  • -
-
-
-

Notes

-

The Comm system is created very open-ended, so it’s fully possible -to let a message both go to several channels and to several -receivers at the same time, it’s up to the command definitions to -limit this as desired.

-
- -
-
-evennia.utils.create.create_channel(key, aliases=None, desc=None, locks=None, keep_log=True, typeclass=None, tags=None)[source]
-

Create A communication Channel. A Channel serves as a central hub -for distributing Msgs to groups of people without specifying the -receivers explicitly. Instead accounts may ‘connect’ to the channel -and follow the flow of messages. By default the channel allows -access to all old messages, but this can be turned off with the -keep_log switch.

-
-
Parameters
-

key (str) – This must be unique.

-
-
Keyword Arguments
-
    -
  • aliases (list of str) – List of alternative (likely shorter) keynames.

  • -
  • desc (str) – A description of the channel, for use in listings.

  • -
  • locks (str) – Lockstring.

  • -
  • keep_log (bool) – Log channel throughput.

  • -
  • typeclass (str or class) – The typeclass of the Channel (not -often used).

  • -
  • tags (list) – A list of tags or tuples (tag, category).

  • -
-
-
Returns
-

channel (Channel) – A newly created channel.

-
-
-
- -
-
-evennia.utils.create.create_account(key, email, password, typeclass=None, is_superuser=False, locks=None, permissions=None, tags=None, attributes=None, report_to=None)[source]
-

This creates a new account.

-
-
Parameters
-
    -
  • key (str) – The account’s name. This should be unique.

  • -
  • email (str or None) – Email on valid addr@addr.domain form. If -the empty string, will be set to None.

  • -
  • password (str) – Password in cleartext.

  • -
-
-
Keyword Arguments
-
    -
  • typeclass (str) – The typeclass to use for the account.

  • -
  • is_superuser (bool) – Wether or not this account is to be a superuser

  • -
  • locks (str) – Lockstring.

  • -
  • permission (list) – List of permission strings.

  • -
  • tags (list) – List of Tags on form (key, category[, data])

  • -
  • attributes (list) – List of Attributes on form -(key, value [, category, [,lockstring [, default_pass]]])

  • -
  • report_to (Object) – An object with a msg() method to report -errors to. If not given, errors will be logged.

  • -
-
-
Returns
-

Account – The newly created Account.

-
-
Raises
-

ValueError – If key already exists in database.

-
-
-

Notes

-

Usually only the server admin should need to be superuser, all -other access levels can be handled with more fine-grained -permissions or groups. A superuser bypasses all lock checking -operations and is thus not suitable for play-testing the game.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.dbserialize.html b/docs/0.9.5/api/evennia.utils.dbserialize.html deleted file mode 100644 index 6b4eb6cd6b..0000000000 --- a/docs/0.9.5/api/evennia.utils.dbserialize.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - evennia.utils.dbserialize — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.dbserialize

-

This module handles serialization of arbitrary python structural data, -intended primarily to be stored in the database. It also supports -storing Django model instances (which plain pickle cannot do).

-

This serialization is used internally by the server, notably for -storing data in Attributes and for piping data to process pools.

-

The purpose of dbserialize is to handle all forms of data. For -well-structured non-arbitrary exchange, such as communicating with a -rich web client, a simpler JSON serialization makes more sense.

-

This module also implements the SaverList, SaverDict and SaverSet -classes. These are iterables that track their position in a nested -structure and makes sure to send updates up to their root. This is -used by Attributes - without it, one would not be able to update mutables -in-situ, e.g obj.db.mynestedlist[3][5] = 3 would never be saved and -be out of sync with the database.

-
-
-evennia.utils.dbserialize.to_pickle(data)[source]
-

This prepares data on arbitrary form to be pickled. It handles any -nested structure and returns data on a form that is safe to pickle -(including having converted any database models to their internal -representation). We also convert any Saver*-type objects back to -their normal representations, they are not pickle-safe.

-
-
Parameters
-

data (any) – Data to pickle.

-
-
Returns
-

data (any) – Pickled data.

-
-
-
- -
-
-evennia.utils.dbserialize.from_pickle(data, db_obj=None)[source]
-

This should be fed a just de-pickled data object. It will be converted back -to a form that may contain database objects again. Note that if a database -object was removed (or changed in-place) in the database, None will be -returned.

-
-
Parameters
-
    -
  • data (any) – Pickled data to unpickle.

  • -
  • db_obj (Atribute, any) – This is the model instance (normally -an Attribute) that _Saver*-type iterables (_SaverList etc) -will save to when they update. It must have a ‘value’ property -that saves assigned data to the database. Skip if not -serializing onto a given object. If db_obj is given, this -function will convert lists, dicts and sets to their -_SaverList, _SaverDict and _SaverSet counterparts.

  • -
-
-
Returns
-

data (any) – Unpickled data.

-
-
-
- -
-
-evennia.utils.dbserialize.do_pickle(data)[source]
-

Perform pickle to string

-
- -
-
-evennia.utils.dbserialize.do_unpickle(data)[source]
-

Retrieve pickle from pickled string

-
- -
-
-evennia.utils.dbserialize.dbserialize(data)[source]
-

Serialize to pickled form in one step

-
- -
-
-evennia.utils.dbserialize.dbunserialize(data, db_obj=None)[source]
-

Un-serialize in one step. See from_pickle for help db_obj.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.eveditor.html b/docs/0.9.5/api/evennia.utils.eveditor.html deleted file mode 100644 index a0be455a53..0000000000 --- a/docs/0.9.5/api/evennia.utils.eveditor.html +++ /dev/null @@ -1,547 +0,0 @@ - - - - - - - - - evennia.utils.eveditor — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.eveditor

-

EvEditor (Evennia Line Editor)

-

This implements an advanced line editor for editing longer texts -in-game. The editor mimics the command mechanisms of the “VI” editor -(a famous line-by-line editor) as far as reasonable.

-

Features of the editor:

-
-
    -
  • undo/redo.

  • -
  • edit/replace on any line of the buffer.

  • -
  • search&replace text anywhere in buffer.

  • -
  • formatting of buffer, or selection, to certain width + indentations.

  • -
  • allow to echo the input or not, depending on your client.

  • -
-
-

To use the editor, just import EvEditor from this module -and initialize it:

-
from evennia.utils.eveditor import EvEditor
-EvEditor(caller, loadfunc=None, savefunc=None, quitfunc=None, key="", persistent=True)
-
-
-
    -
  • caller is the user of the editor, the one to see all feedback.

  • -
  • loadfunc(caller) is called when the editor is first launched; the -return from this function is loaded as the starting buffer in the -editor.

  • -
  • safefunc(caller, buffer) is called with the current buffer when -saving in the editor. The function should return True/False depending -on if the saving was successful or not.

  • -
  • quitfunc(caller) is called when the editor exits. If this is given, -no automatic quit messages will be given.

  • -
  • key is an optional identifier for the editing session, to be -displayed in the editor.

  • -
  • persistent means the editor state will be saved to the database making it -survive a server reload. Note that using this mode, the load- save- -and quit-funcs must all be possible to pickle - notable unusable -callables are class methods and functions defined inside other -functions. With persistent=False, no such restriction exists.

  • -
  • code set to True activates features on the EvEditor to enter Python code.

  • -
-

In addition, the EvEditor can be used to enter Python source code, -and offers basic handling of indentation.

-
-
-
-class evennia.utils.eveditor.CmdSaveYesNo(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Save the editor state on quit. This catches -nomatches (defaults to Yes), and avoid saves only if -command was given specifically as “no” or “n”.

-
-
-key = '__nomatch_command'
-
- -
-
-aliases = ['__noinput_command']
-
- -
-
-locks = 'cmd:all()'
-
- -
-
-help_cateogory = 'LineEditor'
-
- -
-
-func()[source]
-

Implement the yes/no choice.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.utils.eveditor.SaveYesNoCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Stores the yesno question

-
-
-key = 'quitsave_yesno'
-
- -
-
-priority = 150
-
- -
-
-mergetype = 'Replace'
-
- -
-
-at_cmdset_creation()[source]
-

at cmdset creation

-
- -
-
-path = 'evennia.utils.eveditor.SaveYesNoCmdSet'
-
- -
- -
-
-class evennia.utils.eveditor.CmdEditorBase(**kwargs)[source]
-

Bases: evennia.commands.default.muxcommand.MuxCommand

-

Base parent for editor commands

-
-
-locks = 'cmd:all()'
-
- -
-
-help_entry = 'LineEditor'
-
- -
-
-editor = None
-
- -
-
-parse()[source]
-

Handles pre-parsing

-
-
Usage:

:cmd [li] [w] [txt]

-
-
-

Where all arguments are optional.

-
    -
  • li - line number (int), starting from 1. This could also -be a range given as <l>:<l>.

  • -
  • w - word(s) (string), could be encased in quotes.

  • -
  • txt - extra text (string), could be encased in quotes.

  • -
-
- -
-
-aliases = []
-
- -
-
-help_category = 'general'
-
- -
-
-key = 'command'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.utils.eveditor.CmdLineInput(**kwargs)[source]
-

Bases: evennia.utils.eveditor.CmdEditorBase

-

No command match - Inputs line of text into buffer.

-
-
-key = '__nomatch_command'
-
- -
-
-aliases = ['__noinput_command']
-
- -
-
-func()[source]
-

Adds the line without any formatting changes.

-

If the editor handles code, it might add automatic -indentation.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.utils.eveditor.CmdEditorGroup(**kwargs)[source]
-

Bases: evennia.utils.eveditor.CmdEditorBase

-

Commands for the editor

-
-
-key = ':editor_command_group'
-
- -
-
-aliases = [':=', ':x', ':q!', ':dd', ':u', ':fd', '::', ':!', ':::', ':uu', ':fi', ':p', ':r', ':f', ':<', ':dw', ':', ':UU', ':>', ':i', ':A', ':I', ':DD', ':S', ':j', ':s', ':echo', ':q', ':h', ':w', ':wq', ':y']
-
- -
-
-arg_regex = re.compile('\\s.*?|$', re.IGNORECASE)
-
- -
-
-func()[source]
-

This command handles all the in-editor :-style commands. Since -each command is small and very limited, this makes for a more -efficient presentation.

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all()'
-
- -
- -
-
-class evennia.utils.eveditor.EvEditorCmdSet(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

CmdSet for the editor commands

-
-
-key = 'editorcmdset'
-
- -
-
-mergetype = 'Replace'
-
- -
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.utils.eveditor.EvEditorCmdSet'
-
- -
- -
-
-class evennia.utils.eveditor.EvEditor(caller, loadfunc=None, savefunc=None, quitfunc=None, key='', persistent=False, codefunc=False)[source]
-

Bases: object

-

This defines a line editor object. It creates all relevant commands -and tracks the current state of the buffer. It also cleans up after -itself.

-
-
-__init__(caller, loadfunc=None, savefunc=None, quitfunc=None, key='', persistent=False, codefunc=False)[source]
-

Launches a full in-game line editor, mimicking the functionality of VIM.

-
-
Parameters
-
    -
  • caller (Object) – Who is using the editor.

  • -
  • loadfunc (callable, optional) – This will be called as -loadfunc(caller) when the editor is first started. Its -return will be used as the editor’s starting buffer.

  • -
  • savefunc (callable, optional) – This will be called as -savefunc(caller, buffer) when the save-command is given and -is used to actually determine where/how result is saved. -It should return True if save was successful and also -handle any feedback to the user.

  • -
  • quitfunc (callable, optional) – This will optionally be -called as quitfunc(caller) when the editor is -exited. If defined, it should handle all wanted feedback -to the user.

  • -
  • quitfunc_args (tuple, optional) – Optional tuple of arguments to -supply to quitfunc.

  • -
  • key (str, optional) – An optional key for naming this -session and make it unique from other editing sessions.

  • -
  • persistent (bool, optional) – Make the editor survive a reboot. Note -that if this is set, all callables must be possible to pickle

  • -
  • codefunc (bool, optional) – If given, will run the editor in code mode. -This will be called as codefunc(caller, buf).

  • -
-
-
-

Notes

-

In persistent mode, all the input callables (savefunc etc) -must be possible to be pickled, this excludes e.g. -callables that are class methods or functions defined -dynamically or as part of another function. In -non-persistent mode no such restrictions exist.

-
- -
-
-load_buffer()[source]
-

Load the buffer using the load function hook.

-
- -
-
-get_buffer()[source]
-
-
Returns
-

buffer (str) – The current buffer.

-
-
-
- -
-
-update_buffer(buf)[source]
-

This should be called when the buffer has been changed -somehow. It will handle unsaved flag and undo updating.

-
-
Parameters
-

buf (str) – The text to update the buffer with.

-
-
-
- -
-
-quit()[source]
-

Cleanly exit the editor.

-
- -
-
-save_buffer()[source]
-

Saves the content of the buffer.

-
- -
-
-update_undo(step=None)[source]
-

This updates the undo position.

-
-
Parameters
-

step (int, optional) – The amount of steps -to progress the undo position to. This -may be a negative value for undo and -a positive value for redo.

-
-
-
- -
-
-display_buffer(buf=None, offset=0, linenums=True, options={'raw': False})[source]
-

This displays the line editor buffer, or selected parts of it.

-
-
Parameters
-
    -
  • buf (str, optional) – The buffer or part of buffer to display.

  • -
  • offset (int, optional) – If buf is set and is not the full buffer, -offset should define the actual starting line number, to -get the linenum display right.

  • -
  • linenums (bool, optional) – Show line numbers in buffer.

  • -
  • options – raw (bool, optional): Tell protocol to not parse -formatting information.

  • -
-
-
-
- -
-
-display_help()[source]
-

Shows the help entry for the editor.

-
- -
-
-deduce_indent(line, buffer)[source]
-

Try to deduce the level of indentation of the given line.

-
- -
-
-decrease_indent()[source]
-

Decrease automatic indentation by 1 level.

-
- -
-
-increase_indent()[source]
-

Increase automatic indentation by 1 level.

-
- -
-
-swap_autoindent()[source]
-

Swap automatic indentation on or off.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.evform.html b/docs/0.9.5/api/evennia.utils.evform.html deleted file mode 100644 index 6d0b7f1892..0000000000 --- a/docs/0.9.5/api/evennia.utils.evform.html +++ /dev/null @@ -1,289 +0,0 @@ - - - - - - - - - evennia.utils.evform — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.evform

-

EvForm - a way to create advanced ASCII forms

-

This is intended for creating advanced ASCII game forms, such as a -large pretty character sheet or info document.

-

The system works on the basis of a readin template that is given in a -separate Python file imported into the handler. This file contains -some optional settings and a string mapping out the form. The template -has markers in it to denounce fields to fill. The markers map the -absolute size of the field and will be filled with an evtable.EvCell -object when displaying the form.

-

Example of input file testform.py:

-
FORMCHAR = "x"
-TABLECHAR = "c"
-
-FORM = '''
-.------------------------------------------------.
-|                                                |
-|  Name: xxxxx1xxxxx    Player: xxxxxxx2xxxxxxx  |
-|        xxxxxxxxxxx                             |
-|                                                |
- >----------------------------------------------<
-|                                                |
-| Desc:  xxxxxxxxxxx    STR: x4x    DEX: x5x     |
-|        xxxxx3xxxxx    INT: x6x    STA: x7x     |
-|        xxxxxxxxxxx    LUC: x8x    MAG: x9x     |
-|                                                |
- >----------------------------------------------<
-|          |                                     |
-| cccccccc | ccccccccccccccccccccccccccccccccccc |
-| cccccccc | ccccccccccccccccccccccccccccccccccc |
-| cccAcccc | ccccccccccccccccccccccccccccccccccc |
-| cccccccc | ccccccccccccccccccccccccccccccccccc |
-| cccccccc | cccccccccccccccccBccccccccccccccccc |
-|          |                                     |
--------------------------------------------------
-
-
-

The first line of the FORM string is ignored. The forms and table -markers must mark out complete, unbroken rectangles, each containing -one embedded single-character identifier (so the smallest element -possible is a 3-character wide form). The identifier can be any -character except for the FORM_CHAR and TABLE_CHAR and some of the -common ASCII-art elements, like space, _ | * etc (see -INVALID_FORMCHARS in this module). Form Rectangles can have any size, -but must be separated from each other by at least one other -character’s width.

-

Use as follows:

-
from evennia import EvForm, EvTable
-
-# create a new form from the template
-form = EvForm("path/to/testform.py")
-
-(MudForm can also take a dictionary holding
- the required keys FORMCHAR, TABLECHAR and FORM)
-
-# add data to each tagged form cell
-form.map(cells={1: "Tom the Bouncer",
-                2: "Griatch",
-                3: "A sturdy fellow",
-                4: 12,
-                5: 10,
-                6:  5,
-                7: 18,
-                8: 10,
-                9:  3})
-# create the EvTables
-tableA = EvTable("HP","MV","MP",
-                           table=[["**"], ["*****"], ["***"]],
-                           border="incols")
-tableB = EvTable("Skill", "Value", "Exp",
-                           table=[["Shooting", "Herbalism", "Smithing"],
-                                  [12,14,9],["550/1200", "990/1400", "205/900"]],
-                           border="incols")
-# add the tables to the proper ids in the form
-form.map(tables={"A": tableA,
-                 "B": tableB})
-
-print(form)
-
-
-

This produces the following result:

-
.------------------------------------------------.
-|                                                |
-|  Name: Tom the        Player: Griatch          |
-|        Bouncer                                 |
-|                                                |
- >----------------------------------------------<
-|                                                |
-| Desc:  A sturdy       STR: 12     DEX: 10      |
-|        fellow         INT: 5      STA: 18      |
-|                       LUC: 10     MAG: 3       |
-|                                                |
- >----------------------------------------------<
-|          |                                     |
-| HP|MV|MP | Skill      |Value      |Exp         |
-| ~~+~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~ |
-| **|**|** | Shooting   |12         |550/1200    |
-|   |**|*  | Herbalism  |14         |990/1400    |
-|   |* |   | Smithing   |9          |205/900     |
-|          |                                     |
- ------------------------------------------------
-
-
-

The marked forms have been replaced with EvCells of text and with -EvTables. The form can be updated by simply re-applying form.map() -with the updated data.

-

When working with the template ASCII file, you can use form.reload() -to re-read the template and re-apply all existing mappings.

-

Each component is restrained to the width and height specified by the -template, so it will resize to fit (or crop text if the area is too -small for it). If you try to fit a table into an area it cannot fit -into (when including its borders and at least one line of text), the -form will raise an error.

-
-
-
-class evennia.utils.evform.EvForm(filename=None, cells=None, tables=None, form=None, **kwargs)[source]
-

Bases: object

-

This object is instantiated with a text file and parses -it for rectangular form fields. It can then be fed a -mapping so as to populate the fields with fixed-width -EvCell or Tables.

-
-
-__init__(filename=None, cells=None, tables=None, form=None, **kwargs)[source]
-

Initiate the form.

-
-
Keyword Arguments
-
    -
  • filename (str) – Path to template file.

  • -
  • cells (dict) – A dictionary mapping of {id:text}.

  • -
  • tables (dict) – A dictionary mapping of {id:EvTable}.

  • -
  • form (dict) – A dictionary of -{“FORMCHAR”:char, “TABLECHAR”:char, “FORM”:templatestring}. -if this is given, filename is not read.

  • -
-
-
-

Notes

-

Other kwargs are fed as options to the EvCells and EvTables -(see evtable.EvCell and evtable.EvTable for more info).

-
- -
-
-map(cells=None, tables=None, **kwargs)[source]
-

Add mapping for form.

-
-
Parameters
-
    -
  • cells (dict) – A dictionary of {identifier:celltext}

  • -
  • tables (dict) – A dictionary of {identifier:table}

  • -
-
-
-

Notes

-

kwargs will be forwarded to tables/cells. See -evtable.EvCell and evtable.EvTable for info.

-
- -
-
-reload(filename=None, form=None, **kwargs)[source]
-

Creates the form from a stored file name.

-
-
Parameters
-
    -
  • filename (str) – The file to read from.

  • -
  • form (dict) – A mapping for the form.

  • -
-
-
-

Notes

-

Kwargs are passed through to Cel creation.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.evmenu.html b/docs/0.9.5/api/evennia.utils.evmenu.html deleted file mode 100644 index 78f640ecb5..0000000000 --- a/docs/0.9.5/api/evennia.utils.evmenu.html +++ /dev/null @@ -1,1060 +0,0 @@ - - - - - - - - - evennia.utils.evmenu — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.evmenu

-

The EvMenu is a full in-game menu system for Evennia.

-

To start the menu, just import the EvMenu class from this module.

-

Example usage:

-
from evennia.utils.evmenu import EvMenu
-
-EvMenu(caller, menu_module_path,
-     startnode="node1",
-     cmdset_mergetype="Replace", cmdset_priority=1,
-     auto_quit=True, cmd_on_exit="look", persistent=True)
-
-
-

Where caller is the Object to use the menu on - it will get a new -cmdset while using the Menu. The menu_module_path is the python path -to a python module containing function definitions. By adjusting the -keyword options of the Menu() initialization call you can start the -menu at different places in the menu definition file, adjust if the -menu command should overload the normal commands or not, etc.

-

The persistent keyword will make the menu survive a server reboot. -It is False by default. Note that if using persistent mode, every -node and callback in the menu must be possible to be pickled, this -excludes e.g. callables that are class methods or functions defined -dynamically or as part of another function. In non-persistent mode -no such restrictions exist.

-

The menu is defined in a module (this can be the same module as the -command definition too) with function definitions:

-
def node1(caller):
-    # (this is the start node if called like above)
-    # code
-    return text, options
-
-def node_with_other_name(caller, input_string):
-    # code
-    return text, options
-
-def another_node(caller, input_string, **kwargs):
-    # code
-    return text, options
-
-
-

Where caller is the object using the menu and input_string is the -command entered by the user on the previous node (the command -entered to get to this node). The node function code will only be -executed once per node-visit and the system will accept nodes with -both one or two arguments interchangeably. It also accepts nodes -that takes **kwargs.

-

The menu tree itself is available on the caller as -caller.ndb._evmenu. This makes it a convenient place to store -temporary state variables between nodes, since this NAttribute is -deleted when the menu is exited.

-

The return values must be given in the above order, but each can be -returned as None as well. If the options are returned as None, the -menu is immediately exited and the default “look” command is called.

-
    -
  • text (str, tuple or None): Text shown at this node. If a tuple, the -second element in the tuple is a help text to display at this -node when the user enters the menu help command there.

  • -
  • options (tuple, dict or None): If None, this exits the menu. -If a single dict, this is a single-option node. If a tuple, -it should be a tuple of option dictionaries. Option dicts have -the following keys:

    -
      -
    • -
      key (str or tuple, optional): What to enter to choose this option.

      If a tuple, it must be a tuple of strings, where the first string is the -key which will be shown to the user and the others are aliases. -If unset, the options’ number will be used. The special key _default -marks this option as the default fallback when no other option matches -the user input. There can only be one _default option per node. It -will not be displayed in the list.

      -
      -
      -
    • -
    • desc (str, optional): This describes what choosing the option will do.

    • -
    • -
      goto (str, tuple or callable): If string, should be the name of node to go to

      when this option is selected. If a callable, it has the signature -callable(caller[,raw_input][,**kwargs]). If a tuple, the first element -is the callable and the second is a dict with the kwargs to pass to -the callable. Those kwargs will also be passed into the next node if possible. -Such a callable should return either a str or a (str, dict), where the -string is the name of the next node to go to and the dict is the new, -(possibly modified) kwarg to pass into the next node. If the callable returns -None or the empty string, the current node will be revisited.

      -
      -
      -
    • -
    • -
      exec (str, callable or tuple, optional): This takes the same input as goto above

      and runs before it. If given a node name, the node will be executed but will not -be considered the next node. If node/callback returns str or (str, dict), these will -replace the goto step (goto callbacks will not fire), with the string being the -next node name and the optional dict acting as the kwargs-input for the next node. -If an exec callable returns None, the current node is re-run.

      -
      -
      -
    • -
    -
  • -
-

If key is not given, the option will automatically be identified by -its number 1..N.

-

Example:

-
# in menu_module.py
-
-def node1(caller):
-    text = ("This is a node text",
-            "This is help text for this node")
-    options = ({"key": "testing",
-                "desc": "Select this to go to node 2",
-                "goto": ("node2", {"foo": "bar"}),
-                "exec": "callback1"},
-               {"desc": "Go to node 3.",
-                "goto": "node3"})
-    return text, options
-
-def callback1(caller):
-    # this is called when choosing the "testing" option in node1
-    # (before going to node2). If it returned a string, say 'node3',
-    # then the next node would be node3 instead of node2 as specified
-    # by the normal 'goto' option key above.
-    caller.msg("Callback called!")
-
-def node2(caller, **kwargs):
-    text = '''
-        This is node 2. It only allows you to go back
-        to the original node1. This extra indent will
-        be stripped. We don't include a help text but
-        here are the variables passed to us: {}
-        '''.format(kwargs)
-    options = {"goto": "node1"}
-    return text, options
-
-def node3(caller):
-    text = "This ends the menu since there are no options."
-    return text, None
-
-
-

When starting this menu with Menu(caller, “path.to.menu_module”), -the first node will look something like this:

-
This is a node text
-______________________________________
-
-testing: Select this to go to node 2
-2: Go to node 3
-
-
-

Where you can both enter “testing” and “1” to select the first option. -If the client supports MXP, they may also mouse-click on “testing” to -do the same. When making this selection, a function “callback1” in the -same Using help will show the help text, otherwise a list of -available commands while in menu mode.

-

The menu tree is exited either by using the in-menu quit command or by -reaching a node without any options.

-

For a menu demo, import CmdTestMenu from this module and add it to -your default cmdset. Run it with this module, like testmenu -evennia.utils.evmenu.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.evmore.html b/docs/0.9.5/api/evennia.utils.evmore.html deleted file mode 100644 index d2f01a5c46..0000000000 --- a/docs/0.9.5/api/evennia.utils.evmore.html +++ /dev/null @@ -1,554 +0,0 @@ - - - - - - - - - evennia.utils.evmore — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.evmore

-

EvMore - pager mechanism

-

This is a pager for displaying long texts and allows stepping up and -down in the text (the name comes from the traditional ‘more’ unix -command).

-

To use, simply pass the text through the EvMore object:

-
from evennia.utils.evmore import EvMore
-
-text = some_long_text_output()
-EvMore(caller, text, always_page=False, session=None, justify_kwargs=None, **kwargs)
-
-
-

One can also use the convenience function msg from this module:

-
from evennia.utils import evmore
-
-text = some_long_text_output()
-evmore.msg(caller, text, always_page=False, session=None, justify_kwargs=None, **kwargs)
-
-
-

Where always_page decides if the pager is used also if the text is not long -enough to need to scroll, session is used to determine which session to relay -to and justify_kwargs are kwargs to pass to utils.utils.justify in order to -change the formatting of the text. The remaining **kwargs will be passed on to -the caller.msg() construct every time the page is updated.

-
-
-
-class evennia.utils.evmore.CmdMore(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Manipulate the text paging

-
-
-key = '__noinput_command'
-
- -
-
-aliases = ['t', 'back', 'e', 'n', 'q', 'quit', 'end', 'abort', 'top', 'b', 'next', 'a']
-
- -
-
-auto_help = False
-
- -
-
-func()[source]
-

Implement the command

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.utils.evmore.CmdMoreLook(**kwargs)[source]
-

Bases: evennia.commands.command.Command

-

Override look to display window and prevent OOCLook from firing

-
-
-key = 'look'
-
- -
-
-aliases = ['l']
-
- -
-
-auto_help = False
-
- -
-
-func()[source]
-

Implement the command

-
- -
-
-help_category = 'general'
-
- -
-
-lock_storage = 'cmd:all();'
-
- -
- -
-
-class evennia.utils.evmore.CmdSetMore(cmdsetobj=None, key=None)[source]
-

Bases: evennia.commands.cmdset.CmdSet

-

Stores the more command

-
-
-key = 'more_commands'
-
- -
-
-priority = 110
-
- -
-
-at_cmdset_creation()[source]
-

Hook method - this should be overloaded in the inheriting -class, and should take care of populating the cmdset by use of -self.add().

-
- -
-
-path = 'evennia.utils.evmore.CmdSetMore'
-
- -
- -
-
-evennia.utils.evmore.queryset_maxsize(qs)[source]
-
- -
-
-class evennia.utils.evmore.EvMore(caller, inp, always_page=False, session=None, justify=False, justify_kwargs=None, exit_on_lastpage=False, exit_cmd=None, page_formatter=<class 'str'>, **kwargs)[source]
-

Bases: object

-

The main pager object.

-
-
-__init__(caller, inp, always_page=False, session=None, justify=False, justify_kwargs=None, exit_on_lastpage=False, exit_cmd=None, page_formatter=<class 'str'>, **kwargs)[source]
-

Initialization of the Evmore input handler.

-
-
Parameters
-
    -
  • caller (Object or Account) – Entity reading the text.

  • -
  • inp (str, EvTable, Paginator or iterator) –

    The text or data to put under paging.

    -
      -
    • If a string, paginage normally. If this text contains -one or more \f (backslash + f) format symbols, automatic -pagination and justification are force-disabled and -page-breaks will only happen after each \f.

    • -
    • If EvTable, the EvTable will be paginated with the same -setting on each page if it is too long. The table -decorations will be considered in the size of the page.

    • -
    • Otherwise inp is converted to an iterator, where each step is -expected to be a line in the final display. Each line -will be run through iter_callable.

    • -
    -

  • -
  • always_page (bool, optional) – If False, the pager will only kick -in if inp is too big to fit the screen.

  • -
  • session (Session, optional) – If given, this session will be used -to determine the screen width and will receive all output.

  • -
  • justify (bool, optional) – If set, auto-justify long lines. This must be turned -off for fixed-width or formatted output, like tables. It’s force-disabled -if inp is an EvTable.

  • -
  • justify_kwargs (dict, optional) – Keywords for the justifiy function. Used only -if justify is True. If this is not set, default arguments will be used.

  • -
  • exit_on_lastpage (bool, optional) – If reaching the last page without the -page being completely filled, exit pager immediately. If unset, -another move forward is required to exit. If set, the pager -exit message will not be shown.

  • -
  • exit_cmd (str, optional) – If given, this command-string will be executed on -the caller when the more page exits. Note that this will be using whatever -cmdset the user had before the evmore pager was activated (so none of -the evmore commands will be available when this is run).

  • -
  • kwargs (any, any) – These will be passed on to the caller.msg method.

  • -
-
-
-

Examples

-

Basic use:

-
super_long_text = " ... "
-EvMore(caller, super_long_text)
-
-
-

Paginated query data - this is an optimization to avoid fetching -database data until it’s actually paged to.

-
from django.core.paginator import Paginator
-
-query = ObjectDB.objects.all()
-pages = Paginator(query, 10)  # 10 objs per page
-EvMore(caller, pages)
-
-
-

Automatic split EvTable over multiple EvMore pages

-
table = EvMore(*header, table=tabledata)
-EvMore(caller, table)
-
-
-

Every page a separate EvTable (optimization for very large data sets)

-
from evennia import EvTable, EvMore
-
-class TableEvMore(EvMore):
-    def init_pages(self, data):
-        pages = # depends on data type
-        super().init_pages(pages)
-
-    def page_formatter(self, page):
-        table = EvTable()
-
-        for line in page:
-            cols = # split raw line into columns
-            table.add_row(*cols)
-
-        return str(table)
-
-TableEvMore(caller, pages)
-
-
-
- -
-
-display(show_footer=True)[source]
-

Pretty-print the page.

-
- -
-
-page_top()[source]
-

Display the top page

-
- -
-
-page_end()[source]
-

Display the bottom page.

-
- -
-
-page_next()[source]
-

Scroll the text to the next page. Quit if already at the end -of the page.

-
- -
-
-page_back()[source]
-

Scroll the text back up, at the most to the top.

-
- -
-
-page_quit(quiet=False)[source]
-

Quit the pager

-
- -
-
-start()[source]
-

Starts the pagination

-
- -
-
-paginator_index(pageno)[source]
-

Paginate to specific, known index

-
- -
-
-paginator_slice(pageno)[source]
-

Paginate by slice. This is done with an eye on memory efficiency (usually for -querysets); to avoid fetching all objects at the same time.

-
- -
-
-paginator_django(pageno)[source]
-

Paginate using the django queryset Paginator API. Note that his is indexed from 1.

-
- -
-
-init_evtable(table)[source]
-

The input is an EvTable.

-
- -
-
-init_queryset(qs)[source]
-

The input is a queryset

-
- -
-
-init_django_paginator(pages)[source]
-

The input is a django Paginator object.

-
- -
-
-init_iterable(inp)[source]
-

The input is something other than a string - convert to iterable of strings

-
- -
-
-init_f_str(text)[source]
-

The input contains \f (backslash + f) markers. We use \f to indicate -the user wants to enforce their line breaks on their own. If so, we do -no automatic line-breaking/justification at all.

-
- -
-
-init_str(text)[source]
-

The input is a string

-
- -
-
-init_pages(inp)[source]
-

Initialize the pagination. By default, will analyze input type to determine -how pagination automatically.

-
-
Parameters
-

inp (any) – Incoming data to be paginated. By default, handles pagination of -strings, querysets, django.Paginator, EvTables and any iterables with strings.

-
-
-

Notes

-

If overridden, this method must perform the following actions:

-
    -
  • read and re-store self._data (the incoming data set) if needed -for pagination to work.

  • -
  • set self._npages to the total number of pages. Default is 1.

  • -
  • set self._paginator to a callable that will take a page number 1…N and return -the data to display on that page (not any decorations or next/prev buttons). If only -wanting to change the paginator, override self.paginator instead.

  • -
  • set self._page_formatter to a callable that will receive the -page from self._paginator and format it with one element per -line. Default is str. Or override self.page_formatter -directly instead.

  • -
-

By default, helper methods are called that perform these actions -depending on supported inputs.

-
- -
-
-paginator(pageno)[source]
-

Paginator. The data operated upon is in self._data.

-
-
Parameters
-

pageno (int) – The page number to view, from 0…N-1

-
-
Returns
-

str

-
-
The page to display (without any decorations, those are added

by EvMore).

-
-
-

-
-
-
- -
-
-page_formatter(page)[source]
-

Page formatter. Every page passes through this method. Override -it to customize behvaior per-page. A common use is to generate a new -EvTable for every page (this is more efficient than to generate one huge -EvTable across many pages and feed it into EvMore all at once).

-
-
Parameters
-

page (any) – A piece of data representing one page to display. This must

-
-
Returns
-

str

-
-
A ready-formatted page to display. Extra footer with help about

switching to the next/prev page will be added automatically

-
-
-

-
-
-
- -
- -
-
-evennia.utils.evmore.msg(caller, text='', always_page=False, session=None, justify=False, justify_kwargs=None, exit_on_lastpage=True, **kwargs)[source]
-

EvMore-supported version of msg, mimicking the normal msg method.

-
-
Parameters
-
    -
  • caller (Object or Account) – Entity reading the text.

  • -
  • text (str, EvTable or iterator) –

    The text or data to put under paging.

    -
      -
    • If a string, paginage normally. If this text contains -one or more \f (backslash + f) format symbol, automatic pagination is disabled -and page-breaks will only happen after each \f.

    • -
    • If EvTable, the EvTable will be paginated with the same -setting on each page if it is too long. The table -decorations will be considered in the size of the page.

    • -
    • Otherwise text is converted to an iterator, where each step is -is expected to be a line in the final display, and each line -will be run through repr().

    • -
    -

  • -
  • always_page (bool, optional) – If False, the -pager will only kick in if text is too big -to fit the screen.

  • -
  • session (Session, optional) – If given, this session will be used -to determine the screen width and will receive all output.

  • -
  • justify (bool, optional) – If set, justify long lines in output. Disable for -fixed-format output, like tables.

  • -
  • justify_kwargs (dict, bool or None, optional) – If given, this should -be valid keyword arguments to the utils.justify() function. If False, -no justification will be done.

  • -
  • exit_on_lastpage (bool, optional) – Immediately exit pager when reaching the last page.

  • -
  • use_evtable (bool, optional) – If True, each page will be rendered as an -EvTable. For this to work, text must be an iterable, where each element -is the table (list of list) to render on that page.

  • -
  • evtable_args (tuple, optional) – The args to use for EvTable on each page.

  • -
  • evtable_kwargs (dict, optional) – The kwargs to use for EvTable on each -page (except table, which is supplied by EvMore per-page).

  • -
  • kwargs (any, optional) – These will be passed on -to the caller.msg method.

  • -
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.evtable.html b/docs/0.9.5/api/evennia.utils.evtable.html deleted file mode 100644 index 536f28d003..0000000000 --- a/docs/0.9.5/api/evennia.utils.evtable.html +++ /dev/null @@ -1,677 +0,0 @@ - - - - - - - - - evennia.utils.evtable — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.evtable

-

This is an advanced ASCII table creator. It was inspired by -[prettytable](https://code.google.com/p/prettytable/) but shares no code.

-

Example usage:

-
from evennia.utils import evtable
-
-table = evtable.EvTable("Heading1", "Heading2",
-              table=[[1,2,3],[4,5,6],[7,8,9]], border="cells")
-table.add_column("This is long data", "This is even longer data")
-table.add_row("This is a single row")
-print table
-
-
-

Result:

-
+----------------------+----------+---+--------------------------+
-|       Heading1       | Heading2 |   |                          |
-+~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~+~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~+
-|           1          |     4    | 7 |     This is long data    |
-+----------------------+----------+---+--------------------------+
-|           2          |     5    | 8 | This is even longer data |
-+----------------------+----------+---+--------------------------+
-|           3          |     6    | 9 |                          |
-+----------------------+----------+---+--------------------------+
-| This is a single row |          |   |                          |
-+----------------------+----------+---+--------------------------+
-
-
-

As seen, the table will automatically expand with empty cells to make -the table symmetric. Tables can be restricted to a given width:

-
table.reformat(width=50, align="l")
-
-
-

(We could just have added these keywords to the table creation call)

-

This yields the following result:

-
+-----------+------------+-----------+-----------+
-| Heading1  | Heading2   |           |           |
-+~~~~~~~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~+
-| 1         | 4          | 7         | This is   |
-|           |            |           | long data |
-+-----------+------------+-----------+-----------+
-|           |            |           | This is   |
-| 2         | 5          | 8         | even      |
-|           |            |           | longer    |
-|           |            |           | data      |
-+-----------+------------+-----------+-----------+
-| 3         | 6          | 9         |           |
-+-----------+------------+-----------+-----------+
-| This is a |            |           |           |
-|  single   |            |           |           |
-| row       |            |           |           |
-+-----------+------------+-----------+-----------+
-
-
-

Table-columns can be individually formatted. Note that if an -individual column is set with a specific width, table auto-balancing -will not affect this column (this may lead to the full table being too -wide, so be careful mixing fixed-width columns with auto- balancing). -Here we change the width and alignment of the column at index 3 -(Python starts from 0):

-
table.reformat_column(3, width=30, align="r")
-print table
-
-+-----------+-------+-----+-----------------------------+---------+
-| Heading1  | Headi |     |                             |         |
-|           | ng2   |     |                             |         |
-+~~~~~~~~~~~+~~~~~~~+~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~+
-| 1         | 4     | 7   |           This is long data | Test1   |
-+-----------+-------+-----+-----------------------------+---------+
-| 2         | 5     | 8   |    This is even longer data | Test3   |
-+-----------+-------+-----+-----------------------------+---------+
-| 3         | 6     | 9   |                             | Test4   |
-+-----------+-------+-----+-----------------------------+---------+
-| This is a |       |     |                             |         |
-|  single   |       |     |                             |         |
-| row       |       |     |                             |         |
-+-----------+-------+-----+-----------------------------+---------+
-
-
-

When adding new rows/columns their data can have its own alignments -(left/center/right, top/center/bottom).

-

If the height is restricted, cells will be restricted from expanding -vertically. This will lead to text contents being cropped. Each cell -can only shrink to a minimum width and height of 1.

-

EvTable is intended to be used with [ANSIString](evennia.utils.ansi#ansistring) -for supporting ANSI-coloured string types.

-

When a cell is auto-wrapped across multiple lines, ANSI-reset -sequences will be put at the end of each wrapped line. This means that -the colour of a wrapped cell will not “bleed”, but it also means that -eventual colour outside the table will not transfer “across” a table, -you need to re-set the color to have it appear on both sides of the -table string.

-
-
-
-class evennia.utils.evtable.ANSITextWrapper(width=70, initial_indent='', subsequent_indent='', expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]')[source]
-

Bases: textwrap.TextWrapper

-

This is a wrapper work class for handling strings with ANSI tags -in it. It overloads the standard library TextWrapper class and -is used internally in EvTable and has no public methods.

-
- -
-
-evennia.utils.evtable.wrap(text, width=78, **kwargs)[source]
-

Wrap a single paragraph of text, returning a list of wrapped lines.

-

Reformat the single paragraph in ‘text’ so it fits in lines of no -more than ‘width’ columns, and return a list of wrapped lines. By -default, tabs in ‘text’ are expanded with string.expandtabs(), and -all other whitespace characters (including newline) are converted to

-
-
Parameters
-
    -
  • text (str) – Text to wrap.

  • -
  • width (int, optional) – Width to wrap text to.

  • -
-
-
Keyword Arguments
-
    -
  • TextWrapper class for available keyword args to customize (See) –

  • -
  • behaviour. (wrapping) –

  • -
-
-
-
- -
-
-evennia.utils.evtable.fill(text, width=78, **kwargs)[source]
-

Fill a single paragraph of text, returning a new string.

-

Reformat the single paragraph in ‘text’ to fit in lines of no more -than ‘width’ columns, and return a new string containing the entire -wrapped paragraph. As with wrap(), tabs are expanded and other -whitespace characters converted to space.

-
-
Parameters
-
    -
  • text (str) – Text to fill.

  • -
  • width (int, optional) – Width of fill area.

  • -
-
-
Keyword Arguments
-
    -
  • TextWrapper class for available keyword args to customize (See) –

  • -
  • behaviour. (filling) –

  • -
-
-
-
- -
-
-class evennia.utils.evtable.EvCell(data, **kwargs)[source]
-

Bases: object

-

Holds a single data cell for the table. A cell has a certain width -and height and contains one or more lines of data. It can shrink -and resize as needed.

-
-
-__init__(data, **kwargs)[source]
-
-
Parameters
-

data (str) – The un-padded data of the entry.

-
-
Keyword Arguments
-
    -
  • width (int) – Desired width of cell. It will pad -to this size.

  • -
  • height (int) – Desired height of cell. it will pad -to this size.

  • -
  • pad_width (int) – General padding width. This can be overruled -by individual settings below.

  • -
  • pad_left (int) – Number of extra pad characters on the left.

  • -
  • pad_right (int) – Number of extra pad characters on the right.

  • -
  • pad_top (int) – Number of extra pad lines top (will pad with vpad_char).

  • -
  • pad_bottom (int) – Number of extra pad lines bottom (will pad with vpad_char).

  • -
  • pad_char (str) – by individual settings below (default ” “).

  • -
  • hpad_char (str) – Pad character to use both for extra horizontal -padding (default ” “).

  • -
  • vpad_char (str) – Pad character to use for extra vertical padding -and for vertical fill (default ” “).

  • -
  • fill_char (str) – Character used to filling (expanding cells to -desired size). This can be overruled by individual settings below.

  • -
  • hfill_char (str) – Character used for horizontal fill (default ” “).

  • -
  • vfill_char (str) – Character used for vertical fill (default ” “).

  • -
  • align (str) – Should be one of “l”, “r” or “c” for left-, right- or center -horizontal alignment respectively. Default is left-aligned.

  • -
  • valign (str) – Should be one of “t”, “b” or “c” for top-, bottom and center -vertical alignment respectively. Default is centered.

  • -
  • border_width (int) – General border width. This is overruled -by individual settings below.

  • -
  • border_left (int) – Left border width.

  • -
  • border_right (int) – Right border width.

  • -
  • border_top (int) – Top border width.

  • -
  • border_bottom (int) – Bottom border width.

  • -
  • border_char (str) – This will use a single border char for all borders. -overruled by individual settings below.

  • -
  • border_left_char (str) – Char used for left border.

  • -
  • border_right_char (str) – Char used for right border.

  • -
  • border_top_char (str) – Char used for top border.

  • -
  • border_bottom_char (str) – Char user for bottom border.

  • -
  • corner_char (str) – Character used when two borders cross. (default is “”). -This is overruled by individual settings below.

  • -
  • corner_top_left_char (str) – Char used for “nw” corner.

  • -
  • corner_top_right_char (str) – Char used for “ne” corner.

  • -
  • corner_bottom_left_char (str) – Char used for “sw” corner.

  • -
  • corner_bottom_right_char (str) – Char used for “se” corner.

  • -
  • crop_string (str) – String to use when cropping sideways, default is ‘[…]’.

  • -
  • crop (bool) – Crop contentof cell rather than expand vertically, default=**False**.

  • -
  • enforce_size (bool) – If true, the width/height of the cell is -strictly enforced and extra text will be cropped rather than the -cell growing vertically.

  • -
-
-
Raises
-

Exception – for impossible cell size requirements where the -border width or height cannot fit, or the content is too -small.

-
-
-
- -
-
-get_min_height()[source]
-

Get the minimum possible height of cell, including at least -one line for data.

-
-
Returns
-

min_height (int) – The mininum height of cell.

-
-
-
- -
-
-get_min_width()[source]
-

Get the minimum possible width of cell, including at least one -character-width for data.

-
-
Returns
-

min_width (int) – The minimum width of cell.

-
-
-
- -
-
-get_height()[source]
-

Get natural height of cell, including padding.

-
-
Returns
-

natural_height (int) – Height of cell.

-
-
-
- -
-
-get_width()[source]
-

Get natural width of cell, including padding.

-
-
Returns
-

natural_width (int) – Width of cell.

-
-
-
- -
-
-replace_data(data, **kwargs)[source]
-

Replace cell data. This causes a full reformat of the cell.

-
-
Parameters
-

data (str) – Cell data.

-
-
-

Notes

-

The available keyword arguments are the same as for -EvCell.__init__.

-
- -
-
-reformat(**kwargs)[source]
-

Reformat the EvCell with new options

-
-
Keyword Arguments
-

available keyword arguments are the same as for EvCell.__init__. (The) –

-
-
Raises
-

Exception – If the cells cannot shrink enough to accomodate -the options or the data given.

-
-
-
- -
-
-get()[source]
-

Get data, padded and aligned in the form of a list of lines.

-
- -
- -
-
-class evennia.utils.evtable.EvColumn(*args, **kwargs)[source]
-

Bases: object

-

This class holds a list of Cells to represent a column of a table. -It holds operations and settings that affect all cells in the -column.

-

Columns are not intended to be used stand-alone; they should be -incorporated into an EvTable (like EvCells)

-
-
-__init__(*args, **kwargs)[source]
-
-
Parameters
-

for each row in the column (Text) –

-
-
Keyword Arguments
-
    -
  • EvCell.__init_ keywords are available, these (All) –

  • -
  • will be persistently applied to every Cell in the (settings) –

  • -
  • column.

  • -
-
-
-
- -
-
-add_rows(*args, **kwargs)[source]
-

Add new cells to column. They will be inserted as -a series of rows. It will inherit the options -of the rest of the column’s cells (use update to change -options).

-
-
Parameters
-
    -
  • for the new cells (Texts) –

  • -
  • ypos (int, optional) – Index position in table before which to insert the -new column. Uses Python indexing, so to insert at the top, -use ypos=0. If not given, data will be inserted at the end -of the column.

  • -
-
-
Keyword Arguments
-

keywods as per EvCell.__init__. (Available) –

-
-
-
- -
-
-reformat(**kwargs)[source]
-

Change the options for the column.

-
-
Keyword Arguments
-

as per EvCell.__init__. (Keywords) –

-
-
-
- -
-
-reformat_cell(index, **kwargs)[source]
-

reformat cell at given index, keeping column options if -necessary.

-
-
Parameters
-

index (int) – Index location of the cell in the column, -starting from 0 for the first row to Nrows-1.

-
-
Keyword Arguments
-

as per EvCell.__init__. (Keywords) –

-
-
-
- -
- -
-
-class evennia.utils.evtable.EvTable(*args, **kwargs)[source]
-

Bases: object

-

The table class holds a list of EvColumns, each consisting of EvCells so -that the result is a 2D matrix.

-
-
-__init__(*args, **kwargs)[source]
-
-
Parameters
-

texts for the table. (Header) –

-
-
Keyword Arguments
-
    -
  • table (list of lists or list of EvColumns, optional) – This is used to build the table in a quick way. If not -given, the table will start out empty and add_ methods -need to be used to add rows/columns.

  • -
  • header (bool, optional) – True/False - turn off the -header texts (*args) being treated as a header (such as -not adding extra underlining)

  • -
  • pad_width (int, optional) – How much empty space to pad your cells with -(default is 1)

  • -
  • border (str, optional)) – The border style to use. This is one of -- None - No border drawing at all. -- “table” - only a border around the whole table. -- “tablecols” - table and column borders. (default) -- “header” - only border under header. -- “cols” - only vertical borders. -- “incols” - vertical borders, no outer edges. -- “rows” - only borders between rows. -- “cells” - border around all cells.

  • -
  • border_width (int, optional) – Width of table borders, if border is active. -Note that widths wider than 1 may give artifacts in the corners. Default is 1.

  • -
  • corner_char (str, optional) – Character to use in corners when border is active. -Default is +.

  • -
  • corner_top_left_char (str, optional) – Character used for “nw” corner of table. -Defaults to corner_char.

  • -
  • corner_top_right_char (str, optional) – Character used for “ne” corner of table. -Defaults to corner_char.

  • -
  • corner_bottom_left_char (str, optional) – Character used for “sw” corner of table. -Defaults to corner_char.

  • -
  • corner_bottom_right_char (str, optional) – Character used for “se” corner of table. -Defaults to corner_char.

  • -
  • pretty_corners (bool, optional) – Use custom characters to -make the table corners look “rounded”. Uses UTF-8 -characters. Defaults to False for maximum compatibility with various displays -that may occationally have issues with UTF-8 characters.

  • -
  • header_line_char (str, optional) – Character to use for underlining -the header row (default is ‘~’). Requires border to not be None.

  • -
  • width (int, optional) – Fixed width of table. If not set, -width is set by the total width of each column. This will -resize individual columns in the vertical direction to fit.

  • -
  • height (int, optional) – Fixed height of table. Defaults to being unset. Width is -still given precedence. If given, table cells will crop text rather -than expand vertically.

  • -
  • evenwidth (bool, optional) – Used with the width keyword. Adjusts columns to have as even width as -possible. This often looks best also for mixed-length tables. Default is False.

  • -
  • maxwidth (int, optional) – This will set a maximum width -of the table while allowing it to be smaller. Only if it grows wider than this -size will it be resized by expanding horizontally (or crop height is given). -This keyword has no meaning if width is set.

  • -
-
-
Raises
-

Exception – If given erroneous input or width settings for the data.

-
-
-

Notes

-

Beyond those table-specific keywords, the non-overlapping keywords -of EvCell.__init__ are also available. These will be passed down -to every cell in the table.

-
- -
-
-add_header(*args, **kwargs)[source]
-

Add header to table. This is a number of texts to be put at -the top of the table. They will replace an existing header.

-
-
Parameters
-

args (str) – These strings will be used as the header texts.

-
-
Keyword Arguments
-
    -
  • keywords as per EvTable.__init__. Will be applied (Same) –

  • -
  • the new header's cells. (to) –

  • -
-
-
-
- -
-
-add_column(*args, **kwargs)[source]
-

Add a column to table. If there are more rows in new column -than there are rows in the current table, the table will -expand with empty rows in the other columns. If too few, the -new column with get new empty rows. All filling rows are added -to the end.

-
-
Parameters
-
    -
  • args (EvColumn or multiple strings) – Either a single EvColumn instance or -a number of data string arguments to be used to create a new column.

  • -
  • header (str, optional) – The header text for the column

  • -
  • xpos (int, optional) – Index position in table before which -to input new column. If not given, column will be added to the end -of the table. Uses Python indexing (so first column is xpos=0)

  • -
-
-
Keyword Arguments
-

keywords as per Cell.__init__. (Other) –

-
-
-
- -
-
-add_row(*args, **kwargs)[source]
-

Add a row to table (not a header). If there are more cells in -the given row than there are cells in the current table the -table will be expanded with empty columns to match. These will -be added to the end of the table. In the same way, adding a -line with too few cells will lead to the last ones getting -padded.

-
-
Parameters
-
    -
  • args (str) – Any number of string argumnets to use as the -data in the row (one cell per argument).

  • -
  • ypos (int, optional) – Index position in table before which to -input new row. If not given, will be added to the end of the table. -Uses Python indexing (so first row is ypos=0)

  • -
-
-
Keyword Arguments
-

keywords are as per EvCell.__init__. (Other) –

-
-
-
- -
-
-reformat(**kwargs)[source]
-

Force a re-shape of the entire table.

-
-
Keyword Arguments
-

options as per EvTable.__init__. (Table) –

-
-
-
- -
-
-reformat_column(index, **kwargs)[source]
-

Sends custom options to a specific column in the table.

-
-
Parameters
-

index (int) – Which column to reformat. The column index is -given from 0 to Ncolumns-1.

-
-
Keyword Arguments
-

options as per EvCell.__init__. (Column) –

-
-
Raises
-

Exception – if an invalid index is found.

-
-
-
- -
-
-get()[source]
-

Return lines of table as a list.

-
-
Returns
-

table_lines (list) – The lines of the table, in order.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.gametime.html b/docs/0.9.5/api/evennia.utils.gametime.html deleted file mode 100644 index 0eb877148a..0000000000 --- a/docs/0.9.5/api/evennia.utils.gametime.html +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - - - - evennia.utils.gametime — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.gametime

-

The gametime module handles the global passage of time in the mud.

-

It also supplies some useful methods to convert between -in-mud time and real-world time as well allows to get the -total runtime of the server and the current uptime.

-
-
-class evennia.utils.gametime.TimeScript(*args, **kwargs)[source]
-

Bases: evennia.scripts.scripts.DefaultScript

-

Gametime-sensitive script.

-
-
-at_script_creation()[source]
-

The script is created.

-
- -
-
-at_repeat()[source]
-

Call the callback and reset interval.

-
- -
-
-exception DoesNotExist
-

Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

-
- -
-
-path = 'evennia.utils.gametime.TimeScript'
-
- -
-
-typename = 'TimeScript'
-
- -
- -
-
-evennia.utils.gametime.runtime()[source]
-

Get the total runtime of the server since first start (minus -downtimes)

-
-
Parameters
-

format (bool, optional) – Format into a time representation.

-
-
Returns
-

time (float or tuple)

-
-
The runtime or the same time split up

into time units.

-
-
-

-
-
-
- -
-
-evennia.utils.gametime.server_epoch()[source]
-

Get the server epoch. We may need to calculate this on the fly.

-
- -
-
-evennia.utils.gametime.uptime()[source]
-

Get the current uptime of the server since last reload

-
-
Parameters
-

format (bool, optional) – Format into time representation.

-
-
Returns
-

time (float or tuple)

-
-
The uptime or the same time split up

into time units.

-
-
-

-
-
-
- -
-
-evennia.utils.gametime.portal_uptime()[source]
-

Get the current uptime of the portal.

-
-
Returns
-

time (float) – The uptime of the portal.

-
-
-
- -
-
-evennia.utils.gametime.game_epoch()[source]
-

Get the game epoch.

-
- -
-
-evennia.utils.gametime.gametime(absolute=False)[source]
-

Get the total gametime of the server since first start (minus downtimes)

-
-
Parameters
-

absolute (bool, optional) – Get the absolute game time, including -the epoch. This could be converted to an absolute in-game -date.

-
-
Returns
-

time (float) – The gametime as a virtual timestamp.

-
-
-

Notes

-

If one is using a standard calendar, one could convert the unformatted -return to a date using Python’s standard datetime module like this: -datetime.datetime.fromtimestamp(gametime(absolute=True))

-
- -
-
-evennia.utils.gametime.real_seconds_until(sec=None, min=None, hour=None, day=None, month=None, year=None)[source]
-

Return the real seconds until game time.

-
-
Parameters
-
    -
  • sec (int or None) – number of absolute seconds.

  • -
  • min (int or None) – number of absolute minutes.

  • -
  • hour (int or None) – number of absolute hours.

  • -
  • day (int or None) – number of absolute days.

  • -
  • month (int or None) – number of absolute months.

  • -
  • year (int or None) – number of absolute years.

  • -
-
-
Returns
-

The number of real seconds before the given game time is up.

-
-
-

Example

-

real_seconds_until(hour=5, min=10, sec=0)

-

If the game time is 5:00, TIME_FACTOR is set to 2 and you ask -the number of seconds until it’s 5:10, then this function should -return 300 (5 minutes).

-
- -
-
-evennia.utils.gametime.schedule(callback, repeat=False, sec=None, min=None, hour=None, day=None, month=None, year=None)[source]
-

Call a callback at a given in-game time.

-
-
Parameters
-
    -
  • callback (function) – The callback function that will be called. Note -that the callback must be a module-level function, since the script will -be persistent.

  • -
  • repeat (bool, optional) – Defines if the callback should be called regularly -at the specified time.

  • -
  • sec (int or None) – Number of absolute game seconds at which to run repeat.

  • -
  • min (int or None) – Number of absolute minutes.

  • -
  • hour (int or None) – Number of absolute hours.

  • -
  • day (int or None) – Number of absolute days.

  • -
  • month (int or None) – Number of absolute months.

  • -
  • year (int or None) – Number of absolute years.

  • -
-
-
Returns
-

script (Script) – The created Script handling the sceduling.

-
-
-

Examples

-

schedule(func, min=5, sec=0) # Will call 5 minutes past the next (in-game) hour. -schedule(func, hour=2, min=30, sec=0) # Will call the next (in-game) day at 02:30.

-
- -
-
-evennia.utils.gametime.reset_gametime()[source]
-

Resets the game time to make it start from the current time. Note that -the epoch set by settings.TIME_GAME_EPOCH will still apply.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.html b/docs/0.9.5/api/evennia.utils.html deleted file mode 100644 index 37a21ed961..0000000000 --- a/docs/0.9.5/api/evennia.utils.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - evennia.utils — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.idmapper.html b/docs/0.9.5/api/evennia.utils.idmapper.html deleted file mode 100644 index ee10a128eb..0000000000 --- a/docs/0.9.5/api/evennia.utils.idmapper.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - evennia.utils.idmapper — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.idmapper

-

The idmapper holds the main database caching mechanism.

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.idmapper.manager.html b/docs/0.9.5/api/evennia.utils.idmapper.manager.html deleted file mode 100644 index 6f40569e25..0000000000 --- a/docs/0.9.5/api/evennia.utils.idmapper.manager.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - evennia.utils.idmapper.manager — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.idmapper.manager

-

IDmapper extension to the default manager.

-
-
-class evennia.utils.idmapper.manager.SharedMemoryManager(*args, **kwargs)[source]
-

Bases: django.db.models.manager.Manager

-
-
-get(*args, **kwargs)[source]
-

Data entity lookup.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.idmapper.models.html b/docs/0.9.5/api/evennia.utils.idmapper.models.html deleted file mode 100644 index cb3a068946..0000000000 --- a/docs/0.9.5/api/evennia.utils.idmapper.models.html +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - - - - evennia.utils.idmapper.models — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.idmapper.models

-

Django ID mapper

-

Modified for Evennia by making sure that no model references -leave caching unexpectedly (no use of WeakRefs).

-

Also adds cache_size() for monitoring the size of the cache.

-
-
-class evennia.utils.idmapper.models.SharedMemoryModelBase(name, bases, attrs)[source]
-

Bases: django.db.models.base.ModelBase

-
- -
-
-class evennia.utils.idmapper.models.SharedMemoryModel(*args, **kwargs)[source]
-

Bases: django.db.models.base.Model

-

Base class for idmapped objects. Inherit from this.

-
-
-objects
-
- -
-
-class Meta[source]
-

Bases: object

-
-
-abstract = False
-
- -
- -
-
-classmethod get_cached_instance(id)[source]
-

Method to retrieve a cached instance by pk value. Returns None -when not found (which will always be the case when caching is -disabled for this class). Please note that the lookup will be -done even when instance caching is disabled.

-
- -
-
-classmethod cache_instance(instance, new=False)[source]
-

Method to store an instance in the cache.

-
-
Parameters
-
    -
  • instance (Class instance) – the instance to cache.

  • -
  • new (bool, optional) – this is the first time this instance is -cached (i.e. this is not an update operation like after a -db save).

  • -
-
-
-
- -
-
-classmethod get_all_cached_instances()[source]
-

Return the objects so far cached by idmapper for this class.

-
- -
-
-classmethod flush_cached_instance(instance, force=True)[source]
-

Method to flush an instance from the cache. The instance will -always be flushed from the cache, since this is most likely -called from delete(), and we want to make sure we don’t cache -dead objects.

-
- -
-
-classmethod flush_instance_cache(force=False)[source]
-

This will clean safe objects from the cache. Use force -keyword to remove all objects, safe or not.

-
- -
-
-at_idmapper_flush()[source]
-

This is called when the idmapper cache is flushed and -allows customized actions when this happens.

-
-
Returns
-

do_flush (bool)

-
-
If True, flush this object as normal. If

False, don’t flush and expect this object to handle -the flushing on its own.

-
-
-

-
-
-
- -
-
-flush_from_cache(force=False)[source]
-

Flush this instance from the instance cache. Use -force to override the result of at_idmapper_flush() for the object.

-
- -
-
-delete(*args, **kwargs)[source]
-

Delete the object, clearing cache.

-
- -
-
-save(*args, **kwargs)[source]
-

Central database save operation.

-

Notes

-

Arguments as per Django documentation. -Calls self.at_<fieldname>_postsave(new) -(this is a wrapper set by oobhandler: -self._oob_at_<fieldname>_postsave())

-
- -
-
-path = 'evennia.utils.idmapper.models.SharedMemoryModel'
-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
-
-class evennia.utils.idmapper.models.WeakSharedMemoryModelBase(name, bases, attrs)[source]
-

Bases: evennia.utils.idmapper.models.SharedMemoryModelBase

-

Uses a WeakValue dictionary for caching instead of a regular one.

-
- -
-
-class evennia.utils.idmapper.models.WeakSharedMemoryModel(*args, **kwargs)[source]
-

Bases: evennia.utils.idmapper.models.SharedMemoryModel

-

Uses a WeakValue dictionary for caching instead of a regular one

-
-
-class Meta[source]
-

Bases: object

-
-
-abstract = False
-
- -
- -
-
-path = 'evennia.utils.idmapper.models.WeakSharedMemoryModel'
-
- -
-
-typename = 'WeakSharedMemoryModelBase'
-
- -
- -
-
-evennia.utils.idmapper.models.flush_cache(**kwargs)[source]
-

Flush idmapper cache. When doing so the cache will fire the -at_idmapper_flush hook to allow the object to optionally handle -its own flushing.

-

Uses a signal so we make sure to catch cascades.

-
- -
-
-evennia.utils.idmapper.models.flush_cached_instance(sender, instance, **kwargs)[source]
-

Flush the idmapper cache only for a given instance.

-
- -
-
-evennia.utils.idmapper.models.update_cached_instance(sender, instance, **kwargs)[source]
-

Re-cache the given instance in the idmapper cache.

-
- -
-
-evennia.utils.idmapper.models.conditional_flush(max_rmem, force=False)[source]
-

Flush the cache if the estimated memory usage exceeds max_rmem.

-

The flusher has a timeout to avoid flushing over and over -in particular situations (this means that for some setups -the memory usage will exceed the requirement and a server with -more memory is probably required for the given game).

-
-
Parameters
-
    -
  • max_rmem (int) – memory-usage estimation-treshold after which -cache is flushed.

  • -
  • force (bool, optional) – forces a flush, regardless of timeout. -Defaults to False.

  • -
-
-
-
- -
-
-evennia.utils.idmapper.models.cache_size(mb=True)[source]
-

Calculate statistics about the cache.

-

Note: we cannot get reliable memory statistics from the cache - -whereas we could do getsizof each object in cache, the result is -highly imprecise and for a large number of objects the result is -many times larger than the actual memory usage of the entire server; -Python is clearly reusing memory behind the scenes that we cannot -catch in an easy way here. Ideas are appreciated. /Griatch

-
-
Returns
-

total_num, {objclass – total_num, …}

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.idmapper.tests.html b/docs/0.9.5/api/evennia.utils.idmapper.tests.html deleted file mode 100644 index cf224ed036..0000000000 --- a/docs/0.9.5/api/evennia.utils.idmapper.tests.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - - - - - - evennia.utils.idmapper.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.idmapper.tests

-
-
-class evennia.utils.idmapper.tests.Category(id, name)[source]
-

Bases: evennia.utils.idmapper.models.SharedMemoryModel

-
-
-name
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-article_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-path = 'evennia.utils.idmapper.tests.Category'
-
- -
-
-regulararticle_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
-
-class evennia.utils.idmapper.tests.RegularCategory(id, name)[source]
-

Bases: django.db.models.base.Model

-
-
-name
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-article_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-objects = <django.db.models.manager.Manager object>
-
- -
-
-regulararticle_set
-

Accessor to the related objects manager on the reverse side of a -many-to-one relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Parent.children** is a **ReverseManyToOneDescriptor** instance.

-

Most of the implementation is delegated to a dynamically defined manager -class built by **create_forward_many_to_many_manager()** defined below.

-
- -
- -
-
-class evennia.utils.idmapper.tests.Article(id, name, category, category2)[source]
-

Bases: evennia.utils.idmapper.models.SharedMemoryModel

-
-
-name
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-category
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-category2
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-category2_id
-
- -
-
-category_id
-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-path = 'evennia.utils.idmapper.tests.Article'
-
- -
-
-typename = 'SharedMemoryModelBase'
-
- -
- -
-
-class evennia.utils.idmapper.tests.RegularArticle(id, name, category, category2)[source]
-

Bases: django.db.models.base.Model

-
-
-name
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-category
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-category2
-

Accessor to the related object on the forward side of a many-to-one or -one-to-one (via ForwardOneToOneDescriptor subclass) relation.

-

In the example:

-
class Child(Model):
-    parent = ForeignKey(Parent, related_name='children')
-
-
-

**Child.parent** is a **ForwardManyToOneDescriptor** instance.

-
- -
-
-exception DoesNotExist
-

Bases: django.core.exceptions.ObjectDoesNotExist

-
- -
-
-exception MultipleObjectsReturned
-

Bases: django.core.exceptions.MultipleObjectsReturned

-
- -
-
-category2_id
-
- -
-
-category_id
-
- -
-
-id
-

A wrapper for a deferred-loading field. When the value is read from this -object the first time, the query is executed.

-
- -
-
-objects = <django.db.models.manager.Manager object>
-
- -
- -
-
-class evennia.utils.idmapper.tests.SharedMemorysTest(methodName='runTest')[source]
-

Bases: django.test.testcases.TestCase

-
-
-setUp()[source]
-

Hook method for setting up the test fixture before exercising it.

-
- -
-
-testSharedMemoryReferences()[source]
-
- -
-
-testRegularReferences()[source]
-
- -
-
-testMixedReferences()[source]
-
- -
-
-testObjectDeletion()[source]
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.inlinefuncs.html b/docs/0.9.5/api/evennia.utils.inlinefuncs.html deleted file mode 100644 index 16b077325c..0000000000 --- a/docs/0.9.5/api/evennia.utils.inlinefuncs.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - evennia.utils.inlinefuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.inlinefuncs

-

Inline functions (nested form).

-

This parser accepts nested inlinefunctions on the form

-
$funcname(arg, arg, ...)
-
-
-

embedded in any text where any arg can be another $funcname{} call. -This functionality is turned off by default - to activate, -settings.INLINEFUNC_ENABLED must be set to True.

-

Each token starts with $funcname( where there must be no space between the -$funcname and “(”. It ends with a matched ending parentesis “)”.

-

Inside the inlinefunc definition, one can use \ to escape. This is -mainly needed for escaping commas in flowing text (which would -otherwise be interpreted as an argument separator), or to escape } -when not intended to close the function block. Enclosing text in -matched “”” (triple quotes) or ‘’’ (triple single-quotes) will -also escape everything within without needing to escape individual -characters.

-

The available inlinefuncs are defined as global-level functions in -modules defined by settings.INLINEFUNC_MODULES. They are identified -by their function name (and ignored if this name starts with _). They -should be on the following form:

-
def funcname (*args, **kwargs):
-# ...
-
-
-

Here, the arguments given to $funcname(arg1,arg2) will appear as the -*args tuple. This will be populated by the arguments given to the -inlinefunc in-game - the only part that will be available from -in-game. **kwargs are not supported from in-game but are only used -internally by Evennia to make details about the caller available to -the function. The kwarg passed to all functions is session, the -Sessionobject for the object seeing the string. This may be None if -the string is sent to a non-puppetable object. The inlinefunc should -never raise an exception.

-

There are two reserved function names:

-
    -
  • “nomatch”: This is called if the user uses a functionname that is -not registered. The nomatch function will get the name of the -not-found function as its first argument followed by the normal -arguments to the given function. If not defined the default effect is -to print <UNKNOWN> to replace the unknown function.

  • -
  • “stackfull”: This is called when the maximum nested function stack is reached. -When this happens, the original parsed string is returned and the result of -the stackfull inlinefunc is appended to the end. By default this is an -error message.

  • -
-

Syntax errors, notably not completely closing all inlinefunc blocks, will lead -to the entire string remaining unparsed.

-
-
-
-evennia.utils.inlinefuncs.random(*args, **kwargs)[source]
-

Inlinefunc. Returns a random number between -0 and 1, from 0 to a maximum value, or within a given range (inclusive).

-
-
Parameters
-
    -
  • minval (str, optional) – Minimum value. If not given, assumed 0.

  • -
  • maxval (str, optional) – Maximum value.

  • -
-
-
-
-
Keyword argumuents:

session (Session): Session getting the string.

-
-
-

Notes

-

If either of the min/maxvalue has a ‘.’ in it, a floating-point random -value will be returned. Otherwise it will be an integer value in the -given range.

-

Example

-
    -
  • $random()

  • -
  • $random(5)

  • -
  • $random(5, 10)

  • -
-
- -
-
-evennia.utils.inlinefuncs.pad(*args, **kwargs)[source]
-

Inlinefunc. Pads text to given width.

-
-
Parameters
-
    -
  • text (str, optional) – Text to pad.

  • -
  • width (str, optional) – Will be converted to integer. Width -of padding.

  • -
  • align (str, optional) – Alignment of padding; one of ‘c’, ‘l’ or ‘r’.

  • -
  • fillchar (str, optional) – Character used for padding. Defaults to a -space.

  • -
-
-
Keyword Arguments
-

session (Session) – Session performing the pad.

-
-
-

Example

-

$pad(text, width, align, fillchar)

-
- -
-
-evennia.utils.inlinefuncs.crop(*args, **kwargs)[source]
-

Inlinefunc. Crops ingoing text to given widths.

-
-
Parameters
-
    -
  • text (str, optional) – Text to crop.

  • -
  • width (str, optional) – Will be converted to an integer. Width of -crop in characters.

  • -
  • suffix (str, optional) – End string to mark the fact that a part -of the string was cropped. Defaults to […].

  • -
-
-
Keyword Arguments
-

session (Session) – Session performing the crop.

-
-
-

Example

-

$crop(text, width=78, suffix=’[…]’)

-
- -
-
-evennia.utils.inlinefuncs.space(*args, **kwargs)[source]
-

Inlinefunc. Inserts an arbitrary number of spaces. Defaults to 4 spaces.

-
-
Parameters
-

spaces (int, optional) – The number of spaces to insert.

-
-
Keyword Arguments
-

session (Session) – Session performing the crop.

-
-
-

Example

-

$space(20)

-
- -
-
-evennia.utils.inlinefuncs.clr(*args, **kwargs)[source]
-

Inlinefunc. Colorizes nested text.

-
-
Parameters
-
    -
  • startclr (str, optional) – An ANSI color abbreviation without the -prefix |, such as r (red foreground) or [r (red background).

  • -
  • text (str, optional) – Text

  • -
  • endclr (str, optional) – The color to use at the end of the string. Defaults -to |n (reset-color).

  • -
-
-
Keyword Arguments
-

session (Session) – Session object triggering inlinefunc.

-
-
-

Example

-

$clr(startclr, text, endclr)

-
- -
-
-evennia.utils.inlinefuncs.null(*args, **kwargs)[source]
-
- -
-
-evennia.utils.inlinefuncs.nomatch(name, *args, **kwargs)[source]
-

Default implementation of nomatch returns the function as-is as a string.

-
- -
-
-class evennia.utils.inlinefuncs.ParseStack(*args, **kwargs)[source]
-

Bases: list

-

Custom stack that always concatenates strings together when the -strings are added next to one another. Tuples are stored -separately and None is used to mark that a string should be broken -up into a new chunk. Below is the resulting stack after separately -appending 3 strings, None, 2 strings, a tuple and finally 2 -strings:

-

[string + string + string, -None -string + string, -tuple, -string + string]

-
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-append(item)[source]
-

The stack will merge strings, add other things as normal

-
- -
- -
-
-exception evennia.utils.inlinefuncs.InlinefuncError[source]
-

Bases: RuntimeError

-
- -
-
-evennia.utils.inlinefuncs.parse_inlinefunc(string, strip=False, available_funcs=None, stacktrace=False, **kwargs)[source]
-

Parse the incoming string.

-
-
Parameters
-
    -
  • string (str) – The incoming string to parse.

  • -
  • strip (bool, optional) – Whether to strip function calls rather than -execute them.

  • -
  • available_funcs (dict, optional) – Define an alternative source of functions to parse for. -If unset, use the functions found through settings.INLINEFUNC_MODULES.

  • -
  • stacktrace (bool, optional) – If set, print the stacktrace to log.

  • -
-
-
Keyword Arguments
-
    -
  • session (Session) – This is sent to this function by Evennia when triggering -it. It is passed to the inlinefunc.

  • -
  • kwargs (any) – All other kwargs are also passed on to the inlinefunc.

  • -
-
-
-
- -
-
-evennia.utils.inlinefuncs.raw(string)[source]
-

Escape all inlinefuncs in a string so they won’t get parsed.

-
-
Parameters
-

string (str) – String with inlinefuncs to escape.

-
-
-
- -
-
-exception evennia.utils.inlinefuncs.NickTemplateInvalid[source]
-

Bases: ValueError

-
- -
-
-evennia.utils.inlinefuncs.initialize_nick_templates(in_template, out_template)[source]
-

Initialize the nick templates for matching and remapping a string.

-
-
Parameters
-
    -
  • in_template (str) – The template to be used for nick recognition.

  • -
  • out_template (str) – The template to be used to replace the string -matched by the in_template.

  • -
-
-
Returns
-

regex, template (regex, str) – Regex to match against strings and a -template with markers {arg1}, {arg2}, etc for replacement using the -standard .format method.

-
-
Raises
-

inlinefuncs.NickTemplateInvalid – If the in/out template does not have a matching -number of $args.

-
-
-
- -
-
-evennia.utils.inlinefuncs.parse_nick_template(string, template_regex, outtemplate)[source]
-

Parse a text using a template and map it to another template

-
-
Parameters
-
    -
  • string (str) – The input string to processj

  • -
  • template_regex (regex) – A template regex created with -initialize_nick_template.

  • -
  • outtemplate (str) – The template to which to map the matches -produced by the template_regex. This should have $1, $2, -etc to match the regex.

  • -
-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.logger.html b/docs/0.9.5/api/evennia.utils.logger.html deleted file mode 100644 index 051832e741..0000000000 --- a/docs/0.9.5/api/evennia.utils.logger.html +++ /dev/null @@ -1,468 +0,0 @@ - - - - - - - - - evennia.utils.logger — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.logger

-

Logging facilities

-

These are thin wrappers on top of Twisted’s logging facilities; logs -are all directed either to stdout (if Evennia is running in -interactive mode) or to $GAME_DIR/server/logs.

-

The log_file() function uses its own threading system to log to -arbitrary files in $GAME_DIR/server/logs.

-

Note: All logging functions have two aliases, log_type() and -log_typemsg(). This is for historical, back-compatible reasons.

-
-
-evennia.utils.logger.timeformat(when=None)[source]
-

This helper function will format the current time in the same -way as the twisted logger does, including time zone info. Only -difference from official logger is that we only use two digits -for the year and don’t show timezone for CET times.

-
-
Parameters
-

when (int, optional) – This is a time in POSIX seconds on the form -given by time.time(). If not given, this function will -use the current time.

-
-
Returns
-

timestring (str) – A formatted string of the given time.

-
-
-
- -
-
-class evennia.utils.logger.WeeklyLogFile(name, directory, defaultMode=None, day_rotation=7, max_size=1000000)[source]
-

Bases: twisted.python.logfile.DailyLogFile

-

Log file that rotates once per week by default. Overrides key methods to change format.

-
-
-__init__(name, directory, defaultMode=None, day_rotation=7, max_size=1000000)[source]
-
-
Parameters
-
    -
  • name (str) – Name of log file.

  • -
  • directory (str) – Directory holding the file.

  • -
  • defaultMode (str) – Permissions used to create file. Defaults to -current permissions of this file if it exists.

  • -
  • day_rotation (int) – How often to rotate the file.

  • -
  • max_size (int) – Max size of log file before rotation (regardless of -time). Defaults to 1M.

  • -
-
-
-
- -
-
-shouldRotate()[source]
-

Rotate when the date has changed since last write

-
- -
-
-suffix(tupledate)[source]
-

Return the suffix given a (year, month, day) tuple or unixtime. -Format changed to have 03 for march instead of 3 etc (retaining unix -file order)

-

If we get duplicate suffixes in location (due to hitting size limit), -we append __1, __2 etc.

-

Examples

-

server.log.2020_01_29 -server.log.2020_01_29__1 -server.log.2020_01_29__2

-
- -
-
-write(data)[source]
-

Write data to log file

-
- -
- -
-
-class evennia.utils.logger.PortalLogObserver(f)[source]
-

Bases: twisted.python.log.FileLogObserver

-

Reformat logging

-
-
-timeFormat: Optional[str] = None
-
- -
-
-prefix = ' |Portal| '
-
- -
-
-emit(eventDict)[source]
-

Copied from Twisted parent, to change logging output

-
- -
- -
-
-class evennia.utils.logger.ServerLogObserver(f)[source]
-

Bases: evennia.utils.logger.PortalLogObserver

-
-
-prefix = ' '
-
- -
- -
-
-evennia.utils.logger.log_msg(msg)[source]
-

Wrapper around log.msg call to catch any exceptions that might -occur in logging. If an exception is raised, we’ll print to -stdout instead.

-
-
Parameters
-

msg – The message that was passed to log.msg

-
-
-
- -
-
-evennia.utils.logger.log_trace(errmsg=None)[source]
-

Log a traceback to the log. This should be called from within an -exception.

-
-
Parameters
-

errmsg (str, optional) – Adds an extra line with added info -at the end of the traceback in the log.

-
-
-
- -
-
-evennia.utils.logger.log_tracemsg(errmsg=None)
-

Log a traceback to the log. This should be called from within an -exception.

-
-
Parameters
-

errmsg (str, optional) – Adds an extra line with added info -at the end of the traceback in the log.

-
-
-
- -
-
-evennia.utils.logger.log_err(errmsg)[source]
-

Prints/logs an error message to the server log.

-
-
Parameters
-

errmsg (str) – The message to be logged.

-
-
-
- -
-
-evennia.utils.logger.log_errmsg(errmsg)
-

Prints/logs an error message to the server log.

-
-
Parameters
-

errmsg (str) – The message to be logged.

-
-
-
- -
-
-evennia.utils.logger.log_server(servermsg)[source]
-

This is for the Portal to log captured Server stdout messages (it’s -usually only used during startup, before Server log is open)

-
- -
-
-evennia.utils.logger.log_warn(warnmsg)[source]
-

Prints/logs any warnings that aren’t critical but should be noted.

-
-
Parameters
-

warnmsg (str) – The message to be logged.

-
-
-
- -
-
-evennia.utils.logger.log_warnmsg(warnmsg)
-

Prints/logs any warnings that aren’t critical but should be noted.

-
-
Parameters
-

warnmsg (str) – The message to be logged.

-
-
-
- -
-
-evennia.utils.logger.log_info(infomsg)[source]
-

Prints any generic debugging/informative info that should appear in the log.

-

infomsg: (string) The message to be logged.

-
- -
-
-evennia.utils.logger.log_infomsg(infomsg)
-

Prints any generic debugging/informative info that should appear in the log.

-

infomsg: (string) The message to be logged.

-
- -
-
-evennia.utils.logger.log_dep(depmsg)[source]
-

Prints a deprecation message.

-
-
Parameters
-

depmsg (str) – The deprecation message to log.

-
-
-
- -
-
-evennia.utils.logger.log_depmsg(depmsg)
-

Prints a deprecation message.

-
-
Parameters
-

depmsg (str) – The deprecation message to log.

-
-
-
- -
-
-evennia.utils.logger.log_sec(secmsg)[source]
-

Prints a security-related message.

-
-
Parameters
-

secmsg (str) – The security message to log.

-
-
-
- -
-
-evennia.utils.logger.log_secmsg(secmsg)
-

Prints a security-related message.

-
-
Parameters
-

secmsg (str) – The security message to log.

-
-
-
- -
-
-class evennia.utils.logger.EvenniaLogFile(name, directory, rotateLength=1000000, defaultMode=None, maxRotatedFiles=None)[source]
-

Bases: twisted.python.logfile.LogFile

-

A rotating logfile based off Twisted’s LogFile. It overrides -the LogFile’s rotate method in order to append some of the last -lines of the previous log to the start of the new log, in order -to preserve a continuous chat history for channel log files.

-
-
-settings = <LazySettings "evennia.settings_default">
-
- -
-
-num_lines_to_append = 20
-
- -
-
-rotate()[source]
-

Rotates our log file and appends some number of lines from -the previous log to the start of the new one.

-
- -
-
-seek(*args, **kwargs)[source]
-

Convenience method for accessing our _file attribute’s seek method, -which is used in tail_log_function. -:param *args: Same args as file.seek -:param **kwargs: Same kwargs as file.seek

-
- -
-
-readlines(*args, **kwargs)[source]
-

Convenience method for accessing our _file attribute’s readlines method, -which is used in tail_log_function. -:param *args: same args as file.readlines -:param **kwargs: same kwargs as file.readlines

-
-
Returns
-

lines (list) – lines from our _file attribute.

-
-
-
- -
- -
-
-evennia.utils.logger.log_file(msg, filename='game.log')[source]
-

Arbitrary file logger using threads.

-
-
Parameters
-
    -
  • msg (str) – String to append to logfile.

  • -
  • filename (str, optional) – Defaults to ‘game.log’. All logs -will appear in the logs directory and log entries will start -on new lines following datetime info.

  • -
-
-
-
- -
-
-evennia.utils.logger.tail_log_file(filename, offset, nlines, callback=None)[source]
-

Return the tail of the log file.

-
-
Parameters
-
    -
  • filename (str) – The name of the log file, presumed to be in -the Evennia log dir.

  • -
  • offset (int) – The line offset from the end of the file to start -reading from. 0 means to start at the latest entry.

  • -
  • nlines (int) – How many lines to return, counting backwards -from the offset. If file is shorter, will get all lines.

  • -
  • callback (callable, optional) – A function to manage the result of the -asynchronous file access. This will get a list of lines. If unset, -the tail will happen synchronously.

  • -
-
-
Returns
-

lines (deferred or list)

-
-
This will be a deferred if callable is given,

otherwise it will be a list with The nline entries from the end of the file, or -all if the file is shorter than nlines.

-
-
-

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.optionclasses.html b/docs/0.9.5/api/evennia.utils.optionclasses.html deleted file mode 100644 index 75701f8ce3..0000000000 --- a/docs/0.9.5/api/evennia.utils.optionclasses.html +++ /dev/null @@ -1,929 +0,0 @@ - - - - - - - - - evennia.utils.optionclasses — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.optionclasses

-

Option classes store user- or server Options in a generic way -while also providing validation.

-
-
-class evennia.utils.optionclasses.BaseOption(handler, key, description, default)[source]
-

Bases: object

-

Abstract Class to deal with encapsulating individual Options. An Option has -a name/key, a description to display in relevant commands and menus, and a -default value. It saves to the owner’s Attributes using its Handler’s save -category.

-

Designed to be extremely overloadable as some options can be cantankerous.

-
-
Properties:

valid: Shortcut to the loaded VALID_HANDLER. -validator_key (str): The key of the Validator this uses.

-
-
-
-
-__init__(handler, key, description, default)[source]
-
-
Parameters
-
    -
  • handler (OptionHandler) – The OptionHandler that ‘owns’ this Option.

  • -
  • key (str) – The name this will be used for storage in a dictionary. -Must be unique per OptionHandler.

  • -
  • description (str) – What this Option’s text will show in commands and menus.

  • -
  • default – A default value for this Option.

  • -
-
-
-
- -
-
-property changed
-
- -
-
-property default
-
- -
-
-property value
-
- -
-
-set(value, **kwargs)[source]
-

Takes user input and stores appropriately. This method allows for -passing extra instructions into the validator.

-
-
Parameters
-
    -
  • value (str) – The new value of this Option.

  • -
  • kwargs (any) – Any kwargs will be passed into -self.validate(value, **kwargs) and self.save(**kwargs).

  • -
-
-
-
- -
-
-load()[source]
-

Takes the provided save data, validates it, and gets this Option ready to use.

-
-
Returns
-

Boolean – Whether loading was successful.

-
-
-
- -
-
-save(**kwargs)[source]
-

Stores the current value using .handler.save_handler(self.key, value, **kwargs) -where kwargs are a combination of those passed into this function and the -ones specified by the OptionHandler.

-
-
Keyword Arguments
-

any (any) – Not used by default. These are passed in from self.set -and allows the option to let the caller customize saving by -overriding or extend the default save kwargs

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
-
-serialize()[source]
-

Serializes the save data for Attribute storage.

-
-
Returns
-

any (any) – Whatever is best for storage.

-
-
-
- -
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-display(**kwargs)[source]
-

Renders the Option’s value as something pretty to look at.

-
-
Keyword Arguments
-

any (any) – These are options passed by the caller to potentially -customize display dynamically.

-
-
Returns
-

str

-
-
How the stored value should be projected to users (e.g. a raw

timedelta is pretty ugly).

-
-
-

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Text(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Email(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Boolean(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-display(**kwargs)[source]
-

Renders the Option’s value as something pretty to look at.

-
-
Keyword Arguments
-

any (any) – These are options passed by the caller to potentially -customize display dynamically.

-
-
Returns
-

str

-
-
How the stored value should be projected to users (e.g. a raw

timedelta is pretty ugly).

-
-
-

-
-
-
- -
-
-serialize()[source]
-

Serializes the save data for Attribute storage.

-
-
Returns
-

any (any) – Whatever is best for storage.

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Color(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-display(**kwargs)[source]
-

Renders the Option’s value as something pretty to look at.

-
-
Keyword Arguments
-

any (any) – These are options passed by the caller to potentially -customize display dynamically.

-
-
Returns
-

str

-
-
How the stored value should be projected to users (e.g. a raw

timedelta is pretty ugly).

-
-
-

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Timezone(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-property default
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
-
-serialize()[source]
-

Serializes the save data for Attribute storage.

-
-
Returns
-

any (any) – Whatever is best for storage.

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.UnsignedInteger(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validator_key = 'unsigned_integer'
-
- -
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.SignedInteger(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.PositiveInteger(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Duration(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
-
-serialize()[source]
-

Serializes the save data for Attribute storage.

-
-
Returns
-

any (any) – Whatever is best for storage.

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Datetime(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.BaseOption

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
-
-deserialize(save_data)[source]
-

Perform sanity-checking on the save data as it is loaded from storage. -This isn’t the same as what validator-functions provide (those work on -user input). For example, save data might be a timedelta or a list or -some other object.

-
-
Parameters
-

save_data – The data to check.

-
-
Returns
-

any (any)

-
-
Whatever the Option needs to track, like a string or a

datetime. The display hook is responsible for what is actually -displayed to user.

-
-
-

-
-
-
- -
-
-serialize()[source]
-

Serializes the save data for Attribute storage.

-
-
Returns
-

any (any) – Whatever is best for storage.

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Future(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.Datetime

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
- -
-
-class evennia.utils.optionclasses.Lock(handler, key, description, default)[source]
-

Bases: evennia.utils.optionclasses.Text

-
-
-validate(value, **kwargs)[source]
-

Validate user input, which is presumed to be a string.

-
-
Parameters
-
    -
  • value (str) – User input.

  • -
  • account (AccountDB) – The Account that is performing the validation. -This is necessary because of other settings which may affect the -check, such as an Account’s timezone affecting how their datetime -entries are processed.

  • -
-
-
Returns
-

any (any) – The results of the validation.

-
-
Raises
-

ValidationError – If input value failed validation.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.optionhandler.html b/docs/0.9.5/api/evennia.utils.optionhandler.html deleted file mode 100644 index 415abc8fdb..0000000000 --- a/docs/0.9.5/api/evennia.utils.optionhandler.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - - - - evennia.utils.optionhandler — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.optionhandler

-
-
-class evennia.utils.optionhandler.InMemorySaveHandler[source]
-

Bases: object

-

Fallback SaveHandler, implementing a minimum of the required save mechanism -and storing data in memory.

-
-
-__init__()[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-add(key, value=None, **kwargs)[source]
-
- -
-
-get(key, default=None, **kwargs)[source]
-
- -
- -
-
-class evennia.utils.optionhandler.OptionHandler(obj, options_dict=None, savefunc=None, loadfunc=None, save_kwargs=None, load_kwargs=None)[source]
-

Bases: object

-

This is a generic Option handler. Retrieve options either as properties on -this handler or by using the .get method.

-

This is used for Account.options but it could be used by Scripts or Objects -just as easily. All it needs to be provided is an options_dict.

-
-
-__init__(obj, options_dict=None, savefunc=None, loadfunc=None, save_kwargs=None, load_kwargs=None)[source]
-

Initialize an OptionHandler.

-
-
Parameters
-
    -
  • obj (object) – The object this handler sits on. This is usually a TypedObject.

  • -
  • options_dict (dict) – A dictionary of option keys, where the values -are options. The format of those tuples is: (‘key’, “Description to -show”, ‘option_type’, <default value>)

  • -
  • savefunc (callable) – A callable for all options to call when saving itself. -It will be called as savefunc(key, value, **save_kwargs). A common one -to pass would be AttributeHandler.add.

  • -
  • loadfunc (callable) – A callable for all options to call when loading data into -itself. It will be called as loadfunc(key, default=default, **load_kwargs). -A common one to pass would be AttributeHandler.get.

  • -
  • save_kwargs (any) – Optional extra kwargs to pass into savefunc above.

  • -
  • load_kwargs (any) – Optional extra kwargs to pass into loadfunc above.

  • -
-
-
-

Notes

-

Both loadfunc and savefunc must be specified. If only one is given, the other -will be ignored and in-memory storage will be used.

-
- -
-
-get(key, default=None, return_obj=False, raise_error=False)[source]
-

Retrieves an Option stored in the handler. Will load it if it doesn’t exist.

-
-
Parameters
-
    -
  • key (str) – The option key to retrieve.

  • -
  • default (any) – What to return if the option is defined.

  • -
  • return_obj (bool, optional) – If True, returns the actual option -object instead of its value.

  • -
  • raise_error (bool, optional) – Raise Exception if key is not found in options.

  • -
-
-
Returns
-

option_value (any or Option) – An option value the Option itself.

-
-
Raises
-

KeyError – If option is not defined.

-
-
-
- -
-
-set(key, value, **kwargs)[source]
-

Change an individual option.

-
-
Parameters
-
    -
  • key (str) – The key of an option that can be changed. Allows partial matching.

  • -
  • value (str) – The value that should be checked, coerced, and stored.:

  • -
  • kwargs (any, optional) – These are passed into the Option’s validation function, -save function and display function and allows to customize either.

  • -
-
-
Returns
-

value (any) – Value stored in option, after validation.

-
-
-
- -
-
-all(return_objs=False)[source]
-

Get all options defined on this handler.

-
-
Parameters
-

return_objs (bool, optional) – Return the actual Option objects rather -than their values.

-
-
Returns
-

all_options (dict)

-
-
All options on this handler, either {key: value}

or {key: <Option>} if return_objs is True.

-
-
-

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.picklefield.html b/docs/0.9.5/api/evennia.utils.picklefield.html deleted file mode 100644 index ee2cdd5b5c..0000000000 --- a/docs/0.9.5/api/evennia.utils.picklefield.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - evennia.utils.picklefield — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.picklefield

-

Pickle field implementation for Django.

-

Modified for Evennia by Griatch and the Evennia community.

-
-
-class evennia.utils.picklefield.PickledObject[source]
-

Bases: str

-

A subclass of string so it can be told whether a string is a pickled -object or not (if the object is an instance of this class then it must -[well, should] be a pickled one).

-

Only really useful for passing pre-encoded values to **default** -with **dbsafe_encode**, not that doing so is necessary. If you -remove PickledObject and its references, you won’t be able to pass -in pre-encoded values anymore, but you can always just pass in the -python objects themselves.

-
- -
-
-evennia.utils.picklefield.wrap_conflictual_object(obj)[source]
-
- -
-
-evennia.utils.picklefield.dbsafe_encode(value, compress_object=False, pickle_protocol=4)[source]
-
- -
-
-evennia.utils.picklefield.dbsafe_decode(value, compress_object=False)[source]
-
- -
-
-class evennia.utils.picklefield.PickledWidget(attrs=None)[source]
-

Bases: django.forms.widgets.Textarea

-

This is responsible for outputting HTML representing a given field.

-
-
-render(name, value, attrs=None, renderer=None)[source]
-

Display of the PickledField in django admin

-
- -
-
-value_from_datadict(data, files, name)[source]
-

Given a dictionary of data and this widget’s name, return the value -of this widget or None if it’s not provided.

-
- -
-
-property media
-
- -
- -
-
-class evennia.utils.picklefield.PickledFormField(*args, **kwargs)[source]
-

Bases: django.forms.fields.CharField

-

This represents one input field for the form.

-
-
-widget
-

alias of PickledWidget

-
- -
-
-default_error_messages = {'invalid': 'This is not a Python Literal. You can store things like strings, integers, or floats, but you must do it by typing them as you would type them in the Python Interpreter. For instance, strings must be surrounded by quote marks. We have converted it to a string for your convenience. If it is acceptable, please hit save again.', 'required': 'This field is required.'}
-
- -
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-clean(value)[source]
-

Validate the given value and return its “cleaned” value as an -appropriate Python object. Raise ValidationError for any errors.

-
- -
- -
-
-class evennia.utils.picklefield.PickledObjectField(*args, **kwargs)[source]
-

Bases: django.db.models.fields.Field

-

A field that will accept any python object and store it in the -database. PickledObjectField will optionally compress its values if -declared with the keyword argument **compress=True**.

-

Does not actually encode and compress **None** objects (although you -can still do lookups using None). This way, it is still possible to -use the **isnull** lookup type correctly.

-
-
-__init__(*args, **kwargs)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-get_default()[source]
-

Returns the default value for this field.

-

The default implementation on models.Field calls force_str -on the default, which means you can’t set arbitrary Python -objects as the default. To fix this, we just return the value -without calling force_str on it. Note that if you set a -callable as a default, the field will still call it. It will -not try to pickle and encode it.

-
- -
-
-from_db_value(value, *args)[source]
-

B64decode and unpickle the object, optionally decompressing it.

-

If an error is raised in de-pickling and we’re sure the value is -a definite pickle, the error is allowed to propagate. If we -aren’t sure if the value is a pickle or not, then we catch the -error and return the original value instead.

-
- -
-
-formfield(**kwargs)[source]
-

Return a django.forms.Field instance for this field.

-
- -
-
-pre_save(model_instance, add)[source]
-

Return field’s value just before saving.

-
- -
-
-get_db_prep_value(value, connection=None, prepared=False)[source]
-

Pickle and b64encode the object, optionally compressing it.

-

The pickling protocol is specified explicitly (by default 2), -rather than as -1 or HIGHEST_PROTOCOL, because we don’t want the -protocol to change over time. If it did, **exact** and **in** -lookups would likely fail, since pickle would now be generating -a different string.

-
- -
-
-value_to_string(obj)[source]
-

Return a string value of this field from the passed obj. -This is used by the serialization framework.

-
- -
-
-get_internal_type()[source]
-
- -
-
-get_db_prep_lookup(lookup_type, value, connection=None, prepared=False)[source]
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.search.html b/docs/0.9.5/api/evennia.utils.search.html deleted file mode 100644 index 4a55498a97..0000000000 --- a/docs/0.9.5/api/evennia.utils.search.html +++ /dev/null @@ -1,386 +0,0 @@ - - - - - - - - - evennia.utils.search — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.search

-

This is a convenient container gathering all the main -search methods for the various database tables.

-

It is intended to be used e.g. as

-

> from evennia.utils import search -> match = search.objects(…)

-

Note that this is not intended to be a complete listing of all search -methods! You need to refer to the respective manager to get all -possible search methods. To get to the managers from your code, import -the database model and call its ‘objects’ property.

-

Also remember that all commands in this file return lists (also if -there is only one match) unless noted otherwise.

-
-
Example: To reach the search method ‘get_object_with_account’

in evennia/objects/managers.py:

-
-
-

> from evennia.objects.models import ObjectDB -> match = Object.objects.get_object_with_account(…)

-
-
-evennia.utils.search.search_object(searchdata, attribute_name=None, typeclass=None, candidates=None, exact=True, use_dbref=True)
-

Search as an object globally or in a list of candidates and -return results. The result is always an Object. Always returns -a list.

-
-
Parameters
-
    -
  • searchdata (str or Object) – The entity to match for. This is -usually a key string but may also be an object itself. -By default (if no attribute_name is set), this will -search object.key and object.aliases in order. -Can also be on the form #dbref, which will (if -exact=True) be matched against primary key.

  • -
  • attribute_name (str) – Use this named Attribute to -match searchdata against, instead of the defaults. If -this is the name of a database field (with or without -the db_ prefix), that will be matched too.

  • -
  • typeclass (str or TypeClass) – restrict matches to objects -having this typeclass. This will help speed up global -searches.

  • -
  • candidates (list) – If supplied, search will -only be performed among the candidates in this list. A -common list of candidates is the contents of the -current location searched.

  • -
  • exact (bool) – Match names/aliases exactly or partially. -Partial matching matches the beginning of words in the -names/aliases, using a matching routine to separate -multiple matches in names with multiple components (so -“bi sw” will match “Big sword”). Since this is more -expensive than exact matching, it is recommended to be -used together with the candidates keyword to limit the -number of possibilities. This value has no meaning if -searching for attributes/properties.

  • -
  • use_dbref (bool) – If False, bypass direct lookup of a string -on the form #dbref and treat it like any string.

  • -
-
-
Returns
-

matches (list) – Matching objects

-
-
-
- -
-
-evennia.utils.search.search_account(ostring, exact=True, typeclass=None)
-

Searches for a particular account by name or -database id.

-
-
Parameters
-
    -
  • ostring (str or int) – A key string or database id.

  • -
  • exact (bool, optional) – Only valid for string matches. If -True, requires exact (non-case-sensitive) match, -otherwise also match also keys containing the ostring -(non-case-sensitive fuzzy match).

  • -
  • typeclass (str or Typeclass, optional) – Limit the search only to -accounts of this typeclass.

  • -
-
-
-
- -
-
-evennia.utils.search.search_script(ostring, obj=None, only_timed=False, typeclass=None)
-

Search for a particular script.

-
-
Parameters
-
    -
  • ostring (str) – Search criterion - a script dbef or key.

  • -
  • obj (Object, optional) – Limit search to scripts defined on -this object

  • -
  • only_timed (bool) – Limit search only to scripts that run -on a timer.

  • -
  • typeclass (class or str) – Typeclass or path to typeclass.

  • -
-
-
-
- -
-
-evennia.utils.search.search_message(sender=None, receiver=None, freetext=None, dbref=None)
-

Search the message database for particular messages. At least -one of the arguments must be given to do a search.

-
-
Parameters
-
    -
  • sender (Object or Account, optional) – Get messages sent by a particular account or object

  • -
  • receiver (Object, Account or Channel, optional) – Get messages -received by a certain account,object or channel

  • -
  • freetext (str) – Search for a text string in a message. NOTE: -This can potentially be slow, so make sure to supply one of -the other arguments to limit the search.

  • -
  • dbref (int) – The exact database id of the message. This will override -all other search criteria since it’s unique and -always gives only one match.

  • -
-
-
Returns
-

messages (list or Msg) – A list of message matches or a single match if dbref was given.

-
-
-
- -
-
-evennia.utils.search.search_channel(ostring, exact=True)
-

Search the channel database for a particular channel.

-
-
Parameters
-
    -
  • ostring (str) – The key or database id of the channel.

  • -
  • exact (bool, optional) – Require an exact (but not -case sensitive) match.

  • -
-
-
-
- -
-
-evennia.utils.search.search_help_entry(ostring, help_category=None)
-

Retrieve a search entry object.

-
-
Parameters
-
    -
  • ostring (str) – The help topic to look for.

  • -
  • category (str) – Limit the search to a particular help topic

  • -
-
-
-
- -
-
-evennia.utils.search.search_tag(key=None, category=None, tagtype=None, **kwargs)
-

Find object based on tag or category.

-
-
Parameters
-
    -
  • key (str, optional) – The tag key to search for.

  • -
  • category (str, optional) – The category of tag -to search for. If not set, uncategorized -tags will be searched.

  • -
  • tagtype (str, optional) – ‘type’ of Tag, by default -this is either None (a normal Tag), alias or -permission. This always apply to all queried tags.

  • -
  • kwargs (any) – Other optional parameter that may be supported -by the manager method.

  • -
-
-
Returns
-

matches (list)

-
-
List of Objects with tags matching

the search criteria, or an empty list if no -matches were found.

-
-
-

-
-
-
- -
-
-evennia.utils.search.search_script_tag(key=None, category=None, tagtype=None, **kwargs)[source]
-

Find script based on tag or category.

-
-
Parameters
-
    -
  • key (str, optional) – The tag key to search for.

  • -
  • category (str, optional) – The category of tag -to search for. If not set, uncategorized -tags will be searched.

  • -
  • tagtype (str, optional) – ‘type’ of Tag, by default -this is either None (a normal Tag), alias or -permission. This always apply to all queried tags.

  • -
  • kwargs (any) – Other optional parameter that may be supported -by the manager method.

  • -
-
-
Returns
-

matches (list)

-
-
List of Scripts with tags matching

the search criteria, or an empty list if no -matches were found.

-
-
-

-
-
-
- -
-
-evennia.utils.search.search_account_tag(key=None, category=None, tagtype=None, **kwargs)[source]
-

Find account based on tag or category.

-
-
Parameters
-
    -
  • key (str, optional) – The tag key to search for.

  • -
  • category (str, optional) – The category of tag -to search for. If not set, uncategorized -tags will be searched.

  • -
  • tagtype (str, optional) – ‘type’ of Tag, by default -this is either None (a normal Tag), alias or -permission. This always apply to all queried tags.

  • -
  • kwargs (any) – Other optional parameter that may be supported -by the manager method.

  • -
-
-
Returns
-

matches (list)

-
-
List of Accounts with tags matching

the search criteria, or an empty list if no -matches were found.

-
-
-

-
-
-
- -
-
-evennia.utils.search.search_channel_tag(key=None, category=None, tagtype=None, **kwargs)[source]
-

Find channel based on tag or category.

-
-
Parameters
-
    -
  • key (str, optional) – The tag key to search for.

  • -
  • category (str, optional) – The category of tag -to search for. If not set, uncategorized -tags will be searched.

  • -
  • tagtype (str, optional) – ‘type’ of Tag, by default -this is either None (a normal Tag), alias or -permission. This always apply to all queried tags.

  • -
  • kwargs (any) – Other optional parameter that may be supported -by the manager method.

  • -
-
-
Returns
-

matches (list)

-
-
List of Channels with tags matching

the search criteria, or an empty list if no -matches were found.

-
-
-

-
-
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.test_resources.html b/docs/0.9.5/api/evennia.utils.test_resources.html deleted file mode 100644 index 5cb7badcbf..0000000000 --- a/docs/0.9.5/api/evennia.utils.test_resources.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - - - evennia.utils.test_resources — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.test_resources

-

Various helper resources for writing unittests.

-
-
-evennia.utils.test_resources.mockdelay(timedelay, callback, *args, **kwargs)[source]
-
- -
-
-evennia.utils.test_resources.mockdeferLater(reactor, timedelay, callback, *args, **kwargs)[source]
-
- -
-
-evennia.utils.test_resources.unload_module(module)[source]
-

Reset import so one can mock global constants.

-
-
Parameters
-

module (module, object or str) – The module will -be removed so it will have to be imported again. If given -an object, the module in which that object sits will be unloaded. A string -should directly give the module pathname to unload.

-
-
-

Example

-
# (in a test method)
-unload_module(foo)
-with mock.patch("foo.GLOBALTHING", "mockval"):
-    import foo
-    ... # test code using foo.GLOBALTHING, now set to 'mockval'
-
-
-

Notes

-

This allows for mocking constants global to the module, since -otherwise those would not be mocked (since a module is only -loaded once).

-
- -
-
-class evennia.utils.test_resources.EvenniaTest(methodName='runTest')[source]
-

Bases: django.test.testcases.TestCase

-

Base test for Evennia, sets up a basic environment.

-
-
-account_typeclass
-

alias of evennia.accounts.accounts.DefaultAccount

-
- -
-
-object_typeclass
-

alias of evennia.objects.objects.DefaultObject

-
- -
-
-character_typeclass
-

alias of evennia.objects.objects.DefaultCharacter

-
- -
-
-exit_typeclass
-

alias of evennia.objects.objects.DefaultExit

-
- -
-
-room_typeclass
-

alias of evennia.objects.objects.DefaultRoom

-
- -
-
-script_typeclass
-

alias of evennia.scripts.scripts.DefaultScript

-
- -
-
-setUp()[source]
-

Sets up testing environment

-
- -
-
-tearDown()[source]
-

Hook method for deconstructing the test fixture after testing it.

-
- -
- -
-
-class evennia.utils.test_resources.LocalEvenniaTest(methodName='runTest')[source]
-

Bases: evennia.utils.test_resources.EvenniaTest

-

This test class is intended for inheriting in mygame tests. -It helps ensure your tests are run with your own objects.

-
-
-account_typeclass = 'typeclasses.accounts.Account'
-
- -
-
-object_typeclass = 'typeclasses.objects.Object'
-
- -
-
-character_typeclass = 'typeclasses.characters.Character'
-
- -
-
-exit_typeclass = 'typeclasses.exits.Exit'
-
- -
-
-room_typeclass = 'typeclasses.rooms.Room'
-
- -
-
-script_typeclass = 'typeclasses.scripts.Script'
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.text2html.html b/docs/0.9.5/api/evennia.utils.text2html.html deleted file mode 100644 index 71d6940a68..0000000000 --- a/docs/0.9.5/api/evennia.utils.text2html.html +++ /dev/null @@ -1,469 +0,0 @@ - - - - - - - - - evennia.utils.text2html — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.text2html

-

ANSI -> html converter

-

Credit for original idea and implementation -goes to Muhammad Alkarouri and his -snippet #577349 on http://code.activestate.com.

-

(extensively modified by Griatch 2010)

-
-
-class evennia.utils.text2html.TextToHTMLparser[source]
-

Bases: object

-

This class describes a parser for converting from ANSI to html.

-
-
-tabstop = 4
-
- -
-
-hilite = '\x1b[1m'
-
- -
-
-unhilite = '\x1b[22m'
-
- -
-
-normal = '\x1b[0m'
-
- -
-
-underline = '\x1b[4m'
-
- -
- -
- -
-
-inverse = '\x1b[7m'
-
- -
-
-colorcodes = [('color-000', '\x1b[22m\x1b[30m'), ('color-001', '\x1b[22m\x1b[31m'), ('color-002', '\x1b[22m\x1b[32m'), ('color-003', '\x1b[22m\x1b[33m'), ('color-004', '\x1b[22m\x1b[34m'), ('color-005', '\x1b[22m\x1b[35m'), ('color-006', '\x1b[22m\x1b[36m'), ('color-007', '\x1b[22m\x1b[37m'), ('color-008', '\x1b[1m\x1b[30m'), ('color-009', '\x1b[1m\x1b[31m'), ('color-010', '\x1b[1m\x1b[32m'), ('color-011', '\x1b[1m\x1b[33m'), ('color-012', '\x1b[1m\x1b[34m'), ('color-013', '\x1b[1m\x1b[35m'), ('color-014', '\x1b[1m\x1b[36m'), ('color-015', '\x1b[1m\x1b[37m'), ('color-016', '\x1b[38;5;16m'), ('color-017', '\x1b[38;5;17m'), ('color-018', '\x1b[38;5;18m'), ('color-019', '\x1b[38;5;19m'), ('color-020', '\x1b[38;5;20m'), ('color-021', '\x1b[38;5;21m'), ('color-022', '\x1b[38;5;22m'), ('color-023', '\x1b[38;5;23m'), ('color-024', '\x1b[38;5;24m'), ('color-025', '\x1b[38;5;25m'), ('color-026', '\x1b[38;5;26m'), ('color-027', '\x1b[38;5;27m'), ('color-028', '\x1b[38;5;28m'), ('color-029', '\x1b[38;5;29m'), ('color-030', '\x1b[38;5;30m'), ('color-031', '\x1b[38;5;31m'), ('color-032', '\x1b[38;5;32m'), ('color-033', '\x1b[38;5;33m'), ('color-034', '\x1b[38;5;34m'), ('color-035', '\x1b[38;5;35m'), ('color-036', '\x1b[38;5;36m'), ('color-037', '\x1b[38;5;37m'), ('color-038', '\x1b[38;5;38m'), ('color-039', '\x1b[38;5;39m'), ('color-040', '\x1b[38;5;40m'), ('color-041', '\x1b[38;5;41m'), ('color-042', '\x1b[38;5;42m'), ('color-043', '\x1b[38;5;43m'), ('color-044', '\x1b[38;5;44m'), ('color-045', '\x1b[38;5;45m'), ('color-046', '\x1b[38;5;46m'), ('color-047', '\x1b[38;5;47m'), ('color-048', '\x1b[38;5;48m'), ('color-049', '\x1b[38;5;49m'), ('color-050', '\x1b[38;5;50m'), ('color-051', '\x1b[38;5;51m'), ('color-052', '\x1b[38;5;52m'), ('color-053', '\x1b[38;5;53m'), ('color-054', '\x1b[38;5;54m'), ('color-055', '\x1b[38;5;55m'), ('color-056', '\x1b[38;5;56m'), ('color-057', '\x1b[38;5;57m'), ('color-058', '\x1b[38;5;58m'), ('color-059', '\x1b[38;5;59m'), ('color-060', '\x1b[38;5;60m'), ('color-061', '\x1b[38;5;61m'), ('color-062', '\x1b[38;5;62m'), ('color-063', '\x1b[38;5;63m'), ('color-064', '\x1b[38;5;64m'), ('color-065', '\x1b[38;5;65m'), ('color-066', '\x1b[38;5;66m'), ('color-067', '\x1b[38;5;67m'), ('color-068', '\x1b[38;5;68m'), ('color-069', '\x1b[38;5;69m'), ('color-070', '\x1b[38;5;70m'), ('color-071', '\x1b[38;5;71m'), ('color-072', '\x1b[38;5;72m'), ('color-073', '\x1b[38;5;73m'), ('color-074', '\x1b[38;5;74m'), ('color-075', '\x1b[38;5;75m'), ('color-076', '\x1b[38;5;76m'), ('color-077', '\x1b[38;5;77m'), ('color-078', '\x1b[38;5;78m'), ('color-079', '\x1b[38;5;79m'), ('color-080', '\x1b[38;5;80m'), ('color-081', '\x1b[38;5;81m'), ('color-082', '\x1b[38;5;82m'), ('color-083', '\x1b[38;5;83m'), ('color-084', '\x1b[38;5;84m'), ('color-085', '\x1b[38;5;85m'), ('color-086', '\x1b[38;5;86m'), ('color-087', '\x1b[38;5;87m'), ('color-088', '\x1b[38;5;88m'), ('color-089', '\x1b[38;5;89m'), ('color-090', '\x1b[38;5;90m'), ('color-091', '\x1b[38;5;91m'), ('color-092', '\x1b[38;5;92m'), ('color-093', '\x1b[38;5;93m'), ('color-094', '\x1b[38;5;94m'), ('color-095', '\x1b[38;5;95m'), ('color-096', '\x1b[38;5;96m'), ('color-097', '\x1b[38;5;97m'), ('color-098', '\x1b[38;5;98m'), ('color-099', '\x1b[38;5;99m'), ('color-100', '\x1b[38;5;100m'), ('color-101', '\x1b[38;5;101m'), ('color-102', '\x1b[38;5;102m'), ('color-103', '\x1b[38;5;103m'), ('color-104', '\x1b[38;5;104m'), ('color-105', '\x1b[38;5;105m'), ('color-106', '\x1b[38;5;106m'), ('color-107', '\x1b[38;5;107m'), ('color-108', '\x1b[38;5;108m'), ('color-109', '\x1b[38;5;109m'), ('color-110', '\x1b[38;5;110m'), ('color-111', '\x1b[38;5;111m'), ('color-112', '\x1b[38;5;112m'), ('color-113', '\x1b[38;5;113m'), ('color-114', '\x1b[38;5;114m'), ('color-115', '\x1b[38;5;115m'), ('color-116', '\x1b[38;5;116m'), ('color-117', '\x1b[38;5;117m'), ('color-118', '\x1b[38;5;118m'), ('color-119', '\x1b[38;5;119m'), ('color-120', '\x1b[38;5;120m'), ('color-121', '\x1b[38;5;121m'), ('color-122', '\x1b[38;5;122m'), ('color-123', '\x1b[38;5;123m'), ('color-124', '\x1b[38;5;124m'), ('color-125', '\x1b[38;5;125m'), ('color-126', '\x1b[38;5;126m'), ('color-127', '\x1b[38;5;127m'), ('color-128', '\x1b[38;5;128m'), ('color-129', '\x1b[38;5;129m'), ('color-130', '\x1b[38;5;130m'), ('color-131', '\x1b[38;5;131m'), ('color-132', '\x1b[38;5;132m'), ('color-133', '\x1b[38;5;133m'), ('color-134', '\x1b[38;5;134m'), ('color-135', '\x1b[38;5;135m'), ('color-136', '\x1b[38;5;136m'), ('color-137', '\x1b[38;5;137m'), ('color-138', '\x1b[38;5;138m'), ('color-139', '\x1b[38;5;139m'), ('color-140', '\x1b[38;5;140m'), ('color-141', '\x1b[38;5;141m'), ('color-142', '\x1b[38;5;142m'), ('color-143', '\x1b[38;5;143m'), ('color-144', '\x1b[38;5;144m'), ('color-145', '\x1b[38;5;145m'), ('color-146', '\x1b[38;5;146m'), ('color-147', '\x1b[38;5;147m'), ('color-148', '\x1b[38;5;148m'), ('color-149', '\x1b[38;5;149m'), ('color-150', '\x1b[38;5;150m'), ('color-151', '\x1b[38;5;151m'), ('color-152', '\x1b[38;5;152m'), ('color-153', '\x1b[38;5;153m'), ('color-154', '\x1b[38;5;154m'), ('color-155', '\x1b[38;5;155m'), ('color-156', '\x1b[38;5;156m'), ('color-157', '\x1b[38;5;157m'), ('color-158', '\x1b[38;5;158m'), ('color-159', '\x1b[38;5;159m'), ('color-160', '\x1b[38;5;160m'), ('color-161', '\x1b[38;5;161m'), ('color-162', '\x1b[38;5;162m'), ('color-163', '\x1b[38;5;163m'), ('color-164', '\x1b[38;5;164m'), ('color-165', '\x1b[38;5;165m'), ('color-166', '\x1b[38;5;166m'), ('color-167', '\x1b[38;5;167m'), ('color-168', '\x1b[38;5;168m'), ('color-169', '\x1b[38;5;169m'), ('color-170', '\x1b[38;5;170m'), ('color-171', '\x1b[38;5;171m'), ('color-172', '\x1b[38;5;172m'), ('color-173', '\x1b[38;5;173m'), ('color-174', '\x1b[38;5;174m'), ('color-175', '\x1b[38;5;175m'), ('color-176', '\x1b[38;5;176m'), ('color-177', '\x1b[38;5;177m'), ('color-178', '\x1b[38;5;178m'), ('color-179', '\x1b[38;5;179m'), ('color-180', '\x1b[38;5;180m'), ('color-181', '\x1b[38;5;181m'), ('color-182', '\x1b[38;5;182m'), ('color-183', '\x1b[38;5;183m'), ('color-184', '\x1b[38;5;184m'), ('color-185', '\x1b[38;5;185m'), ('color-186', '\x1b[38;5;186m'), ('color-187', '\x1b[38;5;187m'), ('color-188', '\x1b[38;5;188m'), ('color-189', '\x1b[38;5;189m'), ('color-190', '\x1b[38;5;190m'), ('color-191', '\x1b[38;5;191m'), ('color-192', '\x1b[38;5;192m'), ('color-193', '\x1b[38;5;193m'), ('color-194', '\x1b[38;5;194m'), ('color-195', '\x1b[38;5;195m'), ('color-196', '\x1b[38;5;196m'), ('color-197', '\x1b[38;5;197m'), ('color-198', '\x1b[38;5;198m'), ('color-199', '\x1b[38;5;199m'), ('color-200', '\x1b[38;5;200m'), ('color-201', '\x1b[38;5;201m'), ('color-202', '\x1b[38;5;202m'), ('color-203', '\x1b[38;5;203m'), ('color-204', '\x1b[38;5;204m'), ('color-205', '\x1b[38;5;205m'), ('color-206', '\x1b[38;5;206m'), ('color-207', '\x1b[38;5;207m'), ('color-208', '\x1b[38;5;208m'), ('color-209', '\x1b[38;5;209m'), ('color-210', '\x1b[38;5;210m'), ('color-211', '\x1b[38;5;211m'), ('color-212', '\x1b[38;5;212m'), ('color-213', '\x1b[38;5;213m'), ('color-214', '\x1b[38;5;214m'), ('color-215', '\x1b[38;5;215m'), ('color-216', '\x1b[38;5;216m'), ('color-217', '\x1b[38;5;217m'), ('color-218', '\x1b[38;5;218m'), ('color-219', '\x1b[38;5;219m'), ('color-220', '\x1b[38;5;220m'), ('color-221', '\x1b[38;5;221m'), ('color-222', '\x1b[38;5;222m'), ('color-223', '\x1b[38;5;223m'), ('color-224', '\x1b[38;5;224m'), ('color-225', '\x1b[38;5;225m'), ('color-226', '\x1b[38;5;226m'), ('color-227', '\x1b[38;5;227m'), ('color-228', '\x1b[38;5;228m'), ('color-229', '\x1b[38;5;229m'), ('color-230', '\x1b[38;5;230m'), ('color-231', '\x1b[38;5;231m'), ('color-232', '\x1b[38;5;232m'), ('color-233', '\x1b[38;5;233m'), ('color-234', '\x1b[38;5;234m'), ('color-235', '\x1b[38;5;235m'), ('color-236', '\x1b[38;5;236m'), ('color-237', '\x1b[38;5;237m'), ('color-238', '\x1b[38;5;238m'), ('color-239', '\x1b[38;5;239m'), ('color-240', '\x1b[38;5;240m'), ('color-241', '\x1b[38;5;241m'), ('color-242', '\x1b[38;5;242m'), ('color-243', '\x1b[38;5;243m'), ('color-244', '\x1b[38;5;244m'), ('color-245', '\x1b[38;5;245m'), ('color-246', '\x1b[38;5;246m'), ('color-247', '\x1b[38;5;247m'), ('color-248', '\x1b[38;5;248m'), ('color-249', '\x1b[38;5;249m'), ('color-250', '\x1b[38;5;250m'), ('color-251', '\x1b[38;5;251m'), ('color-252', '\x1b[38;5;252m'), ('color-253', '\x1b[38;5;253m'), ('color-254', '\x1b[38;5;254m'), ('color-255', '\x1b[38;5;255m')]
-
- -
-
-colorback = [('bgcolor-000', '\x1b[40m'), ('bgcolor-001', '\x1b[41m'), ('bgcolor-002', '\x1b[42m'), ('bgcolor-003', '\x1b[43m'), ('bgcolor-004', '\x1b[44m'), ('bgcolor-005', '\x1b[45m'), ('bgcolor-006', '\x1b[46m'), ('bgcolor-007', '\x1b[47m'), ('bgcolor-008', '\x1b[1m\x1b[40m'), ('bgcolor-009', '\x1b[1m\x1b[41m'), ('bgcolor-010', '\x1b[1m\x1b[42m'), ('bgcolor-011', '\x1b[1m\x1b[43m'), ('bgcolor-012', '\x1b[1m\x1b[44m'), ('bgcolor-013', '\x1b[1m\x1b[45m'), ('bgcolor-014', '\x1b[1m\x1b[46m'), ('bgcolor-015', '\x1b[1m\x1b[47m'), ('bgcolor-016', '\x1b[48;5;16m'), ('bgcolor-017', '\x1b[48;5;17m'), ('bgcolor-018', '\x1b[48;5;18m'), ('bgcolor-019', '\x1b[48;5;19m'), ('bgcolor-020', '\x1b[48;5;20m'), ('bgcolor-021', '\x1b[48;5;21m'), ('bgcolor-022', '\x1b[48;5;22m'), ('bgcolor-023', '\x1b[48;5;23m'), ('bgcolor-024', '\x1b[48;5;24m'), ('bgcolor-025', '\x1b[48;5;25m'), ('bgcolor-026', '\x1b[48;5;26m'), ('bgcolor-027', '\x1b[48;5;27m'), ('bgcolor-028', '\x1b[48;5;28m'), ('bgcolor-029', '\x1b[48;5;29m'), ('bgcolor-030', '\x1b[48;5;30m'), ('bgcolor-031', '\x1b[48;5;31m'), ('bgcolor-032', '\x1b[48;5;32m'), ('bgcolor-033', '\x1b[48;5;33m'), ('bgcolor-034', '\x1b[48;5;34m'), ('bgcolor-035', '\x1b[48;5;35m'), ('bgcolor-036', '\x1b[48;5;36m'), ('bgcolor-037', '\x1b[48;5;37m'), ('bgcolor-038', '\x1b[48;5;38m'), ('bgcolor-039', '\x1b[48;5;39m'), ('bgcolor-040', '\x1b[48;5;40m'), ('bgcolor-041', '\x1b[48;5;41m'), ('bgcolor-042', '\x1b[48;5;42m'), ('bgcolor-043', '\x1b[48;5;43m'), ('bgcolor-044', '\x1b[48;5;44m'), ('bgcolor-045', '\x1b[48;5;45m'), ('bgcolor-046', '\x1b[48;5;46m'), ('bgcolor-047', '\x1b[48;5;47m'), ('bgcolor-048', '\x1b[48;5;48m'), ('bgcolor-049', '\x1b[48;5;49m'), ('bgcolor-050', '\x1b[48;5;50m'), ('bgcolor-051', '\x1b[48;5;51m'), ('bgcolor-052', '\x1b[48;5;52m'), ('bgcolor-053', '\x1b[48;5;53m'), ('bgcolor-054', '\x1b[48;5;54m'), ('bgcolor-055', '\x1b[48;5;55m'), ('bgcolor-056', '\x1b[48;5;56m'), ('bgcolor-057', '\x1b[48;5;57m'), ('bgcolor-058', '\x1b[48;5;58m'), ('bgcolor-059', '\x1b[48;5;59m'), ('bgcolor-060', '\x1b[48;5;60m'), ('bgcolor-061', '\x1b[48;5;61m'), ('bgcolor-062', '\x1b[48;5;62m'), ('bgcolor-063', '\x1b[48;5;63m'), ('bgcolor-064', '\x1b[48;5;64m'), ('bgcolor-065', '\x1b[48;5;65m'), ('bgcolor-066', '\x1b[48;5;66m'), ('bgcolor-067', '\x1b[48;5;67m'), ('bgcolor-068', '\x1b[48;5;68m'), ('bgcolor-069', '\x1b[48;5;69m'), ('bgcolor-070', '\x1b[48;5;70m'), ('bgcolor-071', '\x1b[48;5;71m'), ('bgcolor-072', '\x1b[48;5;72m'), ('bgcolor-073', '\x1b[48;5;73m'), ('bgcolor-074', '\x1b[48;5;74m'), ('bgcolor-075', '\x1b[48;5;75m'), ('bgcolor-076', '\x1b[48;5;76m'), ('bgcolor-077', '\x1b[48;5;77m'), ('bgcolor-078', '\x1b[48;5;78m'), ('bgcolor-079', '\x1b[48;5;79m'), ('bgcolor-080', '\x1b[48;5;80m'), ('bgcolor-081', '\x1b[48;5;81m'), ('bgcolor-082', '\x1b[48;5;82m'), ('bgcolor-083', '\x1b[48;5;83m'), ('bgcolor-084', '\x1b[48;5;84m'), ('bgcolor-085', '\x1b[48;5;85m'), ('bgcolor-086', '\x1b[48;5;86m'), ('bgcolor-087', '\x1b[48;5;87m'), ('bgcolor-088', '\x1b[48;5;88m'), ('bgcolor-089', '\x1b[48;5;89m'), ('bgcolor-090', '\x1b[48;5;90m'), ('bgcolor-091', '\x1b[48;5;91m'), ('bgcolor-092', '\x1b[48;5;92m'), ('bgcolor-093', '\x1b[48;5;93m'), ('bgcolor-094', '\x1b[48;5;94m'), ('bgcolor-095', '\x1b[48;5;95m'), ('bgcolor-096', '\x1b[48;5;96m'), ('bgcolor-097', '\x1b[48;5;97m'), ('bgcolor-098', '\x1b[48;5;98m'), ('bgcolor-099', '\x1b[48;5;99m'), ('bgcolor-100', '\x1b[48;5;100m'), ('bgcolor-101', '\x1b[48;5;101m'), ('bgcolor-102', '\x1b[48;5;102m'), ('bgcolor-103', '\x1b[48;5;103m'), ('bgcolor-104', '\x1b[48;5;104m'), ('bgcolor-105', '\x1b[48;5;105m'), ('bgcolor-106', '\x1b[48;5;106m'), ('bgcolor-107', '\x1b[48;5;107m'), ('bgcolor-108', '\x1b[48;5;108m'), ('bgcolor-109', '\x1b[48;5;109m'), ('bgcolor-110', '\x1b[48;5;110m'), ('bgcolor-111', '\x1b[48;5;111m'), ('bgcolor-112', '\x1b[48;5;112m'), ('bgcolor-113', '\x1b[48;5;113m'), ('bgcolor-114', '\x1b[48;5;114m'), ('bgcolor-115', '\x1b[48;5;115m'), ('bgcolor-116', '\x1b[48;5;116m'), ('bgcolor-117', '\x1b[48;5;117m'), ('bgcolor-118', '\x1b[48;5;118m'), ('bgcolor-119', '\x1b[48;5;119m'), ('bgcolor-120', '\x1b[48;5;120m'), ('bgcolor-121', '\x1b[48;5;121m'), ('bgcolor-122', '\x1b[48;5;122m'), ('bgcolor-123', '\x1b[48;5;123m'), ('bgcolor-124', '\x1b[48;5;124m'), ('bgcolor-125', '\x1b[48;5;125m'), ('bgcolor-126', '\x1b[48;5;126m'), ('bgcolor-127', '\x1b[48;5;127m'), ('bgcolor-128', '\x1b[48;5;128m'), ('bgcolor-129', '\x1b[48;5;129m'), ('bgcolor-130', '\x1b[48;5;130m'), ('bgcolor-131', '\x1b[48;5;131m'), ('bgcolor-132', '\x1b[48;5;132m'), ('bgcolor-133', '\x1b[48;5;133m'), ('bgcolor-134', '\x1b[48;5;134m'), ('bgcolor-135', '\x1b[48;5;135m'), ('bgcolor-136', '\x1b[48;5;136m'), ('bgcolor-137', '\x1b[48;5;137m'), ('bgcolor-138', '\x1b[48;5;138m'), ('bgcolor-139', '\x1b[48;5;139m'), ('bgcolor-140', '\x1b[48;5;140m'), ('bgcolor-141', '\x1b[48;5;141m'), ('bgcolor-142', '\x1b[48;5;142m'), ('bgcolor-143', '\x1b[48;5;143m'), ('bgcolor-144', '\x1b[48;5;144m'), ('bgcolor-145', '\x1b[48;5;145m'), ('bgcolor-146', '\x1b[48;5;146m'), ('bgcolor-147', '\x1b[48;5;147m'), ('bgcolor-148', '\x1b[48;5;148m'), ('bgcolor-149', '\x1b[48;5;149m'), ('bgcolor-150', '\x1b[48;5;150m'), ('bgcolor-151', '\x1b[48;5;151m'), ('bgcolor-152', '\x1b[48;5;152m'), ('bgcolor-153', '\x1b[48;5;153m'), ('bgcolor-154', '\x1b[48;5;154m'), ('bgcolor-155', '\x1b[48;5;155m'), ('bgcolor-156', '\x1b[48;5;156m'), ('bgcolor-157', '\x1b[48;5;157m'), ('bgcolor-158', '\x1b[48;5;158m'), ('bgcolor-159', '\x1b[48;5;159m'), ('bgcolor-160', '\x1b[48;5;160m'), ('bgcolor-161', '\x1b[48;5;161m'), ('bgcolor-162', '\x1b[48;5;162m'), ('bgcolor-163', '\x1b[48;5;163m'), ('bgcolor-164', '\x1b[48;5;164m'), ('bgcolor-165', '\x1b[48;5;165m'), ('bgcolor-166', '\x1b[48;5;166m'), ('bgcolor-167', '\x1b[48;5;167m'), ('bgcolor-168', '\x1b[48;5;168m'), ('bgcolor-169', '\x1b[48;5;169m'), ('bgcolor-170', '\x1b[48;5;170m'), ('bgcolor-171', '\x1b[48;5;171m'), ('bgcolor-172', '\x1b[48;5;172m'), ('bgcolor-173', '\x1b[48;5;173m'), ('bgcolor-174', '\x1b[48;5;174m'), ('bgcolor-175', '\x1b[48;5;175m'), ('bgcolor-176', '\x1b[48;5;176m'), ('bgcolor-177', '\x1b[48;5;177m'), ('bgcolor-178', '\x1b[48;5;178m'), ('bgcolor-179', '\x1b[48;5;179m'), ('bgcolor-180', '\x1b[48;5;180m'), ('bgcolor-181', '\x1b[48;5;181m'), ('bgcolor-182', '\x1b[48;5;182m'), ('bgcolor-183', '\x1b[48;5;183m'), ('bgcolor-184', '\x1b[48;5;184m'), ('bgcolor-185', '\x1b[48;5;185m'), ('bgcolor-186', '\x1b[48;5;186m'), ('bgcolor-187', '\x1b[48;5;187m'), ('bgcolor-188', '\x1b[48;5;188m'), ('bgcolor-189', '\x1b[48;5;189m'), ('bgcolor-190', '\x1b[48;5;190m'), ('bgcolor-191', '\x1b[48;5;191m'), ('bgcolor-192', '\x1b[48;5;192m'), ('bgcolor-193', '\x1b[48;5;193m'), ('bgcolor-194', '\x1b[48;5;194m'), ('bgcolor-195', '\x1b[48;5;195m'), ('bgcolor-196', '\x1b[48;5;196m'), ('bgcolor-197', '\x1b[48;5;197m'), ('bgcolor-198', '\x1b[48;5;198m'), ('bgcolor-199', '\x1b[48;5;199m'), ('bgcolor-200', '\x1b[48;5;200m'), ('bgcolor-201', '\x1b[48;5;201m'), ('bgcolor-202', '\x1b[48;5;202m'), ('bgcolor-203', '\x1b[48;5;203m'), ('bgcolor-204', '\x1b[48;5;204m'), ('bgcolor-205', '\x1b[48;5;205m'), ('bgcolor-206', '\x1b[48;5;206m'), ('bgcolor-207', '\x1b[48;5;207m'), ('bgcolor-208', '\x1b[48;5;208m'), ('bgcolor-209', '\x1b[48;5;209m'), ('bgcolor-210', '\x1b[48;5;210m'), ('bgcolor-211', '\x1b[48;5;211m'), ('bgcolor-212', '\x1b[48;5;212m'), ('bgcolor-213', '\x1b[48;5;213m'), ('bgcolor-214', '\x1b[48;5;214m'), ('bgcolor-215', '\x1b[48;5;215m'), ('bgcolor-216', '\x1b[48;5;216m'), ('bgcolor-217', '\x1b[48;5;217m'), ('bgcolor-218', '\x1b[48;5;218m'), ('bgcolor-219', '\x1b[48;5;219m'), ('bgcolor-220', '\x1b[48;5;220m'), ('bgcolor-221', '\x1b[48;5;221m'), ('bgcolor-222', '\x1b[48;5;222m'), ('bgcolor-223', '\x1b[48;5;223m'), ('bgcolor-224', '\x1b[48;5;224m'), ('bgcolor-225', '\x1b[48;5;225m'), ('bgcolor-226', '\x1b[48;5;226m'), ('bgcolor-227', '\x1b[48;5;227m'), ('bgcolor-228', '\x1b[48;5;228m'), ('bgcolor-229', '\x1b[48;5;229m'), ('bgcolor-230', '\x1b[48;5;230m'), ('bgcolor-231', '\x1b[48;5;231m'), ('bgcolor-232', '\x1b[48;5;232m'), ('bgcolor-233', '\x1b[48;5;233m'), ('bgcolor-234', '\x1b[48;5;234m'), ('bgcolor-235', '\x1b[48;5;235m'), ('bgcolor-236', '\x1b[48;5;236m'), ('bgcolor-237', '\x1b[48;5;237m'), ('bgcolor-238', '\x1b[48;5;238m'), ('bgcolor-239', '\x1b[48;5;239m'), ('bgcolor-240', '\x1b[48;5;240m'), ('bgcolor-241', '\x1b[48;5;241m'), ('bgcolor-242', '\x1b[48;5;242m'), ('bgcolor-243', '\x1b[48;5;243m'), ('bgcolor-244', '\x1b[48;5;244m'), ('bgcolor-245', '\x1b[48;5;245m'), ('bgcolor-246', '\x1b[48;5;246m'), ('bgcolor-247', '\x1b[48;5;247m'), ('bgcolor-248', '\x1b[48;5;248m'), ('bgcolor-249', '\x1b[48;5;249m'), ('bgcolor-250', '\x1b[48;5;250m'), ('bgcolor-251', '\x1b[48;5;251m'), ('bgcolor-252', '\x1b[48;5;252m'), ('bgcolor-253', '\x1b[48;5;253m'), ('bgcolor-254', '\x1b[48;5;254m'), ('bgcolor-255', '\x1b[48;5;255m')]
-
- -
-
-fg_colormap = {'\x1b[1m\x1b[30m': 'color-008', '\x1b[1m\x1b[31m': 'color-009', '\x1b[1m\x1b[32m': 'color-010', '\x1b[1m\x1b[33m': 'color-011', '\x1b[1m\x1b[34m': 'color-012', '\x1b[1m\x1b[35m': 'color-013', '\x1b[1m\x1b[36m': 'color-014', '\x1b[1m\x1b[37m': 'color-015', '\x1b[22m\x1b[30m': 'color-000', '\x1b[22m\x1b[31m': 'color-001', '\x1b[22m\x1b[32m': 'color-002', '\x1b[22m\x1b[33m': 'color-003', '\x1b[22m\x1b[34m': 'color-004', '\x1b[22m\x1b[35m': 'color-005', '\x1b[22m\x1b[36m': 'color-006', '\x1b[22m\x1b[37m': 'color-007', '\x1b[38;5;100m': 'color-100', '\x1b[38;5;101m': 'color-101', '\x1b[38;5;102m': 'color-102', '\x1b[38;5;103m': 'color-103', '\x1b[38;5;104m': 'color-104', '\x1b[38;5;105m': 'color-105', '\x1b[38;5;106m': 'color-106', '\x1b[38;5;107m': 'color-107', '\x1b[38;5;108m': 'color-108', '\x1b[38;5;109m': 'color-109', '\x1b[38;5;110m': 'color-110', '\x1b[38;5;111m': 'color-111', '\x1b[38;5;112m': 'color-112', '\x1b[38;5;113m': 'color-113', '\x1b[38;5;114m': 'color-114', '\x1b[38;5;115m': 'color-115', '\x1b[38;5;116m': 'color-116', '\x1b[38;5;117m': 'color-117', '\x1b[38;5;118m': 'color-118', '\x1b[38;5;119m': 'color-119', '\x1b[38;5;120m': 'color-120', '\x1b[38;5;121m': 'color-121', '\x1b[38;5;122m': 'color-122', '\x1b[38;5;123m': 'color-123', '\x1b[38;5;124m': 'color-124', '\x1b[38;5;125m': 'color-125', '\x1b[38;5;126m': 'color-126', '\x1b[38;5;127m': 'color-127', '\x1b[38;5;128m': 'color-128', '\x1b[38;5;129m': 'color-129', '\x1b[38;5;130m': 'color-130', '\x1b[38;5;131m': 'color-131', '\x1b[38;5;132m': 'color-132', '\x1b[38;5;133m': 'color-133', '\x1b[38;5;134m': 'color-134', '\x1b[38;5;135m': 'color-135', '\x1b[38;5;136m': 'color-136', '\x1b[38;5;137m': 'color-137', '\x1b[38;5;138m': 'color-138', '\x1b[38;5;139m': 'color-139', '\x1b[38;5;140m': 'color-140', '\x1b[38;5;141m': 'color-141', '\x1b[38;5;142m': 'color-142', '\x1b[38;5;143m': 'color-143', '\x1b[38;5;144m': 'color-144', '\x1b[38;5;145m': 'color-145', '\x1b[38;5;146m': 'color-146', '\x1b[38;5;147m': 'color-147', '\x1b[38;5;148m': 'color-148', '\x1b[38;5;149m': 'color-149', '\x1b[38;5;150m': 'color-150', '\x1b[38;5;151m': 'color-151', '\x1b[38;5;152m': 'color-152', '\x1b[38;5;153m': 'color-153', '\x1b[38;5;154m': 'color-154', '\x1b[38;5;155m': 'color-155', '\x1b[38;5;156m': 'color-156', '\x1b[38;5;157m': 'color-157', '\x1b[38;5;158m': 'color-158', '\x1b[38;5;159m': 'color-159', '\x1b[38;5;160m': 'color-160', '\x1b[38;5;161m': 'color-161', '\x1b[38;5;162m': 'color-162', '\x1b[38;5;163m': 'color-163', '\x1b[38;5;164m': 'color-164', '\x1b[38;5;165m': 'color-165', '\x1b[38;5;166m': 'color-166', '\x1b[38;5;167m': 'color-167', '\x1b[38;5;168m': 'color-168', '\x1b[38;5;169m': 'color-169', '\x1b[38;5;16m': 'color-016', '\x1b[38;5;170m': 'color-170', '\x1b[38;5;171m': 'color-171', '\x1b[38;5;172m': 'color-172', '\x1b[38;5;173m': 'color-173', '\x1b[38;5;174m': 'color-174', '\x1b[38;5;175m': 'color-175', '\x1b[38;5;176m': 'color-176', '\x1b[38;5;177m': 'color-177', '\x1b[38;5;178m': 'color-178', '\x1b[38;5;179m': 'color-179', '\x1b[38;5;17m': 'color-017', '\x1b[38;5;180m': 'color-180', '\x1b[38;5;181m': 'color-181', '\x1b[38;5;182m': 'color-182', '\x1b[38;5;183m': 'color-183', '\x1b[38;5;184m': 'color-184', '\x1b[38;5;185m': 'color-185', '\x1b[38;5;186m': 'color-186', '\x1b[38;5;187m': 'color-187', '\x1b[38;5;188m': 'color-188', '\x1b[38;5;189m': 'color-189', '\x1b[38;5;18m': 'color-018', '\x1b[38;5;190m': 'color-190', '\x1b[38;5;191m': 'color-191', '\x1b[38;5;192m': 'color-192', '\x1b[38;5;193m': 'color-193', '\x1b[38;5;194m': 'color-194', '\x1b[38;5;195m': 'color-195', '\x1b[38;5;196m': 'color-196', '\x1b[38;5;197m': 'color-197', '\x1b[38;5;198m': 'color-198', '\x1b[38;5;199m': 'color-199', '\x1b[38;5;19m': 'color-019', '\x1b[38;5;200m': 'color-200', '\x1b[38;5;201m': 'color-201', '\x1b[38;5;202m': 'color-202', '\x1b[38;5;203m': 'color-203', '\x1b[38;5;204m': 'color-204', '\x1b[38;5;205m': 'color-205', '\x1b[38;5;206m': 'color-206', '\x1b[38;5;207m': 'color-207', '\x1b[38;5;208m': 'color-208', '\x1b[38;5;209m': 'color-209', '\x1b[38;5;20m': 'color-020', '\x1b[38;5;210m': 'color-210', '\x1b[38;5;211m': 'color-211', '\x1b[38;5;212m': 'color-212', '\x1b[38;5;213m': 'color-213', '\x1b[38;5;214m': 'color-214', '\x1b[38;5;215m': 'color-215', '\x1b[38;5;216m': 'color-216', '\x1b[38;5;217m': 'color-217', '\x1b[38;5;218m': 'color-218', '\x1b[38;5;219m': 'color-219', '\x1b[38;5;21m': 'color-021', '\x1b[38;5;220m': 'color-220', '\x1b[38;5;221m': 'color-221', '\x1b[38;5;222m': 'color-222', '\x1b[38;5;223m': 'color-223', '\x1b[38;5;224m': 'color-224', '\x1b[38;5;225m': 'color-225', '\x1b[38;5;226m': 'color-226', '\x1b[38;5;227m': 'color-227', '\x1b[38;5;228m': 'color-228', '\x1b[38;5;229m': 'color-229', '\x1b[38;5;22m': 'color-022', '\x1b[38;5;230m': 'color-230', '\x1b[38;5;231m': 'color-231', '\x1b[38;5;232m': 'color-232', '\x1b[38;5;233m': 'color-233', '\x1b[38;5;234m': 'color-234', '\x1b[38;5;235m': 'color-235', '\x1b[38;5;236m': 'color-236', '\x1b[38;5;237m': 'color-237', '\x1b[38;5;238m': 'color-238', '\x1b[38;5;239m': 'color-239', '\x1b[38;5;23m': 'color-023', '\x1b[38;5;240m': 'color-240', '\x1b[38;5;241m': 'color-241', '\x1b[38;5;242m': 'color-242', '\x1b[38;5;243m': 'color-243', '\x1b[38;5;244m': 'color-244', '\x1b[38;5;245m': 'color-245', '\x1b[38;5;246m': 'color-246', '\x1b[38;5;247m': 'color-247', '\x1b[38;5;248m': 'color-248', '\x1b[38;5;249m': 'color-249', '\x1b[38;5;24m': 'color-024', '\x1b[38;5;250m': 'color-250', '\x1b[38;5;251m': 'color-251', '\x1b[38;5;252m': 'color-252', '\x1b[38;5;253m': 'color-253', '\x1b[38;5;254m': 'color-254', '\x1b[38;5;255m': 'color-255', '\x1b[38;5;25m': 'color-025', '\x1b[38;5;26m': 'color-026', '\x1b[38;5;27m': 'color-027', '\x1b[38;5;28m': 'color-028', '\x1b[38;5;29m': 'color-029', '\x1b[38;5;30m': 'color-030', '\x1b[38;5;31m': 'color-031', '\x1b[38;5;32m': 'color-032', '\x1b[38;5;33m': 'color-033', '\x1b[38;5;34m': 'color-034', '\x1b[38;5;35m': 'color-035', '\x1b[38;5;36m': 'color-036', '\x1b[38;5;37m': 'color-037', '\x1b[38;5;38m': 'color-038', '\x1b[38;5;39m': 'color-039', '\x1b[38;5;40m': 'color-040', '\x1b[38;5;41m': 'color-041', '\x1b[38;5;42m': 'color-042', '\x1b[38;5;43m': 'color-043', '\x1b[38;5;44m': 'color-044', '\x1b[38;5;45m': 'color-045', '\x1b[38;5;46m': 'color-046', '\x1b[38;5;47m': 'color-047', '\x1b[38;5;48m': 'color-048', '\x1b[38;5;49m': 'color-049', '\x1b[38;5;50m': 'color-050', '\x1b[38;5;51m': 'color-051', '\x1b[38;5;52m': 'color-052', '\x1b[38;5;53m': 'color-053', '\x1b[38;5;54m': 'color-054', '\x1b[38;5;55m': 'color-055', '\x1b[38;5;56m': 'color-056', '\x1b[38;5;57m': 'color-057', '\x1b[38;5;58m': 'color-058', '\x1b[38;5;59m': 'color-059', '\x1b[38;5;60m': 'color-060', '\x1b[38;5;61m': 'color-061', '\x1b[38;5;62m': 'color-062', '\x1b[38;5;63m': 'color-063', '\x1b[38;5;64m': 'color-064', '\x1b[38;5;65m': 'color-065', '\x1b[38;5;66m': 'color-066', '\x1b[38;5;67m': 'color-067', '\x1b[38;5;68m': 'color-068', '\x1b[38;5;69m': 'color-069', '\x1b[38;5;70m': 'color-070', '\x1b[38;5;71m': 'color-071', '\x1b[38;5;72m': 'color-072', '\x1b[38;5;73m': 'color-073', '\x1b[38;5;74m': 'color-074', '\x1b[38;5;75m': 'color-075', '\x1b[38;5;76m': 'color-076', '\x1b[38;5;77m': 'color-077', '\x1b[38;5;78m': 'color-078', '\x1b[38;5;79m': 'color-079', '\x1b[38;5;80m': 'color-080', '\x1b[38;5;81m': 'color-081', '\x1b[38;5;82m': 'color-082', '\x1b[38;5;83m': 'color-083', '\x1b[38;5;84m': 'color-084', '\x1b[38;5;85m': 'color-085', '\x1b[38;5;86m': 'color-086', '\x1b[38;5;87m': 'color-087', '\x1b[38;5;88m': 'color-088', '\x1b[38;5;89m': 'color-089', '\x1b[38;5;90m': 'color-090', '\x1b[38;5;91m': 'color-091', '\x1b[38;5;92m': 'color-092', '\x1b[38;5;93m': 'color-093', '\x1b[38;5;94m': 'color-094', '\x1b[38;5;95m': 'color-095', '\x1b[38;5;96m': 'color-096', '\x1b[38;5;97m': 'color-097', '\x1b[38;5;98m': 'color-098', '\x1b[38;5;99m': 'color-099'}
-
- -
-
-bg_colormap = {'\x1b[1m\x1b[40m': 'bgcolor-008', '\x1b[1m\x1b[41m': 'bgcolor-009', '\x1b[1m\x1b[42m': 'bgcolor-010', '\x1b[1m\x1b[43m': 'bgcolor-011', '\x1b[1m\x1b[44m': 'bgcolor-012', '\x1b[1m\x1b[45m': 'bgcolor-013', '\x1b[1m\x1b[46m': 'bgcolor-014', '\x1b[1m\x1b[47m': 'bgcolor-015', '\x1b[40m': 'bgcolor-000', '\x1b[41m': 'bgcolor-001', '\x1b[42m': 'bgcolor-002', '\x1b[43m': 'bgcolor-003', '\x1b[44m': 'bgcolor-004', '\x1b[45m': 'bgcolor-005', '\x1b[46m': 'bgcolor-006', '\x1b[47m': 'bgcolor-007', '\x1b[48;5;100m': 'bgcolor-100', '\x1b[48;5;101m': 'bgcolor-101', '\x1b[48;5;102m': 'bgcolor-102', '\x1b[48;5;103m': 'bgcolor-103', '\x1b[48;5;104m': 'bgcolor-104', '\x1b[48;5;105m': 'bgcolor-105', '\x1b[48;5;106m': 'bgcolor-106', '\x1b[48;5;107m': 'bgcolor-107', '\x1b[48;5;108m': 'bgcolor-108', '\x1b[48;5;109m': 'bgcolor-109', '\x1b[48;5;110m': 'bgcolor-110', '\x1b[48;5;111m': 'bgcolor-111', '\x1b[48;5;112m': 'bgcolor-112', '\x1b[48;5;113m': 'bgcolor-113', '\x1b[48;5;114m': 'bgcolor-114', '\x1b[48;5;115m': 'bgcolor-115', '\x1b[48;5;116m': 'bgcolor-116', '\x1b[48;5;117m': 'bgcolor-117', '\x1b[48;5;118m': 'bgcolor-118', '\x1b[48;5;119m': 'bgcolor-119', '\x1b[48;5;120m': 'bgcolor-120', '\x1b[48;5;121m': 'bgcolor-121', '\x1b[48;5;122m': 'bgcolor-122', '\x1b[48;5;123m': 'bgcolor-123', '\x1b[48;5;124m': 'bgcolor-124', '\x1b[48;5;125m': 'bgcolor-125', '\x1b[48;5;126m': 'bgcolor-126', '\x1b[48;5;127m': 'bgcolor-127', '\x1b[48;5;128m': 'bgcolor-128', '\x1b[48;5;129m': 'bgcolor-129', '\x1b[48;5;130m': 'bgcolor-130', '\x1b[48;5;131m': 'bgcolor-131', '\x1b[48;5;132m': 'bgcolor-132', '\x1b[48;5;133m': 'bgcolor-133', '\x1b[48;5;134m': 'bgcolor-134', '\x1b[48;5;135m': 'bgcolor-135', '\x1b[48;5;136m': 'bgcolor-136', '\x1b[48;5;137m': 'bgcolor-137', '\x1b[48;5;138m': 'bgcolor-138', '\x1b[48;5;139m': 'bgcolor-139', '\x1b[48;5;140m': 'bgcolor-140', '\x1b[48;5;141m': 'bgcolor-141', '\x1b[48;5;142m': 'bgcolor-142', '\x1b[48;5;143m': 'bgcolor-143', '\x1b[48;5;144m': 'bgcolor-144', '\x1b[48;5;145m': 'bgcolor-145', '\x1b[48;5;146m': 'bgcolor-146', '\x1b[48;5;147m': 'bgcolor-147', '\x1b[48;5;148m': 'bgcolor-148', '\x1b[48;5;149m': 'bgcolor-149', '\x1b[48;5;150m': 'bgcolor-150', '\x1b[48;5;151m': 'bgcolor-151', '\x1b[48;5;152m': 'bgcolor-152', '\x1b[48;5;153m': 'bgcolor-153', '\x1b[48;5;154m': 'bgcolor-154', '\x1b[48;5;155m': 'bgcolor-155', '\x1b[48;5;156m': 'bgcolor-156', '\x1b[48;5;157m': 'bgcolor-157', '\x1b[48;5;158m': 'bgcolor-158', '\x1b[48;5;159m': 'bgcolor-159', '\x1b[48;5;160m': 'bgcolor-160', '\x1b[48;5;161m': 'bgcolor-161', '\x1b[48;5;162m': 'bgcolor-162', '\x1b[48;5;163m': 'bgcolor-163', '\x1b[48;5;164m': 'bgcolor-164', '\x1b[48;5;165m': 'bgcolor-165', '\x1b[48;5;166m': 'bgcolor-166', '\x1b[48;5;167m': 'bgcolor-167', '\x1b[48;5;168m': 'bgcolor-168', '\x1b[48;5;169m': 'bgcolor-169', '\x1b[48;5;16m': 'bgcolor-016', '\x1b[48;5;170m': 'bgcolor-170', '\x1b[48;5;171m': 'bgcolor-171', '\x1b[48;5;172m': 'bgcolor-172', '\x1b[48;5;173m': 'bgcolor-173', '\x1b[48;5;174m': 'bgcolor-174', '\x1b[48;5;175m': 'bgcolor-175', '\x1b[48;5;176m': 'bgcolor-176', '\x1b[48;5;177m': 'bgcolor-177', '\x1b[48;5;178m': 'bgcolor-178', '\x1b[48;5;179m': 'bgcolor-179', '\x1b[48;5;17m': 'bgcolor-017', '\x1b[48;5;180m': 'bgcolor-180', '\x1b[48;5;181m': 'bgcolor-181', '\x1b[48;5;182m': 'bgcolor-182', '\x1b[48;5;183m': 'bgcolor-183', '\x1b[48;5;184m': 'bgcolor-184', '\x1b[48;5;185m': 'bgcolor-185', '\x1b[48;5;186m': 'bgcolor-186', '\x1b[48;5;187m': 'bgcolor-187', '\x1b[48;5;188m': 'bgcolor-188', '\x1b[48;5;189m': 'bgcolor-189', '\x1b[48;5;18m': 'bgcolor-018', '\x1b[48;5;190m': 'bgcolor-190', '\x1b[48;5;191m': 'bgcolor-191', '\x1b[48;5;192m': 'bgcolor-192', '\x1b[48;5;193m': 'bgcolor-193', '\x1b[48;5;194m': 'bgcolor-194', '\x1b[48;5;195m': 'bgcolor-195', '\x1b[48;5;196m': 'bgcolor-196', '\x1b[48;5;197m': 'bgcolor-197', '\x1b[48;5;198m': 'bgcolor-198', '\x1b[48;5;199m': 'bgcolor-199', '\x1b[48;5;19m': 'bgcolor-019', '\x1b[48;5;200m': 'bgcolor-200', '\x1b[48;5;201m': 'bgcolor-201', '\x1b[48;5;202m': 'bgcolor-202', '\x1b[48;5;203m': 'bgcolor-203', '\x1b[48;5;204m': 'bgcolor-204', '\x1b[48;5;205m': 'bgcolor-205', '\x1b[48;5;206m': 'bgcolor-206', '\x1b[48;5;207m': 'bgcolor-207', '\x1b[48;5;208m': 'bgcolor-208', '\x1b[48;5;209m': 'bgcolor-209', '\x1b[48;5;20m': 'bgcolor-020', '\x1b[48;5;210m': 'bgcolor-210', '\x1b[48;5;211m': 'bgcolor-211', '\x1b[48;5;212m': 'bgcolor-212', '\x1b[48;5;213m': 'bgcolor-213', '\x1b[48;5;214m': 'bgcolor-214', '\x1b[48;5;215m': 'bgcolor-215', '\x1b[48;5;216m': 'bgcolor-216', '\x1b[48;5;217m': 'bgcolor-217', '\x1b[48;5;218m': 'bgcolor-218', '\x1b[48;5;219m': 'bgcolor-219', '\x1b[48;5;21m': 'bgcolor-021', '\x1b[48;5;220m': 'bgcolor-220', '\x1b[48;5;221m': 'bgcolor-221', '\x1b[48;5;222m': 'bgcolor-222', '\x1b[48;5;223m': 'bgcolor-223', '\x1b[48;5;224m': 'bgcolor-224', '\x1b[48;5;225m': 'bgcolor-225', '\x1b[48;5;226m': 'bgcolor-226', '\x1b[48;5;227m': 'bgcolor-227', '\x1b[48;5;228m': 'bgcolor-228', '\x1b[48;5;229m': 'bgcolor-229', '\x1b[48;5;22m': 'bgcolor-022', '\x1b[48;5;230m': 'bgcolor-230', '\x1b[48;5;231m': 'bgcolor-231', '\x1b[48;5;232m': 'bgcolor-232', '\x1b[48;5;233m': 'bgcolor-233', '\x1b[48;5;234m': 'bgcolor-234', '\x1b[48;5;235m': 'bgcolor-235', '\x1b[48;5;236m': 'bgcolor-236', '\x1b[48;5;237m': 'bgcolor-237', '\x1b[48;5;238m': 'bgcolor-238', '\x1b[48;5;239m': 'bgcolor-239', '\x1b[48;5;23m': 'bgcolor-023', '\x1b[48;5;240m': 'bgcolor-240', '\x1b[48;5;241m': 'bgcolor-241', '\x1b[48;5;242m': 'bgcolor-242', '\x1b[48;5;243m': 'bgcolor-243', '\x1b[48;5;244m': 'bgcolor-244', '\x1b[48;5;245m': 'bgcolor-245', '\x1b[48;5;246m': 'bgcolor-246', '\x1b[48;5;247m': 'bgcolor-247', '\x1b[48;5;248m': 'bgcolor-248', '\x1b[48;5;249m': 'bgcolor-249', '\x1b[48;5;24m': 'bgcolor-024', '\x1b[48;5;250m': 'bgcolor-250', '\x1b[48;5;251m': 'bgcolor-251', '\x1b[48;5;252m': 'bgcolor-252', '\x1b[48;5;253m': 'bgcolor-253', '\x1b[48;5;254m': 'bgcolor-254', '\x1b[48;5;255m': 'bgcolor-255', '\x1b[48;5;25m': 'bgcolor-025', '\x1b[48;5;26m': 'bgcolor-026', '\x1b[48;5;27m': 'bgcolor-027', '\x1b[48;5;28m': 'bgcolor-028', '\x1b[48;5;29m': 'bgcolor-029', '\x1b[48;5;30m': 'bgcolor-030', '\x1b[48;5;31m': 'bgcolor-031', '\x1b[48;5;32m': 'bgcolor-032', '\x1b[48;5;33m': 'bgcolor-033', '\x1b[48;5;34m': 'bgcolor-034', '\x1b[48;5;35m': 'bgcolor-035', '\x1b[48;5;36m': 'bgcolor-036', '\x1b[48;5;37m': 'bgcolor-037', '\x1b[48;5;38m': 'bgcolor-038', '\x1b[48;5;39m': 'bgcolor-039', '\x1b[48;5;40m': 'bgcolor-040', '\x1b[48;5;41m': 'bgcolor-041', '\x1b[48;5;42m': 'bgcolor-042', '\x1b[48;5;43m': 'bgcolor-043', '\x1b[48;5;44m': 'bgcolor-044', '\x1b[48;5;45m': 'bgcolor-045', '\x1b[48;5;46m': 'bgcolor-046', '\x1b[48;5;47m': 'bgcolor-047', '\x1b[48;5;48m': 'bgcolor-048', '\x1b[48;5;49m': 'bgcolor-049', '\x1b[48;5;50m': 'bgcolor-050', '\x1b[48;5;51m': 'bgcolor-051', '\x1b[48;5;52m': 'bgcolor-052', '\x1b[48;5;53m': 'bgcolor-053', '\x1b[48;5;54m': 'bgcolor-054', '\x1b[48;5;55m': 'bgcolor-055', '\x1b[48;5;56m': 'bgcolor-056', '\x1b[48;5;57m': 'bgcolor-057', '\x1b[48;5;58m': 'bgcolor-058', '\x1b[48;5;59m': 'bgcolor-059', '\x1b[48;5;60m': 'bgcolor-060', '\x1b[48;5;61m': 'bgcolor-061', '\x1b[48;5;62m': 'bgcolor-062', '\x1b[48;5;63m': 'bgcolor-063', '\x1b[48;5;64m': 'bgcolor-064', '\x1b[48;5;65m': 'bgcolor-065', '\x1b[48;5;66m': 'bgcolor-066', '\x1b[48;5;67m': 'bgcolor-067', '\x1b[48;5;68m': 'bgcolor-068', '\x1b[48;5;69m': 'bgcolor-069', '\x1b[48;5;70m': 'bgcolor-070', '\x1b[48;5;71m': 'bgcolor-071', '\x1b[48;5;72m': 'bgcolor-072', '\x1b[48;5;73m': 'bgcolor-073', '\x1b[48;5;74m': 'bgcolor-074', '\x1b[48;5;75m': 'bgcolor-075', '\x1b[48;5;76m': 'bgcolor-076', '\x1b[48;5;77m': 'bgcolor-077', '\x1b[48;5;78m': 'bgcolor-078', '\x1b[48;5;79m': 'bgcolor-079', '\x1b[48;5;80m': 'bgcolor-080', '\x1b[48;5;81m': 'bgcolor-081', '\x1b[48;5;82m': 'bgcolor-082', '\x1b[48;5;83m': 'bgcolor-083', '\x1b[48;5;84m': 'bgcolor-084', '\x1b[48;5;85m': 'bgcolor-085', '\x1b[48;5;86m': 'bgcolor-086', '\x1b[48;5;87m': 'bgcolor-087', '\x1b[48;5;88m': 'bgcolor-088', '\x1b[48;5;89m': 'bgcolor-089', '\x1b[48;5;90m': 'bgcolor-090', '\x1b[48;5;91m': 'bgcolor-091', '\x1b[48;5;92m': 'bgcolor-092', '\x1b[48;5;93m': 'bgcolor-093', '\x1b[48;5;94m': 'bgcolor-094', '\x1b[48;5;95m': 'bgcolor-095', '\x1b[48;5;96m': 'bgcolor-096', '\x1b[48;5;97m': 'bgcolor-097', '\x1b[48;5;98m': 'bgcolor-098', '\x1b[48;5;99m': 'bgcolor-099'}
-
- -
-
-fgstop = '(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m|\x1b\\[0m|$'
-
- -
-
-bgstop = '(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m|\x1b\\[0m|$'
-
- -
-
-bgfgstop = '(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m|\x1b\\[0m(\\s*)(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m|\x1b\\[0m|$'
-
- -
-
-fgstart = '((?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m)'
-
- -
-
-bgstart = '((?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m)'
-
- -
-
-bgfgstart = '((?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m)(\\s*)((?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[[3-4][0-8].*?m){0,1}'
-
- -
-
-re_fgs = re.compile('((?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m)(.*?)(?=(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m|\x1b\\[0m|$)')
-
- -
-
-re_bgs = re.compile('((?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m)(.*?)(?=(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m|\x1b\\[0m|$)')
-
- -
-
-re_bgfg = re.compile('((?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m)(\\s*)((?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[[3-4][0-8].*?m){0,1}(.*?)(?=(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m|\x1b\\[0m(\\s*)(?:\x1b\\[1m|\x1b\)
-
- -
-
-re_normal = re.compile('\x1b\\[0m')
-
- -
-
-re_hilite = re.compile('(?:\x1b\\[1m)(.*)(?=(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m|\x1b\\[0m|$|(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m|\x1b\\[0m|$)')
-
- -
-
-re_unhilite = re.compile('(?:\x1b\\[22m)(.*)(?=(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m|\x1b\\[0m|$|(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m|\x1b\\[0m|$)')
-
- -
-
-re_uline = re.compile('(?:\x1b\\[4m)(.*?)(?=(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m|\x1b\\[0m|$|(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m|\x1b\\[0m|$)')
-
- -
- -
- -
-
-re_inverse = re.compile('(?:\x1b\\[7m)(.*?)(?=(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[3[0-8].*?m|\x1b\\[0m|$|(?:\x1b\\[1m|\x1b\\[22m){0,1}\x1b\\[4[0-8].*?m|\x1b\\[0m|$)')
-
- -
-
-re_string = re.compile('(?P<htmlchars>[<&>])|(?P<tab>[\\t]+)|(?P<space> +)|(?P<spacestart>^ )|(?P<lineend>\\r\\n|\\r|\\n)', re.IGNORECASE|re.MULTILINE|re.DOTALL)
-
- -
-
-re_dblspace = re.compile(' {2,}', re.MULTILINE)
-
- -
-
-re_url = re.compile('((?:ftp|www|https?)\\W+(?:(?!\\.(?:\\s|$)|&\\w+;)[^"\\\',;$*^\\\\(){}<>\\[\\]\\s])+)(\\.(?:\\s|$)|&\\w+;|)')
-
- -
- -
- -
-
-re_color(text)[source]
-

Replace ansi colors with html color class names. Let the -client choose how it will display colors, if it wishes to.

-
-
Parameters
-

text (str) – the string with color to replace.

-
-
Returns
-

text (str) – Re-colored text.

-
-
-
- -
-
-re_bold(text)[source]
-

Clean out superfluous hilights rather than set <strong>to make -it match the look of telnet.

-
-
Parameters
-

text (str) – Text to process.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-re_underline(text)[source]
-

Replace ansi underline with html underline class name.

-
-
Parameters
-

text (str) – Text to process.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-re_blinking(text)[source]
-

Replace ansi blink with custom blink css class

-
-
Parameters
-

text (str) – Text to process.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-re_inversing(text)[source]
-

Replace ansi inverse with custom inverse css class

-
-
Parameters
-

text (str) – Text to process.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-remove_bells(text)[source]
-

Remove ansi specials

-
-
Parameters
-

text (str) – Text to process.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-remove_backspaces(text)[source]
-

Removes special escape sequences

-
-
Parameters
-

text (str) – Text to process.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-convert_linebreaks(text)[source]
-

Extra method for cleaning linebreaks

-
-
Parameters
-

text (str) – Text to process.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-convert_urls(text)[source]
-

Replace urls (http://…) by valid HTML.

-
-
Parameters
-

text (str) – Text to process.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-re_double_space(text)[source]
-

HTML will swallow any normal space after the first, so if any slipped -through we must make sure to replace them with ” &nbsp;”

-
- -
- -

Helper method to be passed to re.sub, -replaces MXP links with HTML code.

-
-
Parameters
-

match (re.Matchobject) – Match for substitution.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-sub_text(match)[source]
-

Helper method to be passed to re.sub, -for handling all substitutions.

-
-
Parameters
-

match (re.Matchobject) – Match for substitution.

-
-
Returns
-

text (str) – Processed text.

-
-
-
- -
-
-sub_dblspace(match)[source]
-

clean up double-spaces

-
- -
-
-parse(text, strip_ansi=False)[source]
-

Main access function, converts a text containing ANSI codes -into html statements.

-
-
Parameters
-
    -
  • text (str) – Text to process.

  • -
  • strip_ansi (bool, optional) –

  • -
-
-
Returns
-

text (str) – Parsed text.

-
-
-
- -
- -
-
-evennia.utils.text2html.parse_html(string, strip_ansi=False, parser=<evennia.utils.text2html.TextToHTMLparser object>)[source]
-

Parses a string, replace ANSI markup with html

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.utils.html b/docs/0.9.5/api/evennia.utils.utils.html deleted file mode 100644 index af88c09f59..0000000000 --- a/docs/0.9.5/api/evennia.utils.utils.html +++ /dev/null @@ -1,1468 +0,0 @@ - - - - - - - - - evennia.utils.utils — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.utils

-

General helper functions that don’t fit neatly under any given category.

-

They provide some useful string and conversion methods that might -be of use when designing your own game.

-
-
-evennia.utils.utils.is_iter(obj)[source]
-

Checks if an object behaves iterably.

-
-
Parameters
-

obj (any) – Entity to check for iterability.

-
-
Returns
-

is_iterable (bool) – If obj is iterable or not.

-
-
-

Notes

-

Strings are not accepted as iterable (although they are -actually iterable), since string iterations are usually not -what we want to do with a string.

-
- -
-
-evennia.utils.utils.make_iter(obj)[source]
-

Makes sure that the object is always iterable.

-
-
Parameters
-

obj (any) – Object to make iterable.

-
-
Returns
-

iterable (list or iterable)

-
-
The same object

passed-through or made iterable.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.wrap(text, width=None, indent=0)[source]
-

Safely wrap text to a certain number of characters.

-
-
Parameters
-
    -
  • text (str) – The text to wrap.

  • -
  • width (int, optional) – The number of characters to wrap to.

  • -
  • indent (int) – How much to indent each line (with whitespace).

  • -
-
-
Returns
-

text (str) – Properly wrapped text.

-
-
-
- -
-
-evennia.utils.utils.fill(text, width=None, indent=0)
-

Safely wrap text to a certain number of characters.

-
-
Parameters
-
    -
  • text (str) – The text to wrap.

  • -
  • width (int, optional) – The number of characters to wrap to.

  • -
  • indent (int) – How much to indent each line (with whitespace).

  • -
-
-
Returns
-

text (str) – Properly wrapped text.

-
-
-
- -
-
-evennia.utils.utils.pad(text, width=None, align='c', fillchar=' ')[source]
-

Pads to a given width.

-
-
Parameters
-
    -
  • text (str) – Text to pad.

  • -
  • width (int, optional) – The width to pad to, in characters.

  • -
  • align (str, optional) – This is one of ‘c’, ‘l’ or ‘r’ (center, -left or right).

  • -
  • fillchar (str, optional) – The character to fill with.

  • -
-
-
Returns
-

text (str) – The padded text.

-
-
-
- -
-
-evennia.utils.utils.crop(text, width=None, suffix='[...]')[source]
-

Crop text to a certain width, throwing away text from too-long -lines.

-
-
Parameters
-
    -
  • text (str) – Text to crop.

  • -
  • width (int, optional) – Width of line to crop, in characters.

  • -
  • suffix (str, optional) – This is appended to the end of cropped -lines to show that the line actually continues. Cropping -will be done so that the suffix will also fit within the -given width. If width is too small to fit both crop and -suffix, the suffix will be dropped.

  • -
-
-
Returns
-

text (str) – The cropped text.

-
-
-
- -
-
-evennia.utils.utils.dedent(text, baseline_index=None)[source]
-

Safely clean all whitespace at the left of a paragraph.

-
-
Parameters
-
    -
  • text (str) – The text to dedent.

  • -
  • baseline_index (int or None, optional) – Which row to use as a ‘base’ -for the indentation. Lines will be dedented to this level but -no further. If None, indent so as to completely deindent the -least indented text.

  • -
-
-
Returns
-

text (str) – Dedented string.

-
-
-

Notes

-

This is useful for preserving triple-quoted string indentation -while still shifting it all to be next to the left edge of the -display.

-
- -
-
-evennia.utils.utils.justify(text, width=None, align='f', indent=0)[source]
-

Fully justify a text so that it fits inside width. When using -full justification (default) this will be done by padding between -words with extra whitespace where necessary. Paragraphs will -be retained.

-
-
Parameters
-
    -
  • text (str) – Text to justify.

  • -
  • width (int, optional) – The length of each line, in characters.

  • -
  • align (str, optional) – The alignment, ‘l’, ‘c’, ‘r’ or ‘f’ -for left, center, right or full justification respectively.

  • -
  • indent (int, optional) – Number of characters indentation of -entire justified text block.

  • -
-
-
Returns
-

justified (str) – The justified and indented block of text.

-
-
-
- -
-
-evennia.utils.utils.columnize(string, columns=2, spacing=4, align='l', width=None)[source]
-

Break a string into a number of columns, using as little -vertical space as possible.

-
-
Parameters
-
    -
  • string (str) – The string to columnize.

  • -
  • columns (int, optional) – The number of columns to use.

  • -
  • spacing (int, optional) – How much space to have between columns.

  • -
  • width (int, optional) – The max width of the columns. -Defaults to client’s default width.

  • -
-
-
Returns
-

columns (str) – Text divided into columns.

-
-
Raises
-

RuntimeError – If given invalid values.

-
-
-
- -
-
-evennia.utils.utils.iter_to_string(initer, endsep='and', addquote=False)[source]
-

This pretty-formats an iterable list as string output, adding an optional -alternative separator to the second to last entry. If addquote -is True, the outgoing strings will be surrounded by quotes.

-
-
Parameters
-
    -
  • initer (any) – Usually an iterable to print. Each element must be possible to -present with a string. Note that if this is a generator, it will be -consumed by this operation.

  • -
  • endsep (str, optional) – If set, the last item separator will -be replaced with this value.

  • -
  • addquote (bool, optional) – This will surround all outgoing -values with double quotes.

  • -
-
-
Returns
-

liststr (str) – The list represented as a string.

-
-
-

Examples

-
# no endsep:
-   [1,2,3] -> '1, 2, 3'
-# with endsep=='and':
-   [1,2,3] -> '1, 2 and 3'
-# with addquote and endsep
-   [1,2,3] -> '"1", "2" and "3"'
-
-
-
- -
-
-evennia.utils.utils.list_to_string(initer, endsep='and', addquote=False)
-

This pretty-formats an iterable list as string output, adding an optional -alternative separator to the second to last entry. If addquote -is True, the outgoing strings will be surrounded by quotes.

-
-
Parameters
-
    -
  • initer (any) – Usually an iterable to print. Each element must be possible to -present with a string. Note that if this is a generator, it will be -consumed by this operation.

  • -
  • endsep (str, optional) – If set, the last item separator will -be replaced with this value.

  • -
  • addquote (bool, optional) – This will surround all outgoing -values with double quotes.

  • -
-
-
Returns
-

liststr (str) – The list represented as a string.

-
-
-

Examples

-
# no endsep:
-   [1,2,3] -> '1, 2, 3'
-# with endsep=='and':
-   [1,2,3] -> '1, 2 and 3'
-# with addquote and endsep
-   [1,2,3] -> '"1", "2" and "3"'
-
-
-
- -
-
-evennia.utils.utils.wildcard_to_regexp(instring)[source]
-

Converts a player-supplied string that may have wildcards in it to -regular expressions. This is useful for name matching.

-
-
Parameters
-

instring (string) – A string that may potentially contain -wildcards (* or ?).

-
-
Returns
-

regex (str)

-
-
A string where wildcards were replaced with

regular expressions.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.time_format(seconds, style=0)[source]
-

Function to return a ‘prettified’ version of a value in seconds.

-
-
Parameters
-
    -
  • seconds (int) – Number if seconds to format.

  • -
  • style (int) – One of the following styles: -0. “1d 08:30” -1. “1d” -2. “1 day, 8 hours, 30 minutes” -3. “1 day, 8 hours, 30 minutes, 10 seconds” -4. highest unit (like “3 years” or “8 months” or “1 second”)

  • -
-
-
Returns
-

timeformatted (str) – A pretty time string.

-
-
-
- -
-
-evennia.utils.utils.datetime_format(dtobj)[source]
-

Pretty-prints the time since a given time.

-
-
Parameters
-

dtobj (datetime) – An datetime object, e.g. from Django’s -DateTimeField.

-
-
Returns
-

deltatime (str)

-
-
A string describing how long ago dtobj

took place.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.host_os_is(osname)[source]
-

Check to see if the host OS matches the query.

-
-
Parameters
-
    -
  • osname (str) – Common names are “posix” (linux/unix/mac) and -“nt” (windows).

  • -
  • is_os (bool) – If the os matches or not.

  • -
-
-
-
- -
-
-evennia.utils.utils.get_evennia_version(mode='long')[source]
-

Helper method for getting the current evennia version.

-
-
Parameters
-

mode (str, optional) – One of: -- long: 0.9.0 rev342453534 -- short: 0.9.0 -- pretty: Evennia 0.9.0

-
-
Returns
-

version (str) – The version string.

-
-
-
- -
-
-evennia.utils.utils.pypath_to_realpath(python_path, file_ending='.py', pypath_prefixes=None)[source]
-

Converts a dotted Python path to an absolute path under the -Evennia library directory or under the current game directory.

-
-
Parameters
-
    -
  • python_path (str) – A dot-python path

  • -
  • file_ending (str) – A file ending, including the period.

  • -
  • pypath_prefixes (list) – A list of paths to test for existence. These -should be on python.path form. EVENNIA_DIR and GAME_DIR are automatically -checked, they need not be added to this list.

  • -
-
-
Returns
-

abspaths (list)

-
-
All existing, absolute paths created by

converting python_path to an absolute paths and/or -prepending python_path by settings.EVENNIA_DIR, -settings.GAME_DIR and by**pypath_prefixes** respectively.

-
-
-

-
-
-

Notes

-

This will also try a few combinations of paths to allow cases -where pypath is given including the “evennia.” or “mygame.” -prefixes.

-
- -
-
-evennia.utils.utils.dbref(inp, reqhash=True)[source]
-

Converts/checks if input is a valid dbref.

-
-
Parameters
-
    -
  • inp (int, str) – A database ref on the form N or #N.

  • -
  • reqhash (bool, optional) – Require the #N form to accept -input as a valid dbref.

  • -
-
-
Returns
-

dbref (int or None)

-
-
The integer part of the dbref or None

if input was not a valid dbref.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.dbref_to_obj(inp, objclass, raise_errors=True)[source]
-

Convert a #dbref to a valid object.

-
-
Parameters
-
    -
  • inp (str or int) – A valid #dbref.

  • -
  • objclass (class) – A valid django model to filter against.

  • -
  • raise_errors (bool, optional) – Whether to raise errors -or return None on errors.

  • -
-
-
Returns
-

obj (Object or None) – An entity loaded from the dbref.

-
-
Raises
-

Exception – If raise_errors is True and -objclass.objects.get(id=dbref) did not return a valid -object.

-
-
-
- -
-
-evennia.utils.utils.dbid_to_obj(inp, objclass, raise_errors=True)
-

Convert a #dbref to a valid object.

-
-
Parameters
-
    -
  • inp (str or int) – A valid #dbref.

  • -
  • objclass (class) – A valid django model to filter against.

  • -
  • raise_errors (bool, optional) – Whether to raise errors -or return None on errors.

  • -
-
-
Returns
-

obj (Object or None) – An entity loaded from the dbref.

-
-
Raises
-

Exception – If raise_errors is True and -objclass.objects.get(id=dbref) did not return a valid -object.

-
-
-
- -
-
-evennia.utils.utils.latinify(string, default='?', pure_ascii=False)[source]
-

Convert a unicode string to “safe” ascii/latin-1 characters. -This is used as a last resort when normal encoding does not work.

-
-
Parameters
-
    -
  • string (str) – A string to convert to ‘safe characters’ convertable -to an latin-1 bytestring later.

  • -
  • default (str, optional) – Characters resisting mapping will be replaced -with this character or string. The intent is to apply an encode operation -on the string soon after.

  • -
-
-
Returns
-

string (str)

-
-
A ‘latinified’ string where each unicode character has been

replaced with a ‘safe’ equivalent available in the ascii/latin-1 charset.

-
-
-

-
-
-

Notes

-
-
This is inspired by the gist by Ricardo Murri:

https://gist.github.com/riccardomurri/3c3ccec30f037be174d3

-
-
-
- -
-
-evennia.utils.utils.to_bytes(text, session=None)[source]
-

Try to encode the given text to bytes, using encodings from settings or from Session. Will -always return a bytes, even if given something that is not str or bytes.

-
-
Parameters
-
    -
  • text (any) – The text to encode to bytes. If bytes, return unchanged. If not a str, convert -to str before converting.

  • -
  • session (Session, optional) – A Session to get encoding info from. Will try this before -falling back to settings.ENCODINGS.

  • -
-
-
Returns
-

encoded_text (bytes)

-
-
the encoded text following the session’s protocol flag followed by the

encodings specified in settings.ENCODINGS. If all attempt fail, log the error and send -the text with “?” in place of problematic characters. If the specified encoding cannot -be found, the protocol flag is reset to utf-8. In any case, returns bytes.

-
-
-

-
-
-
-

Note

-

If text is already bytes, return it as is.

-
-
- -
-
-evennia.utils.utils.to_str(text, session=None)[source]
-

Try to decode a bytestream to a python str, using encoding schemas from settings -or from Session. Will always return a str(), also if not given a str/bytes.

-
-
Parameters
-
    -
  • text (any) – The text to encode to bytes. If a str, return it. If also not bytes, convert -to str using str() or repr() as a fallback.

  • -
  • session (Session, optional) – A Session to get encoding info from. Will try this before -falling back to settings.ENCODINGS.

  • -
-
-
Returns
-

decoded_text (str) – The decoded text.

-
-
-
-

Note

-

If text is already str, return it as is.

-
-
- -
-
-evennia.utils.utils.validate_email_address(emailaddress)[source]
-

Checks if an email address is syntactically correct. Makes use -of the django email-validator for consistency.

-
-
Parameters
-

emailaddress (str) – Email address to validate.

-
-
Returns
-

bool – If this is a valid email or not.

-
-
-
- -
-
-evennia.utils.utils.inherits_from(obj, parent)[source]
-

Takes an object and tries to determine if it inherits at any -distance from parent.

-
-
Parameters
-
    -
  • obj (any) – Object to analyze. This may be either an instance -or a class.

  • -
  • parent (any) – Can be either instance, class or python path to class.

  • -
-
-
Returns
-

inherits_from (bool) – If parent is a parent to obj or not.

-
-
-

Notes

-

What differs this function from e.g. isinstance() is that obj -may be both an instance and a class, and parent may be an -instance, a class, or the python path to a class (counting from -the evennia root directory).

-
- -
-
-evennia.utils.utils.server_services()[source]
-

Lists all services active on the Server. Observe that since -services are launched in memory, this function will only return -any results if called from inside the game.

-
-
Returns
-

services (dict) – A dict of available services.

-
-
-
- -
-
-evennia.utils.utils.uses_database(name='sqlite3')[source]
-

Checks if the game is currently using a given database. This is a -shortcut to having to use the full backend name.

-
-
Parameters
-
    -
  • name (str) – One of ‘sqlite3’, ‘mysql’, ‘postgresql’

  • -
  • 'oracle'. (or) –

  • -
-
-
Returns
-

uses (bool) – If the given database is used or not.

-
-
-
- -
-
-evennia.utils.utils.delay(timedelay, callback, *args, **kwargs)[source]
-

Delay the calling of a callback (function).

-
-
Parameters
-
    -
  • timedelay (int or float) – The delay in seconds.

  • -
  • callback (callable) – Will be called as callback(*args, **kwargs) -after timedelay seconds.

  • -
  • args (any) – Will be used as arguments to callback.

  • -
-
-
Keyword Arguments
-
    -
  • persistent (bool, optional) – If True the delay remains after a server restart. -persistent is False by default.

  • -
  • any (any) – Will be used as keyword arguments to callback.

  • -
-
-
Returns
-

task (TaskHandlerTask)

-
-
An instance of a task.

Refer to, evennia.scripts.taskhandler.TaskHandlerTask

-
-
-

-
-
-
-

Note

-

The task handler (evennia.scripts.taskhandler.TASK_HANDLER) will -be called for persistent or non-persistent tasks. -If persistent is set to True, the callback, its arguments -and other keyword arguments will be saved (serialized) in the database, -assuming they can be. The callback will be executed even after -a server restart/reload, taking into account the specified delay -(and server down time). -Keep in mind that persistent tasks arguments and callback should not -use memory references. -If persistent is set to True the delay function will return an int -which is the task’s id itended for use with TASK_HANDLER’s do_task -and remove methods. -All persistent tasks whose time delays have passed will be called on server startup.

-
-
- -
-
-evennia.utils.utils.run_async(to_execute, *args, **kwargs)[source]
-

Runs a function or executes a code snippet asynchronously.

-
-
Parameters
-

to_execute (callable) – If this is a callable, it will be -executed with *args and non-reserved **kwargs as arguments. -The callable will be executed using ProcPool, or in a thread -if ProcPool is not available.

-
-
Keyword Arguments
-
    -
  • at_return (callable) – Should point to a callable with one -argument. It will be called with the return value from -to_execute.

  • -
  • at_return_kwargs (dict) – This dictionary will be used as -keyword arguments to the at_return callback.

  • -
  • at_err (callable) – This will be called with a Failure instance -if there is an error in to_execute.

  • -
  • at_err_kwargs (dict) – This dictionary will be used as keyword -arguments to the at_err errback.

  • -
-
-
-

Notes

-

All other *args and **kwargs will be passed on to -to_execute. Run_async will relay executed code to a thread -or procpool.

-

Use this function with restrain and only for features/commands -that you know has no influence on the cause-and-effect order of your -game (commands given after the async function might be executed before -it has finished). Accessing the same property from different threads -can lead to unpredicted behaviour if you are not careful (this is called a -“race condition”).

-

Also note that some databases, notably sqlite3, don’t support access from -multiple threads simultaneously, so if you do heavy database access from -your to_execute under sqlite3 you will probably run very slow or even get -tracebacks.

-
- -
-
-evennia.utils.utils.check_evennia_dependencies()[source]
-

Checks the versions of Evennia’s dependencies including making -some checks for runtime libraries.

-
-
Returns
-

result (bool)

-
-
False if a show-stopping version mismatch is

found.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.has_parent(basepath, obj)[source]
-

Checks if basepath is somewhere in obj’s parent tree.

-
-
Parameters
-
    -
  • basepath (str) – Python dotpath to compare against obj path.

  • -
  • obj (any) – Object whose path is to be checked.

  • -
-
-
Returns
-

has_parent (bool) – If the check was successful or not.

-
-
-
- -
-
-evennia.utils.utils.mod_import_from_path(path)[source]
-

Load a Python module at the specified path.

-
-
Parameters
-

path (str) – An absolute path to a Python module to load.

-
-
Returns
-

(module or None) – An imported module if the path was a valid -Python module. Returns None if the import failed.

-
-
-
- -
-
-evennia.utils.utils.mod_import(module)[source]
-

A generic Python module loader.

-
-
Parameters
-

module (str, module) – This can be either a Python path -(dot-notation like evennia.objects.models), an absolute path -(e.g. /home/eve/evennia/evennia/objects/models.py) or an -already imported module object (e.g. models)

-
-
Returns
-

(module or None) – An imported module. If the input argument was -already a module, this is returned as-is, otherwise the path is -parsed and imported. Returns None and logs error if import failed.

-
-
-
- -
-
-evennia.utils.utils.all_from_module(module)[source]
-

Return all global-level variables defined in a module.

-
-
Parameters
-

module (str, module) – This can be either a Python path -(dot-notation like evennia.objects.models), an absolute path -(e.g. /home/eve/evennia/evennia/objects.models.py) or an -already imported module object (e.g. models)

-
-
Returns
-

variables (dict)

-
-
A dict of {variablename: variable} for all

variables in the given module.

-
-
-

-
-
-

Notes

-

Ignores modules and variable names starting with an underscore.

-
- -
-
-evennia.utils.utils.callables_from_module(module)[source]
-

Return all global-level callables defined in a module.

-
-
Parameters
-

module (str, module) – A python-path to a module or an actual -module object.

-
-
Returns
-

callables (dict) – A dict of {name: callable, …} from the module.

-
-
-

Notes

-

Will ignore callables whose names start with underscore “_”.

-
- -
-
-evennia.utils.utils.variable_from_module(module, variable=None, default=None)[source]
-

Retrieve a variable or list of variables from a module. The -variable(s) must be defined globally in the module. If no variable -is given (or a list entry is None), all global variables are -extracted from the module.

-
-
Parameters
-
    -
  • module (string or module) – Python path, absolute path or a module.

  • -
  • variable (string or iterable, optional) – Single variable name or iterable -of variable names to extract. If not given, all variables in -the module will be returned.

  • -
  • default (string, optional) – Default value to use if a variable fails to -be extracted. Ignored if variable is not given.

  • -
-
-
Returns
-

variables (value or list) – A single value or a list of values -depending on if variable is given or not. Errors in lists -are replaced by the default argument.

-
-
-
- -
-
-evennia.utils.utils.string_from_module(module, variable=None, default=None)[source]
-

This is a wrapper for variable_from_module that requires return -value to be a string to pass. It’s primarily used by login screen.

-
-
Parameters
-
    -
  • module (string or module) – Python path, absolute path or a module.

  • -
  • variable (string or iterable, optional) – Single variable name or iterable -of variable names to extract. If not given, all variables in -the module will be returned.

  • -
  • default (string, optional) – Default value to use if a variable fails to -be extracted. Ignored if variable is not given.

  • -
-
-
Returns
-

variables (value or list) – A single (string) value or a list of values -depending on if variable is given or not. Errors in lists (such -as the value not being a string) are replaced by the default argument.

-
-
-
- -
-
-evennia.utils.utils.random_string_from_module(module)[source]
-

Returns a random global string from a module.

-
-
Parameters
-

module (string or module) – Python path, absolute path or a module.

-
-
Returns
-

random (string) – A random stribg variable from module.

-
-
-
- -
-
-evennia.utils.utils.fuzzy_import_from_module(path, variable, default=None, defaultpaths=None)[source]
-

Import a variable based on a fuzzy path. First the literal -path will be tried, then all given defaultpaths will be -prepended to see a match is found.

-
-
Parameters
-
    -
  • path (str) – Full or partial python path.

  • -
  • variable (str) – Name of variable to import from module.

  • -
  • default (string, optional) – Default value to use if a variable fails to -be extracted. Ignored if variable is not given.

  • -
  • defaultpaths (iterable, options) – Python paths to attempt in order if -importing directly from path doesn’t work.

  • -
-
-
Returns
-

value (any)

-
-
The variable imported from the module, or default, if

not found.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.class_from_module(path, defaultpaths=None, fallback=None)[source]
-

Return a class from a module, given the module’s path. This is -primarily used to convert db_typeclass_path:s to classes.

-
-
Parameters
-
    -
  • path (str) – Full Python dot-path to module.

  • -
  • defaultpaths (iterable, optional) – If a direct import from path fails, -try subsequent imports by prepending those paths to path.

  • -
  • fallback (str) – If all other attempts fail, use this path as a fallback. -This is intended as a last-resport. In the example of Evennia -loading, this would be a path to a default parent class in the -evennia repo itself.

  • -
-
-
Returns
-

class (Class) – An uninstatiated class recovered from path.

-
-
Raises
-

ImportError – If all loading failed.

-
-
-
- -
-
-evennia.utils.utils.object_from_module(path, defaultpaths=None, fallback=None)
-

Return a class from a module, given the module’s path. This is -primarily used to convert db_typeclass_path:s to classes.

-
-
Parameters
-
    -
  • path (str) – Full Python dot-path to module.

  • -
  • defaultpaths (iterable, optional) – If a direct import from path fails, -try subsequent imports by prepending those paths to path.

  • -
  • fallback (str) – If all other attempts fail, use this path as a fallback. -This is intended as a last-resport. In the example of Evennia -loading, this would be a path to a default parent class in the -evennia repo itself.

  • -
-
-
Returns
-

class (Class) – An uninstatiated class recovered from path.

-
-
Raises
-

ImportError – If all loading failed.

-
-
-
- -
-
-evennia.utils.utils.init_new_account(account)[source]
-

Deprecated.

-
- -
-
-evennia.utils.utils.string_similarity(string1, string2)[source]
-

This implements a “cosine-similarity” algorithm as described for example in -Proceedings of the 22nd International Conference on Computation -Linguistics (Coling 2008), pages 593-600, Manchester, August 2008. -The measure-vectors used is simply a “bag of words” type histogram -(but for letters).

-
-
Parameters
-
    -
  • string1 (str) – String to compare (may contain any number of words).

  • -
  • string2 (str) – Second string to compare (any number of words).

  • -
-
-
Returns
-

similarity (float)

-
-
A value 0…1 rating how similar the two

strings are.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.string_suggestions(string, vocabulary, cutoff=0.6, maxnum=3)[source]
-

Given a string and a vocabulary, return a match or a list of -suggestions based on string similarity.

-
-
Parameters
-
    -
  • string (str) – A string to search for.

  • -
  • vocabulary (iterable) – A list of available strings.

  • -
  • cutoff (int, 0-1) – Limit the similarity matches (the higher -the value, the more exact a match is required).

  • -
  • maxnum (int) – Maximum number of suggestions to return.

  • -
-
-
Returns
-

suggestions (list)

-
-
Suggestions from vocabulary with a

similarity-rating that higher than or equal to cutoff. -Could be empty if there are no matches.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.string_partial_matching(alternatives, inp, ret_index=True)[source]
-

Partially matches a string based on a list of alternatives. -Matching is made from the start of each subword in each -alternative. Case is not important. So e.g. “bi sh sw” or just -“big” or “shiny” or “sw” will match “Big shiny sword”. Scoring is -done to allow to separate by most common demoninator. You will get -multiple matches returned if appropriate.

-
-
Parameters
-
    -
  • alternatives (list of str) – A list of possible strings to -match.

  • -
  • inp (str) – Search criterion.

  • -
  • ret_index (bool, optional) – Return list of indices (from alternatives -array) instead of strings.

  • -
-
-
Returns
-

matches (list) – String-matches or indices if ret_index is True.

-
-
-
- -
-
-evennia.utils.utils.format_table(table, extra_space=1)[source]
-

Note: evennia.utils.evtable is more powerful than this, but this function -can be useful when the number of columns and rows are unknown and must be -calculated on the fly.

-
-
Args.
-
table (list): A list of lists to represent columns in the

table: [[val,val,val,…], [val,val,val,…], …], where -each val will be placed on a separate row in the -column. All columns must have the same number of rows (some -positions may be empty though).

-
-
extra_space (int, optional): Sets how much minimum extra

padding (in characters) should be left between columns.

-
-
-
-
-
-
Returns
-

table (list)

-
-
A list of lists representing the rows to print

out one by one.

-
-
-

-
-
-

Notes

-

The function formats the columns to be as wide as the widest member -of each column.

-

Example

-
ftable = format_table([[...], [...], ...])
-for ir, row in enumarate(ftable):
-    if ir == 0:
-        # make first row white
-        string += "\\n|w" + ""join(row) + "|n"
-    else:
-        string += "\\n" + "".join(row)
-print(string)
-
-
-
- -
-
-evennia.utils.utils.get_evennia_pids()[source]
-

Get the currently valid PIDs (Process IDs) of the Portal and -Server by trying to access a PID file.

-
-
Returns
-

server, portal (tuple)

-
-
The PIDs of the respective processes,

or two None values if not found.

-
-
-

-
-
-

Examples

-

This can be used to determine if we are in a subprocess by -something like:

-
self_pid = os.getpid()
-server_pid, portal_pid = get_evennia_pids()
-is_subprocess = self_pid not in (server_pid, portal_pid)
-
-
-
- -
-
-evennia.utils.utils.deepsize(obj, max_depth=4)[source]
-

Get not only size of the given object, but also the size of -objects referenced by the object, down to max_depth distance -from the object.

-
-
Parameters
-
    -
  • obj (object) – the object to be measured.

  • -
  • max_depth (int, optional) – maximum referential distance -from obj that deepsize() should cover for -measuring objects referenced by obj.

  • -
-
-
Returns
-

size (int) – deepsize of obj in Bytes.

-
-
-

Notes

-

This measure is necessarily approximate since some -memory is shared between objects. The max_depth of 4 is roughly -tested to give reasonable size information about database models -and their handlers.

-
- -
-
-class evennia.utils.utils.lazy_property(func, name=None, doc=None)[source]
-

Bases: object

-

Delays loading of property until first access. Credit goes to the -Implementation in the werkzeug suite: -http://werkzeug.pocoo.org/docs/utils/#werkzeug.utils.cached_property

-

This should be used as a decorator in a class and in Evennia is -mainly used to lazy-load handlers:

-
-
@lazy_property
-def attributes(self):
-    return AttributeHandler(self)
-
-
-
-

Once initialized, the AttributeHandler will be available as a -property “attributes” on the object.

-
-
-__init__(func, name=None, doc=None)[source]
-

Store all properties for now

-
- -
- -
-
-evennia.utils.utils.strip_control_sequences(string)[source]
-

Remove non-print text sequences.

-
-
Parameters
-

string (str) – Text to strip.

-
-
-
-
Returns.

text (str): Stripped text.

-
-
-
- -
-
-evennia.utils.utils.calledby(callerdepth=1)[source]
-

Only to be used for debug purposes. Insert this debug function in -another function; it will print which function called it.

-
-
Parameters
-

callerdepth (int) – Must be larger than 0. When > 1, it will -print the caller of the caller etc.

-
-
Returns
-

calledby (str)

-
-
A debug string detailing which routine called

us.

-
-
-

-
-
-
- -
-
-evennia.utils.utils.m_len(target)[source]
-

Provides length checking for strings with MXP patterns, and falls -back to normal len for other objects.

-
-
Parameters
-

target (str) – A string with potential MXP components -to search.

-
-
Returns
-

length (int) – The length of target, ignoring MXP components.

-
-
-
- -
-
-evennia.utils.utils.display_len(target)[source]
-

Calculate the ‘visible width’ of text. This is not necessarily the same as the -number of characters in the case of certain asian characters. This will also -strip MXP patterns.

-
-
Parameters
-

target (any) – Something to measure the length of. If a string, it will be -measured keeping asian-character and MXP links in mind.

-
-
Returns
-

int – The visible width of the target.

-
-
-
- -
-
-evennia.utils.utils.at_search_result(matches, caller, query='', quiet=False, **kwargs)[source]
-

This is a generic hook for handling all processing of a search -result, including error reporting. This is also called by the cmdhandler -to manage errors in command lookup.

-
-
Parameters
-
    -
  • matches (list) – This is a list of 0, 1 or more typeclass -instances or Command instances, the matched result of the -search. If 0, a nomatch error should be echoed, and if >1, -multimatch errors should be given. Only if a single match -should the result pass through.

  • -
  • caller (Object) – The object performing the search and/or which should

  • -
  • error messages. (receive) –

  • -
  • query (str, optional) – The search query used to produce matches.

  • -
  • quiet (bool, optional) – If True, no messages will be echoed to caller -on errors.

  • -
-
-
Keyword Arguments
-
    -
  • nofound_string (str) – Replacement string to echo on a notfound error.

  • -
  • multimatch_string (str) – Replacement string to echo on a multimatch error.

  • -
-
-
Returns
-

processed_result (Object or None)

-
-
This is always a single result

or None. If None, any error reporting/handling should -already have happened. The returned object is of the type we are -checking multimatches for (e.g. Objects or Commands)

-
-
-

-
-
-
- -
-
-class evennia.utils.utils.LimitedSizeOrderedDict(*args, **kwargs)[source]
-

Bases: collections.OrderedDict

-

This dictionary subclass is both ordered and limited to a maximum -number of elements. Its main use is to hold a cache that can never -grow out of bounds.

-
-
-__init__(*args, **kwargs)[source]
-

Limited-size ordered dict.

-
-
Keyword Arguments
-
    -
  • size_limit (int) – Use this to limit the number of elements -alloweds to be in this list. By default the overshooting elements -will be removed in FIFO order.

  • -
  • fifo (bool, optional) – Defaults to True. Remove overshooting elements -in FIFO order. If False, remove in FILO order.

  • -
-
-
-
- -
-
-update([E, ]**F) → None. Update D from dict/iterable E and F.[source]
-

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] -If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v -In either case, this is followed by: for k in F: D[k] = F[k]

-
- -
- -
-
-evennia.utils.utils.get_game_dir_path()[source]
-

This is called by settings_default in order to determine the path -of the game directory.

-
-
Returns
-

path (str) – Full OS path to the game dir

-
-
-
- -
-
-evennia.utils.utils.get_all_typeclasses(parent=None)[source]
-

List available typeclasses from all available modules.

-
-
Parameters
-

parent (str, optional) – If given, only return typeclasses inheriting (at any distance) -from this parent.

-
-
Returns
-

typeclasses (dict) – On the form {“typeclass.path”: typeclass, …}

-
-
-

Notes

-

This will dynamicall retrieve all abstract django models inheriting at any distance -from the TypedObject base (aka a Typeclass) so it will work fine with any custom -classes being added.

-
- -
-
-evennia.utils.utils.interactive(func)[source]
-

Decorator to make a method pausable with yield(seconds) and able to ask for -user-input with response=yield(question). For the question-asking to -work, ‘caller’ must the name of an argument or kwarg to the decorated -function.

-

Example:

-
@interactive
-def myfunc(caller):
-    caller.msg("This is a test")
-    # wait five seconds
-    yield(5)
-    # ask user (caller) a question
-    response = yield("Do you want to continue waiting?")
-    if response == "yes":
-        yield(5)
-    else:
-        # ...
-
-
-

Notes

-

This turns the method into a generator!

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.utils.validatorfuncs.html b/docs/0.9.5/api/evennia.utils.validatorfuncs.html deleted file mode 100644 index aafd98ebb4..0000000000 --- a/docs/0.9.5/api/evennia.utils.validatorfuncs.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - - evennia.utils.validatorfuncs — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.utils.validatorfuncs

-

Contains all the validation functions.

-

All validation functions must have a checker (probably a session) and entry arg.

-

They can employ more paramters at your leisure.

-
-
-evennia.utils.validatorfuncs.text(entry, option_key='Text', **kwargs)[source]
-
- -
-
-evennia.utils.validatorfuncs.color(entry, option_key='Color', **kwargs)[source]
-

The color should be just a color character, so ‘r’ if red color is desired.

-
- -
-
-evennia.utils.validatorfuncs.datetime(entry, option_key='Datetime', account=None, from_tz=None, **kwargs)[source]
-

Process a datetime string in standard forms while accounting for the -inputer’s timezone. Always returns a result in UTC.

-
-
Parameters
-
    -
  • entry (str) – A date string from a user.

  • -
  • option_key (str) – Name to display this datetime as.

  • -
  • account (AccountDB) – The Account performing this lookup. Unless from_tz is provided, -the account’s timezone option will be used.

  • -
  • from_tz (pytz.timezone) – An instance of a pytz timezone object from the -user. If not provided, tries to use the timezone option of the account. -If neither one is provided, defaults to UTC.

  • -
-
-
Returns
-

datetime in UTC.

-
-
Raises
-

ValueError – If encountering a malformed timezone, date string or other format error.

-
-
-
- -
-
-evennia.utils.validatorfuncs.duration(entry, option_key='Duration', **kwargs)[source]
-

Take a string and derive a datetime timedelta from it.

-
-
Parameters
-
    -
  • entry (string) – This is a string from user-input. The intended format is, for example: “5d 2w 90s” for -‘five days, two weeks, and ninety seconds.’ Invalid sections are ignored.

  • -
  • option_key (str) – Name to display this query as.

  • -
-
-
Returns
-

timedelta

-
-
-
- -
-
-evennia.utils.validatorfuncs.future(entry, option_key='Future Datetime', from_tz=None, **kwargs)[source]
-
- -
-
-evennia.utils.validatorfuncs.signed_integer(entry, option_key='Signed Integer', **kwargs)[source]
-
- -
-
-evennia.utils.validatorfuncs.positive_integer(entry, option_key='Positive Integer', **kwargs)[source]
-
- -
-
-evennia.utils.validatorfuncs.unsigned_integer(entry, option_key='Unsigned Integer', **kwargs)[source]
-
- -
-
-evennia.utils.validatorfuncs.boolean(entry, option_key='True/False', **kwargs)[source]
-

Simplest check in computer logic, right? This will take user input to flick the switch on or off -:param entry: A value such as True, On, Enabled, Disabled, False, 0, or 1. -:type entry: str -:param option_key: What kind of Boolean we are setting. What Option is this for? -:type option_key: str

-
-
Returns
-

Boolean

-
-
-
- -
-
-evennia.utils.validatorfuncs.timezone(entry, option_key='Timezone', **kwargs)[source]
-

Takes user input as string, and partial matches a Timezone.

-
-
Parameters
-
    -
  • entry (str) – The name of the Timezone.

  • -
  • option_key (str) – What this Timezone is used for.

  • -
-
-
Returns
-

A PYTZ timezone.

-
-
-
- -
-
-evennia.utils.validatorfuncs.email(entry, option_key='Email Address', **kwargs)[source]
-
- -
-
-evennia.utils.validatorfuncs.lock(entry, option_key='locks', access_options=None, **kwargs)[source]
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.html b/docs/0.9.5/api/evennia.web.html deleted file mode 100644 index ba1dd00a95..0000000000 --- a/docs/0.9.5/api/evennia.web.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - evennia.web — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web

-

This sub-package holds the web presence of Evennia, using normal -Django to relate the database contents to web pages. Also the basic -webclient and the website are defined in here (the webserver itself is -found under the server package).

- - -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.urls.html b/docs/0.9.5/api/evennia.web.urls.html deleted file mode 100644 index e98d22c4cc..0000000000 --- a/docs/0.9.5/api/evennia.web.urls.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - evennia.web.urls — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.urls

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.utils.backends.html b/docs/0.9.5/api/evennia.web.utils.backends.html deleted file mode 100644 index e5776552aa..0000000000 --- a/docs/0.9.5/api/evennia.web.utils.backends.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - evennia.web.utils.backends — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.utils.backends

-
-
-class evennia.web.utils.backends.CaseInsensitiveModelBackend[source]
-

Bases: django.contrib.auth.backends.ModelBackend

-

By default ModelBackend does case _sensitive_ username -authentication, which isn’t what is generally expected. This -backend supports case insensitive username authentication.

-
-
-authenticate(request, username=None, password=None, autologin=None)[source]
-

Custom authenticate with bypass for auto-logins

-
-
Parameters
-
    -
  • request (Request) – Request object.

  • -
  • username (str, optional) – Name of user to authenticate.

  • -
  • password (str, optional) – Password of user

  • -
  • autologin (Account, optional) – If given, assume this is -an already authenticated account and bypass authentication.

  • -
-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.utils.general_context.html b/docs/0.9.5/api/evennia.web.utils.general_context.html deleted file mode 100644 index 3584bc0072..0000000000 --- a/docs/0.9.5/api/evennia.web.utils.general_context.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - evennia.web.utils.general_context — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.utils.general_context

-
-
-evennia.web.utils.general_context.set_game_name_and_slogan()[source]
-

Sets global variables GAME_NAME and GAME_SLOGAN which are used by -general_context.

-

Notes

-

This function is used for unit testing the values of the globals.

-
- -
-
-evennia.web.utils.general_context.set_webclient_settings()[source]
-

As with set_game_name_and_slogan above, this sets global variables pertaining -to webclient settings.

-

Notes

-

Used for unit testing.

-
- -
-
-evennia.web.utils.general_context.general_context(request)[source]
-

Returns common Evennia-related context stuff, which -is automatically added to context of all views.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.utils.html b/docs/0.9.5/api/evennia.web.utils.html deleted file mode 100644 index 751f170c60..0000000000 --- a/docs/0.9.5/api/evennia.web.utils.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - evennia.web.utils — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
- - -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.utils.middleware.html b/docs/0.9.5/api/evennia.web.utils.middleware.html deleted file mode 100644 index 11301d2a7e..0000000000 --- a/docs/0.9.5/api/evennia.web.utils.middleware.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - evennia.web.utils.middleware — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.utils.middleware

-
-
-class evennia.web.utils.middleware.SharedLoginMiddleware(get_response)[source]
-

Bases: object

-

Handle the shared login between website and webclient.

-
-
-__init__(get_response)[source]
-

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
-classmethod make_shared_login(request)[source]
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.utils.tests.html b/docs/0.9.5/api/evennia.web.utils.tests.html deleted file mode 100644 index fca4071067..0000000000 --- a/docs/0.9.5/api/evennia.web.utils.tests.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - evennia.web.utils.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.utils.tests

-
-
-class evennia.web.utils.tests.TestGeneralContext(methodName='runTest')[source]
-

Bases: django.test.testcases.TestCase

-
-
-maxDiff = None
-
- -
-
-test_general_context()[source]
-
- -
-
-test_set_game_name_and_slogan(mock_get_version, mock_settings)[source]
-
- -
-
-test_set_webclient_settings(mock_settings)[source]
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.webclient.html b/docs/0.9.5/api/evennia.web.webclient.html deleted file mode 100644 index 33b1584648..0000000000 --- a/docs/0.9.5/api/evennia.web.webclient.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - evennia.web.webclient — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.webclient

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.webclient.urls.html b/docs/0.9.5/api/evennia.web.webclient.urls.html deleted file mode 100644 index e70d2d4493..0000000000 --- a/docs/0.9.5/api/evennia.web.webclient.urls.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - evennia.web.webclient.urls — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.webclient.urls

-

This structures the (simple) structure of the -webpage ‘application’.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.webclient.views.html b/docs/0.9.5/api/evennia.web.webclient.views.html deleted file mode 100644 index 417bbf871f..0000000000 --- a/docs/0.9.5/api/evennia.web.webclient.views.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - evennia.web.webclient.views — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.webclient.views

-

This contains a simple view for rendering the webclient -page and serve it eventual static content.

-
-
-evennia.web.webclient.views.webclient(request)[source]
-

Webclient page template loading.

-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.website.forms.html b/docs/0.9.5/api/evennia.web.website.forms.html deleted file mode 100644 index aea1721da8..0000000000 --- a/docs/0.9.5/api/evennia.web.website.forms.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - - - - evennia.web.website.forms — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.website.forms

-
-
-class evennia.web.website.forms.EvenniaForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None)[source]
-

Bases: django.forms.forms.Form

-

This is a stock Django form, but modified so that all values provided -through it are escaped (sanitized). Validation is performed by the fields -you define in the form.

-

This has little to do with Evennia itself and is more general web security- -related.

-

https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#Goals_of_Input_Validation

-
-
-clean()[source]
-

Django hook. Performed on form submission.

-
-
Returns
-

cleaned (dict) – Dictionary of key:value pairs submitted on the form.

-
-
-
- -
-
-base_fields = {}
-
- -
-
-declared_fields = {}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.web.website.forms.AccountForm(*args, **kwargs)[source]
-

Bases: django.contrib.auth.forms.UserCreationForm

-

This is a generic Django form tailored to the Account model.

-

In this incarnation it does not allow getting/setting of attributes, only -core User model fields (username, email, password).

-
-
-class Meta[source]
-

Bases: object

-

This is a Django construct that provides additional configuration to -the form.

-
-
-model
-

alias of evennia.accounts.accounts.DefaultAccount

-
- -
-
-fields = ('username', 'email')
-
- -
-
-field_classes = {'username': <class 'django.contrib.auth.forms.UsernameField'>}
-
- -
- -
-
-base_fields = {'email': <django.forms.fields.EmailField object>, 'password1': <django.forms.fields.CharField object>, 'password2': <django.forms.fields.CharField object>, 'username': <django.contrib.auth.forms.UsernameField object>}
-
- -
-
-declared_fields = {'email': <django.forms.fields.EmailField object>, 'password1': <django.forms.fields.CharField object>, 'password2': <django.forms.fields.CharField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.web.website.forms.ObjectForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]
-

Bases: evennia.web.website.forms.EvenniaForm, django.forms.models.ModelForm

-

This is a Django form for generic Evennia Objects that allows modification -of attributes when called from a descendent of ObjectUpdate or ObjectCreate -views.

-

It defines no fields by default; you have to do that by extending this class -and defining what fields you want to be recorded. See the CharacterForm for -a simple example of how to do this.

-
-
-class Meta[source]
-

Bases: object

-

This is a Django construct that provides additional configuration to -the form.

-
-
-model
-

alias of evennia.objects.objects.DefaultObject

-
- -
-
-fields = ('db_key',)
-
- -
-
-labels = {'db_key': 'Name'}
-
- -
- -
-
-base_fields = {'db_key': <django.forms.fields.CharField object>}
-
- -
-
-declared_fields = {}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.web.website.forms.CharacterForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]
-

Bases: evennia.web.website.forms.ObjectForm

-

This is a Django form for Evennia Character objects.

-

Since Evennia characters only have one attribute by default, this form only -defines a field for that single attribute. The names of fields you define should -correspond to their names as stored in the dbhandler; you can display -‘prettier’ versions of the fieldname on the form using the ‘label’ kwarg.

-

The basic field types are CharFields and IntegerFields, which let you enter -text and numbers respectively. IntegerFields have some neat validation tricks -they can do, like mandating values fall within a certain range.

-

For example, a complete “age” field (which stores its value to -character.db.age might look like:

-
-
age = forms.IntegerField(

label=”Your Age”, -min_value=18, max_value=9000, -help_text=”Years since your birth.”)

-
-
-

Default input fields are generic single-line text boxes. You can control what -sort of input field users will see by specifying a “widget.” An example of -this is used for the ‘desc’ field to show a Textarea box instead of a Textbox.

-

For help in building out your form, please see: -https://docs.djangoproject.com/en/1.11/topics/forms/#building-a-form-in-django

-

For more information on fields and their capabilities, see: -https://docs.djangoproject.com/en/1.11/ref/forms/fields/

-

For more on widgets, see: -https://docs.djangoproject.com/en/1.11/ref/forms/widgets/

-
-
-class Meta[source]
-

Bases: object

-

This is a Django construct that provides additional configuration to -the form.

-
-
-model
-

alias of evennia.objects.objects.DefaultCharacter

-
- -
-
-fields = ('db_key',)
-
- -
-
-labels = {'db_key': 'Name'}
-
- -
- -
-
-base_fields = {'db_key': <django.forms.fields.CharField object>, 'desc': <django.forms.fields.CharField object>}
-
- -
-
-declared_fields = {'desc': <django.forms.fields.CharField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
-
-class evennia.web.website.forms.CharacterUpdateForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]
-

Bases: evennia.web.website.forms.CharacterForm

-

This is a Django form for updating Evennia Character objects.

-

By default it is the same as the CharacterForm, but if there are circumstances -in which you don’t want to let players edit all the same attributes they had -access to during creation, you can redefine this form with those fields you do -wish to allow.

-
-
-base_fields = {'db_key': <django.forms.fields.CharField object>, 'desc': <django.forms.fields.CharField object>}
-
- -
-
-declared_fields = {'desc': <django.forms.fields.CharField object>}
-
- -
-
-property media
-

Return all media required to render the widgets on this form.

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.website.html b/docs/0.9.5/api/evennia.web.website.html deleted file mode 100644 index 268dd0012f..0000000000 --- a/docs/0.9.5/api/evennia.web.website.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - evennia.web.website — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
- - -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.website.templatetags.addclass.html b/docs/0.9.5/api/evennia.web.website.templatetags.addclass.html deleted file mode 100644 index 1ae7d0dbb7..0000000000 --- a/docs/0.9.5/api/evennia.web.website.templatetags.addclass.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - evennia.web.website.templatetags.addclass — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.website.templatetags.addclass

-
-
-evennia.web.website.templatetags.addclass.addclass(field, given_class)[source]
-
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.website.templatetags.html b/docs/0.9.5/api/evennia.web.website.templatetags.html deleted file mode 100644 index e587d0d3db..0000000000 --- a/docs/0.9.5/api/evennia.web.website.templatetags.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - evennia.web.website.templatetags — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.website.templatetags

- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.website.tests.html b/docs/0.9.5/api/evennia.web.website.tests.html deleted file mode 100644 index fd538e8a83..0000000000 --- a/docs/0.9.5/api/evennia.web.website.tests.html +++ /dev/null @@ -1,483 +0,0 @@ - - - - - - - - - evennia.web.website.tests — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.website.tests

-
-
-class evennia.web.website.tests.EvenniaWebTest(methodName='runTest')[source]
-

Bases: evennia.utils.test_resources.EvenniaTest

-
-
-account_typeclass = 'typeclasses.accounts.Account'
-
- -
-
-object_typeclass = 'typeclasses.objects.Object'
-
- -
-
-character_typeclass = 'typeclasses.characters.Character'
-
- -
-
-exit_typeclass = 'typeclasses.exits.Exit'
-
- -
-
-room_typeclass = 'typeclasses.rooms.Room'
-
- -
-
-script_typeclass = 'typeclasses.scripts.Script'
-
- -
-
-channel_typeclass = 'typeclasses.channels.Channel'
-
- -
-
-url_name = 'index'
-
- -
-
-unauthenticated_response = 200
-
- -
-
-authenticated_response = 200
-
- -
-
-setUp()[source]
-

Sets up testing environment

-
- -
-
-test_valid_chars()[source]
-

Make sure account has playable characters

-
- -
-
-get_kwargs()[source]
-
- -
-
-test_get()[source]
-
- -
-
-login()[source]
-
- -
-
-test_get_authenticated()[source]
-
- -
- -
-
-class evennia.web.website.tests.AdminTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'django_admin'
-
- -
-
-unauthenticated_response = 302
-
- -
- -
-
-class evennia.web.website.tests.IndexTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'index'
-
- -
- -
-
-class evennia.web.website.tests.RegisterTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'register'
-
- -
- -
-
-class evennia.web.website.tests.LoginTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'login'
-
- -
- -
-
-class evennia.web.website.tests.LogoutTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'logout'
-
- -
- -
-
-class evennia.web.website.tests.PasswordResetTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'password_change'
-
- -
-
-unauthenticated_response = 302
-
- -
- -
-
-class evennia.web.website.tests.WebclientTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'webclient:index'
-
- -
-
-test_get()[source]
-
- -
-
-test_get_disabled()[source]
-
- -
- -
-
-class evennia.web.website.tests.ChannelListTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'channels'
-
- -
- -
-
-class evennia.web.website.tests.ChannelDetailTest(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'channel-detail'
-
- -
-
-setUp()[source]
-

Sets up testing environment

-
- -
-
-get_kwargs()[source]
-
- -
- -
-
-class evennia.web.website.tests.CharacterCreateView(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'character-create'
-
- -
-
-unauthenticated_response = 302
-
- -
-
-test_valid_access_multisession_0()[source]
-

Account1 with no characters should be able to create a new one

-
- -
-
-test_valid_access_multisession_2()[source]
-

Account1 should be able to create a new character

-
- -
- -
-
-class evennia.web.website.tests.CharacterPuppetView(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'character-puppet'
-
- -
-
-unauthenticated_response = 302
-
- -
-
-get_kwargs()[source]
-
- -
-
-test_invalid_access()[source]
-

Account1 should not be able to puppet Account2:Char2

-
- -
- -
-
-class evennia.web.website.tests.CharacterListView(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'characters'
-
- -
-
-unauthenticated_response = 302
-
- -
- -
-
-class evennia.web.website.tests.CharacterManageView(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'character-manage'
-
- -
-
-unauthenticated_response = 302
-
- -
- -
-
-class evennia.web.website.tests.CharacterUpdateView(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'character-update'
-
- -
-
-unauthenticated_response = 302
-
- -
-
-get_kwargs()[source]
-
- -
-
-test_valid_access()[source]
-

Account1 should be able to update Account1:Char1

-
- -
-
-test_invalid_access()[source]
-

Account1 should not be able to update Account2:Char2

-
- -
- -
-
-class evennia.web.website.tests.CharacterDeleteView(methodName='runTest')[source]
-

Bases: evennia.web.website.tests.EvenniaWebTest

-
-
-url_name = 'character-delete'
-
- -
-
-unauthenticated_response = 302
-
- -
-
-get_kwargs()[source]
-
- -
-
-test_valid_access()[source]
-

Account1 should be able to delete Account1:Char1

-
- -
-
-test_invalid_access()[source]
-

Account1 should not be able to delete Account2:Char2

-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.website.urls.html b/docs/0.9.5/api/evennia.web.website.urls.html deleted file mode 100644 index 833449a9cf..0000000000 --- a/docs/0.9.5/api/evennia.web.website.urls.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - evennia.web.website.urls — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.website.urls

-

This structures the website.

-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/api/evennia.web.website.views.html b/docs/0.9.5/api/evennia.web.website.views.html deleted file mode 100644 index ee63d1abe5..0000000000 --- a/docs/0.9.5/api/evennia.web.website.views.html +++ /dev/null @@ -1,899 +0,0 @@ - - - - - - - - - evennia.web.website.views — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
-

evennia.web.website.views

-

This file contains the generic, assorted views that don’t fall under one of the other applications. -Views are django’s way of processing e.g. html templates on the fly.

-
-
-evennia.web.website.views.to_be_implemented(request)[source]
-

A notice letting the user know that this particular feature hasn’t been -implemented yet.

-
- -
-
-evennia.web.website.views.evennia_admin(request)[source]
-

Helpful Evennia-specific admin page.

-
- -
-
-evennia.web.website.views.admin_wrapper(request)[source]
-

Wrapper that allows us to properly use the base Django admin site, if needed.

-
- -
-
-class evennia.web.website.views.EvenniaIndexView(**kwargs)[source]
-

Bases: django.views.generic.base.TemplateView

-

This is a basic example of a Django class-based view, which are functionally -very similar to Evennia Commands but differ in structure. Commands are used -to interface with users using a terminal client. Views are used to interface -with users using a web browser.

-

To use a class-based view, you need to have written a template in HTML, and -then you write a view like this to tell Django what values to display on it.

-

While there are simpler ways of writing views using plain functions (and -Evennia currently contains a few examples of them), just like Commands, -writing views as classes provides you with more flexibility– you can extend -classes and change things to suit your needs rather than having to copy and -paste entire code blocks over and over. Django also comes with many default -views for displaying things, all of them implemented as classes.

-

This particular example displays the index page.

-
-
-template_name = 'website/index.html'
-
- -
-
-get_context_data(**kwargs)[source]
-

This is a common Django method. Think of this as the website -equivalent of the Evennia Command.func() method.

-

If you just want to display a static page with no customization, you -don’t need to define this method– just create a view, define -template_name and you’re done.

-

The only catch here is that if you extend or overwrite this method, -you’ll always want to make sure you call the parent method to get a -context object. It’s just a dict, but it comes prepopulated with all -sorts of background data intended for display on the page.

-

You can do whatever you want to it, but it must be returned at the end -of this method.

-
-
Keyword Arguments
-

any (any) – Passed through.

-
-
Returns
-

context (dict) – Dictionary of data you want to display on the page.

-
-
-
- -
- -
-
-class evennia.web.website.views.TypeclassMixin[source]
-

Bases: object

-

This is a “mixin”, a modifier of sorts.

-

Django views typically work with classes called “models.” Evennia objects -are an enhancement upon these Django models and are called “typeclasses.” -But Django itself has no idea what a “typeclass” is.

-

For the sake of mitigating confusion, any view class with this in its -inheritance list will be modified to work with Evennia Typeclass objects or -Django models interchangeably.

-
-
-property typeclass
-
- -
- -
-
-class evennia.web.website.views.EvenniaCreateView(**kwargs)[source]
-

Bases: django.views.generic.edit.CreateView, evennia.web.website.views.TypeclassMixin

-

This view extends Django’s default CreateView.

-

CreateView is used for creating new objects, be they Accounts, Characters or -otherwise.

-
-
-property page_title
-
- -
- -
-
-class evennia.web.website.views.EvenniaDetailView(**kwargs)[source]
-

Bases: django.views.generic.detail.DetailView, evennia.web.website.views.TypeclassMixin

-

This view extends Django’s default DetailView.

-

DetailView is used for displaying objects, be they Accounts, Characters or -otherwise.

-
-
-property page_title
-
- -
- -
-
-class evennia.web.website.views.EvenniaUpdateView(**kwargs)[source]
-

Bases: django.views.generic.edit.UpdateView, evennia.web.website.views.TypeclassMixin

-

This view extends Django’s default UpdateView.

-

UpdateView is used for updating objects, be they Accounts, Characters or -otherwise.

-
-
-property page_title
-
- -
- -
-
-class evennia.web.website.views.EvenniaDeleteView(**kwargs)[source]
-

Bases: django.views.generic.edit.DeleteView, evennia.web.website.views.TypeclassMixin

-

This view extends Django’s default DeleteView.

-

DeleteView is used for deleting objects, be they Accounts, Characters or -otherwise.

-
-
-property page_title
-
- -
- -
-
-class evennia.web.website.views.ObjectDetailView(**kwargs)[source]
-

Bases: evennia.web.website.views.EvenniaDetailView

-

This is an important view.

-

Any view you write that deals with displaying, updating or deleting a -specific object will want to inherit from this. It provides the mechanisms -by which to retrieve the object and make sure the user requesting it has -permissions to actually do things to it.

-
-
-model
-

alias of evennia.objects.objects.DefaultObject

-
- -
-
-template_name = 'website/object_detail.html'
-
- -
-
-access_type = 'view'
-
- -
-
-attributes = ['name', 'desc']
-
- -
-
-get_context_data(**kwargs)[source]
-

Adds an ‘attributes’ list to the request context consisting of the -attributes specified at the class level, and in the order provided.

-

Django views do not provide a way to reference dynamic attributes, so -we have to grab them all before we render the template.

-
-
Returns
-

context (dict) – Django context object

-
-
-
- -
-
-get_object(queryset=None)[source]
-

Override of Django hook that provides some important Evennia-specific -functionality.

-

Evennia does not natively store slugs, so where a slug is provided, -calculate the same for the object and make sure it matches.

-

This also checks to make sure the user has access to view/edit/delete -this object!

-
- -
- -
-
-class evennia.web.website.views.ObjectCreateView(**kwargs)[source]
-

Bases: django.contrib.auth.mixins.LoginRequiredMixin, evennia.web.website.views.EvenniaCreateView

-

This is an important view.

-

Any view you write that deals with creating a specific object will want to -inherit from this. It provides the mechanisms by which to make sure the user -requesting creation of an object is authenticated, and provides a sane -default title for the page.

-
-
-model
-

alias of evennia.objects.objects.DefaultObject

-
- -
- -
-
-class evennia.web.website.views.ObjectDeleteView(**kwargs)[source]
-

Bases: django.contrib.auth.mixins.LoginRequiredMixin, evennia.web.website.views.ObjectDetailView, evennia.web.website.views.EvenniaDeleteView

-

This is an important view for obvious reasons!

-

Any view you write that deals with deleting a specific object will want to -inherit from this. It provides the mechanisms by which to make sure the user -requesting deletion of an object is authenticated, and that they have -permissions to delete the requested object.

-
-
-model
-

alias of evennia.objects.objects.DefaultObject

-
- -
-
-template_name = 'website/object_confirm_delete.html'
-
- -
-
-access_type = 'delete'
-
- -
-
-delete(request, *args, **kwargs)[source]
-

Calls the delete() method on the fetched object and then -redirects to the success URL.

-

We extend this so we can capture the name for the sake of confirmation.

-
- -
- -
-
-class evennia.web.website.views.ObjectUpdateView(**kwargs)[source]
-

Bases: django.contrib.auth.mixins.LoginRequiredMixin, evennia.web.website.views.ObjectDetailView, evennia.web.website.views.EvenniaUpdateView

-

This is an important view.

-

Any view you write that deals with updating a specific object will want to -inherit from this. It provides the mechanisms by which to make sure the user -requesting editing of an object is authenticated, and that they have -permissions to edit the requested object.

-

This functions slightly different from default Django UpdateViews in that -it does not update core model fields, only object attributes!

-
-
-model
-

alias of evennia.objects.objects.DefaultObject

-
- -
-
-access_type = 'edit'
-
- -
-
-get_success_url()[source]
-

Django hook.

-

Can be overridden to return any URL you want to redirect the user to -after the object is successfully updated, but by default it goes to the -object detail page so the user can see their changes reflected.

-
- -
-
-get_initial()[source]
-

Django hook, modified for Evennia.

-

Prepopulates the update form field values based on object db attributes.

-
-
Returns
-

data (dict)

-
-
Dictionary of key:value pairs containing initial form

data.

-
-
-

-
-
-
- -
-
-form_valid(form)[source]
-

Override of Django hook.

-

Updates object attributes based on values submitted.

-

This is run when the form is submitted and the data on it is deemed -valid– all values are within expected ranges, all strings contain -valid characters and lengths, etc.

-

This method is only called if all values for the fields submitted -passed form validation, so at this point we can assume the data is -validated and sanitized.

-
- -
- -
-
-class evennia.web.website.views.AccountMixin[source]
-

Bases: evennia.web.website.views.TypeclassMixin

-

This is a “mixin”, a modifier of sorts.

-

Any view class with this in its inheritance list will be modified to work -with Account objects instead of generic Objects or otherwise.

-
-
-model
-

alias of evennia.accounts.accounts.DefaultAccount

-
- -
-
-form_class
-

alias of evennia.web.website.forms.AccountForm

-
- -
- -
-
-class evennia.web.website.views.AccountCreateView(**kwargs)[source]
-

Bases: evennia.web.website.views.AccountMixin, evennia.web.website.views.EvenniaCreateView

-

Account creation view.

-
-
-template_name = 'website/registration/register.html'
-
- -
-
-success_url
-
- -
-
-form_valid(form)[source]
-

Django hook, modified for Evennia.

-

This hook is called after a valid form is submitted.

-

When an account creation form is submitted and the data is deemed valid, -proceeds with creating the Account object.

-
- -
- -
-
-class evennia.web.website.views.CharacterMixin[source]
-

Bases: evennia.web.website.views.TypeclassMixin

-

This is a “mixin”, a modifier of sorts.

-

Any view class with this in its inheritance list will be modified to work -with Character objects instead of generic Objects or otherwise.

-
-
-model
-

alias of evennia.objects.objects.DefaultCharacter

-
- -
-
-form_class
-

alias of evennia.web.website.forms.CharacterForm

-
- -
-
-success_url
-
- -
-
-get_queryset()[source]
-

This method will override the Django get_queryset method to only -return a list of characters associated with the current authenticated -user.

-
-
Returns
-

queryset (QuerySet) – Django queryset for use in the given view.

-
-
-
- -
- -
-
-class evennia.web.website.views.CharacterListView(**kwargs)[source]
-

Bases: django.contrib.auth.mixins.LoginRequiredMixin, evennia.web.website.views.CharacterMixin, django.views.generic.list.ListView

-

This view provides a mechanism by which a logged-in player can view a list -of all other characters.

-

This view requires authentication by default as a nominal effort to prevent -human stalkers and automated bots/scrapers from harvesting data on your users.

-
-
-template_name = 'website/character_list.html'
-
- -
-
-paginate_by = 100
-
- -
-
-page_title = 'Character List'
-
- -
-
-access_type = 'view'
-
- -
-
-get_queryset()[source]
-

This method will override the Django get_queryset method to return a -list of all characters (filtered/sorted) instead of just those limited -to the account.

-
-
Returns
-

queryset (QuerySet) – Django queryset for use in the given view.

-
-
-
- -
- -
-
-class evennia.web.website.views.CharacterPuppetView(**kwargs)[source]
-

Bases: django.contrib.auth.mixins.LoginRequiredMixin, evennia.web.website.views.CharacterMixin, django.views.generic.base.RedirectView, evennia.web.website.views.ObjectDetailView

-

This view provides a mechanism by which a logged-in player can “puppet” one -of their characters within the context of the website.

-

It also ensures that any user attempting to puppet something is logged in, -and that their intended puppet is one that they own.

-
-
-get_redirect_url(*args, **kwargs)[source]
-

Django hook.

-

This view returns the URL to which the user should be redirected after -a passed or failed puppet attempt.

-
-
Returns
-

url (str) – Path to post-puppet destination.

-
-
-
- -
- -
-
-class evennia.web.website.views.CharacterManageView(**kwargs)[source]
-

Bases: django.contrib.auth.mixins.LoginRequiredMixin, evennia.web.website.views.CharacterMixin, django.views.generic.list.ListView

-

This view provides a mechanism by which a logged-in player can browse, -edit, or delete their own characters.

-
-
-paginate_by = 10
-
- -
-
-template_name = 'website/character_manage_list.html'
-
- -
-
-page_title = 'Manage Characters'
-
- -
- -
-
-class evennia.web.website.views.CharacterUpdateView(**kwargs)[source]
-

Bases: evennia.web.website.views.CharacterMixin, evennia.web.website.views.ObjectUpdateView

-

This view provides a mechanism by which a logged-in player (enforced by -ObjectUpdateView) can edit the attributes of a character they own.

-
-
-form_class
-

alias of evennia.web.website.forms.CharacterUpdateForm

-
- -
-
-template_name = 'website/character_form.html'
-
- -
- -
-
-class evennia.web.website.views.CharacterDetailView(**kwargs)[source]
-

Bases: evennia.web.website.views.CharacterMixin, evennia.web.website.views.ObjectDetailView

-

This view provides a mechanism by which a user can view the attributes of -a character, owned by them or not.

-
-
-template_name = 'website/object_detail.html'
-
- -
-
-attributes = ['name', 'desc']
-
- -
-
-access_type = 'view'
-
- -
-
-get_queryset()[source]
-

This method will override the Django get_queryset method to return a -list of all characters the user may access.

-
-
Returns
-

queryset (QuerySet) – Django queryset for use in the given view.

-
-
-
- -
- -
-
-class evennia.web.website.views.CharacterDeleteView(**kwargs)[source]
-

Bases: evennia.web.website.views.CharacterMixin, evennia.web.website.views.ObjectDeleteView

-

This view provides a mechanism by which a logged-in player (enforced by -ObjectDeleteView) can delete a character they own.

-
- -
-
-class evennia.web.website.views.CharacterCreateView(**kwargs)[source]
-

Bases: evennia.web.website.views.CharacterMixin, evennia.web.website.views.ObjectCreateView

-

This view provides a mechanism by which a logged-in player (enforced by -ObjectCreateView) can create a new character.

-
-
-template_name = 'website/character_form.html'
-
- -
-
-form_valid(form)[source]
-

Django hook, modified for Evennia.

-

This hook is called after a valid form is submitted.

-

When an character creation form is submitted and the data is deemed valid, -proceeds with creating the Character object.

-
- -
- -
-
-class evennia.web.website.views.ChannelMixin[source]
-

Bases: evennia.web.website.views.TypeclassMixin

-

This is a “mixin”, a modifier of sorts.

-

Any view class with this in its inheritance list will be modified to work -with HelpEntry objects instead of generic Objects or otherwise.

-
-
-model
-

alias of evennia.comms.comms.DefaultChannel

-
- -
-
-page_title = 'Channels'
-
- -
-
-access_type = 'listen'
-
- -
-
-get_queryset()[source]
-

Django hook; here we want to return a list of only those Channels -and other documentation that the current user is allowed to see.

-
-
Returns
-

queryset (QuerySet) – List of Channels available to the user.

-
-
-
- -
- -
-
-class evennia.web.website.views.ChannelListView(**kwargs)[source]
-

Bases: evennia.web.website.views.ChannelMixin, django.views.generic.list.ListView

-

Returns a list of channels that can be viewed by a user, authenticated -or not.

-
-
-paginate_by = 100
-
- -
-
-template_name = 'website/channel_list.html'
-
- -
-
-page_title = 'Channel Index'
-
- -
- -
- -
-
-get_context_data(**kwargs)[source]
-

Django hook; we override it to calculate the most popular channels.

-
-
Returns
-

context (dict) – Django context object

-
-
-
- -
- -
-
-class evennia.web.website.views.ChannelDetailView(**kwargs)[source]
-

Bases: evennia.web.website.views.ChannelMixin, evennia.web.website.views.ObjectDetailView

-

Returns the log entries for a given channel.

-
-
-template_name = 'website/channel_detail.html'
-
- -
-
-attributes = ['name']
-
- -
-
-max_num_lines = 1000
-
- -
-
-get_context_data(**kwargs)[source]
-

Django hook; before we can display the channel logs, we need to recall -the logfile and read its lines.

-
-
Returns
-

context (dict) – Django context object

-
-
-
- -
-
-get_object(queryset=None)[source]
-

Override of Django hook that retrieves an object by slugified channel -name.

-
-
Returns
-

channel (Channel) – Channel requested in the URL.

-
-
-
- -
- -
-
-class evennia.web.website.views.HelpMixin[source]
-

Bases: evennia.web.website.views.TypeclassMixin

-

This is a “mixin”, a modifier of sorts.

-

Any view class with this in its inheritance list will be modified to work -with HelpEntry objects instead of generic Objects or otherwise.

-
-
-model
-

alias of evennia.help.models.HelpEntry

-
- -
-
-page_title = 'Help'
-
- -
-
-get_queryset()[source]
-

Django hook; here we want to return a list of only those HelpEntries -and other documentation that the current user is allowed to see.

-
-
Returns
-

queryset (QuerySet) – List of Help entries available to the user.

-
-
-
- -
- -
-
-class evennia.web.website.views.HelpListView(**kwargs)[source]
-

Bases: evennia.web.website.views.HelpMixin, django.views.generic.list.ListView

-

Returns a list of help entries that can be viewed by a user, authenticated -or not.

-
-
-paginate_by = 500
-
- -
-
-template_name = 'website/help_list.html'
-
- -
-
-page_title = 'Help Index'
-
- -
- -
-
-class evennia.web.website.views.HelpDetailView(**kwargs)[source]
-

Bases: evennia.web.website.views.HelpMixin, evennia.web.website.views.EvenniaDetailView

-

Returns the detail page for a given help entry.

-
-
-template_name = 'website/help_detail.html'
-
- -
-
-get_context_data(**kwargs)[source]
-

Adds navigational data to the template to let browsers go to the next -or previous entry in the help list.

-
-
Returns
-

context (dict) – Django context object

-
-
-
- -
-
-get_object(queryset=None)[source]
-

Override of Django hook that retrieves an object by category and topic -instead of pk and slug.

-
-
Returns
-

entry (HelpEntry) – HelpEntry requested in the URL.

-
-
-
- -
- -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/genindex.html b/docs/0.9.5/genindex.html deleted file mode 100644 index 462e6a70ac..0000000000 --- a/docs/0.9.5/genindex.html +++ /dev/null @@ -1,14243 +0,0 @@ - - - - - - - - Index — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- - -

Index

- -
- _ - | A - | B - | C - | D - | E - | F - | G - | H - | I - | J - | K - | L - | M - | N - | O - | P - | Q - | R - | S - | T - | U - | V - | W - | X - -
-

_

- - -
- -

A

- - - -
- -

B

- - - -
- -

C

- - - -
- -

D

- - - -
- -

E

- - - -
- -

F

- - - -
- -

G

- - - -
- -

H

- - - -
- -

I

- - - -
- -

J

- - - -
- -

K

- - - -
- -

L

- - - -
- -

M

- - - -
- -

N

- - - -
- -

O

- - - -
- -

P

- - - -
- -

Q

- - - -
- -

R

- - - -
- -

S

- - - -
- -

T

- - - -
- -

U

- - - -
- -

V

- - - -
- -

W

- - - -
- -

X

- - - -
- - - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/index.html b/docs/0.9.5/index.html deleted file mode 100644 index 1944659679..0000000000 --- a/docs/0.9.5/index.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - Evennia Documentation — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - -
-
-
-
- -
-

Important

-

This is the v0.9.5 documentation of Evennia, originally converted from the old -evennia wiki at 2020-10-11 18:06:03.062022. -While we will fix outright mistakes, minor typos and visual conversion issues will not be -addressed in this version. -A new and refactored version of the docs is being prepared for version 1.0 of Evennia.

-
-
-

Evennia Documentation

-

This is the manual of Evennia, the open source Python MU* creation -system. -You can Search the documentation on the left. If you have trouble with unclear documentation, please -let us know on our mailing list, over IRC or by making a new documentation issue.

-

There is a lengthier introduction to read. You might also want to read about -how to get and give help.

-
    -
  • The Getting Started page helps installing and starting Evennia for the first -time.

  • -
  • The Admin Docs covers running and maintaining an Evennia server.

  • -
  • The Builder Docs helps for starting to build a game world using Evennia.

  • -
  • The Developer Central describes how Evennia works and is used by coders.

  • -
  • The Tutorials & Examples contains help pages on a step-by-step or tutorial format.

  • -
  • The Documentation-Contributing page helps with how to write and contribute to these docs.

  • -
  • The API documentation is created from the latest source code.

  • -
  • The TOC lists all regular documentation pages.

  • -
-
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/objects.inv b/docs/0.9.5/objects.inv deleted file mode 100644 index 797f66dbd7701f5a205433a502860dde5f13d907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95213 zcmX6^V{~RsvyE-rw(aDJZQHiZC$??doYX4JDIa}K}dXv~VdRjQTJGuIhcv`r+**H0pu)&d&D7!k@T9~=>kpRBEjy5LWCe8nM z^O1b(>edz{t`-g^Hjd^Nt|U&DB<|KWZX}jA_7)`HqnVR~v#W)hn}s=vhntO~6^XCC zjVTkH_vT8!j@R~N%gyt(lFMp~5p%$^5YhEru0(ZjJA>>t+1q5`uo$S0hBc@(2NcrV zGC#Ham?>!#aPm&PGlqgL5R~LI*^SE2)We|cYq9Us2y;Fv!8<$nCQB zE@(CApVQY&(0k{X&3izOg@4uC^$v3aw!N-1%dzAA3Hny0_(srzu$+Wug~2oNR}*Ph z*1*3}o#*)HLZcW(mvy=EoPj+~unR?hrks?3-ydTw18+mG#q~M~@t8lX@&lFXgzpHz z7pcuL{;^K!X4FhX%+A#GnFY8S3v$si_r$HW!ywo%2{FoMP*VzHoXtBKwHYWypD$5I zD$t#SeNd+7eGOZx?~vN)jKjT4QK=Bty4fp|%SK!6p2&F0Ux2?laQ2m|Hw-5Ot8H)* ztIV#@?Ep9#0&~&EJ1%E#pK<<9;z8yfM&>q&_>sNpY08a==w55ch;*{R8im$^b`Ff~ z&2XkoO0uiBj&;NXK~iOROB9XtGG@AMY2|b>I0-Ry7+!kZyN}s!y~)apBfHJcKD>+U zD*LD57Ez&DIPTl%X}gpRizNhwkyoVra^ABnQS*DgdhNTk3_!sX0k08;&(vQ|efKG* zdWBk)c(NL^s;3LGw_hfet2v$N-j83FYBWW==xgdA%!p;WQ#XY z=_?ivi+evl6UrStNBhwY7ErgRw~562Np;=`j}v{w#T|wf(kiOBIYM9#Hq#2?PBaua zR?>RYqorBNz5rJQ(#s;3JN2b_k|^O5iN?usI+FJ@?b$ zi^rt2KG|d5X+5(?4alW_M&~V68=k(|xIf38=rVHm`)8cEfI|TAt?QvEV_nS{nz__~ zp=ko5=Rb#0xA`OeNM`EqOD{XDTQ!B;l5LiMVIa6e{J%9Sp=9XX7Ah1{)BYflJKt&m8wCBw7Dt72N6k1A71UDt9~|Jm zXyQCZxt63wY{=s0{yk7uLrc6_H-}PcnDtv}sO2QEX%CwD4g#O17^c~cv?qKn*+B*_ ztR@^R^JZ#0{GL}di2pr_4B20o5I(zr>Wz?&fOH@fBDv~qHK?uFU-8WqHFMf>au2U& zc_8=H4>c%l5&aad{PdD5MP333aw3RnjuYhcNWMXk;j4(*G1^JYfL{Wb7%rmWkmxA{ zP#jY|`$6}zt)oFOm#lGgZ%PfE^FMbh5dF|1IMhMAIDn9dx>psX(T%Gak~ zh30&B2dIjYyUBtvv*>HRKf&mNEU@)UjRLpDs?~Cb7t5RD>!rg$*Gih<8^p}80YT#( zc~Fd%n;9n_p}dGxHfG3&C%Gk&9xWED%lQO#V3zo1r~>ov-rm8}?3);h1*{#-_i;nP zr{HGT>Tu&!#&Ni@NvxXX9=T%xItSojf5tQ|R5FA3?8YsK8Vm!@Sw3)i{ID=}lQ zybX~ubcf&-OJOiCu>m=%AN*y#C~=+Kp(DxgBtD6@LDQq&X+na={qVoe%$NgO{1jGH zbAEW6v>S4uv@dh0r5W$%WC&vVp8>BOBgnf(GLR89jR|7&>NOj^fIjtuX!CVJXHh{l z{?>0l*6AZRQivre8v+3S*%|2pR=vLeN)|8+E|_XtcIx%GS9cV0<1;tS9kopb$qnX3 z0}xyIGmZ@N_^7t$wm8Lh=B6zW5$$S+P5wOkWyNWgR44!Voq)sa-3iKSKtkssbz9uG z!0&dVi`~m@bY9SYweZmSnwVMhc+=8Ba`Co~>;YxcFSGUYZt6^~o+z%{og2fVK;5R0 z(4&24_=3`6dAZ3hSEU0R_i5e>8I#x5i|$-hg}PlE^NE?!1=)p0-31rk z%)l!1l{B8Qnga%9U@malJ`-p+${f>XMhI8ucjc>DeFoa;WP7+UZ;+)xw@S3)FPYX= zy=26Idwy^e5+a;7<%Td}*P!XRS`mvrB;ZHu%dk~K8e-~O3%l>2wL-O=^Gh)1wqws> zv6QCD0y8KS`Ko|gO(@EJt2|J^$Wbo{hk1?rc4D@rNOqOIAX0tD6-zgv|+L`Z^-6qGC$BY}H?9$J*~qNMc9B+>E05pBVG zOwGQ-4__NWv_<`{GnyWKI3~m`ZFk!HJO`(|AXHNy#}EfC+wJzo8ZUlECOI|E-Mr|p|xFJ{vy-fvkz4O5>t+F#_zF#F$Esi^&NA}%E8_c=U zEFnO^40?31MuogM_=1X$je%H}&aYdWR(4kq$#@mxm&Oi@t$R&{RZ-UgwfvEefFZ*Z zW`!ac)}0^I_YF^ZMtn_V1kUC*32$E zF^y<){49`T?Db^A4tTY8NQNXxA8gM1$22NV7N$p7qK{0hQIAX}&F@$Qe0>-OF-S+{ zc7+Ui2*7Z}jqK;l*>n`8;WQLkY*iGE@e~w!Ol2Cn$`+T?0plqK^u1!KJEVBN#Gv_= zSB*5!b%SkQb-G#SU14ofKe3D)UGCpA_3j{;lhv~gnqX9Wicn5nMJZ$sLdA3Y5tB$b z27;tAGC-2-&;x;*QRa}r|DtTgy zwTdRyX_dxD=eboFtjlqBIw+7;I1jJv+HC)9h$3D&qILaqWJ?9-$TD^j%HcDwyU$w9 zOkK#h5S{{E_HYW0q~N;GtXR@7smVhAQe!ngP%}lpP~+9VYiWlL{#zS-Xn_;JrGw~( zLQIzp#*-=%LL|#5h)g+CEVTx3v&)c`pEW~jJ)k~lKBT$Q`{K1@3ihwZ6v)~El$hE} zD%J6Pem$A6wqhzBkVojBuxH7hqbG}8qsMCBp=U}wp~q{!(lY?|CVe_4+0tOdXlV!{ zkT8I{5OA`kAQ7t2i?ti;gj4qv8M&sqj13WcDA=vSi3D1kU?PTHbW|E_$t-m3SvdnnTYRSlxDxyL_kjIz3 zy!1oxTdjCzM1Cxu8&vU}`vRH@N?86X$}qMXN@U?W$_SPQDFT2<_{z0@NMS9Qip&yU zdjhAqdUB{9C%n1d`>eR0#yweZ-xBSLjOEj#IkjJ>x@r>Rhje zSnu6`#_bL2L$85&LC(zBnR_F|Vk6gXGo33DK zPJ?wth-e>M=k}zAr-oY#p!qo*3uKkf0zH}v2Lw1*$z?h5tdNg8Rn?p1XuXZLi1D(% z-${eavpPT|fwf40{NkmBXnJ=OAxWds&p{3iW{riV~jN>S|=2<2>VQ#Vj$X!~#= z3AUe>Dd(t{>YQgu5BrrWIp)Zv?{dTo49#gztD~sK-$L{{s1r(%&eaR?1EQMG($6~O z+;QUdqX+mUqHb-U7?1@OFtkuEz)ACj5dBRJBJ&|A!q^~bh-PAibP7~svSBciDUNB- ze1=ujOWaGfSEejOmmsY%>Yo{dD3f|eQRodx*$r>t1@|N1Y(6t2MARq=AY+r=FtV7g zX!OHp*K&iqy~1X?B1gbZ}9()k*-ZEia`LZTG84xcRWhS@l!+Y}5d* zFCQJihHdy^G46pmy^jKaNTPfLsOFb3%Tlu@Ymmg$U6%dVsT-3d;H3eHU@Y=ebA{KF zlnytIGW#IeoDV>z#2q7>W`_~0dyAgNqe0)ff&7paLTvB)I3cELH?-P(FZR^ONwJZj+j-{33q zOtX@@Ad_}kjlSz68g~k(u~iq@uO(Brf|a@p9VRw`FHVj&_oaUy^lS=nhG>7#Z&MW0 z6EfxR%KYIf=ZPfc7(YH%`;?yitK3{&^#IbDgmQW+XxBb+h3xz_fMGjZwHR|7usEVG zbzlYv<`c4^$_f`nWLHUXl6pj9=opO`Mpnb5N*7G|+-P}B!AfL+>O^?? z(j-J&aVo50WjZXbC^J2mj|9M4fcp=-Z)baGQjI#O%|o^F!?e0KodO+BHy$;e$?O9C zUsixoJq=3Qxf;*(m^hj=nI|k+W=}X8tU*bYg%I16SgPt)ojIb@VpOywQuVYHRs5YK zUjMr!TRM~!Q8!Aa@g|CGqCp8?qI5Ot^lNd0PILhxMg#}8wW(nn19@PU#2=~Fv(&^B z)p8;QawP%w3^r%7$**=3-IsM-V~Av`5Lj9ADOjn-X;=lySy<_YIm~RHgfq-nD}`n} zH8q-c8z99n2ZO5yQ@2D9hqNx92b{LCjOnA*Vo_q9EL8a?MxsPOj6{PVfHEb-R3+9p z^dM^HdB@1h#(?AG5>=fxuW6FPWx5=3U5yX0SHLC%{(`Yv<`C7Yq^x;td!4*XHTQ{- z`}lmR)WZ937y6@-NV8q@CvO+`Bh?l9y;r-dq`JXXAx9l`gA1aZ)6ypn`fsd0M&BIj zB*JB`IbU|bI^{v76W_%9``p%Ts?l~~7s_ZPFi<&2fD`0AtP#yPA7&veio67CF`oH1 z0Qto4r!CHp=c2DiwPCnxg@^@^B|6M#*5+TwNUF-2v3{>frzIg~#Lw*G zW1nr1?im@D3Rxc`_W)mxnE)Nfe)R#2FQ^~Pi#qQG1UHcZFev;S(`Z^c(?azv2q_Xe zIT7^?3dQOfR0s5OZZPSb;K|~LPvob91x;x_ss^}Z5p^7JIvRLlG<1j(YiZ*PGPWnl z5`e)KP_Kh|a;dbYCdFD~ND9;@3X=;hHkxC4pO^D=46)k;p3C4#g9O+|cBR_#?Pqya zC5(=oyCyeL!H^f<=`clb#647rh-H(!T?khLAL7qHO61B~d!$*P@@Vl9gaNpbqey6) zm|2XxHU*kyInrb-5yP69p~|vyptm07Ea4?N-V`dLn<@Mit~QhPo3~ghGja&^6>H{b z4vk6cct%4s?E_$kc7YM(E1&pG3J>mQrkAE?P2}muQe^{7p?~?<<~$x;FQn5ndkAQb ztiIn-e)9)VKx6NbZeoSy19t170hOkaSf2D1^9IGK$jy?ERd%& zi908A2w*vE^Q`UzJc%vkE%9c1@d#f(7 zZq~jPg3|E11*)pG(KLsrs#oRXC(I2ovemCL~$iYlG--WP3wbqwB(7; z;a8yqz3n`^X1}(Pt&57&E^fgR6+-?tmNehooHE${9E<7WoG}{gPaa*uhHePbxAc3X zF1|DfK3H44{8x-b5$_oBI^Hpg!`&>8z!9PT#CfN%w9qjI9F5!Y1PD0!p>s4 z&sYUv@_MiB9$!EhPb~yqjQ$&jLU|ZSra26xR2haT)fi5R!DPRy-h4CV)gNvG^SePsMB9cuDR+P-e zsU9B*dUdK{m;&E-;P>J0UHQ7DOy4S&=n}YiofEu!jInE=*xUhmI74!x$Pog+X;sQ) ziWC|qX#Qu$sIB+Z347fY-PFC;W$kqA%{C(QA#GDarBz{Qe>5q56lnfmX=Z2lFLRL7 zFuTBDa=*$r5NwzcK~p#{lZ?$2_^$O3umNNj)wd9J1@IcLFuBy!alr)w##-5(H- zs-5HvqeyHSO)pg0hGaZ7O)r({x@3G6O|N9xT4>GF#O=!?C8`EY&Y00vq6IjIxdiIY zcH)&1z2#Dwn|gd!@Wp1(a|CRW%JIss-Rd3M#!P;st1dl6?}IK7fAlEgSzf6ZlhCCQ z;wABafki0EZ|Jv&;$BT&RiI8d=B&bZe@6l-79~HW%V9o~0eD|>GzMJ|aa+E|KW;uv zaU1?3gvxr6BN=#-1Jymq(?s0KV^rPbsC>(|f0#yoe9&u*{{C?Q{q{lckoWtCYWT|s zy^Z^~0?FkQ5q`xrqs`K%^tja8 zHVm1@4h-eO4zNUhH$?`Ewk^E}dR3_TiQaY0u_EbDAk~4KljUNK(}^mKb49YPb9^}h zfiEQ%?np+lh_bEnfiwh-=+SwqGT9#)jP`5E?}!(L{+j23H>g2{(hA2s2jAzV1Yf4D z2EQ7k1z)GEM^9{V z`x>JX?mt#_*l)@)WXO6T-Kcz_DR|)|yO9jP7_u1*+{u25Py9vXsvNbI$#pm*1kW%LaOd_Uf{@ zX|l;uG5h2`%vyjqh?pm%mZX%Oc<>Qk%Sj%8B)7wAwfavE=2@lHm2pf&E`Vr6!QeKa z@}6XO^5Xuz2}w3zNY-2!E3ZQGFtfsf{rhiHMaM^DC5tk-t?|Y@bM?%tM5azG0f^gv z>M#z}cFc8#C@x5@6(K$z{_&Q(8% zd$m7DOAOD~!gFqB&hp8ZJdqo6bkk^(uney-K2Am}yo3(havf%Z`-0QE6nwh*n&Bp96u7Ntmn#yi zVni3IC|hOooRkd=nxKHFYqjCfIx9*$Z#kUi4;IH|(Wzy?G{=uD z6s3@~kDV+=W{WRLny-1N4=(xpN(PH3suBzSpMG>$o;z3J5e8pI2Ti@U84Uef(ozv4 z4cv@6ll|fF>Sp5@u*!nM1hSEg=f58p#em%lDDpXC#DhBfM?Z&L*Y^635eeJ8P0d*@ zdoJNL<0dvzQJbMFG@ER_Wilqfm02#xN_&SQz1)YE%H2)w1PWiH#@zTq#^(D}+Llek zNhy9|NmeMRnd>Kl!$`&R&b()cZ6Yr_+DSiGfmSdOa_f)uAkv>}GyWGaL7977s zKQh|O*ZU=8YDD#5Fp^|kz~XuKYW@7({CmjtHh+uGXs7no_`_IfUsSzuV`7lVn=ldX zB5bS7TD#**z(!kGzfy5zsozR%q!$%{uxt)LO;A+Csut`*VhOdtBq0NWM`-Z1883ae z_ILffupdYoHh)mMOu?mXKMKLpjUpa|F3XE}n!q>7Zpkxpk62uB^k7lbtMsBOHZ)U9!VrHkniz;OYN2p_$ip)xOQ;WMp zv63Spxso$Hv5*5cv6|ybmavM!*Ro1QY$~z)W0p@wtX{@kqMp_tp`O;+zMhu)z*?c0 z+|J>SRPspBoYBleR8PYNo^zCi2%obKhAGTfkU*0g(SC z!ULL+9>7D*NFR~o<}FQW%HN+=yX|nNY5nt|WW;x?VqPYhnvV!|wzXm3VidjNQHt=Z<6cqHK-5I;7vyCYNy0(4717funJ*UoOu+Q7BMF2A*M}VNOi$ zZsiq+&RFRk!dNvuursM>;sNAsk2-8H(cc97)nvY6pZMOuE4Z3JE^=>XuQda7=68EQ zc#9Vp2w>YBxotMTPAo&$t~3~O#vZ3hnN>}9?CXB1j@|o5!oYxcX-lAMGit|J+0T0d zsbB|mTo_CKLKvvdjqAvvNr2^Lxsw_vX$HvIN?w#G)(%f?Zlu+s3<$XEAGl!ByB#Br zqANk05uTvgDHLic+(8AhaE%9<&>&{+Ek;3-X;sC^wmJ+*Qqa>uU%Z$N2q4I6ON=n8 z^_ivhUA^E=oha5v5?6on>sO)s{OJ`zJ$9RrUOT(jeRS(J{*CYt4Bsu&rxezHeulUY z{%)))+HBskH|XzhW7WgUI&Ey)leu--m9GEh_v+8?g1CUi8p__Sp=&dP3Mc1Yeif(m z+_6cEI(+=;e_6zZzRN8Ew_r|zhWZe8UVd)U#I0Dl*?!rKe09*I@`=4U8e&;`Z=NqC zPkr^O8qZb^`5D?z$vZdN`MGMTB6GF<+@6HY$MiV5FB5L^fKmx5WJyhqL_t)<-k#KX zxPk@3JqIKhwsgsvk}&K)rW*ew{U$fs1W<6tm~L3i#IkNue|W2GaiP4!l*s%Ybw*`t zEIMXVp(7Wj1QiLCW*iBS6s*OI!yu-F&f$&nfBYud0<>^nCX4=wN&{`_G(+^fo7)X7Cg~L*7{S9XPV5jBO z3YF)>;tIm9?<5G#PJ7yDpAmgtQjex$#8EyTFW{e~R#eZ6tjYm=j}J(k`z$>O#@-OEDp zNCH_h65KGNc5bgI~UzLC~LRr@QSvprb}ic;>dvYS?k$eg?`uO zzN+jITta*!!lkXetHZvz4Wa3+yg6pil?e96ty1a4T^u8%qfmS)pR;kUX>{niJK5OD z^c~a?{kin4?kbRGE-D?GFi7($@0?Onf;QdsYvcJA;_+32K4s90uchrYo8oKjdDpgU z19uL)wNz`n2l<*vcK9cW5I1UPyUSz^METfRZ7_%JIIev>o>IW%DEp?w!q4bGA|ESv zn#}GSH9w5b>VF0OE_VoW*;&40le2S;ibT$~*Z)Br?KHQ!-m3TO$i~l1Be_x|ic_#4 ze2$W{!1F5C1r%gY>tD1*rWTSP-Y9I}B9HS?yd|o*lSJ^ut~CzxO9sT=^S^(zj8U<@ z({H+?(Lbj6oIw;lQOTW6gQND$ahajCVD>U!bE}#?q`FX6o}9rJ)}fGu%`jPC5I_YU z;tcu*^+1A+j-Nk1m->jA-FQSR6~HI*xMFZz@j!@NHW>^D=bl1-ABYz|i9=&MZxShv z;%*I+TGf$_=aB|O=QqbTdQ0Z6a}_z2n^+deaceLY>`JPRy^8mZ+1*v&cZhVQcB{T=c=5gR7{xq5u3i!p+4cmIy4wF7$V^SAjFZ2Rs+3^Qn19Pty6 zI5s9b?6mkUiuDgm_gdjT=kgkH<}{)>zRHtvAGnRd{0A8GZ_TU{xdMrDCMnhgmGStF zD=J0pP2w2Ug3v7WI-(A#;Y=+jWME^%r*&Wtdr>T#-;4`5560#YP5%ucGn*uwoVMXZ z+vpEyG*ivHgaaK*@vWAgN3A==3L1 z49y>H@?Sg2;R6J@CW5{@Qf7U=(ZLJq|CaXZE;g0>{;Mt??i^mJ+lE2MaK-Bja-k%n zJG^3n!C1CV6!bq-<*@>aKvyd4tHN_)sV$a?567zRoZjVj5O_8mxDpAw*e5Nzs8KAq zAJAc3h@s#hOtHY={WfFSeIT}u`pz{q4*qNG42!+e)9BEHKQV&7#9((f`xk|mL$ z`uk7YRjdtdKXiQz(f+Zbi8ovd!|lS5#nCTZmI|bwd(sci!+;BX;SteGvDB~Jp32`l zxr`VrPm-me5?eY0?mUv;aW{A^ca+YoM=m(Sp@(bD?emu2ty)er)!LQ2?r=Geu4rC; zZ&|lUiiRh=IS+NaNPz_td|6io3kZRvi2w0BwljxZDv-2p)@-z%Esn4bE)>0Zn!{CF z5n-L19^CNpYpUrh|CceUkL`Np=_;}1mQRlYjZY9qRI4G=&TPci*p~Nx{rlh@?QeUb zSInWQwtM@06wSf9e3ZIJdSv6abHkK2o2(ZN2UaLTF}G|~^ucEQL0Bkn%3$LvhhbzW zlZe78!K6mXz(GZrJ8%=SBw`7F1EJNW-y8=sJ3^h`qa|1}aRD>2=DB0nw9`Lydl_qa zK8;wx*{yGZ(5n?INCKnd%5kO7=xOS2>jRF@xI|{yWPRZ&RqMAU1q4`N^I~9vQ=+9? zyo{8ji6znZPKq|*(1hfVc^z0?jT|gMg*9<$l zu1rK9Qsq%ux2+~_6wB5J2oO=6wkSNR^q9sEw&nG0-f?6ijkjjyaKZE;S=JwT7AN{G0N)FSN8~%;smA1A&FY%}iTq zt9p6Z6Ea51^8Cr7i`(f?fR1#0jCmb5<-LrlPoWL$-NpX3|J9C%e}RygfIL?dnG9uC zQY^x&tH4s$cqdELte3HAjPi)(FPINeNcj@afsl6SOJcPfQ!!kLf|PuDns0>v3|qYR;FbTO)V9=Szsr72phv zl|!gRRA7>=G#Buou=0JSj()kS00lMir-BhJ4q+c0Ft!+Ao_1~Za_(_$wQ?R%EfAgR z*|l7oK)+x0G@uLIMW~G_==3;^$>*MGAv<;W-F|Qa@v0E``VK)P58unpIj>W~z0> zl@6zP3DgrUfrmCb?VjktA-;=J-79~xE@w~dGh1H$|5)l8>GNsYN~~yON>rw~oy6)g z$;swup#rz$P&KujdIkpg6>ik&zR1y}{Ezt2*5>*^)08feM*5j$`M_GbsCbV9& zpDF<_W=vuw7EXs@AB$QPyJbFslnH6a9y}=q@zk19b*bK_iaoPf?L{w&8}?)h$I^Fc z9e^JC4G(YgZY|De=AE|aS_^AO3x@nvL|7jtY(6_U1xGO4r0wO|fq$%_4H0y_jT1OG3SNmsKD+M^zv zc_W{NN&|`>vncWucnDmj&-P3(FGlrKWY6H^B5AM7cI=VF2Dm`%=a-()gBr||dW9Ah zDzu$XQFUorsyL;{5~GAhMchv5dZ=H&8*WRw`V2H@7sveJ%GDq6x$5iY_r~5VnIToG z{ym3+g3$iH?_pAWO;B1G?S4ZLoJZ&UnyS2mK8cWOgvZIPL-C1y$`S^j9th?Bk;hL6 zdb2!t20{E<;$9z6d&9Fj@aQ$LB51c&yy$hlTV;^skgT@0ai_u8ZY5C6>Gvn~wC#*G z_{aJMyOZ0zix9H0oBeXa;;pVHiiYkgQ;%qKvh5dq8h+jQ2P%_IgKBSs15n<8qGrzv zOK*1$s*|C67SgrJmUC=us6aiNV7tM-BiFgsN>ij9Xi&`4}QIDW)=Z@*;8+q zALmlyk%ynEBoAYMVMw>Z?QFfpknGY#6-j<(8AroG1AWhMJ2W&I-|`-KkW0Epm!|-3 zCxrw(S#QaZal_cK!^DlXHm#Q=ym@K+9=UJ2P3s;i;z_B1GAyVV=G=jb58;xqV~nTB zAbtN;o||xa&*RwlYpJP(W8}w|LCY0XYPX|W)S<`0kiT`U-{W!ylvamBe2?`!vT-@D z@bXXH`w?y1+4-*8j(6I<8|^~PGFV}4O^zbc*Hu2vx&qSZ+9!Y>YLe@YPr(A}5Ts9J zNX;y}07P^_v9awVA%S;O(9c++0fFp#(`a{55ahiGhm}DCXB03`6GC=nC6+zrSG&c> zl0DOZANrM{aLa02+C#6A;6yD>FjXOMk&!3^PB>Yj{#cP{P&08zL^Je@%Ngf9$UM^ij~=~=||QS~il_|b?Adkb_Nhlz|gtqvbi z2ww0Tm#(piU(0NK=G{6vakx7>>g}mSB)8gr`cS&+>#al`4ce|&2>vEA^*Qy+-RZjt zxVfKUB^V(Ug4~bt$IV(iVReK1Zc8 z$)xQ#9)20|o#O0DSg%hi{X5i6C2^oGG8R25oJi?F5xB7mC7tI|Jv+oZ`l%|V@nsDC z6pGHQki8l;)K<%+#aSWOX@`AZZZrpdp>m@ZS06z84=LBQdzE~)#}5mBB;u%&rDaa6 zOF3N16`owTvYthrxovl<)T-%bg7&_<_((U$;&a0q(5)LJ8>@Pa%F4>;4fFddO3|~8 zba!&rTL{a*tle}FX|Z~>6L7wUtxFN;R!q=P>pkDWe!SF@E|W>5OMI_s&?*#}|7IRQ zl9AsmRAJy}5`YWi^z_pn9|Sw*I}D|uyMPI)x+I$@HZ2M-Zi&N>c8?*p5u9RFTq{R4E*e7h4)b9EiWd z;YSXW7^27?!jRI;kwF(p6onUZ#NqpsgB}K)V8sRr$dgGLJN7S3Ot5%joFK*e`pc6` znO?UAPmFbajwSkCq^F3E3-W!5S&Ie)k?yrAdD_qGWw`&LuQLAGqCfA52tMtxcPJCD z2|XRLf9XdMM#DI-y;lF)&Ehn@x7jQtKX^Va2A#~ggM7V};|Pg;xXOE}xa0#%erP*G z@n^LN=vH5hMky>#KII=7k^O{FqH#kblN%WgY-C?~Tf~<#Mr-=@8g>sSUc1B;t%?cM zu8fJsoq-E}IM-q^PlkoKev`Z+59rZQ4n& z5Z8NX%w?|`hv&9ZSHP|iH+%pDG;s?Cj^w+He77GlAPspx=YG!S7Er&HwD}O@yIa+H z_m*GhmV$WI1+=V7-1Ia13YgrFA>Ub1)dbyDLa%yM#Q0RlbzcHl42{~dt(*HNmxVAR z4m@5+#prs-)8}lAP!!kmyYGKK3zQZ8cpiGE>E!d$8XWI9;Zk-h3%2?j#=G|~r}HI3 zppEtvYexH2uJiY?f3@{UpwqhNH6N>^)vt2miPWOa=h^E%G^KDitK{zhr+0F>$1W59ffn#62WDCL&?YmLa-_kCRq^QlPLd|8WLgt zMSNLkqq0OJg*qTNrY9b-=87`wF}X^d!S2&UMmE?HyHU8%ylZI zel+hkcVN0>*XP=M&S_9Bg-nDu&NZ>1IIFkiK}Q*Y>p521=<)2jm}#y@^1A881ZyBF z=rjl}vo3*1Wew=;>gsZ3$=lth3EWqC@4~Ebn$_F%ka7MFS1C>%c6b+k zQW;Y7I|J6ju_1bG=mwZz<%%wqCObQ6&K}wd`UXY#+z`dw-1ivJbbp zCF2dl(P)b|5nVl@5QvKKq<8(yE7`pdGA_fnc`4M6{6Zgb9y=E;tj}E$urdZ_G?$r` z>xjE_N3t9B8jR@Z(=RP^qmp*^ZC@Mtk2teltFOU=ZD@FhqFlEZkmNa_VnVnye043g znCTK{>1wB#oAo*T8O2Pg+)*Lai#B;5Of8o2p3}S`%28eE&J!G)o39`Vt;A>%m_qz6%L$;a=YJux}AA;LZ13KC7wDg6dsUi%5%TpRr5u*n|Z4W?6Ch78Sc{+ zFG5<+5GYcI5O9RG9hP*f?`(CF z4`1(zF5)uilZCqt+d|aAZC*)gL-Jbsl6vfj05fR~r~m2e=w2!n{; z{AlndV2RH86VL@7yh5qG8Jao-zF>($n*PEP6(e~+4u*?;)Cdx(&%+XtQha?5lhjBh znxHT@&9eX%Fk6+;1>P41&;`W4(IVg)BG38;OPtypLd^`K?Gz$W1jmC1TH!AYKoa(3 z@O!-ln&R1KzZ4oQf;*55`JVOkHXalDsWeo2AD7Vfis)ZCLh5$Y()DJLwhH)_9%(F( zSsd6PB}f`KGH#?@H2ZigHcSvW{ZSt1NXB{*SyD^biDBOnWv?%Tg0jU^+KpLhHf#70V5SiEcQw_vqFlyN6(~TtS zQ}fRatG%NaOrE`R0NLw&;eF|+xsCWjCU0^H1#MVDUU?jLT1kGgc};#YtGV4-S`VkR z?f!I-V5SHtNa>{am8dyqV?L}d803)%LVtaJQQA%pfq1~KT#-o9A_lMp0&bk}H>I-i zLldR4v*UxrW2_q2C>jlpQeURX&|bi|_}(roliwEq1vDBGY?_&pB1&Z;fg4I?qNc%b zF&a#%T)@$zh1~)%4UXLgF`anY{;gqlK}<)T^?z$v-?nJ2hq`luOp)pv9;T3`-?v67 zZN{&I!1LJ-_xY`3FxpYBJgn0zCqMs5-yEdF-Dm?@i!(JxN;R} z=k=5_W6LxVB!d(T`FPZY0=X10la+mfHVRsd?=uSiy?~fmc+14YKM)iT7(b;#33!#ABLUF>Tf#p+hYn9<&L9hNG%#uw#N`=p-%Fwkp-w%DC%*R3@;=x$d zx*$&qPTi1VB#mf_SasM~v??@toJuq)qKZPHuMGd!&wD-B=a1B7C?%pg=ETCAz_Excqa`?x4t zFv#BfJHqiQW#55D%D+AA`zXCV1VbvmJ&@&iWj+9&=uhVKjVG6{_E0s3of3{n4p;kqjSL zsnC9l#5fb%1n<)KnRhFD^a@Rg);4zd-q0=JCi`wJ;)H+k;@~1)Ib{W^3cjb(pNAEq z>WRh6B!+uuHYmAtmc=LLZ^cf_4V5r@Q^iQSV-)Z@fLOGq7ni9dnMtH zgJ7X3#&Jm>(@PzJ*4jF%qgV2!NV|~P458SwG$ws}=;L>xc;rPpTB9#+RblI10QqkM z*n4dR!v2o{Fj@ac00yY&|04i;)Bh1bvC>-zMW-#A#~>b^&Lo~;S`nxHa1*D*-C)4G zH}%(}z9JY1LLNKZKInR!3XD}~h6@NU3YQ=LFlj;~P14hi~L8H<|up>+Ftiw$ASOkF9MZ#6IX!h}3zt zT5NO+ETtZvdyM}*k3+c%zM6qYhX}TWxYwK+u#Nay2<{UtkBLhkfnEw42mrCEy}>JD zp;65s8g3A_mGL}4qUHt>EhS-(7I3?UR&YSCc)__*Bnf|tvO9|>tAz48LpjoLNYePt z4kCi6L^VAlY@_I>Eg)uv;Dg1Xwp^oQeekY;Uhtc+24hj`e^4_({WXQCeG3N!ec!aN zirNT809eg0*jUg$oS|3eqdW>-Ody{=XBvYcCzI@;Er-ddt#Mk?W0o1d7#Xte4R42N zACCz>%LEq?huk>`>x0#4*(zp%P-kU`-(_mpi%b%BFIo|27(G=0NG$xB^yRCF0SyAB zs21@T#6E>CoLv-sEQA0imj-DcVx#L0zvb&lAgBjPfbNftF2@e;J0|d_Hyb^ZQ+_1? zdWDNN?O~;Ub({$`%5vEjN zpN%04-;AF}Dl~($N2+kMnqZC0SWm(k;YrC-+A6Lb+|C4W4qkiwSf+GYe3FexX>(g(GD9q>O;OO_>_+(XJ!)?d$-m z_<yPdbHBO($@<}BQ7#&I;r6P4?>>M=e((Vx5e_(Thp(~b*OMgy?F zV@?}%#X+lKf5U||>M+v{QTMe@b>_y>0ly7O&HSI&9{(K|E{5czebW56#9Bw?JLK8{ zF0s!1lTKsuk=HJ9ztK(kGfn$UtF*FfpmiB^{J!?f9Ologh9%7Lx`zeK@zUeBw%h)# zt^BeCPW9GCe{7|AUD_oZiHG6@ba%Y7{PW=aV{&&!*E({vgGm`RRcSYiNPpTub@>Ca zd(IGAB0*nIbwSZUL2}_*--2(U!9ay+0m;COEK9IAhv#dz=~ea|dOY232BK6?D-~Wc zs(}qZ?x%r@5RO65cwE!Nl`Yw4;90_25=t5IS_Wd8l2$5Q8n;$9befe`8bq4#S{5kP zL<7|*aZMZlr=hOHr1+n?_VUmOE3H%%EFl96UF{byz?8}UFU|@e`g4OgNa4B=;q<3$ z1xsCD_p}Ux9daY(%bKCVnnYx~lNLK0bInDK_>HdRP2P)J`cTmMVSD%&?vIoUw4v&# zH45+z@BZ)3oL&7|2rMGQWRO)u1EUw__gIW~(PgQyllcDwa6pg0F24MO+_V;Y z*E1VnRz09IpmXT~{qou|2hpEhS=7HGyC5FKucQ>0#PuVSWoc3td0aQR$Z7Gt@K2|Z zPPDA+mgzOx;|N=$5bB6o7qGmFp<}@+2pCSt>b=F>ixC2@cVfcqsHet95 zvc3x6MY(CibvnkItMdyYwPW}Nm16;Y`=!(WXYu|1O0wM2rEAS}D<7{v+pXLUS>j6K z-IeC!m77JEJf)#^SS;tREdQ;1yRsZO&Q86S1i13&8nfTp!Rt-C(zZS4+I6&UPdvN2 zR6M(OZMk^%?8=hyCnk6QdJ>*pR}1{CU)PwL&pUeE8T!1>S5+~6*TpL=OWybOT8fnO z9$rzBJn!I@qJFpnH;$$z7T1f`6tR?Es53uDRZ@agH-T*B3oRsldN}Al zcXSsjzH@EGi?6sg-{n_stNf1dzSrOPd&k5(0o_Sn+u(##-L|+1=lO466VCRl@`Vt+ zx1E7OQv}nUmL;M{1i^nW(jN&izUWUEEv949V=L2;#=`*#P&Io_(zY$0(vpbiy=L2= zDk_Wf@s0$r<52geW=cR4ZL^k>ygkoWpbtv+O^6csiBa5Fr;ct-a}t(16^P0CA|@mPNIrQj7m=V>b_~f!u)OdQD@WcToCs)g+U%D$9ouHOffDT|N zPdvMW1M%%p$<3S(Khv_LeFaN${MWGZhFt$|Jcu6*A@fhe+#qf!j>PCO7^wj@k)y<% zLC1D9Z!#EO27luySKs{kc z6eRJ8=qWa~C?`LzN0!Us;>f~`HDLUPA3-3b!_{VlIFc9)J-!6y@%l)CA7`ZAP)SQ5 z!;B^@!WjvR5%{PAQyyH4$i}G|QH2&`R>YFr@_fix@;s$_;#((FpWHamd1DCmCD&~s z^p`%iMSfsj+j}giUO0dmN5L-mO}_}ndTJ@0>p!8>pY5^TbQmnO_z0xK6&-<&gcloN z4wNi)-$)CL-m;7pq$`wg`{|q6r|Xs z(ow`Xnwij)!KAPIA`jXi63v1bK|D)3rHGc?SYlfGKr5;xIgYr#a{fy*=FtcF zf=w9W3u>eQUwA+o-V1K*;HEA=Bw5z==tL!HaU`Y4#DY@Hh;mYVaxp1tJSk0h!T@_< zJ$+s51R81Z^^7B=5qAnF9HSBRalRbQ8y{)(8FV5OX3&WlDT7XY(s-VzaWd!%A0fbg zFP_++4XvCsGoGjnKZ3LjIkLcv7g1zp*m1=st1Yx4FNyI2UeP0jyW&O;cI88qP*>!* zfi5a*pIDz3#Py{K8!zN%7b3*{438ZAvm;RyFypU82Gq5zpNwjBO_J!2O+Y_mKiHjld6+f=VSFXg7`8zHUif!cqq25+pYRQfJC=p3+ zfr}!!B|oy{maasS+<6x$CAai|QgTbK*_1Y)2K-3xT6UpELPyLsWL?&8p^>_-&p1NY zg?$DdNo{fZm4E7;A&5%31uaG)x5URSvP-k)iWX=O`=9_ zl4SGc22mU)NCa`LrbGl04x-pUn(amKD9%$Yf@mrN)RA&6?rm9CSb`X?^EQHDcIWVg zAnvEvf|UX^fhG|c)}FUjN}!`PZs<%%VDm)fw5Gc|O#{5S=c3G+sO5FryLrx+ZWhan zOMgxuMYVRb5qHXPGVWY|4xD;MtN{|X>-4LJ1A;iFk}oiEfa+=OIp(;N58QfCZ1*@-zBjmDQ``9_JJvrT_aka4==C$zUF4D33x4n?yaY6#k3DdSDbp!!YHy=KFxX&6A9g0BPq-kiJ~xn$l9X8tCef>bcxaM zVx&fco(^iKNs%+PQ=jbn%OQ|`+V=cV*(GxLkC01S5gVaBt0;LS&AXAs zMd2XcBsvJa#4{Yk;zy+pmaK~%*~t!0R0}r1y1`%U-gU-+Z)j!N z+T=}ImLeUlrZl|&$~+nSqRx|4v?Chc;Q>TaFS2Sp6$6pciAr&QBzR<+HXIJDll5Vo zXD2J+G6>A=6bu)m?+FEA3gC;w`!@Dvp6NDZtghKmvA$+<^Rjvu1~)BP_tANn_UMlD zprl!LsjJk-;`T%~53BhuVe&)IUV3=b)|1Kn%x_HU@-u2Xi_6b?*QfivsG*k#p7-ei zIy_df0TszAQ|>xm3Kx^1Eg@~ci8v_3MGpLEP4GhbcN(^kcA=xnobL>5b*t0U!{l16 zP>&Qrr#2n29%i4}E>UWgtpaGNj>b(-FOqffYldlEE`P(aZWcj!Tc5Ohww|Z{`fd$R z|5g59_Ev9MnXa?7C!1v#y28&|y=YDZJ+!@q{S9tXCudlbJ-Epd-pdl+%No?neAlu) zq``e!cAy>}(=HM13O>}J&+^Dk_Z^f$T~z|JR(D0t;4R7-T##1Pj3(G5RCCx67xgY^ z7llcL8y8kZB?;1E11|4|=|Kxfd!kvypFm%E11rV`{H}3syJO`Z)?TaD9rV2!4+Jwj zhzBs3byiSDfwj}O1JKJkrgdIk7(sY*rA2jeyz;B~{CVoYyU?5W4rJTDjf+H4BRZd@*^%kKzujw-Ug)|FN6B4GkH2Z@0urfYX^UrG zx7_jUA6SoPyk!-CVwm=uGR;r{9p>{<6HC*u*SqLOJX@NWzrS0CjDNp8DQc{LTpc?4h8Ltve(T}EQ0-P?En&Z3LOpG%-aqyPmrEv?YrT% zC@?Rz*aPfaOh6B{K2;}BeBYkq9zw94=z>tphf6Vw?7qaund2Bea8B-?Q%$8c571U#vtZ7 z|Q)(Wo)kG2U^Lkw z0p7l21V}Ddgx1sMQMd%WmvQ73Lo|)&_?Rk?&EwsGZUiG4-&(=rZF(K`pt*BlyGzX6 z4ru@Nen&5TvU`=~jat2;m-po0_aj+N%bp58bQHX!oHM>}d1YD;Xb*AB8L-|9qi7!E z=xmvSK%d2E80~qCjpbpd_d%424i$xxjJDQO`r0)+=T4haB0U-X=v7I8W1rPI{!L7! z7^nhlmW$fjww;A%I~#BO_Y>QvqTzLWOxrZ;w|gvB_FW7+w;%mU0WC>?=(Gu}Ra-uY zBiofpGc=7wcSksAs7X*Q!@ngt{3%5#^T^O3feG(q@X@B)RxXE1g&tIK|8kRqWO`xyuqhn z)JEPyiYI~xhW!2wrAuECA&7Ov8IrRu!!(Z@xY%{v1*vG|vWOHltZ-j7vhq;c0kip% z^IwSOtM0IQq8kjQyR?l#IKp&CZyvk^M%q}V55~ECbQKszbu7yH&G;z%c*E?DJ)xlW z#V$B_9kFW&!M)&C9rSKRa`_%lb9_#B9QvF;VF5Sx0=FUSSB|!7lDe0Kopp0 z&Z6fD>jQ=XIQ3BNm|W_K2|C%16$4Rpi5akEIU-}V2_Xgpw{zb>kb5sh;|+PNR@Sa4^8@91{Gxl}JM11iCy#U5&TkXs!a@d6yd;q29Im4&$ zMz6_2fyi5sO}p|n$&3A^qal2apDf)3w$5JCs;HSftJBOe1O|+Y4uQ=UbZ>K3YXaJmNGut0$!QcU!^cmwFjC;<{C|F3>r){6`672QLkC#v1J9n{7 z;WuAw>UG=hp4i_stafy4MSX%-IDkwfL)IpL4I=UGUy&F`UStf%_ST71A*PEb9K%d# z2*>h%C`;UTS+aePOQ?p6ryK@YHqBHN!=>p-gf}ifS(iqx#${?L>K@NHR^Doh!x1?U zyJ3f8xaSCu+`ggc)@EFM{It7YmiZe^y;F>ned`{2FrY_oJs4Zg0#oBz+fz=#m|I=p zaG+((;NFu>4}1{pp0!n>`glIKEpO3+W4fi!Pj|eyxlqTG5# z0GLx_OweVw21f$_mu{1xhowFhubD((Uxqny^qO497N$GL9BYrx(Q%igPiax6yHXRU zHYo=2$SZzeb@6Y4yHiqD-3{|*(HI!`K=-ArT-6SPLq66qZ$OvRIQ}mN zF9_@}H`&Bwp<*1yFy1x0m?(NSL72b4n-#{tUq0V~F zY#mhuz_32B$b+?Utf#+4yE8EZ>|31u7+~G@>_;HP_Sui2Ap4UU14!Uci>uMVqhF8)CZOurYF?xH{2qQ4^#9$s}P~);K&pzci`kSv0KeM9Ic# z#OG*+s5aI`g=2K~!;Xrvn+2Kxrp(Ldm#r_`&L8@+@gZz__s(|Z@0hb5t@X9j)%o)0cK6^fl^pE>Q@}=UgIR9*zW-SqIkLWxfHm zY*k@(w|VKH*f6sw`q`Qs-!RoFabEel(FRyvZqXK4U4Ey3+*oFNiRBF0o@)k+yR_^Um{gWjq}~kg67eoC`-Np~ zES!ag$>s4>w2QkRyQ1{JwrOH@^=6h>U#-)eWsW!lawOO%2mqE%c7VWGqy_-Y#vXlu zq)Q9v0Wqr3rU6~pXC07Z-SgMZr&St@_p+vZ{Fh~3tFx9R2Ds&mJoCnli((32=P zbI&We4_ce-%b`13zHJ|nu9G7IY!{~hkgPUc2IFjQx(q;aNMC;Tt+ST1`HkJa0-ulG@EgL#+=?6!^qCnMR4w zhyKDkO+RaRp`*=HgpDR)hKweFhl@4~8Y+F-{j%+=SzNo>URz%KC_Y>E(!4=9*tYMV z38GkzafLA~dVhidvfh)(KVoC*%woc87^7rqEn775%y9Ah4j5zf(nOB&A0L@U>#t1N(fFF=S{LYgN@5ekHy9~1*Wx)lby6jBfq zY+!MwCIP%(O;Ue;zh0#OysEGY#1AImE__wsU*q4R^{mQ!@8M#;g z+Jti$M+QidmY{Pf>K1RmFc(ulC**}>@ea`oWnJL_g{IxFWIj`f z7Rzgq^apydkm}HrQ{{N*{d1ORA;9h(#o9Xw`je*H|_(^oMtd1Bde}! z$Y4~Qr-D5vsVKbl0hk@@W$h324d$#71(KQ{*=>~@%WJoajMcUJ3Z=JH3AE3B9>q)EmB<&z5!Y^GHvYb-4;lT~~+n-p+P4b67u~ z(?Xq-YgsSzhl`5%2wzmiQQq=flpIj|Jn3+w*$NA84?`uYvE<-P9~l2_Yf3c&nniii z=HNa`WXrlnGr!RFGb9pju^lAu3$spb#>! zW8y=z@BknU42v$E~5>(5KPQbs4vc$A+rcT&)XZV9;V#l=q_rTg$(Qe*ze9GJ~ z#p1MV$9X>%gt#PiaYz#vraS5MF6Z;|Ib5b0LM3DJhUVo7 zj=44Y3NcxIT9ir252~7sT9};aP$fdi-DW_a;$l3@Zra|PFKi74y#0bND2#XWSzq_N z`1=?6=iAAR3!PqKlGwttN zk(V>d*jQm>#KhVGwZ2*ZWHFl+%?(wjyaqRTL+OS5!#I!4scK=q=w7!Nzj#`JJ0spuga-kVfcK@g8yBN+MJZ(QmSR5|DHSw#%qj)hFF7<@>R4U8rHJ*#a+~H;)a8cC z8Q3PpadgQGp+`b8Q7Nv?xq)bq#lbu93^_z_;Q8{fJa~v{Iv~Dsz2~iM|Bo%98G31` z+g8MQ<+dEAD|ftC!@P-2H~lH$vT3dQwRaT&9dys!EL_utgTbE zMMK8hJUOzm1gA)K;y!Y9;~ehgRhv)2oEf-qb?ffmJ1Ov8gZ8_I zdlP&%4)=!TyToo1r=RVqG5~MA$Z_nN$8j;J-L2Z3rs{2cb9;7g%NrYNdE2$x`Q6+4 z6mwt;A%mN~^}wp19K9rd;Era>l&y(sEJNDa{vS-KPi!+_gN-vvhlO z1&ru6sDpsD*);+T>oR5p3N^hWBs|u(yEOYn#;ej_Z2mPFc3gHi)>$ua%MA=yBlf=; z@+~}B?y{j*Nd5C+>J5ZTd2h@+tyl_&IV@KShPka(3dS+Ar)XEf4aPVuQ37DOtxxg* zzS%+tR#hwW*s3~#;i_s4Ls!)r$H<-{nyS_qTveU2FjaNSt47?7*}@-8O|#h>xud{G z{sLRXp4itjI?xb3$Gm}_W4M1>y#af*OT&mMa*aVtjw=jiT3mq`!85~1g)0DB5U@S4A;#&H!z)%1OXLRJ$=v-b@L zNl8Ra-ZLa*72!2^PmSZMh^ndk28FC5l4kB34w8z9nz(03*ebHwILgy1vTocri1KA% zE5&gmC|>}UHk>zn@<(8*As?#7Vy=*zQMr`_w9&-wpq@8hupglwzc6hVZnt*w9ikV? zV~836>U5F^D7oZ^`Gj6AF4Zd8@Q;88L^Qj|mxoX2@^ zT&to0#I#SQ#ZELtx4C=}Kv(GjFnN7j@{Umg?^xHR2U6tsh7rC;Re+^Ke-u9y*~N3w zQP5_IH!*dw5|)s9WTL3vnKe^{+-}&(GlX~4?{=sO$Um5>0TuP(C);&A`^9>XP7eey zLAkT(f=`DCh97>%o)S9b(7S-)j`eY=h(0(TrHbf@!!fIfUWn#3b_HkChv_)5n0%M> zVO@PG+RU=uA+EG+amXvb{J8zdEt?pVWgC+>smp2-N7HW21~=o2ik6+G+|0W*Kpbtm zwl&<$@8^mF#*>jp}G;rQ?9U%xkp%42B=xcE+UE3P_%tfLKtZiW5TZk~ z(2M8V?(>59HSD}l?kzVj3~Dm-X?yttJQ`P)uqJIgmZWu8$Vm*Qp=C9AFpFeTedLintR=h=RBDn3AF7R$F}@ml9|LQ_BC_b-pu^tvVv-9-_o#a zy!asWCdqrAas6UyaXg_p?>zG^n9orXCtxeR(KwezO z<8d2JBk+X}*YU2LhXSeGxf;oPO-Uwk>B`>S8?UbI73z$1Eos0wWiCy+*E+irztoRi zB6rJ5GWBDA9iHJnqPDYbdWtJ8tA6sz9}f?%Lsb*N!Ln=3&5PmEhi?z~!P4Be_UmdD z@39#BZdD6+^R7wWnV{y=uxBjuLzCOjg14~Vbw^8?iYqN;DzE&YPZay~@m_@BGjH?T zBNOdU%dnhSeh}9v4aH?G%jE)vwEQ9uXy2z9bJ#&3lOKE3^hcLHto-fY$mJzTW9}0Dc933|d`sgid1P%>)M+VFMp=PAzIQ1|clGe_ zhsSCOPvg$JB=~ZWo>g~r+XKPfE>d1y5*#NPdl0<#YzX2woYmmLaqIx`A-GnWJQ$+d zq}OJB$n$YP9*1O(BmMmSH+05t_=qpx9?=>9`tu7SM9ryoJ6yzrf}V5LB-5|sLUojT z@R&*LOKDnOHQ12xKtUWtZ|Wxx#lL~y$yoC-9V(A)T6gl#iAZAi47kR+1w$&6H?cT^ z64w83_IjTdt@S(zOUv@OokcAY&U9(relD%s&!wm|es!(Ql@`7(?Yc*f6+yg00lDS1 z-FcyI#>J1t{xuN*c~D`J%C#G;g~bewni7~CQrkB>f5Y|-FTU|$KL7r~@xV$ZoC75R_MQ69(we?hFCuwC~Psgdmt?-?9!u6Sr=FI zf7&a^6C!_?$ZeM$TF#*;mjXG2Ri^DhX4nG`?aPl_ln4-Zwf{R_=4h^Kgh)E(R2xJ{ zp(erguhvT;DtZ(Qz;ffasQ2Ab_sp z2LMdDkVF4zTvbNLu6dm~Y|{xqbUVBP1n72n1qjmN@CpE)k>0jxKYdNL9=e`fief_5HR55NV_A~c+A z{^DQ2g}ZQC(B4?gV(9}m8!|9<_fCIqwunQ%e7A$-+;b-XV2_N#1cVJ+Vm<+u;|r6$>U z;bt>zx-cKA>a`t2c4+ObFTUhOy7?qO+co#zhx*W5C#9p&t!Ai$(+9ap2Q}7dmOL<< zwJj&VVNu<&Dei%(UC$aarsbShV4Qtr78=|vW=U-|)B)IsB43Dgqbvd75#!{F)Pjtr z_8OCW2qx;crXJdnz(sAhmnVpHUWM!QZLd;x{&r?&r*(-4+g{hbJ#PKPoe)3x?yq=G zk?)ZhfnPgQbUV&T)H=j7`zVZI^i#g1Zy=1Jh zd4~STyP@ohrexoDe~AWc_k;CATkG~50X^-LmNla4U|;bMf^}J2G`+5^*z{EbP@A+r zI<+qRQF6MP?P<6kxKW%*RlJyPcPrtozh|#MH3{?Yfwcv5sAey}<;ODT6D-KKsq8I# z6Y6+pj<=-ewSJm2?8BnjVwKD`X0_VUZnBc4?Kn^%kE-qLr3d3d!&;Ng5{*sfr%&jH z-HdEts9rB|rmYy!3DZ!F;KA5RjDTJ?5hLKo_F)9H-AFMaTQtoQ(>9Caf>X~iFx<4t zBCutXB&lP>U-tUYlmjp$<5H#i1TJ zePzY7uG6lGXHea=r1;W3n143hHvfFqzukDlOOuj{#_P5`_H6l8L+?loSbon8_cs_&e_|{-R5Gu6h2=3?Zem+Wp{?@8I(K2G1rIE z(mW>uZJ(D115*7pG^hjXX+X!cqXD%{*8NdfpLTzUA{s|TqitOAOcbnGo&d7cLxSk7 zTI?181lvb_A8S&YWfljO0B}SjwaP`Ao*=3J4bFb5JP6LJy9Bd4=2ZjX%qkj- zUOO0IGc*nBDOqwmJ+fCQQZPQmVu!YHNKKr5Yl_?684}b{fJ{fgzDy4t z8Erg<+{`Bj16%e#SoS=OH(*yWxAN`Axw7SE*1GQDVb-dq;b_;PBJE~bmJ{*;t9{p? zjjI-vD@ z4sCQWq^U_Imh~w(ZdoQK1+aXq&vnEOnCm*XaW^;@>RQ}&#_T$nz_6LTb+yrm9KEG{ z=5D1F7}34}13|i$ZLrW}8Q5_=$OnCX8H?eE9;S>C+?Ks{U_6tv=wr=*qr~|eHgWDW z{FbSn#d*pKHC-)Qjl~~Zam2;tn%>32TMzhyk;mt~Z+T_k^@W4C+X)YWZXSOl0>7ZI zs-1Wdhu%ew0K<3a(#G;2Rume(ZKpRJUgi58ckO+EU_Yg0kyBLW2-@81c2LPuQHZfR zcj$2mLmu#==1nD7wF4aclftREL6WmFePwx4R5B57Pe8V%6o)!3Hy6>F%eMN*`eV!c zzTBLTJzi&P@8)q~_<}grc5Mt=cRYER&Wb01Zj| z1}V88&#jLb*(Rax7H>1_faqO!hSf5Yi#V?qK|TPa&8!B%*rgpyNbW}W(Mdoe+9m_TysKRP zwPiWYomDI^Qq0@w(kINv_3-|=vmnMX{r^&z=J+{y_qFXC_99X}UI-b=-XukIh?$a% z%8@-W3l`dYN0r`);AUDn>Jw{Onf7DC0;fHuhYw@94yKdQC{|%O*_Zpn$$278nZ6=Z z_C;w%LvbJo@jBV=SlVXC1n+Ht*V~Lu_!`^N@D1(*L8_&V)-mg6BLlm(G;(l)AE}09 zEJN3gX0tdxBh-f`t4KAW**;1gXl)*&_A?XE>OC_8M?|P~o!LHGEoZeLa=YhZy43CLlR-zTLbq(<=Ugh~ksH2>;&XEd7HystK8- zhp7mkqMEd(cM8t&?@!#=^0ndL-7ZM> z%bSEk(+;+MEC8%aTZKZiHg*ez$H~boS(+WunBRzU$v5(3@{KZ^e4|V!-)PnSjZ)s< zDE0k~Sm57^>=&}kW&*yz7A#C@EijmYx4>YA+ye9e5%+e>k>g0Z;C(&?$JWlVD(j|l zD$CB;Ox<*=lz%#ts#K*^ojbO+CZi${AyzWTDE>&PwsTRBv12y#a`hw&cLzWK1VAu? zjK0WJ5ODuK2MB=pb(H*f&GtpMIz@>)LD{~A%U3ekH-RTy7Mz|IR}JKUXLpc`cdcL! z-2iwbSvvm+f%;Ke0@T)6BnI-NVz#}p;5ATZPiMz1bAc?A4fcHh8}rRW*=W4Nk%&1!LyaA~ebR^M09Yo0LeejwkZ z(e8m96Sl$}$^e}m)-0S?8GM~~*`7D!cblSX9u`efdsnMGl#QH*f(=H$ss#t4rgn{O$7_Hs6NM(rR{_H@`Wo< z^kl<<1gv1~q75hx@b8%Y^92vS>`ojQRxHTRxRb86tn9Wf~&>p)j>Okte z>8tUQJh3~X4uqe)7wQO}rCZjet5bQhPkFg)uY|_gODEz4-?n}#X6eM>UIhGPqj%Pm zRQ={D+n$>jn6wuPW21zECl1{`%J+ITTwOL-AicMVf zcOo`%H`$zQvd)gqErMXw%pwY&!z9dveHuM_5cXQN)bnHC(%krbXa}8fn*e|0iM9z$ z2c0XMfPc`5s|olAoIsj}E*)3Tk33;^WE$|K^E}g_`R^%*%O-$>`&(AF_47gnCH{QM z1khcxUC7)+&QMH-Zx%p4KYNDlzV2r)GJrX|p|s&yHIlQmLU7zO-`{b2XUD9w zr@Iv&7c_GsclWbr0Ed)?Yi3wkIc9|I>)UqT!lePvUvd@rX1f^hf>v)A%k1r=CQAH;x$=OcDtDo_Yc{ui0gct z&zg%(Q9aGtLgY=e4;^nj1hLKKt$%YN!aCTTss6Rf=WG^>U%O_`*F5)2F0cbWBOu;B z+=fn;~~J$Fl6&s@0cW?$s<0?u5z=Qa?pKbpH2 zF?*t)vi&M+<}BK6%|nXN{f^TyXZaGC6DOVn;*nUQ**Xq{CP?NnsGn>(8V{FV_H_k^ zYC@N+I;BTuL#NJA~#8MhtiCD_e~etN9qI{B82lUea-tyB!~A zoFqF-E$|)%TvWV>2XCxD3SR7{zoQo)u}V0J=Uq|VXT=R}!&su& zIPn)LLT;v+i<6sV=Az{$S)`b`sjwG3C(TFPgSbTbvXB?4Ex1W~teHlku_l^?#I9?x znR4`tqC<~iiVc*rV&YZvxY=%2*d6~sic>O) z6eeBL!Vg+HU>Jk$%H_bTyM4a;QY-=7mCHcmft_Wb+c~p?KdtkY1`~k-C&flen-aU& zt>56UAH@uWzaclFV=-g=HXMcDXUih+*j&c!GC!V0HOh-njO;i+3On`~{?R1M6(bxu zk93lzL%*x5Za;UmLeM4al_03Mv3uckO&- zQ$a3MGJE)$3r!EaV3FxbW-l;3^m4`JCVOQ60f*k^D=rFhnc~vJ&sqdI67~j=&DABy%Hw%2nYTgQr9E?7WV$502M0EA#ob}~e zs0+Sbn`z$HYojmmer=9rU+~$!&Z|0aXUQdb+j(#j5`;#lAW`Ve<68k3DuO-U2vB?< zD;`pu*P98gX7J%X5^%@LZuBL9(CAA*A((e~yyCxp5_|YV@1lo|!<#a|n~u#t*|el3 zyLpj~!DObWapYuF=s!kvOeZ5x9-dBSSSqKd4jB(3n6Ws%V{O!2+BCHJtBpdQu_!Cd zg^HWPpS7?l)CGzfm9rVj0s+N;(BdIi1$drU-;jAg^ot*BG50dqz;nX%${MH?}Wj0fK}+ZoZ|Mcv)wbqcVDrJl8F5&9CDAj{Vx;1zCd zY8Jt!ZNItv2+f6B=Os8^yzjPQ7tzz9>#m%;wn@ml_JNgm^VYq%aq>jCLk7rGDdziX z8nBaw#8bh`O744W7J_GA^L8oh>i*Y7pkrqUCgV<+8i>YzQ`fY5;&WIz8Sx#j&*%J$ z(q8rZ3}*nxUf^uCTSHr@a_bE9)omSh?y9Y0&tJ1G^f@cG1wCWE){$qd)&}gnwHhz& zlf8@JPR1y>B5zvk%+CWyy&w1)bN%cDV2Soh4wa6BtZFtsXUB!c=j=d;zv(N17;Lr_ z;26@R35N-&a)xeZ+_*`Mc)aNo81dM#O?5otpOy4z>lyv*boj2ZFSW1HOMud}n;3a6%*FqUWTsDH}Ipucsj zer{MhH}1Uo3Q51a&jQy}ABf4y_1usXW-q4!mr?Wj(=elUp-seWtNpx?vo|RUQ?y%) zK*#txIt;lf^3|NJOU&u#C_J%5GBySr2a>JQ-7fpu#2mvML+!sFp`j&8jgBoz3jF@u z@*4TM&ZOI)&_`c`_G7+zykK;Eef&TZN%qdA|4;1fLG($Vc}}A1)-nv^{C^l z>B#e=)-1|t=2TGhHy7igAs)CYx?MR}4M4E@>j1%K>-Ak-JvB4K&i<-Q*t-=Qbr2Gb zeJfkl8D17*&Hk3580-U>~ny9nBuuWbR5q*d# ztf!hbyB>qJ&3Kyf-B^!Aij8zhl-Mo*??$=r@K;I`HwrNNO25My4!G6ta57%N`1gyA zmfi!g$%#tr4BH_jaT8*PAjjPQIuRXy+_8PNdKj%alQ3l+ng?-T)ooVXb^Cc?c^N3~ z8n&v+b#vF1JaSHK7$wc06*bs7Ct$OBVA#&hhsf$jR?l6kW_tPgdZ&hkfd*cRjWqB| zY?qbUHXc=oPeebvdB|$EzG8ex{e0Ck`oAY`!o!#%`YF@t@H*h=T+=QOj>t5UHc}!l z7>&s>&rQXVENA2Q#?4A~mfbhF9eDiXFdHxboJyg)P0=+E(Z%CPV!ULK4@jCeD@P~X zW@DvG!%xMA8A{hFu1}s+B1ivX;R_uX;gzX9EjM0yUlewhH>c$P2fglyTXkn=X9*KNKkVoR?^ z5Q{u6t^X7j^e1ga2IU{rA+Xt( z|Mu97OILzZ^b%~UlU9S%bsO|atLBz9?E*D$z;VXY^UFSVmijm<2*1j|vh^pnVKtkw z%yB$1RruGinl`=zXGUI8oc<9>)y`&3kE*m+E(~C6eOes*24~3209(yDes zf;DV4E7;7&$u@T$JlI~wW`>(J!B1IT&Wkpid2N;#ck|XY_+0nrC6B-(FUk0!V`FnM zhU`es+?0j=b9B{w__OL@h=-2-!X8Hg4mG!Iy(q_;ZL+Rt7v?bUR9}oI%0<+191$11 z97hnb75hknle~*@&rHYR<3#9qC-i8NZV85}Dp5bT0>f2hV5Xb%Kk(B|u^$=m#qU6w5yJ&u1o z{;~zrCR;AsZt`u()=OoYfxc{(nN-VV*sbs}I%_@w=(5=Z-fR9|mGd`75_q`*zQ%BZ z3_3dn$8yE%1Jen|;WE;k-%qr%p#KXW@ zxdb?Pk-l41<)+}7((xKtsju9zG!4sYI$jXynUT;)r%VXZpSx_es=9LAL>)R3^RCUd zv*L*``jn$4*ifs-@=r)U5k@s=mgT@d3l`s+-9fXzY$A}PS)OD023fL1$`y|tHMW8EBRCRcta%qlT^~k>hyz7sRTC-@z5f>{K?I<_hz41X-9IzxX~uyPK#O&;MT zH3kRE>0Jvg)GT=2-~ax6m+#;X^0{lgymC~nvdz;v0jzBK_W62tOdPOQ8Xft+*i=5Yu zSzpI)uJ6e%`@&``cD%_KpMZJM=z}s>yC-(2%>0pxaWx(T^9;#^!cw!Wm@CQT@mrL? zxl>+uA=esm_CV$Bb|QEsK?$1a5Hr}>fh!lw})sob)K ziVV5&Se8Ssr!0W1dFh!Sy(?$VohNqhZMAruw~>N4wcpLB3<6<<;8KW^7{@q+^-ET+ zW5A^71k|$&{^h+a8&*;UxT@By_>m%g14)*FLba%~zo%l4-nKRJbJrBrHux=_jhDx+ik!c*JN@36Iazo4 zdR?%mjJ_r#@2(e)cx{?6-MROn+UDhz!51$0Hr-)7<8v&tGvb6tgHhliKQCva#`^~& z-jsGvVelo992_%wM`h-suT~1d(=T&&YBB!K+1+JGG{qUQjk*JS3dy0ic*(tbYP6QfYCx-*Pk=FzTj0Kr7BT)us zFq$Ydl#ItbJExTlaAOo|5*+Z+xMx?a%@zeeLst-Y+Ol4#-IxqFpe@z*}FI9LQOhtCjijy!cQ&CK!C98_bv~*=L z*_Ny>zu<1Yj#FZf#nHG>dU+?lVg0=;PH0w_E1MGiACQYZ_2PMWm6h3c z!5ZG<{{!+-Cto}#ue<%8Em$6V{N>w9jq7x6rAB)CqN0;8UQ}q_aJiz=<1b%SG_KPX z6`S<5l&3G9I{8wiQ_OLGX7{rXgG}gY&U$AdJk9H}Sd~BiQzdhvFXA~U<9!g%!M0Rm zX3>mq8#|U^iWB8I_~M->&%rjuY4R+5^Upt73}Qc9dlHg6qq$n1l0Kf$cr073PWIWf zd6)WPUY2Y2b@q>?LS4>q+%r47;Rg`c!g2w-oaxux)~Bjoe`373nmq*r^wZTg z2lY~U_=#=vhWBq~&psV^IrBfu{i(CtZy+!9wQ_UZt@8#>jjop@Ti}hv?1NTLUfv}`ltDsv__xpY3MEKTLR%D>X!VeS`% z!1Qh=^{Nl!nY05Yn!;GtoxLOv(9e|d&GJdX(WyA=f=B-6tvqhN{=&N@nReONyRzHi zl?=`)teZ+Pp2;DX8)iiUaw?T$Jl1 zMQ{FZgbsWr_Cm-zlNUh=O*jkJm_xRr z9D=`8pTx#rx_%D%7VQNM=oW1i25cuPeoOThtPLM0DH=xaWZ^e;qW;2=Y{?#j#de}1 zIBl!WG59BWSti}`uS?@Q)eBpqo2Nl9IT>IvouH*yxUZ*^EII9T;C`@U@bT@`Y@OGz zs4Nn-z#E#jZRr^Nlay>@6VZ|lf>@7SsQ*7ew_5M{3W8u-s3$%^Up^g+^!NwpOQ!=D zjF(D=0$+G4Ye09BD!EXLHb7sj2)t*-exY(~LNEOK9b`*PZCf$l==JR3U$Gba?i0$AGyb0ut?E?@r8OqHuyprMKlZbf^6_bXM_f1Yy3HH zxgC(4=B+H;j!3#iKD{>hNeaO!ri%w$%l0aqbW8Lr21Lu2@e_4mh;E@ivJHNMGH%Z= zEU-W}Ac7JHE^za#I02J2v9%!EG6nor9vPUwi)KQf8HW?*%cl(dh}^;D6#Jlu_k+OY z#dAn9H#pp%!r(rpg6&u-4jxP=I8Ps|e8p0@V+-~jr~HHb2u|$N4sLf(0itb^rd|KA zu2xCzP{M^06iT=drl?7tSOP*5 zQUvc4P7Tydy;gY5V1X)>2PY^7{39C^zml?vSg{cODt|(7l7qKs4~&nmMG;Jks`|o9B_Dc8Gk7aD zB_UH#aXy8&nIJ36c!IJCmsN=a*-%27mhIE;QZ@aNrPLIq3Lx70QK%a(kbL(;KaG+l zBq6OGHS|^Le%D#L=HrmAXw%hh4ch@GS{;pxNqF^xUW_jKVw5F9Jh?z|nI4+bPu0A5 zsN#ENjTV5U5+yaLeSVm}8fq1TWP*)D*syWTSu7VdM7|Z8sG~_b<&vu>KO`L!8ALUF zE#CVl5dU-X2IT*osQs^fa^q4-!-5PualnNGY09`mk`#awBaRIFhPWUIBX5k0GPR+x ze+nzg@MRPWaQWZeov{dOM-Xqsybvy7^Jg7&W(7Q78ku9 zh>tOj2d`!Nrx_$QC*+FW&8AUE{mAM_ZjZ9wN2ies1p6$|#}%}~kk!R=TI8G9cM?Ll z)CV%~QF;S$$p;?iGw7+Rzo?$Rr)%!9#Hsq30%??>`ri9&#dGYUDmS?~udGLiLXJTR z1(}+(j96B@G2>}pRcl@xSy5%{;neZ~*RObi(T8r>s^eX17_n8WFRY!Iw~;Wi7fxb{5=&O$ zeBN|5&xRVyCoMZZC;ZN~x-katNi=)cQoBFF#?Z&>J$gMMD{LXq{02-s*Du;U+%0ait|3e7bajuzb=r{zk`*SUN^--Z}M@}CC#o5&A ztupm=xjxFmDtLbq3m&iiylW~RA_>NnKTtfj8ZzE*3NOkHxI8Y~^s(VJyG;9#upL3o z>;C6slbDgV1=<#$O>{1Jwe+n)8-KcM#-G?Du8MXocrNzLvs%0Xy-zACxuU3kPSxO+ zlgM~U;Xanxt0gC$WyA<8Y`mPxY3WEt3ij|}=Szk>@X}ZehAS4EQv)ip5`0gA4yN@z zR)~`SZ65e{Y1NIT&8r2BXX|^cKxaHEQBfA}(?cd!ZlMg~lE8}hdil6Hd&E8Q{GmS< z6EtQT)MqYD%fL{^aj}6*CzlCG!zLB}pcwx0;>V0;y|va(1j(jOKH{d8URar{iz^8c zXed`zJEANkex$#;vbHwMDFPUlptfSmA9cihKY}C#F0XCPBHu)rV#r=cNmEN=u! ziTng>7nXk0?RHuHj7{A=9Js*Rfph&;W$(i>V8=9XC?3j@8J}Q0Hv+ZqVMg9dwQ_t!CxqaZ) zA=;GmO~I9ELmjN`LWdFoI=l&fPOec01HFmTyRTD<$3X8=>+A-LN|EpKmiBy6Cz5|O zvbKpO_Njm9!|~I3LE&CA$_h8LbR}F&0?UQ>tbJlktbpA^H#gd*D6F5V#1Kk^5EV}n z@><(u8`eI@+9RZI@Q)YMtUJqb)dY%oBb@Bs??moyQ^wmRFr%y zr=@nqAA4~h7nI5(d#<`RZJwK!?V{<1noy)aU`TR^alo4Rk1_%DJb;G}vhrbo zxiH4y(135aI)8ydFC=ez&FWp=H0t=5{^qTCtptF@T8tA;jNA3?;@B{oTV(74KOjh6q?v6|H(W<>@;`+3vMTHmZCn|DnxyR3(Ige1p6Mqzf1k>vE3qZYAPQ>;EI$)pO}Z)Prz)44P!uB)a$p zs^|EO8GOZ`gS7Xd)JF1qF%HTI!^POttQaaA&d5fyW7D;8bOFMfDZGZ>TYNyWCdek* z2obYa+W}L!)^rMe7)UfZ8CsO|u?fWX2JTqdx%+b%W0h?Tvy^F~5eG^6DrK;jx<3)` zNl%X*P(4pXOd?{`QD~O|!p91P#E|OZ=hCqaJh{Nv>$}y1@Fdm-dSyDp8n#CnIz5bV z-{i{4`{9cT@>uetZwgnW*w=lLm5!Z?E{1^yE;(xfPL^_2bWJ3_lD$wrU6|UI$*H*% z(ABqD-jC%Tdt~)7k$)+m`-rc^(kTh$+ zZCVCS?ovF6%OWym9O5mPNTfuPaZDkR2nmN_vNlsmk1)elcqb~ocn(mY-O)ADsU^uU zf&N-qvIZ6kQS}RZj$j*T{Dc8S1OX0~-jQQmD`v^}P98n&n0%AUV+L>YzQ$Iekn)X; zlkPPj&RdCaDNKakVFrQg^*X;FH$qoU4OLuF(?Jsv^WuVc@X*`id70k){2{&R&ozrr zo?C)GeKIaNFB2XoPKSCxr4Oy$bMsqap6CLP8O~*-0C@}+_|v+22FuI?WNoF^nf$h{ zWDIYOfW>v>J7k|ne{!D5Go<^fX}5e3VJA4=qERHdTzE|agNzGUsSeVVYw37FoFl6? z8ezP#v4HZR4s?p76L&frS#7k0M3~NTIbhzh@26X-=}Ln*{msu9G|nw2Xa@AN~#8?E**S~S@hk%$QJA-i^A(Lp~9-b(1+ zP7k=DA&4Rf8WB95$k3dSvb zg8UE3Eh;2GeY{Sj0`?|_LZTGTe8~ZK8d+U+5}> z#~;7{S6lt@`+p}&ZfF*bxC@>ih)15CoeNA8LWeAL#7SVg9gzhe2K|t=?|(qe)r`YQfI9 zEKyokVrsTwH7i$4bXhd7SmK-wdlUm3#{`<4j>%}Bq(agwNXL$JaOchA6^))y;cQ^O z$ROqWkK~#$JAG=OM2u<+WQ;}JumTb#cd|}@J20QB55ye-vDL@L60_i3Wh%bm$00&&4puK za2+T7r@#hXb<>CV)Pg^WCg-o?*_qOT9fyKnkTVb3Q-JR7NbS$CIau8-LV7QgUNMkH z2==>J2qj>kIU#dl-%gtG0*-!VdFKVU0Pti*();ysixiJZ&!Kr#(;50h~!dt<vXcA4aG74u&ZiABgO$ z+&=W~a37M$+{3s;#7To- z!(roSGBMm}D>1}S!@)t>u3# zV|XM*E@=lCVpQ$hLq3EXD!itUM1>4U@axd#yHQ(v!FTXwfL{Z!qS|hK12zpxihZEJ z0~omm-mLOG#cslA(p@TYu^hq@3L8=l*GU}UD0GD@A2xvKmpj+zhDesQa1DJS*4=FE zhBv0GhlmXA1#jf90T^6^8()+x>0AC7C`3>gCSaS=w@{{j@nctaO&vo-U41_qEqmO$yj4?FM- zWLk>kdv7WRaPT zWl<;)j|PD(G_t(l-wxP&W`5JmIQdi2guom)doJ^0mIEXuTd8XTF}-YH83GgYL&Y^s-=h>YE91K)bZgWsp7t;^gb7 zxph}*51Z5xmHQk*2335>b}zj>O-bBP=|>R~PMR#WYxk5w=@FagBmH8a4}jF&|bLLRIxD*p!pY(?$=dw zQAqZjvtk`(Kxh9JF_nn8MMKYA5As+7>}9C9dZS9uVj>K zLf5u|=Z3t;IWm<4jT;JpQKp&F8`d_iq5RAlrh3LYg1wGSMJLes&|u%Wn-o<|TngtHihBU?ARC5SM5C24 z%4e;PDYF9iHSw=DO)A$cr3Do~MOaa0sfxuJX%Xu(po!p8HQ!8vU6+DTLt(qpdBD|W zz-o}=o=st(MKk8P%yzJ4IVPdD>}v|}B$Fa>g91(Yt*5ZK^V!DXnMTH)tQK z@S6?l;YPoNBE_WJb0Q*tFzB&2jf=@K#mOOkjX|)q=PWwf_lHhnGn`S=?+lb}P@1*4 z#Oj(#HjFqBQv2RXJs3~u*IG)wBx@6?XC#NdNKtuZi;8clGnbwoD)3*k5<6G5q=Xm( zTAv>(aDOAB^fdkjiIumx!B@18(Z8iysMyTp+Ws}r?j^M?Tm!VBJz-2!-V|}*@0#s6 zCNISh=Y!Qc01U43x4&suK7)ugvxsnmuPVwP?|157XoSZ$m++OJq^;~qZ(?S zljnitqX-DFuKJkVN@NtbM}KnajkCtQstsKs1g&ss8;6yzDKKCGZ|J+PY0I+R3c3Q0 zJGDX5_9O<&oUG)Bf2BPb9%V)YC-hZL zkX~(vnKC2{sh`tKwT#kS;d(6D=Wx1xxcSIUtfbGcFBAObhDFBTI@f6439QTi*#Gu` z%kzpb&rq|1WetmCaVe?;-Y`EQ0!MhOvY=ZgcNy={^TN^XSlN0Cb;ynokg^Meuq6Yb z#1I;>6K_O-?Q}dxaYQ!BxPi8^2rn~&x*=jB%orvRr*fe{4V{9;ST+V|7(2{4oG6Au zy!KX7MTg4|SxMtY*ow2x>uCl%5tN0vdLd#H5$iU|>q*;VfG&TbImPyaLw2rr%n071l zg%kzv)V#saMEF=2p>AsTrcWoA5WSWAJ6&jO^#BtbMI4}#zLme+uw7j=>Z}&dtHS-D zd|0xI%O%iHeE`4k$d~G17f#W_HKQ2mHir+l?|Hvsm%`r7C<9E5zlo;=p1c%zN(j$2 ziW|7sj1K}m(|DVgkG(pOKroRTD-8=9D4m$!ybP$#I zAM2{yK7g~Js{X>(fkQqa#vqQ_4Ix2rK!k$k`JI5J*l`pg%_Q4QO~V@OZ;Fs6f`1RU z1+W9?rQ(qNnqmgz>#7_p&FYFDA^2MU8i3)!Av7d%h$j0eZZ&jUiq^mOj={)1w798L zdTd;eF*-DEWDt&DAc*tu|8uyQ+M1M5!TlsgAJp%>fs>4|qf}WD zR%$}VskVV_Wy-#0xJzxsf?n{vOvFzcZDnPVA5NFlA_IWnkfF`C6M&V5{^qhKfiRnB zlb2#ffH}{%S}X(cw-kWiDRE@giQm93IT${0nI@9N)R9ag6xjyJX{sIUs?=_W$2~@4 zj9#@H9+C7BiHxv$Tevq0T;8q6%9Ad63l~Yz?-%5~4z|d()vB@%Qdx0g`VT`B6*_>> z{X$UG-azk-bm4ndrSFF*`p^a&H*0vKjf3Nmv<-BkGL!d4SWc(k;i}9C9!ukse8jsp zog8vsBwpQF4YoZwSq3_CFC~&zu<6Oc^i>)5XqdQG62FaFHOP=kpNH(?CUx!QlwRtbpwS*?UREP(_Ac{)YeWXs^7M#)0+zSLG_UG#|SaQqE`cT67We#^#AMGXNkO6Ns~o z^O$2H2zMkde|#QuLI4-03Bs3&@V@6ZM|nEMK@Ga%bR1~*+2Ygg9jM|^2qMI}X15jY zsx{6()Qhffyp2Im4zR5*j?K_`!;@8QXjh(x!Tv1Mgj*z;Puc#Yi*Sz#xQ75t6nA9B zaSycMPy{8L0zzz}3WLko32eI7t+qi50s?H{X^M$mXz^@@7O-J}*stbc!^PAmKFk2u z9BqxKNY+&xr$-kTV4wIf1r8;`RS)2as(OL9+-|K!R~0A+D}8|Oby{@r(+)j!?QPe9 zCQ3+6xuTY%2aHO1l*!B+UY@q*fiZMb!sGYd`rfl&&7vRbrl#^mF93Co3%mq@eoLES z%G$^`A89Y8B0MneX4wa)r2E$=TV4Ym@{{%eKZ4iA;hdpG;F5nM!x9x{ldx5%_IIqh zx`y*WL%;3=F0a}f8AV(BD2iOT8_Wf3^?fDVI@`g9$RMpx=L3Z!qX>shk``g!(2^22 z37qD^Z!oJNH$CfcipRA-kVK+MV6ySbdHkdq=BQgAv`H?fUvA5gke<3OLYohh4!K{8 zb5E`_3U(Yd9U`>f_B{}zUG9OSSWe)fj+Y6xR;O=xe%4NVwP@31h)5{Hg{AfOl;=N) z7)gaqH-aI$eDYKo#_BDS*DUcN2hpLYsh}~o=6V15zYt+QVwDI8W_^~A6T39sFXE9E zu%9!%fD4Pvt4aou@`Z}vLB~`xZ&hIN41!f^Y$Qepcoclq%M3(pm-H*BR{I zhD!kH!ncQ<*VCbcMUQ1sKz%-GLTF|$G-l^Ydn@rqNv+5?CASh}`#g$BmzO&Y&aW@s z;PT!>wO@gRcIY&{Qb(CK2dybnN4e1v?iPF38?G z?Tr$FO3ZI@QI%V$^;uDX@3Uy*Y`yb@LJ}F`qg;F5!#Jr+bHdv}UjSIogoU`PQO?zW zVb8E=6l+T)82S`|s;u$}HciTv04e6RSYC}e@TcpyOR4a(@>_qC=G(LLv!Bm65$qSK z_(1fdzIR^L0XtS!0TJGBlXr;XyOp8tt@$1TmtHidY|o3Fe<^vKYr4b7>(P}>zX%pn zA3t~ufTDoyw@O7ExkC?%3py-QK`&q*0PMTS;L)BMu;Rqqf=zu|=m0`FowQ+llU=|= zohYU)FMsWdH(lK}{qFnp)jxmtZ!u&pwRaSjd~@DylkdL&a{{5?I$6I|kG!M&rNb+Q z+Vs2cqKQ44|1N`*Z*F+_S@PX?A%1XJhWUs^7b22ON?*`lAE-P}E&FY|Y#&MB(wvZun>;X#^l@5BZ*z0-=w|YJ z!S(VODu&-%4@V;=f4aSVx$-WDNX7ubjhH~{_Og&j?~*=iur;o8oac9}^?QvN>R}@e zcV$$Yq>MDN$#IV?R3h2kb&e2dT2jP=LzBQ1Pp~g!2GD- z8YV&W)-`VbP*`*A<*-~h#U^WDM?e4NnDjJB{7O(}Rq<&nWTZJx;f&uXQV`M?D10L4 zC)5e1l?8|I6HU`Cecp%;GOa+6-xHm|zbV7S4sFHg@$@?x@s0u--48cX>fj+I{U%4u zwmwdyRlYze|Ue~0=Jst z=U?H3qLV(_#ZGMiA8d&>e58B^)p~YnL{v20N$Fsxo-%Dhx@YYZVgP=ct|4%n?xP7(-O<)Le0XJiSjk-6t zSFIV|WAm65FiVCPD~%V{kkTiNODK-+Tyhjv?L#LkDLD8BS5R=R*1l>QJb^x%vZ+9~ zeTkA&!SE)yJYL!*8tuswv9ibg2&f?_Pf z3ct|MUqf!g~=WH_*!y+ zjG1D8Q}KqvzTD6e>oDw49MFwsy*3+Z&_bWU9`QI|{7iZsac;N?Y?E0n;V5jQ}>F=f0K!TrbyO z(jtX$CiSvh3tyU6%hJbE10xp!aZ(<`9YC5}wf7rXOWr&HE@@mohG>IByasM2iz5}`6sAR`($~Ggcsbi zb!-9@M(P5&k+Cne!^ASyYnR7gir)yA%M>3tp5KA{I!i{bpYa9ozu>A{U9&PgE8=IL zfQYAr;9gJ=g;#{{lK({);@C5g2TlC;i#uU)E^A6d@6h}#uaU*cFSB`v9{lV4OPBuA zFmrFeTf+Q|LVq6O5{&bJs(s<>e4{+gI8)=G8fxMe@Yj7nfR*h!ITa^lpvB-L4m$hD z7OJ9Eacoj;T7K033 z;~gA1?RV=>ezADyag*(MpFOML#I3hW>9WG9jprrbiooRd!RTND%GED7@5AM_ml&2; zh;t-MQ_*$=!y%tkT>DAp36G@GJa1Ah6XFr&#$XUkNH7sOLmp3sV90uKEtZKZ7I>*^ zb6gK#Q_S^3oU0h#FVJHG2Jufq0T>&A+dZ$yxp!MTe`nCj|3hYIs2S&fG>SRa*IRRU! zf(?Xio?Bh*)o&ojt|>HNgx<{Ty5?c5nX;|$=ufWCCEPR%HEII4rt=dm7x13}j(vuQ zFL`dMxe@#rH2dtSjHeC5{FS01_y4Nm@dNT#6fo?otAfFsBi}YnV_)VlCOd3AHK{~O zjZzvrNwfY8gg7Vc%^9yrAvCqg6n!hmlN%5fWvAPlL7Y{?n=B$3MFQ|{JaI+Bf&4C{ zJ{$DliMAd5s%(gZUW)WJz-6-4s_JO8ORoFoC{NP}e#@hdu#@gdp3P8# zb6bkeVxvqg*%PmrUAf)4{T#(vNGPcR^AmeY{<_`0{40biKSwb((VmQ~NL9i)ES4DU z>b*nEj065Cq<-i#_uZ8eo0O9M?x5NOYS&kBb;5D*AdZ9->9j!%QPXBMjdr?&z6FN< zjd(zZZ87ZX z7Upc&Q-!8E^PMh^v^EC!HfO`WFf{*K;0tmV!%`Xzrx$V@E(Z$3XfZaR2sw(rz~Qq) z7$zmuO<5i{IzAGo=DE5{t9Pj5A!Z_2Oba;X7<|Mo%?T-1zDw`nUZ&X8+FHutDLwnh zXbYsM+QTjIKDL3{CmE1XfR7KB45`6hGtdQR@2RZVk>p{FTZ*NdeMBrygP?oew>X;y zj&>SAv`*L#W2wAiIJ)P-D?DIJm_wA@zwlZFpH$09j+rzMPeA>Az2-F!zRQ-g9giXX ztRY+rwI1w*^I71~i%vZMGBaU5%rU^qHI=?NYQH-FmNSuh*fRnP-ybo?V@O+99aQay zsuCv#*O~GH+OT31XV5sztts(C@{15j{zHiPEPP_wm;NQYTXU~n9_3pI@5_g3rQIeR zm?=ZG*fgsprmc5<%17!}RSQ(_s^l#!mAuC_DmObifK}c!%7OPDAGJdd(kW`i1qHpp z%_!GJR+d(aVnk45g1r?PWNKm)QtTfx_qAO^FiZLzp43eo1L%9!Dp}qu*)zC^7|yX| zqddB5YU%#ST1EQuTE=1U&<0gpn^8_pR2AmNfmo3jZdf6CH4bV>i%-BexN7q1TNii< z_vCeWy`Q_LI7wVu7g}f{L;q>WNvjchVG41X(j$e-*{5)TO1Y1s_b>pGgp`rrjRM;8 zs;*Ghf9J@0$LPobtPh0-5KeDN4iG7KBfKLGfM62=Z*g~3D(8?#F&!#@{Qh6C2Z1-y zyXueM|65Hu5BZ*|x>zek%CzrCDcxG1A+(?1rVRg{G90dcwl!sG(M5k<+|G^vS}B`2(|O5}@XQ zqR%gw(T&b7%e%!Pg>D4Ga*`2M*f2nPaC3R@@|fb%yVb&^@2*%yyjFRvbUdhCAzApaYEv61svJ+)BdP zz;Oywp6+VCq$fYTHCl?MvGv5&&}OKMc9*Rtq)C^)%2uKDfavtpxU_&MA|-T5Ilj;c zB3+tpGskEbaLu@I%^d!NoXCdwC+)$7Jw1nPjfkAl2Y%(?@THSwprQ{GVMm6OCok94 zQ!}AF*&3heIaIEs%JiJ`X-d*TP8LX!i`Z|4X=s0%#kudVKyix~h2q2A@D0Yc~Zf)GMP z{I%3;7S^rlgq)$Z{xvJNRY*@r&>us0gQrsisw9B_8;aXJSZW}e|Fcydy896=kf1-N zu3oTx+EPXa8wk4`#^Ay1l_KJ=sSbPT#BvHAXAA-jg!K?S-e`ckW=l;(Awho(-F?0f zDMB>=XR45smSAQo8TrFR==OUSG+0Q`A4Au7r=+Q`NojXN>Nw4+&i6qZqy+vkb;X+q zDN7OhYpA!`HlzyC{GX}9zt`cmMM%&eQ}+oRgaxaq6cK+-b+zv=1fYb}F9YN;uVHeJ zm5(75O{)L-;$5BX*`vIhLVlKHp!zPU0BT4#`L;u+J6l7&XEp#l8W(+NuV7Ne!S_-g z7=73%!)Yew{u%!U;m5VLQJm8KGwu4mf5yKr-gR(Z??biC&3&7CxEP>7NJ1fo8fFMS z7d=dGv<+m+8mRBjuj}gTb9&LAj27mYF=UVk6YneeM(*eM*22+;PAhOUh5HljIeiC% zTT70i^gsvWL$=2dDr2eKk}^)}Q=F1q(b2@cFXcMb%ejCbw<7}MQi@i_kk}vb=j8qE z?ez^N@CE_EWxN1LaLSMX5((f)^S+~kl(*=iCq6Ac#cK+O1GO-q=&9kpU7DFTs!ty* z>JHq$O98r$b9&bmKS1ec+??V#7fEN8`7jZVliP{+kqq_D6AVaXfR|Btv!G?-t}bF3 z2YSK_Mwak^=Qs; z(Pw->JVL6QG+Kks*@wGnggTft>h4~F)h4rPNM+>n@6jk(b%hOD0I*rMfSf5h5(>ra{XA|X6nFp{MOU`0o8 z#@s2$nzE`&F;TW+NHpa6BiyG=N`!sq@e1(O>A6{FV{GPS%KWq)?s5kCq%g)AlVGAv zm{RdFXtWvV>_uqtZO(~dgoX(DELvWZj-pa9{zKMh;yCDnNM>8$o!S?+$o^(x>Bwl0 zyB<$TyUJv@ibbc~(;LU{ntA5#Ut=wVocId}NMQD*<6m`GfrYO57c_9&*|CFGxL16W z52VnLZ?q8&=K%aLoEaUvAM+IhhCVTNcnt93@$K38nT%iT*E5CP@lg45X3)UbhwWXW z$75g-jx(C!VO0TFq%U9JX^q*bNwxrPk<&gUu+XwWAo=2BzG~(1qVk|N`}@&I(WfVU z2y+_nXgYf==i)!qLJjLI;HE97?#h)MpE3M}PX}cN;gM81(gI_#*^g`n zTODtQw{yW-zfXmk%6_>@{FCNm6!l2BqDR7!5aM(^07GU08{Mc;;FCdQesQ5Jpu_u> z%T*Hg!4MTG2TAZ}+a(L9+?NTLs|dQ* zkbhctk^Z)=UcCOBfNdyo5WGG7XzzRTi|K1OLMlePVUKPkhpNvP<-~e(Jd$>QBajx5 z(ujnV2MQB;Ck}K`IOOhf7XNfii#1G)wwGJ(PgrFH`74Tlx(ErIg$e26w%n5DRpVCB zQ%d&vrINOtd^`c>#hcT_wV6y*(|o7(pkql8BsWj+rDHq>HV zy9NLD1?-lVI#%*Nm0iTl^h!({s$U{u@F1UC90JV!p4&|-`7T!;cWQPhy zx_VNT<+i)}U_89S1pFcg9Ll);(&$pSq^eZ&7+g@>#j}o!y%YVE)%C%%1Lh=3Wm3d1 zBVE&v#03UpOOmFigY`K{i|Mg{NYqwtERj&M*VIv^sk+3?PX1F7`u28M>L*9*Ct1Vi zw5LE;MWTE`#IA4tjj1K%~wa}sQP`K$XX*}b?b)H*=6O*{NFUEoodeH zbaE(m{(G!&Vx^ug8FAo;BXhz|qTq2#RJ0)jzuJCviBZVHoDw14(kyQNhV{M54AQ(F z9$v!$(YFGyRqHgCt7=^pjEU2tf-8hvCcR$#{lO&I1`ya7r&ad5n6^AweQ*>>rh37U zXQi-Ed|K9oRP|&y>3-tRwZO)217XrfwRM(KX=xZo9d~4r4dk#TO{INX4;e6IGGU>m zA`*aSWM|-qj#rtgazN0(qV5%cw(lBhr9nAMFnMtUBwXQU`JA|LG#fV9OkY7@8%E6r zF+ct<;ClnjF|VV^!$m-~ePK?URZ>l_Wmn1D5uI>b*ScD;m9R4aoz|V(+*ZHpAwUFe zRw8wKn~d`puJ+6#@`Xsdz6=FNLXaFN^7KfI;kn_3r7jcbO<((6(G= z8Vy5bby@c}JPWDLiKdfH8Ncs8^cWf+aLGNhyTa8J7nGN+3CbjcRL^}uPRB1OwzG+f zR0E4eajsm_(HYL1LbP@TKCfUg-YNCbWd$8&<@sKLcnzWR_dG${u;Sz4s0#X(k7wa1a6fc}9cmHJ)l$$l&SCadn?Nmtse8XXA86eNcq8v0VRf(jKG+B{5Q$n)Elgwu zok~xqTNsercxb%^%exgj5QUiCkfqw;X<`GP+v+IO+((XE5_9_}0l*#>HMYe!><@-{ z>I#X`7+uVQJ;IRMqH zbfyM&P4l-S^e8;F*5#}Hi}p)Sg&^O=ZO&y8+?p&GLC3N*>z3t9r!5zO1<0zu1cUgV zRSnL{xnS5okVO3J6LfyznNjH~GX$t5IG zqRodL_YbJqgtc>>d;Dl>yN4L?(oCow+KJy05Aly_|+XM!1J;ai_Tmdpofg)w0Ntm3P<4X~Ze%H**|0)Rt zgUM%e2sfC_B82tsaK-BEMat;PboO9-%%*wHtD##s$=~3L!-6HGkaM>O?Ef+-&;RQS z0lgMSee5d| zaD8ZtJCLt>6eUDw+2?9ibLq*7Mqc}bCPCL^#(McySpi6kBS zK}S9Gw_uG%Jj`Ypf-t)AY`I-=pdV{*d{nU=lL5b~Vx}|^0X=I>eG;4{CyEQt&BxTU zJqObg6WvYXTD!d(8mE8tsLKxagln%26`FtQ6Ymckg)g!Ly*Yzkwv61%tPn1LNcap# z??y~?Kj+cx;J4N?w=s*>%~~wn$@x>1)odWD@Y#!AHx_ccAOf(2_|dRjdoyM5pZiTY zIQ4Ga{Jv@fk`3BfkKQ9A@tmk%(S;}Z-L$}JjtQ?i)ZAWZ;=xBSE)PRd7>tktHE7rC z>zx=+|EB^en(su`T-0e3qE^_sC4fhL@;E^=m}GhJ6VdP>BrG zAy5Yu9WQ-C(0=lPkP_SN3Au{4ZC<{j>qy0(LV|_{>frqsCspMF{URLNpB*>s$dJ%U+ z*TbeRaDk+^8QW2X0Cc>tr|orr7;g?(MHHK7RwVN1b+7GI<2Dp_@SX$$YCy3>*|$Y&q0QOb9Q9T{o(m)2mdxQ;Nm z@nlDmhY+J1md#VUEm!|cH;jeGE26!cg$taD){B9tnS__abb*>dv;XzXKRDxkeHPfR zVWQv9w>;WG)&>dMEOU=52Z@s;A`MpFRnjIrKy0H|82TEv3VOJV&Qpt&qV=#H3B|ki zE6pkz$FE28h-QX0ZmZ3;Eg+?+@AS~e?}#usuJCQiA%eZ4H!kMNV0+{Su2I!nnZtzX z*5iza*@rub;MUvdL*ZeofCaIKS=Ub~h;g_@!Jz@_Ah$^#+yxrTwCV3OtbSjTmpg(Q zQu6ti?M56Ll_)epA)n`|anUA(l1u_P>rAN;HV)zy{K1`7B0i3WbAofdQRx<2eNC%H zM9&LBu!Hsgyv)xHTi0Cd`h>G>LSJKSvEtA4Q&xcq zRam`UDc2A$)c{%pWUrH)@u%w=CqzmGG-KPRqZ(wz)((%s6$S=YBn<+3+iES?0+ZLsU2%C=ElVxelsO-dUFRN2sL{@PqzO9y z7)=rs+xnrU?An2>Z;N!Z@8QE$$)aV>yGXZ!j1U4Qb0 zw>#m)F|`Os(04)fxs}%J3b1%?;0pscjxNNW1UD^bUY1`PBJqbaNH0ulb>+>M@2n8l zBsZ`tp%<3*=A0lX4jhYe#>~$DkSXrKBm-vhNe@;|QN>Ros4z5NFI^-N<>XCzp7|Pa zo~cQjwDMoww6_J~nW^MZJNCwW$Txu9du-*U>_$RCsW{i|%zPN@lNMOuR|((!GS4Ma zXFrZ6Y)8k&Zz7na637I^J}-dk?km{o+l^zpU7W&-{3P(LdG@3{c2Jaj5+&Vchi0d$ z6g8;Ot)dtG-Lt5rkA8P@B)!3q08QIWmFzy@6>0b>d~ZIH4tlbF{J6iZbH4_HI~{tL zEz(YENDg%EhZ$0Os9$Azt7*i-UwaptT_ha!&mD3$Lg{21&XLz{#N`=5O|f2>Vcii? ze7D3PL4yXk5NM~dH)C65gxZ3YaX)<#g<^M(vPD<8Y9+nrlU*V8Km!9JcZ+=%wAST( zq-xVd2%#$}48m+j7K8VKinAkR8!XC-D`MZUcQ8mv#tqA%paIm!7qLl^7ztGNrG79r z&~lT+I*^7aN=UNGfBg)(2t-@p4Omo>WFfKXh*dE2C+-S_zX>B_p<%&`&*k^3BLPK) zq7^m{UR!?8$}#ct4F^4CmTx-eagn>LL7UZtmly$FL`E?bMhI92)N#XC3^>(%70q>~ z9r?axaU@rhmqRFOSS}YPTYX=zlR3u6;5+kndVUfx%(aU$*n(S@H>g5}&>n`)jgZVt zmNeWh*cMR@6v3`kgm;1AQQv?(+)J#s<-XNKw6X`)u}Lz{gJo2Ntti#D0}oKVMb*>~bsR!jRlDm6-=4s8|;9a10&23KLHPE#8QxkG9l!{-1*ZzS7squidt!-D}HJG3j z(`7O$lasOTo}Hj}ZNm@oC_8uou}SDNEC<;goc?2gCAdKEs^@~LL0GB1iHx>8Ce#QM zx8Y2$nGLBmbR<*XZeG0Z2J5ulH!Xj>bOwH44I-eW@TG%xUD~=CR9dALYCv>%?=?A1 zLWv0XW-&WzW%=GaAi@B36LkHiNo->HyNgzXwgN+y+IMe~2r=b#(JmCb81|p~nx6z( zJ(QKMhY$eugYEP2U4_k6Z~HZK4tc3}wr(0|YIB_TSZ{wvH}O=ky-Db0v-g&F5AA%e zTo}Xu>oDufgkF_9>Qs0aSyF=2x2r&LSjf>TKR2$2Ah3dP$-l3nn1=BU=44mirTI}f~J+D1b# zg$K&1me90~!;PsEK33=REX%az8Gi%HMI}}R)u*lgv1C68C>lbe#kAw*vl4Hg_qxMm zk6v$we8~c7KC-{_psPQn+F^t&2X9-(GCJ0DE%`#as}N)aulJ%jM|iO656POQxI-3L zG8il5kiXOfXhA-ZJJdDIF_HjC#*_MdeSGn&;S67rY0w9j+>5xqf{=djZ~SStk0Y$DnaMAu*}PAvk)dGE+{Wpv|xm@pY>3~Rca^%xGQi^>1~VXrXovm)Ls~8vzQ@} z(oHdlL(~4VB26{@JIaAE!8T6f_jdd{R7D0Cz+?E|kw4yy-R{=b_wR(&&+#zKk7r$j zN15iA*H+jBuRp5bD&IfN33VyCw10&FQLR}qBH#j;SiFW0xhR%A!z@uHm7K+TO6W8_ zUHNZ}LHyZ-LAFMP87D^e;gv#WbIr&HoM9)$go7SmE*BjFoA~$t%v<%1+lcE@SFNp* z9sP#Q1E~e-;RX}HZ(<#%5ZOf)eogTO?ylv~8tO<^4bIvS6a^^;eVRP1+V)cnDx@dO z&UrPQ+cQu8wAN}mceH5X>X1auU|<^u@CTXXu(5|O_fT1lfX!&#pjss@=jXxx(M?MJ ze)23YT2i%6)0txkyhH0S*fD-qP-tZkBC!B?{Oc}VU0%;c7NRY0i%gkW(cGP4N!RvgGfdY=AE9I+sOLVRP%Fo=8t5XorUjGbKtFhs#Vt6hsr8 zimhXFpUGqLY+MRs0`-7E^?;jQvM+wS)ohFOwy+2l+QvXk;LLUZh!e60 zSiq*S&7>rh)i9D4L)?syBu~(C%ut>n}RB6M(=#vp9^JSj${a7 z9zlbZvC)vPEVJ{?1dX?aF54RV91~hwA604UD2h}fb(~x|l?Y&>ewP$Itf9_9L}dsA zB>ltLue?#fcG3vN3R*kwBr3C{!#9{XHB$xcT4lYzgq#cR2C!eGMnFsLZfw{QXi9ho zFP@qBbJ81C*;OuZSjc!kMeT#I2H}{=Jb*mxv?CO*fdoS+fdrB$4v&tD815XqHwo5O za}%&KYoKCH*1=m>T;jOZM_faT4eDPy>b?fcD~fX4Qg`qD8a#|TnR*Wk`vp^_@;XVQgeY!Sqm zx{WziW~%4cAk5iwfD)GG8qF5%K6MW$h7@Dk8Rm-bMJojCSSS<+BpHEKu5GjNDCi1h z3_8I-@A)9sCDWM3YP%mG&^2v=zgBROnWN>-tWb<)@2oycq&i3&6>+8PPNWm_VEQZy zoXO3eUL853P$=Uq1plnC9S(*RmnQSA=xWG}YSJ!ttO=}?blid)yr>YUJMjRSSLoEA z{24bf7UuH|+&Dj%-w7Iq)Ljdx@i!Z>z*zzN`R$z@j@;L_s5ifRd^zjd%!#r$b@KEB z-E%_-Tuby7{1YC@8$eCdJ(~jmsO_9yc&Ac(QjZAuHQCo-7D~S?{Yo>##J+_Pr(j% zLfqXYXl&}EGT}U}ErIGPy0JS zGWN@4X)v zJ-2nrYROH$$gr6?>6D9JCHwnnFmOMQlxjrqpA{LsMz7VYc-#ry_oUC`pb)*gI>rCF zOBw8ASEu-r_;ws*g~N%_nxm0`ZYp;m%0+a)m)F+^3UJZ$5wif*Oi;l@>2?zjU2`tR zLYRYV;L)}bi!vL;8u(NWoq^#!bVJGI5uP>4@|aVTXRceaS^8lyC{zc?m@8aa)B1p=Z;w7r>?8!D(`I&{zRh^?~Ex``Pi zX?|7z z=n@IzXw(^o^)y@pbhB;Dvnq`2gJK*+Kh8GSk(GhlaOY^Z&VkdcDYn7@+ z6e>v{OhP%OxRN6p2_gW>yUu*J^Ge_@uI3q;tN;PEHYYd+tShyXFd~qGYEYp$X~z9` z`^a5KU5%@)`Mq>9=f~M8BODCKM&+oIj(z{4tzj9sNrdjGv8{C3UxZTkjo?Z^Ug%bk zo8;CQ;N%E>!ilLUf+N10_{QxEB!QFh5}F8RbSV+SIw~;F;cDGK1ev{}B%svTxGn$y zi`upJd$o6{>oVZ-hXznM)$s_sg@DucfSyn}fC(^vX!sZHMApV)36U&{6$zoMfj&rc0x{4)vW=x z^6&Yk_FF}1h9OmDWvKBO6h>mM-5Wr_%ff5lcj-T<#-II!1e&D2OQU_%)Db9~BC3Cf z6JL8?ScK0;(OSTAUAqH~6-`_F?!r|?hOp)0c-LoVwU8$3aqOWQMOgw_!JIWG+CV;+^+bkSJi zzgFWie*e^S8iA%7{?MLrAj~3=juD+Zu4Sa{rDc~QuuaOgngieDq-&?Pi_J%b$JV#j zaNa(GjvDTZw4sU@UOFFkA@z^P9ZB5UHJNkTs{NX~sP1{xu*Nb>!aN8B4>;(dU>YD_ z8-*N_eIm)7YQfQugE*25B=S-ZU@K7GMPt#B$*`J&-!+oIw|6b_4#fM8gp|+=TMW&e zv$oF%`-xTG4XDq1=O|qMX&JDcFr`&Om@=af9l_e*-Sc&LH2p((Uc&kJ%f4XC5dW=% zU&%v1T10VxW6;&##-MBMX8BhKtUl_Hie~D#ohKTW{bZ1g$P5vUD1b?%q=^xiG!esS zlup7cG@$OF`W?e_R2kUr#4oBj{;LH+zQXL;a*CaXxt>F0ySZ}}32r-IiiCW(E^plO zsp%K`x2kHzcs2uKcvKHsicy8^M$k7}rN3T?U65!QO+y54C(P`NCc+Hs#;{?Of{7-E z@waH%eFNqnpW}^yD^Bw$7#r_aHvw9O(iPz}J7e!NSY$tm-4v${>af8`jT?lBh|#E+ zl>%uSORXoJ;-e(Nl1#PbbD=KZsCk%X0Otn=H-1SPZg1=#hDsLxKTh>@MbJLtJFr(# zm+MKSa7MMyvY+UO2-hg23*t2UJ_gam%-N3$@iTHK01Gg)T-r@WBbqx2U}sBIZtUaO z%|?W;vx3-K_&((l+4D@pql%`IVA_0@;Cj(k-sEF!TG&R~z!&YwXSKlZND<~t z;|R|$JMS%3#x;!$38Y*Bo^F0O%KBY8*kjEvCV>Jx10XyRF?0^3!6&kZ()93`=L_UW z`fM><6Y+0c&_=G%`n1aOgn@)Kidy!P>1s%$EAyjBe%wD9+MZS3<4z zB7CYR-zu#Iy=-&FRs~%7Nd)dGdaG*&a-}szGND&U?RmVyHg@8R(}l%7>UPtvf07i66_$LU z*D3;KV*B4-Q~=w5ic%>ARK0 zaKZc*Z+<)aQ$JbX3wy9wIVrA*jO+=SwV7jRrz!&(JSh8K220g6&}yOyLju!3c`a@_ zHCXKp!9Ncab3D6jv@giyX_ST~F?_5d+iNtb<_QfYWXS!1vwtL#?fsS7!VSOfjikk< zy0Y_(9dEkKuDkZcUE72a*SxAzup)$al2)TT584^vc<=*QFxaXPG+w6bq#a|0b3=pw zwR>pER)d#~5iO~zF+*SWC4-r04Y+oMGLlVB0t)M?iiA5z%zh=JN%3s1%pc$nW4~q7 z^V}EPwO?YX7TbW6PiUYuXk-eKeu3H`7S22 z!8qr>nBD~pL+C)ZOR56!sFj4!vrH0G7b?FQR}MookY1ST-iBM83Ce&+e;j;jDh0Pi zx2RWm4`^qqY0eJDPD;`t&pvQz4nP&_v1LQ@TCVw?amae3_7ZHU{X zCOz(NTB&zFl?k(U?>F5Zj;JTf!^U$)TCBcO7L{n@k#3bOIMCiss8u!h@?_A=r1B3$ z>TyBLKUJ<#iaKAwDrYn%S3Vj#U}+e@7aVxe#!VJ>%ym>=Hr!r>&?TY_caX1PC#Iel z6?fuFYj`p?G4jzd-F8jAqMn3dLYhGj^644cBlWHP44UL#OFxB};R@0jc%}Ky8El*y z_!J$)m6q}q)$q`g(?wI1mc|yP+C2aMvn8a4oiK6*o^epd>c~o8JX+63@e0CnvuXlA zS`mIy->k_eg6iZm0ymHF7CgH?N7qOraD5OhC;13>PbzJ!#IGky8@&aed5Y0Vsq6;I@%*pR>$&1b2=+b{y(P}4+bAwW z#ph)*d-Bj-qUH3{@eY*^j3d7!aGIl00%Vdwg^NH<3^Z{Gv>D%;GLtH_x6#@UPnkN6 zuf_*AH&2b$?P=SpmWUL9JUX%pIyho z-rY$sDAN%9^wKnd4T?Z<7n)p=OXhYv`F!!OprPQ5+Ve73!%?G4=4|)0-bMzSsjq%he}yn_DD$1Un{F!AnxU3c(%deZ$h}x_+Rd zx0SMwd@HsY0m@e&Y8ok%*acS4wXm~+xd^Mx5_MFeN`+uh5Zzz+~ zuc_wfV%2|)H#N)rOs>eoaa~X0a>_^Bhe;x$LfX#WwKmxftokkpW$7ujCnRi6!a?A) z3D?zi3XefnZ9YQsp2b)}%s0Yc$~)Bdc$|7S+Tf*wdLs-a16-NP81CBo3CAOPT@TLh z$JsctgEz~2)W@QbPpjF-dHBX^Mx`7sy@=D@w-TSCe>a-=lW_e%gdj0 zpl|u(yF5cY=)n*P7$Psv*sB`z^T#-E~cl$ z->GW0WWa6 zo0wrz{4e~(`g|2UgIHT>V_C1*s1of3MR-&$Q)WuxT*&}$G5e!o#&evoYWIU7eu~hp zlL9%NQZ2mVBf$RKEAnQq6C)t`<~|7*p2kbPxI?F?BDEeUfU$T(r8ho~};H;=Csjj7M@X9afK9bDdMjxBHOjjO1)jxo?fFI-djgvxPn#?*SB? z$wJiRl~KOrvU0StdUiyWLeP#lwnXQ?jW|fkn<~RA|{oD-+OE_MCjia zO7#aZK^<%TlymF$Aro7Ww(0So%_w*HqNaynltuy1`g75iI$z6M@-!p+WY{m4P|Y}p zgazUh0dyKPGAJ#g6~`_)(jLQ(g|JV-54znPoVccQ(>xOoK)u)V2jt9^#_yGC-n5gb zVr9d4xD^~K>d86*s$tXck4$EmQGi#CmI3Kg>b8*!j-5t&aXvn2(1cgOQCYyNyFbsp za(g4cm%L$+&eM6rlk<&G&vP5vLBxBYpOm3a7A6%yg>}&fboCQsN)s@1%FSS1n;)H) z9he83>fuak&qWcqsa8IO#>b=UEZiVo#8@7s=lL5m?{uVkp$0iLH&vp&J2*L~C-<3SytSXa0j}1dEnXqeuzzyjecwOK^*dQrgwv$B67W&^Gb>cXvwe$UPs4pd^Hm+$(6}{j1qT) zaS_}Lwpp2Q=F7)dfO&Xj-QbIz#nUK>LmWUr*f5n)w({gbff&FjGnb34Ti^eF=nHcy zBOg=Hs?Cs#{J>MJF&>NTy857&1cjb33z1{n+fnAhDi;bfj>Vf?O{TX~TOWqI^-9XZ zp@Z+yx(m?$J!&704{O}vJj0JMefehi-i#euA+$`}bUR-TZqi&PagFCum~6E}yBAX0 z-XC$WQP93tA8;8w9O-$>xY6qs++2(rMDXEG-lBZpf&SFpv7=a(x^2sJzcCBE7PJ)s zsp*zQ!RsWNslBjZqt)OhPBgAw7E1>+NBptl1y&v7I1L>sY+3u}j#G)I6B>WMY&2V0MXyK_GZ)tl+;3MpX`L5Z) zw8IHc&%B|AVvWA59Exe4*;RHV|MN+!Yp)$RiKSz=7(Y6r-U_}uR&&@mYO8-U9H>1; z;`)2!?8Sq}>R4?IvE$u=$MD|#;=n<@M zxk1g>Q}a7N6Hp?2F$)RWI_`1Q>uIV6sK_Z@E|)PL^p^*45A>_3CVYre-nLr&v04b^ zTKNo09B>^!M^ChqI z%Kk)@Ai1YCV#|W`3NZTN_A3!pl|OD`VL&IsrQT9RS{&QYNx~0)GcW%*SXV@bFt}d? ze(TQ*PR$p$VHhI}1|VZ4mzA1a^uHHjkiOY(6^t{dOg22^gmpzEi#k8@8jdhJD>%Esujd-<1)?u2JQ!T^x{ z_Ll)>?e%ozQsF>c#^Cv&3W<2W4Fd0l;Um@^4N#<1Q`Vr|kSc_*Nt~TT)^=F`wf6x( zPWT#)>S4Jo(tG%*CgyPr+K)xD-g6fxyfEST!GQU%_O zMLzEf+GS)NzeeksWtq+j`#<9is$YNXPIK_pDeaQh+%DPLJ52{Klbu;QXl zSou;ho)Z3PN%@wr0zW-cLON^XOlD>18{~*LU*XDdB(**vYmG@jaGXQ4l%Sz)s#Hb5 z3`c!r%1fH9lSb-Vb6QS{^I@r?QFU*^L+wPFL~?{8b8-n?BhGI0xCcVi_pp;gSWnjX zS*Dy7pl~+`zZ`ki;E~sG#|tZ!rlCighSj|Hw0iv}X8k#!d-Xn%Ko|H8z_Q?`IUpR1zb|b}peq`&1cYt})%j>&!9(*)=Z1Lg1AZDo_Qqg! zFX}DBq}~8%+x|WCvJ_!g)$b(?39IkywMpJRE&kPpXPY*&j3D1bt8y z!kL?OCLCuMo-1h$2LgGfl$cb)ZaKcbvI@6zw7|;F2`?m~&+7;vUpSZv?F*!!*Wg8* z1@7d&mA{)2e^67NOsD$Tx#FO$mhJT|fTL@9w$s3?8!wD5j;~Pg(<`$hZ{Mk>ZQ1&I zVpY4M{E(!>Xk(x5MB7mhBxVdS{ZNq-x1`e*hGq>*`j(7Z?5WSHxfL50BswO0GmUUQ zYjqzy$dwxczhme%EuALEl*uO~Kz&E@aA)>Gw};MAlvM>4tQ>&9i>Vj}e@#zMM`w)Y zTuuf^Y|eD8(rjyo_g*&5D;+vf5ipnWlwNMfCn&cR_(V^yx4X-+E$cgNpJ9BkTDPya zrq_EqGbX)~0VYdsx{I->0dm?v666s0Sp+qUNI^RMFbvd4`daIZTRJk4>3j}hFgn&! zGzaSJb|NV}nvp<&Tx~JRk-xeK4S>P>?ztAf4{gWT9}0GOt#0%1Gq|@P#NA1edO)h{ zuK3oItZv7F`ug;C8pqb_>-er0Co=&(kX&BKNDYlsvjBv}2#84AN8*-H!{C zc1GGrLr@)@J*oe7(+K{dG4kf(SXOrA{qTk<{Ah^wgpAbnOF`!H{dx!(%1L(Gmo~TN z6qs*?w2^~OhCc2gZNKLRkBL2azjq%F&eXbP5|mZZ-`A)t=w`otc^hf#1kjJ>;ttj= zEz?~-@0)=y0J}%nz3Ln4RdFy45?AQwF^(JhW(9(aAw+;OFTEL+fp4H~%1nZ}g0#T4 zY@M$MZY%O##aJdnFus^M#Mv6A2Y$iI3BAO=Hc|d9-QiX`m2yTdg!C`Kg_=|;XDE1I zGh+TuW-Oq3h#= zn^cL)8b~75wJ1dexf}O3sTch@qHQr}x0ev}mpQ5=?`{Xdf%>pJlEv!Oevwlh%K&A= zUbIw%ML1gd%1FO(_OTCFpXH0`f^*~MuhCD3{WXcS1>hk5uu8FWJOMO?L!m?fE}^Sl zoRY3~n+ZGhr*B}Hq`COWkURoZgg-Zb>E*SYa8O2wP=L>q!6@ZOR0y-5usr}2oMQaB@yjPayLR^P%n0LK-(Nn4)r8R z|2imO{;e3rF*p&{l)5-2$x7-AN-3>R4f%_}*`bi13FRTn<0cnMciouu|JF4LrBU~S z?({;e}|I6F~CaWR|O1a(Ld?-cNl&4!*@0hP}9(K95SPv2Av!H)1kb@w$0niG8T=L50u1gl(Le9 z#!yU-K@0p1x!!p!WqT8FlL(&4I<+i+d@%)8Sjdba9duNQ#KI%HzC9W&N7xi<(h^On zWu@z^(>#8g$-GN!Vz2)mxyJb5te&9&YnM_;#TBclAq)QJvl8*I-0)-smL}Q22;8{T zpfX10y~+rYOdoYRJaO~nrhsWB`78&6%j6bCcXrW z%vF~=)uTE+Z5e41Pk#D$R75(QfcR_K96_HlrN0jjgq~8R*|ZB3^K_gDIr#<%b)p4! z9nl8(tErL|HVH+QiJF88_)By9_WG-ovZsILc_B|od*_*to03a}AIu3igbff3PlO0< z79u5cyU_mB5@shjYgRccqA2&2oLR#zhDulQ=?ru!fV3NqL(A>f>E9%#JL`8;+B%?sbqALF9BJ^{kvsh|KJau*!p2hG~|A zcAIX4Cf?D8Ra{oEb+?pt*?%O}=5i5gi(c#Y^lGs#H%^{D5+I-cyK-dL$2=l3T+Hvv zcvQt;XCuS*t96%lwpW7-OjAJf4Ka@HRPWznTI$@U!A<&0*~)l{fi%`Vso-d9zfu}5 zfi=o8lo)?QC=x7#h7@M|z(W=*(w1?;CHmqdEA*l^%KIHLA#rB#nq+zEsikVw50lWv ziPqIRY;Fv!MRs_cMdE4okZa$izypSRVvs`PV(0Gb&6e|)R-4u0f+Fa1@bXQEU*^Jt ziLAy)hKFgBJ-`9B%uY2PR>N2bkZ(PtR>|hW1lJNEhsU(f8Tu%>KKZax?U!NwlT3mG z>BA?WJm1Poi7;=^&M0_CztF5mUO7~EGxhZC(1VvqQf(qVI?O1+ggYq%mh{DjF@q7Q zo%RGtwefC5Eif;ZEvtA|K1%d9eHf2j66s;WgVh1VY?|qfiQ&^Ev7T}F^5N9Wn9v4u ze6?VwI->n?EUcqdo0uHgMdT}TiSy<$&vw>h;8;4nc72+%k0#4ekM_$FiVAEXwcza( z9CSO0XIjN{3>)aq;}MK3!DvlVBJ3Q%7-^YkI&RYUm5L_T9X+%?8w^wv8^1JQJe2cW zp?4+$8zG}lOWEo=Iyo_Y*k!Jn*mkD$i=}1P6&lopDwj3Sp$M!fJ}|P8y^)qn7Ma;d zHZ0onH=}7jl~dOxnF*jR<=|VR97UGs)Q2QS(^Dm1)~j*rM69Of4^4bP6j0_9BzQ}O zs4=6fTY<2CC9b*@(*PyNzaR;txKMR&P9*Gy1#Z)w?^GlY6lZPPFxfCVhOoi_3t<(t zRV2r{vtyI=zYGHCP`UPgK5M0Y6GAa@;vbko_eS1{2I(_~0pjpcZtf0Z2BOF;^7?6%a1gL31UGms*$+tE`Mzkx+TVmXlZ}~R$Y^Y zR|JQ;Z&{=C%Ug^sb1F3o*>uX&kDVt!Pg73hfD1WalmfL%;@Cc|%*2%gh;wlQ+}kD} zwCT2d7FBE0p2L%=n0j1}4l>@PIV&-prI(9l)+(t~FXBzJ-XRCyE2Q6Qk*%-ExGX#E z`L@RiT-SC0c4!v+95c}`o$>(+yu>JFEgvJ=Pr~@MB@yatRnK=bKA-ER!>_Z+n5U=- z*GO+w82gx5Xo~dyD!-f_Yd#nD{;+Hk=wvLV26+F!wNs-cHkUY7=J3v=o*m>6Q7B8{W*pBX}zOmbVEA^RKMsmcBY0UmarwQnJSL^lCzs z4Vq_>W0vI}3CF~{q$)I4db0lrqi8u2L{4M)rZ-p`=OmDq-9_htE+N`!=FBhycrz)nsQIDtwQN2wz+OEvr`9 zCb>&XWli~ycm-zf5OF4c#i2vJP#~Hf5ymtrm;G|RdjM*R=TtG z-V|{^S!+CDdI^6WCLZXV=vVj}{;gdOV~?i9-5$C;Urt{Fzl)*sd=V zSlxNeF^EMP^vJ3l-C>rrR~{c)i1XUf8SgUBckY`WGwI`M^qaY604DCI60QboOiV@7 z$$Mu3XNb+6h^e@nh^Xf9X2E1d(|g!nORi@mq$=;yCb$oG|Z_Ta*h|%gXu+!oF=#`(*8yEix4|1sTL2R48&5AULK34;Tg3pnUi@R)3t$ zgVvDXp#Dd*B+SA_2W8+PL;l*OjRmgEk+sCIli{{5$eV<#y9leB5~wl-S+f>Uc#&&Q zGI}+D*v9DCsBDO!!mxe^`W%a);>a$>SssOujv+~?T*5Pf5{iEx&v0>p(^~7HeLhz+jeZ+ihZ;|BvYE7pEz3MGwYctznn3F}W$5 zIPDt2fa_#hL9LsN%2I2qIs!H3XR&6b&6^^iJcYd^tWtLGPu{Scxc?7V0`-9(9lHj< zkzp&~Ed8T@+49TIHswqnxQI2;A3Xbzp^4ci1PX!ovB`MzxOf=2ihCNE!aRb>@F8Me z(3Kp@DKXFyGiM-UpyNpi?_6vq&ibX&{&uxt_dz_`8-3r3& zJLa%nL)axqnT%TTOO=QoMq8@vgP0P|l_}SLRq)C(MnXzrLVQj4%TWVd{dPC{v5kS) z-!>G$&~Oq*ojDrdsaqkkN^%x7*7)_vY>-wZtZ$5}Gx?@IezHhnjijXn$gnCBM06;R zvV-fjl-W=;ce!8nf~RAFF}Y;01OOhf*TyQG&uh}D7N`*M|B|Y*u{26To%k=QR`S24 zETF6=QEnn0HXq4Tv)Gfv6tb}XhqXfdf3S{>n>h=a{D*aE?LVx_!(;y!>+;(FU}a(b z4{NWh@&CnYWevMTDdWQhXH)zatM&I0N&r6pQ-3H6X~$Y-|bk{ zf@fa+EmAPQApkCg=QzYX62vTWUyWfWT3#AMAYBzA>)*MO={LRfIRN@E;ZYV7VM41( zkmHk{S=L{jC?#nvsgDHg{a4&jR+Hfp$|=x`quyGsqp0{rNJfQ6s96_8?UWZ^4JFzd zpw?nzup;BDpfT7g3(#^>)BYc_-Z4m%=J^60?>u8`$F^wBPuE?y1O#-oXR|zh5j3vRBO!Euj_1`)SLCMnK+rsR&1m4cTQZ1zxlik zAph|KrFy;<^S!$daJ~Y6W%tx712))5cCfEuqM#-a6j#!!UT5wRkp5=DM~t~)%Ey|; z**+ggPjX<2^?y!MJ)4^(072sq)lei~ZHMO>+sjzP7z-KbA=c5Y>f={jbDjEMaf^U( zk8)mo@YVyp4ZAElkK$xAx#8)?@d936`93Uyk&7qg;4LxR*93W7uXcs2fKqIj1vVMV z&2a3Ig!>7Y?mAr(3bN0Vcb7Z1Co?`Ei5HoLtb}RM`4&>0UtL2qRv-P|b>$gpK7=3Y z&Bm$q)QQfhJ`aTiq-ZpHviORwSSwQ7ba%i%I|1Rh2iLUX(AxoGJqp-BK9HJ(^b@?M zsxjoKEc=Bj>Rxm)_d1KYQ1vXz4LWno=^+x2{qfIkK$o#9l9~k>Pi@2-Zo5VT9ElnW zzE#sR{-wU3%`gZCwjI%^VE3c0iVfs9hwTL=(KT!8;rS3&uNkm$o*~CBid!kyIV!kM zWaw~pP2j@EbTjI*<8ARtLgzQZjml#j->Y_XHW{2&MT5_Q;Nu3qY8`{dWQqol`tMUY zd`HiUgWvrD+9g18%aSp*086cx@BIQ(NSTdCge@k-g$w4EK4CtB2-Xk7;pY1P> z8sQIgXu#QXID8<*jjf0DUt~#!rq>{NXW|z>*zKEsPYC?~pGf>9GgGmlKe>kO4o^i< zO4J;NpwN{avWnmG{$8wSEECVjrrbkh|i4k_qfK)L7**WY!tH>*xac9@zM|0_b zdFa1aA$mDXadL!HdWIjd;GBmJX^H_2)`4SIzW<3C5D&E<0Mt7`?AjJ^y8mOjtEwb}xk@A3_MSAlDzd%E-m4bq+YAnwA`zY@>6qXzw3B&F6@M5loq&3fk>vv|) zibg=zkgs8TXduF*Ic1E6U%CmU4tn&f{KWk~ z(O`BjIY~q>{;7c>US?JmgU$*!8oo*LfY#CJIAm%yH7-5uH!PC9&Ob#z4NRWLF(?8} z(+P3#heEUt(jrY;Q*Dl^6_RSGIDKPtxkw#`UIw@p8*~oE(WXMYs?OiV?Ax>*TL-O= zKJ79xZ-eDB6V{k2&C91ofC^Y1lEmYz5*Uc!J%q%wcyX$e&v>ezu)YzuUyMtj5-%*L zh-F3u*>8aTGIOi@_wi*I(IxVXkowQ|)mXpZx2}*1RqglFp)^jfb-PMCMA?qlBFt|7 z!P+YG*M$V!bfdwK##K4wK1s=fE|2dk#h4qUJ@yPaND6E+4=KtDm2s)No$g+VNpsDF z4>9vCw1XV=Qv}sNq=cA*$ZJXM!uEBhM^zFs01&6VTclIsPpM>Nia?Wc#!nepD=mjw zO>=B9qtIEwo5AvrnDLC5TO=q~H<6>5m?thcmpd_$NK~?6p30hN3=K`9owA$+2UGn%0GlI^Okw|VjrB=aGD$?p7!Cp;|NGk+rBgH2<54aRA`77fy(h%&KbtVVuYQ0IPU@{BBFH^Zj+0LsDqJ1( zFVO$dT8uD}JZNCST9q|WsOJ(g0FE&vzl;rg*}*K=&b!1od+isc-(kSeAkekHfGUB0 zVWNf&!A1fn48@f?T#XnEtg|Ob(pX57EHM0dvSETBNdkurL7ac}c}7H41`7W~f>O^_ z0e^~td_o~g1(s3!-=1H0bh-9P55-!U6yv9%teTSA=D>BcjP&C^+OM*dpBG(~rx*Xv z-E$$qhvmS90Jn&4tu8txiF2>V14jM_!KJE9p^1|Rj49UyxhhSrrgo6ZnPJL{u>OaT z6CKrnbG(u5V}zeq&|KA`!~`@I2;%=o`z1O?rN}l1YUZE0Vz`wb2Eyj3w;~5u2Wrn5 zQ#Q*JB9dxJT%Iy4gv$PLYfk+yLK8v8;D(gTQlS;T8NOSY)AR4Ax?{1(x`H_K~ZWcb!;tt_$0mZ zi&Ihd-RpvJH@Sh^;1ChL#Qc%@$sb54OR6|M^_tbo=UHD8z#17iawCpMQ_+-Dt?K8& zW4`TcD0Gr0-!M=A^h*+z{SyC>xbYGwl)L-Gr)p_VTtl@)hl);21U+#<`1a(5qM(DYd>zb=^x?r3RRYm{vXu= z2&?pm3k9@_?k_GL9P{FW+6s~6?Ev~!1!}}SVOcrLK}eMV(|9;p7V5(?74~&D zreM71Z4MDR0*oLmHaux~DY0i#j-fhY7m|sS_WwoT2R{g05Wnv}#3Z^Rcvv-%i2BtI zbw7ewk!cQai0?tTxR@3_9%my(veYa-uOdN{K#pK}YR_JGfui=^swu1_FKpa;l@Fl( zGHtmeZ-lP7xnm421j&r~qL1fZi!b<=5jYUG8fJuCYY*)H%{(qYe(r!@hpEmmbo;?}p3%Qh{Md2)FkQi*B7# z7fHK_WLUt+INoun=shtS$JS-J>kGKfP7MIt=Z-{K*2@|H>+Exr6+Qz| zU_o7c_Wx*TV_^}hh_H|#T;gV^lS!BW35z#OH_H>!JMZ3LZ(4hzU=J9hJS8|rEQq_e zD|RW=lQ{521q8?n;8xuNY_I64N9eV%0Ygm_1-z9F8duBeWqh26LGlNeiVBzaq#5Sy zz*VLaK#xsgWtDUTZGhGM6U!Qff?hq0(OO6ambzQzPblDQ$b$aczH2agrn3P$jNtK1 zneB(UW`rn@>j>4Vraft_fgK=#^d))v51>Q~-W%Bh)ugJ2beBkI00cPl*2k|f0ErNz zf?Xs?>@bo*l68fipMgo%8@`zraH;ZXFETB)>I8rXr}jNtPsL5Rt!11h_Iw-;%op0e z4wPv!Uj1ZjQ9AuFA_r#4#yr(~Cx0YhNjUog2Rg(TB-$_&XD6m-dc-qZixwG&2ldMf zI_Xb!^994xsIcZ8IQU91$y22KiL8-CfkyS1lCRk`r1!xUM*+*XUY!1j{S_Iy)^1TQ zA0^JVQ2~;yn(_~jT-r7TIc3oI)LZ@HoAM~MJ)HNF^qVZfVMFWZ0ZK;*c}9OlMf_fh zh~LQs-w+3e8<$zW8;`&3MiR;p91x9@(FQYmc^&GM40kgm?kY`sMb5lS?t-o0STquk z6s639-bD)Htv)=AhX`|xS|bRY{$f3BEj?1T>1=hb;^-oyrak{@Z!Jwdi5K(3Sed{xTDtCRQ+epL2D#c_Uyn{JEz z*Pl#YrRv0MT8|kfOQuZGO>Fr&2q0}cx71B1DhQ9G>J3<$xt{yfLM)fE(JsYUWj`31 z-l;Cl%4sK@k8v)9sk?i=jX8Tqe)lh2Do$ytitBsZD|bD5JzWfy0^)kjCwEHhi3r$o z0p4Cn`W`a#rfI33SIR(j#w|2-tnA%to!%bxpAN&8+qRK`DY_|B*?7wy#n+Vio19SY z76KVVRl?}(rmPJg+Ui@%;xxi8m|lfsF$UQkRHkQi%N4Jz$3o4Ic zx^a`RO+oF&+yQzr?vNp-ZzYd?M>=W`C|3#)eAtP0umPnBxxS_kFzF7lmqzG`YAd@& zVMwn0y_mD&x_8eNV*Oq)L|>%laSu(`4o~mFf-6&lCz?rXen$A9jTs{YC^_nF>X}3@ z-Qk3|vZXp|G3l!k^^*4*hw`I&^|z*EtM1w2L)w|yFP=}_elv7vGaWnD1o+V@Xzhl| zNsDtEr^dR1S*y@sz+9nl+{rix5btA8R#Z&}^qlwwZvGw&IDAVOOKVTopX~pc;M`~*BX79N+D$r*lrG^>wHduf6u{c5i#(~==yVmy#ecQUX`B-$+cz_#4OHRX zIxQ?g=bUvqtjrwRxt2TB_q1n}Xe;-oD_#1(VnDsdYZtKI^Zi8*rFQNc52Lb7ea$m- z%@Tf>v6@1kht2m>f}@?FsPma1%qy0@H)r5E4MAQ=eF}h9ME6j7t=D3zf-sLN=zOXB@6uQitlBYRl+Vn&a!A+&{0ker)hL_xXp60MZX= z3RUf{%5o<_U-hExiGRVQ1mP&1&KEm%Ro{uQHJ?I`_bBT8<(*|vR^RHq=Lp;lRr z^Z9qOOoU9HE2HA8fImekA7=LD)hfKt!pvt%V7VR*^G%Fdh0N`hA}N7HDeIw^#SQNb zLQ?{;tc@jjN}9jJ#T7$hZ6X)n0bk>5sz)f&DLwDrXY{|xy^Y0#XMWaePpE&U46r#b z8RI5`D@w0yaS}W?pl8WFtdj(!rd&skjw@;GVH80UlV2v*!_xG413cNlDF0a#kP#AQNlN#D+0-R-=l{$@(r}9(lKrHGp2pc48 zzf`REP2Ef=fPV!U)@Aw|&gndCghM4zEk3N*#U1Ci4tuHAO&md4FAr*<9Doi#c*RIZ zbdtRs@7R<0(RGO5%N$bf`b0)V?!{vW4L!7C?}YCm2bu!uOlvU>z^aCG9muH2ySRGx zbmIG)b$k+7I|~~EjI(H+n#h$hFLoe~kydfMabD+2XuSo7WTHhsr3SU4*jmAJFDv+m z_ha!|A7x-?CfxM!*gd8mW16n?aoDWw3Cv1N^h&q#@clHef+v2R*pe}^EYdSjrRZI{m z8?4t{@=FxtU#bJwGj)&l&0B8e#znRd&tOcYLszh5G=FjuVGX(BU~-bd36zLR$!@w& zlc`yJJ)X1MaR|u0!rV(Fk)Ll-yg~&sUVM=S;B1{eRC-7k7~)|^_WV5LZ$%jZ>Clf| z$kuSB|3JEWWAd)5@YlVNv)qm0#gAbGJ-m}ZxgV~1{?ps%Y0<_aDxwpxUtdKi8t ztp@4{p1k-uqk{^teD)crpmIlmn0~alvHv^UinZG_i&n9b#FZ64`*9{Te_6rJ;hzn% zSo|&$`Gb=8oCa_S@^E_TfF*(Igg;5ZWT*nH>1pcnW*FOqHVr&SflpNmR#LzVB7ROE zrpU67NX+h+y)((IuA<|B8+ypetkCRYm}VreJqYJ?LD!*M(ac~~&I*G!B^-R}tR5j^ z#wFf0NznsJ8WUJaHJ)1C=}cvf6VDy)btebuhsQtmX>B})6R6eWSrg9 z!gX8k)s)+HA-VBac=W*TR;6Diu58G(n5`V^_M)zcu0N&)U{1`^OS^wHUftrm4s`x* zei?|T;s;3v>V4Tv21-Nv;Lz^@yHxvJMy%9^Ng7RCsHL~Sl@m9(F{<}6M&C7Z+}Nfu zLlZZG_net<{VjplnIZWzL8_1kQFVh?ZD(oZZ;0z0ru4 zt!copnc3c$Qh|B;)r{hAI5cE$HL8X@FJYJynQUu1S<~$FaNrps9skI)V4vpKCj~HE zyE&E^!TvHvER^0yEhf8nf-vXrZqNiTerwLzT5!sOzw%Dx(*b!dD|UD zep1;)l8xW}A^x``!t1^mdF*SMr-#Q^%wPO_G2NQa$gx)K{gB?Wf^ zsVs%V>nCw91a4qy^$ZE%*nE zK#}%fb>J3bfIcw)FN-C+W#!!rlR?^j+IGj~n>(;a>i zaj+`po6s4<-tx8n%M`40G4DGw?VQlXdv}&hp;Sl!lgYWFkS;dip!C@4Cackc9oC(T z82Oywp;G@)?sq}Y7ke%gxSA#V+%C$)97p!~s$bfn-`(5hVargI^7x7}h73glVNnBy zyzPykKPiCXc?x~VNlm0aVa8_p3(=V>CN=SYUBD^bu0u}14fuZS|PN6 zB#;4WzZU9mZ*P)7?c)TEchhvg#ia$9FOjMed|4FR7y1HbPpJoPi(S1yK4(4~sXe{s z55;#q2=ZxK;t&e)s+zW;ZAm}U&NRKNa8OvK4^gQ_c_Z}gaEveUW7N4zDjdtLxI}#bi{gH{& zy~;PGu6|XeTi!^Dc^dU+{-10`#F5T(?-3bd;#s5O% z2E4y`e(>2-6i2C_IfKyu7%eC@HVy6qaROGXM}V}LhUc!$uS9ml7@z~y($8=vXntl& zp(F(#>OK8QMxSfJRTy=I53~8#QT5rvJ*qQ9qCQ06eAd_iqN(lMJl4=`P9~f6=3YBn z#v#WEV*1N_R9rgbFDxdgLVb~4butAptktU_BtI4^=G(~!f|w64#o7AD#Z+F zKy%2MDDY2Wc-P+tXIR)-ZR4|Go2@{wf>wMpoq_B_POjB-%r+?jf9$d6)li?``UsVY zG~6D2#e2u4`!$)FHUVX5*~||X*);IWb!#=5k7QW$C%o}#`<>ypHbb}aDm(m5pqjGz zC7f>VwAh0$+MZ{r?-9R*6KR|feF8wNq3Pg`DxLR1->lgjsbX0ul4LmzX0$Q#B#tL& zc)G=q+VCm$c-4s9Xk!xC=;t&5jEmx zIj&(MUPQCtA1R~a(I`v813|4)F0IN)D@(%=;=b1y4VnzUDMpR)=z|x0Xvwn&bdHqp zFs*>yKH4gvzoi)Zg!s9#KgH!HS$?)HL#@tdVg4l~Y&>EuZc-t20A6IATU+XevOMh(O}5mnWfD1V|Mrgu&aJXxL58g8&}&fyLw>d3_|{gPu# z8Pwx^#_<*H@(8wl;0xGozh54BL!xuW<)KwvT-zOF84bi(CpDFpjP^dc!R^RBE-!E< zQ)eUyTUZpyvm?Onx!5NsJ|dc}$nA+WUOk+N%SuA$w7FK)dh?^#s_WW6wBDR0lS78MsHCJN!jCl?x9JK4(x)-$JUcI#CqR#U>Rg z74hbEEEtq=38%GDi+Qu>>m3IB&5$yU@KB1u>lB0o7o3&xyf1`tH@YrvRw0ONEKS5S z1xgbmLCc!_WCroa#xARMx8sa922%87A?L!%n4fy_P5y0$P=K%0A?A`4sP4G`Yf9Rr zH;UZE(qJ5Z8MwTJ1)<)b2&=)60=9NlE_NVsU?olVvU2wEy;0BaMPD{8*7`|b6;_mzF6THnd?F8D996ugodH39qjnRSpxU<2t#Syz@j7yO}K ztS);L{}HmB>Ay+VnuTWN+;Xpk@%z=S2nCC2&TZM1aaCLS?p(Ii7q4t1%Slg_p=@sa&MQ8Ub+0FNnTI08CP;*hJZcB2XKGEc0a_Eys(bD5h&M zeZrMm=p{vB1(^@CcQ+^vTB;z|^HpaV9u^N2#}s&Gui^tH5E@a=M|wUOPfaUT8{?N$ zorRfz77|hr|JM?Dn6keCxh8c(!uxPf^lzcyNEiPqllM{w=*z`C`Jneom=PEPe|kc) z`SGP-Yxq{x^}sh2Z99|n!L^}98|~NEwp@E$K#&5gHym;_FJ;jni-q0M2FM=4+)7~T zvO45)v9hEg;`drS9^IOv@_+M|vO=B1X*I$yt3V0Y@n3|E!&v}5(z6}(37|p) zrnO|W)s_y)K|LjfB?%Z`60~14m#~yc*u&buXU@Cap++XKo33~-;=cZmbT@mdg~cXJ z!H+5j9w@d@Nb%&U!+D`Pb&CyM<|1sH@vDQtLs~2!fVb}x{}})lM3E%$n!I^J z5@PHiGXPiOj%nt(&m4n-)0(m@k=uX8=x>%VZJ!E!nb9J1l#3ZFijbsAT9d|1@tC$O)cWPi%YuQpVurpQ~w3q?7DY2deI_tHX+9 zIxcWo3kT^zC@;&6Vt=$Sy{d*5SNXc$B~qziv@T?dBCwZrV%4XRU~)Tam|fPaQy(mU z5|UA={3%kw!)+Ivia+Fnst~*5#_2}MVnf{a4ve0i^qUm!FO{QSv5Xm;c7@K}MFJGQ z*!-f!_kSa>4Y5ff=p5$3WW!kv0+shk{I9gsD%yIeXIbWPhcEXhqnPwB~>z!fL8&Gj=WbEbLaexju~f8caWaVux+o59{MW2`a*e18cb?@${4maDv_g|(AD8+41+-?meH4Ml4rDV$pnytch6ljmZ< z(&3oDVT|@Tq;)UbuO!QbcCu5f?y?k2KWnSDydhZ?FEcQ}P`MJbxdu~1{!7pG*E`@^ z6xO;6j(VDz== zOrKG;**o2CPYF8dXCKQ4#XOQrb)s}leW77=kQ-{qJZ<)4jJ~5`$xB0QCp*2+|$C-K-_)|9%vF`)F8pAN1ratQ;v2T(%3^f z4$Jl!jcb#r=0wUU;y%KTNmG7~cdyrh}3VKq(Y< zjBx)*l)IfW+N)MQyIX!FyWK-EZ5su0TwirIMDy*jcy_`q1`hPl^iPI5r0f%f^fI?? zXzjZe8nCpq*@AVGD$I1R!v{v0#2~7zDWx4Hz3oH0ZS<`5UZGWfU3q4c1kyxcqv_IWz}jP@Y1}AW)Gd2 zR0Mtv2%0bFmfF;PUp>;2&%M2tx{N3rimie3*C#+_^@db+8b!|Uv)lbW4Db82s@v~c zeS&uGOPlwr9xQCXLuz7&tanXjI0PF4J=m_>MElm_TAeizRIsNqtz$ooWehQw#EIGu zq1=;XPF9*Z?`h#SV+oUwMk&C7o7VT$0KD;@mZ6C6vv=&DPLS^UpKc)ThnEh#JnpX* zGP!^8FOTVBV~KCI(N(Q~H{%W)*xm%Tm%QBI4t;NYFoKak%!N#;W-fR*))0BBIbu%S zV~kPpdZkG%e8&9}!yVu7sDkgF*4xOMAm#wfO}MvJCw3T2q#AY`;upli;j--p_YF-O z#9%V+Xgw`i4o9CGFiNhT)bHeQYm+|2%(R0LVKX$a_x1mw@ z>nBKU#SNe(Ar00|A;EAz0(1ZeLxIgm;-r}l+YEMNNot0zrhf}J2`YtWMQoacRpLyaT0i8Ot)@eil~q`Rij7qeaTxvQ2~A7Skt_Xc!k4H$%NnA7h7=v1$#>1J zGY!y?`?qN~b~ILOS=!r>8I$AvpIWlmu!!~>Jx$fnp(>{ERiQDgbl16nWM$@+xg0Wxbt9xWSN?sf5jUC<8vL8rzs zaYaK%a*qbP=?Opf*k5L>4>V2vT3&dmjWgJHVz|R{no6d9I1J<)y3OVm%X{(&7}=S= zGah!#uiz3gS6JQ^biw2)+q2gSevQDsUA~Dj@7HDvecpGnRRxsn-016~;&k$mYR?+| zd*Fd%qhXj1Fze+aVFCXy!7-2gqs|0J-N0-%{F5S}2C|#&++WC~pL;C96RkO;k>T@o zl4!kwQUSCCi0p*k6O_MowBo>Vz1jpp-~duLitY#Qw^9I)5OkXNEygYA_q>0dwFajr zC;Jb|pPyzam43tK-=8NIdVG(3VcE7@`{qX{<;wD}pXM!LrW!F z1i|xPZQ!1LwV^2KBG}rps17w?Sj&|Ay&>fLFo-;QEwh(u2tjgVKE8_AXnpL?u!F&J zvM%tL)l=KH*~SP(CH|cYD^G3s*i-C&UyG!YQAMSUM-umJP-FSX@Ag4F^~gg3hlG%5 z^Ar+wqN{y!qdwFZc_G=FgN}|}YcS9soPE@W+Qs)Y_wkvNgH>R_iw^7oY1Oi@9AvrK zLP{fXdF>ltLW;v#JZ>G%6|K=~CwfT*HXQ*GS32k){-G>EUKE>oR%Oh!8u3^V_eDuT z+eawnDwB#u7Hf45=uyvG;UloYaJJ50X3^@)n4`6z#1SJGxlk-tuXr$Z-cm&^Ht5kj@yq9kF3Cig^jiJTwD`L7RK*JF8U-?n0M`qN6I z{<}U97%VYwWKi7I5qac#ZYuc&qx=k|>RC=T=oAT3sB)m{W4@KID&~4QUY}>qmu|nh z7X=G|JcgMvtN* z<|`gu5=jB`#?7_vy})8yNsd;_s}a~BvV7YmHxk|9t+uZkx3P=H&fFaG@lpShqo`H> z0$9g{?OLgUr{QG&aVaa?hV~njuv{T6%V=>y>n(-3N&t>zKUA5Fq`0wm3|QE~6YP#P z`oO5~KIz0gIi%krhjffp@-7T7e=u2aT;dm)u**9bp|5z-L=z+M-F~MCw~x6>{Wm_m z!=R8S;z>{(sYRSf&jbp#ryANcw(Xgkr@R&Y!LCCp4OM|7c`t2n90u6yR6GgZB*<(y zKYk;Wtj~$6oP@mrW^Woh3OJt7j6VMW0*FCLvb&L88m$~dnlJC-ooNf|!3Ad7 zdO9gNEORDw8ojBa!v=j)Z$SiU%MSfuKgCK=BtY#zwAgyI61ks|K_-sK z(BG=nedy=mT?*C_fICOeBTIG88>}W65`mYthr^Mg_-8slG6B>w7`;7knHc*DxeA*} z&~vsU_bc(&&b2USgmGJWx+Cwc(rG1cLY|{)>An(H!?|}2%h3L=hcib$Bsehw+MK>b zza$rwhQW{e0%@Fu2*^haPIe@YkT58Ew3i2a6)6iT?@b9#geDXWi1wm)&7Sow4BP@Y zKS}8ee!&dnQg}y+oD)DOQ=|alb&Gz7%rc_c#9v?^0Y!){dZoj2P~+4B_h&c+X|XUd zhC}lv%VX~QFPVhwW+DF+yYdowAKh$>L}E~?OyDn{Ra3Q=Efy;Ajc9Luj2}W=-jO-* zVq)`g!_@1_?o+%W;~1FNp#LTG5z}sORFhked#ps-f;8r^aWf#)L6Z>*%S5u#oH*~O zyS(G=dwt_~7WGUr&D6k+kcKcqJzI>)fYp*!?ry`R1*a{c=Y&%9h@*(xup8q5kC4I{ zBPpt>GO>}bMrPSKF?eLuqQTOrCC56GSbSenBD2k~D_`w0(Wc{|cxZB>aTD8Va$$ch zP?CK<3mc~7U#nEyAc|tB8A83vFg*R-{NTb5tlf`zQgEtF@Gpv09#vOINobqj1@h`( zh?NpYzBElX{^48jzEsr`uM(s-*nb#jy=%ViH|%s|k5NLI!-Vhd#8hMJu@LzF9B01B zyHjaXbo&!c&BRFAYFnZS&U9CPbPQ>AOG(N%1FOb}AQoqRG@FD6o9{Y}l&?_}<;tZM z#HTWc^aGTzSE9H*$g-r`2PLFJvip3wsULjDD3*e1GAt#>k_#;@hi5o$EJvBtl6J=u zlHcxrIxs^)`GCE_Wp5Cih^+kI^aw5k11#4TX~zjAz*w%oUs{ROkK2O)%`0ENNyiM) z7NiF9AXt4YtW!^m&f5WL#A&Q>_6$Lae-O1NR(BR3Hce1M*I9du>J5cQ9;R6G)*3?< zIE%|B-a_RVB`wYWs{_=8{du1Og{91YziI6){=bLI^Nyz(kQ_&5`^V%^#I%X3MSoci zEOyS~fSY|6nr6%~Po!9>a6N1El6a=oIJ?j*qaUGbZW=WHl7@NHsX2<_gJDxIpTU$SfJRCBcWAdP%a1PT{ln}XL zXMZZ?ny`#C60jv01}n;8C{9yIf)73zQ@f`y>t3QQ6p^P+ zJbPWbFCc{RLB&&}jJE9fO%-bpa%q?n zm!y>!Niphigfh41btG#M2CcaQJF{g;lwQ)0^veX}q;c@?xPxhKtd@n+frsaGdeoU? zg3ximNdWhdZsU$~iWlDnI#U&-z-cpvwz4QvFT6Jk&_)zTBrYj744l>Tb~bUw3z|mxs|1Bn{Eq;1M>``zU@hWi2q4QrHY493)S$ z45^pVPd`0UaPfpPOc@m*YX4VY^>2{+0VprToSu|(q@32iwonVBc$;q^66A4*cdu?k z&#t{WLWcZB+9GhP8$}#9N*!doA(XbsF&qDZTU^$L1UnISzfK!a=~}y77@)+_X!c=hfKfPY`MqVuK+3X1FY&l!qy0B6n_4 zYRd7fbWWzn8kvduGI3KKIR~yli=HAX*$gv(Q9O6*qu53R(OraGv%REGy$lo9`iHI) zy86s-EnoeNgi#Ozf0z(sT27`IdVUWqqqgbKR(D*$^zJWlpskpv?zc-E1$0wYI0nNQ ztbLmi8CoKy7zbd*aFdobV?MsRm^*ycc=OErh;*A|$htsby?c-DL5uESjrVth@nYZ& z40ywk@a3-Fu^09y&Idfh47K+0^hX&fhgQMvfWG%1QNN{83&uot??H!A;I#iranX6(MVFg2RLonqOAZ z0brVU@=an8E|-X8p)HUH?D;&=N3B95Qmp70Vr>XCgHJz|8Os$qVHl;NGWWVGJ*^#d zX7Pn;sewp$K9~pg8?JNZZZ#w_rS|5#&zhe{xbDG@JQAW}7K zHmPkEU6-B%m5AKJTx{8+9FT#YCPSiSj_626m1(mhRaLc$oojk+~J_CCUa3QFs> zqwe!E(D*ByzC_%bIg?7GjVyXV+jl6p<7Q*3w5G2bUr{=gV#tWFjd~w{3=FuXpf02b zI?Cp%J|om*vnVgpMrKYwNc-T;S?{=XH`Np=lsQ7!;jZyqB!6AuPizquwZ=`3jZ8he zduD8tX8-AB2`t2N>QBwU@H{OIcQVo!c9}bN3IORKj^W3{6!OdgdCEI2gp^$qlk{^O)B zy?3+b(3LDT3ycX~e(0E>6Z+}$M#q3Ki>!EmnL0GdL-@Q6mYk!digjh+E4K-cG?ks3 z4y)7{X=L3>^GvM(FA60Wy!ta&%z%VrDUwxU(8qVo0jU;XKf6)z%3u6Ukunp7pG|mp zL0D<{vAV*=USO6U2AQT#PgN!L*O!1H_csP^pAHlHll#In(I9<-8p#aKGP(WW=AX5lxzSvZRV!RpCL^1)FG#af#jwVY z&(>1r_LR^p!=55}AKP!$ooJVyHZ?ol%&ato@fbRkrA?HI8Q2M|H5t{ln*Zem>vl3& z0B^Q{?I^z9r_ISD4!#s|vo0k0`tx5sV`ju=Ff}NqQ}zGTC+Cwu2{Woltdo3KmN3iB zySTG8@N1w$@k+y|8)72oOc@8r5(i*M%9I7Z=p3wp609Qp=qIiTFQP?-o2Kiu1x7Sy*+P^yu z*?a#<+_GzZ{JE2bK!=#&(xP><(rQEPHu&A^_pOVX@Nv&lgs=b*P&?}bVELhzf%H}< z{KbxooVeU5+mQJ@#g3lc4EV<^4&m>x`neeT5SyxQshZ_J~c4NmCF^YoQ5hU8^(d z#iALh^N-^$%Vl~6j%zxvVi}Fp_YZj;u9>w2rJvf?SyTMhzGeY+)W2}kZzQjP$ZRah zV~ic@Ie*eG*7?xkpNz7>&bd1Me<}H^ZSPv|@e0bBK#;CeaDPq=AuY)kY@Gx~jFYy2 zaohIl$cTeUdtV=&@O!TV|FrMS&|iMhkM>0aS{N-+ z{nKNKXa0wX*HJ|QR3By;BJ7Otj5p!p)^TtOVO^GM>#y z?Zgr2Yr?HvxL+$!FHUeUXWdA>c6V^aflIUK?hn^tz`2?#)E_i-7r0?2*_qd@aOEXj32_N};W5_chr!Tq3j=@YqHtl{qh^~Uas@Q4*U!An2q5qIR< zE4|47RQQD|39Tt@!T!^pJ&D^pn?>)}&12RKjN?_m{4Z(I{+!$^nQPhmt!IeIVIaS< zY*W&*iq6i#c{D zucK90sIY)c&)brbo+4flWSL+aI3qWP@yT7J2)84N7bwRsL2;jF4Hy)Q_1+CVK)NMP zNslkL-)bAC!eQ6@+ipO_I!z~6KHet9TY1pN1ae3)9*rfL$@?|&15i><>k$c;4mmtX zq!PjkVW2IWp^DX$#}eq8GzcO*R&j6E^4=`wd zkGqHTW|8f@%80E0p8#Pnp3iJJ=&(Z{zSVt;jvXL*<|k0wI)(B*XfENha4*GI{~*hH0wD$4Z{7eC?2k`{ENW zj#CytO>bDJRKnDgIDj%M^sgq2Tz?ctkyeHJ&A<=`Yeo@-O(A~T-n^xh@)tb|>NgXE z?`RuG5a(2ipO*L7BwShkgntaD?Jum=Jw)BONx^>@t~if5>d9w1l2ZN4a==%(X8 z4A*_TkNUCu1^dtR;4?TL<8H!8&i8mU0hax~iL2!%1^;2Vj?I3UV0BTPU7Hp9R|AIc zHW-EACI$aty7DIw_mzv0`A@@puJ59r?0&)iGd*xa#JCfh6#R$j`p~k5YW%elHih_U zddn4saerx5sNW2X&+Q0(NKOAa3Zl&l{i}wNzeL|zX(KNmseQ1Z59qeNTX1MCu;h4BTwS$veCg3M{8{ini|_b&f8G5)&1oZ*(o@=sJW+i2RzkYmqPhhVp>M{_i&9on zgvKbLE6B{965}jPBM@C-yZRF!tHp`C-xv4uyr0k+Pv~k2n8rJ>B8Ancu`j7_H%<4* zDy8f>csp`ZvN?m=bQ1|M0i2-J1SR%sae`72oO6XQq{7!%R~MJ4@RBS15Lf7?c)#$h z!s-N20nSf|a4g#yXU{0d3mWhU_rmA>lV%^*&Db`#?1Qv}mb6O?F1#z#>#{<>Qu&=Y zKs22g)#pQf%h=`8iCF@Cj1T-yO`!Mh-X!ScocYy!%JI~NN-)AS^VMwVbZSCGSorAn zSk(LdDk45iK9=Bj>HyJnxNZ<UFm!N!>N`?^tr12HvWNVf>WK+d(4FH+~W=h11ogfoTuObSbyg~cXWp)TF`-|TyXsf-%7+W{ z-Y(EDK^$Ki=L9!_hkOQ1n2YyV9+E`9@vxOb8!SLN6zz_ET|UDslXfa@+1tI0`OHHp z+)HpTrTY~PYig+EFi*`8OY=<)amzX_A4pPeGRUWL;);W3k;1(78yJ~Jg9iEwnkm63 zeK?!+c}^i&T={7-lVKDw78;c8a_TlBtE9xSg&&SDAfEF3YY6cO=GVeZJHD5?s`MudToQe&>&YFMAZubsYE0_`-sBN&}CK_#kGS}nRQc%W44=w zafTw8dknJ^xrMB!;v#{s(3j)|Ad~ln=FQhG`&e-{sK6By33tPGargho)GkmlM+#Iq z%TA!4rFb*6i!N)JMs|ouaOYEeu;ZFfNRza#pZGoc<_5p!~ zX3VX4rR3lWC>RdG79=0w9T!-+#EZy!p6ksgx->eA`>M`x2A~Tejwk0mrsxCn>#~@& zubxnsRk7Qu?2oXi09lx?*7MH+W;I;vh?eT-Gt%@_Pd4g~orU@sqO5 z`-PZof%>S=aF)IVqXj}RCKa@T%*UG;K1!)@uaRBJQ!n~jpAU0KOrj*Py3RdJ7_2bIQ^c1bP0PL-;_4yH8q*|an8G2zzaJZ%`~;I&@}HvZbz%M$;DKH34~ebt zQ-OUaaN<#oPCAOPrRHk2J={)f0K{_-^;OqqOor z4FJ?43dLuBpdfcE`ZPY1Op?{f97p`k{WTZpLiw}T;|+pbXb{=Ua>YPi1IUM3On8t;VhQBFc;-T9N@~6|ui)RKZrCt; zTvE30nz<>A-b49qLmOW8Bv*33f&0c{G6X7(-b4AtLm*xUlWV$9Rx%60oBL*iGk3&> zUVQ(GDX2@@%bmQW{V9zjS@^n@dIOvNUlYg8n+$v@47V-0?WylCkY!&6PhbdB_F!j4Al}WUkPI&Y1^_1s-@Z`EQAAI zn4nU7mC6hD`rzsaOu@^p@M+?|n~FVa__bnAEzu!u@G=(eHI>>)_Bp@pi|2R~=Y@En zf>>V3LFhOyjqzTuRY9$J`Eo{6$9t+un1VnP4fMa~d$Q1Yb; zIts5nY)glIf@N+LEH9P(OS~F?-VR@Gf^9;TQim}kiEE!XD9r6_k^wx#LL~)Z|vnRk$Hu<$DD5n*_UBvBLS$wN$RBFh!=_EY#s@F0tzWz$r1p<2eCi(kKxLaP5Ly6P|lO(AL1kK&TbZd#E`>=u)XQMmFb)Ti!+1@2B#Fi0<1qXal^pxFjMY_lPpfo|7qCs~A59>Ri?e`)R9}eVMd}_lEAt?~$ z`(Q!Rv8MA!@Lz2sTT5vBr*^mGRgcItrh_P3C}=CG_TwaLp4oie&9K)x-Eh`6wIG&O zsuieNX5zo8vu;#ljq@7X8rFcs`n-j~R}-1Ga`^0!k7Bdl-bh+b^{ISIhu^k<7y~Qz z@Wesku)_^zErsQ+?ebtcZ??1Fd$?3!0tIAl1WS$a9}j@Gz2K)+Qa4DL=`K`lYBEBI z={ODR^4~XWS#-lmVD-AL)F*XQ&Y!;P62P`O+vchv5L;DI)3|X3Q8(H&sT6Oc;hY>Ci?5OC}3<+X2lu;efD}MtrZjfp=6l6SVM48f|>NAi)vi!3FLla)-oe4tQx?B zU(Y)cRT1^9Tszfv)@5fq!KE(o9~XcIX!<^Y55^XDxDB}PG(yw2Tp*l#e$`Fm>Vho7 zu`I74J!FC8lGoZO1nYM#0LCS+wb5SF+#FyW6!>WIR+)WDLBTz3zp*;|UVBc0Ro?L4 zo7}Vdz8w{r?-tXg?$OuKcKqrT#?^;9NN%AufW00sKsiSWSmV*`_;h^k^WdV!{+Y{z zssaVSW;RhmOTg^d1Z=;es9ZL80Y$K{EQZ zbq=<+HeV=*It?8 zzri}zYK1k5O;vfQKQ02+fmQ@8p%LJDB0{oB67zUVv@BL@@(|#d7?OkV8|Xl&4gfyc z8;EAMgaKzYEn$YKp3b-Hpo*TS!2apQI1Q)A27)=(f9@<#wToUp#d^EA2}akSC>U7W zjE=%GZjrFtMd{55II&rN#tV^7qDDUQsCE;4H*aomUH5bAC14}>*d^LcEspyB4q_44 zXno_*>?T!5-ZPc!v&j`~W0ai*bi2uBBmE6GMj095=K+i|ZS?srEGao{D13ty?{;tg zyshAK^Vw@3;+op`lS8wcR7mvltt$B$RRUyAkOID=w#}+qE`#CUCu!4=6Zva5ZIAZ_ zd=AR6HhuT`)B=ezC`04yI?cc11vRS&J%O+;F)YO2qe)^On$%z(`?^|Xe2z5HemX%RwPqOpRh1^a|Pu7SY?o8 zaoq~Wdo-|kPQ@nyf(4He2q9nyYy=LToQsVi2d%QOXmn_PTc|WeGVSx2XevY(rCE>Z zxjY!-txmch0crD!XrlFk*v`9K{?go*wd0x;8 zESSFu5L@U?N%GD}x-tSN#%|CN6f^_W8ZQM2;RF2{a zwlrHET2iM)M~Lrgk|k)|W0!9pn}hQGa^9?@i8cEpZ&pE&`14jISs`XPXYoMbjvTTh zm#j;vx;$Js-*WPW+xW6AnuBa1~S^QkXuBJFimpbPGYk!F)`X)R!!zH%Uxz9VG z>k?}}>NPZL!}ZyZmmn^!M~X)}RX`3`iQ;S}Undq0_x6HfqQIU;aNFgmnkkX1i%IYjU2DO-dZRJcP5 zH78JX|0zpJOA9}79mWQI^$CjPsvc@swb?FJx4A49+0FK@MJyK1rErdge+U5td$A?2 zQ=Qs~+&|O{RIMa3Dm38`j1~HK(TKV>KLh3l5c}l_m>WQhLkgWh0X*GJh+Ky>k&>cZ z6?@D3cB8H)wq&-=CEqzD;IvJ}FU1DuUBK*tA|lj4V^vW?_FGk1?^N?g+tykhm@$H; z9*uG_I!glzC;gL#=ks((j9@YyGh$h;OW%bpm+ADZEyy z<}Zrn0srd~UC=@CA)*p|aBsCs$ zMw?s9;L0Q!RYsNfqRM#DlOX=b3^HD<(94{sDDjSNX_DhM^wQvnOFFcO!Z<_><8{u{ zp8}L?ppju23(J^Kwutln-q5?ES_BJf9nyRkJ9OwEm~oP!Mb@lTeYq!F4Y)ln6CK+Q zaRg>*LJvy|?68BaRVd}10vPp*qpvod%9PRD__pUxy&4mm6l`Ykbgm%va_?ctY>4$v z=(KWht>k12!>S>8RdVJAa-UQv*BAvRO(xOhOuV2~v%R|)#Y5;N;+zTSgHX(*BhVd1 znOY>$U8^@m1rr+BUBWu+)e1W%$`IM~=op^*X!y2xwbF*a7_6xM@|>t}s)^s><|py} zXmNml*yEO)og;Wv=+VN6!&@O?GB*~zX!zwJyo+wm7RBTbF82`q1-A94)Mj!&7b5gh z*w%iX#+;q{&c<0=MB~(cb(>YHX#J&n<-u|73&@zU-hA=hAh|e!U)|=F!md>t0Df`= zL=*Um7dX2+V=g46xES+&MNmeu6%&q_5Oyi59fyPOQ*1OU?j+5`Ns4^5~Fx*#wz z!N8Tbg};q4t^|5FFe))dhbcJ2NSiz{FP7@5!*E043?cW62*JLjx#(v_a`Ia@aQCP>7S;YE?c4xu<=c4Mj`Q zg(tqbL56vd0^&entkF3pAdD>cxePGzQb1@aGRlq7RSIs8|J>GFIaosAtgen0HV!`crBfb;mtRs18f8Y ziI&?q!#vHWL7rxf99hRMV}Zt8ieMc(*H-5Sa3YTM9Er@3ns1v)N!(6ozMSxl)MO4& zNVF+_3F+}{CaAF#E3L-b_N@(E`3 z7YD_h!zaHhc^plXPqghLr+|^aaC$~+8DNea!$!&;a$kfdIe&uH0m;N;n0Q=oG3A;k z7akbP5RdF*&qkj?Z1g5TR-k)Y*~B9mef_~{poxc_PF|%z33x70P z5v9}w<5Pv9ur=W)%4uLQM6$ue5qZeU!v{$_l9V9VLL-axMc8rAYOOVVpuN=FkphbcMu4cZCylE~t4%eh0xQk5qhXtmpjk>Q39wqeBEGF!BCno0(QbafW zm35-)@^}I>p;;JDy)XXBx%&TVoXcMpk1)3h-5=bpf}7_}K2mecqp3}~Y>3!Omjmf$E)rO9LW)J1lKnnbwqusd!(@ln1 zjCP`9%X1GZMahS;^SsMLjKhiv!k%|N!u5uHi*^z~whnm!mm=oR5Z|fZocw`fvr8K! z#YC=}|AA-w6Oq>@8C_Wj?JV3e`PQ8Hmdk(j^-yl<2oVa^eJ<}-%~rjSk->bN!`RT6{*{XnVR@)1ro>n3 zzuWiwi(;w<$@8bLd(p9Rj<&ErJ_pqinE&LYs+;5o39(H^qBdL7S1c%^W5ck_+A` zZbahMwVaT0Kyuc$Cw=%p=LVgTX*Cx-kDO?2;9yj&$|7u!!0e>XkHPFze3G1)ZOS$$ z+wY^0F>NeQ+TVdlTHMO9aUQ&);p z1F+GF3lir^d8VgHQafuR@XL8o*Y;!VMIlyFC$e)J<}`jKbO_c2=$iYIg-~2nU0eo5 zk#JMoNo>-62n2)C2;iVga;c6u;Yg)yj%6th>HdWPKyij%uPLwq1Z7Nva;cn3jT|Ea zvL~!>N#R$xbq{=cd4<->zwvd7%AUr)MfKQpF%0$0>@*$@?WLw2S+rKFkFQCr8^Tlk zO>tOVV0!l&t{^4RjpQjFr#MzGFtK|Ly1lTTDH0OY_C)cVd1BNVP2AQL26aVKH<7xg ziCeY@nN}e-7KWNMVfc7mrp)vyj~vX7i|;M9E)T}4miCD z^}51z8QWC^)Or_Gxm?PPew>_$k%5vtt`HpW*Jp3r(733o>yq^qtbToFfz7Mw15V9F za*26hQk3(Eja^d<6BVl$I83(dA#%YS3yy6wgH}-tvP7aI7m&(Y*^W*;V*?95GE_V= zUE-pGkBwdQN7vL~${%fDx{Mq)=CCNec=6Bw(x;wCSzTBDAjV^U02yLmDkL&#E#C1zM1w7oxd)0tFQ(u5*vKwL;6WC)aMBaztT3-V? zBwu7~Pf0VibJ(k&nxK{5 zDYzpM&q!xYtaC-`HlgOVz6ly zb_{R6L7edmddg+srPVIU4(#plqne;sCIPT%E{hD%-t9LnI`?(Tu^l(vn%59NuuKE6X4qygyDGU;1!+x=eA0 z`B`CXS}!3Dy%aSkGC1K2VDphN|M7gh_5^s~Mu#zjfXr(n2jf2DF2BWgeXqXf4<(Kh zXr=MNiH7+bI+kiw0>sm+$!mjXEkiyy0XgZAZe~WE#68rT&6kwqH5~r+)NA%#T0myM z<*`24Q9)C_WYy%4o#a? zxm(Qy$Q%$MwEd(0#1P z1BGoY7;=W&@?BIgMlKkaE@mGU@YK{*EStx2^+`2|d@~71SC`8ed>>iTeZl+_7#RXe zWKe=;L3fha#cR4vh8KVguh9_LkNoVn#& zI{tRJCPPCD44h4ex6DmEx-*+?uvTzjUw=NS2$xc7b9D*b++!C5L%<-|ZL>pzK`0c3 z1_}X%`aXxyopN;Et?2U8aAB=<=6hsrp@Ps^hP3tOu`Kbu#>Gxp`?RFWN$EQjNuiel zgKMd2e8)>F_C%yf{81Z*t01^$SDL#7|?&15B}>&L{_=-HH8N$D^McN{mOVPPVV(QRKe z@5)~M3lq?cY7ttV#_%G%`aPB-y*L9L^O{OQ!-a7#-9U%%elO?6Q-bUoElz^ShT80y z|7azVSS7(i#a*en3fHkxxp zQ;b)o&$gm+&QS`Eib1f9-B?gxA?n0Ib=YeN1=qntWybWcZH-PLx(|b~kol$EeI zs+z8QJ}&Ym6C>+c9VgIiLR>bPJAR%$S;^7~CSg%C?qLkq;#ZBrR5cStT{hdbOg{D# z;F!wNuQ5GsMYbgLV>6{LBNz~=q8|=KyD%lMZM!|IB8qc}9uQmJb}qcJar{K&aBdln zl9&#)I5V~LNGWz=auM7t z%P>GW1KBMIwkQI0*qhUliXL@#x#?z$X+5E{*`W?S>TEe|i@jEIs!Jg|8el(>k@L~4iNd}|o#@nY6Gi*R@v!4=p4(a-_SoW@NP}r(u6UWwp?JwUh zd6^KoUCyXBm+vcDA21@YA5c#_c?2UD4C{?I7*~Cm_O|s*eT6pc-viETtK2w9Iy$bL zqGK%TI!dC0WCtYvCXeB&j&GspCbCeW1FC}wfPW^IC*R;oZ*NbFV%u*rR0l7ucKaJ3 z%7k%K;-HbxAV-3a;s9Sf1JjzE6ZRb&peP0Inpt-%EoYY-qHV0JRikTOBs(Z@Nd>d| z(zFRS?O|ezD=^}fG=BWJHiKD%X$l6v7HBz2f~Lr=gM^-=@S`umzZ$ zU~+~T^`rAOa?X#azg|oXC;gD}BM-oZ=K$}#MCokI>|)|*3)5*n?j_W0P=+iKHanibFx*cswbf*bM3u&_-aJ@brEH2@2$g^j!ey_Y<}e~HLT13 zxS|Ef-d`m)us%lDKE$5xw&@_XY6N_@GHzZbME~@@T=)&KSVQOyTo93wFhe*&`E!Oi zbqI9)j0v8NDr#0`M&nP0V1i&hH7Q6TCO~a` z5eJR&>+ZVtg5{XE@;xdLp(?6cvx59>{`xdrxC1xaJgOIyY-`?jgQ@=Y-;LA z&Zg%H%n13$Yw&NIxf-iyIKa=ic*ezhN!qw=d`^Hl0_GmS5;}xGkZ*)~kQiH$qP0B(FLlmP2y&ktZA@?dvz_ZKu_ zuqv0$x(dS?$0flM-E$cKJUC-b!pbeI3Ddsnq)jT8HOrn4hNMZ_4tzKaB)K}GPYBAq zI8goarsjC)x53nSn(#%AhV+ckaKeCN7cfao0p&6H_dbMbsOHu*0gPGuU;PV?WKZ$N z3CxIU6L1w!- z#ekx4L#M*B58&PYx6XqW?Qlow+}^fMt`_fJ`}l#J;cchiIC3HR4hIZQg6AQ1-^tx< z>i%^UZ||`zFci8>cpUHnlk?GGyyoJvo+-`<`XtKF@1q zrMEogW^6oCwW`2lEzp&_LeqchfMeyXN?%eTmXng>t>2re4 zg##w-pWK9=_T^dP#-sD`>yZ-2f3%59UoPibi_NX|sO+Uorg1{GQ<^dBV%x5E;zBcK zy1IDN%>xy67b3D_rk~+QPpVwq{c`%SY^r*5uq$zHFdPjEeQeOX6j30G;S$iyf=KgF z12mY2+buM+AksWoa#JprIKMQ63jUKD%h#8uZ?a4InI^Q-whwM5(aHd{kzhZQyw-NC zH3q}3Gbc83uNq@%DeA0PE_W1B)HxWRIB=uovW+?AGucI&3c{A8!j@|HdyVGyZ9ZkZ zUUzs|O(FZ`pxUc#&iRityo`dH8%WHDLD@IuL;1wXLe%TLp^fjUbd^?p%Niu zICw$xV>I8>>=!|0jzC$=wtL{23N~Z?G~oM z(H}D!Q^d8vutV0k$yS8j_tX%|76o06U^JCv@udB12e(0Wx0LobOw0uNv;t=Zkn)Nx z;#rx*%umO3O~+kMTryN$nb2W9B*Mi(DSK)ex{$ViBDXk+UZx9g`zdl8LUy*h;-LlT z(qcnGXTQW;*k5MLAQNfGiftFSSeRiOjbOVyQ+@pKe3|3DL%Wh1l>I)#IeFfh|hr4Fo=~ zJynC`00azJU1_eUIE}08cDusandGbLYT)AfZGGjMyV$l5d>627h;_wo%BW7mVheom z)%Or?r~0#*XD?SXh~?HyH_c7C`X+nv+vmRvxB`29LlGobAP_T-L2|_K8P05m4pG0~ z+<&?#1(FuU3_V0)k8cXecvVeM#eTp4TlK#eedS;W6U?EqfqDL0^}iQT;TKTbJlME~ zJM|zp)B8*{eA$bad_R`WdXx3m!4SEE$X~p~yO9Gz{vtbO^#2Ue`^));ES`U|p65|L z0^cr3J?rfG%VG6JgecqmISJ7U&Mdq*b2Wo#Ww4A;WVc&rw=-QXyW3WMRUi&7T@*Rt z`edGXY2+aHfp@ECu*!5CT(U)C^;uveR!?_bZtND*mV3hySwt+us z;w5WtIl18yMnOoR%L@u;vd1@@elL1##tcvgtMvaYM-Vd63-E6M diff --git a/docs/0.9.5/py-modindex.html b/docs/0.9.5/py-modindex.html deleted file mode 100644 index dcfba91b19..0000000000 --- a/docs/0.9.5/py-modindex.html +++ /dev/null @@ -1,1197 +0,0 @@ - - - - - - - - Python Module Index — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - -
-
-
-
- - -

Python Module Index

- -
- e -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
- e
- evennia -
    - evennia.accounts -
    - evennia.accounts.accounts -
    - evennia.accounts.admin -
    - evennia.accounts.bots -
    - evennia.accounts.manager -
    - evennia.accounts.models -
    - evennia.commands -
    - evennia.commands.cmdhandler -
    - evennia.commands.cmdparser -
    - evennia.commands.cmdset -
    - evennia.commands.cmdsethandler -
    - evennia.commands.command -
    - evennia.commands.default -
    - evennia.commands.default.account -
    - evennia.commands.default.admin -
    - evennia.commands.default.batchprocess -
    - evennia.commands.default.building -
    - evennia.commands.default.cmdset_account -
    - evennia.commands.default.cmdset_character -
    - evennia.commands.default.cmdset_session -
    - evennia.commands.default.cmdset_unloggedin -
    - evennia.commands.default.comms -
    - evennia.commands.default.general -
    - evennia.commands.default.help -
    - evennia.commands.default.muxcommand -
    - evennia.commands.default.syscommands -
    - evennia.commands.default.system -
    - evennia.commands.default.unloggedin -
    - evennia.comms -
    - evennia.comms.admin -
    - evennia.comms.channelhandler -
    - evennia.comms.comms -
    - evennia.comms.managers -
    - evennia.comms.models -
    - evennia.contrib -
    - evennia.contrib.barter -
    - evennia.contrib.building_menu -
    - evennia.contrib.chargen -
    - evennia.contrib.clothing -
    - evennia.contrib.color_markups -
    - evennia.contrib.custom_gametime -
    - evennia.contrib.dice -
    - evennia.contrib.email_login -
    - evennia.contrib.extended_room -
    - evennia.contrib.fieldfill -
    - evennia.contrib.gendersub -
    - evennia.contrib.health_bar -
    - evennia.contrib.ingame_python -
    - evennia.contrib.ingame_python.callbackhandler -
    - evennia.contrib.ingame_python.commands -
    - evennia.contrib.ingame_python.eventfuncs -
    - evennia.contrib.ingame_python.scripts -
    - evennia.contrib.ingame_python.tests -
    - evennia.contrib.ingame_python.utils -
    - evennia.contrib.mail -
    - evennia.contrib.multidescer -
    - evennia.contrib.puzzles -
    - evennia.contrib.random_string_generator -
    - evennia.contrib.rplanguage -
    - evennia.contrib.rpsystem -
    - evennia.contrib.security -
    - evennia.contrib.security.auditing -
    - evennia.contrib.security.auditing.outputs -
    - evennia.contrib.security.auditing.server -
    - evennia.contrib.security.auditing.tests -
    - evennia.contrib.simpledoor -
    - evennia.contrib.slow_exit -
    - evennia.contrib.talking_npc -
    - evennia.contrib.tree_select -
    - evennia.contrib.turnbattle -
    - evennia.contrib.turnbattle.tb_basic -
    - evennia.contrib.turnbattle.tb_equip -
    - evennia.contrib.turnbattle.tb_items -
    - evennia.contrib.turnbattle.tb_magic -
    - evennia.contrib.turnbattle.tb_range -
    - evennia.contrib.tutorial_examples -
    - evennia.contrib.tutorial_examples.bodyfunctions -
    - evennia.contrib.tutorial_examples.cmdset_red_button -
    - evennia.contrib.tutorial_examples.red_button -
    - evennia.contrib.tutorial_examples.red_button_scripts -
    - evennia.contrib.tutorial_examples.tests -
    - evennia.contrib.tutorial_world -
    - evennia.contrib.tutorial_world.intro_menu -
    - evennia.contrib.tutorial_world.mob -
    - evennia.contrib.tutorial_world.objects -
    - evennia.contrib.tutorial_world.rooms -
    - evennia.contrib.unixcommand -
    - evennia.contrib.wilderness -
    - evennia.help -
    - evennia.help.admin -
    - evennia.help.manager -
    - evennia.help.models -
    - evennia.locks -
    - evennia.locks.lockfuncs -
    - evennia.locks.lockhandler -
    - evennia.objects -
    - evennia.objects.admin -
    - evennia.objects.manager -
    - evennia.objects.models -
    - evennia.objects.objects -
    - evennia.prototypes -
    - evennia.prototypes.menus -
    - evennia.prototypes.protfuncs -
    - evennia.prototypes.prototypes -
    - evennia.prototypes.spawner -
    - evennia.scripts -
    - evennia.scripts.admin -
    - evennia.scripts.manager -
    - evennia.scripts.models -
    - evennia.scripts.monitorhandler -
    - evennia.scripts.scripthandler -
    - evennia.scripts.scripts -
    - evennia.scripts.taskhandler -
    - evennia.scripts.tickerhandler -
    - evennia.server -
    - evennia.server.admin -
    - evennia.server.amp_client -
    - evennia.server.connection_wizard -
    - evennia.server.deprecations -
    - evennia.server.evennia_launcher -
    - evennia.server.game_index_client -
    - evennia.server.game_index_client.client -
    - evennia.server.game_index_client.service -
    - evennia.server.initial_setup -
    - evennia.server.inputfuncs -
    - evennia.server.manager -
    - evennia.server.models -
    - evennia.server.portal -
    - evennia.server.portal.amp -
    - evennia.server.portal.amp_server -
    - evennia.server.portal.grapevine -
    - evennia.server.portal.irc -
    - evennia.server.portal.mccp -
    - evennia.server.portal.mssp -
    - evennia.server.portal.mxp -
    - evennia.server.portal.naws -
    - evennia.server.portal.portal -
    - evennia.server.portal.portalsessionhandler -
    - evennia.server.portal.rss -
    - evennia.server.portal.ssh -
    - evennia.server.portal.ssl -
    - evennia.server.portal.suppress_ga -
    - evennia.server.portal.telnet -
    - evennia.server.portal.telnet_oob -
    - evennia.server.portal.telnet_ssl -
    - evennia.server.portal.tests -
    - evennia.server.portal.ttype -
    - evennia.server.portal.webclient -
    - evennia.server.portal.webclient_ajax -
    - evennia.server.profiling -
    - evennia.server.profiling.dummyrunner -
    - evennia.server.profiling.dummyrunner_settings -
    - evennia.server.profiling.memplot -
    - evennia.server.profiling.settings_mixin -
    - evennia.server.profiling.test_queries -
    - evennia.server.profiling.tests -
    - evennia.server.profiling.timetrace -
    - evennia.server.server -
    - evennia.server.serversession -
    - evennia.server.session -
    - evennia.server.sessionhandler -
    - evennia.server.signals -
    - evennia.server.throttle -
    - evennia.server.validators -
    - evennia.server.webserver -
    - evennia.settings_default -
    - evennia.typeclasses -
    - evennia.typeclasses.admin -
    - evennia.typeclasses.attributes -
    - evennia.typeclasses.managers -
    - evennia.typeclasses.models -
    - evennia.typeclasses.tags -
    - evennia.utils -
    - evennia.utils.ansi -
    - evennia.utils.batchprocessors -
    - evennia.utils.containers -
    - evennia.utils.create -
    - evennia.utils.dbserialize -
    - evennia.utils.eveditor -
    - evennia.utils.evform -
    - evennia.utils.evmenu -
    - evennia.utils.evmore -
    - evennia.utils.evtable -
    - evennia.utils.gametime -
    - evennia.utils.idmapper -
    - evennia.utils.idmapper.manager -
    - evennia.utils.idmapper.models -
    - evennia.utils.idmapper.tests -
    - evennia.utils.inlinefuncs -
    - evennia.utils.logger -
    - evennia.utils.optionclasses -
    - evennia.utils.optionhandler -
    - evennia.utils.picklefield -
    - evennia.utils.search -
    - evennia.utils.test_resources -
    - evennia.utils.text2html -
    - evennia.utils.utils -
    - evennia.utils.validatorfuncs -
    - evennia.web -
    - evennia.web.urls -
    - evennia.web.utils -
    - evennia.web.utils.backends -
    - evennia.web.utils.general_context -
    - evennia.web.utils.middleware -
    - evennia.web.utils.tests -
    - evennia.web.webclient -
    - evennia.web.webclient.urls -
    - evennia.web.webclient.views -
    - evennia.web.website -
    - evennia.web.website.forms -
    - evennia.web.website.templatetags -
    - evennia.web.website.templatetags.addclass -
    - evennia.web.website.tests -
    - evennia.web.website.urls -
    - evennia.web.website.views -
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/search.html b/docs/0.9.5/search.html deleted file mode 100644 index ec4bc2f906..0000000000 --- a/docs/0.9.5/search.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - Search — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -

Search

-
- -

- Please activate JavaScript to enable the search - functionality. -

-
-

- Searching for multiple words only shows matches that contain - all words. -

-
- - - -
- -
- -
- -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/0.9.5/searchindex.js b/docs/0.9.5/searchindex.js deleted file mode 100644 index 1b080b4efc..0000000000 --- a/docs/0.9.5/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({docnames:["A-voice-operated-elevator-using-events","API-refactoring","Accounts","Add-a-simple-new-web-page","Add-a-wiki-on-your-website","Adding-Command-Tutorial","Adding-Object-Typeclass-Tutorial","Administrative-Docs","Apache-Config","Arxcode-installing-help","Async-Process","Attributes","Banning","Batch-Code-Processor","Batch-Command-Processor","Batch-Processors","Bootstrap-&-Evennia","Bootstrap-Components-and-Utilities","Builder-Docs","Building-Permissions","Building-Quickstart","Building-a-mech-tutorial","Building-menus","Choosing-An-SQL-Server","Client-Support-Grid","Coding-FAQ","Coding-Introduction","Coding-Utils","Command-Cooldown","Command-Duration","Command-Prompt","Command-Sets","Command-System","Commands","Communications","Connection-Screen","Continuous-Integration","Contributing","Contributing-Docs","Coordinates","Custom-Protocols","Customize-channels","Debugging","Default-Commands","Default-Exit-Errors","Developer-Central","Dialogues-in-events","Directory-Overview","Docs-refactoring","Dynamic-In-Game-Map","EvEditor","EvMenu","EvMore","Evennia-API","Evennia-Game-Index","Evennia-Introduction","Evennia-for-Diku-Users","Evennia-for-MUSH-Users","Evennia-for-roleplaying-sessions","Execute-Python-Code","First-Steps-Coding","Game-Planning","Gametime-Tutorial","Getting-Started","Glossary","Grapevine","Guest-Logins","HAProxy-Config","Help-System","Help-System-Tutorial","How-To-Get-And-Give-Help","How-to-connect-Evennia-to-Twitter","IRC","Implementing-a-game-rule-system","Inputfuncs","Installing-on-Android","Internationalization","Learn-Python-for-Evennia-The-Hard-Way","Licensing","Links","Locks","Manually-Configuring-Color","Mass-and-weight-for-objects","Messagepath","MonitorHandler","NPC-shop-Tutorial","New-Models","Nicks","OOB","Objects","Online-Setup","Parsing-command-arguments,-theory-and-best-practices","Portal-And-Server","Profiling","Python-3","Python-basic-introduction","Python-basic-tutorial-part-two","Quirks","RSS","Roadmap","Running-Evennia-in-Docker","Screenshot","Scripts","Security","Server-Conf","Sessions","Setting-up-PyCharm","Signals","Soft-Code","Spawner-and-Prototypes","Start-Stop-Reload","Static-In-Game-Map","Tags","Text-Encodings","TextTags","TickerHandler","Turn-based-Combat-System","Tutorial-Aggressive-NPCs","Tutorial-NPCs-listening","Tutorial-Searching-For-Objects","Tutorial-Tweeting-Game-Stats","Tutorial-Vehicles","Tutorial-World-Introduction","Tutorial-for-basic-MUSH-like-game","Tutorials","Typeclasses","Understanding-Color-Tags","Unit-Testing","Updating-Your-Game","Using-MUX-as-a-Standard","Using-Travis","Version-Control","Weather-Tutorial","Web-Character-Generation","Web-Character-View-Tutorial","Web-Features","Web-Tutorial","Webclient","Webclient-brainstorm","Wiki-Index","Zones","api/evennia","api/evennia-api","api/evennia.accounts","api/evennia.accounts.accounts","api/evennia.accounts.admin","api/evennia.accounts.bots","api/evennia.accounts.manager","api/evennia.accounts.models","api/evennia.commands","api/evennia.commands.cmdhandler","api/evennia.commands.cmdparser","api/evennia.commands.cmdset","api/evennia.commands.cmdsethandler","api/evennia.commands.command","api/evennia.commands.default","api/evennia.commands.default.account","api/evennia.commands.default.admin","api/evennia.commands.default.batchprocess","api/evennia.commands.default.building","api/evennia.commands.default.cmdset_account","api/evennia.commands.default.cmdset_character","api/evennia.commands.default.cmdset_session","api/evennia.commands.default.cmdset_unloggedin","api/evennia.commands.default.comms","api/evennia.commands.default.general","api/evennia.commands.default.help","api/evennia.commands.default.muxcommand","api/evennia.commands.default.syscommands","api/evennia.commands.default.system","api/evennia.commands.default.tests","api/evennia.commands.default.unloggedin","api/evennia.comms","api/evennia.comms.admin","api/evennia.comms.channelhandler","api/evennia.comms.comms","api/evennia.comms.managers","api/evennia.comms.models","api/evennia.contrib","api/evennia.contrib.barter","api/evennia.contrib.building_menu","api/evennia.contrib.chargen","api/evennia.contrib.clothing","api/evennia.contrib.color_markups","api/evennia.contrib.custom_gametime","api/evennia.contrib.dice","api/evennia.contrib.email_login","api/evennia.contrib.extended_room","api/evennia.contrib.fieldfill","api/evennia.contrib.gendersub","api/evennia.contrib.health_bar","api/evennia.contrib.ingame_python","api/evennia.contrib.ingame_python.callbackhandler","api/evennia.contrib.ingame_python.commands","api/evennia.contrib.ingame_python.eventfuncs","api/evennia.contrib.ingame_python.scripts","api/evennia.contrib.ingame_python.tests","api/evennia.contrib.ingame_python.typeclasses","api/evennia.contrib.ingame_python.utils","api/evennia.contrib.mail","api/evennia.contrib.mapbuilder","api/evennia.contrib.menu_login","api/evennia.contrib.multidescer","api/evennia.contrib.puzzles","api/evennia.contrib.random_string_generator","api/evennia.contrib.rplanguage","api/evennia.contrib.rpsystem","api/evennia.contrib.security","api/evennia.contrib.security.auditing","api/evennia.contrib.security.auditing.outputs","api/evennia.contrib.security.auditing.server","api/evennia.contrib.security.auditing.tests","api/evennia.contrib.simpledoor","api/evennia.contrib.slow_exit","api/evennia.contrib.talking_npc","api/evennia.contrib.tree_select","api/evennia.contrib.turnbattle","api/evennia.contrib.turnbattle.tb_basic","api/evennia.contrib.turnbattle.tb_equip","api/evennia.contrib.turnbattle.tb_items","api/evennia.contrib.turnbattle.tb_magic","api/evennia.contrib.turnbattle.tb_range","api/evennia.contrib.tutorial_examples","api/evennia.contrib.tutorial_examples.bodyfunctions","api/evennia.contrib.tutorial_examples.cmdset_red_button","api/evennia.contrib.tutorial_examples.example_batch_code","api/evennia.contrib.tutorial_examples.red_button","api/evennia.contrib.tutorial_examples.red_button_scripts","api/evennia.contrib.tutorial_examples.tests","api/evennia.contrib.tutorial_world","api/evennia.contrib.tutorial_world.intro_menu","api/evennia.contrib.tutorial_world.mob","api/evennia.contrib.tutorial_world.objects","api/evennia.contrib.tutorial_world.rooms","api/evennia.contrib.unixcommand","api/evennia.contrib.wilderness","api/evennia.help","api/evennia.help.admin","api/evennia.help.manager","api/evennia.help.models","api/evennia.locks","api/evennia.locks.lockfuncs","api/evennia.locks.lockhandler","api/evennia.objects","api/evennia.objects.admin","api/evennia.objects.manager","api/evennia.objects.models","api/evennia.objects.objects","api/evennia.prototypes","api/evennia.prototypes.menus","api/evennia.prototypes.protfuncs","api/evennia.prototypes.prototypes","api/evennia.prototypes.spawner","api/evennia.scripts","api/evennia.scripts.admin","api/evennia.scripts.manager","api/evennia.scripts.models","api/evennia.scripts.monitorhandler","api/evennia.scripts.scripthandler","api/evennia.scripts.scripts","api/evennia.scripts.taskhandler","api/evennia.scripts.tickerhandler","api/evennia.server","api/evennia.server.admin","api/evennia.server.amp_client","api/evennia.server.connection_wizard","api/evennia.server.deprecations","api/evennia.server.evennia_launcher","api/evennia.server.game_index_client","api/evennia.server.game_index_client.client","api/evennia.server.game_index_client.service","api/evennia.server.initial_setup","api/evennia.server.inputfuncs","api/evennia.server.manager","api/evennia.server.models","api/evennia.server.portal","api/evennia.server.portal.amp","api/evennia.server.portal.amp_server","api/evennia.server.portal.grapevine","api/evennia.server.portal.irc","api/evennia.server.portal.mccp","api/evennia.server.portal.mssp","api/evennia.server.portal.mxp","api/evennia.server.portal.naws","api/evennia.server.portal.portal","api/evennia.server.portal.portalsessionhandler","api/evennia.server.portal.rss","api/evennia.server.portal.ssh","api/evennia.server.portal.ssl","api/evennia.server.portal.suppress_ga","api/evennia.server.portal.telnet","api/evennia.server.portal.telnet_oob","api/evennia.server.portal.telnet_ssl","api/evennia.server.portal.tests","api/evennia.server.portal.ttype","api/evennia.server.portal.webclient","api/evennia.server.portal.webclient_ajax","api/evennia.server.profiling","api/evennia.server.profiling.dummyrunner","api/evennia.server.profiling.dummyrunner_settings","api/evennia.server.profiling.memplot","api/evennia.server.profiling.settings_mixin","api/evennia.server.profiling.test_queries","api/evennia.server.profiling.tests","api/evennia.server.profiling.timetrace","api/evennia.server.server","api/evennia.server.serversession","api/evennia.server.session","api/evennia.server.sessionhandler","api/evennia.server.signals","api/evennia.server.throttle","api/evennia.server.validators","api/evennia.server.webserver","api/evennia.settings_default","api/evennia.typeclasses","api/evennia.typeclasses.admin","api/evennia.typeclasses.attributes","api/evennia.typeclasses.managers","api/evennia.typeclasses.models","api/evennia.typeclasses.tags","api/evennia.utils","api/evennia.utils.ansi","api/evennia.utils.batchprocessors","api/evennia.utils.containers","api/evennia.utils.create","api/evennia.utils.dbserialize","api/evennia.utils.eveditor","api/evennia.utils.evform","api/evennia.utils.evmenu","api/evennia.utils.evmore","api/evennia.utils.evtable","api/evennia.utils.gametime","api/evennia.utils.idmapper","api/evennia.utils.idmapper.manager","api/evennia.utils.idmapper.models","api/evennia.utils.idmapper.tests","api/evennia.utils.inlinefuncs","api/evennia.utils.logger","api/evennia.utils.optionclasses","api/evennia.utils.optionhandler","api/evennia.utils.picklefield","api/evennia.utils.search","api/evennia.utils.test_resources","api/evennia.utils.text2html","api/evennia.utils.utils","api/evennia.utils.validatorfuncs","api/evennia.web","api/evennia.web.urls","api/evennia.web.utils","api/evennia.web.utils.backends","api/evennia.web.utils.general_context","api/evennia.web.utils.middleware","api/evennia.web.utils.tests","api/evennia.web.webclient","api/evennia.web.webclient.urls","api/evennia.web.webclient.views","api/evennia.web.website","api/evennia.web.website.forms","api/evennia.web.website.templatetags","api/evennia.web.website.templatetags.addclass","api/evennia.web.website.tests","api/evennia.web.website.urls","api/evennia.web.website.views","index","toc"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["A-voice-operated-elevator-using-events.md","API-refactoring.md","Accounts.md","Add-a-simple-new-web-page.md","Add-a-wiki-on-your-website.md","Adding-Command-Tutorial.md","Adding-Object-Typeclass-Tutorial.md","Administrative-Docs.md","Apache-Config.md","Arxcode-installing-help.md","Async-Process.md","Attributes.md","Banning.md","Batch-Code-Processor.md","Batch-Command-Processor.md","Batch-Processors.md","Bootstrap-&-Evennia.md","Bootstrap-Components-and-Utilities.md","Builder-Docs.md","Building-Permissions.md","Building-Quickstart.md","Building-a-mech-tutorial.md","Building-menus.md","Choosing-An-SQL-Server.md","Client-Support-Grid.md","Coding-FAQ.md","Coding-Introduction.md","Coding-Utils.md","Command-Cooldown.md","Command-Duration.md","Command-Prompt.md","Command-Sets.md","Command-System.md","Commands.md","Communications.md","Connection-Screen.md","Continuous-Integration.md","Contributing.md","Contributing-Docs.md","Coordinates.md","Custom-Protocols.md","Customize-channels.md","Debugging.md","Default-Commands.md","Default-Exit-Errors.md","Developer-Central.md","Dialogues-in-events.md","Directory-Overview.md","Docs-refactoring.md","Dynamic-In-Game-Map.md","EvEditor.md","EvMenu.md","EvMore.md","Evennia-API.md","Evennia-Game-Index.md","Evennia-Introduction.md","Evennia-for-Diku-Users.md","Evennia-for-MUSH-Users.md","Evennia-for-roleplaying-sessions.md","Execute-Python-Code.md","First-Steps-Coding.md","Game-Planning.md","Gametime-Tutorial.md","Getting-Started.md","Glossary.md","Grapevine.md","Guest-Logins.md","HAProxy-Config.md","Help-System.md","Help-System-Tutorial.md","How-To-Get-And-Give-Help.md","How-to-connect-Evennia-to-Twitter.md","IRC.md","Implementing-a-game-rule-system.md","Inputfuncs.md","Installing-on-Android.md","Internationalization.md","Learn-Python-for-Evennia-The-Hard-Way.md","Licensing.md","Links.md","Locks.md","Manually-Configuring-Color.md","Mass-and-weight-for-objects.md","Messagepath.md","MonitorHandler.md","NPC-shop-Tutorial.md","New-Models.md","Nicks.md","OOB.md","Objects.md","Online-Setup.md","Parsing-command-arguments,-theory-and-best-practices.md","Portal-And-Server.md","Profiling.md","Python-3.md","Python-basic-introduction.md","Python-basic-tutorial-part-two.md","Quirks.md","RSS.md","Roadmap.md","Running-Evennia-in-Docker.md","Screenshot.md","Scripts.md","Security.md","Server-Conf.md","Sessions.md","Setting-up-PyCharm.md","Signals.md","Soft-Code.md","Spawner-and-Prototypes.md","Start-Stop-Reload.md","Static-In-Game-Map.md","Tags.md","Text-Encodings.md","TextTags.md","TickerHandler.md","Turn-based-Combat-System.md","Tutorial-Aggressive-NPCs.md","Tutorial-NPCs-listening.md","Tutorial-Searching-For-Objects.md","Tutorial-Tweeting-Game-Stats.md","Tutorial-Vehicles.md","Tutorial-World-Introduction.md","Tutorial-for-basic-MUSH-like-game.md","Tutorials.md","Typeclasses.md","Understanding-Color-Tags.md","Unit-Testing.md","Updating-Your-Game.md","Using-MUX-as-a-Standard.md","Using-Travis.md","Version-Control.md","Weather-Tutorial.md","Web-Character-Generation.md","Web-Character-View-Tutorial.md","Web-Features.md","Web-Tutorial.md","Webclient.md","Webclient-brainstorm.md","Wiki-Index.md","Zones.md","api/evennia.md","api/evennia-api.md","api/evennia.accounts.md","api/evennia.accounts.accounts.md","api/evennia.accounts.admin.md","api/evennia.accounts.bots.md","api/evennia.accounts.manager.md","api/evennia.accounts.models.md","api/evennia.commands.md","api/evennia.commands.cmdhandler.md","api/evennia.commands.cmdparser.md","api/evennia.commands.cmdset.md","api/evennia.commands.cmdsethandler.md","api/evennia.commands.command.md","api/evennia.commands.default.md","api/evennia.commands.default.account.md","api/evennia.commands.default.admin.md","api/evennia.commands.default.batchprocess.md","api/evennia.commands.default.building.md","api/evennia.commands.default.cmdset_account.md","api/evennia.commands.default.cmdset_character.md","api/evennia.commands.default.cmdset_session.md","api/evennia.commands.default.cmdset_unloggedin.md","api/evennia.commands.default.comms.md","api/evennia.commands.default.general.md","api/evennia.commands.default.help.md","api/evennia.commands.default.muxcommand.md","api/evennia.commands.default.syscommands.md","api/evennia.commands.default.system.md","api/evennia.commands.default.tests.md","api/evennia.commands.default.unloggedin.md","api/evennia.comms.md","api/evennia.comms.admin.md","api/evennia.comms.channelhandler.md","api/evennia.comms.comms.md","api/evennia.comms.managers.md","api/evennia.comms.models.md","api/evennia.contrib.md","api/evennia.contrib.barter.md","api/evennia.contrib.building_menu.md","api/evennia.contrib.chargen.md","api/evennia.contrib.clothing.md","api/evennia.contrib.color_markups.md","api/evennia.contrib.custom_gametime.md","api/evennia.contrib.dice.md","api/evennia.contrib.email_login.md","api/evennia.contrib.extended_room.md","api/evennia.contrib.fieldfill.md","api/evennia.contrib.gendersub.md","api/evennia.contrib.health_bar.md","api/evennia.contrib.ingame_python.md","api/evennia.contrib.ingame_python.callbackhandler.md","api/evennia.contrib.ingame_python.commands.md","api/evennia.contrib.ingame_python.eventfuncs.md","api/evennia.contrib.ingame_python.scripts.md","api/evennia.contrib.ingame_python.tests.md","api/evennia.contrib.ingame_python.typeclasses.md","api/evennia.contrib.ingame_python.utils.md","api/evennia.contrib.mail.md","api/evennia.contrib.mapbuilder.md","api/evennia.contrib.menu_login.md","api/evennia.contrib.multidescer.md","api/evennia.contrib.puzzles.md","api/evennia.contrib.random_string_generator.md","api/evennia.contrib.rplanguage.md","api/evennia.contrib.rpsystem.md","api/evennia.contrib.security.md","api/evennia.contrib.security.auditing.md","api/evennia.contrib.security.auditing.outputs.md","api/evennia.contrib.security.auditing.server.md","api/evennia.contrib.security.auditing.tests.md","api/evennia.contrib.simpledoor.md","api/evennia.contrib.slow_exit.md","api/evennia.contrib.talking_npc.md","api/evennia.contrib.tree_select.md","api/evennia.contrib.turnbattle.md","api/evennia.contrib.turnbattle.tb_basic.md","api/evennia.contrib.turnbattle.tb_equip.md","api/evennia.contrib.turnbattle.tb_items.md","api/evennia.contrib.turnbattle.tb_magic.md","api/evennia.contrib.turnbattle.tb_range.md","api/evennia.contrib.tutorial_examples.md","api/evennia.contrib.tutorial_examples.bodyfunctions.md","api/evennia.contrib.tutorial_examples.cmdset_red_button.md","api/evennia.contrib.tutorial_examples.example_batch_code.md","api/evennia.contrib.tutorial_examples.red_button.md","api/evennia.contrib.tutorial_examples.red_button_scripts.md","api/evennia.contrib.tutorial_examples.tests.md","api/evennia.contrib.tutorial_world.md","api/evennia.contrib.tutorial_world.intro_menu.md","api/evennia.contrib.tutorial_world.mob.md","api/evennia.contrib.tutorial_world.objects.md","api/evennia.contrib.tutorial_world.rooms.md","api/evennia.contrib.unixcommand.md","api/evennia.contrib.wilderness.md","api/evennia.help.md","api/evennia.help.admin.md","api/evennia.help.manager.md","api/evennia.help.models.md","api/evennia.locks.md","api/evennia.locks.lockfuncs.md","api/evennia.locks.lockhandler.md","api/evennia.objects.md","api/evennia.objects.admin.md","api/evennia.objects.manager.md","api/evennia.objects.models.md","api/evennia.objects.objects.md","api/evennia.prototypes.md","api/evennia.prototypes.menus.md","api/evennia.prototypes.protfuncs.md","api/evennia.prototypes.prototypes.md","api/evennia.prototypes.spawner.md","api/evennia.scripts.md","api/evennia.scripts.admin.md","api/evennia.scripts.manager.md","api/evennia.scripts.models.md","api/evennia.scripts.monitorhandler.md","api/evennia.scripts.scripthandler.md","api/evennia.scripts.scripts.md","api/evennia.scripts.taskhandler.md","api/evennia.scripts.tickerhandler.md","api/evennia.server.md","api/evennia.server.admin.md","api/evennia.server.amp_client.md","api/evennia.server.connection_wizard.md","api/evennia.server.deprecations.md","api/evennia.server.evennia_launcher.md","api/evennia.server.game_index_client.md","api/evennia.server.game_index_client.client.md","api/evennia.server.game_index_client.service.md","api/evennia.server.initial_setup.md","api/evennia.server.inputfuncs.md","api/evennia.server.manager.md","api/evennia.server.models.md","api/evennia.server.portal.md","api/evennia.server.portal.amp.md","api/evennia.server.portal.amp_server.md","api/evennia.server.portal.grapevine.md","api/evennia.server.portal.irc.md","api/evennia.server.portal.mccp.md","api/evennia.server.portal.mssp.md","api/evennia.server.portal.mxp.md","api/evennia.server.portal.naws.md","api/evennia.server.portal.portal.md","api/evennia.server.portal.portalsessionhandler.md","api/evennia.server.portal.rss.md","api/evennia.server.portal.ssh.md","api/evennia.server.portal.ssl.md","api/evennia.server.portal.suppress_ga.md","api/evennia.server.portal.telnet.md","api/evennia.server.portal.telnet_oob.md","api/evennia.server.portal.telnet_ssl.md","api/evennia.server.portal.tests.md","api/evennia.server.portal.ttype.md","api/evennia.server.portal.webclient.md","api/evennia.server.portal.webclient_ajax.md","api/evennia.server.profiling.md","api/evennia.server.profiling.dummyrunner.md","api/evennia.server.profiling.dummyrunner_settings.md","api/evennia.server.profiling.memplot.md","api/evennia.server.profiling.settings_mixin.md","api/evennia.server.profiling.test_queries.md","api/evennia.server.profiling.tests.md","api/evennia.server.profiling.timetrace.md","api/evennia.server.server.md","api/evennia.server.serversession.md","api/evennia.server.session.md","api/evennia.server.sessionhandler.md","api/evennia.server.signals.md","api/evennia.server.throttle.md","api/evennia.server.validators.md","api/evennia.server.webserver.md","api/evennia.settings_default.md","api/evennia.typeclasses.md","api/evennia.typeclasses.admin.md","api/evennia.typeclasses.attributes.md","api/evennia.typeclasses.managers.md","api/evennia.typeclasses.models.md","api/evennia.typeclasses.tags.md","api/evennia.utils.md","api/evennia.utils.ansi.md","api/evennia.utils.batchprocessors.md","api/evennia.utils.containers.md","api/evennia.utils.create.md","api/evennia.utils.dbserialize.md","api/evennia.utils.eveditor.md","api/evennia.utils.evform.md","api/evennia.utils.evmenu.md","api/evennia.utils.evmore.md","api/evennia.utils.evtable.md","api/evennia.utils.gametime.md","api/evennia.utils.idmapper.md","api/evennia.utils.idmapper.manager.md","api/evennia.utils.idmapper.models.md","api/evennia.utils.idmapper.tests.md","api/evennia.utils.inlinefuncs.md","api/evennia.utils.logger.md","api/evennia.utils.optionclasses.md","api/evennia.utils.optionhandler.md","api/evennia.utils.picklefield.md","api/evennia.utils.search.md","api/evennia.utils.test_resources.md","api/evennia.utils.text2html.md","api/evennia.utils.utils.md","api/evennia.utils.validatorfuncs.md","api/evennia.web.md","api/evennia.web.urls.md","api/evennia.web.utils.md","api/evennia.web.utils.backends.md","api/evennia.web.utils.general_context.md","api/evennia.web.utils.middleware.md","api/evennia.web.utils.tests.md","api/evennia.web.webclient.md","api/evennia.web.webclient.urls.md","api/evennia.web.webclient.views.md","api/evennia.web.website.md","api/evennia.web.website.forms.md","api/evennia.web.website.templatetags.md","api/evennia.web.website.templatetags.addclass.md","api/evennia.web.website.tests.md","api/evennia.web.website.urls.md","api/evennia.web.website.views.md","index.md","toc.md"],objects:{"":{evennia:[141,0,0,"-"]},"evennia.accounts":{accounts:[144,0,0,"-"],admin:[145,0,0,"-"],bots:[146,0,0,"-"],manager:[147,0,0,"-"],models:[148,0,0,"-"]},"evennia.accounts.accounts":{DefaultAccount:[144,1,1,""],DefaultGuest:[144,1,1,""]},"evennia.accounts.accounts.DefaultAccount":{"delete":[144,3,1,""],DoesNotExist:[144,2,1,""],MultipleObjectsReturned:[144,2,1,""],access:[144,3,1,""],at_access:[144,3,1,""],at_account_creation:[144,3,1,""],at_cmdset_get:[144,3,1,""],at_disconnect:[144,3,1,""],at_failed_login:[144,3,1,""],at_first_login:[144,3,1,""],at_first_save:[144,3,1,""],at_init:[144,3,1,""],at_look:[144,3,1,""],at_msg_receive:[144,3,1,""],at_msg_send:[144,3,1,""],at_password_change:[144,3,1,""],at_post_disconnect:[144,3,1,""],at_post_login:[144,3,1,""],at_pre_login:[144,3,1,""],at_server_reload:[144,3,1,""],at_server_shutdown:[144,3,1,""],authenticate:[144,3,1,""],basetype_setup:[144,3,1,""],character:[144,3,1,""],characters:[144,3,1,""],cmdset:[144,4,1,""],connection_time:[144,3,1,""],create:[144,3,1,""],create_character:[144,3,1,""],disconnect_session_from_account:[144,3,1,""],execute_cmd:[144,3,1,""],get_all_puppets:[144,3,1,""],get_puppet:[144,3,1,""],get_username_validators:[144,3,1,""],idle_time:[144,3,1,""],is_banned:[144,3,1,""],msg:[144,3,1,""],nicks:[144,4,1,""],normalize_username:[144,3,1,""],objects:[144,4,1,""],options:[144,4,1,""],path:[144,4,1,""],puppet:[144,3,1,""],puppet_object:[144,3,1,""],scripts:[144,4,1,""],search:[144,3,1,""],sessions:[144,4,1,""],set_password:[144,3,1,""],typename:[144,4,1,""],unpuppet_all:[144,3,1,""],unpuppet_object:[144,3,1,""],validate_password:[144,3,1,""],validate_username:[144,3,1,""]},"evennia.accounts.accounts.DefaultGuest":{DoesNotExist:[144,2,1,""],MultipleObjectsReturned:[144,2,1,""],at_post_disconnect:[144,3,1,""],at_post_login:[144,3,1,""],at_server_shutdown:[144,3,1,""],authenticate:[144,3,1,""],create:[144,3,1,""],path:[144,4,1,""],typename:[144,4,1,""]},"evennia.accounts.admin":{AccountAttributeInline:[145,1,1,""],AccountDBAdmin:[145,1,1,""],AccountDBChangeForm:[145,1,1,""],AccountDBCreationForm:[145,1,1,""],AccountForm:[145,1,1,""],AccountInline:[145,1,1,""],AccountTagInline:[145,1,1,""]},"evennia.accounts.admin.AccountAttributeInline":{media:[145,3,1,""],model:[145,4,1,""],related_field:[145,4,1,""]},"evennia.accounts.admin.AccountDBAdmin":{add_fieldsets:[145,4,1,""],add_form:[145,4,1,""],fieldsets:[145,4,1,""],form:[145,4,1,""],inlines:[145,4,1,""],list_display:[145,4,1,""],media:[145,3,1,""],response_add:[145,3,1,""],save_model:[145,3,1,""],user_change_password:[145,3,1,""]},"evennia.accounts.admin.AccountDBChangeForm":{Meta:[145,1,1,""],base_fields:[145,4,1,""],clean_username:[145,3,1,""],declared_fields:[145,4,1,""],media:[145,3,1,""]},"evennia.accounts.admin.AccountDBChangeForm.Meta":{fields:[145,4,1,""],model:[145,4,1,""]},"evennia.accounts.admin.AccountDBCreationForm":{Meta:[145,1,1,""],base_fields:[145,4,1,""],clean_username:[145,3,1,""],declared_fields:[145,4,1,""],media:[145,3,1,""]},"evennia.accounts.admin.AccountDBCreationForm.Meta":{fields:[145,4,1,""],model:[145,4,1,""]},"evennia.accounts.admin.AccountForm":{Meta:[145,1,1,""],base_fields:[145,4,1,""],declared_fields:[145,4,1,""],media:[145,3,1,""]},"evennia.accounts.admin.AccountForm.Meta":{app_label:[145,4,1,""],fields:[145,4,1,""],model:[145,4,1,""]},"evennia.accounts.admin.AccountInline":{extra:[145,4,1,""],fieldsets:[145,4,1,""],form:[145,4,1,""],max_num:[145,4,1,""],media:[145,3,1,""],model:[145,4,1,""],template:[145,4,1,""]},"evennia.accounts.admin.AccountTagInline":{media:[145,3,1,""],model:[145,4,1,""],related_field:[145,4,1,""]},"evennia.accounts.bots":{Bot:[146,1,1,""],BotStarter:[146,1,1,""],GrapevineBot:[146,1,1,""],IRCBot:[146,1,1,""],RSSBot:[146,1,1,""]},"evennia.accounts.bots.Bot":{DoesNotExist:[146,2,1,""],MultipleObjectsReturned:[146,2,1,""],at_server_shutdown:[146,3,1,""],basetype_setup:[146,3,1,""],execute_cmd:[146,3,1,""],msg:[146,3,1,""],path:[146,4,1,""],start:[146,3,1,""],typename:[146,4,1,""]},"evennia.accounts.bots.BotStarter":{DoesNotExist:[146,2,1,""],MultipleObjectsReturned:[146,2,1,""],at_repeat:[146,3,1,""],at_script_creation:[146,3,1,""],at_server_reload:[146,3,1,""],at_server_shutdown:[146,3,1,""],at_start:[146,3,1,""],path:[146,4,1,""],typename:[146,4,1,""]},"evennia.accounts.bots.GrapevineBot":{DoesNotExist:[146,2,1,""],MultipleObjectsReturned:[146,2,1,""],at_msg_send:[146,3,1,""],execute_cmd:[146,3,1,""],factory_path:[146,4,1,""],msg:[146,3,1,""],path:[146,4,1,""],start:[146,3,1,""],typename:[146,4,1,""]},"evennia.accounts.bots.IRCBot":{DoesNotExist:[146,2,1,""],MultipleObjectsReturned:[146,2,1,""],at_msg_send:[146,3,1,""],execute_cmd:[146,3,1,""],factory_path:[146,4,1,""],get_nicklist:[146,3,1,""],msg:[146,3,1,""],path:[146,4,1,""],ping:[146,3,1,""],reconnect:[146,3,1,""],start:[146,3,1,""],typename:[146,4,1,""]},"evennia.accounts.bots.RSSBot":{DoesNotExist:[146,2,1,""],MultipleObjectsReturned:[146,2,1,""],execute_cmd:[146,3,1,""],path:[146,4,1,""],start:[146,3,1,""],typename:[146,4,1,""]},"evennia.accounts.manager":{AccountDBManager:[147,1,1,""],AccountManager:[147,1,1,""]},"evennia.accounts.manager.AccountDBManager":{account_search:[147,3,1,""],get_account_from_email:[147,3,1,""],get_account_from_name:[147,3,1,""],get_account_from_uid:[147,3,1,""],get_connected_accounts:[147,3,1,""],get_recently_connected_accounts:[147,3,1,""],get_recently_created_accounts:[147,3,1,""],num_total_accounts:[147,3,1,""],search_account:[147,3,1,""]},"evennia.accounts.models":{AccountDB:[148,1,1,""]},"evennia.accounts.models.AccountDB":{DoesNotExist:[148,2,1,""],MultipleObjectsReturned:[148,2,1,""],account_subscription_set:[148,4,1,""],cmdset_storage:[148,3,1,""],db_attributes:[148,4,1,""],db_cmdset_storage:[148,4,1,""],db_is_bot:[148,4,1,""],db_is_connected:[148,4,1,""],db_tags:[148,4,1,""],get_next_by_date_joined:[148,3,1,""],get_next_by_db_date_created:[148,3,1,""],get_previous_by_date_joined:[148,3,1,""],get_previous_by_db_date_created:[148,3,1,""],groups:[148,4,1,""],hide_from_accounts_set:[148,4,1,""],id:[148,4,1,""],is_bot:[148,3,1,""],is_connected:[148,3,1,""],key:[148,3,1,""],logentry_set:[148,4,1,""],name:[148,3,1,""],objectdb_set:[148,4,1,""],objects:[148,4,1,""],path:[148,4,1,""],receiver_account_set:[148,4,1,""],scriptdb_set:[148,4,1,""],sender_account_set:[148,4,1,""],typename:[148,4,1,""],uid:[148,3,1,""],user_permissions:[148,4,1,""]},"evennia.commands":{"default":[155,0,0,"-"],cmdhandler:[150,0,0,"-"],cmdparser:[151,0,0,"-"],cmdset:[152,0,0,"-"],cmdsethandler:[153,0,0,"-"],command:[154,0,0,"-"]},"evennia.commands.cmdhandler":{InterruptCommand:[150,2,1,""],cmdhandler:[150,5,1,""]},"evennia.commands.cmdparser":{build_matches:[151,5,1,""],cmdparser:[151,5,1,""],create_match:[151,5,1,""],try_num_prefixes:[151,5,1,""]},"evennia.commands.cmdset":{CmdSet:[152,1,1,""]},"evennia.commands.cmdset.CmdSet":{__init__:[152,3,1,""],add:[152,3,1,""],at_cmdset_creation:[152,3,1,""],count:[152,3,1,""],duplicates:[152,4,1,""],errmessage:[152,4,1,""],get:[152,3,1,""],get_all_cmd_keys_and_aliases:[152,3,1,""],get_system_cmds:[152,3,1,""],key:[152,4,1,""],key_mergetypes:[152,4,1,""],make_unique:[152,3,1,""],mergetype:[152,4,1,""],no_channels:[152,4,1,""],no_exits:[152,4,1,""],no_objs:[152,4,1,""],path:[152,4,1,""],permanent:[152,4,1,""],priority:[152,4,1,""],remove:[152,3,1,""],to_duplicate:[152,4,1,""]},"evennia.commands.cmdsethandler":{CmdSetHandler:[153,1,1,""],import_cmdset:[153,5,1,""]},"evennia.commands.cmdsethandler.CmdSetHandler":{"delete":[153,3,1,""],__init__:[153,3,1,""],add:[153,3,1,""],add_default:[153,3,1,""],all:[153,3,1,""],clear:[153,3,1,""],delete_default:[153,3,1,""],get:[153,3,1,""],has:[153,3,1,""],has_cmdset:[153,3,1,""],remove:[153,3,1,""],remove_default:[153,3,1,""],reset:[153,3,1,""],update:[153,3,1,""]},"evennia.commands.command":{Command:[154,1,1,""],CommandMeta:[154,1,1,""],InterruptCommand:[154,2,1,""]},"evennia.commands.command.Command":{__init__:[154,3,1,""],access:[154,3,1,""],aliases:[154,4,1,""],arg_regex:[154,4,1,""],at_post_cmd:[154,3,1,""],at_pre_cmd:[154,3,1,""],auto_help:[154,4,1,""],client_height:[154,3,1,""],client_width:[154,3,1,""],execute_cmd:[154,3,1,""],func:[154,3,1,""],get_command_info:[154,3,1,""],get_extra_info:[154,3,1,""],get_help:[154,3,1,""],help_category:[154,4,1,""],is_exit:[154,4,1,""],key:[154,4,1,""],lock_storage:[154,4,1,""],lockhandler:[154,4,1,""],locks:[154,4,1,""],match:[154,3,1,""],msg:[154,3,1,""],msg_all_sessions:[154,4,1,""],parse:[154,3,1,""],save_for_next:[154,4,1,""],set_aliases:[154,3,1,""],set_key:[154,3,1,""],styled_footer:[154,3,1,""],styled_header:[154,3,1,""],styled_separator:[154,3,1,""],styled_table:[154,3,1,""]},"evennia.commands.command.CommandMeta":{__init__:[154,3,1,""]},"evennia.commands.default":{account:[156,0,0,"-"],admin:[157,0,0,"-"],batchprocess:[158,0,0,"-"],building:[159,0,0,"-"],cmdset_account:[160,0,0,"-"],cmdset_character:[161,0,0,"-"],cmdset_session:[162,0,0,"-"],cmdset_unloggedin:[163,0,0,"-"],comms:[164,0,0,"-"],general:[165,0,0,"-"],help:[166,0,0,"-"],muxcommand:[167,0,0,"-"],syscommands:[168,0,0,"-"],system:[169,0,0,"-"],unloggedin:[171,0,0,"-"]},"evennia.commands.default.account":{CmdCharCreate:[156,1,1,""],CmdCharDelete:[156,1,1,""],CmdColorTest:[156,1,1,""],CmdIC:[156,1,1,""],CmdOOC:[156,1,1,""],CmdOOCLook:[156,1,1,""],CmdOption:[156,1,1,""],CmdPassword:[156,1,1,""],CmdQuell:[156,1,1,""],CmdQuit:[156,1,1,""],CmdSessions:[156,1,1,""],CmdStyle:[156,1,1,""],CmdWho:[156,1,1,""]},"evennia.commands.default.account.CmdCharCreate":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.account.CmdCharDelete":{aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.account.CmdColorTest":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""],slice_bright_bg:[156,4,1,""],slice_bright_fg:[156,4,1,""],slice_dark_bg:[156,4,1,""],slice_dark_fg:[156,4,1,""],table_format:[156,3,1,""]},"evennia.commands.default.account.CmdIC":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.account.CmdOOC":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.account.CmdOOCLook":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.account.CmdOption":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""],switch_options:[156,4,1,""]},"evennia.commands.default.account.CmdPassword":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.account.CmdQuell":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.account.CmdQuit":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""],switch_options:[156,4,1,""]},"evennia.commands.default.account.CmdSessions":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.account.CmdStyle":{aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],list_styles:[156,3,1,""],lock_storage:[156,4,1,""],set:[156,3,1,""],switch_options:[156,4,1,""]},"evennia.commands.default.account.CmdWho":{account_caller:[156,4,1,""],aliases:[156,4,1,""],func:[156,3,1,""],help_category:[156,4,1,""],key:[156,4,1,""],lock_storage:[156,4,1,""],locks:[156,4,1,""]},"evennia.commands.default.admin":{CmdBan:[157,1,1,""],CmdBoot:[157,1,1,""],CmdEmit:[157,1,1,""],CmdForce:[157,1,1,""],CmdNewPassword:[157,1,1,""],CmdPerm:[157,1,1,""],CmdUnban:[157,1,1,""],CmdWall:[157,1,1,""]},"evennia.commands.default.admin.CmdBan":{aliases:[157,4,1,""],func:[157,3,1,""],help_category:[157,4,1,""],key:[157,4,1,""],lock_storage:[157,4,1,""],locks:[157,4,1,""]},"evennia.commands.default.admin.CmdBoot":{aliases:[157,4,1,""],func:[157,3,1,""],help_category:[157,4,1,""],key:[157,4,1,""],lock_storage:[157,4,1,""],locks:[157,4,1,""],switch_options:[157,4,1,""]},"evennia.commands.default.admin.CmdEmit":{aliases:[157,4,1,""],func:[157,3,1,""],help_category:[157,4,1,""],key:[157,4,1,""],lock_storage:[157,4,1,""],locks:[157,4,1,""],switch_options:[157,4,1,""]},"evennia.commands.default.admin.CmdForce":{aliases:[157,4,1,""],func:[157,3,1,""],help_category:[157,4,1,""],key:[157,4,1,""],lock_storage:[157,4,1,""],locks:[157,4,1,""],perm_used:[157,4,1,""]},"evennia.commands.default.admin.CmdNewPassword":{aliases:[157,4,1,""],func:[157,3,1,""],help_category:[157,4,1,""],key:[157,4,1,""],lock_storage:[157,4,1,""],locks:[157,4,1,""]},"evennia.commands.default.admin.CmdPerm":{aliases:[157,4,1,""],func:[157,3,1,""],help_category:[157,4,1,""],key:[157,4,1,""],lock_storage:[157,4,1,""],locks:[157,4,1,""],switch_options:[157,4,1,""]},"evennia.commands.default.admin.CmdUnban":{aliases:[157,4,1,""],func:[157,3,1,""],help_category:[157,4,1,""],key:[157,4,1,""],lock_storage:[157,4,1,""],locks:[157,4,1,""]},"evennia.commands.default.admin.CmdWall":{aliases:[157,4,1,""],func:[157,3,1,""],help_category:[157,4,1,""],key:[157,4,1,""],lock_storage:[157,4,1,""],locks:[157,4,1,""]},"evennia.commands.default.batchprocess":{CmdBatchCode:[158,1,1,""],CmdBatchCommands:[158,1,1,""]},"evennia.commands.default.batchprocess.CmdBatchCode":{aliases:[158,4,1,""],func:[158,3,1,""],help_category:[158,4,1,""],key:[158,4,1,""],lock_storage:[158,4,1,""],locks:[158,4,1,""],switch_options:[158,4,1,""]},"evennia.commands.default.batchprocess.CmdBatchCommands":{aliases:[158,4,1,""],func:[158,3,1,""],help_category:[158,4,1,""],key:[158,4,1,""],lock_storage:[158,4,1,""],locks:[158,4,1,""],switch_options:[158,4,1,""]},"evennia.commands.default.building":{CmdCopy:[159,1,1,""],CmdCpAttr:[159,1,1,""],CmdCreate:[159,1,1,""],CmdDesc:[159,1,1,""],CmdDestroy:[159,1,1,""],CmdDig:[159,1,1,""],CmdExamine:[159,1,1,""],CmdFind:[159,1,1,""],CmdLink:[159,1,1,""],CmdListCmdSets:[159,1,1,""],CmdLock:[159,1,1,""],CmdMvAttr:[159,1,1,""],CmdName:[159,1,1,""],CmdOpen:[159,1,1,""],CmdScript:[159,1,1,""],CmdSetAttribute:[159,1,1,""],CmdSetHome:[159,1,1,""],CmdSetObjAlias:[159,1,1,""],CmdSpawn:[159,1,1,""],CmdTag:[159,1,1,""],CmdTeleport:[159,1,1,""],CmdTunnel:[159,1,1,""],CmdTypeclass:[159,1,1,""],CmdUnLink:[159,1,1,""],CmdWipe:[159,1,1,""],ObjManipCommand:[159,1,1,""]},"evennia.commands.default.building.CmdCopy":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""]},"evennia.commands.default.building.CmdCpAttr":{aliases:[159,4,1,""],check_from_attr:[159,3,1,""],check_has_attr:[159,3,1,""],check_to_attr:[159,3,1,""],func:[159,3,1,""],get_attr:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdCreate":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],new_obj_lockstring:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdDesc":{aliases:[159,4,1,""],edit_handler:[159,3,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdDestroy":{aliases:[159,4,1,""],confirm:[159,4,1,""],default_confirm:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdDig":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],new_room_lockstring:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdExamine":{account_mode:[159,4,1,""],aliases:[159,4,1,""],arg_regex:[159,4,1,""],detail_color:[159,4,1,""],format_attributes:[159,3,1,""],format_output:[159,3,1,""],func:[159,3,1,""],header_color:[159,4,1,""],help_category:[159,4,1,""],key:[159,4,1,""],list_attribute:[159,3,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],quell_color:[159,4,1,""],separator:[159,4,1,""]},"evennia.commands.default.building.CmdFind":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdLink":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""]},"evennia.commands.default.building.CmdListCmdSets":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""]},"evennia.commands.default.building.CmdLock":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""]},"evennia.commands.default.building.CmdMvAttr":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdName":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""]},"evennia.commands.default.building.CmdOpen":{aliases:[159,4,1,""],create_exit:[159,3,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],new_obj_lockstring:[159,4,1,""]},"evennia.commands.default.building.CmdScript":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdSetAttribute":{aliases:[159,4,1,""],check_attr:[159,3,1,""],check_obj:[159,3,1,""],do_nested_lookup:[159,3,1,""],edit_handler:[159,3,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],nested_re:[159,4,1,""],not_found:[159,4,1,""],rm_attr:[159,3,1,""],search_for_obj:[159,3,1,""],set_attr:[159,3,1,""],split_nested_attr:[159,3,1,""],view_attr:[159,3,1,""]},"evennia.commands.default.building.CmdSetHome":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""]},"evennia.commands.default.building.CmdSetObjAlias":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdSpawn":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdTag":{aliases:[159,4,1,""],arg_regex:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],options:[159,4,1,""]},"evennia.commands.default.building.CmdTeleport":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],rhs_split:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdTunnel":{aliases:[159,4,1,""],directions:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdTypeclass":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""],switch_options:[159,4,1,""]},"evennia.commands.default.building.CmdUnLink":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],help_key:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""]},"evennia.commands.default.building.CmdWipe":{aliases:[159,4,1,""],func:[159,3,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],locks:[159,4,1,""]},"evennia.commands.default.building.ObjManipCommand":{aliases:[159,4,1,""],help_category:[159,4,1,""],key:[159,4,1,""],lock_storage:[159,4,1,""],parse:[159,3,1,""]},"evennia.commands.default.cmdset_account":{AccountCmdSet:[160,1,1,""]},"evennia.commands.default.cmdset_account.AccountCmdSet":{at_cmdset_creation:[160,3,1,""],key:[160,4,1,""],path:[160,4,1,""],priority:[160,4,1,""]},"evennia.commands.default.cmdset_character":{CharacterCmdSet:[161,1,1,""]},"evennia.commands.default.cmdset_character.CharacterCmdSet":{at_cmdset_creation:[161,3,1,""],key:[161,4,1,""],path:[161,4,1,""],priority:[161,4,1,""]},"evennia.commands.default.cmdset_session":{SessionCmdSet:[162,1,1,""]},"evennia.commands.default.cmdset_session.SessionCmdSet":{at_cmdset_creation:[162,3,1,""],key:[162,4,1,""],path:[162,4,1,""],priority:[162,4,1,""]},"evennia.commands.default.cmdset_unloggedin":{UnloggedinCmdSet:[163,1,1,""]},"evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet":{at_cmdset_creation:[163,3,1,""],key:[163,4,1,""],path:[163,4,1,""],priority:[163,4,1,""]},"evennia.commands.default.comms":{CmdAddCom:[164,1,1,""],CmdAllCom:[164,1,1,""],CmdCBoot:[164,1,1,""],CmdCWho:[164,1,1,""],CmdCdesc:[164,1,1,""],CmdCdestroy:[164,1,1,""],CmdCemit:[164,1,1,""],CmdChannelCreate:[164,1,1,""],CmdChannels:[164,1,1,""],CmdClock:[164,1,1,""],CmdDelCom:[164,1,1,""],CmdGrapevine2Chan:[164,1,1,""],CmdIRC2Chan:[164,1,1,""],CmdIRCStatus:[164,1,1,""],CmdPage:[164,1,1,""],CmdRSS2Chan:[164,1,1,""]},"evennia.commands.default.comms.CmdAddCom":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdAllCom":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdCBoot":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""],switch_options:[164,4,1,""]},"evennia.commands.default.comms.CmdCWho":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdCdesc":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdCdestroy":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdCemit":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""],switch_options:[164,4,1,""]},"evennia.commands.default.comms.CmdChannelCreate":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdChannels":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdClock":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdDelCom":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdGrapevine2Chan":{aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""],switch_options:[164,4,1,""]},"evennia.commands.default.comms.CmdIRC2Chan":{aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""],switch_options:[164,4,1,""]},"evennia.commands.default.comms.CmdIRCStatus":{aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""]},"evennia.commands.default.comms.CmdPage":{account_caller:[164,4,1,""],aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""],switch_options:[164,4,1,""]},"evennia.commands.default.comms.CmdRSS2Chan":{aliases:[164,4,1,""],func:[164,3,1,""],help_category:[164,4,1,""],key:[164,4,1,""],lock_storage:[164,4,1,""],locks:[164,4,1,""],switch_options:[164,4,1,""]},"evennia.commands.default.general":{CmdAccess:[165,1,1,""],CmdDrop:[165,1,1,""],CmdGet:[165,1,1,""],CmdGive:[165,1,1,""],CmdHome:[165,1,1,""],CmdInventory:[165,1,1,""],CmdLook:[165,1,1,""],CmdNick:[165,1,1,""],CmdPose:[165,1,1,""],CmdSay:[165,1,1,""],CmdSetDesc:[165,1,1,""],CmdWhisper:[165,1,1,""]},"evennia.commands.default.general.CmdAccess":{aliases:[165,4,1,""],arg_regex:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.general.CmdDrop":{aliases:[165,4,1,""],arg_regex:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.general.CmdGet":{aliases:[165,4,1,""],arg_regex:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.general.CmdGive":{aliases:[165,4,1,""],arg_regex:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""],rhs_split:[165,4,1,""]},"evennia.commands.default.general.CmdHome":{aliases:[165,4,1,""],arg_regex:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.general.CmdInventory":{aliases:[165,4,1,""],arg_regex:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.general.CmdLook":{aliases:[165,4,1,""],arg_regex:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.general.CmdNick":{aliases:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""],parse:[165,3,1,""],switch_options:[165,4,1,""]},"evennia.commands.default.general.CmdPose":{aliases:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""],parse:[165,3,1,""]},"evennia.commands.default.general.CmdSay":{aliases:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.general.CmdSetDesc":{aliases:[165,4,1,""],arg_regex:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.general.CmdWhisper":{aliases:[165,4,1,""],func:[165,3,1,""],help_category:[165,4,1,""],key:[165,4,1,""],lock_storage:[165,4,1,""],locks:[165,4,1,""]},"evennia.commands.default.help":{CmdHelp:[166,1,1,""],CmdSetHelp:[166,1,1,""]},"evennia.commands.default.help.CmdHelp":{aliases:[166,4,1,""],arg_regex:[166,4,1,""],check_show_help:[166,3,1,""],format_help_entry:[166,3,1,""],format_help_list:[166,3,1,""],func:[166,3,1,""],help_category:[166,4,1,""],help_more:[166,4,1,""],key:[166,4,1,""],lock_storage:[166,4,1,""],locks:[166,4,1,""],msg_help:[166,3,1,""],parse:[166,3,1,""],return_cmdset:[166,4,1,""],should_list_cmd:[166,3,1,""],suggestion_cutoff:[166,4,1,""],suggestion_maxnum:[166,4,1,""]},"evennia.commands.default.help.CmdSetHelp":{aliases:[166,4,1,""],func:[166,3,1,""],help_category:[166,4,1,""],key:[166,4,1,""],lock_storage:[166,4,1,""],locks:[166,4,1,""],switch_options:[166,4,1,""]},"evennia.commands.default.muxcommand":{MuxAccountCommand:[167,1,1,""],MuxCommand:[167,1,1,""]},"evennia.commands.default.muxcommand.MuxAccountCommand":{account_caller:[167,4,1,""],aliases:[167,4,1,""],help_category:[167,4,1,""],key:[167,4,1,""],lock_storage:[167,4,1,""]},"evennia.commands.default.muxcommand.MuxCommand":{aliases:[167,4,1,""],at_post_cmd:[167,3,1,""],at_pre_cmd:[167,3,1,""],func:[167,3,1,""],get_command_info:[167,3,1,""],has_perm:[167,3,1,""],help_category:[167,4,1,""],key:[167,4,1,""],lock_storage:[167,4,1,""],parse:[167,3,1,""]},"evennia.commands.default.syscommands":{SystemMultimatch:[168,1,1,""],SystemNoInput:[168,1,1,""],SystemNoMatch:[168,1,1,""],SystemSendToChannel:[168,1,1,""]},"evennia.commands.default.syscommands.SystemMultimatch":{aliases:[168,4,1,""],func:[168,3,1,""],help_category:[168,4,1,""],key:[168,4,1,""],lock_storage:[168,4,1,""],locks:[168,4,1,""]},"evennia.commands.default.syscommands.SystemNoInput":{aliases:[168,4,1,""],func:[168,3,1,""],help_category:[168,4,1,""],key:[168,4,1,""],lock_storage:[168,4,1,""],locks:[168,4,1,""]},"evennia.commands.default.syscommands.SystemNoMatch":{aliases:[168,4,1,""],func:[168,3,1,""],help_category:[168,4,1,""],key:[168,4,1,""],lock_storage:[168,4,1,""],locks:[168,4,1,""]},"evennia.commands.default.syscommands.SystemSendToChannel":{aliases:[168,4,1,""],func:[168,3,1,""],help_category:[168,4,1,""],key:[168,4,1,""],lock_storage:[168,4,1,""],locks:[168,4,1,""],parse:[168,3,1,""]},"evennia.commands.default.system":{CmdAbout:[169,1,1,""],CmdAccounts:[169,1,1,""],CmdObjects:[169,1,1,""],CmdPy:[169,1,1,""],CmdReload:[169,1,1,""],CmdReset:[169,1,1,""],CmdScripts:[169,1,1,""],CmdServerLoad:[169,1,1,""],CmdService:[169,1,1,""],CmdShutdown:[169,1,1,""],CmdTickers:[169,1,1,""],CmdTime:[169,1,1,""]},"evennia.commands.default.system.CmdAbout":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""]},"evennia.commands.default.system.CmdAccounts":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""],switch_options:[169,4,1,""]},"evennia.commands.default.system.CmdObjects":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""]},"evennia.commands.default.system.CmdPy":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""],switch_options:[169,4,1,""]},"evennia.commands.default.system.CmdReload":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""]},"evennia.commands.default.system.CmdReset":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""]},"evennia.commands.default.system.CmdScripts":{aliases:[169,4,1,""],excluded_typeclass_paths:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""],switch_options:[169,4,1,""]},"evennia.commands.default.system.CmdServerLoad":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""],switch_options:[169,4,1,""]},"evennia.commands.default.system.CmdService":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""],switch_options:[169,4,1,""]},"evennia.commands.default.system.CmdShutdown":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""]},"evennia.commands.default.system.CmdTickers":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""]},"evennia.commands.default.system.CmdTime":{aliases:[169,4,1,""],func:[169,3,1,""],help_category:[169,4,1,""],key:[169,4,1,""],lock_storage:[169,4,1,""],locks:[169,4,1,""]},"evennia.commands.default.tests":{CmdInterrupt:[170,1,1,""],CommandTest:[170,1,1,""],TestAccount:[170,1,1,""],TestAdmin:[170,1,1,""],TestBatchProcess:[170,1,1,""],TestBuilding:[170,1,1,""],TestComms:[170,1,1,""],TestGeneral:[170,1,1,""],TestHelp:[170,1,1,""],TestInterruptCommand:[170,1,1,""],TestSystem:[170,1,1,""],TestSystemCommands:[170,1,1,""],TestUnconnectedCommand:[170,1,1,""]},"evennia.commands.default.tests.CmdInterrupt":{aliases:[170,4,1,""],func:[170,3,1,""],help_category:[170,4,1,""],key:[170,4,1,""],lock_storage:[170,4,1,""],parse:[170,3,1,""]},"evennia.commands.default.tests.CommandTest":{call:[170,3,1,""]},"evennia.commands.default.tests.TestAccount":{test_char_create:[170,3,1,""],test_char_delete:[170,3,1,""],test_color_test:[170,3,1,""],test_ic:[170,3,1,""],test_ic__nonaccess:[170,3,1,""],test_ic__other_object:[170,3,1,""],test_ooc:[170,3,1,""],test_ooc_look:[170,3,1,""],test_option:[170,3,1,""],test_password:[170,3,1,""],test_quell:[170,3,1,""],test_quit:[170,3,1,""],test_sessions:[170,3,1,""],test_who:[170,3,1,""]},"evennia.commands.default.tests.TestAdmin":{test_ban:[170,3,1,""],test_emit:[170,3,1,""],test_force:[170,3,1,""],test_perm:[170,3,1,""],test_wall:[170,3,1,""]},"evennia.commands.default.tests.TestBatchProcess":{test_batch_commands:[170,3,1,""]},"evennia.commands.default.tests.TestBuilding":{test_attribute_commands:[170,3,1,""],test_copy:[170,3,1,""],test_create:[170,3,1,""],test_desc:[170,3,1,""],test_desc_default_to_room:[170,3,1,""],test_destroy:[170,3,1,""],test_destroy_sequence:[170,3,1,""],test_dig:[170,3,1,""],test_do_nested_lookup:[170,3,1,""],test_empty_desc:[170,3,1,""],test_examine:[170,3,1,""],test_exit_commands:[170,3,1,""],test_find:[170,3,1,""],test_list_cmdsets:[170,3,1,""],test_lock:[170,3,1,""],test_name:[170,3,1,""],test_nested_attribute_commands:[170,3,1,""],test_script:[170,3,1,""],test_set_home:[170,3,1,""],test_set_obj_alias:[170,3,1,""],test_spawn:[170,3,1,""],test_split_nested_attr:[170,3,1,""],test_tag:[170,3,1,""],test_teleport:[170,3,1,""],test_tunnel:[170,3,1,""],test_tunnel_exit_typeclass:[170,3,1,""],test_typeclass:[170,3,1,""]},"evennia.commands.default.tests.TestComms":{setUp:[170,3,1,""],test_all_com:[170,3,1,""],test_cboot:[170,3,1,""],test_cdesc:[170,3,1,""],test_cdestroy:[170,3,1,""],test_cemit:[170,3,1,""],test_channels:[170,3,1,""],test_clock:[170,3,1,""],test_cwho:[170,3,1,""],test_page:[170,3,1,""],test_toggle_com:[170,3,1,""]},"evennia.commands.default.tests.TestGeneral":{test_access:[170,3,1,""],test_get_and_drop:[170,3,1,""],test_give:[170,3,1,""],test_home:[170,3,1,""],test_inventory:[170,3,1,""],test_look:[170,3,1,""],test_mux_command:[170,3,1,""],test_nick:[170,3,1,""],test_pose:[170,3,1,""],test_say:[170,3,1,""],test_whisper:[170,3,1,""]},"evennia.commands.default.tests.TestHelp":{test_help:[170,3,1,""],test_set_help:[170,3,1,""]},"evennia.commands.default.tests.TestInterruptCommand":{test_interrupt_command:[170,3,1,""]},"evennia.commands.default.tests.TestSystem":{test_about:[170,3,1,""],test_objects:[170,3,1,""],test_py:[170,3,1,""],test_scripts:[170,3,1,""],test_server_load:[170,3,1,""]},"evennia.commands.default.tests.TestSystemCommands":{test_channelcommand:[170,3,1,""],test_multimatch:[170,3,1,""],test_simple_defaults:[170,3,1,""]},"evennia.commands.default.tests.TestUnconnectedCommand":{test_info_command:[170,3,1,""]},"evennia.commands.default.unloggedin":{CmdUnconnectedConnect:[171,1,1,""],CmdUnconnectedCreate:[171,1,1,""],CmdUnconnectedEncoding:[171,1,1,""],CmdUnconnectedHelp:[171,1,1,""],CmdUnconnectedInfo:[171,1,1,""],CmdUnconnectedLook:[171,1,1,""],CmdUnconnectedQuit:[171,1,1,""],CmdUnconnectedScreenreader:[171,1,1,""]},"evennia.commands.default.unloggedin.CmdUnconnectedConnect":{aliases:[171,4,1,""],arg_regex:[171,4,1,""],func:[171,3,1,""],help_category:[171,4,1,""],key:[171,4,1,""],lock_storage:[171,4,1,""],locks:[171,4,1,""]},"evennia.commands.default.unloggedin.CmdUnconnectedCreate":{aliases:[171,4,1,""],arg_regex:[171,4,1,""],func:[171,3,1,""],help_category:[171,4,1,""],key:[171,4,1,""],lock_storage:[171,4,1,""],locks:[171,4,1,""]},"evennia.commands.default.unloggedin.CmdUnconnectedEncoding":{aliases:[171,4,1,""],func:[171,3,1,""],help_category:[171,4,1,""],key:[171,4,1,""],lock_storage:[171,4,1,""],locks:[171,4,1,""]},"evennia.commands.default.unloggedin.CmdUnconnectedHelp":{aliases:[171,4,1,""],func:[171,3,1,""],help_category:[171,4,1,""],key:[171,4,1,""],lock_storage:[171,4,1,""],locks:[171,4,1,""]},"evennia.commands.default.unloggedin.CmdUnconnectedInfo":{aliases:[171,4,1,""],func:[171,3,1,""],help_category:[171,4,1,""],key:[171,4,1,""],lock_storage:[171,4,1,""],locks:[171,4,1,""]},"evennia.commands.default.unloggedin.CmdUnconnectedLook":{aliases:[171,4,1,""],func:[171,3,1,""],help_category:[171,4,1,""],key:[171,4,1,""],lock_storage:[171,4,1,""],locks:[171,4,1,""]},"evennia.commands.default.unloggedin.CmdUnconnectedQuit":{aliases:[171,4,1,""],func:[171,3,1,""],help_category:[171,4,1,""],key:[171,4,1,""],lock_storage:[171,4,1,""],locks:[171,4,1,""]},"evennia.commands.default.unloggedin.CmdUnconnectedScreenreader":{aliases:[171,4,1,""],func:[171,3,1,""],help_category:[171,4,1,""],key:[171,4,1,""],lock_storage:[171,4,1,""]},"evennia.comms":{admin:[173,0,0,"-"],channelhandler:[174,0,0,"-"],comms:[175,0,0,"-"],managers:[176,0,0,"-"],models:[177,0,0,"-"]},"evennia.comms.admin":{ChannelAdmin:[173,1,1,""],ChannelAttributeInline:[173,1,1,""],ChannelTagInline:[173,1,1,""],MsgAdmin:[173,1,1,""]},"evennia.comms.admin.ChannelAdmin":{fieldsets:[173,4,1,""],inlines:[173,4,1,""],list_display:[173,4,1,""],list_display_links:[173,4,1,""],list_select_related:[173,4,1,""],media:[173,3,1,""],ordering:[173,4,1,""],raw_id_fields:[173,4,1,""],response_add:[173,3,1,""],save_as:[173,4,1,""],save_model:[173,3,1,""],save_on_top:[173,4,1,""],search_fields:[173,4,1,""],subscriptions:[173,3,1,""]},"evennia.comms.admin.ChannelAttributeInline":{media:[173,3,1,""],model:[173,4,1,""],related_field:[173,4,1,""]},"evennia.comms.admin.ChannelTagInline":{media:[173,3,1,""],model:[173,4,1,""],related_field:[173,4,1,""]},"evennia.comms.admin.MsgAdmin":{list_display:[173,4,1,""],list_display_links:[173,4,1,""],list_select_related:[173,4,1,""],media:[173,3,1,""],ordering:[173,4,1,""],save_as:[173,4,1,""],save_on_top:[173,4,1,""],search_fields:[173,4,1,""]},"evennia.comms.channelhandler":{ChannelCommand:[174,1,1,""],ChannelHandler:[174,1,1,""]},"evennia.comms.channelhandler.ChannelCommand":{aliases:[174,4,1,""],arg_regex:[174,4,1,""],func:[174,3,1,""],get_extra_info:[174,3,1,""],help_category:[174,4,1,""],is_channel:[174,4,1,""],key:[174,4,1,""],lock_storage:[174,4,1,""],obj:[174,4,1,""],parse:[174,3,1,""]},"evennia.comms.channelhandler.ChannelHandler":{__init__:[174,3,1,""],add:[174,3,1,""],add_channel:[174,3,1,""],clear:[174,3,1,""],get:[174,3,1,""],get_cmdset:[174,3,1,""],remove:[174,3,1,""],update:[174,3,1,""]},"evennia.comms.comms":{DefaultChannel:[175,1,1,""]},"evennia.comms.comms.DefaultChannel":{"delete":[175,3,1,""],DoesNotExist:[175,2,1,""],MultipleObjectsReturned:[175,2,1,""],access:[175,3,1,""],at_channel_creation:[175,3,1,""],at_first_save:[175,3,1,""],at_init:[175,3,1,""],basetype_setup:[175,3,1,""],channel_prefix:[175,3,1,""],connect:[175,3,1,""],create:[175,3,1,""],disconnect:[175,3,1,""],distribute_message:[175,3,1,""],format_external:[175,3,1,""],format_message:[175,3,1,""],format_senders:[175,3,1,""],get_absolute_url:[175,3,1,""],has_connection:[175,3,1,""],message_transform:[175,3,1,""],msg:[175,3,1,""],mute:[175,3,1,""],mutelist:[175,3,1,""],objects:[175,4,1,""],path:[175,4,1,""],pose_transform:[175,3,1,""],post_join_channel:[175,3,1,""],post_leave_channel:[175,3,1,""],post_send_message:[175,3,1,""],pre_join_channel:[175,3,1,""],pre_leave_channel:[175,3,1,""],pre_send_message:[175,3,1,""],tempmsg:[175,3,1,""],typename:[175,4,1,""],unmute:[175,3,1,""],web_get_admin_url:[175,3,1,""],web_get_create_url:[175,3,1,""],web_get_delete_url:[175,3,1,""],web_get_detail_url:[175,3,1,""],web_get_update_url:[175,3,1,""],wholist:[175,3,1,""]},"evennia.comms.managers":{ChannelDBManager:[176,1,1,""],ChannelManager:[176,1,1,""],CommError:[176,2,1,""],MsgManager:[176,1,1,""],identify_object:[176,5,1,""],to_object:[176,5,1,""]},"evennia.comms.managers.ChannelDBManager":{channel_search:[176,3,1,""],get_all_channels:[176,3,1,""],get_channel:[176,3,1,""],get_subscriptions:[176,3,1,""],search_channel:[176,3,1,""]},"evennia.comms.managers.MsgManager":{get_message_by_id:[176,3,1,""],get_messages_by_channel:[176,3,1,""],get_messages_by_receiver:[176,3,1,""],get_messages_by_sender:[176,3,1,""],identify_object:[176,3,1,""],message_search:[176,3,1,""],search_message:[176,3,1,""]},"evennia.comms.models":{ChannelDB:[177,1,1,""],Msg:[177,1,1,""],TempMsg:[177,1,1,""]},"evennia.comms.models.ChannelDB":{DoesNotExist:[177,2,1,""],MultipleObjectsReturned:[177,2,1,""],channel_set:[177,4,1,""],db_account_subscriptions:[177,4,1,""],db_attributes:[177,4,1,""],db_object_subscriptions:[177,4,1,""],db_tags:[177,4,1,""],get_next_by_db_date_created:[177,3,1,""],get_previous_by_db_date_created:[177,3,1,""],hide_from_channels_set:[177,4,1,""],id:[177,4,1,""],objects:[177,4,1,""],path:[177,4,1,""],subscriptions:[177,4,1,""],typename:[177,4,1,""]},"evennia.comms.models.Msg":{DoesNotExist:[177,2,1,""],MultipleObjectsReturned:[177,2,1,""],__init__:[177,3,1,""],access:[177,3,1,""],channels:[177,3,1,""],date_created:[177,3,1,""],db_date_created:[177,4,1,""],db_header:[177,4,1,""],db_hide_from_accounts:[177,4,1,""],db_hide_from_channels:[177,4,1,""],db_hide_from_objects:[177,4,1,""],db_lock_storage:[177,4,1,""],db_message:[177,4,1,""],db_receivers_accounts:[177,4,1,""],db_receivers_channels:[177,4,1,""],db_receivers_objects:[177,4,1,""],db_receivers_scripts:[177,4,1,""],db_sender_accounts:[177,4,1,""],db_sender_external:[177,4,1,""],db_sender_objects:[177,4,1,""],db_sender_scripts:[177,4,1,""],db_tags:[177,4,1,""],get_next_by_db_date_created:[177,3,1,""],get_previous_by_db_date_created:[177,3,1,""],header:[177,3,1,""],hide_from:[177,3,1,""],id:[177,4,1,""],lock_storage:[177,3,1,""],locks:[177,4,1,""],message:[177,3,1,""],objects:[177,4,1,""],path:[177,4,1,""],receivers:[177,3,1,""],remove_receiver:[177,3,1,""],remove_sender:[177,3,1,""],sender_external:[177,3,1,""],senders:[177,3,1,""],tags:[177,4,1,""],typename:[177,4,1,""]},"evennia.comms.models.TempMsg":{__init__:[177,3,1,""],access:[177,3,1,""],locks:[177,4,1,""],remove_receiver:[177,3,1,""],remove_sender:[177,3,1,""]},"evennia.contrib":{barter:[179,0,0,"-"],building_menu:[180,0,0,"-"],chargen:[181,0,0,"-"],clothing:[182,0,0,"-"],color_markups:[183,0,0,"-"],custom_gametime:[184,0,0,"-"],dice:[185,0,0,"-"],email_login:[186,0,0,"-"],extended_room:[187,0,0,"-"],fieldfill:[188,0,0,"-"],gendersub:[189,0,0,"-"],health_bar:[190,0,0,"-"],ingame_python:[191,0,0,"-"],mail:[199,0,0,"-"],multidescer:[202,0,0,"-"],puzzles:[203,0,0,"-"],random_string_generator:[204,0,0,"-"],rplanguage:[205,0,0,"-"],rpsystem:[206,0,0,"-"],security:[207,0,0,"-"],simpledoor:[212,0,0,"-"],slow_exit:[213,0,0,"-"],talking_npc:[214,0,0,"-"],tree_select:[215,0,0,"-"],turnbattle:[216,0,0,"-"],tutorial_examples:[222,0,0,"-"],tutorial_world:[229,0,0,"-"],unixcommand:[234,0,0,"-"],wilderness:[235,0,0,"-"]},"evennia.contrib.barter":{CmdAccept:[179,1,1,""],CmdDecline:[179,1,1,""],CmdEvaluate:[179,1,1,""],CmdFinish:[179,1,1,""],CmdOffer:[179,1,1,""],CmdStatus:[179,1,1,""],CmdTrade:[179,1,1,""],CmdTradeBase:[179,1,1,""],CmdTradeHelp:[179,1,1,""],CmdsetTrade:[179,1,1,""],TradeHandler:[179,1,1,""],TradeTimeout:[179,1,1,""]},"evennia.contrib.barter.CmdAccept":{aliases:[179,4,1,""],func:[179,3,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],locks:[179,4,1,""]},"evennia.contrib.barter.CmdDecline":{aliases:[179,4,1,""],func:[179,3,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],locks:[179,4,1,""]},"evennia.contrib.barter.CmdEvaluate":{aliases:[179,4,1,""],func:[179,3,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],locks:[179,4,1,""]},"evennia.contrib.barter.CmdFinish":{aliases:[179,4,1,""],func:[179,3,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],locks:[179,4,1,""]},"evennia.contrib.barter.CmdOffer":{aliases:[179,4,1,""],func:[179,3,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],locks:[179,4,1,""]},"evennia.contrib.barter.CmdStatus":{aliases:[179,4,1,""],func:[179,3,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],locks:[179,4,1,""]},"evennia.contrib.barter.CmdTrade":{aliases:[179,4,1,""],func:[179,3,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],locks:[179,4,1,""]},"evennia.contrib.barter.CmdTradeBase":{aliases:[179,4,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],parse:[179,3,1,""]},"evennia.contrib.barter.CmdTradeHelp":{aliases:[179,4,1,""],func:[179,3,1,""],help_category:[179,4,1,""],key:[179,4,1,""],lock_storage:[179,4,1,""],locks:[179,4,1,""]},"evennia.contrib.barter.CmdsetTrade":{at_cmdset_creation:[179,3,1,""],key:[179,4,1,""],path:[179,4,1,""]},"evennia.contrib.barter.TradeHandler":{__init__:[179,3,1,""],accept:[179,3,1,""],decline:[179,3,1,""],finish:[179,3,1,""],get_other:[179,3,1,""],join:[179,3,1,""],list:[179,3,1,""],msg_other:[179,3,1,""],offer:[179,3,1,""],search:[179,3,1,""],unjoin:[179,3,1,""]},"evennia.contrib.barter.TradeTimeout":{DoesNotExist:[179,2,1,""],MultipleObjectsReturned:[179,2,1,""],at_repeat:[179,3,1,""],at_script_creation:[179,3,1,""],is_valid:[179,3,1,""],path:[179,4,1,""],typename:[179,4,1,""]},"evennia.contrib.building_menu":{BuildingMenu:[180,1,1,""],BuildingMenuCmdSet:[180,1,1,""],Choice:[180,1,1,""],CmdNoInput:[180,1,1,""],CmdNoMatch:[180,1,1,""],GenericBuildingCmd:[180,1,1,""],GenericBuildingMenu:[180,1,1,""],menu_edit:[180,5,1,""],menu_quit:[180,5,1,""],menu_setattr:[180,5,1,""]},"evennia.contrib.building_menu.BuildingMenu":{__init__:[180,3,1,""],add_choice:[180,3,1,""],add_choice_edit:[180,3,1,""],add_choice_quit:[180,3,1,""],close:[180,3,1,""],current_choice:[180,3,1,""],display:[180,3,1,""],display_choice:[180,3,1,""],display_title:[180,3,1,""],init:[180,3,1,""],joker_key:[180,4,1,""],keys_go_back:[180,4,1,""],min_shortcut:[180,4,1,""],move:[180,3,1,""],open:[180,3,1,""],open_parent_menu:[180,3,1,""],open_submenu:[180,3,1,""],relevant_choices:[180,3,1,""],restore:[180,3,1,""],sep_keys:[180,4,1,""]},"evennia.contrib.building_menu.BuildingMenuCmdSet":{at_cmdset_creation:[180,3,1,""],key:[180,4,1,""],path:[180,4,1,""],priority:[180,4,1,""]},"evennia.contrib.building_menu.Choice":{__init__:[180,3,1,""],enter:[180,3,1,""],format_text:[180,3,1,""],keys:[180,3,1,""],leave:[180,3,1,""],nomatch:[180,3,1,""]},"evennia.contrib.building_menu.CmdNoInput":{__init__:[180,3,1,""],aliases:[180,4,1,""],func:[180,3,1,""],help_category:[180,4,1,""],key:[180,4,1,""],lock_storage:[180,4,1,""],locks:[180,4,1,""]},"evennia.contrib.building_menu.CmdNoMatch":{__init__:[180,3,1,""],aliases:[180,4,1,""],func:[180,3,1,""],help_category:[180,4,1,""],key:[180,4,1,""],lock_storage:[180,4,1,""],locks:[180,4,1,""]},"evennia.contrib.building_menu.GenericBuildingCmd":{aliases:[180,4,1,""],func:[180,3,1,""],help_category:[180,4,1,""],key:[180,4,1,""],lock_storage:[180,4,1,""]},"evennia.contrib.building_menu.GenericBuildingMenu":{init:[180,3,1,""]},"evennia.contrib.chargen":{CmdOOCCharacterCreate:[181,1,1,""],CmdOOCLook:[181,1,1,""],OOCCmdSetCharGen:[181,1,1,""]},"evennia.contrib.chargen.CmdOOCCharacterCreate":{aliases:[181,4,1,""],func:[181,3,1,""],help_category:[181,4,1,""],key:[181,4,1,""],lock_storage:[181,4,1,""],locks:[181,4,1,""]},"evennia.contrib.chargen.CmdOOCLook":{aliases:[181,4,1,""],func:[181,3,1,""],help_category:[181,4,1,""],key:[181,4,1,""],lock_storage:[181,4,1,""],locks:[181,4,1,""]},"evennia.contrib.chargen.OOCCmdSetCharGen":{at_cmdset_creation:[181,3,1,""],path:[181,4,1,""]},"evennia.contrib.clothing":{ClothedCharacter:[182,1,1,""],ClothedCharacterCmdSet:[182,1,1,""],Clothing:[182,1,1,""],CmdCover:[182,1,1,""],CmdDrop:[182,1,1,""],CmdGive:[182,1,1,""],CmdInventory:[182,1,1,""],CmdRemove:[182,1,1,""],CmdUncover:[182,1,1,""],CmdWear:[182,1,1,""],clothing_type_count:[182,5,1,""],get_worn_clothes:[182,5,1,""],order_clothes_list:[182,5,1,""],single_type_count:[182,5,1,""]},"evennia.contrib.clothing.ClothedCharacter":{DoesNotExist:[182,2,1,""],MultipleObjectsReturned:[182,2,1,""],path:[182,4,1,""],return_appearance:[182,3,1,""],typename:[182,4,1,""]},"evennia.contrib.clothing.ClothedCharacterCmdSet":{at_cmdset_creation:[182,3,1,""],key:[182,4,1,""],path:[182,4,1,""]},"evennia.contrib.clothing.Clothing":{DoesNotExist:[182,2,1,""],MultipleObjectsReturned:[182,2,1,""],at_get:[182,3,1,""],path:[182,4,1,""],remove:[182,3,1,""],typename:[182,4,1,""],wear:[182,3,1,""]},"evennia.contrib.clothing.CmdCover":{aliases:[182,4,1,""],func:[182,3,1,""],help_category:[182,4,1,""],key:[182,4,1,""],lock_storage:[182,4,1,""]},"evennia.contrib.clothing.CmdDrop":{aliases:[182,4,1,""],arg_regex:[182,4,1,""],func:[182,3,1,""],help_category:[182,4,1,""],key:[182,4,1,""],lock_storage:[182,4,1,""],locks:[182,4,1,""]},"evennia.contrib.clothing.CmdGive":{aliases:[182,4,1,""],arg_regex:[182,4,1,""],func:[182,3,1,""],help_category:[182,4,1,""],key:[182,4,1,""],lock_storage:[182,4,1,""],locks:[182,4,1,""]},"evennia.contrib.clothing.CmdInventory":{aliases:[182,4,1,""],arg_regex:[182,4,1,""],func:[182,3,1,""],help_category:[182,4,1,""],key:[182,4,1,""],lock_storage:[182,4,1,""],locks:[182,4,1,""]},"evennia.contrib.clothing.CmdRemove":{aliases:[182,4,1,""],func:[182,3,1,""],help_category:[182,4,1,""],key:[182,4,1,""],lock_storage:[182,4,1,""]},"evennia.contrib.clothing.CmdUncover":{aliases:[182,4,1,""],func:[182,3,1,""],help_category:[182,4,1,""],key:[182,4,1,""],lock_storage:[182,4,1,""]},"evennia.contrib.clothing.CmdWear":{aliases:[182,4,1,""],func:[182,3,1,""],help_category:[182,4,1,""],key:[182,4,1,""],lock_storage:[182,4,1,""]},"evennia.contrib.custom_gametime":{GametimeScript:[184,1,1,""],custom_gametime:[184,5,1,""],gametime_to_realtime:[184,5,1,""],real_seconds_until:[184,5,1,""],realtime_to_gametime:[184,5,1,""],schedule:[184,5,1,""],time_to_tuple:[184,5,1,""]},"evennia.contrib.custom_gametime.GametimeScript":{DoesNotExist:[184,2,1,""],MultipleObjectsReturned:[184,2,1,""],at_repeat:[184,3,1,""],at_script_creation:[184,3,1,""],path:[184,4,1,""],typename:[184,4,1,""]},"evennia.contrib.dice":{CmdDice:[185,1,1,""],DiceCmdSet:[185,1,1,""],roll_dice:[185,5,1,""]},"evennia.contrib.dice.CmdDice":{aliases:[185,4,1,""],func:[185,3,1,""],help_category:[185,4,1,""],key:[185,4,1,""],lock_storage:[185,4,1,""],locks:[185,4,1,""]},"evennia.contrib.dice.DiceCmdSet":{at_cmdset_creation:[185,3,1,""],path:[185,4,1,""]},"evennia.contrib.email_login":{CmdUnconnectedConnect:[186,1,1,""],CmdUnconnectedCreate:[186,1,1,""],CmdUnconnectedHelp:[186,1,1,""],CmdUnconnectedLook:[186,1,1,""],CmdUnconnectedQuit:[186,1,1,""]},"evennia.contrib.email_login.CmdUnconnectedConnect":{aliases:[186,4,1,""],func:[186,3,1,""],help_category:[186,4,1,""],key:[186,4,1,""],lock_storage:[186,4,1,""],locks:[186,4,1,""]},"evennia.contrib.email_login.CmdUnconnectedCreate":{aliases:[186,4,1,""],func:[186,3,1,""],help_category:[186,4,1,""],key:[186,4,1,""],lock_storage:[186,4,1,""],locks:[186,4,1,""],parse:[186,3,1,""]},"evennia.contrib.email_login.CmdUnconnectedHelp":{aliases:[186,4,1,""],func:[186,3,1,""],help_category:[186,4,1,""],key:[186,4,1,""],lock_storage:[186,4,1,""],locks:[186,4,1,""]},"evennia.contrib.email_login.CmdUnconnectedLook":{aliases:[186,4,1,""],func:[186,3,1,""],help_category:[186,4,1,""],key:[186,4,1,""],lock_storage:[186,4,1,""],locks:[186,4,1,""]},"evennia.contrib.email_login.CmdUnconnectedQuit":{aliases:[186,4,1,""],func:[186,3,1,""],help_category:[186,4,1,""],key:[186,4,1,""],lock_storage:[186,4,1,""],locks:[186,4,1,""]},"evennia.contrib.extended_room":{CmdExtendedRoomDesc:[187,1,1,""],CmdExtendedRoomDetail:[187,1,1,""],CmdExtendedRoomGameTime:[187,1,1,""],CmdExtendedRoomLook:[187,1,1,""],ExtendedRoom:[187,1,1,""],ExtendedRoomCmdSet:[187,1,1,""]},"evennia.contrib.extended_room.CmdExtendedRoomDesc":{aliases:[187,4,1,""],func:[187,3,1,""],help_category:[187,4,1,""],key:[187,4,1,""],lock_storage:[187,4,1,""],reset_times:[187,3,1,""],switch_options:[187,4,1,""]},"evennia.contrib.extended_room.CmdExtendedRoomDetail":{aliases:[187,4,1,""],func:[187,3,1,""],help_category:[187,4,1,""],key:[187,4,1,""],lock_storage:[187,4,1,""],locks:[187,4,1,""]},"evennia.contrib.extended_room.CmdExtendedRoomGameTime":{aliases:[187,4,1,""],func:[187,3,1,""],help_category:[187,4,1,""],key:[187,4,1,""],lock_storage:[187,4,1,""],locks:[187,4,1,""]},"evennia.contrib.extended_room.CmdExtendedRoomLook":{aliases:[187,4,1,""],func:[187,3,1,""],help_category:[187,4,1,""],key:[187,4,1,""],lock_storage:[187,4,1,""]},"evennia.contrib.extended_room.ExtendedRoom":{DoesNotExist:[187,2,1,""],MultipleObjectsReturned:[187,2,1,""],at_object_creation:[187,3,1,""],del_detail:[187,3,1,""],get_time_and_season:[187,3,1,""],path:[187,4,1,""],replace_timeslots:[187,3,1,""],return_appearance:[187,3,1,""],return_detail:[187,3,1,""],set_detail:[187,3,1,""],typename:[187,4,1,""],update_current_description:[187,3,1,""]},"evennia.contrib.extended_room.ExtendedRoomCmdSet":{at_cmdset_creation:[187,3,1,""],path:[187,4,1,""]},"evennia.contrib.fieldfill":{CmdTestMenu:[188,1,1,""],FieldEvMenu:[188,1,1,""],display_formdata:[188,5,1,""],form_template_to_dict:[188,5,1,""],init_delayed_message:[188,5,1,""],init_fill_field:[188,5,1,""],menunode_fieldfill:[188,5,1,""],sendmessage:[188,5,1,""],verify_online_player:[188,5,1,""]},"evennia.contrib.fieldfill.CmdTestMenu":{aliases:[188,4,1,""],func:[188,3,1,""],help_category:[188,4,1,""],key:[188,4,1,""],lock_storage:[188,4,1,""]},"evennia.contrib.fieldfill.FieldEvMenu":{node_formatter:[188,3,1,""]},"evennia.contrib.gendersub":{GenderCharacter:[189,1,1,""],SetGender:[189,1,1,""]},"evennia.contrib.gendersub.GenderCharacter":{DoesNotExist:[189,2,1,""],MultipleObjectsReturned:[189,2,1,""],at_object_creation:[189,3,1,""],msg:[189,3,1,""],path:[189,4,1,""],typename:[189,4,1,""]},"evennia.contrib.gendersub.SetGender":{aliases:[189,4,1,""],func:[189,3,1,""],help_category:[189,4,1,""],key:[189,4,1,""],lock_storage:[189,4,1,""],locks:[189,4,1,""]},"evennia.contrib.health_bar":{display_meter:[190,5,1,""]},"evennia.contrib.ingame_python":{callbackhandler:[192,0,0,"-"],commands:[193,0,0,"-"],eventfuncs:[194,0,0,"-"],scripts:[195,0,0,"-"],tests:[196,0,0,"-"],utils:[198,0,0,"-"]},"evennia.contrib.ingame_python.callbackhandler":{Callback:[192,1,1,""],CallbackHandler:[192,1,1,""]},"evennia.contrib.ingame_python.callbackhandler.Callback":{author:[192,3,1,""],code:[192,3,1,""],created_on:[192,3,1,""],name:[192,3,1,""],number:[192,3,1,""],obj:[192,3,1,""],parameters:[192,3,1,""],updated_by:[192,3,1,""],updated_on:[192,3,1,""],valid:[192,3,1,""]},"evennia.contrib.ingame_python.callbackhandler.CallbackHandler":{__init__:[192,3,1,""],add:[192,3,1,""],all:[192,3,1,""],call:[192,3,1,""],edit:[192,3,1,""],format_callback:[192,3,1,""],get:[192,3,1,""],get_variable:[192,3,1,""],remove:[192,3,1,""],script:[192,4,1,""]},"evennia.contrib.ingame_python.commands":{CmdCallback:[193,1,1,""]},"evennia.contrib.ingame_python.commands.CmdCallback":{accept_callback:[193,3,1,""],add_callback:[193,3,1,""],aliases:[193,4,1,""],del_callback:[193,3,1,""],edit_callback:[193,3,1,""],func:[193,3,1,""],get_help:[193,3,1,""],help_category:[193,4,1,""],key:[193,4,1,""],list_callbacks:[193,3,1,""],list_tasks:[193,3,1,""],lock_storage:[193,4,1,""],locks:[193,4,1,""]},"evennia.contrib.ingame_python.eventfuncs":{call_event:[194,5,1,""],deny:[194,5,1,""],get:[194,5,1,""]},"evennia.contrib.ingame_python.scripts":{EventHandler:[195,1,1,""],TimeEventScript:[195,1,1,""],complete_task:[195,5,1,""]},"evennia.contrib.ingame_python.scripts.EventHandler":{DoesNotExist:[195,2,1,""],MultipleObjectsReturned:[195,2,1,""],accept_callback:[195,3,1,""],add_callback:[195,3,1,""],add_event:[195,3,1,""],at_script_creation:[195,3,1,""],at_start:[195,3,1,""],call:[195,3,1,""],del_callback:[195,3,1,""],edit_callback:[195,3,1,""],get_callbacks:[195,3,1,""],get_events:[195,3,1,""],get_variable:[195,3,1,""],handle_error:[195,3,1,""],path:[195,4,1,""],set_task:[195,3,1,""],typename:[195,4,1,""]},"evennia.contrib.ingame_python.scripts.TimeEventScript":{DoesNotExist:[195,2,1,""],MultipleObjectsReturned:[195,2,1,""],at_repeat:[195,3,1,""],at_script_creation:[195,3,1,""],path:[195,4,1,""],typename:[195,4,1,""]},"evennia.contrib.ingame_python.tests":{TestCmdCallback:[196,1,1,""],TestDefaultCallbacks:[196,1,1,""],TestEventHandler:[196,1,1,""]},"evennia.contrib.ingame_python.tests.TestCmdCallback":{setUp:[196,3,1,""],tearDown:[196,3,1,""],test_accept:[196,3,1,""],test_add:[196,3,1,""],test_del:[196,3,1,""],test_list:[196,3,1,""],test_lock:[196,3,1,""]},"evennia.contrib.ingame_python.tests.TestDefaultCallbacks":{setUp:[196,3,1,""],tearDown:[196,3,1,""],test_exit:[196,3,1,""]},"evennia.contrib.ingame_python.tests.TestEventHandler":{setUp:[196,3,1,""],tearDown:[196,3,1,""],test_accept:[196,3,1,""],test_add_validation:[196,3,1,""],test_call:[196,3,1,""],test_del:[196,3,1,""],test_edit:[196,3,1,""],test_edit_validation:[196,3,1,""],test_handler:[196,3,1,""],test_start:[196,3,1,""]},"evennia.contrib.ingame_python.utils":{InterruptEvent:[198,2,1,""],get_event_handler:[198,5,1,""],get_next_wait:[198,5,1,""],keyword_event:[198,5,1,""],phrase_event:[198,5,1,""],register_events:[198,5,1,""],time_event:[198,5,1,""]},"evennia.contrib.mail":{CmdMail:[199,1,1,""],CmdMailCharacter:[199,1,1,""]},"evennia.contrib.mail.CmdMail":{aliases:[199,4,1,""],func:[199,3,1,""],get_all_mail:[199,3,1,""],help_category:[199,4,1,""],key:[199,4,1,""],lock:[199,4,1,""],lock_storage:[199,4,1,""],parse:[199,3,1,""],search_targets:[199,3,1,""],send_mail:[199,3,1,""]},"evennia.contrib.mail.CmdMailCharacter":{account_caller:[199,4,1,""],aliases:[199,4,1,""],help_category:[199,4,1,""],key:[199,4,1,""],lock_storage:[199,4,1,""]},"evennia.contrib.multidescer":{CmdMultiDesc:[202,1,1,""],DescValidateError:[202,2,1,""]},"evennia.contrib.multidescer.CmdMultiDesc":{aliases:[202,4,1,""],func:[202,3,1,""],help_category:[202,4,1,""],key:[202,4,1,""],lock_storage:[202,4,1,""],locks:[202,4,1,""]},"evennia.contrib.puzzles":{CmdArmPuzzle:[203,1,1,""],CmdCreatePuzzleRecipe:[203,1,1,""],CmdEditPuzzle:[203,1,1,""],CmdListArmedPuzzles:[203,1,1,""],CmdListPuzzleRecipes:[203,1,1,""],CmdUsePuzzleParts:[203,1,1,""],PuzzleRecipe:[203,1,1,""],PuzzleSystemCmdSet:[203,1,1,""],maskout_protodef:[203,5,1,""],proto_def:[203,5,1,""]},"evennia.contrib.puzzles.CmdArmPuzzle":{aliases:[203,4,1,""],func:[203,3,1,""],help_category:[203,4,1,""],key:[203,4,1,""],lock_storage:[203,4,1,""],locks:[203,4,1,""]},"evennia.contrib.puzzles.CmdCreatePuzzleRecipe":{aliases:[203,4,1,""],confirm:[203,4,1,""],default_confirm:[203,4,1,""],func:[203,3,1,""],help_category:[203,4,1,""],key:[203,4,1,""],lock_storage:[203,4,1,""],locks:[203,4,1,""]},"evennia.contrib.puzzles.CmdEditPuzzle":{aliases:[203,4,1,""],func:[203,3,1,""],help_category:[203,4,1,""],key:[203,4,1,""],lock_storage:[203,4,1,""],locks:[203,4,1,""]},"evennia.contrib.puzzles.CmdListArmedPuzzles":{aliases:[203,4,1,""],func:[203,3,1,""],help_category:[203,4,1,""],key:[203,4,1,""],lock_storage:[203,4,1,""],locks:[203,4,1,""]},"evennia.contrib.puzzles.CmdListPuzzleRecipes":{aliases:[203,4,1,""],func:[203,3,1,""],help_category:[203,4,1,""],key:[203,4,1,""],lock_storage:[203,4,1,""],locks:[203,4,1,""]},"evennia.contrib.puzzles.CmdUsePuzzleParts":{aliases:[203,4,1,""],func:[203,3,1,""],help_category:[203,4,1,""],key:[203,4,1,""],lock_storage:[203,4,1,""],locks:[203,4,1,""]},"evennia.contrib.puzzles.PuzzleRecipe":{DoesNotExist:[203,2,1,""],MultipleObjectsReturned:[203,2,1,""],path:[203,4,1,""],save_recipe:[203,3,1,""],typename:[203,4,1,""]},"evennia.contrib.puzzles.PuzzleSystemCmdSet":{at_cmdset_creation:[203,3,1,""],path:[203,4,1,""]},"evennia.contrib.random_string_generator":{ExhaustedGenerator:[204,2,1,""],RandomStringGenerator:[204,1,1,""],RandomStringGeneratorScript:[204,1,1,""],RejectedRegex:[204,2,1,""]},"evennia.contrib.random_string_generator.RandomStringGenerator":{__init__:[204,3,1,""],all:[204,3,1,""],clear:[204,3,1,""],get:[204,3,1,""],remove:[204,3,1,""],script:[204,4,1,""]},"evennia.contrib.random_string_generator.RandomStringGeneratorScript":{DoesNotExist:[204,2,1,""],MultipleObjectsReturned:[204,2,1,""],at_script_creation:[204,3,1,""],path:[204,4,1,""],typename:[204,4,1,""]},"evennia.contrib.rplanguage":{LanguageError:[205,2,1,""],LanguageExistsError:[205,2,1,""],LanguageHandler:[205,1,1,""],add_language:[205,5,1,""],available_languages:[205,5,1,""],obfuscate_language:[205,5,1,""],obfuscate_whisper:[205,5,1,""]},"evennia.contrib.rplanguage.LanguageHandler":{DoesNotExist:[205,2,1,""],MultipleObjectsReturned:[205,2,1,""],add:[205,3,1,""],at_script_creation:[205,3,1,""],path:[205,4,1,""],translate:[205,3,1,""],typename:[205,4,1,""]},"evennia.contrib.rpsystem":{CmdEmote:[206,1,1,""],CmdMask:[206,1,1,""],CmdPose:[206,1,1,""],CmdRecog:[206,1,1,""],CmdSay:[206,1,1,""],CmdSdesc:[206,1,1,""],ContribRPCharacter:[206,1,1,""],ContribRPObject:[206,1,1,""],ContribRPRoom:[206,1,1,""],EmoteError:[206,2,1,""],LanguageError:[206,2,1,""],RPCommand:[206,1,1,""],RPSystemCmdSet:[206,1,1,""],RecogError:[206,2,1,""],RecogHandler:[206,1,1,""],SdescError:[206,2,1,""],SdescHandler:[206,1,1,""],ordered_permutation_regex:[206,5,1,""],parse_language:[206,5,1,""],parse_sdescs_and_recogs:[206,5,1,""],regex_tuple_from_key_alias:[206,5,1,""],send_emote:[206,5,1,""]},"evennia.contrib.rpsystem.CmdEmote":{aliases:[206,4,1,""],func:[206,3,1,""],help_category:[206,4,1,""],key:[206,4,1,""],lock_storage:[206,4,1,""],locks:[206,4,1,""]},"evennia.contrib.rpsystem.CmdMask":{aliases:[206,4,1,""],func:[206,3,1,""],help_category:[206,4,1,""],key:[206,4,1,""],lock_storage:[206,4,1,""]},"evennia.contrib.rpsystem.CmdPose":{aliases:[206,4,1,""],func:[206,3,1,""],help_category:[206,4,1,""],key:[206,4,1,""],lock_storage:[206,4,1,""],parse:[206,3,1,""]},"evennia.contrib.rpsystem.CmdRecog":{aliases:[206,4,1,""],func:[206,3,1,""],help_category:[206,4,1,""],key:[206,4,1,""],lock_storage:[206,4,1,""],parse:[206,3,1,""]},"evennia.contrib.rpsystem.CmdSay":{aliases:[206,4,1,""],func:[206,3,1,""],help_category:[206,4,1,""],key:[206,4,1,""],lock_storage:[206,4,1,""],locks:[206,4,1,""]},"evennia.contrib.rpsystem.CmdSdesc":{aliases:[206,4,1,""],func:[206,3,1,""],help_category:[206,4,1,""],key:[206,4,1,""],lock_storage:[206,4,1,""],locks:[206,4,1,""]},"evennia.contrib.rpsystem.ContribRPCharacter":{DoesNotExist:[206,2,1,""],MultipleObjectsReturned:[206,2,1,""],at_before_say:[206,3,1,""],at_object_creation:[206,3,1,""],get_display_name:[206,3,1,""],path:[206,4,1,""],process_language:[206,3,1,""],process_recog:[206,3,1,""],process_sdesc:[206,3,1,""],recog:[206,4,1,""],sdesc:[206,4,1,""],typename:[206,4,1,""]},"evennia.contrib.rpsystem.ContribRPObject":{DoesNotExist:[206,2,1,""],MultipleObjectsReturned:[206,2,1,""],at_object_creation:[206,3,1,""],get_display_name:[206,3,1,""],path:[206,4,1,""],return_appearance:[206,3,1,""],search:[206,3,1,""],typename:[206,4,1,""]},"evennia.contrib.rpsystem.ContribRPRoom":{DoesNotExist:[206,2,1,""],MultipleObjectsReturned:[206,2,1,""],path:[206,4,1,""],typename:[206,4,1,""]},"evennia.contrib.rpsystem.RPCommand":{aliases:[206,4,1,""],help_category:[206,4,1,""],key:[206,4,1,""],lock_storage:[206,4,1,""],parse:[206,3,1,""]},"evennia.contrib.rpsystem.RPSystemCmdSet":{at_cmdset_creation:[206,3,1,""],path:[206,4,1,""]},"evennia.contrib.rpsystem.RecogHandler":{__init__:[206,3,1,""],add:[206,3,1,""],all:[206,3,1,""],get:[206,3,1,""],get_regex_tuple:[206,3,1,""],remove:[206,3,1,""]},"evennia.contrib.rpsystem.SdescHandler":{__init__:[206,3,1,""],add:[206,3,1,""],get:[206,3,1,""],get_regex_tuple:[206,3,1,""]},"evennia.contrib.security":{auditing:[208,0,0,"-"]},"evennia.contrib.security.auditing":{outputs:[209,0,0,"-"],server:[210,0,0,"-"],tests:[211,0,0,"-"]},"evennia.contrib.security.auditing.outputs":{to_file:[209,5,1,""],to_syslog:[209,5,1,""]},"evennia.contrib.security.auditing.server":{AuditedServerSession:[210,1,1,""]},"evennia.contrib.security.auditing.server.AuditedServerSession":{audit:[210,3,1,""],data_in:[210,3,1,""],data_out:[210,3,1,""],mask:[210,3,1,""]},"evennia.contrib.security.auditing.tests":{AuditingTest:[211,1,1,""]},"evennia.contrib.security.auditing.tests.AuditingTest":{test_audit:[211,3,1,""],test_mask:[211,3,1,""]},"evennia.contrib.simpledoor":{CmdOpen:[212,1,1,""],CmdOpenCloseDoor:[212,1,1,""],SimpleDoor:[212,1,1,""]},"evennia.contrib.simpledoor.CmdOpen":{aliases:[212,4,1,""],create_exit:[212,3,1,""],help_category:[212,4,1,""],key:[212,4,1,""],lock_storage:[212,4,1,""]},"evennia.contrib.simpledoor.CmdOpenCloseDoor":{aliases:[212,4,1,""],func:[212,3,1,""],help_category:[212,4,1,""],key:[212,4,1,""],lock_storage:[212,4,1,""],locks:[212,4,1,""]},"evennia.contrib.simpledoor.SimpleDoor":{"delete":[212,3,1,""],DoesNotExist:[212,2,1,""],MultipleObjectsReturned:[212,2,1,""],at_failed_traverse:[212,3,1,""],at_object_creation:[212,3,1,""],path:[212,4,1,""],setdesc:[212,3,1,""],setlock:[212,3,1,""],typename:[212,4,1,""]},"evennia.contrib.slow_exit":{CmdSetSpeed:[213,1,1,""],CmdStop:[213,1,1,""],SlowExit:[213,1,1,""]},"evennia.contrib.slow_exit.CmdSetSpeed":{aliases:[213,4,1,""],func:[213,3,1,""],help_category:[213,4,1,""],key:[213,4,1,""],lock_storage:[213,4,1,""]},"evennia.contrib.slow_exit.CmdStop":{aliases:[213,4,1,""],func:[213,3,1,""],help_category:[213,4,1,""],key:[213,4,1,""],lock_storage:[213,4,1,""]},"evennia.contrib.slow_exit.SlowExit":{DoesNotExist:[213,2,1,""],MultipleObjectsReturned:[213,2,1,""],at_traverse:[213,3,1,""],path:[213,4,1,""],typename:[213,4,1,""]},"evennia.contrib.talking_npc":{CmdTalk:[214,1,1,""],END:[214,5,1,""],TalkingCmdSet:[214,1,1,""],TalkingNPC:[214,1,1,""],info1:[214,5,1,""],info2:[214,5,1,""],info3:[214,5,1,""],menu_start_node:[214,5,1,""]},"evennia.contrib.talking_npc.CmdTalk":{aliases:[214,4,1,""],func:[214,3,1,""],help_category:[214,4,1,""],key:[214,4,1,""],lock_storage:[214,4,1,""],locks:[214,4,1,""]},"evennia.contrib.talking_npc.TalkingCmdSet":{at_cmdset_creation:[214,3,1,""],key:[214,4,1,""],path:[214,4,1,""]},"evennia.contrib.talking_npc.TalkingNPC":{DoesNotExist:[214,2,1,""],MultipleObjectsReturned:[214,2,1,""],at_object_creation:[214,3,1,""],path:[214,4,1,""],typename:[214,4,1,""]},"evennia.contrib.tree_select":{CmdNameColor:[215,1,1,""],change_name_color:[215,5,1,""],dashcount:[215,5,1,""],go_up_one_category:[215,5,1,""],index_to_selection:[215,5,1,""],init_tree_selection:[215,5,1,""],is_category:[215,5,1,""],menunode_treeselect:[215,5,1,""],optlist_to_menuoptions:[215,5,1,""],parse_opts:[215,5,1,""]},"evennia.contrib.tree_select.CmdNameColor":{aliases:[215,4,1,""],func:[215,3,1,""],help_category:[215,4,1,""],key:[215,4,1,""],lock_storage:[215,4,1,""]},"evennia.contrib.turnbattle":{tb_basic:[217,0,0,"-"],tb_equip:[218,0,0,"-"],tb_items:[219,0,0,"-"],tb_magic:[220,0,0,"-"],tb_range:[221,0,0,"-"]},"evennia.contrib.turnbattle.tb_basic":{ACTIONS_PER_TURN:[217,6,1,""],BattleCmdSet:[217,1,1,""],CmdAttack:[217,1,1,""],CmdCombatHelp:[217,1,1,""],CmdDisengage:[217,1,1,""],CmdFight:[217,1,1,""],CmdPass:[217,1,1,""],CmdRest:[217,1,1,""],TBBasicCharacter:[217,1,1,""],TBBasicTurnHandler:[217,1,1,""],apply_damage:[217,5,1,""],at_defeat:[217,5,1,""],combat_cleanup:[217,5,1,""],get_attack:[217,5,1,""],get_damage:[217,5,1,""],get_defense:[217,5,1,""],is_in_combat:[217,5,1,""],is_turn:[217,5,1,""],resolve_attack:[217,5,1,""],roll_init:[217,5,1,""],spend_action:[217,5,1,""]},"evennia.contrib.turnbattle.tb_basic.BattleCmdSet":{at_cmdset_creation:[217,3,1,""],key:[217,4,1,""],path:[217,4,1,""]},"evennia.contrib.turnbattle.tb_basic.CmdAttack":{aliases:[217,4,1,""],func:[217,3,1,""],help_category:[217,4,1,""],key:[217,4,1,""],lock_storage:[217,4,1,""]},"evennia.contrib.turnbattle.tb_basic.CmdCombatHelp":{aliases:[217,4,1,""],func:[217,3,1,""],help_category:[217,4,1,""],key:[217,4,1,""],lock_storage:[217,4,1,""]},"evennia.contrib.turnbattle.tb_basic.CmdDisengage":{aliases:[217,4,1,""],func:[217,3,1,""],help_category:[217,4,1,""],key:[217,4,1,""],lock_storage:[217,4,1,""]},"evennia.contrib.turnbattle.tb_basic.CmdFight":{aliases:[217,4,1,""],func:[217,3,1,""],help_category:[217,4,1,""],key:[217,4,1,""],lock_storage:[217,4,1,""]},"evennia.contrib.turnbattle.tb_basic.CmdPass":{aliases:[217,4,1,""],func:[217,3,1,""],help_category:[217,4,1,""],key:[217,4,1,""],lock_storage:[217,4,1,""]},"evennia.contrib.turnbattle.tb_basic.CmdRest":{aliases:[217,4,1,""],func:[217,3,1,""],help_category:[217,4,1,""],key:[217,4,1,""],lock_storage:[217,4,1,""]},"evennia.contrib.turnbattle.tb_basic.TBBasicCharacter":{DoesNotExist:[217,2,1,""],MultipleObjectsReturned:[217,2,1,""],at_before_move:[217,3,1,""],at_object_creation:[217,3,1,""],path:[217,4,1,""],typename:[217,4,1,""]},"evennia.contrib.turnbattle.tb_basic.TBBasicTurnHandler":{DoesNotExist:[217,2,1,""],MultipleObjectsReturned:[217,2,1,""],at_repeat:[217,3,1,""],at_script_creation:[217,3,1,""],at_stop:[217,3,1,""],initialize_for_combat:[217,3,1,""],join_fight:[217,3,1,""],next_turn:[217,3,1,""],path:[217,4,1,""],start_turn:[217,3,1,""],turn_end_check:[217,3,1,""],typename:[217,4,1,""]},"evennia.contrib.turnbattle.tb_equip":{ACTIONS_PER_TURN:[218,6,1,""],BattleCmdSet:[218,1,1,""],CmdAttack:[218,1,1,""],CmdCombatHelp:[218,1,1,""],CmdDisengage:[218,1,1,""],CmdDoff:[218,1,1,""],CmdDon:[218,1,1,""],CmdFight:[218,1,1,""],CmdPass:[218,1,1,""],CmdRest:[218,1,1,""],CmdUnwield:[218,1,1,""],CmdWield:[218,1,1,""],TBEArmor:[218,1,1,""],TBEWeapon:[218,1,1,""],TBEquipCharacter:[218,1,1,""],TBEquipTurnHandler:[218,1,1,""],apply_damage:[218,5,1,""],at_defeat:[218,5,1,""],combat_cleanup:[218,5,1,""],get_attack:[218,5,1,""],get_damage:[218,5,1,""],get_defense:[218,5,1,""],is_in_combat:[218,5,1,""],is_turn:[218,5,1,""],resolve_attack:[218,5,1,""],roll_init:[218,5,1,""],spend_action:[218,5,1,""]},"evennia.contrib.turnbattle.tb_equip.BattleCmdSet":{at_cmdset_creation:[218,3,1,""],key:[218,4,1,""],path:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdAttack":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdCombatHelp":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdDisengage":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdDoff":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdDon":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdFight":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdPass":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdRest":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdUnwield":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.CmdWield":{aliases:[218,4,1,""],func:[218,3,1,""],help_category:[218,4,1,""],key:[218,4,1,""],lock_storage:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.TBEArmor":{DoesNotExist:[218,2,1,""],MultipleObjectsReturned:[218,2,1,""],at_before_drop:[218,3,1,""],at_before_give:[218,3,1,""],at_drop:[218,3,1,""],at_give:[218,3,1,""],at_object_creation:[218,3,1,""],path:[218,4,1,""],typename:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.TBEWeapon":{DoesNotExist:[218,2,1,""],MultipleObjectsReturned:[218,2,1,""],at_drop:[218,3,1,""],at_give:[218,3,1,""],at_object_creation:[218,3,1,""],path:[218,4,1,""],typename:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.TBEquipCharacter":{DoesNotExist:[218,2,1,""],MultipleObjectsReturned:[218,2,1,""],at_before_move:[218,3,1,""],at_object_creation:[218,3,1,""],path:[218,4,1,""],typename:[218,4,1,""]},"evennia.contrib.turnbattle.tb_equip.TBEquipTurnHandler":{DoesNotExist:[218,2,1,""],MultipleObjectsReturned:[218,2,1,""],at_repeat:[218,3,1,""],at_script_creation:[218,3,1,""],at_stop:[218,3,1,""],initialize_for_combat:[218,3,1,""],join_fight:[218,3,1,""],next_turn:[218,3,1,""],path:[218,4,1,""],start_turn:[218,3,1,""],turn_end_check:[218,3,1,""],typename:[218,4,1,""]},"evennia.contrib.turnbattle.tb_items":{BattleCmdSet:[219,1,1,""],CmdAttack:[219,1,1,""],CmdCombatHelp:[219,1,1,""],CmdDisengage:[219,1,1,""],CmdFight:[219,1,1,""],CmdPass:[219,1,1,""],CmdRest:[219,1,1,""],CmdUse:[219,1,1,""],DEF_DOWN_MOD:[219,6,1,""],ITEMFUNCS:[219,6,1,""],TBItemsCharacter:[219,1,1,""],TBItemsCharacterTest:[219,1,1,""],TBItemsTurnHandler:[219,1,1,""],add_condition:[219,5,1,""],apply_damage:[219,5,1,""],at_defeat:[219,5,1,""],combat_cleanup:[219,5,1,""],condition_tickdown:[219,5,1,""],get_attack:[219,5,1,""],get_damage:[219,5,1,""],get_defense:[219,5,1,""],is_in_combat:[219,5,1,""],is_turn:[219,5,1,""],itemfunc_add_condition:[219,5,1,""],itemfunc_attack:[219,5,1,""],itemfunc_cure_condition:[219,5,1,""],itemfunc_heal:[219,5,1,""],resolve_attack:[219,5,1,""],roll_init:[219,5,1,""],spend_action:[219,5,1,""],spend_item_use:[219,5,1,""],use_item:[219,5,1,""]},"evennia.contrib.turnbattle.tb_items.BattleCmdSet":{at_cmdset_creation:[219,3,1,""],key:[219,4,1,""],path:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.CmdAttack":{aliases:[219,4,1,""],func:[219,3,1,""],help_category:[219,4,1,""],key:[219,4,1,""],lock_storage:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.CmdCombatHelp":{aliases:[219,4,1,""],func:[219,3,1,""],help_category:[219,4,1,""],key:[219,4,1,""],lock_storage:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.CmdDisengage":{aliases:[219,4,1,""],func:[219,3,1,""],help_category:[219,4,1,""],key:[219,4,1,""],lock_storage:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.CmdFight":{aliases:[219,4,1,""],func:[219,3,1,""],help_category:[219,4,1,""],key:[219,4,1,""],lock_storage:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.CmdPass":{aliases:[219,4,1,""],func:[219,3,1,""],help_category:[219,4,1,""],key:[219,4,1,""],lock_storage:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.CmdRest":{aliases:[219,4,1,""],func:[219,3,1,""],help_category:[219,4,1,""],key:[219,4,1,""],lock_storage:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.CmdUse":{aliases:[219,4,1,""],func:[219,3,1,""],help_category:[219,4,1,""],key:[219,4,1,""],lock_storage:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.TBItemsCharacter":{DoesNotExist:[219,2,1,""],MultipleObjectsReturned:[219,2,1,""],apply_turn_conditions:[219,3,1,""],at_before_move:[219,3,1,""],at_object_creation:[219,3,1,""],at_turn_start:[219,3,1,""],at_update:[219,3,1,""],path:[219,4,1,""],typename:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.TBItemsCharacterTest":{DoesNotExist:[219,2,1,""],MultipleObjectsReturned:[219,2,1,""],at_object_creation:[219,3,1,""],path:[219,4,1,""],typename:[219,4,1,""]},"evennia.contrib.turnbattle.tb_items.TBItemsTurnHandler":{DoesNotExist:[219,2,1,""],MultipleObjectsReturned:[219,2,1,""],at_repeat:[219,3,1,""],at_script_creation:[219,3,1,""],at_stop:[219,3,1,""],initialize_for_combat:[219,3,1,""],join_fight:[219,3,1,""],next_turn:[219,3,1,""],path:[219,4,1,""],start_turn:[219,3,1,""],turn_end_check:[219,3,1,""],typename:[219,4,1,""]},"evennia.contrib.turnbattle.tb_magic":{ACTIONS_PER_TURN:[220,6,1,""],BattleCmdSet:[220,1,1,""],CmdAttack:[220,1,1,""],CmdCast:[220,1,1,""],CmdCombatHelp:[220,1,1,""],CmdDisengage:[220,1,1,""],CmdFight:[220,1,1,""],CmdLearnSpell:[220,1,1,""],CmdPass:[220,1,1,""],CmdRest:[220,1,1,""],CmdStatus:[220,1,1,""],TBMagicCharacter:[220,1,1,""],TBMagicTurnHandler:[220,1,1,""],apply_damage:[220,5,1,""],at_defeat:[220,5,1,""],combat_cleanup:[220,5,1,""],get_attack:[220,5,1,""],get_damage:[220,5,1,""],get_defense:[220,5,1,""],is_in_combat:[220,5,1,""],is_turn:[220,5,1,""],resolve_attack:[220,5,1,""],roll_init:[220,5,1,""],spell_attack:[220,5,1,""],spell_conjure:[220,5,1,""],spell_healing:[220,5,1,""],spend_action:[220,5,1,""]},"evennia.contrib.turnbattle.tb_magic.BattleCmdSet":{at_cmdset_creation:[220,3,1,""],key:[220,4,1,""],path:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdAttack":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdCast":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdCombatHelp":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdDisengage":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdFight":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdLearnSpell":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdPass":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdRest":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.CmdStatus":{aliases:[220,4,1,""],func:[220,3,1,""],help_category:[220,4,1,""],key:[220,4,1,""],lock_storage:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.TBMagicCharacter":{DoesNotExist:[220,2,1,""],MultipleObjectsReturned:[220,2,1,""],at_before_move:[220,3,1,""],at_object_creation:[220,3,1,""],path:[220,4,1,""],typename:[220,4,1,""]},"evennia.contrib.turnbattle.tb_magic.TBMagicTurnHandler":{DoesNotExist:[220,2,1,""],MultipleObjectsReturned:[220,2,1,""],at_repeat:[220,3,1,""],at_script_creation:[220,3,1,""],at_stop:[220,3,1,""],initialize_for_combat:[220,3,1,""],join_fight:[220,3,1,""],next_turn:[220,3,1,""],path:[220,4,1,""],start_turn:[220,3,1,""],turn_end_check:[220,3,1,""],typename:[220,4,1,""]},"evennia.contrib.turnbattle.tb_range":{ACTIONS_PER_TURN:[221,6,1,""],BattleCmdSet:[221,1,1,""],CmdApproach:[221,1,1,""],CmdAttack:[221,1,1,""],CmdCombatHelp:[221,1,1,""],CmdDisengage:[221,1,1,""],CmdFight:[221,1,1,""],CmdPass:[221,1,1,""],CmdRest:[221,1,1,""],CmdShoot:[221,1,1,""],CmdStatus:[221,1,1,""],CmdWithdraw:[221,1,1,""],TBRangeCharacter:[221,1,1,""],TBRangeObject:[221,1,1,""],TBRangeTurnHandler:[221,1,1,""],apply_damage:[221,5,1,""],approach:[221,5,1,""],at_defeat:[221,5,1,""],combat_cleanup:[221,5,1,""],combat_status_message:[221,5,1,""],distance_inc:[221,5,1,""],get_attack:[221,5,1,""],get_damage:[221,5,1,""],get_defense:[221,5,1,""],get_range:[221,5,1,""],is_in_combat:[221,5,1,""],is_turn:[221,5,1,""],resolve_attack:[221,5,1,""],roll_init:[221,5,1,""],spend_action:[221,5,1,""],withdraw:[221,5,1,""]},"evennia.contrib.turnbattle.tb_range.BattleCmdSet":{at_cmdset_creation:[221,3,1,""],key:[221,4,1,""],path:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdApproach":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdAttack":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdCombatHelp":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdDisengage":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdFight":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdPass":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdRest":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdShoot":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdStatus":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.CmdWithdraw":{aliases:[221,4,1,""],func:[221,3,1,""],help_category:[221,4,1,""],key:[221,4,1,""],lock_storage:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.TBRangeCharacter":{DoesNotExist:[221,2,1,""],MultipleObjectsReturned:[221,2,1,""],at_before_move:[221,3,1,""],at_object_creation:[221,3,1,""],path:[221,4,1,""],typename:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.TBRangeObject":{DoesNotExist:[221,2,1,""],MultipleObjectsReturned:[221,2,1,""],at_before_drop:[221,3,1,""],at_before_get:[221,3,1,""],at_before_give:[221,3,1,""],at_drop:[221,3,1,""],at_get:[221,3,1,""],at_give:[221,3,1,""],path:[221,4,1,""],typename:[221,4,1,""]},"evennia.contrib.turnbattle.tb_range.TBRangeTurnHandler":{DoesNotExist:[221,2,1,""],MultipleObjectsReturned:[221,2,1,""],at_repeat:[221,3,1,""],at_script_creation:[221,3,1,""],at_stop:[221,3,1,""],init_range:[221,3,1,""],initialize_for_combat:[221,3,1,""],join_fight:[221,3,1,""],join_rangefield:[221,3,1,""],next_turn:[221,3,1,""],path:[221,4,1,""],start_turn:[221,3,1,""],turn_end_check:[221,3,1,""],typename:[221,4,1,""]},"evennia.contrib.tutorial_examples":{bodyfunctions:[223,0,0,"-"],cmdset_red_button:[224,0,0,"-"],red_button:[226,0,0,"-"],red_button_scripts:[227,0,0,"-"],tests:[228,0,0,"-"]},"evennia.contrib.tutorial_examples.bodyfunctions":{BodyFunctions:[223,1,1,""]},"evennia.contrib.tutorial_examples.bodyfunctions.BodyFunctions":{DoesNotExist:[223,2,1,""],MultipleObjectsReturned:[223,2,1,""],at_repeat:[223,3,1,""],at_script_creation:[223,3,1,""],path:[223,4,1,""],send_random_message:[223,3,1,""],typename:[223,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button":{BlindCmdSet:[224,1,1,""],CmdBlindHelp:[224,1,1,""],CmdBlindLook:[224,1,1,""],CmdCloseLid:[224,1,1,""],CmdNudge:[224,1,1,""],CmdOpenLid:[224,1,1,""],CmdPush:[224,1,1,""],CmdSmashGlass:[224,1,1,""],DefaultCmdSet:[224,1,1,""],LidClosedCmdSet:[224,1,1,""],LidOpenCmdSet:[224,1,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.BlindCmdSet":{at_cmdset_creation:[224,3,1,""],key:[224,4,1,""],mergetype:[224,4,1,""],no_exits:[224,4,1,""],no_objs:[224,4,1,""],path:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.CmdBlindHelp":{aliases:[224,4,1,""],func:[224,3,1,""],help_category:[224,4,1,""],key:[224,4,1,""],lock_storage:[224,4,1,""],locks:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.CmdBlindLook":{aliases:[224,4,1,""],func:[224,3,1,""],help_category:[224,4,1,""],key:[224,4,1,""],lock_storage:[224,4,1,""],locks:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.CmdCloseLid":{aliases:[224,4,1,""],func:[224,3,1,""],help_category:[224,4,1,""],key:[224,4,1,""],lock_storage:[224,4,1,""],locks:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.CmdNudge":{aliases:[224,4,1,""],func:[224,3,1,""],help_category:[224,4,1,""],key:[224,4,1,""],lock_storage:[224,4,1,""],locks:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.CmdOpenLid":{aliases:[224,4,1,""],func:[224,3,1,""],help_category:[224,4,1,""],key:[224,4,1,""],lock_storage:[224,4,1,""],locks:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.CmdPush":{aliases:[224,4,1,""],func:[224,3,1,""],help_category:[224,4,1,""],key:[224,4,1,""],lock_storage:[224,4,1,""],locks:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.CmdSmashGlass":{aliases:[224,4,1,""],func:[224,3,1,""],help_category:[224,4,1,""],key:[224,4,1,""],lock_storage:[224,4,1,""],locks:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.DefaultCmdSet":{at_cmdset_creation:[224,3,1,""],key:[224,4,1,""],mergetype:[224,4,1,""],path:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.LidClosedCmdSet":{at_cmdset_creation:[224,3,1,""],key:[224,4,1,""],key_mergetype:[224,4,1,""],path:[224,4,1,""]},"evennia.contrib.tutorial_examples.cmdset_red_button.LidOpenCmdSet":{at_cmdset_creation:[224,3,1,""],key:[224,4,1,""],key_mergetype:[224,4,1,""],path:[224,4,1,""]},"evennia.contrib.tutorial_examples.red_button":{RedButton:[226,1,1,""]},"evennia.contrib.tutorial_examples.red_button.RedButton":{DoesNotExist:[226,2,1,""],MultipleObjectsReturned:[226,2,1,""],at_object_creation:[226,3,1,""],blink:[226,3,1,""],break_lamp:[226,3,1,""],close_lid:[226,3,1,""],open_lid:[226,3,1,""],path:[226,4,1,""],press_button:[226,3,1,""],typename:[226,4,1,""]},"evennia.contrib.tutorial_examples.red_button_scripts":{BlindedState:[227,1,1,""],BlinkButtonEvent:[227,1,1,""],CloseLidEvent:[227,1,1,""],ClosedLidState:[227,1,1,""],DeactivateButtonEvent:[227,1,1,""],OpenLidState:[227,1,1,""]},"evennia.contrib.tutorial_examples.red_button_scripts.BlindedState":{DoesNotExist:[227,2,1,""],MultipleObjectsReturned:[227,2,1,""],at_script_creation:[227,3,1,""],at_start:[227,3,1,""],at_stop:[227,3,1,""],path:[227,4,1,""],typename:[227,4,1,""]},"evennia.contrib.tutorial_examples.red_button_scripts.BlinkButtonEvent":{DoesNotExist:[227,2,1,""],MultipleObjectsReturned:[227,2,1,""],at_repeat:[227,3,1,""],at_script_creation:[227,3,1,""],is_valid:[227,3,1,""],path:[227,4,1,""],typename:[227,4,1,""]},"evennia.contrib.tutorial_examples.red_button_scripts.CloseLidEvent":{DoesNotExist:[227,2,1,""],MultipleObjectsReturned:[227,2,1,""],at_repeat:[227,3,1,""],at_script_creation:[227,3,1,""],is_valid:[227,3,1,""],path:[227,4,1,""],typename:[227,4,1,""]},"evennia.contrib.tutorial_examples.red_button_scripts.ClosedLidState":{DoesNotExist:[227,2,1,""],MultipleObjectsReturned:[227,2,1,""],at_script_creation:[227,3,1,""],at_start:[227,3,1,""],at_stop:[227,3,1,""],is_valid:[227,3,1,""],path:[227,4,1,""],typename:[227,4,1,""]},"evennia.contrib.tutorial_examples.red_button_scripts.DeactivateButtonEvent":{DoesNotExist:[227,2,1,""],MultipleObjectsReturned:[227,2,1,""],at_repeat:[227,3,1,""],at_script_creation:[227,3,1,""],at_start:[227,3,1,""],path:[227,4,1,""],typename:[227,4,1,""]},"evennia.contrib.tutorial_examples.red_button_scripts.OpenLidState":{DoesNotExist:[227,2,1,""],MultipleObjectsReturned:[227,2,1,""],at_script_creation:[227,3,1,""],at_start:[227,3,1,""],at_stop:[227,3,1,""],is_valid:[227,3,1,""],path:[227,4,1,""],typename:[227,4,1,""]},"evennia.contrib.tutorial_examples.tests":{TestBodyFunctions:[228,1,1,""]},"evennia.contrib.tutorial_examples.tests.TestBodyFunctions":{script_typeclass:[228,4,1,""],setUp:[228,3,1,""],tearDown:[228,3,1,""],test_at_repeat:[228,3,1,""],test_send_random_message:[228,3,1,""]},"evennia.contrib.tutorial_world":{intro_menu:[230,0,0,"-"],mob:[231,0,0,"-"],objects:[232,0,0,"-"],rooms:[233,0,0,"-"]},"evennia.contrib.tutorial_world.intro_menu":{DemoCommandSetComms:[230,1,1,""],DemoCommandSetHelp:[230,1,1,""],DemoCommandSetRoom:[230,1,1,""],TutorialEvMenu:[230,1,1,""],do_nothing:[230,5,1,""],goto_cleanup_cmdsets:[230,5,1,""],goto_command_demo_comms:[230,5,1,""],goto_command_demo_help:[230,5,1,""],goto_command_demo_room:[230,5,1,""],init_menu:[230,5,1,""],send_testing_tagged:[230,5,1,""]},"evennia.contrib.tutorial_world.intro_menu.DemoCommandSetComms":{at_cmdset_creation:[230,3,1,""],key:[230,4,1,""],no_exits:[230,4,1,""],no_objs:[230,4,1,""],path:[230,4,1,""],priority:[230,4,1,""]},"evennia.contrib.tutorial_world.intro_menu.DemoCommandSetHelp":{at_cmdset_creation:[230,3,1,""],key:[230,4,1,""],path:[230,4,1,""],priority:[230,4,1,""]},"evennia.contrib.tutorial_world.intro_menu.DemoCommandSetRoom":{at_cmdset_creation:[230,3,1,""],key:[230,4,1,""],no_exits:[230,4,1,""],no_objs:[230,4,1,""],path:[230,4,1,""],priority:[230,4,1,""]},"evennia.contrib.tutorial_world.intro_menu.TutorialEvMenu":{close_menu:[230,3,1,""],options_formatter:[230,3,1,""]},"evennia.contrib.tutorial_world.mob":{CmdMobOnOff:[231,1,1,""],Mob:[231,1,1,""],MobCmdSet:[231,1,1,""]},"evennia.contrib.tutorial_world.mob.CmdMobOnOff":{aliases:[231,4,1,""],func:[231,3,1,""],help_category:[231,4,1,""],key:[231,4,1,""],lock_storage:[231,4,1,""],locks:[231,4,1,""]},"evennia.contrib.tutorial_world.mob.Mob":{DoesNotExist:[231,2,1,""],MultipleObjectsReturned:[231,2,1,""],at_hit:[231,3,1,""],at_init:[231,3,1,""],at_new_arrival:[231,3,1,""],at_object_creation:[231,3,1,""],do_attack:[231,3,1,""],do_hunting:[231,3,1,""],do_patrol:[231,3,1,""],path:[231,4,1,""],set_alive:[231,3,1,""],set_dead:[231,3,1,""],start_attacking:[231,3,1,""],start_hunting:[231,3,1,""],start_idle:[231,3,1,""],start_patrolling:[231,3,1,""],typename:[231,4,1,""]},"evennia.contrib.tutorial_world.mob.MobCmdSet":{at_cmdset_creation:[231,3,1,""],path:[231,4,1,""]},"evennia.contrib.tutorial_world.objects":{CmdAttack:[232,1,1,""],CmdClimb:[232,1,1,""],CmdGetWeapon:[232,1,1,""],CmdLight:[232,1,1,""],CmdPressButton:[232,1,1,""],CmdRead:[232,1,1,""],CmdSetClimbable:[232,1,1,""],CmdSetCrumblingWall:[232,1,1,""],CmdSetLight:[232,1,1,""],CmdSetReadable:[232,1,1,""],CmdSetWeapon:[232,1,1,""],CmdSetWeaponRack:[232,1,1,""],CmdShiftRoot:[232,1,1,""],CrumblingWall:[232,1,1,""],LightSource:[232,1,1,""],Obelisk:[232,1,1,""],TutorialClimbable:[232,1,1,""],TutorialObject:[232,1,1,""],TutorialReadable:[232,1,1,""],Weapon:[232,1,1,""],WeaponRack:[232,1,1,""]},"evennia.contrib.tutorial_world.objects.CmdAttack":{aliases:[232,4,1,""],func:[232,3,1,""],help_category:[232,4,1,""],key:[232,4,1,""],lock_storage:[232,4,1,""],locks:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdClimb":{aliases:[232,4,1,""],func:[232,3,1,""],help_category:[232,4,1,""],key:[232,4,1,""],lock_storage:[232,4,1,""],locks:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdGetWeapon":{aliases:[232,4,1,""],func:[232,3,1,""],help_category:[232,4,1,""],key:[232,4,1,""],lock_storage:[232,4,1,""],locks:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdLight":{aliases:[232,4,1,""],func:[232,3,1,""],help_category:[232,4,1,""],key:[232,4,1,""],lock_storage:[232,4,1,""],locks:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdPressButton":{aliases:[232,4,1,""],func:[232,3,1,""],help_category:[232,4,1,""],key:[232,4,1,""],lock_storage:[232,4,1,""],locks:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdRead":{aliases:[232,4,1,""],func:[232,3,1,""],help_category:[232,4,1,""],key:[232,4,1,""],lock_storage:[232,4,1,""],locks:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdSetClimbable":{at_cmdset_creation:[232,3,1,""],path:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdSetCrumblingWall":{at_cmdset_creation:[232,3,1,""],key:[232,4,1,""],path:[232,4,1,""],priority:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdSetLight":{at_cmdset_creation:[232,3,1,""],key:[232,4,1,""],path:[232,4,1,""],priority:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdSetReadable":{at_cmdset_creation:[232,3,1,""],path:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdSetWeapon":{at_cmdset_creation:[232,3,1,""],path:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdSetWeaponRack":{at_cmdset_creation:[232,3,1,""],key:[232,4,1,""],path:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.CmdShiftRoot":{aliases:[232,4,1,""],func:[232,3,1,""],help_category:[232,4,1,""],key:[232,4,1,""],lock_storage:[232,4,1,""],locks:[232,4,1,""],parse:[232,3,1,""]},"evennia.contrib.tutorial_world.objects.CrumblingWall":{DoesNotExist:[232,2,1,""],MultipleObjectsReturned:[232,2,1,""],at_after_traverse:[232,3,1,""],at_failed_traverse:[232,3,1,""],at_init:[232,3,1,""],at_object_creation:[232,3,1,""],open_wall:[232,3,1,""],path:[232,4,1,""],reset:[232,3,1,""],return_appearance:[232,3,1,""],typename:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.LightSource":{DoesNotExist:[232,2,1,""],MultipleObjectsReturned:[232,2,1,""],at_init:[232,3,1,""],at_object_creation:[232,3,1,""],light:[232,3,1,""],path:[232,4,1,""],typename:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.Obelisk":{DoesNotExist:[232,2,1,""],MultipleObjectsReturned:[232,2,1,""],at_object_creation:[232,3,1,""],path:[232,4,1,""],return_appearance:[232,3,1,""],typename:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.TutorialClimbable":{DoesNotExist:[232,2,1,""],MultipleObjectsReturned:[232,2,1,""],at_object_creation:[232,3,1,""],path:[232,4,1,""],typename:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.TutorialObject":{DoesNotExist:[232,2,1,""],MultipleObjectsReturned:[232,2,1,""],at_object_creation:[232,3,1,""],path:[232,4,1,""],reset:[232,3,1,""],typename:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.TutorialReadable":{DoesNotExist:[232,2,1,""],MultipleObjectsReturned:[232,2,1,""],at_object_creation:[232,3,1,""],path:[232,4,1,""],typename:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.Weapon":{DoesNotExist:[232,2,1,""],MultipleObjectsReturned:[232,2,1,""],at_object_creation:[232,3,1,""],path:[232,4,1,""],reset:[232,3,1,""],typename:[232,4,1,""]},"evennia.contrib.tutorial_world.objects.WeaponRack":{DoesNotExist:[232,2,1,""],MultipleObjectsReturned:[232,2,1,""],at_object_creation:[232,3,1,""],path:[232,4,1,""],produce_weapon:[232,3,1,""],typename:[232,4,1,""]},"evennia.contrib.tutorial_world.rooms":{BridgeCmdSet:[233,1,1,""],BridgeRoom:[233,1,1,""],CmdBridgeHelp:[233,1,1,""],CmdDarkHelp:[233,1,1,""],CmdDarkNoMatch:[233,1,1,""],CmdEast:[233,1,1,""],CmdEvenniaIntro:[233,1,1,""],CmdLookBridge:[233,1,1,""],CmdLookDark:[233,1,1,""],CmdSetEvenniaIntro:[233,1,1,""],CmdTutorial:[233,1,1,""],CmdTutorialGiveUp:[233,1,1,""],CmdTutorialLook:[233,1,1,""],CmdTutorialSetDetail:[233,1,1,""],CmdWest:[233,1,1,""],DarkCmdSet:[233,1,1,""],DarkRoom:[233,1,1,""],IntroRoom:[233,1,1,""],OutroRoom:[233,1,1,""],TeleportRoom:[233,1,1,""],TutorialRoom:[233,1,1,""],TutorialRoomCmdSet:[233,1,1,""],WeatherRoom:[233,1,1,""]},"evennia.contrib.tutorial_world.rooms.BridgeCmdSet":{at_cmdset_creation:[233,3,1,""],key:[233,4,1,""],path:[233,4,1,""],priority:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.BridgeRoom":{DoesNotExist:[233,2,1,""],MultipleObjectsReturned:[233,2,1,""],at_object_creation:[233,3,1,""],at_object_leave:[233,3,1,""],at_object_receive:[233,3,1,""],path:[233,4,1,""],typename:[233,4,1,""],update_weather:[233,3,1,""]},"evennia.contrib.tutorial_world.rooms.CmdBridgeHelp":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdDarkHelp":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdDarkNoMatch":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdEast":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdEvenniaIntro":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdLookBridge":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdLookDark":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdSetEvenniaIntro":{at_cmdset_creation:[233,3,1,""],key:[233,4,1,""],path:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdTutorial":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdTutorialGiveUp":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdTutorialLook":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdTutorialSetDetail":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.CmdWest":{aliases:[233,4,1,""],func:[233,3,1,""],help_category:[233,4,1,""],key:[233,4,1,""],lock_storage:[233,4,1,""],locks:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.DarkCmdSet":{at_cmdset_creation:[233,3,1,""],key:[233,4,1,""],mergetype:[233,4,1,""],path:[233,4,1,""],priority:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.DarkRoom":{DoesNotExist:[233,2,1,""],MultipleObjectsReturned:[233,2,1,""],at_init:[233,3,1,""],at_object_creation:[233,3,1,""],at_object_leave:[233,3,1,""],at_object_receive:[233,3,1,""],check_light_state:[233,3,1,""],path:[233,4,1,""],typename:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.IntroRoom":{DoesNotExist:[233,2,1,""],MultipleObjectsReturned:[233,2,1,""],at_object_creation:[233,3,1,""],at_object_receive:[233,3,1,""],path:[233,4,1,""],typename:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.OutroRoom":{DoesNotExist:[233,2,1,""],MultipleObjectsReturned:[233,2,1,""],at_object_creation:[233,3,1,""],at_object_leave:[233,3,1,""],at_object_receive:[233,3,1,""],path:[233,4,1,""],typename:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.TeleportRoom":{DoesNotExist:[233,2,1,""],MultipleObjectsReturned:[233,2,1,""],at_object_creation:[233,3,1,""],at_object_receive:[233,3,1,""],path:[233,4,1,""],typename:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.TutorialRoom":{DoesNotExist:[233,2,1,""],MultipleObjectsReturned:[233,2,1,""],at_object_creation:[233,3,1,""],at_object_receive:[233,3,1,""],path:[233,4,1,""],return_detail:[233,3,1,""],set_detail:[233,3,1,""],typename:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.TutorialRoomCmdSet":{at_cmdset_creation:[233,3,1,""],key:[233,4,1,""],path:[233,4,1,""],priority:[233,4,1,""]},"evennia.contrib.tutorial_world.rooms.WeatherRoom":{DoesNotExist:[233,2,1,""],MultipleObjectsReturned:[233,2,1,""],at_object_creation:[233,3,1,""],path:[233,4,1,""],typename:[233,4,1,""],update_weather:[233,3,1,""]},"evennia.contrib.unixcommand":{HelpAction:[234,1,1,""],ParseError:[234,2,1,""],UnixCommand:[234,1,1,""],UnixCommandParser:[234,1,1,""]},"evennia.contrib.unixcommand.UnixCommand":{__init__:[234,3,1,""],aliases:[234,4,1,""],func:[234,3,1,""],get_help:[234,3,1,""],help_category:[234,4,1,""],init_parser:[234,3,1,""],key:[234,4,1,""],lock_storage:[234,4,1,""],parse:[234,3,1,""]},"evennia.contrib.unixcommand.UnixCommandParser":{__init__:[234,3,1,""],format_help:[234,3,1,""],format_usage:[234,3,1,""],print_help:[234,3,1,""],print_usage:[234,3,1,""]},"evennia.contrib.wilderness":{WildernessExit:[235,1,1,""],WildernessMapProvider:[235,1,1,""],WildernessRoom:[235,1,1,""],WildernessScript:[235,1,1,""],create_wilderness:[235,5,1,""],enter_wilderness:[235,5,1,""],get_new_coordinates:[235,5,1,""]},"evennia.contrib.wilderness.WildernessExit":{DoesNotExist:[235,2,1,""],MultipleObjectsReturned:[235,2,1,""],at_traverse:[235,3,1,""],at_traverse_coordinates:[235,3,1,""],mapprovider:[235,3,1,""],path:[235,4,1,""],typename:[235,4,1,""],wilderness:[235,3,1,""]},"evennia.contrib.wilderness.WildernessMapProvider":{at_prepare_room:[235,3,1,""],exit_typeclass:[235,4,1,""],get_location_name:[235,3,1,""],is_valid_coordinates:[235,3,1,""],room_typeclass:[235,4,1,""]},"evennia.contrib.wilderness.WildernessRoom":{DoesNotExist:[235,2,1,""],MultipleObjectsReturned:[235,2,1,""],at_object_leave:[235,3,1,""],at_object_receive:[235,3,1,""],coordinates:[235,3,1,""],get_display_name:[235,3,1,""],location_name:[235,3,1,""],path:[235,4,1,""],set_active_coordinates:[235,3,1,""],typename:[235,4,1,""],wilderness:[235,3,1,""]},"evennia.contrib.wilderness.WildernessScript":{DoesNotExist:[235,2,1,""],MultipleObjectsReturned:[235,2,1,""],at_after_object_leave:[235,3,1,""],at_script_creation:[235,3,1,""],at_start:[235,3,1,""],get_obj_coordinates:[235,3,1,""],get_objs_at_coordinates:[235,3,1,""],is_valid_coordinates:[235,3,1,""],itemcoordinates:[235,3,1,""],mapprovider:[235,3,1,""],move_obj:[235,3,1,""],path:[235,4,1,""],typename:[235,4,1,""]},"evennia.help":{admin:[237,0,0,"-"],manager:[238,0,0,"-"],models:[239,0,0,"-"]},"evennia.help.admin":{HelpEntryAdmin:[237,1,1,""],HelpEntryForm:[237,1,1,""],HelpTagInline:[237,1,1,""]},"evennia.help.admin.HelpEntryAdmin":{fieldsets:[237,4,1,""],form:[237,4,1,""],inlines:[237,4,1,""],list_display:[237,4,1,""],list_display_links:[237,4,1,""],list_select_related:[237,4,1,""],media:[237,3,1,""],ordering:[237,4,1,""],save_as:[237,4,1,""],save_on_top:[237,4,1,""],search_fields:[237,4,1,""]},"evennia.help.admin.HelpEntryForm":{Meta:[237,1,1,""],base_fields:[237,4,1,""],declared_fields:[237,4,1,""],media:[237,3,1,""]},"evennia.help.admin.HelpEntryForm.Meta":{fields:[237,4,1,""],model:[237,4,1,""]},"evennia.help.admin.HelpTagInline":{media:[237,3,1,""],model:[237,4,1,""],related_field:[237,4,1,""]},"evennia.help.manager":{HelpEntryManager:[238,1,1,""]},"evennia.help.manager.HelpEntryManager":{all_to_category:[238,3,1,""],find_apropos:[238,3,1,""],find_topicmatch:[238,3,1,""],find_topics_with_category:[238,3,1,""],find_topicsuggestions:[238,3,1,""],get_all_categories:[238,3,1,""],get_all_topics:[238,3,1,""],search_help:[238,3,1,""]},"evennia.help.models":{HelpEntry:[239,1,1,""]},"evennia.help.models.HelpEntry":{DoesNotExist:[239,2,1,""],MultipleObjectsReturned:[239,2,1,""],access:[239,3,1,""],aliases:[239,4,1,""],db_entrytext:[239,4,1,""],db_help_category:[239,4,1,""],db_key:[239,4,1,""],db_lock_storage:[239,4,1,""],db_staff_only:[239,4,1,""],db_tags:[239,4,1,""],entrytext:[239,3,1,""],get_absolute_url:[239,3,1,""],help_category:[239,3,1,""],id:[239,4,1,""],key:[239,3,1,""],lock_storage:[239,3,1,""],locks:[239,4,1,""],objects:[239,4,1,""],path:[239,4,1,""],staff_only:[239,3,1,""],tags:[239,4,1,""],typename:[239,4,1,""],web_get_admin_url:[239,3,1,""],web_get_create_url:[239,3,1,""],web_get_delete_url:[239,3,1,""],web_get_detail_url:[239,3,1,""],web_get_update_url:[239,3,1,""]},"evennia.locks":{lockfuncs:[241,0,0,"-"],lockhandler:[242,0,0,"-"]},"evennia.locks.lockfuncs":{"false":[241,5,1,""],"true":[241,5,1,""],all:[241,5,1,""],attr:[241,5,1,""],attr_eq:[241,5,1,""],attr_ge:[241,5,1,""],attr_gt:[241,5,1,""],attr_le:[241,5,1,""],attr_lt:[241,5,1,""],attr_ne:[241,5,1,""],dbref:[241,5,1,""],has_account:[241,5,1,""],holds:[241,5,1,""],id:[241,5,1,""],inside:[241,5,1,""],inside_rec:[241,5,1,""],locattr:[241,5,1,""],none:[241,5,1,""],objattr:[241,5,1,""],objlocattr:[241,5,1,""],objtag:[241,5,1,""],pdbref:[241,5,1,""],perm:[241,5,1,""],perm_above:[241,5,1,""],pid:[241,5,1,""],pperm:[241,5,1,""],pperm_above:[241,5,1,""],self:[241,5,1,""],serversetting:[241,5,1,""],superuser:[241,5,1,""],tag:[241,5,1,""]},"evennia.locks.lockhandler":{LockException:[242,2,1,""],LockHandler:[242,1,1,""]},"evennia.locks.lockhandler.LockHandler":{"delete":[242,3,1,""],__init__:[242,3,1,""],add:[242,3,1,""],all:[242,3,1,""],append:[242,3,1,""],cache_lock_bypass:[242,3,1,""],check:[242,3,1,""],check_lockstring:[242,3,1,""],clear:[242,3,1,""],get:[242,3,1,""],remove:[242,3,1,""],replace:[242,3,1,""],reset:[242,3,1,""],validate:[242,3,1,""]},"evennia.objects":{admin:[244,0,0,"-"],manager:[245,0,0,"-"],models:[246,0,0,"-"],objects:[247,0,0,"-"]},"evennia.objects.admin":{ObjectAttributeInline:[244,1,1,""],ObjectCreateForm:[244,1,1,""],ObjectDBAdmin:[244,1,1,""],ObjectEditForm:[244,1,1,""],ObjectTagInline:[244,1,1,""]},"evennia.objects.admin.ObjectAttributeInline":{media:[244,3,1,""],model:[244,4,1,""],related_field:[244,4,1,""]},"evennia.objects.admin.ObjectCreateForm":{Meta:[244,1,1,""],base_fields:[244,4,1,""],declared_fields:[244,4,1,""],media:[244,3,1,""],raw_id_fields:[244,4,1,""]},"evennia.objects.admin.ObjectCreateForm.Meta":{fields:[244,4,1,""],model:[244,4,1,""]},"evennia.objects.admin.ObjectDBAdmin":{add_fieldsets:[244,4,1,""],add_form:[244,4,1,""],fieldsets:[244,4,1,""],form:[244,4,1,""],get_fieldsets:[244,3,1,""],get_form:[244,3,1,""],inlines:[244,4,1,""],list_display:[244,4,1,""],list_display_links:[244,4,1,""],list_filter:[244,4,1,""],list_select_related:[244,4,1,""],media:[244,3,1,""],ordering:[244,4,1,""],raw_id_fields:[244,4,1,""],response_add:[244,3,1,""],save_as:[244,4,1,""],save_model:[244,3,1,""],save_on_top:[244,4,1,""],search_fields:[244,4,1,""]},"evennia.objects.admin.ObjectEditForm":{Meta:[244,1,1,""],base_fields:[244,4,1,""],declared_fields:[244,4,1,""],media:[244,3,1,""]},"evennia.objects.admin.ObjectEditForm.Meta":{fields:[244,4,1,""]},"evennia.objects.admin.ObjectTagInline":{media:[244,3,1,""],model:[244,4,1,""],related_field:[244,4,1,""]},"evennia.objects.manager":{ObjectDBManager:[245,1,1,""],ObjectManager:[245,1,1,""]},"evennia.objects.manager.ObjectDBManager":{clear_all_sessids:[245,3,1,""],copy_object:[245,3,1,""],get_contents:[245,3,1,""],get_object_with_account:[245,3,1,""],get_objs_with_attr:[245,3,1,""],get_objs_with_attr_value:[245,3,1,""],get_objs_with_db_property:[245,3,1,""],get_objs_with_db_property_value:[245,3,1,""],get_objs_with_key_and_typeclass:[245,3,1,""],get_objs_with_key_or_alias:[245,3,1,""],object_search:[245,3,1,""],search:[245,3,1,""],search_object:[245,3,1,""]},"evennia.objects.models":{ContentsHandler:[246,1,1,""],ObjectDB:[246,1,1,""]},"evennia.objects.models.ContentsHandler":{__init__:[246,3,1,""],add:[246,3,1,""],clear:[246,3,1,""],get:[246,3,1,""],init:[246,3,1,""],remove:[246,3,1,""]},"evennia.objects.models.ObjectDB":{DoesNotExist:[246,2,1,""],MultipleObjectsReturned:[246,2,1,""],account:[246,3,1,""],at_db_location_postsave:[246,3,1,""],cmdset_storage:[246,3,1,""],contents_cache:[246,4,1,""],db_account:[246,4,1,""],db_account_id:[246,4,1,""],db_attributes:[246,4,1,""],db_cmdset_storage:[246,4,1,""],db_destination:[246,4,1,""],db_destination_id:[246,4,1,""],db_home:[246,4,1,""],db_home_id:[246,4,1,""],db_location:[246,4,1,""],db_location_id:[246,4,1,""],db_sessid:[246,4,1,""],db_tags:[246,4,1,""],destination:[246,3,1,""],destinations_set:[246,4,1,""],get_next_by_db_date_created:[246,3,1,""],get_previous_by_db_date_created:[246,3,1,""],hide_from_objects_set:[246,4,1,""],home:[246,3,1,""],homes_set:[246,4,1,""],id:[246,4,1,""],location:[246,3,1,""],locations_set:[246,4,1,""],object_subscription_set:[246,4,1,""],objects:[246,4,1,""],path:[246,4,1,""],receiver_object_set:[246,4,1,""],scriptdb_set:[246,4,1,""],sender_object_set:[246,4,1,""],sessid:[246,3,1,""],typename:[246,4,1,""]},"evennia.objects.objects":{DefaultCharacter:[247,1,1,""],DefaultExit:[247,1,1,""],DefaultObject:[247,1,1,""],DefaultRoom:[247,1,1,""],ExitCommand:[247,1,1,""],ObjectSessionHandler:[247,1,1,""]},"evennia.objects.objects.DefaultCharacter":{DoesNotExist:[247,2,1,""],MultipleObjectsReturned:[247,2,1,""],at_after_move:[247,3,1,""],at_post_puppet:[247,3,1,""],at_post_unpuppet:[247,3,1,""],at_pre_puppet:[247,3,1,""],basetype_setup:[247,3,1,""],connection_time:[247,3,1,""],create:[247,3,1,""],idle_time:[247,3,1,""],lockstring:[247,4,1,""],path:[247,4,1,""],typename:[247,4,1,""]},"evennia.objects.objects.DefaultExit":{DoesNotExist:[247,2,1,""],MultipleObjectsReturned:[247,2,1,""],at_cmdset_get:[247,3,1,""],at_failed_traverse:[247,3,1,""],at_init:[247,3,1,""],at_traverse:[247,3,1,""],basetype_setup:[247,3,1,""],create:[247,3,1,""],create_exit_cmdset:[247,3,1,""],exit_command:[247,4,1,""],lockstring:[247,4,1,""],path:[247,4,1,""],priority:[247,4,1,""],typename:[247,4,1,""]},"evennia.objects.objects.DefaultObject":{"delete":[247,3,1,""],DoesNotExist:[247,2,1,""],MultipleObjectsReturned:[247,2,1,""],access:[247,3,1,""],announce_move_from:[247,3,1,""],announce_move_to:[247,3,1,""],at_access:[247,3,1,""],at_after_move:[247,3,1,""],at_after_traverse:[247,3,1,""],at_before_drop:[247,3,1,""],at_before_get:[247,3,1,""],at_before_give:[247,3,1,""],at_before_move:[247,3,1,""],at_before_say:[247,3,1,""],at_cmdset_get:[247,3,1,""],at_desc:[247,3,1,""],at_drop:[247,3,1,""],at_failed_traverse:[247,3,1,""],at_first_save:[247,3,1,""],at_get:[247,3,1,""],at_give:[247,3,1,""],at_init:[247,3,1,""],at_look:[247,3,1,""],at_msg_receive:[247,3,1,""],at_msg_send:[247,3,1,""],at_object_creation:[247,3,1,""],at_object_delete:[247,3,1,""],at_object_leave:[247,3,1,""],at_object_post_copy:[247,3,1,""],at_object_receive:[247,3,1,""],at_post_puppet:[247,3,1,""],at_post_unpuppet:[247,3,1,""],at_pre_puppet:[247,3,1,""],at_pre_unpuppet:[247,3,1,""],at_say:[247,3,1,""],at_server_reload:[247,3,1,""],at_server_shutdown:[247,3,1,""],at_traverse:[247,3,1,""],basetype_posthook_setup:[247,3,1,""],basetype_setup:[247,3,1,""],clear_contents:[247,3,1,""],clear_exits:[247,3,1,""],cmdset:[247,4,1,""],contents:[247,3,1,""],contents_get:[247,3,1,""],contents_set:[247,3,1,""],copy:[247,3,1,""],create:[247,3,1,""],execute_cmd:[247,3,1,""],exits:[247,3,1,""],for_contents:[247,3,1,""],get_display_name:[247,3,1,""],get_numbered_name:[247,3,1,""],has_account:[247,3,1,""],is_connected:[247,3,1,""],is_superuser:[247,3,1,""],lockstring:[247,4,1,""],move_to:[247,3,1,""],msg:[247,3,1,""],msg_contents:[247,3,1,""],nicks:[247,4,1,""],objects:[247,4,1,""],path:[247,4,1,""],return_appearance:[247,3,1,""],scripts:[247,4,1,""],search:[247,3,1,""],search_account:[247,3,1,""],sessions:[247,4,1,""],typename:[247,4,1,""]},"evennia.objects.objects.DefaultRoom":{DoesNotExist:[247,2,1,""],MultipleObjectsReturned:[247,2,1,""],basetype_setup:[247,3,1,""],create:[247,3,1,""],lockstring:[247,4,1,""],path:[247,4,1,""],typename:[247,4,1,""]},"evennia.objects.objects.ExitCommand":{aliases:[247,4,1,""],func:[247,3,1,""],get_extra_info:[247,3,1,""],help_category:[247,4,1,""],key:[247,4,1,""],lock_storage:[247,4,1,""],obj:[247,4,1,""]},"evennia.objects.objects.ObjectSessionHandler":{__init__:[247,3,1,""],add:[247,3,1,""],all:[247,3,1,""],clear:[247,3,1,""],count:[247,3,1,""],get:[247,3,1,""],remove:[247,3,1,""]},"evennia.prototypes":{menus:[249,0,0,"-"],protfuncs:[250,0,0,"-"],prototypes:[251,0,0,"-"],spawner:[252,0,0,"-"]},"evennia.prototypes.menus":{OLCMenu:[249,1,1,""],node_apply_diff:[249,5,1,""],node_destination:[249,5,1,""],node_examine_entity:[249,5,1,""],node_home:[249,5,1,""],node_index:[249,5,1,""],node_key:[249,5,1,""],node_location:[249,5,1,""],node_prototype_desc:[249,5,1,""],node_prototype_key:[249,5,1,""],node_prototype_save:[249,5,1,""],node_prototype_spawn:[249,5,1,""],node_validate_prototype:[249,5,1,""],start_olc:[249,5,1,""]},"evennia.prototypes.menus.OLCMenu":{display_helptext:[249,3,1,""],helptext_formatter:[249,3,1,""],nodetext_formatter:[249,3,1,""],options_formatter:[249,3,1,""]},"evennia.prototypes.protfuncs":{add:[250,5,1,""],base_random:[250,5,1,""],center_justify:[250,5,1,""],choice:[250,5,1,""],dbref:[250,5,1,""],div:[250,5,1,""],eval:[250,5,1,""],full_justify:[250,5,1,""],left_justify:[250,5,1,""],mult:[250,5,1,""],obj:[250,5,1,""],objlist:[250,5,1,""],protkey:[250,5,1,""],randint:[250,5,1,""],random:[250,5,1,""],right_justify:[250,5,1,""],sub:[250,5,1,""],toint:[250,5,1,""]},"evennia.prototypes.prototypes":{DbPrototype:[251,1,1,""],PermissionError:[251,2,1,""],PrototypeEvMore:[251,1,1,""],ValidationError:[251,2,1,""],check_permission:[251,5,1,""],create_prototype:[251,5,1,""],delete_prototype:[251,5,1,""],format_available_protfuncs:[251,5,1,""],homogenize_prototype:[251,5,1,""],init_spawn_value:[251,5,1,""],list_prototypes:[251,5,1,""],load_module_prototypes:[251,5,1,""],protfunc_parser:[251,5,1,""],prototype_to_str:[251,5,1,""],save_prototype:[251,5,1,""],search_objects_with_prototype:[251,5,1,""],search_prototype:[251,5,1,""],validate_prototype:[251,5,1,""],value_to_obj:[251,5,1,""],value_to_obj_or_any:[251,5,1,""]},"evennia.prototypes.prototypes.DbPrototype":{DoesNotExist:[251,2,1,""],MultipleObjectsReturned:[251,2,1,""],at_script_creation:[251,3,1,""],path:[251,4,1,""],prototype:[251,3,1,""],typename:[251,4,1,""]},"evennia.prototypes.prototypes.PrototypeEvMore":{__init__:[251,3,1,""],init_pages:[251,3,1,""],page_formatter:[251,3,1,""],prototype_paginator:[251,3,1,""]},"evennia.prototypes.spawner":{Unset:[252,1,1,""],batch_create_object:[252,5,1,""],batch_update_objects_with_prototype:[252,5,1,""],flatten_diff:[252,5,1,""],flatten_prototype:[252,5,1,""],format_diff:[252,5,1,""],prototype_diff:[252,5,1,""],prototype_diff_from_object:[252,5,1,""],prototype_from_object:[252,5,1,""],spawn:[252,5,1,""]},"evennia.scripts":{admin:[254,0,0,"-"],manager:[255,0,0,"-"],models:[256,0,0,"-"],monitorhandler:[257,0,0,"-"],scripthandler:[258,0,0,"-"],scripts:[259,0,0,"-"],taskhandler:[260,0,0,"-"],tickerhandler:[261,0,0,"-"]},"evennia.scripts.admin":{ScriptAttributeInline:[254,1,1,""],ScriptDBAdmin:[254,1,1,""],ScriptTagInline:[254,1,1,""]},"evennia.scripts.admin.ScriptAttributeInline":{media:[254,3,1,""],model:[254,4,1,""],related_field:[254,4,1,""]},"evennia.scripts.admin.ScriptDBAdmin":{fieldsets:[254,4,1,""],inlines:[254,4,1,""],list_display:[254,4,1,""],list_display_links:[254,4,1,""],list_select_related:[254,4,1,""],media:[254,3,1,""],ordering:[254,4,1,""],raw_id_fields:[254,4,1,""],save_as:[254,4,1,""],save_model:[254,3,1,""],save_on_top:[254,4,1,""],search_fields:[254,4,1,""]},"evennia.scripts.admin.ScriptTagInline":{media:[254,3,1,""],model:[254,4,1,""],related_field:[254,4,1,""]},"evennia.scripts.manager":{ScriptDBManager:[255,1,1,""],ScriptManager:[255,1,1,""]},"evennia.scripts.manager.ScriptDBManager":{copy_script:[255,3,1,""],delete_script:[255,3,1,""],get_all_scripts:[255,3,1,""],get_all_scripts_on_obj:[255,3,1,""],remove_non_persistent:[255,3,1,""],script_search:[255,3,1,""],search_script:[255,3,1,""],validate:[255,3,1,""]},"evennia.scripts.models":{ScriptDB:[256,1,1,""]},"evennia.scripts.models.ScriptDB":{DoesNotExist:[256,2,1,""],MultipleObjectsReturned:[256,2,1,""],account:[256,3,1,""],db_account:[256,4,1,""],db_account_id:[256,4,1,""],db_attributes:[256,4,1,""],db_desc:[256,4,1,""],db_interval:[256,4,1,""],db_is_active:[256,4,1,""],db_obj:[256,4,1,""],db_obj_id:[256,4,1,""],db_persistent:[256,4,1,""],db_repeats:[256,4,1,""],db_start_delay:[256,4,1,""],db_tags:[256,4,1,""],desc:[256,3,1,""],get_next_by_db_date_created:[256,3,1,""],get_previous_by_db_date_created:[256,3,1,""],id:[256,4,1,""],interval:[256,3,1,""],is_active:[256,3,1,""],obj:[256,3,1,""],object:[256,3,1,""],objects:[256,4,1,""],path:[256,4,1,""],persistent:[256,3,1,""],receiver_script_set:[256,4,1,""],repeats:[256,3,1,""],sender_script_set:[256,4,1,""],start_delay:[256,3,1,""],typename:[256,4,1,""]},"evennia.scripts.monitorhandler":{MonitorHandler:[257,1,1,""]},"evennia.scripts.monitorhandler.MonitorHandler":{__init__:[257,3,1,""],add:[257,3,1,""],all:[257,3,1,""],at_update:[257,3,1,""],clear:[257,3,1,""],remove:[257,3,1,""],restore:[257,3,1,""],save:[257,3,1,""]},"evennia.scripts.scripthandler":{ScriptHandler:[258,1,1,""]},"evennia.scripts.scripthandler.ScriptHandler":{"delete":[258,3,1,""],__init__:[258,3,1,""],add:[258,3,1,""],all:[258,3,1,""],get:[258,3,1,""],start:[258,3,1,""],stop:[258,3,1,""],validate:[258,3,1,""]},"evennia.scripts.scripts":{DefaultScript:[259,1,1,""],DoNothing:[259,1,1,""],Store:[259,1,1,""]},"evennia.scripts.scripts.DefaultScript":{DoesNotExist:[259,2,1,""],MultipleObjectsReturned:[259,2,1,""],at_idmapper_flush:[259,3,1,""],at_repeat:[259,3,1,""],at_script_creation:[259,3,1,""],at_server_reload:[259,3,1,""],at_server_shutdown:[259,3,1,""],at_start:[259,3,1,""],at_stop:[259,3,1,""],create:[259,3,1,""],force_repeat:[259,3,1,""],is_valid:[259,3,1,""],path:[259,4,1,""],pause:[259,3,1,""],remaining_repeats:[259,3,1,""],reset_callcount:[259,3,1,""],restart:[259,3,1,""],start:[259,3,1,""],stop:[259,3,1,""],time_until_next_repeat:[259,3,1,""],typename:[259,4,1,""],unpause:[259,3,1,""]},"evennia.scripts.scripts.DoNothing":{DoesNotExist:[259,2,1,""],MultipleObjectsReturned:[259,2,1,""],at_script_creation:[259,3,1,""],path:[259,4,1,""],typename:[259,4,1,""]},"evennia.scripts.scripts.Store":{DoesNotExist:[259,2,1,""],MultipleObjectsReturned:[259,2,1,""],at_script_creation:[259,3,1,""],path:[259,4,1,""],typename:[259,4,1,""]},"evennia.scripts.taskhandler":{TaskHandler:[260,1,1,""],TaskHandlerTask:[260,1,1,""],handle_error:[260,5,1,""]},"evennia.scripts.taskhandler.TaskHandler":{__init__:[260,3,1,""],active:[260,3,1,""],add:[260,3,1,""],call_task:[260,3,1,""],cancel:[260,3,1,""],clean_stale_tasks:[260,3,1,""],clear:[260,3,1,""],create_delays:[260,3,1,""],do_task:[260,3,1,""],exists:[260,3,1,""],get_deferred:[260,3,1,""],load:[260,3,1,""],remove:[260,3,1,""],save:[260,3,1,""]},"evennia.scripts.taskhandler.TaskHandlerTask":{__init__:[260,3,1,""],active:[260,3,1,"id6"],call:[260,3,1,"id3"],called:[260,3,1,""],cancel:[260,3,1,"id5"],do_task:[260,3,1,"id2"],exists:[260,3,1,"id7"],get_deferred:[260,3,1,""],get_id:[260,3,1,"id8"],pause:[260,3,1,"id0"],paused:[260,3,1,""],remove:[260,3,1,"id4"],unpause:[260,3,1,"id1"]},"evennia.scripts.tickerhandler":{Ticker:[261,1,1,""],TickerHandler:[261,1,1,""],TickerPool:[261,1,1,""]},"evennia.scripts.tickerhandler.Ticker":{__init__:[261,3,1,""],add:[261,3,1,""],remove:[261,3,1,""],stop:[261,3,1,""],validate:[261,3,1,""]},"evennia.scripts.tickerhandler.TickerHandler":{__init__:[261,3,1,""],add:[261,3,1,""],all:[261,3,1,""],all_display:[261,3,1,""],clear:[261,3,1,""],remove:[261,3,1,""],restore:[261,3,1,""],save:[261,3,1,""],ticker_pool_class:[261,4,1,""]},"evennia.scripts.tickerhandler.TickerPool":{__init__:[261,3,1,""],add:[261,3,1,""],remove:[261,3,1,""],stop:[261,3,1,""],ticker_class:[261,4,1,""]},"evennia.server":{admin:[263,0,0,"-"],amp_client:[264,0,0,"-"],connection_wizard:[265,0,0,"-"],deprecations:[266,0,0,"-"],evennia_launcher:[267,0,0,"-"],game_index_client:[268,0,0,"-"],initial_setup:[271,0,0,"-"],inputfuncs:[272,0,0,"-"],manager:[273,0,0,"-"],models:[274,0,0,"-"],portal:[275,0,0,"-"],profiling:[297,0,0,"-"],server:[305,0,0,"-"],serversession:[306,0,0,"-"],session:[307,0,0,"-"],sessionhandler:[308,0,0,"-"],signals:[309,0,0,"-"],throttle:[310,0,0,"-"],validators:[311,0,0,"-"],webserver:[312,0,0,"-"]},"evennia.server.admin":{ServerConfigAdmin:[263,1,1,""]},"evennia.server.admin.ServerConfigAdmin":{list_display:[263,4,1,""],list_display_links:[263,4,1,""],list_select_related:[263,4,1,""],media:[263,3,1,""],ordering:[263,4,1,""],save_as:[263,4,1,""],save_on_top:[263,4,1,""],search_fields:[263,4,1,""]},"evennia.server.amp_client":{AMPClientFactory:[264,1,1,""],AMPServerClientProtocol:[264,1,1,""]},"evennia.server.amp_client.AMPClientFactory":{__init__:[264,3,1,""],buildProtocol:[264,3,1,""],clientConnectionFailed:[264,3,1,""],clientConnectionLost:[264,3,1,""],factor:[264,4,1,""],initialDelay:[264,4,1,""],maxDelay:[264,4,1,""],noisy:[264,4,1,""],startedConnecting:[264,3,1,""]},"evennia.server.amp_client.AMPServerClientProtocol":{connectionMade:[264,3,1,""],data_to_portal:[264,3,1,""],send_AdminServer2Portal:[264,3,1,""],send_MsgServer2Portal:[264,3,1,""],server_receive_adminportal2server:[264,3,1,""],server_receive_msgportal2server:[264,3,1,""],server_receive_status:[264,3,1,""]},"evennia.server.connection_wizard":{ConnectionWizard:[265,1,1,""],node_game_index_fields:[265,5,1,""],node_game_index_start:[265,5,1,""],node_mssp_start:[265,5,1,""],node_start:[265,5,1,""],node_view_and_apply_settings:[265,5,1,""]},"evennia.server.connection_wizard.ConnectionWizard":{__init__:[265,3,1,""],ask_choice:[265,3,1,""],ask_continue:[265,3,1,""],ask_input:[265,3,1,""],ask_node:[265,3,1,""],ask_yesno:[265,3,1,""],display:[265,3,1,""]},"evennia.server.deprecations":{check_errors:[266,5,1,""],check_warnings:[266,5,1,""]},"evennia.server.evennia_launcher":{AMPLauncherProtocol:[267,1,1,""],MsgLauncher2Portal:[267,1,1,""],MsgStatus:[267,1,1,""],check_database:[267,5,1,""],check_main_evennia_dependencies:[267,5,1,""],collectstatic:[267,5,1,""],create_game_directory:[267,5,1,""],create_secret_key:[267,5,1,""],create_settings_file:[267,5,1,""],create_superuser:[267,5,1,""],del_pid:[267,5,1,""],error_check_python_modules:[267,5,1,""],evennia_version:[267,5,1,""],get_pid:[267,5,1,""],getenv:[267,5,1,""],init_game_directory:[267,5,1,""],kill:[267,5,1,""],list_settings:[267,5,1,""],main:[267,5,1,""],query_info:[267,5,1,""],query_status:[267,5,1,""],reboot_evennia:[267,5,1,""],reload_evennia:[267,5,1,""],run_connect_wizard:[267,5,1,""],run_dummyrunner:[267,5,1,""],run_menu:[267,5,1,""],send_instruction:[267,5,1,""],set_gamedir:[267,5,1,""],show_version_info:[267,5,1,""],start_evennia:[267,5,1,""],start_only_server:[267,5,1,""],start_portal_interactive:[267,5,1,""],start_server_interactive:[267,5,1,""],stop_evennia:[267,5,1,""],stop_server_only:[267,5,1,""],tail_log_files:[267,5,1,""],wait_for_status:[267,5,1,""],wait_for_status_reply:[267,5,1,""]},"evennia.server.evennia_launcher.AMPLauncherProtocol":{__init__:[267,3,1,""],receive_status_from_portal:[267,3,1,""],wait_for_status:[267,3,1,""]},"evennia.server.evennia_launcher.MsgLauncher2Portal":{allErrors:[267,4,1,""],arguments:[267,4,1,""],commandName:[267,4,1,""],errors:[267,4,1,""],key:[267,4,1,""],response:[267,4,1,""],reverseErrors:[267,4,1,""]},"evennia.server.evennia_launcher.MsgStatus":{allErrors:[267,4,1,""],arguments:[267,4,1,""],commandName:[267,4,1,""],errors:[267,4,1,""],key:[267,4,1,""],response:[267,4,1,""],reverseErrors:[267,4,1,""]},"evennia.server.game_index_client":{client:[269,0,0,"-"],service:[270,0,0,"-"]},"evennia.server.game_index_client.client":{EvenniaGameIndexClient:[269,1,1,""],QuietHTTP11ClientFactory:[269,1,1,""],SimpleResponseReceiver:[269,1,1,""],StringProducer:[269,1,1,""]},"evennia.server.game_index_client.client.EvenniaGameIndexClient":{__init__:[269,3,1,""],handle_egd_response:[269,3,1,""],send_game_details:[269,3,1,""]},"evennia.server.game_index_client.client.QuietHTTP11ClientFactory":{noisy:[269,4,1,""]},"evennia.server.game_index_client.client.SimpleResponseReceiver":{__init__:[269,3,1,""],connectionLost:[269,3,1,""],dataReceived:[269,3,1,""]},"evennia.server.game_index_client.client.StringProducer":{__init__:[269,3,1,""],pauseProducing:[269,3,1,""],startProducing:[269,3,1,""],stopProducing:[269,3,1,""]},"evennia.server.game_index_client.service":{EvenniaGameIndexService:[270,1,1,""]},"evennia.server.game_index_client.service.EvenniaGameIndexService":{__init__:[270,3,1,""],name:[270,4,1,""],startService:[270,3,1,""],stopService:[270,3,1,""]},"evennia.server.initial_setup":{at_initial_setup:[271,5,1,""],collectstatic:[271,5,1,""],create_channels:[271,5,1,""],create_objects:[271,5,1,""],get_god_account:[271,5,1,""],handle_setup:[271,5,1,""],reset_server:[271,5,1,""]},"evennia.server.inputfuncs":{"default":[272,5,1,""],bot_data_in:[272,5,1,""],client_gui:[272,5,1,""],client_options:[272,5,1,""],echo:[272,5,1,""],external_discord_hello:[272,5,1,""],get_client_options:[272,5,1,""],get_inputfuncs:[272,5,1,""],get_value:[272,5,1,""],hello:[272,5,1,""],login:[272,5,1,""],monitor:[272,5,1,""],monitored:[272,5,1,""],msdp_list:[272,5,1,""],msdp_report:[272,5,1,""],msdp_send:[272,5,1,""],msdp_unreport:[272,5,1,""],repeat:[272,5,1,""],supports_set:[272,5,1,""],text:[272,5,1,""],unmonitor:[272,5,1,""],unrepeat:[272,5,1,""],webclient_options:[272,5,1,""]},"evennia.server.manager":{ServerConfigManager:[273,1,1,""]},"evennia.server.manager.ServerConfigManager":{conf:[273,3,1,""]},"evennia.server.models":{ServerConfig:[274,1,1,""]},"evennia.server.models.ServerConfig":{DoesNotExist:[274,2,1,""],MultipleObjectsReturned:[274,2,1,""],db_key:[274,4,1,""],db_value:[274,4,1,""],id:[274,4,1,""],key:[274,3,1,""],objects:[274,4,1,""],path:[274,4,1,""],store:[274,3,1,""],typename:[274,4,1,""],value:[274,3,1,""]},"evennia.server.portal":{amp:[276,0,0,"-"],amp_server:[277,0,0,"-"],grapevine:[278,0,0,"-"],irc:[279,0,0,"-"],mccp:[280,0,0,"-"],mssp:[281,0,0,"-"],mxp:[282,0,0,"-"],naws:[283,0,0,"-"],portal:[284,0,0,"-"],portalsessionhandler:[285,0,0,"-"],rss:[286,0,0,"-"],ssh:[287,0,0,"-"],ssl:[288,0,0,"-"],suppress_ga:[289,0,0,"-"],telnet:[290,0,0,"-"],telnet_oob:[291,0,0,"-"],telnet_ssl:[292,0,0,"-"],tests:[293,0,0,"-"],ttype:[294,0,0,"-"],webclient:[295,0,0,"-"],webclient_ajax:[296,0,0,"-"]},"evennia.server.portal.amp":{AMPMultiConnectionProtocol:[276,1,1,""],AdminPortal2Server:[276,1,1,""],AdminServer2Portal:[276,1,1,""],Compressed:[276,1,1,""],FunctionCall:[276,1,1,""],MsgLauncher2Portal:[276,1,1,""],MsgPortal2Server:[276,1,1,""],MsgServer2Portal:[276,1,1,""],MsgStatus:[276,1,1,""],dumps:[276,5,1,""],loads:[276,5,1,""]},"evennia.server.portal.amp.AMPMultiConnectionProtocol":{__init__:[276,3,1,""],broadcast:[276,3,1,""],connectionLost:[276,3,1,""],connectionMade:[276,3,1,""],dataReceived:[276,3,1,""],data_in:[276,3,1,""],errback:[276,3,1,""],makeConnection:[276,3,1,""],receive_functioncall:[276,3,1,""],send_FunctionCall:[276,3,1,""]},"evennia.server.portal.amp.AdminPortal2Server":{allErrors:[276,4,1,""],arguments:[276,4,1,""],commandName:[276,4,1,""],errors:[276,4,1,""],key:[276,4,1,""],response:[276,4,1,""],reverseErrors:[276,4,1,""]},"evennia.server.portal.amp.AdminServer2Portal":{allErrors:[276,4,1,""],arguments:[276,4,1,""],commandName:[276,4,1,""],errors:[276,4,1,""],key:[276,4,1,""],response:[276,4,1,""],reverseErrors:[276,4,1,""]},"evennia.server.portal.amp.Compressed":{fromBox:[276,3,1,""],fromString:[276,3,1,""],toBox:[276,3,1,""],toString:[276,3,1,""]},"evennia.server.portal.amp.FunctionCall":{allErrors:[276,4,1,""],arguments:[276,4,1,""],commandName:[276,4,1,""],errors:[276,4,1,""],key:[276,4,1,""],response:[276,4,1,""],reverseErrors:[276,4,1,""]},"evennia.server.portal.amp.MsgLauncher2Portal":{allErrors:[276,4,1,""],arguments:[276,4,1,""],commandName:[276,4,1,""],errors:[276,4,1,""],key:[276,4,1,""],response:[276,4,1,""],reverseErrors:[276,4,1,""]},"evennia.server.portal.amp.MsgPortal2Server":{allErrors:[276,4,1,""],arguments:[276,4,1,""],commandName:[276,4,1,""],errors:[276,4,1,""],key:[276,4,1,""],response:[276,4,1,""],reverseErrors:[276,4,1,""]},"evennia.server.portal.amp.MsgServer2Portal":{allErrors:[276,4,1,""],arguments:[276,4,1,""],commandName:[276,4,1,""],errors:[276,4,1,""],key:[276,4,1,""],response:[276,4,1,""],reverseErrors:[276,4,1,""]},"evennia.server.portal.amp.MsgStatus":{allErrors:[276,4,1,""],arguments:[276,4,1,""],commandName:[276,4,1,""],errors:[276,4,1,""],key:[276,4,1,""],response:[276,4,1,""],reverseErrors:[276,4,1,""]},"evennia.server.portal.amp_server":{AMPServerFactory:[277,1,1,""],AMPServerProtocol:[277,1,1,""],getenv:[277,5,1,""]},"evennia.server.portal.amp_server.AMPServerFactory":{__init__:[277,3,1,""],buildProtocol:[277,3,1,""],logPrefix:[277,3,1,""],noisy:[277,4,1,""]},"evennia.server.portal.amp_server.AMPServerProtocol":{connectionLost:[277,3,1,""],data_to_server:[277,3,1,""],get_status:[277,3,1,""],portal_receive_adminserver2portal:[277,3,1,""],portal_receive_launcher2portal:[277,3,1,""],portal_receive_server2portal:[277,3,1,""],portal_receive_status:[277,3,1,""],send_AdminPortal2Server:[277,3,1,""],send_MsgPortal2Server:[277,3,1,""],send_Status2Launcher:[277,3,1,""],start_server:[277,3,1,""],stop_server:[277,3,1,""],wait_for_disconnect:[277,3,1,""],wait_for_server_connect:[277,3,1,""]},"evennia.server.portal.grapevine":{GrapevineClient:[278,1,1,""],RestartingWebsocketServerFactory:[278,1,1,""]},"evennia.server.portal.grapevine.GrapevineClient":{__init__:[278,3,1,""],at_login:[278,3,1,""],data_in:[278,3,1,""],disconnect:[278,3,1,""],onClose:[278,3,1,""],onMessage:[278,3,1,""],onOpen:[278,3,1,""],send_authenticate:[278,3,1,""],send_channel:[278,3,1,""],send_default:[278,3,1,""],send_heartbeat:[278,3,1,""],send_subscribe:[278,3,1,""],send_unsubscribe:[278,3,1,""]},"evennia.server.portal.grapevine.RestartingWebsocketServerFactory":{__init__:[278,3,1,""],buildProtocol:[278,3,1,""],clientConnectionFailed:[278,3,1,""],clientConnectionLost:[278,3,1,""],factor:[278,4,1,""],initialDelay:[278,4,1,""],maxDelay:[278,4,1,""],reconnect:[278,3,1,""],start:[278,3,1,""],startedConnecting:[278,3,1,""]},"evennia.server.portal.irc":{IRCBot:[279,1,1,""],IRCBotFactory:[279,1,1,""],parse_ansi_to_irc:[279,5,1,""],parse_irc_to_ansi:[279,5,1,""]},"evennia.server.portal.irc.IRCBot":{action:[279,3,1,""],at_login:[279,3,1,""],channel:[279,4,1,""],data_in:[279,3,1,""],disconnect:[279,3,1,""],factory:[279,4,1,""],get_nicklist:[279,3,1,""],irc_RPL_ENDOFNAMES:[279,3,1,""],irc_RPL_NAMREPLY:[279,3,1,""],lineRate:[279,4,1,""],logger:[279,4,1,""],nickname:[279,4,1,""],pong:[279,3,1,""],privmsg:[279,3,1,""],send_channel:[279,3,1,""],send_default:[279,3,1,""],send_ping:[279,3,1,""],send_privmsg:[279,3,1,""],send_reconnect:[279,3,1,""],send_request_nicklist:[279,3,1,""],signedOn:[279,3,1,""],sourceURL:[279,4,1,""]},"evennia.server.portal.irc.IRCBotFactory":{__init__:[279,3,1,""],buildProtocol:[279,3,1,""],clientConnectionFailed:[279,3,1,""],clientConnectionLost:[279,3,1,""],factor:[279,4,1,""],initialDelay:[279,4,1,""],maxDelay:[279,4,1,""],reconnect:[279,3,1,""],start:[279,3,1,""],startedConnecting:[279,3,1,""]},"evennia.server.portal.mccp":{Mccp:[280,1,1,""],mccp_compress:[280,5,1,""]},"evennia.server.portal.mccp.Mccp":{__init__:[280,3,1,""],do_mccp:[280,3,1,""],no_mccp:[280,3,1,""]},"evennia.server.portal.mssp":{Mssp:[281,1,1,""]},"evennia.server.portal.mssp.Mssp":{__init__:[281,3,1,""],do_mssp:[281,3,1,""],get_player_count:[281,3,1,""],get_uptime:[281,3,1,""],no_mssp:[281,3,1,""]},"evennia.server.portal.mxp":{Mxp:[282,1,1,""],mxp_parse:[282,5,1,""]},"evennia.server.portal.mxp.Mxp":{__init__:[282,3,1,""],do_mxp:[282,3,1,""],no_mxp:[282,3,1,""]},"evennia.server.portal.naws":{Naws:[283,1,1,""]},"evennia.server.portal.naws.Naws":{__init__:[283,3,1,""],do_naws:[283,3,1,""],negotiate_sizes:[283,3,1,""],no_naws:[283,3,1,""]},"evennia.server.portal.portal":{Portal:[284,1,1,""],Websocket:[284,1,1,""]},"evennia.server.portal.portal.Portal":{__init__:[284,3,1,""],get_info_dict:[284,3,1,""],shutdown:[284,3,1,""]},"evennia.server.portal.portalsessionhandler":{PortalSessionHandler:[285,1,1,""]},"evennia.server.portal.portalsessionhandler.PortalSessionHandler":{__init__:[285,3,1,""],announce_all:[285,3,1,""],at_server_connection:[285,3,1,""],connect:[285,3,1,""],count_loggedin:[285,3,1,""],data_in:[285,3,1,""],data_out:[285,3,1,""],disconnect:[285,3,1,""],disconnect_all:[285,3,1,""],server_connect:[285,3,1,""],server_disconnect:[285,3,1,""],server_disconnect_all:[285,3,1,""],server_logged_in:[285,3,1,""],server_session_sync:[285,3,1,""],sessions_from_csessid:[285,3,1,""],sync:[285,3,1,""]},"evennia.server.portal.rss":{RSSBotFactory:[286,1,1,""],RSSReader:[286,1,1,""]},"evennia.server.portal.rss.RSSBotFactory":{__init__:[286,3,1,""],start:[286,3,1,""]},"evennia.server.portal.rss.RSSReader":{__init__:[286,3,1,""],data_in:[286,3,1,""],disconnect:[286,3,1,""],get_new:[286,3,1,""],update:[286,3,1,""]},"evennia.server.portal.ssh":{AccountDBPasswordChecker:[287,1,1,""],ExtraInfoAuthServer:[287,1,1,""],PassAvatarIdTerminalRealm:[287,1,1,""],SSHServerFactory:[287,1,1,""],SshProtocol:[287,1,1,""],TerminalSessionTransport_getPeer:[287,1,1,""],getKeyPair:[287,5,1,""],makeFactory:[287,5,1,""]},"evennia.server.portal.ssh.AccountDBPasswordChecker":{__init__:[287,3,1,""],credentialInterfaces:[287,4,1,""],noisy:[287,4,1,""],requestAvatarId:[287,3,1,""]},"evennia.server.portal.ssh.ExtraInfoAuthServer":{auth_password:[287,3,1,""],noisy:[287,4,1,""]},"evennia.server.portal.ssh.PassAvatarIdTerminalRealm":{noisy:[287,4,1,""]},"evennia.server.portal.ssh.SSHServerFactory":{logPrefix:[287,3,1,""],noisy:[287,4,1,""]},"evennia.server.portal.ssh.SshProtocol":{__init__:[287,3,1,""],at_login:[287,3,1,""],connectionLost:[287,3,1,""],connectionMade:[287,3,1,""],data_out:[287,3,1,""],disconnect:[287,3,1,""],getClientAddress:[287,3,1,""],handle_EOF:[287,3,1,""],handle_FF:[287,3,1,""],handle_INT:[287,3,1,""],handle_QUIT:[287,3,1,""],lineReceived:[287,3,1,""],noisy:[287,4,1,""],sendLine:[287,3,1,""],send_default:[287,3,1,""],send_prompt:[287,3,1,""],send_text:[287,3,1,""],terminalSize:[287,3,1,""]},"evennia.server.portal.ssh.TerminalSessionTransport_getPeer":{__init__:[287,3,1,""],noisy:[287,4,1,""]},"evennia.server.portal.ssl":{SSLProtocol:[288,1,1,""],getSSLContext:[288,5,1,""],verify_SSL_key_and_cert:[288,5,1,""]},"evennia.server.portal.ssl.SSLProtocol":{__init__:[288,3,1,""]},"evennia.server.portal.suppress_ga":{SuppressGA:[289,1,1,""]},"evennia.server.portal.suppress_ga.SuppressGA":{__init__:[289,3,1,""],will_suppress_ga:[289,3,1,""],wont_suppress_ga:[289,3,1,""]},"evennia.server.portal.telnet":{TelnetProtocol:[290,1,1,""],TelnetServerFactory:[290,1,1,""]},"evennia.server.portal.telnet.TelnetProtocol":{__init__:[290,3,1,""],applicationDataReceived:[290,3,1,""],at_login:[290,3,1,""],connectionLost:[290,3,1,""],connectionMade:[290,3,1,""],dataReceived:[290,3,1,""],data_in:[290,3,1,""],data_out:[290,3,1,""],disableLocal:[290,3,1,""],disableRemote:[290,3,1,""],disconnect:[290,3,1,""],enableLocal:[290,3,1,""],enableRemote:[290,3,1,""],handshake_done:[290,3,1,""],sendLine:[290,3,1,""],send_default:[290,3,1,""],send_prompt:[290,3,1,""],send_text:[290,3,1,""],toggle_nop_keepalive:[290,3,1,""]},"evennia.server.portal.telnet.TelnetServerFactory":{logPrefix:[290,3,1,""],noisy:[290,4,1,""]},"evennia.server.portal.telnet_oob":{TelnetOOB:[291,1,1,""]},"evennia.server.portal.telnet_oob.TelnetOOB":{__init__:[291,3,1,""],data_out:[291,3,1,""],decode_gmcp:[291,3,1,""],decode_msdp:[291,3,1,""],do_gmcp:[291,3,1,""],do_msdp:[291,3,1,""],encode_gmcp:[291,3,1,""],encode_msdp:[291,3,1,""],no_gmcp:[291,3,1,""],no_msdp:[291,3,1,""]},"evennia.server.portal.telnet_ssl":{SSLProtocol:[292,1,1,""],getSSLContext:[292,5,1,""],verify_or_create_SSL_key_and_cert:[292,5,1,""]},"evennia.server.portal.telnet_ssl.SSLProtocol":{__init__:[292,3,1,""]},"evennia.server.portal.tests":{TestAMPServer:[293,1,1,""],TestIRC:[293,1,1,""],TestTelnet:[293,1,1,""],TestWebSocket:[293,1,1,""]},"evennia.server.portal.tests.TestAMPServer":{setUp:[293,3,1,""],test_amp_in:[293,3,1,""],test_amp_out:[293,3,1,""],test_large_msg:[293,3,1,""]},"evennia.server.portal.tests.TestIRC":{test_bold:[293,3,1,""],test_colors:[293,3,1,""],test_identity:[293,3,1,""],test_italic:[293,3,1,""],test_plain_ansi:[293,3,1,""]},"evennia.server.portal.tests.TestTelnet":{setUp:[293,3,1,""],test_mudlet_ttype:[293,3,1,""]},"evennia.server.portal.tests.TestWebSocket":{setUp:[293,3,1,""],tearDown:[293,3,1,""],test_data_in:[293,3,1,""],test_data_out:[293,3,1,""]},"evennia.server.portal.ttype":{Ttype:[294,1,1,""]},"evennia.server.portal.ttype.Ttype":{__init__:[294,3,1,""],will_ttype:[294,3,1,""],wont_ttype:[294,3,1,""]},"evennia.server.portal.webclient":{WebSocketClient:[295,1,1,""]},"evennia.server.portal.webclient.WebSocketClient":{__init__:[295,3,1,""],at_login:[295,3,1,""],data_in:[295,3,1,""],disconnect:[295,3,1,""],get_client_session:[295,3,1,""],nonce:[295,4,1,""],onClose:[295,3,1,""],onMessage:[295,3,1,""],onOpen:[295,3,1,""],sendLine:[295,3,1,""],send_default:[295,3,1,""],send_prompt:[295,3,1,""],send_text:[295,3,1,""]},"evennia.server.portal.webclient_ajax":{AjaxWebClient:[296,1,1,""],AjaxWebClientSession:[296,1,1,""],LazyEncoder:[296,1,1,""],jsonify:[296,5,1,""]},"evennia.server.portal.webclient_ajax.AjaxWebClient":{__init__:[296,3,1,""],allowedMethods:[296,4,1,""],at_login:[296,3,1,""],client_disconnect:[296,3,1,""],get_client_sessid:[296,3,1,""],isLeaf:[296,4,1,""],lineSend:[296,3,1,""],mode_close:[296,3,1,""],mode_init:[296,3,1,""],mode_input:[296,3,1,""],mode_keepalive:[296,3,1,""],mode_receive:[296,3,1,""],render_POST:[296,3,1,""]},"evennia.server.portal.webclient_ajax.AjaxWebClientSession":{__init__:[296,3,1,""],at_login:[296,3,1,""],data_in:[296,3,1,""],data_out:[296,3,1,""],disconnect:[296,3,1,""],get_client_session:[296,3,1,""],send_default:[296,3,1,""],send_prompt:[296,3,1,""],send_text:[296,3,1,""]},"evennia.server.portal.webclient_ajax.LazyEncoder":{"default":[296,3,1,""]},"evennia.server.profiling":{dummyrunner:[298,0,0,"-"],dummyrunner_settings:[299,0,0,"-"],memplot:[300,0,0,"-"],settings_mixin:[301,0,0,"-"],test_queries:[302,0,0,"-"],tests:[303,0,0,"-"],timetrace:[304,0,0,"-"]},"evennia.server.profiling.dummyrunner":{DummyClient:[298,1,1,""],DummyFactory:[298,1,1,""],gidcounter:[298,5,1,""],idcounter:[298,5,1,""],makeiter:[298,5,1,""],start_all_dummy_clients:[298,5,1,""]},"evennia.server.profiling.dummyrunner.DummyClient":{connectionLost:[298,3,1,""],connectionMade:[298,3,1,""],counter:[298,3,1,""],dataReceived:[298,3,1,""],error:[298,3,1,""],logout:[298,3,1,""],step:[298,3,1,""]},"evennia.server.profiling.dummyrunner.DummyFactory":{__init__:[298,3,1,""],protocol:[298,4,1,""]},"evennia.server.profiling.dummyrunner_settings":{c_creates_button:[299,5,1,""],c_creates_obj:[299,5,1,""],c_digs:[299,5,1,""],c_examines:[299,5,1,""],c_help:[299,5,1,""],c_idles:[299,5,1,""],c_login:[299,5,1,""],c_login_nodig:[299,5,1,""],c_logout:[299,5,1,""],c_looks:[299,5,1,""],c_moves:[299,5,1,""],c_moves_n:[299,5,1,""],c_moves_s:[299,5,1,""],c_socialize:[299,5,1,""]},"evennia.server.profiling.memplot":{Memplot:[300,1,1,""]},"evennia.server.profiling.memplot.Memplot":{DoesNotExist:[300,2,1,""],MultipleObjectsReturned:[300,2,1,""],at_repeat:[300,3,1,""],at_script_creation:[300,3,1,""],path:[300,4,1,""],typename:[300,4,1,""]},"evennia.server.profiling.test_queries":{count_queries:[302,5,1,""]},"evennia.server.profiling.tests":{TestDummyrunnerSettings:[303,1,1,""],TestMemPlot:[303,1,1,""]},"evennia.server.profiling.tests.TestDummyrunnerSettings":{clear_client_lists:[303,3,1,""],perception_method_tests:[303,3,1,""],setUp:[303,3,1,""],test_c_creates_button:[303,3,1,""],test_c_creates_obj:[303,3,1,""],test_c_digs:[303,3,1,""],test_c_examines:[303,3,1,""],test_c_help:[303,3,1,""],test_c_login:[303,3,1,""],test_c_login_no_dig:[303,3,1,""],test_c_logout:[303,3,1,""],test_c_looks:[303,3,1,""],test_c_move_n:[303,3,1,""],test_c_move_s:[303,3,1,""],test_c_moves:[303,3,1,""],test_c_socialize:[303,3,1,""],test_idles:[303,3,1,""]},"evennia.server.profiling.tests.TestMemPlot":{test_memplot:[303,3,1,""]},"evennia.server.profiling.timetrace":{timetrace:[304,5,1,""]},"evennia.server.server":{Evennia:[305,1,1,""]},"evennia.server.server.Evennia":{__init__:[305,3,1,""],at_post_portal_sync:[305,3,1,""],at_server_cold_start:[305,3,1,""],at_server_cold_stop:[305,3,1,""],at_server_reload_start:[305,3,1,""],at_server_reload_stop:[305,3,1,""],at_server_start:[305,3,1,""],at_server_stop:[305,3,1,""],get_info_dict:[305,3,1,""],run_init_hooks:[305,3,1,""],run_initial_setup:[305,3,1,""],shutdown:[305,3,1,""],sqlite3_prep:[305,3,1,""],update_defaults:[305,3,1,""]},"evennia.server.serversession":{NAttributeHandler:[306,1,1,""],NDbHolder:[306,1,1,""],ServerSession:[306,1,1,""]},"evennia.server.serversession.NAttributeHandler":{__init__:[306,3,1,""],add:[306,3,1,""],all:[306,3,1,""],clear:[306,3,1,""],get:[306,3,1,""],has:[306,3,1,""],remove:[306,3,1,""]},"evennia.server.serversession.NDbHolder":{__init__:[306,3,1,""],all:[306,3,1,""],get_all:[306,3,1,""]},"evennia.server.serversession.ServerSession":{__init__:[306,3,1,""],access:[306,3,1,""],at_cmdset_get:[306,3,1,""],at_disconnect:[306,3,1,""],at_login:[306,3,1,""],at_sync:[306,3,1,""],attributes:[306,4,1,""],cmdset_storage:[306,3,1,""],data_in:[306,3,1,""],data_out:[306,3,1,""],db:[306,3,1,""],execute_cmd:[306,3,1,""],get_account:[306,3,1,""],get_character:[306,3,1,""],get_client_size:[306,3,1,""],get_puppet:[306,3,1,""],get_puppet_or_account:[306,3,1,""],log:[306,3,1,""],msg:[306,3,1,""],nattributes:[306,4,1,""],ndb:[306,3,1,""],ndb_del:[306,3,1,""],ndb_get:[306,3,1,""],ndb_set:[306,3,1,""],update_flags:[306,3,1,""],update_session_counters:[306,3,1,""]},"evennia.server.session":{Session:[307,1,1,""]},"evennia.server.session.Session":{at_sync:[307,3,1,""],data_in:[307,3,1,""],data_out:[307,3,1,""],disconnect:[307,3,1,""],get_sync_data:[307,3,1,""],init_session:[307,3,1,""],load_sync_data:[307,3,1,""]},"evennia.server.sessionhandler":{DummySession:[308,1,1,""],ServerSessionHandler:[308,1,1,""],SessionHandler:[308,1,1,""],delayed_import:[308,5,1,""]},"evennia.server.sessionhandler.DummySession":{sessid:[308,4,1,""]},"evennia.server.sessionhandler.ServerSessionHandler":{__init__:[308,3,1,""],account_count:[308,3,1,""],all_connected_accounts:[308,3,1,""],all_sessions_portal_sync:[308,3,1,""],announce_all:[308,3,1,""],call_inputfuncs:[308,3,1,""],data_in:[308,3,1,""],data_out:[308,3,1,""],disconnect:[308,3,1,""],disconnect_all_sessions:[308,3,1,""],disconnect_duplicate_sessions:[308,3,1,""],get_inputfuncs:[308,3,1,""],login:[308,3,1,""],portal_connect:[308,3,1,""],portal_disconnect:[308,3,1,""],portal_disconnect_all:[308,3,1,""],portal_reset_server:[308,3,1,""],portal_restart_server:[308,3,1,""],portal_session_sync:[308,3,1,""],portal_sessions_sync:[308,3,1,""],portal_shutdown:[308,3,1,""],session_from_account:[308,3,1,""],session_from_sessid:[308,3,1,""],session_portal_partial_sync:[308,3,1,""],session_portal_sync:[308,3,1,""],sessions_from_account:[308,3,1,""],sessions_from_character:[308,3,1,""],sessions_from_csessid:[308,3,1,""],sessions_from_puppet:[308,3,1,""],start_bot_session:[308,3,1,""],validate_sessions:[308,3,1,""]},"evennia.server.sessionhandler.SessionHandler":{clean_senddata:[308,3,1,""],get:[308,3,1,""],get_all_sync_data:[308,3,1,""],get_sessions:[308,3,1,""]},"evennia.server.throttle":{Throttle:[310,1,1,""]},"evennia.server.throttle.Throttle":{__init__:[310,3,1,""],check:[310,3,1,""],error_msg:[310,4,1,""],get:[310,3,1,""],update:[310,3,1,""]},"evennia.server.validators":{EvenniaPasswordValidator:[311,1,1,""],EvenniaUsernameAvailabilityValidator:[311,1,1,""]},"evennia.server.validators.EvenniaPasswordValidator":{__init__:[311,3,1,""],get_help_text:[311,3,1,""],validate:[311,3,1,""]},"evennia.server.webserver":{DjangoWebRoot:[312,1,1,""],EvenniaReverseProxyResource:[312,1,1,""],HTTPChannelWithXForwardedFor:[312,1,1,""],LockableThreadPool:[312,1,1,""],PrivateStaticRoot:[312,1,1,""],WSGIWebServer:[312,1,1,""],Website:[312,1,1,""]},"evennia.server.webserver.DjangoWebRoot":{__init__:[312,3,1,""],empty_threadpool:[312,3,1,""],getChild:[312,3,1,""]},"evennia.server.webserver.EvenniaReverseProxyResource":{getChild:[312,3,1,""],render:[312,3,1,""]},"evennia.server.webserver.HTTPChannelWithXForwardedFor":{allHeadersReceived:[312,3,1,""]},"evennia.server.webserver.LockableThreadPool":{__init__:[312,3,1,""],callInThread:[312,3,1,""],lock:[312,3,1,""]},"evennia.server.webserver.PrivateStaticRoot":{directoryListing:[312,3,1,""]},"evennia.server.webserver.WSGIWebServer":{__init__:[312,3,1,""],startService:[312,3,1,""],stopService:[312,3,1,""]},"evennia.server.webserver.Website":{log:[312,3,1,""],logPrefix:[312,3,1,""],noisy:[312,4,1,""]},"evennia.typeclasses":{admin:[315,0,0,"-"],attributes:[316,0,0,"-"],managers:[317,0,0,"-"],models:[318,0,0,"-"],tags:[319,0,0,"-"]},"evennia.typeclasses.admin":{AttributeForm:[315,1,1,""],AttributeFormSet:[315,1,1,""],AttributeInline:[315,1,1,""],TagAdmin:[315,1,1,""],TagForm:[315,1,1,""],TagFormSet:[315,1,1,""],TagInline:[315,1,1,""]},"evennia.typeclasses.admin.AttributeForm":{Meta:[315,1,1,""],__init__:[315,3,1,""],base_fields:[315,4,1,""],clean_attr_value:[315,3,1,""],declared_fields:[315,4,1,""],media:[315,3,1,""],save:[315,3,1,""]},"evennia.typeclasses.admin.AttributeForm.Meta":{fields:[315,4,1,""]},"evennia.typeclasses.admin.AttributeFormSet":{save:[315,3,1,""]},"evennia.typeclasses.admin.AttributeInline":{extra:[315,4,1,""],form:[315,4,1,""],formset:[315,4,1,""],get_formset:[315,3,1,""],media:[315,3,1,""],model:[315,4,1,""],related_field:[315,4,1,""]},"evennia.typeclasses.admin.TagAdmin":{fields:[315,4,1,""],list_display:[315,4,1,""],list_filter:[315,4,1,""],media:[315,3,1,""],search_fields:[315,4,1,""]},"evennia.typeclasses.admin.TagForm":{Meta:[315,1,1,""],__init__:[315,3,1,""],base_fields:[315,4,1,""],declared_fields:[315,4,1,""],media:[315,3,1,""],save:[315,3,1,""]},"evennia.typeclasses.admin.TagForm.Meta":{fields:[315,4,1,""]},"evennia.typeclasses.admin.TagFormSet":{save:[315,3,1,""]},"evennia.typeclasses.admin.TagInline":{extra:[315,4,1,""],form:[315,4,1,""],formset:[315,4,1,""],get_formset:[315,3,1,""],media:[315,3,1,""],model:[315,4,1,""],related_field:[315,4,1,""]},"evennia.typeclasses.attributes":{Attribute:[316,1,1,""],AttributeHandler:[316,1,1,""],NAttributeHandler:[316,1,1,""],NickHandler:[316,1,1,""],NickTemplateInvalid:[316,2,1,""],initialize_nick_templates:[316,5,1,""],parse_nick_template:[316,5,1,""]},"evennia.typeclasses.attributes.Attribute":{DoesNotExist:[316,2,1,""],MultipleObjectsReturned:[316,2,1,""],access:[316,3,1,""],accountdb_set:[316,4,1,""],attrtype:[316,3,1,""],category:[316,3,1,""],channeldb_set:[316,4,1,""],date_created:[316,3,1,""],db_attrtype:[316,4,1,""],db_category:[316,4,1,""],db_date_created:[316,4,1,""],db_key:[316,4,1,""],db_lock_storage:[316,4,1,""],db_model:[316,4,1,""],db_strvalue:[316,4,1,""],db_value:[316,4,1,""],get_next_by_db_date_created:[316,3,1,""],get_previous_by_db_date_created:[316,3,1,""],id:[316,4,1,""],key:[316,3,1,""],lock_storage:[316,3,1,""],locks:[316,4,1,""],model:[316,3,1,""],objectdb_set:[316,4,1,""],path:[316,4,1,""],scriptdb_set:[316,4,1,""],strvalue:[316,3,1,""],typename:[316,4,1,""],value:[316,3,1,""]},"evennia.typeclasses.attributes.AttributeHandler":{__init__:[316,3,1,""],add:[316,3,1,""],all:[316,3,1,""],batch_add:[316,3,1,""],clear:[316,3,1,""],get:[316,3,1,""],has:[316,3,1,""],remove:[316,3,1,""],reset_cache:[316,3,1,""]},"evennia.typeclasses.attributes.NAttributeHandler":{__init__:[316,3,1,""],add:[316,3,1,""],all:[316,3,1,""],clear:[316,3,1,""],get:[316,3,1,""],has:[316,3,1,""],remove:[316,3,1,""]},"evennia.typeclasses.attributes.NickHandler":{__init__:[316,3,1,""],add:[316,3,1,""],get:[316,3,1,""],has:[316,3,1,""],nickreplace:[316,3,1,""],remove:[316,3,1,""]},"evennia.typeclasses.managers":{TypedObjectManager:[317,1,1,""]},"evennia.typeclasses.managers.TypedObjectManager":{create_tag:[317,3,1,""],dbref:[317,3,1,""],dbref_search:[317,3,1,""],get_alias:[317,3,1,""],get_attribute:[317,3,1,""],get_by_alias:[317,3,1,""],get_by_attribute:[317,3,1,""],get_by_nick:[317,3,1,""],get_by_permission:[317,3,1,""],get_by_tag:[317,3,1,""],get_dbref_range:[317,3,1,""],get_id:[317,3,1,""],get_nick:[317,3,1,""],get_permission:[317,3,1,""],get_tag:[317,3,1,""],get_typeclass_totals:[317,3,1,""],object_totals:[317,3,1,""],typeclass_search:[317,3,1,""]},"evennia.typeclasses.models":{TypedObject:[318,1,1,""]},"evennia.typeclasses.models.TypedObject":{"delete":[318,3,1,""],Meta:[318,1,1,""],__init__:[318,3,1,""],access:[318,3,1,""],aliases:[318,4,1,""],at_idmapper_flush:[318,3,1,""],at_rename:[318,3,1,""],attributes:[318,4,1,""],check_permstring:[318,3,1,""],date_created:[318,3,1,""],db:[318,3,1,""],db_attributes:[318,4,1,""],db_date_created:[318,4,1,""],db_key:[318,4,1,""],db_lock_storage:[318,4,1,""],db_tags:[318,4,1,""],db_typeclass_path:[318,4,1,""],dbid:[318,3,1,""],dbref:[318,3,1,""],get_absolute_url:[318,3,1,""],get_display_name:[318,3,1,""],get_extra_info:[318,3,1,""],get_next_by_db_date_created:[318,3,1,""],get_previous_by_db_date_created:[318,3,1,""],is_typeclass:[318,3,1,""],key:[318,3,1,""],lock_storage:[318,3,1,""],locks:[318,4,1,""],name:[318,3,1,""],nattributes:[318,4,1,""],ndb:[318,3,1,""],objects:[318,4,1,""],path:[318,4,1,""],permissions:[318,4,1,""],set_class_from_typeclass:[318,3,1,""],swap_typeclass:[318,3,1,""],tags:[318,4,1,""],typeclass_path:[318,3,1,""],typename:[318,4,1,""],web_get_admin_url:[318,3,1,""],web_get_create_url:[318,3,1,""],web_get_delete_url:[318,3,1,""],web_get_detail_url:[318,3,1,""],web_get_puppet_url:[318,3,1,""],web_get_update_url:[318,3,1,""]},"evennia.typeclasses.models.TypedObject.Meta":{"abstract":[318,4,1,""],ordering:[318,4,1,""],verbose_name:[318,4,1,""]},"evennia.typeclasses.tags":{AliasHandler:[319,1,1,""],PermissionHandler:[319,1,1,""],Tag:[319,1,1,""],TagHandler:[319,1,1,""]},"evennia.typeclasses.tags.Tag":{DoesNotExist:[319,2,1,""],MultipleObjectsReturned:[319,2,1,""],accountdb_set:[319,4,1,""],channeldb_set:[319,4,1,""],db_category:[319,4,1,""],db_data:[319,4,1,""],db_key:[319,4,1,""],db_model:[319,4,1,""],db_tagtype:[319,4,1,""],helpentry_set:[319,4,1,""],id:[319,4,1,""],msg_set:[319,4,1,""],objectdb_set:[319,4,1,""],objects:[319,4,1,""],scriptdb_set:[319,4,1,""]},"evennia.typeclasses.tags.TagHandler":{__init__:[319,3,1,""],add:[319,3,1,""],all:[319,3,1,""],batch_add:[319,3,1,""],clear:[319,3,1,""],get:[319,3,1,""],remove:[319,3,1,""],reset_cache:[319,3,1,""]},"evennia.utils":{ansi:[321,0,0,"-"],batchprocessors:[322,0,0,"-"],containers:[323,0,0,"-"],create:[324,0,0,"-"],dbserialize:[325,0,0,"-"],eveditor:[326,0,0,"-"],evform:[327,0,0,"-"],evmenu:[328,0,0,"-"],evmore:[329,0,0,"-"],evtable:[330,0,0,"-"],gametime:[331,0,0,"-"],idmapper:[332,0,0,"-"],inlinefuncs:[336,0,0,"-"],logger:[337,0,0,"-"],optionclasses:[338,0,0,"-"],optionhandler:[339,0,0,"-"],picklefield:[340,0,0,"-"],search:[341,0,0,"-"],test_resources:[342,0,0,"-"],text2html:[343,0,0,"-"],utils:[344,0,0,"-"],validatorfuncs:[345,0,0,"-"]},"evennia.utils.ansi":{ANSIMeta:[321,1,1,""],ANSIParser:[321,1,1,""],ANSIString:[321,1,1,""],parse_ansi:[321,5,1,""],raw:[321,5,1,""],strip_ansi:[321,5,1,""],strip_raw_ansi:[321,5,1,""]},"evennia.utils.ansi.ANSIMeta":{__init__:[321,3,1,""]},"evennia.utils.ansi.ANSIParser":{ansi_escapes:[321,4,1,""],ansi_map:[321,4,1,""],ansi_map_dict:[321,4,1,""],ansi_re:[321,4,1,""],ansi_regex:[321,4,1,""],ansi_sub:[321,4,1,""],ansi_xterm256_bright_bg_map:[321,4,1,""],ansi_xterm256_bright_bg_map_dict:[321,4,1,""],brightbg_sub:[321,4,1,""],mxp_re:[321,4,1,""],mxp_sub:[321,4,1,""],parse_ansi:[321,3,1,""],strip_mxp:[321,3,1,""],strip_raw_codes:[321,3,1,""],sub_ansi:[321,3,1,""],sub_brightbg:[321,3,1,""],sub_xterm256:[321,3,1,""],xterm256_bg:[321,4,1,""],xterm256_bg_sub:[321,4,1,""],xterm256_fg:[321,4,1,""],xterm256_fg_sub:[321,4,1,""],xterm256_gbg:[321,4,1,""],xterm256_gbg_sub:[321,4,1,""],xterm256_gfg:[321,4,1,""],xterm256_gfg_sub:[321,4,1,""]},"evennia.utils.ansi.ANSIString":{__init__:[321,3,1,""],capitalize:[321,3,1,""],center:[321,3,1,""],clean:[321,3,1,""],count:[321,3,1,""],decode:[321,3,1,""],encode:[321,3,1,""],endswith:[321,3,1,""],expandtabs:[321,3,1,""],find:[321,3,1,""],format:[321,3,1,""],index:[321,3,1,""],isalnum:[321,3,1,""],isalpha:[321,3,1,""],isdigit:[321,3,1,""],islower:[321,3,1,""],isspace:[321,3,1,""],istitle:[321,3,1,""],isupper:[321,3,1,""],join:[321,3,1,""],ljust:[321,3,1,""],lower:[321,3,1,""],lstrip:[321,3,1,""],partition:[321,3,1,""],raw:[321,3,1,""],re_format:[321,4,1,""],replace:[321,3,1,""],rfind:[321,3,1,""],rindex:[321,3,1,""],rjust:[321,3,1,""],rsplit:[321,3,1,""],rstrip:[321,3,1,""],split:[321,3,1,""],startswith:[321,3,1,""],strip:[321,3,1,""],swapcase:[321,3,1,""],translate:[321,3,1,""],upper:[321,3,1,""]},"evennia.utils.batchprocessors":{BatchCodeProcessor:[322,1,1,""],BatchCommandProcessor:[322,1,1,""],read_batchfile:[322,5,1,""],tb_filename:[322,5,1,""],tb_iter:[322,5,1,""]},"evennia.utils.batchprocessors.BatchCodeProcessor":{code_exec:[322,3,1,""],parse_file:[322,3,1,""]},"evennia.utils.batchprocessors.BatchCommandProcessor":{parse_file:[322,3,1,""]},"evennia.utils.containers":{Container:[323,1,1,""],GlobalScriptContainer:[323,1,1,""],OptionContainer:[323,1,1,""]},"evennia.utils.containers.Container":{__init__:[323,3,1,""],all:[323,3,1,""],get:[323,3,1,""],load_data:[323,3,1,""],storage_modules:[323,4,1,""]},"evennia.utils.containers.GlobalScriptContainer":{__init__:[323,3,1,""],all:[323,3,1,""],get:[323,3,1,""],load_data:[323,3,1,""],start:[323,3,1,""]},"evennia.utils.containers.OptionContainer":{storage_modules:[323,4,1,""]},"evennia.utils.create":{create_account:[324,5,1,""],create_channel:[324,5,1,""],create_help_entry:[324,5,1,""],create_message:[324,5,1,""],create_object:[324,5,1,""],create_script:[324,5,1,""]},"evennia.utils.dbserialize":{dbserialize:[325,5,1,""],dbunserialize:[325,5,1,""],do_pickle:[325,5,1,""],do_unpickle:[325,5,1,""],from_pickle:[325,5,1,""],to_pickle:[325,5,1,""]},"evennia.utils.eveditor":{CmdEditorBase:[326,1,1,""],CmdEditorGroup:[326,1,1,""],CmdLineInput:[326,1,1,""],CmdSaveYesNo:[326,1,1,""],EvEditor:[326,1,1,""],EvEditorCmdSet:[326,1,1,""],SaveYesNoCmdSet:[326,1,1,""]},"evennia.utils.eveditor.CmdEditorBase":{aliases:[326,4,1,""],editor:[326,4,1,""],help_category:[326,4,1,""],help_entry:[326,4,1,""],key:[326,4,1,""],lock_storage:[326,4,1,""],locks:[326,4,1,""],parse:[326,3,1,""]},"evennia.utils.eveditor.CmdEditorGroup":{aliases:[326,4,1,""],arg_regex:[326,4,1,""],func:[326,3,1,""],help_category:[326,4,1,""],key:[326,4,1,""],lock_storage:[326,4,1,""]},"evennia.utils.eveditor.CmdLineInput":{aliases:[326,4,1,""],func:[326,3,1,""],help_category:[326,4,1,""],key:[326,4,1,""],lock_storage:[326,4,1,""]},"evennia.utils.eveditor.CmdSaveYesNo":{aliases:[326,4,1,""],func:[326,3,1,""],help_category:[326,4,1,""],help_cateogory:[326,4,1,""],key:[326,4,1,""],lock_storage:[326,4,1,""],locks:[326,4,1,""]},"evennia.utils.eveditor.EvEditor":{__init__:[326,3,1,""],decrease_indent:[326,3,1,""],deduce_indent:[326,3,1,""],display_buffer:[326,3,1,""],display_help:[326,3,1,""],get_buffer:[326,3,1,""],increase_indent:[326,3,1,""],load_buffer:[326,3,1,""],quit:[326,3,1,""],save_buffer:[326,3,1,""],swap_autoindent:[326,3,1,""],update_buffer:[326,3,1,""],update_undo:[326,3,1,""]},"evennia.utils.eveditor.EvEditorCmdSet":{at_cmdset_creation:[326,3,1,""],key:[326,4,1,""],mergetype:[326,4,1,""],path:[326,4,1,""]},"evennia.utils.eveditor.SaveYesNoCmdSet":{at_cmdset_creation:[326,3,1,""],key:[326,4,1,""],mergetype:[326,4,1,""],path:[326,4,1,""],priority:[326,4,1,""]},"evennia.utils.evform":{EvForm:[327,1,1,""]},"evennia.utils.evform.EvForm":{__init__:[327,3,1,""],map:[327,3,1,""],reload:[327,3,1,""]},"evennia.utils.evmenu":{CmdEvMenuNode:[328,1,1,""],CmdGetInput:[328,1,1,""],EvMenu:[328,1,1,""],EvMenuCmdSet:[328,1,1,""],EvMenuError:[328,2,1,""],EvMenuGotoAbortMessage:[328,2,1,""],InputCmdSet:[328,1,1,""],get_input:[328,5,1,""],list_node:[328,5,1,""],parse_menu_template:[328,5,1,""],template2menu:[328,5,1,""]},"evennia.utils.evmenu.CmdEvMenuNode":{aliases:[328,4,1,""],auto_help_display_key:[328,4,1,""],func:[328,3,1,""],get_help:[328,3,1,""],help_category:[328,4,1,""],key:[328,4,1,""],lock_storage:[328,4,1,""],locks:[328,4,1,""]},"evennia.utils.evmenu.CmdGetInput":{aliases:[328,4,1,""],func:[328,3,1,""],help_category:[328,4,1,""],key:[328,4,1,""],lock_storage:[328,4,1,""]},"evennia.utils.evmenu.EvMenu":{"goto":[328,3,1,""],__init__:[328,3,1,""],close_menu:[328,3,1,""],display_helptext:[328,3,1,""],display_nodetext:[328,3,1,""],extract_goto_exec:[328,3,1,""],helptext_formatter:[328,3,1,""],msg:[328,3,1,""],node_border_char:[328,4,1,""],node_formatter:[328,3,1,""],nodetext_formatter:[328,3,1,""],options_formatter:[328,3,1,""],parse_input:[328,3,1,""],print_debug_info:[328,3,1,""],run_exec:[328,3,1,""],run_exec_then_goto:[328,3,1,""]},"evennia.utils.evmenu.EvMenuCmdSet":{at_cmdset_creation:[328,3,1,""],key:[328,4,1,""],mergetype:[328,4,1,""],no_channels:[328,4,1,""],no_exits:[328,4,1,""],no_objs:[328,4,1,""],path:[328,4,1,""],priority:[328,4,1,""]},"evennia.utils.evmenu.InputCmdSet":{at_cmdset_creation:[328,3,1,""],key:[328,4,1,""],mergetype:[328,4,1,""],no_channels:[328,4,1,""],no_exits:[328,4,1,""],no_objs:[328,4,1,""],path:[328,4,1,""],priority:[328,4,1,""]},"evennia.utils.evmore":{CmdMore:[329,1,1,""],CmdMoreLook:[329,1,1,""],CmdSetMore:[329,1,1,""],EvMore:[329,1,1,""],msg:[329,5,1,""],queryset_maxsize:[329,5,1,""]},"evennia.utils.evmore.CmdMore":{aliases:[329,4,1,""],auto_help:[329,4,1,""],func:[329,3,1,""],help_category:[329,4,1,""],key:[329,4,1,""],lock_storage:[329,4,1,""]},"evennia.utils.evmore.CmdMoreLook":{aliases:[329,4,1,""],auto_help:[329,4,1,""],func:[329,3,1,""],help_category:[329,4,1,""],key:[329,4,1,""],lock_storage:[329,4,1,""]},"evennia.utils.evmore.CmdSetMore":{at_cmdset_creation:[329,3,1,""],key:[329,4,1,""],path:[329,4,1,""],priority:[329,4,1,""]},"evennia.utils.evmore.EvMore":{__init__:[329,3,1,""],display:[329,3,1,""],init_django_paginator:[329,3,1,""],init_evtable:[329,3,1,""],init_f_str:[329,3,1,""],init_iterable:[329,3,1,""],init_pages:[329,3,1,""],init_queryset:[329,3,1,""],init_str:[329,3,1,""],page_back:[329,3,1,""],page_end:[329,3,1,""],page_formatter:[329,3,1,""],page_next:[329,3,1,""],page_quit:[329,3,1,""],page_top:[329,3,1,""],paginator:[329,3,1,""],paginator_django:[329,3,1,""],paginator_index:[329,3,1,""],paginator_slice:[329,3,1,""],start:[329,3,1,""]},"evennia.utils.evtable":{ANSITextWrapper:[330,1,1,""],EvCell:[330,1,1,""],EvColumn:[330,1,1,""],EvTable:[330,1,1,""],fill:[330,5,1,""],wrap:[330,5,1,""]},"evennia.utils.evtable.EvCell":{__init__:[330,3,1,""],get:[330,3,1,""],get_height:[330,3,1,""],get_min_height:[330,3,1,""],get_min_width:[330,3,1,""],get_width:[330,3,1,""],reformat:[330,3,1,""],replace_data:[330,3,1,""]},"evennia.utils.evtable.EvColumn":{__init__:[330,3,1,""],add_rows:[330,3,1,""],reformat:[330,3,1,""],reformat_cell:[330,3,1,""]},"evennia.utils.evtable.EvTable":{__init__:[330,3,1,""],add_column:[330,3,1,""],add_header:[330,3,1,""],add_row:[330,3,1,""],get:[330,3,1,""],reformat:[330,3,1,""],reformat_column:[330,3,1,""]},"evennia.utils.gametime":{TimeScript:[331,1,1,""],game_epoch:[331,5,1,""],gametime:[331,5,1,""],portal_uptime:[331,5,1,""],real_seconds_until:[331,5,1,""],reset_gametime:[331,5,1,""],runtime:[331,5,1,""],schedule:[331,5,1,""],server_epoch:[331,5,1,""],uptime:[331,5,1,""]},"evennia.utils.gametime.TimeScript":{DoesNotExist:[331,2,1,""],MultipleObjectsReturned:[331,2,1,""],at_repeat:[331,3,1,""],at_script_creation:[331,3,1,""],path:[331,4,1,""],typename:[331,4,1,""]},"evennia.utils.idmapper":{manager:[333,0,0,"-"],models:[334,0,0,"-"],tests:[335,0,0,"-"]},"evennia.utils.idmapper.manager":{SharedMemoryManager:[333,1,1,""]},"evennia.utils.idmapper.manager.SharedMemoryManager":{get:[333,3,1,""]},"evennia.utils.idmapper.models":{SharedMemoryModel:[334,1,1,""],SharedMemoryModelBase:[334,1,1,""],WeakSharedMemoryModel:[334,1,1,""],WeakSharedMemoryModelBase:[334,1,1,""],cache_size:[334,5,1,""],conditional_flush:[334,5,1,""],flush_cache:[334,5,1,""],flush_cached_instance:[334,5,1,""],update_cached_instance:[334,5,1,""]},"evennia.utils.idmapper.models.SharedMemoryModel":{"delete":[334,3,1,""],Meta:[334,1,1,""],at_idmapper_flush:[334,3,1,""],cache_instance:[334,3,1,""],flush_cached_instance:[334,3,1,""],flush_from_cache:[334,3,1,""],flush_instance_cache:[334,3,1,""],get_all_cached_instances:[334,3,1,""],get_cached_instance:[334,3,1,""],objects:[334,4,1,""],path:[334,4,1,""],save:[334,3,1,""],typename:[334,4,1,""]},"evennia.utils.idmapper.models.SharedMemoryModel.Meta":{"abstract":[334,4,1,""]},"evennia.utils.idmapper.models.WeakSharedMemoryModel":{Meta:[334,1,1,""],path:[334,4,1,""],typename:[334,4,1,""]},"evennia.utils.idmapper.models.WeakSharedMemoryModel.Meta":{"abstract":[334,4,1,""]},"evennia.utils.idmapper.tests":{Article:[335,1,1,""],Category:[335,1,1,""],RegularArticle:[335,1,1,""],RegularCategory:[335,1,1,""],SharedMemorysTest:[335,1,1,""]},"evennia.utils.idmapper.tests.Article":{DoesNotExist:[335,2,1,""],MultipleObjectsReturned:[335,2,1,""],category2:[335,4,1,""],category2_id:[335,4,1,""],category:[335,4,1,""],category_id:[335,4,1,""],id:[335,4,1,""],name:[335,4,1,""],path:[335,4,1,""],typename:[335,4,1,""]},"evennia.utils.idmapper.tests.Category":{DoesNotExist:[335,2,1,""],MultipleObjectsReturned:[335,2,1,""],article_set:[335,4,1,""],id:[335,4,1,""],name:[335,4,1,""],path:[335,4,1,""],regulararticle_set:[335,4,1,""],typename:[335,4,1,""]},"evennia.utils.idmapper.tests.RegularArticle":{DoesNotExist:[335,2,1,""],MultipleObjectsReturned:[335,2,1,""],category2:[335,4,1,""],category2_id:[335,4,1,""],category:[335,4,1,""],category_id:[335,4,1,""],id:[335,4,1,""],name:[335,4,1,""],objects:[335,4,1,""]},"evennia.utils.idmapper.tests.RegularCategory":{DoesNotExist:[335,2,1,""],MultipleObjectsReturned:[335,2,1,""],article_set:[335,4,1,""],id:[335,4,1,""],name:[335,4,1,""],objects:[335,4,1,""],regulararticle_set:[335,4,1,""]},"evennia.utils.idmapper.tests.SharedMemorysTest":{setUp:[335,3,1,""],testMixedReferences:[335,3,1,""],testObjectDeletion:[335,3,1,""],testRegularReferences:[335,3,1,""],testSharedMemoryReferences:[335,3,1,""]},"evennia.utils.inlinefuncs":{"null":[336,5,1,""],InlinefuncError:[336,2,1,""],NickTemplateInvalid:[336,2,1,""],ParseStack:[336,1,1,""],clr:[336,5,1,""],crop:[336,5,1,""],initialize_nick_templates:[336,5,1,""],nomatch:[336,5,1,""],pad:[336,5,1,""],parse_inlinefunc:[336,5,1,""],parse_nick_template:[336,5,1,""],random:[336,5,1,""],raw:[336,5,1,""],space:[336,5,1,""]},"evennia.utils.inlinefuncs.ParseStack":{__init__:[336,3,1,""],append:[336,3,1,""]},"evennia.utils.logger":{EvenniaLogFile:[337,1,1,""],PortalLogObserver:[337,1,1,""],ServerLogObserver:[337,1,1,""],WeeklyLogFile:[337,1,1,""],log_dep:[337,5,1,""],log_depmsg:[337,5,1,""],log_err:[337,5,1,""],log_errmsg:[337,5,1,""],log_file:[337,5,1,""],log_info:[337,5,1,""],log_infomsg:[337,5,1,""],log_msg:[337,5,1,""],log_sec:[337,5,1,""],log_secmsg:[337,5,1,""],log_server:[337,5,1,""],log_trace:[337,5,1,""],log_tracemsg:[337,5,1,""],log_warn:[337,5,1,""],log_warnmsg:[337,5,1,""],tail_log_file:[337,5,1,""],timeformat:[337,5,1,""]},"evennia.utils.logger.EvenniaLogFile":{num_lines_to_append:[337,4,1,""],readlines:[337,3,1,""],rotate:[337,3,1,""],seek:[337,3,1,""],settings:[337,4,1,""]},"evennia.utils.logger.PortalLogObserver":{emit:[337,3,1,""],prefix:[337,4,1,""],timeFormat:[337,4,1,""]},"evennia.utils.logger.ServerLogObserver":{prefix:[337,4,1,""]},"evennia.utils.logger.WeeklyLogFile":{__init__:[337,3,1,""],shouldRotate:[337,3,1,""],suffix:[337,3,1,""],write:[337,3,1,""]},"evennia.utils.optionclasses":{BaseOption:[338,1,1,""],Boolean:[338,1,1,""],Color:[338,1,1,""],Datetime:[338,1,1,""],Duration:[338,1,1,""],Email:[338,1,1,""],Future:[338,1,1,""],Lock:[338,1,1,""],PositiveInteger:[338,1,1,""],SignedInteger:[338,1,1,""],Text:[338,1,1,""],Timezone:[338,1,1,""],UnsignedInteger:[338,1,1,""]},"evennia.utils.optionclasses.BaseOption":{"default":[338,3,1,""],__init__:[338,3,1,""],changed:[338,3,1,""],deserialize:[338,3,1,""],display:[338,3,1,""],load:[338,3,1,""],save:[338,3,1,""],serialize:[338,3,1,""],set:[338,3,1,""],validate:[338,3,1,""],value:[338,3,1,""]},"evennia.utils.optionclasses.Boolean":{deserialize:[338,3,1,""],display:[338,3,1,""],serialize:[338,3,1,""],validate:[338,3,1,""]},"evennia.utils.optionclasses.Color":{deserialize:[338,3,1,""],display:[338,3,1,""],validate:[338,3,1,""]},"evennia.utils.optionclasses.Datetime":{deserialize:[338,3,1,""],serialize:[338,3,1,""],validate:[338,3,1,""]},"evennia.utils.optionclasses.Duration":{deserialize:[338,3,1,""],serialize:[338,3,1,""],validate:[338,3,1,""]},"evennia.utils.optionclasses.Email":{deserialize:[338,3,1,""],validate:[338,3,1,""]},"evennia.utils.optionclasses.Future":{validate:[338,3,1,""]},"evennia.utils.optionclasses.Lock":{validate:[338,3,1,""]},"evennia.utils.optionclasses.PositiveInteger":{deserialize:[338,3,1,""],validate:[338,3,1,""]},"evennia.utils.optionclasses.SignedInteger":{deserialize:[338,3,1,""],validate:[338,3,1,""]},"evennia.utils.optionclasses.Text":{deserialize:[338,3,1,""]},"evennia.utils.optionclasses.Timezone":{"default":[338,3,1,""],deserialize:[338,3,1,""],serialize:[338,3,1,""],validate:[338,3,1,""]},"evennia.utils.optionclasses.UnsignedInteger":{deserialize:[338,3,1,""],validate:[338,3,1,""],validator_key:[338,4,1,""]},"evennia.utils.optionhandler":{InMemorySaveHandler:[339,1,1,""],OptionHandler:[339,1,1,""]},"evennia.utils.optionhandler.InMemorySaveHandler":{__init__:[339,3,1,""],add:[339,3,1,""],get:[339,3,1,""]},"evennia.utils.optionhandler.OptionHandler":{__init__:[339,3,1,""],all:[339,3,1,""],get:[339,3,1,""],set:[339,3,1,""]},"evennia.utils.picklefield":{PickledFormField:[340,1,1,""],PickledObject:[340,1,1,""],PickledObjectField:[340,1,1,""],PickledWidget:[340,1,1,""],dbsafe_decode:[340,5,1,""],dbsafe_encode:[340,5,1,""],wrap_conflictual_object:[340,5,1,""]},"evennia.utils.picklefield.PickledFormField":{__init__:[340,3,1,""],clean:[340,3,1,""],default_error_messages:[340,4,1,""],widget:[340,4,1,""]},"evennia.utils.picklefield.PickledObjectField":{__init__:[340,3,1,""],formfield:[340,3,1,""],from_db_value:[340,3,1,""],get_db_prep_lookup:[340,3,1,""],get_db_prep_value:[340,3,1,""],get_default:[340,3,1,""],get_internal_type:[340,3,1,""],pre_save:[340,3,1,""],value_to_string:[340,3,1,""]},"evennia.utils.picklefield.PickledWidget":{media:[340,3,1,""],render:[340,3,1,""],value_from_datadict:[340,3,1,""]},"evennia.utils.search":{search_account:[341,5,1,""],search_account_tag:[341,5,1,""],search_channel:[341,5,1,""],search_channel_tag:[341,5,1,""],search_help_entry:[341,5,1,""],search_message:[341,5,1,""],search_object:[341,5,1,""],search_script:[341,5,1,""],search_script_tag:[341,5,1,""],search_tag:[341,5,1,""]},"evennia.utils.test_resources":{EvenniaTest:[342,1,1,""],LocalEvenniaTest:[342,1,1,""],mockdeferLater:[342,5,1,""],mockdelay:[342,5,1,""],unload_module:[342,5,1,""]},"evennia.utils.test_resources.EvenniaTest":{account_typeclass:[342,4,1,""],character_typeclass:[342,4,1,""],exit_typeclass:[342,4,1,""],object_typeclass:[342,4,1,""],room_typeclass:[342,4,1,""],script_typeclass:[342,4,1,""],setUp:[342,3,1,""],tearDown:[342,3,1,""]},"evennia.utils.test_resources.LocalEvenniaTest":{account_typeclass:[342,4,1,""],character_typeclass:[342,4,1,""],exit_typeclass:[342,4,1,""],object_typeclass:[342,4,1,""],room_typeclass:[342,4,1,""],script_typeclass:[342,4,1,""]},"evennia.utils.text2html":{TextToHTMLparser:[343,1,1,""],parse_html:[343,5,1,""]},"evennia.utils.text2html.TextToHTMLparser":{bg_colormap:[343,4,1,""],bgfgstart:[343,4,1,""],bgfgstop:[343,4,1,""],bgstart:[343,4,1,""],bgstop:[343,4,1,""],blink:[343,4,1,""],colorback:[343,4,1,""],colorcodes:[343,4,1,""],convert_linebreaks:[343,3,1,""],convert_urls:[343,3,1,""],fg_colormap:[343,4,1,""],fgstart:[343,4,1,""],fgstop:[343,4,1,""],hilite:[343,4,1,""],inverse:[343,4,1,""],normal:[343,4,1,""],parse:[343,3,1,""],re_bgfg:[343,4,1,""],re_bgs:[343,4,1,""],re_blink:[343,4,1,""],re_blinking:[343,3,1,""],re_bold:[343,3,1,""],re_color:[343,3,1,""],re_dblspace:[343,4,1,""],re_double_space:[343,3,1,""],re_fgs:[343,4,1,""],re_hilite:[343,4,1,""],re_inverse:[343,4,1,""],re_inversing:[343,3,1,""],re_mxplink:[343,4,1,""],re_normal:[343,4,1,""],re_string:[343,4,1,""],re_uline:[343,4,1,""],re_underline:[343,3,1,""],re_unhilite:[343,4,1,""],re_url:[343,4,1,""],remove_backspaces:[343,3,1,""],remove_bells:[343,3,1,""],sub_dblspace:[343,3,1,""],sub_mxp_links:[343,3,1,""],sub_text:[343,3,1,""],tabstop:[343,4,1,""],underline:[343,4,1,""],unhilite:[343,4,1,""]},"evennia.utils.utils":{LimitedSizeOrderedDict:[344,1,1,""],all_from_module:[344,5,1,""],at_search_result:[344,5,1,""],callables_from_module:[344,5,1,""],calledby:[344,5,1,""],check_evennia_dependencies:[344,5,1,""],class_from_module:[344,5,1,""],columnize:[344,5,1,""],crop:[344,5,1,""],datetime_format:[344,5,1,""],dbid_to_obj:[344,5,1,""],dbref:[344,5,1,""],dbref_to_obj:[344,5,1,""],dedent:[344,5,1,""],deepsize:[344,5,1,""],delay:[344,5,1,""],display_len:[344,5,1,""],fill:[344,5,1,""],format_table:[344,5,1,""],fuzzy_import_from_module:[344,5,1,""],get_all_typeclasses:[344,5,1,""],get_evennia_pids:[344,5,1,""],get_evennia_version:[344,5,1,""],get_game_dir_path:[344,5,1,""],has_parent:[344,5,1,""],host_os_is:[344,5,1,""],inherits_from:[344,5,1,""],init_new_account:[344,5,1,""],interactive:[344,5,1,""],is_iter:[344,5,1,""],iter_to_string:[344,5,1,""],justify:[344,5,1,""],latinify:[344,5,1,""],lazy_property:[344,1,1,""],list_to_string:[344,5,1,""],m_len:[344,5,1,""],make_iter:[344,5,1,""],mod_import:[344,5,1,""],mod_import_from_path:[344,5,1,""],object_from_module:[344,5,1,""],pad:[344,5,1,""],pypath_to_realpath:[344,5,1,""],random_string_from_module:[344,5,1,""],run_async:[344,5,1,""],server_services:[344,5,1,""],string_from_module:[344,5,1,""],string_partial_matching:[344,5,1,""],string_similarity:[344,5,1,""],string_suggestions:[344,5,1,""],strip_control_sequences:[344,5,1,""],time_format:[344,5,1,""],to_bytes:[344,5,1,""],to_str:[344,5,1,""],uses_database:[344,5,1,""],validate_email_address:[344,5,1,""],variable_from_module:[344,5,1,""],wildcard_to_regexp:[344,5,1,""],wrap:[344,5,1,""]},"evennia.utils.utils.LimitedSizeOrderedDict":{__init__:[344,3,1,""],update:[344,3,1,""]},"evennia.utils.utils.lazy_property":{__init__:[344,3,1,""]},"evennia.utils.validatorfuncs":{"boolean":[345,5,1,""],color:[345,5,1,""],datetime:[345,5,1,""],duration:[345,5,1,""],email:[345,5,1,""],future:[345,5,1,""],lock:[345,5,1,""],positive_integer:[345,5,1,""],signed_integer:[345,5,1,""],text:[345,5,1,""],timezone:[345,5,1,""],unsigned_integer:[345,5,1,""]},"evennia.web":{urls:[347,0,0,"-"],utils:[348,0,0,"-"],webclient:[353,0,0,"-"],website:[356,0,0,"-"]},"evennia.web.utils":{backends:[349,0,0,"-"],general_context:[350,0,0,"-"],middleware:[351,0,0,"-"],tests:[352,0,0,"-"]},"evennia.web.utils.backends":{CaseInsensitiveModelBackend:[349,1,1,""]},"evennia.web.utils.backends.CaseInsensitiveModelBackend":{authenticate:[349,3,1,""]},"evennia.web.utils.general_context":{general_context:[350,5,1,""],set_game_name_and_slogan:[350,5,1,""],set_webclient_settings:[350,5,1,""]},"evennia.web.utils.middleware":{SharedLoginMiddleware:[351,1,1,""]},"evennia.web.utils.middleware.SharedLoginMiddleware":{__init__:[351,3,1,""],make_shared_login:[351,3,1,""]},"evennia.web.utils.tests":{TestGeneralContext:[352,1,1,""]},"evennia.web.utils.tests.TestGeneralContext":{maxDiff:[352,4,1,""],test_general_context:[352,3,1,""],test_set_game_name_and_slogan:[352,3,1,""],test_set_webclient_settings:[352,3,1,""]},"evennia.web.webclient":{urls:[354,0,0,"-"],views:[355,0,0,"-"]},"evennia.web.webclient.views":{webclient:[355,5,1,""]},"evennia.web.website":{forms:[357,0,0,"-"],templatetags:[358,0,0,"-"],tests:[360,0,0,"-"],urls:[361,0,0,"-"],views:[362,0,0,"-"]},"evennia.web.website.forms":{AccountForm:[357,1,1,""],CharacterForm:[357,1,1,""],CharacterUpdateForm:[357,1,1,""],EvenniaForm:[357,1,1,""],ObjectForm:[357,1,1,""]},"evennia.web.website.forms.AccountForm":{Meta:[357,1,1,""],base_fields:[357,4,1,""],declared_fields:[357,4,1,""],media:[357,3,1,""]},"evennia.web.website.forms.AccountForm.Meta":{field_classes:[357,4,1,""],fields:[357,4,1,""],model:[357,4,1,""]},"evennia.web.website.forms.CharacterForm":{Meta:[357,1,1,""],base_fields:[357,4,1,""],declared_fields:[357,4,1,""],media:[357,3,1,""]},"evennia.web.website.forms.CharacterForm.Meta":{fields:[357,4,1,""],labels:[357,4,1,""],model:[357,4,1,""]},"evennia.web.website.forms.CharacterUpdateForm":{base_fields:[357,4,1,""],declared_fields:[357,4,1,""],media:[357,3,1,""]},"evennia.web.website.forms.EvenniaForm":{base_fields:[357,4,1,""],clean:[357,3,1,""],declared_fields:[357,4,1,""],media:[357,3,1,""]},"evennia.web.website.forms.ObjectForm":{Meta:[357,1,1,""],base_fields:[357,4,1,""],declared_fields:[357,4,1,""],media:[357,3,1,""]},"evennia.web.website.forms.ObjectForm.Meta":{fields:[357,4,1,""],labels:[357,4,1,""],model:[357,4,1,""]},"evennia.web.website.templatetags":{addclass:[359,0,0,"-"]},"evennia.web.website.templatetags.addclass":{addclass:[359,5,1,""]},"evennia.web.website.tests":{AdminTest:[360,1,1,""],ChannelDetailTest:[360,1,1,""],ChannelListTest:[360,1,1,""],CharacterCreateView:[360,1,1,""],CharacterDeleteView:[360,1,1,""],CharacterListView:[360,1,1,""],CharacterManageView:[360,1,1,""],CharacterPuppetView:[360,1,1,""],CharacterUpdateView:[360,1,1,""],EvenniaWebTest:[360,1,1,""],IndexTest:[360,1,1,""],LoginTest:[360,1,1,""],LogoutTest:[360,1,1,""],PasswordResetTest:[360,1,1,""],RegisterTest:[360,1,1,""],WebclientTest:[360,1,1,""]},"evennia.web.website.tests.AdminTest":{unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.ChannelDetailTest":{get_kwargs:[360,3,1,""],setUp:[360,3,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.ChannelListTest":{url_name:[360,4,1,""]},"evennia.web.website.tests.CharacterCreateView":{test_valid_access_multisession_0:[360,3,1,""],test_valid_access_multisession_2:[360,3,1,""],unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.CharacterDeleteView":{get_kwargs:[360,3,1,""],test_invalid_access:[360,3,1,""],test_valid_access:[360,3,1,""],unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.CharacterListView":{unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.CharacterManageView":{unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.CharacterPuppetView":{get_kwargs:[360,3,1,""],test_invalid_access:[360,3,1,""],unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.CharacterUpdateView":{get_kwargs:[360,3,1,""],test_invalid_access:[360,3,1,""],test_valid_access:[360,3,1,""],unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.EvenniaWebTest":{account_typeclass:[360,4,1,""],authenticated_response:[360,4,1,""],channel_typeclass:[360,4,1,""],character_typeclass:[360,4,1,""],exit_typeclass:[360,4,1,""],get_kwargs:[360,3,1,""],login:[360,3,1,""],object_typeclass:[360,4,1,""],room_typeclass:[360,4,1,""],script_typeclass:[360,4,1,""],setUp:[360,3,1,""],test_get:[360,3,1,""],test_get_authenticated:[360,3,1,""],test_valid_chars:[360,3,1,""],unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.IndexTest":{url_name:[360,4,1,""]},"evennia.web.website.tests.LoginTest":{url_name:[360,4,1,""]},"evennia.web.website.tests.LogoutTest":{url_name:[360,4,1,""]},"evennia.web.website.tests.PasswordResetTest":{unauthenticated_response:[360,4,1,""],url_name:[360,4,1,""]},"evennia.web.website.tests.RegisterTest":{url_name:[360,4,1,""]},"evennia.web.website.tests.WebclientTest":{test_get:[360,3,1,""],test_get_disabled:[360,3,1,""],url_name:[360,4,1,""]},"evennia.web.website.views":{AccountCreateView:[362,1,1,""],AccountMixin:[362,1,1,""],ChannelDetailView:[362,1,1,""],ChannelListView:[362,1,1,""],ChannelMixin:[362,1,1,""],CharacterCreateView:[362,1,1,""],CharacterDeleteView:[362,1,1,""],CharacterDetailView:[362,1,1,""],CharacterListView:[362,1,1,""],CharacterManageView:[362,1,1,""],CharacterMixin:[362,1,1,""],CharacterPuppetView:[362,1,1,""],CharacterUpdateView:[362,1,1,""],EvenniaCreateView:[362,1,1,""],EvenniaDeleteView:[362,1,1,""],EvenniaDetailView:[362,1,1,""],EvenniaIndexView:[362,1,1,""],EvenniaUpdateView:[362,1,1,""],HelpDetailView:[362,1,1,""],HelpListView:[362,1,1,""],HelpMixin:[362,1,1,""],ObjectCreateView:[362,1,1,""],ObjectDeleteView:[362,1,1,""],ObjectDetailView:[362,1,1,""],ObjectUpdateView:[362,1,1,""],TypeclassMixin:[362,1,1,""],admin_wrapper:[362,5,1,""],evennia_admin:[362,5,1,""],to_be_implemented:[362,5,1,""]},"evennia.web.website.views.AccountCreateView":{form_valid:[362,3,1,""],success_url:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.AccountMixin":{form_class:[362,4,1,""],model:[362,4,1,""]},"evennia.web.website.views.ChannelDetailView":{attributes:[362,4,1,""],get_context_data:[362,3,1,""],get_object:[362,3,1,""],max_num_lines:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.ChannelListView":{get_context_data:[362,3,1,""],max_popular:[362,4,1,""],page_title:[362,4,1,""],paginate_by:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.ChannelMixin":{access_type:[362,4,1,""],get_queryset:[362,3,1,""],model:[362,4,1,""],page_title:[362,4,1,""]},"evennia.web.website.views.CharacterCreateView":{form_valid:[362,3,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.CharacterDetailView":{access_type:[362,4,1,""],attributes:[362,4,1,""],get_queryset:[362,3,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.CharacterListView":{access_type:[362,4,1,""],get_queryset:[362,3,1,""],page_title:[362,4,1,""],paginate_by:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.CharacterManageView":{page_title:[362,4,1,""],paginate_by:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.CharacterMixin":{form_class:[362,4,1,""],get_queryset:[362,3,1,""],model:[362,4,1,""],success_url:[362,4,1,""]},"evennia.web.website.views.CharacterPuppetView":{get_redirect_url:[362,3,1,""]},"evennia.web.website.views.CharacterUpdateView":{form_class:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.EvenniaCreateView":{page_title:[362,3,1,""]},"evennia.web.website.views.EvenniaDeleteView":{page_title:[362,3,1,""]},"evennia.web.website.views.EvenniaDetailView":{page_title:[362,3,1,""]},"evennia.web.website.views.EvenniaIndexView":{get_context_data:[362,3,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.EvenniaUpdateView":{page_title:[362,3,1,""]},"evennia.web.website.views.HelpDetailView":{get_context_data:[362,3,1,""],get_object:[362,3,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.HelpListView":{page_title:[362,4,1,""],paginate_by:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.HelpMixin":{get_queryset:[362,3,1,""],model:[362,4,1,""],page_title:[362,4,1,""]},"evennia.web.website.views.ObjectCreateView":{model:[362,4,1,""]},"evennia.web.website.views.ObjectDeleteView":{"delete":[362,3,1,""],access_type:[362,4,1,""],model:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.ObjectDetailView":{access_type:[362,4,1,""],attributes:[362,4,1,""],get_context_data:[362,3,1,""],get_object:[362,3,1,""],model:[362,4,1,""],template_name:[362,4,1,""]},"evennia.web.website.views.ObjectUpdateView":{access_type:[362,4,1,""],form_valid:[362,3,1,""],get_initial:[362,3,1,""],get_success_url:[362,3,1,""],model:[362,4,1,""]},"evennia.web.website.views.TypeclassMixin":{typeclass:[362,3,1,""]},evennia:{accounts:[143,0,0,"-"],commands:[149,0,0,"-"],comms:[172,0,0,"-"],contrib:[178,0,0,"-"],help:[236,0,0,"-"],locks:[240,0,0,"-"],objects:[243,0,0,"-"],prototypes:[248,0,0,"-"],scripts:[253,0,0,"-"],server:[262,0,0,"-"],set_trace:[141,5,1,""],settings_default:[313,0,0,"-"],typeclasses:[314,0,0,"-"],utils:[320,0,0,"-"],web:[346,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","exception","Python exception"],"3":["py","method","Python method"],"4":["py","attribute","Python attribute"],"5":["py","function","Python function"],"6":["py","data","Python data"]},objtypes:{"0":"py:module","1":"py:class","2":"py:exception","3":"py:method","4":"py:attribute","5":"py:function","6":"py:data"},terms:{"000":[0,25,46,82,114,343],"0000":[0,46],"0004":22,"001":[22,127,343],"002":343,"003":343,"004":343,"005":[114,321,343],"006":343,"007":343,"008":343,"009":343,"00sc":124,"010":[25,343],"011":343,"012":343,"013":343,"014":343,"015":343,"015public":25,"016":343,"017":343,"018":343,"019":343,"020":343,"020t":25,"021":343,"022":343,"023":343,"024":343,"0247":22,"025":343,"026":343,"027":343,"028":343,"029":343,"030":343,"030a":25,"031":343,"032":343,"033":[321,343],"034":[22,343],"035":343,"036":343,"037":343,"038":343,"039":343,"040":343,"040f":25,"041":343,"042":343,"043":343,"044":343,"045":343,"046":343,"047":343,"048":343,"049":343,"050":[321,343],"050f":25,"051":343,"052":343,"053":343,"054":[114,343],"055":[321,343],"056":343,"057":343,"058":343,"059":343,"060":343,"061":343,"062":343,"062022":363,"063":343,"064":343,"065":343,"066":343,"067":343,"068":343,"069":343,"070":343,"071":343,"072":343,"073":343,"074":343,"075":343,"076":343,"077":343,"078":343,"079":343,"080":343,"081":343,"082":343,"083":343,"084":343,"085":343,"086":343,"087":343,"088":343,"089":343,"090":343,"091":343,"092":343,"093":343,"094":343,"095":343,"096":343,"097":343,"098":343,"099":343,"0b16":24,"0d0":56,"0x045a0990":42,"0x852be2c":59,"100":[31,56,73,85,93,111,125,169,185,188,217,220,221,343,362],"1000":[56,93,100,116,217,218,219,220,221,251,362],"1000000":[82,93,337],"100m":343,"100mb":90,"101":[31,247,343],"101m":343,"102":343,"102m":343,"103":343,"103m":343,"104":343,"104m":343,"105":343,"105m":343,"106":343,"106m":343,"107":343,"107m":343,"108":343,"108m":343,"109":343,"1098":125,"109m":343,"10m":67,"110":[329,343],"110m":343,"111":[12,114,157,343],"111m":343,"112":343,"112m":343,"113":[90,343],"113m":343,"114":343,"114m":343,"115":343,"115600":56,"115m":343,"116":343,"116m":343,"117":343,"1172":138,"117m":343,"118":[115,343],"1184":23,"118m":343,"119":343,"119m":343,"120":[31,343],"1200":327,"120m":343,"121":343,"121m":343,"122":343,"122m":343,"123":[131,134,247,343],"1234":[54,109,203],"123dark":81,"123m":343,"124":343,"12400":82,"124m":343,"125":343,"125m":343,"126":343,"126m":343,"127":[8,9,24,63,67,90,287,343],"127m":343,"128":343,"128m":343,"129":343,"129m":343,"12s":27,"130":343,"130m":343,"131":343,"131m":343,"132":343,"132m":343,"133":343,"133m":343,"134":[12,157,343],"134m":343,"135":343,"135m":343,"136":343,"136m":343,"137":343,"137m":343,"138":343,"138m":343,"139":343,"139m":343,"140":[25,42,141,343],"1400":327,"140313967648552":33,"140m":343,"141":[139,343],"141m":343,"142":[22,180,343],"1424724909023":70,"142m":343,"143":343,"143m":343,"144":343,"144m":343,"145":343,"145m":343,"146":343,"146m":343,"147":343,"147m":343,"148":343,"148m":343,"149":343,"149m":343,"150":[326,343],"150m":343,"151":343,"151m":343,"152":343,"152m":343,"153":343,"153m":343,"154":343,"154m":343,"155":343,"155m":343,"156":[127,343],"156m":343,"157":343,"1577865600":62,"157m":343,"158":343,"158m":343,"159":343,"159m":343,"160":343,"160m":343,"161":343,"161m":343,"162":343,"162m":343,"163":343,"163m":343,"164":343,"164m":343,"165":343,"165m":343,"166":343,"166m":343,"167":343,"167m":343,"168":343,"168m":343,"169":343,"169m":343,"16m":343,"170":343,"170m":343,"171":343,"171m":343,"172":343,"172m":343,"173":343,"1730":79,"173m":343,"174":343,"174m":343,"175":343,"175m":343,"176":343,"1764":119,"176m":343,"177":343,"177m":343,"178":343,"178m":343,"179":343,"179m":343,"17m":343,"180":343,"180m":343,"181":343,"181m":343,"182":343,"182m":343,"183":343,"183m":343,"184":343,"184m":343,"185":343,"185m":343,"186":343,"186m":343,"187":343,"187m":343,"188":343,"188m":343,"189":343,"189m":343,"18m":343,"190":343,"1903":119,"190m":343,"191":343,"191m":343,"192":343,"192m":343,"193":343,"193m":343,"194":343,"194m":343,"195":343,"195m":343,"196":343,"196m":343,"197":343,"1970":62,"197m":343,"198":343,"198m":343,"199":343,"1996":79,"1998":79,"199m":343,"19m":343,"1_7":127,"1d100":[73,185],"1d2":56,"1d6":73,"1gb":90,"1st":62,"200":[343,360],"2001":79,"2003":79,"2004":79,"2008":344,"200m":343,"201":343,"2010":343,"2011":[124,181,214,232],"2012":[179,185,186,187],"2014":[21,213],"2015":[24,189,205,206],"2016":[99,199,202,212,214],"2017":[62,90,97,182,183,184,190,204,209,210,215,217,218,219,220,221,234,235,364],"2018":[9,180,188,198,203],"2019":[79,187,364],"201m":343,"202":343,"2020":[12,62,230,363],"2020_01_29":337,"2020_01_29__1":337,"2020_01_29__2":337,"202m":343,"203":[90,343],"203m":343,"204":343,"2048":67,"204m":343,"205":[327,343],"205m":343,"206":343,"206m":343,"207":343,"2076":119,"207m":343,"208":[91,343],"208m":343,"209":343,"209m":343,"20m":343,"210":343,"210m":343,"211":343,"211m":343,"212":[12,343],"2128":56,"212m":343,"213":343,"213m":343,"214":343,"214m":343,"215":343,"215m":343,"216":343,"216m":343,"217":343,"217m":343,"218":343,"218m":343,"219":[9,343],"219m":343,"21m":343,"220":343,"2207":204,"220m":343,"221":[322,343],"221m":343,"222":[114,321,343],"222m":343,"223":[12,343],"223m":343,"224":343,"224m":343,"225":[12,343],"225m":343,"226":343,"226m":343,"227":343,"227m":343,"228":343,"228m":343,"229":343,"229m":343,"22m":[321,343],"22nd":344,"230":[114,343],"230m":343,"231":343,"231m":343,"232":343,"232m":343,"233":[12,157,343],"233m":343,"234":[183,343],"234m":343,"235":343,"235m":343,"236":343,"236m":343,"237":[12,343],"237m":343,"238":343,"238m":343,"239":343,"239m":343,"23m":343,"240":343,"240m":343,"241":343,"241m":343,"242":343,"242m":343,"243":343,"243m":343,"244":343,"244m":343,"245":343,"245m":343,"246":343,"246m":343,"247":343,"247m":343,"248":343,"248m":343,"249":343,"249m":343,"24m":343,"250":343,"250m":343,"251":343,"251m":343,"252":343,"252m":343,"253":343,"253m":343,"254":343,"254m":343,"255":[24,343],"255m":343,"256":[12,114,156,321],"25m":343,"26m":343,"27m":343,"280":71,"28gmcp":291,"28m":343,"29m":343,"2d6":[58,185],"2gb":90,"300":[114,126,184,331],"3000000":82,"302":360,"30m":[321,343],"31m":[321,343],"31st":62,"32bit":[24,63],"32m":[321,343],"32nd":58,"333":[12,114],"33333":59,"33m":[321,343],"340":56,"34m":[321,343],"358283996582031":93,"35m":[321,343],"360":62,"3600":62,"36m":[321,343],"37m":[321,343],"3872":119,"38m":343,"39m":343,"3c3ccec30f037be174d3":344,"3d6":185,"3rd":[62,364],"4000":[9,36,63,67,75,90,95,100,101,103],"4001":[3,4,8,9,36,63,67,69,75,90,95,100,101,103,133,134,135,137,296],"4002":[8,36,67,90,100],"4003":90,"4004":90,"4005":90,"4006":90,"403":131,"404":69,"40m":[321,343],"41917":287,"41m":[321,343],"4201":90,"4280":55,"42m":[321,343],"430000":62,"43m":[321,343],"443":[8,67,103],"444":114,"44m":[321,343],"45m":[27,321,343],"46m":[321,343],"47m":[321,343],"48m":343,"49m":343,"4er43233fwefwfw":9,"4th":[38,79],"500":[114,126,321,362],"50000":82,"505":321,"50m":343,"50mb":90,"516106":56,"51m":343,"520":114,"52m":343,"53m":343,"54m":343,"550":[321,327],"550n":25,"551e":25,"552w":25,"553b":25,"554i":25,"555":[114,204,321],"555e":25,"55m":343,"565000":62,"56m":343,"577349":343,"57m":343,"5885d80a13c0db1f8e263663d3faee8d66f31424b43e9a70645c907a6cbd8fb4":37,"58m":343,"593":344,"59m":343,"5d5":56,"5fdonatecc":70,"5flg":70,"5fu":70,"5x5":111,"600":344,"60m":343,"614":138,"61m":343,"62m":343,"63m":343,"64m":343,"65m":343,"6666":40,"6667":[72,79,146,164,308],"66m":343,"67m":343,"68m":343,"69m":343,"6d6":56,"70982813835144":93,"70m":343,"71m":343,"72m":343,"73m":343,"74m":343,"75m":343,"760000":62,"76m":343,"775":36,"77m":343,"78m":343,"79m":343,"8080":90,"80m":343,"8111":36,"81m":343,"82m":343,"83m":343,"84m":343,"85000":82,"85m":343,"86400":120,"86m":343,"87m":343,"8859":[15,113,171],"88m":343,"89m":343,"8f64fec2670c":90,"900":[188,327],"9000":357,"90m":343,"90s":345,"91m":343,"92m":343,"93m":343,"94m":343,"95m":343,"96m":343,"97m":343,"981":204,"98m":343,"990":327,"99999":61,"99m":343,"9th":364,"\u6d4b\u8bd5":25,"abstract":[47,64,86,119,221,317,318,334,338,344],"boolean":[13,33,133,137,154,185,188,242,247,250,259,316,321,322,338,345],"break":[10,12,14,30,37,42,51,54,57,58,61,91,96,103,108,111,114,125,137,141,167,168,202,224,226,276,328,329,344],"byte":[15,27,113,267,269,276,278,287,295,344],"case":[1,6,8,10,11,12,13,14,15,21,22,25,27,28,29,31,33,34,37,38,40,41,42,44,46,49,51,55,58,59,60,61,62,64,69,74,79,80,81,82,83,86,88,89,91,95,96,100,102,103,105,107,108,109,110,111,113,114,116,119,120,121,123,125,127,128,131,133,137,144,146,147,151,153,156,159,165,167,168,174,175,176,179,180,182,185,187,188,196,204,206,211,233,238,239,241,242,245,247,251,256,258,272,276,280,284,298,305,308,316,317,318,319,321,323,334,341,344,349,364],"catch":[15,26,27,30,51,58,87,91,97,102,115,118,146,165,233,257,267,272,279,305,306,326,328,334,337,340,362],"char":[56,58,71,73,85,88,105,111,116,119,120,133,144,159,165,189,233,247,264,277,290,291,312,321,327,330],"class":[1,2,3,5,6,10,11,12,16,17,20,21,25,26,28,29,30,31,38,39,40,42,44,47,49,50,52,53,55,56,57,58,60,61,62,64,68,71,73,77,81,82,85,86,89,91,97,102,105,109,116,117,118,119,120,121,123,124,132,133,134,135,144,145,146,147,148,149,152,153,154,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,173,174,175,176,177,179,180,181,182,184,185,186,187,188,189,192,193,195,196,198,199,202,203,204,205,206,210,211,212,213,214,215,217,218,219,220,221,223,224,226,227,228,230,231,232,233,234,235,237,238,239,242,243,244,245,246,247,249,251,252,254,255,256,257,258,259,260,261,263,264,265,267,269,270,273,274,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,298,300,303,305,306,307,308,310,311,312,314,315,316,317,318,319,321,322,323,324,325,326,327,328,329,330,331,333,334,335,336,337,338,339,340,341,342,343,344,349,351,352,357,360,362,364],"const":234,"default":[0,1,2,3,4,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,27,29,31,32,33,34,35,36,38,39,40,41,42,45,46,47,49,50,51,53,56,57,58,59,62,63,64,65,66,67,68,69,71,72,75,76,77,81,82,83,85,86,87,88,89,90,91,93,95,96,97,100,101,102,103,104,105,106,107,109,111,112,113,114,116,117,118,119,121,123,124,125,126,127,128,129,131,133,134,135,136,138,139,140,141,142,144,145,146,148,149,150,151,152,153,154,174,175,177,179,180,181,182,183,184,185,186,187,188,189,190,193,195,196,199,202,203,205,206,209,210,212,213,214,215,217,218,219,220,221,224,231,233,234,235,236,238,239,240,242,245,247,251,252,256,257,259,260,261,265,267,269,271,272,273,277,289,290,291,296,298,299,305,306,307,308,312,313,316,317,318,319,321,323,324,326,328,329,330,333,334,336,337,338,339,340,341,344,345,349,357,362,364],"export":75,"final":[10,23,26,27,29,33,36,38,39,41,58,63,67,68,69,70,73,76,80,83,85,86,102,103,105,109,114,116,123,125,126,127,133,134,136,150,151,152,159,164,168,185,215,242,252,304,308,321,323,328,329,336,364],"float":[38,49,114,146,184,194,195,198,250,260,267,279,317,331,336,340,344],"function":[3,4,5,6,9,10,11,13,14,18,19,20,21,23,25,26,27,29,33,34,37,38,40,41,44,46,48,50,52,55,57,58,59,60,61,62,63,64,68,69,73,74,75,77,81,82,83,85,86,88,91,93,96,104,106,107,108,109,110,111,115,118,119,121,122,123,124,125,127,128,133,134,135,137,138,140,141,144,148,151,153,154,156,157,158,159,160,164,165,166,167,169,170,171,175,176,179,180,181,184,185,187,188,190,194,195,198,199,203,205,206,211,212,215,217,218,219,220,221,224,226,227,230,232,233,234,235,239,240,241,242,247,250,251,252,257,259,260,261,267,272,276,287,288,293,296,299,306,308,310,318,319,320,321,322,324,325,326,328,329,331,336,337,338,339,343,344,345,350,362,364],"g\u00e9n\u00e9ral":79,"goto":[85,230,328,364],"import":[0,2,3,4,5,6,9,10,11,13,14,15,16,19,20,21,22,25,27,28,29,30,31,33,39,40,42,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,68,69,71,72,73,74,76,77,80,81,82,83,84,85,86,89,90,91,93,96,97,102,103,104,105,106,107,110,111,112,113,114,115,116,117,118,119,120,121,123,125,126,127,132,133,134,135,136,137,138,140,141,153,159,169,174,179,180,181,182,183,184,185,187,188,198,199,202,204,205,206,212,213,215,217,218,219,220,221,227,232,233,235,238,242,251,252,261,267,271,279,280,301,305,308,309,318,322,323,326,327,328,329,330,341,342,344,362,364],"int":[11,25,31,39,49,51,56,58,74,85,91,114,123,125,134,144,146,147,151,152,154,176,179,182,184,185,188,190,192,194,195,198,206,215,217,218,219,220,221,234,245,247,252,255,258,259,260,261,264,265,267,271,272,276,277,278,279,281,285,286,287,295,296,298,308,310,312,317,321,324,326,327,328,329,330,331,334,336,337,341,344],"long":[9,10,15,20,22,23,25,26,27,29,33,37,38,40,44,46,49,51,52,55,58,60,62,64,68,71,72,73,78,79,80,81,85,86,87,90,105,108,111,113,115,118,121,125,126,127,129,131,133,135,138,139,156,159,164,179,186,195,203,213,220,227,234,276,281,296,321,322,329,330,344],"new":[0,2,5,9,11,12,13,14,16,19,20,21,22,23,24,25,26,27,29,31,33,34,35,36,37,38,39,40,41,43,44,45,49,50,51,54,55,57,61,62,63,64,65,67,68,70,71,72,73,75,76,77,78,79,80,81,82,83,84,85,88,89,90,91,92,93,95,96,98,100,101,104,105,106,107,108,109,111,112,116,117,118,121,122,123,124,128,129,131,132,134,135,136,137,138,139,144,145,146,152,153,154,156,157,159,164,167,168,170,171,173,174,175,180,181,182,186,187,188,192,195,199,202,203,204,205,206,212,213,215,217,218,219,220,221,231,232,233,235,239,242,244,245,246,247,249,251,252,254,255,256,259,260,261,264,267,276,277,278,279,285,286,287,292,299,306,307,308,312,316,317,318,319,321,322,324,327,328,329,330,334,336,337,338,360,362,363,364],"null":[8,86,315,336],"public":[25,34,41,58,65,67,72,90,93,100,103,131,134,164,247,312,330,364],"return":[3,4,6,10,11,15,20,21,22,25,27,28,29,30,33,36,39,40,41,42,44,48,49,50,52,58,60,62,64,68,69,71,73,74,76,77,80,81,82,83,85,89,91,93,95,96,97,100,102,103,107,108,109,110,111,112,114,116,117,118,119,121,123,125,127,129,133,134,137,138,144,145,146,147,148,150,151,152,153,154,156,159,164,166,169,170,174,175,176,177,179,180,182,184,185,187,188,190,192,193,194,195,198,199,203,204,205,206,210,211,212,215,217,218,219,220,221,223,230,231,232,233,234,235,237,238,239,241,242,244,245,246,247,249,250,251,252,255,257,258,259,260,261,264,265,267,272,273,276,277,279,280,281,282,284,285,286,287,288,290,291,292,294,295,296,298,299,305,306,308,310,311,312,315,316,317,318,319,321,322,323,324,325,326,328,329,330,331,334,336,337,338,339,340,341,343,344,345,350,357,362,364],"short":[20,22,29,39,42,46,51,54,57,58,61,62,70,71,83,87,89,95,96,103,110,112,114,123,129,137,140,180,182,195,202,205,206,227,234,252,322,344],"static":[38,49,58,83,124,127,135,136,137,139,166,180,192,206,214,312,324,355,362,364],"super":[5,22,25,31,40,41,49,57,58,60,62,81,89,96,118,121,123,125,180,182,206,329,364],"switch":[0,2,9,10,13,14,16,19,20,23,25,31,33,34,46,50,58,65,68,72,76,80,81,82,88,90,98,114,116,121,122,123,125,126,129,131,137,138,156,157,158,159,164,165,166,167,168,169,171,174,175,185,187,199,202,203,218,256,318,324,329,345,364],"th\u00ed":20,"throw":[11,22,66,75,109,131,133,153,166,260,344],"true":[1,2,4,5,10,11,13,20,21,22,25,26,27,29,31,33,34,40,41,49,50,51,54,56,58,62,65,66,68,69,72,74,76,80,81,83,84,85,86,87,90,91,96,98,100,102,105,114,115,116,117,120,121,122,123,125,126,127,133,135,137,138,144,147,148,150,152,153,154,156,159,164,166,167,170,173,174,175,176,177,179,180,182,183,184,185,188,190,192,195,203,204,205,206,212,215,217,218,219,220,221,224,226,230,231,235,237,241,242,244,245,246,247,249,251,252,254,256,257,258,259,260,261,263,265,267,272,273,276,278,285,290,295,296,306,308,310,312,315,316,317,318,321,324,326,328,329,330,331,334,336,339,340,341,344,345],"try":[0,4,5,6,8,9,10,11,12,13,15,16,20,21,22,23,25,26,27,29,30,38,39,42,44,46,48,49,50,51,54,55,56,57,58,60,61,63,64,65,66,67,68,69,73,74,75,77,80,81,86,90,91,93,95,96,97,102,103,108,109,110,111,113,118,119,120,121,123,124,126,127,133,134,135,136,137,138,140,144,148,152,154,159,175,177,179,180,186,196,204,205,206,212,213,217,218,219,220,221,224,227,231,232,233,235,239,247,251,259,264,267,276,291,292,296,310,315,316,318,321,323,324,326,327,340,344,364],"var":[67,83,88,137,209,291,322],"void":56,"while":[0,9,10,11,13,14,20,22,23,25,28,29,31,33,35,37,38,41,49,50,51,55,56,57,58,62,63,70,75,83,86,90,91,93,95,96,103,108,109,110,111,114,116,118,119,121,122,124,127,129,133,134,136,137,138,144,156,159,167,175,179,188,196,203,204,218,221,224,227,231,233,235,247,252,259,291,314,315,318,328,330,338,344,345,362,363],AIs:79,AND:[73,80,119,159,188,242],ARE:77,AWS:[90,100],Adding:[18,32,33,45,60,71,82,85,108,116,124,139,187,328,364],Age:[188,357],And:[0,4,9,10,11,21,22,25,26,29,33,36,41,42,46,51,57,61,62,69,73,80,86,91,96,105,111,126,133,138,153,182,215,217,218,219,220,221,364],Are:[33,61,79,82],Aye:46,BGs:126,Being:[58,81,122,123],But:[0,6,10,11,13,15,20,21,22,25,26,27,28,29,31,33,37,38,39,41,42,44,51,54,55,57,59,60,61,62,64,69,72,73,80,82,83,85,86,91,95,96,100,102,104,107,109,111,114,119,125,126,127,133,134,138,152,153,179,227,319,362],DNS:[67,90],DOING:188,DoS:285,Doing:[29,33,55,73,134,153,156],For:[0,2,5,6,8,9,12,13,14,16,17,19,20,21,22,23,25,27,29,31,33,36,37,38,39,41,42,46,49,51,55,56,57,58,59,62,63,64,69,72,73,76,79,80,81,83,85,86,88,90,91,93,95,96,98,100,102,103,105,109,110,111,113,114,116,121,123,126,127,129,131,132,133,134,135,136,138,139,140,152,153,159,169,174,175,176,177,180,182,185,187,188,189,198,206,212,214,215,218,231,239,242,252,260,287,296,316,318,321,325,328,329,338,340,344,357,362,364],GMs:58,Going:234,Has:[24,217,218,219,220,221],His:[57,189],IDE:[38,48,106],IDEs:57,IDs:[0,100,133,134,194,344],INTO:[159,188],IOS:24,IPs:[12,103,209,310],IRE:[88,291],Its:[41,62,69,80,83,86,89,105,189,252,326,328,344],LTS:97,NOT:[11,25,33,80,90,103,119,137,159,242,252,259,310,364],Not:[8,24,30,41,54,57,61,74,90,108,112,115,127,131,132,133,137,146,153,167,168,247,264,277,278,279,281,282,283,289,291,294,316,317,338],OBS:19,ONE:103,Obs:127,One:[0,8,12,20,22,25,29,34,36,38,46,49,51,57,58,60,63,64,69,76,79,80,87,91,95,102,105,110,115,117,121,123,126,128,130,131,132,138,141,148,150,179,185,205,215,231,232,245,251,252,277,305,315,316,317,321,329,344],PMs:364,PRs:131,Such:[6,13,28,33,37,48,51,57,64,73,127,159,252,321,328],THAT:91,THE:[188,227],THEN:[153,188],THERE:188,TLS:[103,364],That:[0,3,4,9,10,15,21,22,25,26,31,33,39,41,42,46,49,55,57,62,64,68,69,73,74,77,91,93,95,96,98,102,105,111,112,115,119,122,125,127,131,134,136,138,140,179,180,186,215,242,252,308,328],The:[0,2,4,5,6,7,8,9,12,15,17,20,21,23,24,25,27,28,30,31,33,34,36,37,38,39,40,42,43,44,45,48,52,53,54,55,56,57,59,60,61,62,63,64,66,67,68,70,72,73,74,75,76,78,79,80,81,82,84,86,87,88,89,90,91,92,95,97,98,100,101,102,103,104,105,106,107,108,110,111,112,113,114,115,118,119,120,121,122,124,125,126,127,128,129,131,132,133,134,136,137,138,139,140,144,146,147,148,150,151,152,153,154,156,159,163,164,165,166,167,168,169,170,171,173,174,175,176,177,179,180,182,184,185,186,187,188,189,190,192,193,194,195,198,199,203,204,205,206,212,213,215,217,218,219,220,221,223,224,226,227,230,231,232,233,234,235,236,238,239,241,242,245,246,247,249,250,251,252,255,256,257,258,259,260,261,264,265,266,267,269,271,272,274,276,277,278,279,280,281,282,283,284,285,286,287,289,290,291,292,294,295,296,298,299,304,305,306,307,308,312,315,316,317,318,319,321,322,323,324,325,326,327,328,329,330,331,332,334,336,337,338,339,340,341,342,344,345,357,362,363,364],Their:[51,73,103,109,114,124,189],Theirs:189,Then:[0,9,15,22,38,39,41,42,46,56,61,63,69,91,93,100,107,127,131,137,187],There:[0,5,8,10,11,13,14,15,19,20,21,22,23,25,26,27,31,33,34,38,41,46,49,51,55,57,58,60,61,62,64,68,69,72,73,77,79,80,81,85,86,88,89,90,91,93,95,96,97,98,102,103,104,105,107,108,111,112,113,114,116,117,118,119,121,123,125,127,128,133,136,138,139,167,187,188,215,217,218,219,220,221,235,252,261,272,291,308,321,322,328,336,363],These:[0,4,5,9,11,13,17,22,25,33,34,35,38,39,40,47,49,51,59,61,65,68,69,73,74,83,86,88,90,91,95,96,100,102,103,105,107,109,110,111,112,114,119,121,122,124,125,127,131,133,137,138,139,143,144,145,150,152,154,156,158,160,168,176,180,184,198,199,203,205,206,210,227,233,238,242,247,251,252,261,266,273,292,295,296,298,307,308,309,316,318,321,325,328,329,330,337,338,339,344],USE:[241,364],Use:[1,2,4,5,8,9,12,13,14,20,22,23,24,25,31,38,48,51,54,58,60,63,65,69,70,89,90,93,95,96,100,105,109,114,116,122,123,125,127,131,137,144,151,156,157,159,164,165,169,171,179,180,184,186,199,202,203,204,206,218,219,220,221,226,234,244,245,246,247,269,273,278,295,296,298,299,302,316,318,321,327,328,330,334,341,344,364],Used:[33,121,139,150,153,159,171,175,188,202,215,235,245,259,269,287,318,329,330,350],Useful:[12,51,90],Uses:[114,159,171,186,209,231,267,330,334],Using:[18,22,27,46,51,55,58,60,62,68,80,91,96,115,121,123,139,159,206,218,234,247,287,314,328,364],VCS:36,VHS:188,VPS:90,WILL:[24,91,259],WIS:58,WITH:[23,188],Will:[31,74,110,114,144,184,204,206,247,250,252,265,267,276,277,318,328,330,331,336,339,344,364],With:[8,11,15,19,23,55,57,77,87,100,111,114,122,123,141,144,180,206,252,321,326],Yes:[33,138,188,326,364],__1:337,__2:337,_________________:125,_________________________:51,______________________________:51,________________________________:51,_________________________________:125,______________________________________:328,______________________________________________:51,_______________________________________________:51,____________________________________________________:51,_________________________________________________________:85,__________________________________________________________:85,__all__:[145,237,244],__defaultclasspath__:318,__doc__:[33,59,68,154,167,169,170,239,324,328],__example__:97,__ge__:97,__getitem__:321,__init_:330,__init__:[3,6,11,40,47,49,53,96,97,107,125,152,153,154,174,177,179,180,192,204,206,226,234,242,246,247,251,257,258,260,261,264,265,267,269,270,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,294,295,296,298,305,306,308,310,311,312,315,316,318,319,321,323,326,327,328,329,330,336,337,338,339,340,344,351],__iter__:11,__multimatch_command:168,__noinput_command:[152,168,180,326,328,329],__nomatch_command:[168,180,233,326,328],__send_to_channel_command:168,__settingsclasspath__:318,__unloggedin_look_command:[43,171,186],_action_thre:51,_action_two:51,_all_:152,_asynctest:293,_attrs_to_sync:307,_attrtyp:316,_cach:318,_cached_cmdset:153,_call_or_get:180,_callback:[27,261],_char_index:321,_character_dbref:181,_check_password:51,_check_usernam:51,_clean_str:321,_cleanup_charact:116,_code_index:321,_copi:[159,247],_create_charact:133,_creation:125,_data:329,_default:[51,328],_defend:51,_differ:321,_errorcmdset:153,_event:198,_evmenu:328,_famili:119,_file:337,_flag:251,_footer:33,_format_diff_text_and_opt:252,_get_a_random_goblin_nam:109,_get_db_hold:306,_get_top:69,_getinput:328,_gettabl:272,_http11clientfactori:269,_init_charact:116,_is_fight:29,_is_in_mage_guild:51,_ital:38,_italic_:54,_menutre:[25,51],_monitor:272,_monitor_callback:84,_nicklist_cal:146,_npage:329,_oob_at_:334,_option:51,_overrid:[135,137],_page_formatt:329,_pagin:329,_pending_request:312,_permission_hierarchi:241,_ping_cal:146,_playable_charact:[69,133],_postsav:334,_prefix:206,_quell:241,_raw_str:321,_reactor_stop:[284,305],_recog_obj2recog:206,_recog_obj2regex:206,_recog_ref2recog:206,_regex:206,_repeat:272,_safe_contents_upd:246,_saver:[11,325],_saverdict:[11,325],_saverlist:[11,325],_saverset:325,_sdesc:206,_select:51,_selectfunc:328,_sensitive_:349,_session:328,_set:119,_set_attribut:51,_set_nam:51,_some_other_monitor_callback:84,_start_delai:261,_static:38,_stop_serv:284,_templat:38,_test:150,_validate_fieldnam:58,a2enmod:8,a8oc3d5b:100,a_off:179,aardwolf:88,abbrevi:[76,114,159,202,336],abcd:165,abi:60,abid:126,abil:[6,10,20,31,33,52,55,56,57,58,60,64,73,77,80,90,100,102,108,109,123,127,134,137,138,139,205,206,213,217,218,219,220,221,247,259,267,316],abl:[0,3,4,5,8,11,13,14,19,20,21,22,23,26,27,28,29,31,33,36,38,41,42,47,49,51,52,55,57,58,59,60,61,63,64,69,71,73,75,76,81,83,85,86,87,89,90,91,93,95,96,100,103,104,106,109,111,112,114,116,121,122,123,130,131,133,134,138,140,153,156,157,159,160,174,177,180,184,190,199,206,212,217,218,219,220,221,227,259,316,318,325,340,344,360],abod:241,abort:[25,27,33,51,52,77,89,122,144,154,159,175,213,233,247,250,328,329,364],about:[0,3,9,10,11,12,13,14,15,16,17,20,21,22,23,24,25,26,30,31,33,36,37,38,39,41,42,43,44,45,46,48,51,54,55,57,59,60,61,63,64,68,69,70,71,73,75,76,77,78,79,81,83,85,86,90,91,93,95,96,97,100,101,103,104,108,109,110,112,113,114,116,118,119,120,123,124,126,127,131,134,135,136,138,139,144,159,169,174,179,180,182,185,214,219,220,221,226,227,232,233,239,247,267,269,272,281,283,285,294,296,306,308,315,317,319,321,329,334,336,344,363,364],abov:[2,4,8,9,10,11,12,13,14,21,23,24,27,28,29,30,31,33,36,37,38,40,44,46,49,50,51,56,57,58,59,60,62,63,64,67,68,69,74,80,81,84,85,86,90,91,93,95,96,100,102,105,106,109,110,111,112,114,116,118,119,121,123,125,127,131,132,133,135,137,138,140,152,153,159,180,185,188,190,199,204,206,213,214,215,217,219,220,221,242,245,247,272,315,328,339,350],abridg:41,absolut:[27,38,56,62,79,91,182,184,185,189,327,331,344],absorb:74,abspath:344,abstractus:148,abus:[7,103,364],academi:79,accept:[11,14,22,23,27,31,37,51,54,58,59,74,80,88,90,95,96,109,114,115,125,131,133,134,138,144,150,151,169,179,185,188,193,196,204,205,206,213,231,233,241,267,272,285,311,312,317,322,328,336,340,344],accept_callback:[193,195],accesing_obj:241,access:[0,4,7,8,11,12,13,14,19,21,22,23,25,27,29,31,33,38,39,40,41,43,47,49,51,52,53,56,57,58,59,60,63,64,66,68,69,71,73,74,80,83,84,85,86,87,90,91,95,96,100,101,102,103,104,105,107,108,109,111,112,114,116,119,121,123,124,125,126,127,128,131,133,134,135,137,139,144,145,148,152,153,154,156,157,159,164,165,166,167,168,169,171,174,175,176,177,180,187,190,192,194,203,205,206,217,218,219,220,221,233,234,239,240,241,242,246,247,250,251,252,256,258,260,261,264,267,276,277,306,308,314,315,316,318,319,322,323,324,337,343,344,357,362],access_obj:[241,316],access_object:80,access_opt:345,access_token_kei:[71,120],access_token_secret:[71,120],access_typ:[34,68,89,144,154,159,175,177,239,241,242,247,316,318,362,364],accessed_obj:[25,80,121,241,242],accessing_obj:[1,11,25,80,121,144,175,177,239,241,242,247,316,318],accessing_object:[11,80,241],accessor:[148,177,239,246,256,316,318,319,335],accessori:63,accident:[15,31,38,123,138,157,159,306],accommod:4,accomod:[101,330],accompani:123,accomplish:[12,25,41,49,55],accord:[31,33,111,116,126,180,182,204,205,218,260,321,322],accordingli:[49,58,90,106,175,234],account1:360,account2:360,account:[0,4,6,9,11,12,14,17,19,20,21,22,24,25,31,33,34,35,37,38,41,43,45,47,49,50,51,52,53,55,56,57,61,62,65,66,69,71,74,80,81,83,87,89,90,91,92,96,100,104,105,107,108,109,110,111,112,114,119,120,122,123,125,126,127,129,131,133,134,135,138,139,141,142,149,150,151,152,153,154,155,157,159,160,161,164,165,166,167,169,171,174,175,176,177,180,181,182,184,186,187,188,190,192,193,195,199,206,209,212,217,219,220,221,224,227,230,231,232,233,235,239,241,242,245,246,247,249,251,253,256,267,271,272,287,298,299,306,307,308,316,318,321,324,328,329,338,339,341,342,344,345,349,357,360,362,364],account_cal:[156,164,167,199],account_count:308,account_id:[133,247],account_mod:159,account_nam:56,account_search:[147,206,247],account_subscription_set:148,account_typeclass:[342,360],accountattributeinlin:145,accountcmdset:[2,22,31,41,43,57,58,62,156,160,164,181,199],accountcreateview:362,accountdb:[53,119,125,133,141,144,145,148,175,239,314,315,318,338,345],accountdb_db_attribut:145,accountdb_db_tag:145,accountdb_set:[316,319],accountdbadmin:145,accountdbchangeform:145,accountdbcreationform:145,accountdbmanag:[147,148],accountdbpasswordcheck:287,accountform:[145,357,362],accountid:133,accountinlin:145,accountlist:58,accountmanag:[144,147],accountmixin:362,accountnam:[58,159,171,176,186,324],accounttaginlin:145,accru:144,accur:[22,154,177,192,218,221,252,260,265,267,269,270,278,287,288,290,292,295,296,306,321,336,339,340,351],accuraci:[46,91,218,219,220],accus:73,accustom:[87,124],acept:188,achiev:[0,22,27,33,38,57,114,124,126,138,220,267],ack:52,acquaint:57,acquir:323,across:[16,20,40,51,56,61,86,91,102,105,108,109,125,144,152,153,182,188,233,238,247,250,259,261,264,276,277,291,308,329,330],act:[2,8,13,23,29,31,34,37,49,51,56,58,61,70,77,95,102,105,110,111,123,139,141,159,177,188,215,241,264,276,277,296,316,319,323,328],action1:116,action2:116,action:[0,11,22,29,39,41,42,46,51,55,57,61,62,64,73,88,90,91,93,102,114,116,117,118,123,133,138,145,146,165,175,179,188,206,217,218,219,220,221,230,234,238,239,250,251,256,257,279,298,299,300,310,318,328,329,334],action_count:116,action_nam:[217,218,219,220,221],actiondict:116,actions_per_turn:[217,218,220,221],activ:[4,9,12,13,26,27,28,31,33,36,38,61,62,63,64,65,66,72,75,76,79,80,81,83,89,90,93,95,98,102,105,110,114,128,131,135,136,138,144,150,153,157,159,169,171,174,175,193,210,227,231,235,246,247,250,255,259,260,272,279,280,281,282,283,287,289,290,291,298,308,310,317,326,328,329,330,336,344,364],activest:343,actor:221,actual:[2,5,8,10,11,13,14,19,20,21,22,26,27,29,34,36,40,41,42,44,46,47,49,51,58,59,60,61,63,64,68,69,71,73,79,80,81,83,85,86,87,88,89,90,91,93,95,96,97,100,104,105,106,109,111,112,113,114,115,116,119,121,123,126,127,128,130,133,134,136,137,138,144,150,154,156,159,165,167,168,169,170,171,175,177,179,180,182,187,188,198,202,203,205,206,213,214,215,217,218,219,220,221,227,232,233,235,239,241,242,246,247,251,252,287,290,296,298,304,306,307,308,312,313,318,321,323,324,326,328,329,334,338,339,340,344,362,364],actual_return:127,adapt:[0,4,21,40,69,73,133],add:[0,2,5,6,8,9,10,11,13,14,15,16,17,19,20,21,22,24,26,29,30,31,33,34,35,36,37,38,39,40,41,42,43,44,46,47,48,49,50,51,54,55,57,58,61,62,64,65,66,67,68,69,71,73,74,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,93,95,96,98,100,102,104,105,106,109,111,112,113,114,115,116,117,118,119,120,121,123,124,125,127,128,131,132,133,134,135,137,138,139,140,141,144,148,152,153,159,164,165,166,168,174,175,179,180,181,182,183,185,186,187,192,193,195,196,198,199,202,203,205,206,209,212,213,215,217,218,219,220,221,223,224,226,227,230,231,232,233,234,241,242,246,247,250,252,256,257,258,260,261,267,272,273,277,280,281,283,285,289,296,298,299,301,306,309,316,319,322,326,327,328,329,330,334,336,337,339,340,362,364],add_:330,add_act:116,add_argu:234,add_callback:[193,195],add_channel:174,add_charact:116,add_choic:[180,364],add_choice_:180,add_choice_edit:[22,180],add_choice_quit:[22,180],add_collumn:154,add_column:[58,330],add_condit:219,add_default:[21,31,85,96,121,153,224],add_dist:221,add_ev:195,add_fieldset:[145,244],add_form:[145,244],add_head:330,add_languag:205,add_row:[58,82,154,329,330],add_view:[145,173,244],add_xp:73,addblindedcmdset:227,addcallback:[33,247],addclass:[137,141,142,346,356,358,364],addcom:[43,58,164],added:[0,4,5,17,21,22,24,25,27,31,33,34,36,38,40,41,42,51,55,57,58,60,65,69,70,73,75,77,78,80,86,88,91,96,100,102,106,108,109,110,111,112,114,116,117,119,121,123,128,131,132,133,138,144,150,152,153,154,164,168,169,171,179,180,182,183,185,189,192,195,198,206,217,218,219,220,221,224,235,242,247,252,258,260,272,306,316,319,322,328,329,330,336,337,344,350],addendum:37,adding:[0,3,5,9,14,17,21,22,25,27,29,31,35,36,38,40,43,46,51,57,58,62,69,76,80,81,85,86,91,97,102,104,106,108,109,112,114,115,116,121,123,125,126,128,131,133,137,138,139,152,153,157,159,166,180,184,188,190,192,195,199,205,206,215,217,218,219,220,227,233,234,250,251,252,258,267,298,315,316,324,330,344,364],addingservermxp:282,addit:[4,8,22,25,31,36,37,38,46,49,50,51,58,62,69,76,82,88,90,91,103,104,109,114,119,134,144,146,153,154,175,180,183,192,193,195,205,209,215,221,234,242,247,260,278,306,316,318,326,328,357,364],addition:[25,111,119,221],additionalcmdset:31,addpart:203,addquot:344,addr:[264,277,278,279,324],address:[3,9,12,23,33,40,49,67,87,90,91,103,105,131,135,144,147,157,175,186,189,247,264,277,279,287,307,310,344,345,363,364],address_and_port:287,addresult:203,addscript:[43,159],addservic:40,adjac:[221,231],adject:97,adjoin:206,adjust:[0,33,37,63,126,133,190,260,328,330],admin:[2,9,11,12,15,19,21,33,34,41,49,58,61,68,69,72,80,85,86,98,101,110,119,121,123,133,134,138,141,142,143,148,149,155,159,164,166,169,171,172,175,186,231,236,239,242,243,246,247,253,262,276,277,314,318,324,340,362,363,364],admin_sit:[145,173,237,244,254,263,315],admin_wrapp:362,administr:[10,23,33,36,38,41,55,58,63,64,68,80,103,129,139,264,276,277,364],adminportal2serv:276,adminserver2port:276,adminstr:264,admintest:360,admit:39,adopt:[21,22,26,57,64,177,291],advanc:[10,12,13,22,28,31,33,39,40,44,51,55,58,64,79,86,93,104,105,108,109,111,119,123,124,125,139,159,167,187,204,206,217,218,219,220,221,226,282,322,326,327,328,330,364],advantag:[3,14,15,28,36,39,46,51,55,56,58,59,62,68,69,73,90,103,104,109,116,118,123,133,179,180,209,215,217,218,219,220,221,319,322],advent:181,adventur:[20,41,77,111,122,124],advic:79,advis:[0,22,25,77],aeioui:119,aesthet:50,affair:323,affect:[11,13,14,19,25,31,33,61,62,73,80,81,105,112,114,116,126,127,128,131,138,141,142,144,152,169,183,198,205,212,219,240,247,251,318,322,330,338,364],affili:260,affliat:260,afford:[85,105],afraid:90,after:[0,5,8,9,10,11,14,15,20,21,22,25,27,28,29,30,31,33,36,38,39,41,44,46,49,50,51,55,58,60,63,67,68,76,77,79,80,83,85,86,90,91,96,100,102,103,107,114,116,117,121,122,123,126,127,128,130,131,133,136,138,139,144,152,153,154,155,156,159,167,169,170,171,174,175,179,180,182,184,185,186,187,188,190,195,203,205,206,215,217,218,219,220,221,227,228,231,232,233,234,235,246,247,250,252,257,259,260,267,289,290,293,305,306,307,308,310,312,316,321,322,323,326,328,329,334,336,339,342,343,344,362],after_mov:247,afternoon:187,afterthought:48,afterward:[20,29,69,86,91,119,131,180],again:[0,6,12,13,14,20,21,22,23,28,29,33,39,41,42,47,48,49,51,54,56,57,58,60,61,62,63,64,67,69,73,76,80,81,85,86,90,91,93,95,96,98,100,102,105,106,110,111,114,116,119,121,123,126,128,131,133,138,146,153,164,184,195,204,217,220,221,226,227,235,259,267,284,287,290,310,321,322,325,340,342],against:[6,11,21,31,33,37,57,58,83,90,103,116,119,125,127,144,151,152,174,206,217,218,219,220,221,242,245,247,251,252,285,310,316,318,336,341,344],age:[188,234,357],agenc:103,agent:36,agenta:114,ages:188,aggreg:79,aggress:[11,14,75,122,124,139,231,318,364],aggressive_pac:231,agi:[11,60,127],agil:[11,60],agnost:[37,64,175],ago:[25,100,344],agre:[1,73,113,179],agree:179,ahead:[14,22,24,36,49,61,90,108,121,289],aid:[113,166,167,168,179,312],aim:[7,55,58,61,73,85,86,90,95,108,126,176,251],ain:46,ainnev:[73,119],air:[20,21,111],ajax:[40,55,90,137,296,307],ajaxwebcli:296,ajaxwebclientsess:296,aka:[9,11,93,203,344],alarm:[20,82],alert:247,alexandrian:79,algebra:49,algorith:205,algorithm:[245,344],alia:[2,6,9,20,21,22,31,33,41,43,44,48,51,57,58,59,60,63,87,89,90,95,105,111,112,119,125,127,129,131,145,148,151,154,156,159,164,165,166,167,168,170,173,174,187,192,206,212,228,231,233,235,237,241,244,245,246,247,250,252,254,256,261,272,298,315,317,318,319,324,340,341,342,357,362,364],alias1:[159,187],alias2:[159,187],alias3:187,alias:[2,13,20,21,22,25,27,29,31,33,34,41,44,45,48,51,58,60,74,81,82,85,87,89,109,111,116,119,123,129,131,140,144,152,154,156,157,158,159,164,165,166,167,168,169,170,171,174,175,176,179,180,181,182,185,186,187,188,189,193,199,202,203,206,212,213,214,215,217,218,219,220,221,224,231,232,233,234,235,238,239,245,246,247,252,317,318,319,324,326,328,329,337,341,364],aliaschan:[43,164],aliasdb:144,aliashandl:[315,319],aliasnam:252,aliasstr:324,align:[41,58,109,114,190,321,330,336,344],alik:68,alist:97,aliv:[55,231],alkarouri:343,all:[0,1,2,3,5,6,8,9,10,11,12,13,14,15,16,17,19,20,21,22,23,26,27,28,29,30,31,33,34,35,36,37,38,39,40,41,43,44,46,47,48,49,50,53,54,55,56,57,58,59,60,61,62,63,64,68,70,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,93,95,96,97,98,100,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,121,122,123,124,125,126,127,128,129,131,132,133,134,135,136,137,138,139,140,144,145,146,147,149,150,151,152,153,154,155,156,157,158,159,160,161,164,165,166,167,168,169,170,171,174,175,176,177,179,180,181,182,185,186,187,188,189,192,195,199,202,203,204,205,206,210,212,213,214,215,217,218,219,220,221,224,226,227,230,231,232,233,234,235,237,238,239,240,241,242,243,244,245,246,247,251,252,255,257,258,259,260,261,262,266,267,271,272,273,276,278,279,281,283,284,285,286,287,290,291,294,295,296,298,299,305,306,307,308,310,312,313,314,315,316,317,318,319,321,322,323,324,325,326,327,328,329,330,334,336,337,339,341,343,344,345,350,357,362,363,364],all_alias:112,all_attr:318,all_connected_account:308,all_displai:261,all_famili:119,all_from_modul:344,all_opt:339,all_receiv:247,all_room:13,all_script:102,all_sessions_portal_sync:308,all_to_categori:238,allcom:[43,164],allerror:[267,276],allevi:[11,108,127,312],allheadersreceiv:312,alli:221,alloc:90,allow:[0,2,3,4,6,8,9,10,11,12,13,14,15,16,19,21,22,23,25,26,27,29,30,31,33,34,36,38,39,41,42,44,46,47,49,51,53,54,55,57,58,59,61,63,64,65,68,71,72,73,74,75,76,78,80,81,85,86,87,89,90,91,92,95,96,97,98,100,101,102,103,104,106,108,109,111,112,113,114,116,119,121,123,125,126,129,131,133,134,135,137,138,144,146,148,150,152,153,154,156,157,158,159,164,167,168,169,170,175,176,177,179,180,182,184,185,187,188,189,195,202,204,205,206,215,217,218,219,220,221,231,232,233,234,235,239,241,242,247,250,251,252,257,259,260,261,267,271,272,274,278,280,281,282,283,290,291,292,294,299,305,306,308,310,311,316,318,319,321,322,324,326,328,329,330,331,334,338,339,340,342,344,357,362],allow_dupl:152,allow_nan:296,allow_quit:328,allowed_attr:58,allowed_fieldnam:58,allowed_host:[90,103],allowed_propnam:123,allowedmethod:296,allowext:312,almost:[19,33,41,95,115,119,125,180,182,269,276,314],alon:[13,29,49,51,56,58,73,80,86,87,116,127,138,152,261,272,298,306,316,322,324,330],alone_suffix:303,along:[5,12,33,48,51,60,64,70,74,78,88,91,93,96,100,104,107,114,121,122,139,144,156,179,185,205,209,215,220,242,247,296,314],alongsid:[5,67,188],alonw:256,alpha:[54,90,321,364],alphabet:[15,111,113,321],alreadi:[0,2,5,6,9,11,13,15,21,22,25,27,29,31,33,34,38,40,41,46,49,50,51,54,56,57,58,60,61,63,64,68,69,70,72,73,77,80,81,82,85,88,89,91,95,96,100,102,103,105,106,109,110,112,116,117,118,119,120,121,123,125,127,128,131,133,134,135,136,137,138,139,152,153,156,159,164,167,168,169,174,175,176,179,181,182,204,205,206,217,218,219,220,221,227,231,232,235,242,247,251,252,255,259,267,276,284,285,287,292,295,300,305,306,308,319,321,324,329,344,349],alredi:40,alright:179,also:[0,1,2,3,5,6,8,9,10,11,12,13,14,15,16,17,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,44,46,47,48,49,50,51,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,72,73,74,75,77,79,80,81,82,83,84,85,86,87,88,89,90,91,93,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,121,122,123,124,125,126,127,128,129,131,132,133,134,135,136,137,138,140,144,147,148,151,152,153,154,156,157,158,159,161,165,167,169,170,174,175,176,177,179,180,181,182,185,187,188,190,195,199,202,204,205,206,213,215,219,220,221,226,231,232,233,235,240,241,242,245,246,247,250,251,252,253,255,256,259,260,261,262,267,271,272,276,278,285,287,290,291,294,295,298,299,308,312,314,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,334,336,338,341,344,346,362,363],alt:321,alter:[0,4,23,41,64,111,137],altern:[23,29,33,34,38,51,55,57,63,64,68,72,76,81,87,90,111,112,114,118,119,122,131,133,138,140,167,168,175,203,206,221,224,241,242,245,285,324,336,344,364],although:[22,29,39,42,63,119,156,180,181,185,312,340,344],althougn:46,altogeth:[50,103,114],alu:33,alwai:[0,2,4,6,8,11,12,13,14,20,21,23,25,27,30,31,33,34,37,38,39,47,49,51,57,58,61,62,63,64,69,72,73,74,77,80,85,86,88,89,90,91,95,96,102,105,107,109,112,114,115,121,123,125,126,127,128,131,134,135,137,144,152,153,154,156,158,159,164,167,170,175,176,177,199,205,206,212,224,227,241,242,245,246,247,250,251,252,259,261,267,269,272,276,284,287,290,291,295,296,299,306,308,313,316,317,318,319,321,324,334,336,340,341,344,345,362],always_pag:329,always_return:267,amaz:75,amazon:[79,90],ambianc:108,ambigu:[41,154,174,189,247,318],ambiti:[108,129],amend:131,amfl:14,ammo:21,among:[2,35,36,62,64,79,89,104,111,123,127,165,182,224,232,242,245,341],amongst:77,amor:196,amount:[11,16,37,61,68,73,102,103,114,123,169,217,218,219,220,221,247,308,326],amp:[40,83,92,105,141,142,262,264,267,275,277,285,293,305,308,364],amp_client:[141,142,262,364],amp_maxlen:293,amp_port:90,amp_serv:[141,142,262,275,364],ampclientfactori:264,ampersand:108,amphack:276,ampl:124,amplauncherprotocol:267,ampmulticonnectionprotocol:[264,276,277],ampprotocol:264,ampserverclientprotocol:264,ampserverfactori:277,ampserverprotocol:277,amsterdam:90,anaconda:9,analog:[49,83],analys:51,analysi:210,analyz:[15,33,41,51,80,118,150,159,175,206,251,252,257,267,329,344,364],anchor:[175,221,239,318],anchor_obj:221,ancient:114,andr:24,android:[139,364],anew:[63,111,267],angl:129,angri:41,angular:169,ani:[0,1,2,5,6,8,10,11,12,14,15,16,19,20,21,22,23,24,25,27,30,31,33,34,36,37,38,39,40,41,42,44,48,49,50,51,54,56,57,58,59,60,61,63,64,65,68,70,72,73,74,76,77,79,80,81,82,83,84,85,86,87,88,89,90,91,95,96,97,98,100,102,103,104,105,107,109,112,114,115,116,117,118,119,121,122,123,125,126,127,128,129,131,133,134,135,136,137,138,139,140,144,148,150,151,152,153,154,156,157,159,165,169,170,175,176,177,179,180,181,182,186,187,188,189,190,194,199,202,204,205,206,209,210,213,217,218,219,220,221,223,224,231,233,234,235,241,242,245,247,250,251,252,255,256,257,259,260,261,264,265,267,269,271,272,276,277,279,285,286,287,290,291,295,296,298,306,307,308,312,316,317,318,319,321,322,323,325,326,327,328,329,330,336,337,338,339,340,341,343,344,362,364],anim:[27,52],anna:[58,63,72,117,118,123,159],annoi:[12,85,91],annot:[79,364],announc:[25,37,79,116,123,128,157,169,217,218,219,220,221,247],announce_al:[285,308],announce_move_from:[25,77,89,247],announce_move_to:[25,77,89,247],annoy:144,anonym:[4,66,69,206],anonymous_add:206,anoth:[0,8,10,11,13,14,16,21,22,29,31,33,36,39,42,46,49,51,56,57,58,62,63,64,67,69,77,78,80,89,90,91,96,97,98,102,105,106,108,109,111,112,113,114,116,121,123,127,131,132,136,137,138,139,140,144,152,153,156,159,164,165,175,179,180,182,188,194,199,204,206,215,217,218,219,220,221,232,235,239,247,250,308,316,318,322,326,328,329,336,344,364],another_batch_fil:322,another_nod:328,another_script:102,anotherscript:102,ansi:[24,53,55,74,81,137,141,142,156,183,190,202,272,279,287,290,295,296,320,330,336,343,364],ansi_escap:321,ansi_map:321,ansi_map_dict:321,ansi_pars:321,ansi_r:321,ansi_regex:321,ansi_sub:321,ansi_xterm256_bright_bg_map:321,ansi_xterm256_bright_bg_map_dict:321,ansimatch:321,ansimeta:321,ansipars:321,ansistr:[141,321,330],ansitextwrapp:330,answer:[0,11,21,25,26,33,46,51,61,63,67,69,70,73,95,96,103,127,265,271],anti:63,anul:8,anwer:44,any_options_her:38,anybodi:[59,103],anymor:[4,181,195,203,204,235,328,340],anyon:[1,4,12,21,25,29,41,42,54,58,60,76,80,85,90,116,118,119,123,138],anyth:[0,1,5,11,13,16,19,20,22,23,26,29,31,33,34,40,41,42,46,49,51,56,61,63,64,69,73,80,82,83,85,87,89,90,91,95,96,100,102,104,106,111,116,118,121,123,125,127,128,130,131,133,135,136,137,138,152,154,168,180,206,215,217,218,219,220,221,242,279,313,322,328],anywai:[0,4,14,20,51,55,75,76,91,95,108,114,140,179,181,186],anywher:[33,51,60,64,95,96,125,134,326],apach:[7,23,90,103,139,312,364],apache2:8,apache_wsgi:8,apart:[2,11,20,27,34,47,55,63,80,81,100,104,125,126,127,134,221],api:[13,15,26,27,34,42,47,48,52,59,60,71,73,89,96,105,109,111,120,125,133,138,139,141,144,158,169,171,177,186,306,316,318,322,323,329,363,364],api_kei:71,api_secret:71,apostroph:15,app:[4,40,71,80,86,90,134,135,136,138,139,364],app_id:133,app_label:145,appar:[48,58,126],apparit:233,appeal:[51,61,114],appear:[9,10,21,22,25,26,27,30,38,47,51,60,63,65,66,68,72,80,82,90,95,96,100,102,104,106,111,114,123,126,127,131,137,138,141,156,166,182,195,206,212,235,247,291,292,315,318,330,336,337],append:[20,22,25,27,31,39,40,49,50,51,68,69,80,85,88,89,90,91,93,96,97,116,123,127,133,138,154,159,166,182,199,206,242,245,300,322,336,337,344],appendix:241,appendto:137,appform:133,appl:[179,247],appli:[0,8,9,13,16,22,23,31,33,36,37,51,60,80,81,102,106,111,115,121,125,126,128,133,144,150,152,167,183,217,218,219,220,221,235,242,247,251,252,256,261,308,316,317,318,321,322,327,330,331,341,344],applic:[8,40,63,79,80,86,100,103,112,124,128,133,134,135,136,144,187,188,221,267,270,280,284,291,305,306,312,354,362],applicationdatareceiv:290,applied_d:133,apply_damag:[217,218,219,220,221],apply_turn_condit:219,appnam:[11,80],appreci:[22,37,70,78,334],approach:[22,25,39,56,77,91,106,115,133,180,221],appropri:[8,9,23,31,33,36,55,71,91,106,119,121,129,133,138,144,157,175,190,267,306,338,340,344],approrpri:40,approv:[133,134,138],approxim:[5,169,344],april:62,apt:[8,63,67,75,90,103,131],arbitr:61,arbitrari:[11,13,19,27,46,59,64,80,96,97,100,111,125,137,138,139,140,144,175,187,215,221,233,247,252,259,265,276,296,316,325,336,337,340,364],arcan:129,archer:252,architectur:[80,252],archiv:[79,103],archwizard:252,area:[2,22,24,48,49,51,58,61,79,117,122,127,138,231,235,241,327,328,330,364],aren:[0,4,29,39,69,103,127,131,133,136,138,144,182,188,195,203,219,337,340],arg1:[80,154,167,168,170,250,316,336],arg2:[154,167,168,170,250,316,336],arg:[1,5,10,21,22,25,29,30,33,38,39,40,41,42,51,58,59,68,71,73,74,80,81,83,85,88,96,109,114,115,116,119,121,123,129,132,137,144,145,146,147,148,151,154,159,167,168,170,175,176,177,179,182,184,187,189,192,195,203,204,205,206,212,213,214,215,217,218,219,220,221,223,226,227,231,232,233,234,235,238,239,241,242,245,246,247,250,251,252,255,256,259,260,261,264,272,273,274,276,277,278,279,284,285,287,288,290,291,292,295,296,300,306,308,312,315,316,317,318,319,321,328,329,330,331,333,334,336,337,340,342,344,345,357,362,364],arg_regex:[5,41,44,154,159,165,166,170,171,174,182,326,364],arglist:[167,168],argpars:234,argu:11,argument:[3,4,5,10,12,14,20,21,22,23,25,27,29,31,33,34,40,41,42,46,48,50,52,57,58,59,62,69,74,80,81,83,85,87,88,89,93,95,96,102,109,111,114,115,119,123,124,125,127,129,134,139,144,146,150,151,153,154,156,157,159,164,165,166,167,168,169,170,175,176,180,182,184,187,188,189,192,194,195,204,205,206,210,212,217,218,219,220,221,233,234,242,245,247,250,251,252,255,257,259,260,261,265,267,272,276,278,279,285,286,287,290,291,295,296,298,299,305,306,307,308,310,311,316,317,318,319,321,322,324,326,327,328,329,330,334,336,338,340,341,344,362,364],argumentpars:234,argumnet:330,argumu:336,aris:103,arm:[26,33,203,364],armi:85,armor:[29,82,182,218],armour:29,armouri:77,armpuzzl:203,armscii:[15,113],arnold:87,around:[0,4,10,13,14,15,21,23,29,31,34,38,39,42,49,55,58,61,63,64,69,70,71,73,77,79,80,85,89,90,91,96,109,111,113,114,116,117,119,121,123,129,136,138,139,159,167,168,182,184,194,203,206,221,224,231,232,233,235,247,321,322,330,337],arrai:[88,91,291,344],arrang:22,arrayclos:[88,291],arrayopen:[88,291],arriv:[0,25,29,73,77,83,105,159,279],arrow:[42,137],art:[114,327],articl:[4,15,21,39,41,48,57,79,113,127,131,335],article_set:335,artifact:330,artifici:73,arx:[79,364],arxcod:[79,139,364],as_view:[175,239,318],ascii:[9,15,111,113,144,171,327,330,344,364],asciiusernamevalid:144,asdf:159,ashlei:[182,188,190,215,217,218,219,220,221],asian:344,asid:[9,227],ask:[1,10,21,23,26,34,37,42,46,48,50,54,58,63,67,68,69,70,73,84,90,91,93,97,119,124,131,133,152,154,159,179,184,193,204,234,265,267,294,328,331,344,364],ask_choic:265,ask_continu:265,ask_input:265,ask_nod:265,ask_yesno:265,asn:209,aspect:[48,51,57,60,64,68,73,86,109,127,190],assert:[116,127],assertequ:127,assertregex:127,asserttru:127,asset:[103,136,271],assetown:9,assign:[2,6,11,12,13,20,36,51,56,58,80,87,89,97,102,109,112,115,116,119,121,123,131,137,138,144,150,151,153,159,166,167,168,170,183,187,188,206,217,218,219,220,221,233,242,246,247,251,252,255,272,279,285,287,290,306,318,325,364],assist:90,associ:[4,11,29,51,79,83,90,105,122,135,138,144,149,159,175,192,195,206,247,306,308,317,362],assort:[362,364],assum:[0,3,5,9,12,13,14,15,19,20,21,22,25,27,28,29,31,33,34,37,38,39,40,41,44,46,47,49,51,55,56,58,60,62,68,73,74,75,80,81,82,84,85,89,90,95,96,97,100,102,103,105,106,108,109,110,111,113,115,116,117,118,120,121,123,127,128,132,133,134,138,150,152,153,154,156,159,170,175,180,181,206,213,232,233,241,247,252,257,259,291,308,321,322,328,336,344,349,362],assumpt:151,assur:[49,125],asterisk:[2,12,38,157],astronaut:77,astronom:62,async:[133,139,344,364],asynccommand:10,asynchron:[27,28,29,33,45,55,64,92,93,139,146,247,276,277,291,337,344,364],at_:[125,334],at_access:[144,247],at_account_cr:[2,144],at_after_mov:[77,89,96,117,247],at_after_object_leav:235,at_after_travers:[89,232,247],at_befor:247,at_before_drop:[218,221,247],at_before_g:[218,221,247],at_before_get:[221,247],at_before_mov:[25,77,89,217,218,219,220,221,247],at_before_sai:[96,206,247],at_channel_cr:175,at_char_ent:117,at_cmdset_cr:[5,21,22,25,30,31,33,41,44,57,58,62,81,85,116,121,123,152,160,161,162,163,179,180,181,182,185,187,199,202,203,206,214,217,218,219,220,221,224,230,231,232,233,326,328,329],at_cmdset_get:[144,247,306],at_db_location_postsav:246,at_defeat:[217,218,219,220,221],at_desc:247,at_disconnect:[144,306],at_drop:[218,221,247],at_end:256,at_err:[10,344],at_err_funct:10,at_err_kwarg:[10,344],at_failed_login:144,at_failed_travers:[89,212,232,247],at_first_login:144,at_first_sav:[144,175,247],at_first_start:318,at_get:[182,221,247],at_giv:[218,221,247],at_heard_sai:118,at_hit:231,at_idmapper_flush:[259,318,334],at_init:[6,107,125,144,175,231,232,233,247],at_initial_setup:[104,271],at_initial_setup_hook_modul:271,at_login:[40,125,278,279,287,290,295,296,306],at_look:[48,96,144,247],at_message_rec:144,at_message_send:144,at_msg_rec:[144,189,247],at_msg_send:[144,146,189,247],at_new_arriv:231,at_now_add:86,at_object_cr:[5,6,21,25,31,39,49,58,60,73,80,81,85,89,96,121,123,125,132,159,187,189,206,212,214,217,218,219,220,221,226,231,232,233,247,318],at_object_delet:247,at_object_leav:[89,233,235,247],at_object_post_copi:247,at_object_rec:[89,117,233,235,247],at_password_chang:144,at_post_cmd:[30,33,150,154,167,170],at_post_command:33,at_post_disconnect:144,at_post_login:[25,144],at_post_portal_sync:305,at_post_puppet:[96,247],at_post_unpuppet:[96,247],at_pre_cmd:[33,150,154,167,170],at_pre_command:33,at_pre_login:144,at_pre_puppet:[96,247],at_pre_unpuppet:247,at_prepare_room:235,at_reload:[169,305],at_renam:318,at_repeat:[102,116,120,121,125,146,179,184,195,217,218,219,220,221,223,227,259,300,331],at_return:[10,344],at_return_funct:10,at_return_kwarg:[10,344],at_sai:[118,247],at_script_cr:[102,116,120,121,146,179,184,195,204,205,217,218,219,220,221,223,227,235,251,259,300,331],at_search:104,at_search_result:[168,344],at_server_cold_start:305,at_server_cold_stop:305,at_server_connect:285,at_server_reload:[102,110,144,146,247,259],at_server_reload_start:305,at_server_reload_stop:[25,305],at_server_shutdown:[102,110,144,146,247,259],at_server_start:305,at_server_startstop:[25,104],at_server_stop:305,at_shutdown:305,at_start:[102,116,146,195,227,235,256,259],at_startstop_modul:261,at_stop:[102,116,121,217,218,219,220,221,227,259],at_sunris:62,at_sync:[306,307],at_tick:[115,261],at_travers:[89,213,235,247],at_traverse_coordin:235,at_turn_start:219,at_upd:[219,257],at_weather_upd:132,atlanti:24,atom:98,atop:235,atribut:325,att:51,attach:[4,11,21,41,56,58,64,77,89,95,102,105,110,112,119,140,154,159,164,167,189,199,215,235,242,247,258,304,315,319,364],attachmentsconfig:4,attack:[14,28,29,30,46,51,77,90,103,116,119,122,134,139,153,206,215,217,218,219,220,221,231,232,247,252,285,364],attack_count:220,attack_messag:73,attack_nam:220,attack_skil:252,attack_summari:73,attack_typ:221,attack_valu:[217,218,219,220,221],attempt:[0,2,22,24,29,31,51,60,61,87,91,103,106,119,120,135,156,159,187,210,212,217,218,219,220,221,264,267,272,305,310,318,344,362],attent:[38,56,58,89,103,111],attitud:57,attr1:[159,203],attr2:[159,203],attr3:159,attr:[11,22,49,51,58,80,109,119,136,137,159,166,180,233,241,251,252,306,334,340],attr_categori:315,attr_eq:241,attr_g:[80,241],attr_gt:[80,241],attr_kei:315,attr_l:[80,241],attr_lockstr:315,attr_lt:[80,241],attr_n:[80,241],attr_nam:159,attr_obj:318,attr_object:318,attr_typ:315,attr_valu:315,attract:37,attrcreat:[80,316],attread:11,attredit:[11,80,316],attrib:242,attribut:[0,2,6,12,20,22,25,27,28,30,39,41,42,45,46,49,50,51,56,57,58,60,61,69,73,74,77,80,81,82,84,85,86,87,89,91,95,102,105,108,109,112,115,116,119,123,125,127,133,134,138,139,141,142,144,145,148,153,159,168,169,173,175,180,181,187,194,195,202,203,206,213,217,218,219,220,221,226,231,232,233,241,244,245,246,247,250,251,252,254,256,257,260,272,306,314,315,317,318,319,324,325,337,338,341,344,357,362,364],attribute1:123,attribute2:123,attribute_nam:[144,206,245,247,341],attribute_valu:245,attributeerror:[42,60,86,306,316],attributeform:315,attributeformset:315,attributehandl:[1,125,306,316,339,344,364],attributeinlin:[145,173,244,254,315],attributeobject:11,attrkei:252,attrnam:[11,51,80,109,125,159,241,245,318],attrread:[11,80,316],attrtyp:[11,316,317],attrvalu:51,attryp:317,atttribut:49,atyp:242,audibl:205,audio:137,audit:[141,142,175,178,207,247,364],audit_callback:209,auditedserversess:[209,210],auditingtest:211,aug:[9,364],august:[9,344],aut:52,auth:[144,145,147,148,164,287,349,357,362],auth_password:287,auth_profile_modul:148,authent:[40,103,105,107,133,138,144,278,285,287,290,296,306,308,349,362],authenticated_respons:360,author:[41,90,126,144,192,195],auto:[0,5,12,14,21,31,32,33,34,38,42,43,45,51,63,67,71,89,95,96,105,122,131,133,138,141,144,148,150,154,158,159,166,169,170,205,206,227,236,239,242,247,252,256,259,261,264,267,278,288,295,296,305,308,318,323,329,330,349,364],auto_help:[33,41,44,51,68,69,154,170,188,230,249,328,329],auto_help_display_kei:[154,170,328],auto_id:[145,237,244,357],auto_look:[51,188,230,249,328],auto_now_add:86,auto_quit:[51,188,230,249,328],auto_transl:205,autobahn:[278,284,295],autodoc:38,autofield:133,autologin:349,autom:[14,36,57,58,67,79,86,100,103,110,362],automat:[0,6,10,14,19,22,23,27,30,31,34,37,41,46,47,50,51,55,58,60,62,64,65,66,67,68,71,72,80,81,84,85,86,90,96,97,100,102,104,105,109,111,116,117,118,119,121,122,123,124,125,126,128,131,135,136,139,140,144,152,153,154,159,164,165,167,174,179,180,181,182,194,195,196,203,204,205,206,214,221,226,227,234,242,246,247,258,259,260,261,272,281,284,287,292,305,308,322,326,328,329,330,344,350,364],automatical:261,autostart:[258,324],autumn:[97,99,187],avail:[0,5,7,8,10,11,13,16,21,22,23,25,26,31,33,36,38,39,40,41,42,44,46,48,49,51,53,57,58,60,62,63,64,65,72,74,75,76,77,78,79,80,81,82,85,88,89,90,91,95,96,98,100,102,104,105,106,108,109,110,111,113,114,116,119,121,122,123,125,127,128,130,131,133,134,137,138,139,141,144,150,151,152,153,154,156,159,161,164,165,166,167,168,169,170,171,179,180,181,185,187,189,195,199,202,204,205,206,214,215,217,218,219,220,221,224,232,233,241,242,247,250,251,252,256,272,296,299,310,321,322,323,328,329,330,336,344,362,364],available_choic:[51,328],available_func:336,available_funct:251,available_languag:205,available_weapon:232,avatar:[64,88,96,247,287],avatarid:287,avenu:182,averag:[13,90,93,169,195,205,234],avoid:[8,11,23,26,27,31,33,37,40,42,51,80,81,85,95,97,100,109,111,114,125,126,127,129,131,138,139,152,159,204,205,234,235,241,246,276,286,296,306,316,318,321,322,323,326,329,334],awai:[0,9,10,11,14,15,21,26,29,42,46,49,51,55,66,68,69,73,80,86,90,96,102,105,109,111,121,123,131,165,182,215,218,221,224,226,231,233,235,247,256,307,321,344],await:10,awar:[11,14,26,31,33,44,51,88,95,96,110,125,126,132,133,189,204,206,231,234,235,247,318,321],awesom:[63,135],aws:90,axhear:241,azur:[90,100],b64decod:340,b64encod:340,b_offer:179,baaaad:127,babi:138,bacground:67,back:[0,3,5,10,11,12,13,14,20,21,22,23,25,26,27,29,31,33,34,36,38,46,49,50,51,56,58,60,61,63,64,67,69,73,74,81,83,85,86,87,90,91,95,96,97,100,102,105,106,110,111,113,116,118,119,121,122,123,125,126,131,133,135,137,141,144,153,156,159,164,168,179,180,206,212,215,220,224,249,259,267,272,276,279,285,287,290,305,318,325,328,329,337,344],back_exit:0,backbon:[133,322],backend:[23,36,109,127,135,141,142,344,346,348,364],background:[10,17,29,51,67,90,103,110,114,126,133,183,190,321,336,362],backpack:31,backslash:[114,329],backtick:[38,131],backtrack:131,backup:[10,89,90,105,131,168,322],backward:[50,51,58,121,147,337],bad:[0,22,24,37,41,58,64,70,76,85,119,127,210,269],bad_back:242,badg:130,bag:344,bake:100,balanc:[29,56,61,79,116,330],balk:95,ball:[31,59,104,151,152,252],ballon:203,balloon:203,ban:[7,25,80,139,144,157,242,364],band:[45,88,118,137,287,290,291],bandit:46,bandwidth:280,banid:157,bank:61,bar:[51,82,83,84,88,112,135,137,190,206,215,291,328],bare:[33,55,58,73,104,190,218],barehandattack:56,bargain:86,barkeep:[42,206],barter:[61,63,102,117,141,142,178,364],bartl:79,bas:120,base:[3,4,6,9,13,16,17,20,21,22,23,30,33,36,38,39,41,42,49,51,53,55,56,57,58,60,61,63,64,67,69,72,73,75,77,79,80,83,85,86,89,90,96,100,102,103,105,108,111,113,115,119,120,123,124,125,126,127,129,130,133,134,136,137,138,139,141,144,145,146,147,148,150,152,153,154,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,173,174,175,176,177,179,180,181,182,184,185,186,187,188,189,192,193,195,196,198,199,202,203,204,205,206,210,211,212,213,214,215,217,218,219,220,221,223,224,226,227,228,230,231,232,233,234,235,237,238,239,242,244,245,246,247,249,251,252,254,255,256,257,258,259,260,261,263,264,265,267,269,270,273,274,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,298,299,300,303,305,306,307,308,310,311,312,315,316,317,318,319,321,322,323,326,327,328,329,330,331,333,334,335,336,337,338,339,340,341,342,343,344,349,351,352,357,360,362,364],base_account_typeclass:2,base_channel_typeclass:34,base_char_typeclass:120,base_character_typeclass:[81,120,133,134,144,159],base_field:[145,237,244,315,357],base_guest_typeclass:66,base_object_typeclass:[109,252,318],base_random:250,base_script_path:241,base_script_typeclass:102,base_set:9,baseclass:232,basecommand:60,basecontain:323,baseinlineformset:315,baseline_index:344,baseobject:125,baseopt:338,basepath:344,basetyp:[247,322],basetype_posthook_setup:247,basetype_setup:[39,80,96,144,146,175,247],bash:[36,63,67,232],basi:[4,33,37,62,90,136,138,167,177,206,241,296,318,327],basic:[0,2,3,6,9,15,16,17,19,20,22,26,29,31,33,34,36,39,40,46,47,48,56,57,58,60,61,62,69,73,77,79,80,81,83,86,87,110,111,113,116,117,118,121,122,124,126,128,133,134,135,137,139,144,146,159,164,166,175,177,188,194,203,218,220,232,241,243,247,298,326,329,342,346,357,362,364],bat:[9,63],batch:[18,20,43,48,63,79,111,122,124,139,141,142,158,252,276,316,319,320,364],batch_add:[252,316,319],batch_cmd:14,batch_cod:[13,322],batch_code_insert:13,batch_create_object:252,batch_exampl:322,batch_import_path:[13,14],batch_insert_fil:14,batch_update_objects_with_prototyp:252,batchcmd:[43,158],batchcmdfil:[14,322],batchcod:[14,43,79,111,158,364],batchcode_map:111,batchcode_world:111,batchcodefil:13,batchcodeprocessor:322,batchcommand:[14,20,22,43,63,122,158,322],batchcommandprocessor:322,batchfil:[14,15,111,322],batchprocess:[141,142,149,155,364],batchprocessor:[13,141,142,158,320,364],batchscript:[13,322],batteri:144,battl:[79,103,116,122,217,218,219,220,221],battlecmdset:[217,218,219,220,221],baz:215,bazaar:108,beach:111,bear:[204,231],beat:[61,116],beaten:[116,233],beauti:[22,49,133],beazlei:79,becam:[29,126],becaus:[0,2,6,8,9,10,11,12,13,15,16,21,22,25,29,31,36,38,40,41,42,44,46,51,54,56,59,64,68,73,76,77,80,89,91,95,96,107,108,109,111,115,116,117,119,125,126,130,133,134,136,145,153,171,175,186,194,205,220,224,235,247,259,279,285,298,308,315,321,338,340],becom:[0,5,10,22,37,38,41,42,47,49,51,56,59,61,64,70,73,78,80,81,86,87,88,95,96,102,104,109,111,119,128,156,189,203,205,215,218,252,306,322,328],bed:61,been:[0,4,5,6,13,14,19,21,22,23,36,38,41,42,46,49,51,58,69,70,76,79,85,91,93,96,103,105,116,117,123,126,128,131,133,134,135,138,152,153,158,159,167,168,175,180,195,203,204,206,217,218,219,220,221,233,235,239,242,246,247,251,252,260,261,269,281,285,287,295,305,306,307,308,310,315,318,322,326,327,344,362],befit:125,befor:[1,4,10,11,12,13,14,15,20,21,22,25,27,28,29,31,33,37,41,42,46,48,49,51,56,57,58,60,61,67,69,71,75,77,79,80,81,84,85,86,90,91,93,96,97,100,102,103,104,106,107,108,109,111,112,113,114,115,116,117,118,119,121,123,124,125,126,127,131,132,133,134,135,137,138,139,144,150,151,154,159,164,167,171,175,184,186,187,188,189,190,194,198,205,206,209,210,215,217,218,219,220,221,226,227,230,232,233,235,241,242,246,247,250,252,259,260,261,267,276,285,287,293,301,303,305,306,310,312,316,321,322,323,324,328,329,330,331,335,337,340,344,362,364],beforehand:[11,131,323],beg:14,beggar:0,begin:[0,4,6,10,13,14,20,22,25,33,38,41,42,46,50,51,55,58,61,69,72,80,91,95,96,106,107,111,116,117,119,127,132,134,165,194,206,215,217,218,219,220,221,245,247,259,321,322,328,341],beginn:[55,60,77,79,91,95,124],behav:[11,13,20,22,29,69,91,95,107,110,127,251,344],behavior:[0,5,11,31,33,41,50,68,69,93,96,102,109,114,126,135,137,138,144,154,170,182,188,219,221,233,234,267,315,328],behaviour:[11,31,33,80,126,313,324,330,344],behind:[11,12,21,33,49,51,55,59,61,63,74,97,109,112,114,122,126,131,158,204,233,256,261,334],behvaior:329,being:[0,5,6,10,11,13,20,21,22,25,28,31,33,34,36,37,42,51,54,56,59,61,63,64,69,83,88,90,91,93,95,96,102,103,107,109,111,115,118,125,126,127,129,131,133,138,144,151,159,165,169,175,184,185,189,199,205,206,217,218,219,220,221,226,227,233,239,247,269,272,279,308,310,315,318,321,322,324,328,329,330,344,363],beipmu:24,belong:[4,14,64,83,95,103,112,119,133,140,153,206,215,235,239,250],below:[0,1,5,8,9,10,11,12,13,14,15,19,20,22,23,25,27,29,31,33,34,36,38,39,42,48,49,50,51,57,58,59,60,61,62,63,64,67,69,70,74,80,81,87,88,90,95,96,100,102,105,106,109,110,111,114,117,118,119,123,125,127,131,133,134,136,138,140,148,159,167,168,177,180,182,185,190,205,206,215,217,218,219,220,221,228,234,239,241,246,247,256,279,299,316,318,319,328,330,335,336],belt:77,beneath:27,benefici:[49,219],benefit:[78,90,100,103,108,127,153,316,322,328],besid:[0,14,31,106,111,190],best:[9,22,24,26,37,50,51,57,58,59,61,72,76,102,103,104,108,133,135,139,166,180,205,215,234,252,267,287,330,338,364],bet:[31,105,138,318],beta:[35,54,90,364],betray:51,better:[0,9,15,23,25,34,38,41,42,44,45,51,55,58,59,61,64,68,70,73,81,85,86,91,93,95,108,109,112,114,133,134,181,213,218,224,233,252,284,287,290,298,322],bettween:73,between:[0,2,10,14,22,25,28,31,33,36,38,39,40,41,46,49,56,57,58,64,67,69,73,76,83,85,87,88,90,91,100,102,105,109,112,113,114,116,120,121,122,123,124,126,131,137,138,140,151,154,159,164,166,169,170,177,179,182,183,194,195,198,199,202,204,205,206,215,217,218,219,220,221,247,252,261,267,276,279,286,287,290,291,298,299,306,319,321,322,324,328,330,331,336,344,351,364],bew:187,bewar:39,beyond:[1,2,9,22,25,33,37,52,57,64,88,89,90,102,127,134,154,159,170,177,180,206,215,233,251,318,328,330],bg_colormap:343,bgcolor:343,bgfgstart:343,bgfgstop:343,bgstart:343,bgstop:343,bias:159,bidirect:276,big:[9,11,13,14,20,25,28,29,33,37,45,57,73,80,96,122,138,140,151,168,245,322,329,341,344],bigger:[21,37,40,69,119,123],biggest:[72,138],biggui:33,bigmech:21,bigsw:29,bikesh:119,bill:[90,103],bin:[4,9,36,47,63,64,75,96,100],binari:[23,47,63,93,95,278,280,295],bind:67,birth:357,bit:[0,4,9,12,17,22,26,29,35,39,41,42,46,59,61,62,63,69,75,76,81,96,102,106,109,121,122,127,131,134,137,138,171,186,242,247,322],bitbucket:57,bite:[61,111],black:[73,114,126],blackbird:79,blackbox:138,blacklist:103,blade:232,blank:[51,86,117,134,144,188,321],blankmsg:188,blarg:83,blargh:109,blatant:12,blaufeuer:119,bleed:[114,131,330],blend:203,blender:203,bless:138,blind:[114,118,224,227],blindcmdset:224,blindedst:227,blindli:242,blink:[20,226,227,343],blinkbuttonev:227,blist:97,blob:[37,38,95,104,138],block:[3,12,25,28,50,51,55,58,64,69,80,90,91,97,102,103,110,114,123,129,133,134,139,157,158,159,187,221,230,231,232,235,249,286,322,328,336,344,362,364],blocking_cmdset:25,blockingcmdset:25,blockingroom:25,blockquot:364,blocktitl:69,blog:[37,55,79,90,98],blowtorch:24,blue:[13,57,81,114,126,232],blueprint:[57,96,111,137],blurb:54,board:[34,49,61,79,80,121],boat:[31,121,153],bob:[33,81,138,157],bodi:[3,17,22,27,33,41,46,51,58,95,109,127,129,133,175,193,199,269,324],bodyfunct:[20,102,141,142,178,222,228,364],bog:21,boi:112,boiler:125,bold:[54,364],bolt:252,bone:[55,73],bonu:[41,73,90,218,219,256],bonus:[29,218],book:[3,49,62,73,79,91,95,109,135],bool:[2,31,33,34,51,74,84,102,144,145,146,147,148,150,151,152,153,154,159,173,175,176,177,179,180,182,184,185,188,190,192,195,204,205,206,215,217,218,219,220,221,226,235,238,242,244,245,246,247,250,251,252,254,255,256,257,258,259,260,261,267,272,273,278,279,284,285,286,290,295,296,304,306,308,310,316,317,318,319,321,322,324,326,328,329,330,331,334,336,339,341,343,344],booleanfield:[133,145,237],boom:[21,51],boot:[80,100,110,157,261,364],bootstrap:[4,124,138,139,364],border:[58,111,156,188,327,330,364],border_bottom:330,border_bottom_char:330,border_char:330,border_left:330,border_left_char:330,border_right:330,border_right_char:330,border_top:330,border_top_char:330,border_width:330,borderless:58,borderstyl:188,bore:[12,55,103],borrow:[31,63,152,276],bort:52,boss:58,bot:[47,65,72,93,103,119,133,141,142,143,148,164,175,272,278,279,286,308,362,364],bot_data_in:[146,272],both:[0,11,15,19,22,23,25,26,27,31,33,34,36,37,38,40,44,49,51,56,57,58,62,65,69,71,74,79,84,85,86,87,88,90,91,95,97,103,104,105,106,110,111,116,119,121,124,125,127,128,131,133,134,136,138,150,152,159,164,169,177,179,183,190,199,203,212,215,220,221,233,242,245,247,251,252,253,256,259,261,276,285,295,296,305,307,310,316,317,321,324,328,330,339,344],bother:[29,103,128,174],botnam:[72,164,279,308],botnet:103,botstart:146,bottom:[4,39,41,52,54,57,58,60,69,85,95,101,106,111,125,127,133,137,153,199,220,235,252,322,329,330],bought:85,bouncer:[27,103,327],bound:[6,27,38,57,108,192,344],bounti:70,bountysourc:70,bow:252,box:[0,3,8,20,42,46,58,63,66,69,70,71,73,80,87,90,104,106,109,111,123,135,138,159,206,241,276,322,357],brace:[0,22,25,41,91,247,321],bracket:[38,96,129,169,183],brainstorm:[139,364],branch:[9,36,37,38,41,63,100,204,215,364],branchnam:131,brandymail:199,bread:16,breadth:221,break_lamp:226,break_long_word:330,break_on_hyphen:330,breakdown:169,breakpoint:[16,106,141],breez:[102,132],breviti:58,bribe:51,brick:82,bridg:[22,23,53,79,83,105,233,364],bridgecmdset:233,bridgeroom:233,brief:[3,16,19,20,21,25,46,58,60,85,86,95,96,101,110,124,131,139,188,234,247,311,364],briefer:[89,110],briefli:[16,90,110,364],bright:[81,114,126,183,321],brightbg_sub:321,brighten:114,brighter:114,brilliant:131,bring:[23,49,96,100,103,121,123,133,136,215,221,224,231,309],broad:39,broadcast:[164,276],broader:[39,206,247],broken:[38,61,108,114,205,226,227,336],brought:102,brows:[3,9,25,39,55,58,62,69,85,90,91,103,106,123,131,136,137,138,362],browser:[3,8,9,16,38,55,63,64,67,69,70,75,77,90,95,96,101,103,133,134,135,136,137,138,295,296,362],brutal:234,bsd:78,btest:114,btn:[17,70],bucket:209,buf:326,buffer:[22,33,50,137,168,269,296,326],bug:[10,13,26,37,42,54,57,60,61,70,78,95,96,110,123,127,131,227,318,364],buggi:[11,328],bui:[85,138,179],build:[1,6,7,9,10,11,13,14,15,27,31,36,43,47,51,55,57,60,63,64,68,69,75,77,79,80,81,86,87,89,96,100,105,106,108,109,112,113,119,120,122,123,125,129,130,136,137,139,140,141,142,149,151,155,157,158,165,166,175,180,187,193,205,206,212,231,234,242,247,251,252,267,278,279,322,330,357,363,364],build_match:151,builder:[2,4,14,19,22,25,56,58,60,61,68,80,85,108,109,112,114,123,124,139,157,159,164,165,169,180,182,187,188,203,206,212,233,234,235,242,247,250,298,318,322,363,364],buildier:252,building_menu:[141,142,178,364],buildingmenu:[22,180],buildingmenucmdset:180,buildprotocol:[264,277,278,279],buildshop:85,built:[13,16,20,27,38,40,51,54,55,57,58,61,63,64,73,75,77,95,96,100,103,121,122,123,135,138,139,148,177,203,205,239,246,256,261,316,318,319,322,328,335],builtin:280,bulk:[96,103],bullet:[38,61],bulletin:[61,79,80],bulletpoint:38,bunch:[15,27,58,108,113],burden:82,buri:[108,122],burn:[61,73,90,232],busi:[64,70,90,179,364],butch:96,butt:138,butter:16,button:[9,13,14,31,33,80,83,87,88,106,131,133,134,135,137,138,159,224,226,227,232,299,329,364],button_expos:232,buy_ware_result:85,byngyri:205,bypass:[4,10,19,20,58,80,116,126,144,159,175,212,241,242,245,318,324,341,349],bypass_superus:80,bytecod:321,bytestr:[276,344],bytestream:344,c_creates_button:299,c_creates_obj:299,c_dig:299,c_examin:299,c_help:299,c_idl:299,c_login:299,c_login_nodig:299,c_logout:299,c_look:299,c_move:299,c_moves_:299,c_moves_n:299,c_social:299,cabl:82,cach:[6,8,11,12,28,33,39,86,119,125,127,130,137,144,154,169,174,175,187,231,232,242,246,247,271,315,316,318,319,320,332,334,344],cache_inst:334,cache_lock_bypass:242,cache_s:[310,334],cached_properti:344,cactu:220,cake:31,calcul:[10,25,27,39,73,116,119,123,139,153,184,187,198,205,217,218,220,221,252,331,334,344,362],calculated_node_to_go_to:51,calculu:56,calendar:[184,198,331,364],call:[0,2,3,4,5,6,10,11,13,14,16,20,21,22,23,25,26,27,28,29,30,31,36,38,39,40,41,42,46,47,48,49,50,51,55,56,57,58,59,60,61,62,63,64,65,69,71,72,73,74,75,80,81,83,84,85,86,88,89,90,91,93,95,96,100,102,104,105,107,108,109,110,111,114,115,116,117,118,119,120,121,122,123,125,126,127,128,131,132,133,134,135,137,138,144,146,150,151,152,153,154,156,159,164,167,168,169,170,171,174,175,179,180,182,184,185,186,187,188,189,192,193,194,195,196,198,203,204,205,206,212,214,215,217,218,219,220,221,223,224,226,227,230,231,232,233,234,235,241,242,246,247,250,251,252,255,257,258,259,260,261,264,267,269,271,272,276,277,278,279,280,281,282,283,285,286,287,288,289,290,291,292,294,295,296,298,299,300,305,306,307,308,309,312,315,316,318,319,321,322,323,324,326,328,329,330,331,334,336,337,339,340,341,344,357,362,364],call_async:10,call_command:127,call_ev:[0,194],call_inputfunc:[83,306,308],call_task:260,callabl:[49,50,84,109,115,123,180,188,195,215,219,247,250,251,252,257,261,265,267,269,277,323,326,328,329,337,339,340,344,364],callables_from_modul:344,callbac:22,callback1:328,callback:[4,10,22,27,29,33,50,51,62,74,84,115,138,146,180,184,188,192,193,194,195,196,198,210,215,230,247,257,259,260,261,265,267,269,272,276,277,278,280,294,295,298,309,328,331,337,342,344,364],callback_nam:[192,195],callbackhandl:[141,142,178,191,364],called_bi:150,calledbi:344,caller:[5,10,11,13,21,22,25,27,28,29,30,33,38,41,42,44,49,50,56,58,59,60,71,73,80,81,82,83,85,86,87,88,89,91,111,115,116,119,121,123,125,129,137,146,150,151,152,154,156,159,160,164,165,166,167,168,169,170,174,180,188,193,199,203,206,214,215,230,232,233,234,235,242,247,249,251,322,326,328,329,336,338,344,364],callerdepth:344,callertyp:150,callinthread:312,calllback:194,callsign:[51,272],calm:111,came:[9,21,25,55,79,111,132,138,231,235,247],camp:111,campfir:111,campsit:111,can:[0,1,2,3,4,5,6,9,10,12,13,14,15,17,19,20,21,23,24,25,26,27,28,29,30,31,33,34,35,36,37,38,39,40,41,42,44,46,48,49,50,51,54,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,130,131,133,134,135,136,137,138,139,140,143,144,146,148,151,152,153,154,156,157,159,164,165,166,167,168,169,170,171,174,175,176,177,179,180,182,183,184,185,187,188,189,190,194,195,198,199,203,204,205,206,209,212,215,217,218,219,220,221,224,226,227,231,232,233,234,235,239,241,242,245,246,247,250,251,252,253,255,256,257,258,259,261,267,278,282,285,287,290,291,295,296,298,299,305,306,307,308,309,312,313,314,316,317,318,319,321,322,323,324,326,327,328,329,330,336,338,339,340,341,342,344,345,357,362,363,364],can_:194,cancel:[27,29,74,194,217,218,219,220,221,247,260],candid:[22,33,119,133,151,203,206,245,247,341],candl:153,cannot:[5,9,10,11,13,14,19,21,22,25,27,28,29,31,33,39,44,46,50,51,56,60,61,63,69,70,73,76,80,85,90,104,109,112,114,122,123,127,128,133,139,144,146,153,156,159,175,180,187,188,192,195,212,215,221,227,231,232,238,241,242,245,247,251,261,316,323,325,327,330,334,344],cantanker:338,cantclear:188,cantillon:79,cantmov:25,canva:49,capabl:[6,36,49,58,64,80,83,88,105,156,214,272,294,357],capcha:364,cape:57,capfirst:69,capit:[9,12,25,29,64,88,95,123,159,189,204,205,321],captcha:133,caption:38,captur:[25,91,138,337,362],car:[87,121],card:[103,364],cardin:[44,49,58,159],care:[0,4,10,12,23,33,38,44,49,51,56,57,62,64,78,86,91,110,116,121,126,132,144,152,175,187,203,206,230,231,233,241,247,250,318,322,326,328,329,330,344,364],carefulli:[55,93,105,111,133],carri:[20,31,61,80,82,85,116,117,177,182,218,231,241,306,317],cascad:334,caseinsensitivemodelbackend:349,cast:[28,109,112,215,220],caster:[28,220],castl:[13,111,122,187,233],cat:[67,75],catchi:4,categor:112,categori:[1,11,33,36,38,39,43,51,68,69,86,109,112,119,127,140,155,159,166,167,168,199,215,238,239,241,251,252,316,317,319,324,335,338,341,344,362],categoris:56,category2:335,category2_id:335,category_id:335,category_index:215,cater:29,caught:[42,51,97,176],caus:[11,12,29,30,31,42,60,61,64,77,80,90,96,114,116,117,119,123,127,137,140,153,186,224,226,235,247,298,330,344],caution:[62,137,328],cave:46,caveat:[5,10,364],caveman:56,cblue:131,cboot:[12,43,164],cc1:63,cccacccc:327,ccccc2ccccc:58,cccccccc:327,ccccccccccc:58,cccccccccccccccccbccccccccccccccccc:327,ccccccccccccccccccccccccccccccccccc:327,ccreat:[41,43,58,65,72,98,164],cdesc:[41,43,164],cdestroi:[43,164],cdmset:31,cdn:103,ceas:[77,159],cel:327,celebr:61,cell:[58,69,111,188,327,330],celltext:327,cemit:[43,164],censu:317,center:[4,16,39,49,109,111,114,190,250,321,330,344],center_justifi:[109,250],centos7:67,centr:111,central:[26,55,61,64,74,100,111,123,124,127,132,138,139,144,153,177,247,252,276,324,328,334,363,364],centre_east:111,centre_north:111,centre_south:111,centre_west:111,centric:[9,80,105,123,206],cert:[8,288,292],certain:[13,14,16,19,25,29,31,33,37,38,48,64,75,80,88,90,97,102,105,107,108,114,115,121,138,159,176,179,205,209,227,232,235,241,245,259,267,273,290,294,309,315,316,317,326,330,341,344,357],certainli:[15,44,138],certbot:[67,90,103],certfil:[288,292],certif:[8,90,288,292,364],certonli:67,cet:337,cfg:67,cflag:75,cgi:90,cha:[51,58],chain:[0,10,29,46,51,109,119,194,195,299,328],chain_1:0,chain_2:0,chain_:0,chain_open_door:0,chain_x:0,chainedprotocol:287,chainsol:[119,364],chair:[13,61,89,91,112,125],challeng:[73,79],chan:164,chanalia:[43,164],chanc:[21,22,28,31,54,61,66,73,115,116,122,131,152,217,218,219,220,221,224,232,233,299],chance_of_act:299,chance_of_login:299,chandler:116,chang:[2,3,4,7,8,9,11,12,13,14,15,16,19,20,21,22,23,26,29,30,31,33,34,35,36,37,39,41,42,45,47,49,50,51,53,54,57,61,62,63,64,66,67,68,71,73,74,75,77,78,80,81,83,84,85,86,87,89,90,91,95,96,100,102,104,105,107,109,110,111,112,114,115,116,118,121,123,125,126,127,132,133,134,135,137,138,139,144,145,153,154,156,157,159,164,165,170,173,175,179,180,182,186,187,189,190,192,195,202,205,206,212,213,215,217,218,219,220,221,231,232,233,234,235,239,244,245,247,252,254,256,257,259,260,261,267,272,283,298,305,306,313,315,318,322,325,326,329,330,337,338,339,340,362,364],change_name_color:215,changeabl:76,changelog:96,changepag:134,changepassword:12,chanlist:[43,164],channam:41,channel:[2,6,7,11,12,19,31,33,43,45,53,55,65,70,71,72,79,80,82,86,87,90,98,107,112,119,123,124,125,138,139,144,146,150,152,153,159,164,168,172,173,174,175,176,177,195,271,278,279,286,299,306,308,316,324,337,341,360,362,364],channel_:34,channel_ban:[41,164],channel_color:25,channel_command_class:[34,41],channel_connectinfo:306,channel_detail:362,channel_handl:[53,141,174],channel_list:362,channel_prefix:[25,175],channel_search:176,channel_set:177,channel_typeclass:360,channeladmin:173,channelam:174,channelattributeinlin:173,channelcmdset:31,channelcommand:[34,41,174],channelconnect:177,channelcr:[43,164],channelcreateview:175,channeldb:[41,53,125,141,173,175,177,314],channeldb_db_attribut:173,channeldb_db_tag:173,channeldb_set:[316,319],channeldbmanag:[176,177],channeldeleteview:175,channeldesc:[41,174],channeldetailtest:360,channeldetailview:[175,362],channelhandl:[34,41,141,142,150,172,175,364],channelkei:[41,174,176],channellist:[43,164],channellisttest:360,channellistview:362,channelmanag:[175,176],channelmixin:362,channelnam:[34,41,72,146,174,278],channeltaginlin:173,channelupdateview:175,char1:[73,127,165,360],char2:[73,127,165,360],char_health:233,char_nam:133,charac:84,charact:[0,2,5,9,11,14,15,17,19,20,21,22,23,27,28,29,30,31,33,34,36,39,40,41,42,45,47,49,50,51,53,55,56,57,62,68,69,71,74,76,77,80,81,83,85,86,87,88,91,95,97,102,105,111,113,114,116,117,118,119,120,121,122,124,125,127,129,135,136,138,139,141,143,144,147,151,152,154,156,159,160,161,165,166,167,171,174,175,180,181,182,187,188,189,190,192,194,195,199,202,204,205,206,209,214,215,217,218,219,220,221,223,231,232,233,235,239,242,247,259,272,293,306,311,316,318,321,322,327,328,330,336,342,344,345,357,360,362,364],character1:73,character2:73,character_cmdset:187,character_form:362,character_id:247,character_list:362,character_manage_list:362,character_typeclass:[127,144,342,360],charactercmdset:[5,21,22,25,30,31,41,43,44,57,58,60,62,81,123,161,180,182,187,199,202,212,217,218,219,220,221,233],charactercreateview:[360,362],characterdeleteview:[360,362],characterdetailview:362,characterform:[357,362],characterlistview:[360,362],charactermanageview:[360,362],charactermixin:362,characternam:114,characterpuppetview:[360,362],charactersheet:51,characterupdateform:[357,362],characterupdateview:[360,362],charapp:133,charat:188,charcreat:[0,43,46,69,156,181],chardata:58,chardelet:[43,156],chardeleteview:[239,318],chardetailview:[239,318],charfield:[86,133,145,237,244,315,340,357],charg:90,chargen:[133,139,141,142,175,178,239,318,364],chargencmdset:123,chargenroom:123,chargenview:[239,318],charnam:[58,156],charpuppetview:318,charset:344,charsheet:58,charsheetform:58,charupdateview:[239,318],chase:122,chat:[1,2,9,26,34,37,48,55,58,60,63,65,70,72,79,80,98,123,131,139,296,337,364],chatroom:57,chatzilla:72,cheap:131,cheaper:[61,115],cheapest:90,cheapli:233,cheat:[23,38,73,364],cheatsheet:48,check:[0,4,5,12,13,14,19,22,25,26,27,28,29,31,33,36,37,38,39,40,41,42,44,46,49,51,54,56,58,60,63,65,67,68,69,70,71,73,77,81,82,85,86,87,89,90,91,95,97,98,100,102,103,106,109,110,111,112,114,115,116,117,118,119,121,123,125,127,128,130,131,133,136,138,139,144,145,147,150,151,152,153,154,156,158,159,164,165,166,167,169,171,175,177,179,181,182,186,187,188,195,199,217,218,219,220,221,223,224,227,231,233,234,235,241,242,246,247,251,252,256,258,259,260,266,267,271,276,282,287,291,306,308,310,311,312,315,316,318,319,321,322,324,338,339,344,345,362,364],check_attr:159,check_circular:296,check_databas:267,check_db:267,check_defeat:73,check_end_turn:116,check_error:266,check_evennia_depend:344,check_from_attr:159,check_grid:49,check_has_attr:159,check_light_st:233,check_lockstr:[4,80,242],check_main_evennia_depend:267,check_obj:159,check_permiss:251,check_permstr:[144,318],check_show_help:166,check_to_attr:159,check_warn:266,checkbox:133,checker:[15,49,241,287,345,364],checkout:[9,100,131],checkoutdir:36,checkpoint:364,chest:[80,91],child:[6,33,51,64,80,96,116,146,148,154,159,170,233,246,252,256,312,335],childhood:51,children:[21,33,64,96,112,117,119,125,148,246,247,256,267,317,335],chillout:159,chime:27,chines:[25,79,113],chip:58,chmod:36,choci:180,chocol:60,choic:[4,15,23,33,51,55,60,78,90,91,95,105,107,109,113,116,119,124,127,129,132,144,156,159,179,180,188,217,234,250,265,326,364],choice1:129,choice2:129,choice3:129,choos:[7,9,10,13,38,49,51,57,62,64,67,72,73,85,101,106,116,120,123,126,133,135,138,139,140,214,215,217,218,219,220,221,224,231,280,328,343,364],chop:[33,232],chore:68,chose:[54,58,86,103,133,215],chosen:[22,51,88,106,116,132,138,188,190,328],chown:100,chractercmdset:233,christin:96,chrome:24,chronicl:188,chroot:67,chug:33,chunk:[13,69,111,269,322,336],church:27,church_clock:27,cid:299,cillum:52,circl:39,circuit:137,circular:[269,323],circumst:[46,51,57,85,119,152,220,357],circumv:157,claim:83,clang:75,clank:0,clarif:[1,48],clarifi:25,clariti:[75,86,91,123],clash:[23,31,90,159,174,318],class_from_modul:344,classic:[3,13,79,105,112,115,116],classmethod:[39,144,175,239,247,259,318,334,351],classnam:11,classobj:318,claus:[78,118],clean:[1,4,17,25,28,48,51,76,110,111,114,116,122,131,145,152,154,159,175,179,206,217,218,219,220,221,227,232,233,235,247,255,256,267,271,285,295,308,315,318,321,326,328,334,340,343,344,357,364],clean_attr_valu:315,clean_attribut:[125,144,318],clean_cmdset:[125,318],clean_senddata:308,clean_stale_task:260,clean_str:321,clean_usernam:145,cleaned_data:133,cleaner:[91,123],cleanli:[64,102,105,110,150,154,164,174,188,269,278,284,295,308,326],cleanup:[1,11,22,33,40,45,50,51,102,127,145,169,179,230,233,328],clear:[1,4,11,12,15,22,29,33,37,38,40,48,50,59,61,64,69,70,73,81,104,110,111,112,113,115,125,128,129,131,132,137,138,153,156,157,159,165,171,174,188,204,206,227,233,242,245,246,247,257,260,261,269,306,316,318,319,328,334],clear_all_sessid:245,clear_client_list:303,clear_cont:[89,247],clear_exit:[89,247],clearal:[129,165],clearli:[12,37,48,128,227,334],cleartext:[210,324],clemesha:312,clever:[10,31,51,95,242],cleverli:105,click:[36,38,69,90,101,106,114,128,131,133,135,137,138,328],clickabl:[18,38,364],client:[3,7,8,9,12,22,23,25,30,33,36,38,40,45,50,52,54,55,60,63,64,65,67,72,74,75,79,81,84,91,95,96,100,101,103,104,105,107,108,111,113,114,116,117,126,128,136,138,139,141,142,144,146,154,156,164,169,171,210,262,264,268,270,272,276,277,278,279,280,281,282,283,285,287,289,290,291,292,294,295,296,298,299,305,306,307,308,325,326,328,343,344,362,364],client_address:40,client_default_height:52,client_disconnect:296,client_encod:23,client_gui:272,client_height:154,client_opt:[88,272,291,364],client_secret:65,client_width:[33,154],clientconnectionfail:[264,278,279],clientconnectionlost:[264,278,279],clientfactori:298,clienthelp:137,clientraw:169,clientsess:[295,296],cliff:[20,159],climat:112,climb:[33,55,77,93,159,232],climbabl:232,clipboard:[1,48],clist:[43,164],clock:[12,27,33,43,73,164],clone:[38,47,63,64,76,96,128,130,364],close:[0,14,22,25,38,39,40,41,46,48,50,51,64,69,76,90,96,100,103,105,106,110,125,131,133,137,169,171,179,180,186,190,212,221,224,226,227,230,269,277,278,285,287,295,296,308,316,322,328,336],close_lid:226,close_menu:[230,328],closedlidst:227,closelidev:227,closer:[205,221],closest:[39,114],cloth:[141,142,178,322,364],clothedcharact:182,clothedcharactercmdset:182,clothes_list:182,clothing_typ:182,clothing_type_count:182,clothing_type_ord:182,cloud9:364,cloud:[90,100,102,103,132],cloudi:102,clr:[114,251,336],cls:[39,144],clue:232,clunki:[131,221],clutter:[38,153],cma:131,cmd:[12,14,22,25,28,29,31,33,41,44,58,60,62,71,80,82,85,88,95,121,123,136,152,154,156,157,158,159,164,165,166,167,168,169,170,171,174,179,180,181,182,185,186,187,188,189,193,199,202,203,206,212,213,214,215,217,218,219,220,221,224,231,232,233,234,236,247,291,295,296,322,326,328,329],cmd_abil_result:127,cmd_arg:91,cmd_channel:[33,150],cmd_ignore_prefix:151,cmd_kei:91,cmd_last:105,cmd_last_vis:105,cmd_loginstart:33,cmd_multimatch:[33,150],cmd_na_m:88,cmd_name:88,cmd_noinput:[33,150,328],cmd_nomatch:[33,150,233,328],cmd_noperm:33,cmd_on_exit:[51,188,215,230,249,328],cmd_total:105,cmdabil:[60,127],cmdabout:169,cmdaccept:179,cmdaccess:165,cmdaccount:169,cmdaddcom:164,cmdallcom:164,cmdapproach:221,cmdarmpuzzl:203,cmdasync:10,cmdattack:[29,73,116,123,217,218,219,220,221,232],cmdban:157,cmdbatchcod:158,cmdbatchcommand:158,cmdbigsw:29,cmdblindhelp:224,cmdblindlook:224,cmdblock:25,cmdboot:157,cmdbridgehelp:233,cmdbui:85,cmdbuildshop:85,cmdcallback:193,cmdcast:220,cmdcboot:164,cmdcdesc:164,cmdcdestroi:164,cmdcemit:164,cmdchannel:164,cmdchannelcr:164,cmdcharactercr:181,cmdcharcreat:156,cmdchardelet:156,cmdclimb:232,cmdclock:164,cmdcloselid:224,cmdcolortest:156,cmdcombathelp:[217,218,219,220,221],cmdconfigcolor:81,cmdconfirm:33,cmdconnect:41,cmdcopi:159,cmdcover:182,cmdcpattr:159,cmdcraftarmour:29,cmdcreat:159,cmdcreatenpc:123,cmdcreatepuzzlerecip:203,cmdcwho:164,cmddarkhelp:233,cmddarknomatch:233,cmddeclin:179,cmddefend:116,cmddelcom:164,cmddesc:[159,187],cmddestroi:159,cmddiagnos:30,cmddice:[58,185],cmddig:159,cmddisconnect:41,cmddisengag:[116,217,218,219,220,221],cmddoff:218,cmddon:218,cmddrop:[165,182],cmdeast:233,cmdecho:[5,29,33,38],cmdedit:180,cmdeditnpc:123,cmdeditorbas:326,cmdeditorgroup:326,cmdeditpuzzl:203,cmdemit:157,cmdemot:206,cmdentertrain:121,cmdevalu:179,cmdevenniaintro:233,cmdevmenunod:328,cmdexamin:159,cmdexiterror:44,cmdexiterroreast:44,cmdexiterrornorth:44,cmdexiterrorsouth:44,cmdexiterrorwest:44,cmdextendedroomdesc:187,cmdextendedroomdetail:187,cmdextendedroomgametim:187,cmdextendedroomlook:187,cmdfeint:116,cmdfight:[217,218,219,220,221],cmdfind:159,cmdfinish:179,cmdforc:157,cmdget:[25,165],cmdgetinput:328,cmdgetweapon:232,cmdgive:[165,182],cmdgmsheet:58,cmdgrapevine2chan:164,cmdhandler:[31,33,83,89,141,142,144,149,151,152,153,154,156,167,168,169,170,171,174,187,203,233,246,247,256,344,364],cmdhelp:[116,166,217,218,219,220,221],cmdhit:116,cmdhome:165,cmdic:156,cmdid:272,cmdinsid:121,cmdinterrupt:170,cmdinventori:[82,165,182],cmdirc2chan:164,cmdircstatu:164,cmdlaunch:21,cmdlearnspel:220,cmdleavetrain:121,cmdlen:[151,168],cmdlight:232,cmdline:267,cmdlineinput:326,cmdlink:159,cmdlistarmedpuzzl:203,cmdlistcmdset:159,cmdlisthangout:119,cmdlistpuzzlerecip:203,cmdlock:159,cmdlook:[30,127,165,181,187,233],cmdlookbridg:233,cmdlookdark:233,cmdmail:199,cmdmailcharact:199,cmdmakegm:58,cmdmask:206,cmdmobonoff:231,cmdmore:329,cmdmorelook:329,cmdmultidesc:[57,202],cmdmvattr:159,cmdmycmd:[56,68],cmdname2:151,cmdname3:151,cmdname:[40,59,74,83,88,123,137,150,151,154,159,167,168,170,272,290,291,295,296,308],cmdnamecolor:215,cmdnewpassword:157,cmdnick:165,cmdnoinput:180,cmdnomatch:180,cmdnpc:123,cmdnudg:224,cmdobj:[150,151,168,170],cmdobj_kei:150,cmdobject:[150,151,169],cmdoffer:179,cmdooc:156,cmdooccharactercr:181,cmdooclook:[156,181],cmdopen:[159,212],cmdopenclosedoor:212,cmdopenlid:224,cmdoption:156,cmdpage:164,cmdparri:116,cmdparser:[104,141,142,149,364],cmdpass:[217,218,219,220,221],cmdpassword:156,cmdperm:157,cmdplant:234,cmdpoke:119,cmdpose:[116,165,206],cmdpressbutton:232,cmdpush:224,cmdpy:169,cmdquell:156,cmdquit:156,cmdread:232,cmdrecog:206,cmdreload:169,cmdremov:182,cmdreset:169,cmdrest:[217,218,219,220,221],cmdroll:91,cmdrss2chan:164,cmdsai:[116,165,206],cmdsaveyesno:326,cmdscript:[159,169],cmdsdesc:206,cmdser:328,cmdserverload:169,cmdservic:169,cmdsession:156,cmdset:[2,7,14,21,22,25,31,33,34,40,41,42,43,44,47,51,53,57,60,62,68,69,81,85,89,96,97,105,116,121,123,141,142,144,149,150,151,153,154,159,160,161,162,163,166,167,168,169,170,174,179,180,181,182,185,187,189,193,199,203,206,213,214,217,218,219,220,221,224,227,230,231,232,233,234,241,246,247,256,298,305,306,318,326,328,329,364],cmdset_account:[2,141,142,149,155,181,364],cmdset_charact:[5,96,141,142,149,155,182,217,218,219,220,221,364],cmdset_mergetyp:[51,188,230,249,328],cmdset_prior:[51,188,230,249,328],cmdset_red_button:[141,142,178,222,364],cmdset_sess:[105,141,142,149,155,364],cmdset_stack:153,cmdset_storag:[148,246,306],cmdset_trad:179,cmdset_unloggedin:[33,141,142,149,155,186,364],cmdsetattribut:159,cmdsetclimb:232,cmdsetcrumblingwal:232,cmdsetdesc:165,cmdsetevenniaintro:233,cmdsethandl:[105,141,142,149,364],cmdsethelp:166,cmdsethom:159,cmdsetkei:31,cmdsetkeystr:152,cmdsetlight:232,cmdsetmor:329,cmdsetobj:[152,153,160,161,162,163,179,180,181,182,185,187,203,206,214,217,218,219,220,221,224,230,231,232,233,326,328,329],cmdsetobjalia:159,cmdsetpow:123,cmdsetread:232,cmdsetspe:213,cmdsettestattr:50,cmdsettrad:179,cmdsettrain:121,cmdsetweapon:232,cmdsetweaponrack:232,cmdsheet:58,cmdshiftroot:232,cmdshoot:[21,221],cmdshutdown:169,cmdsmashglass:224,cmdsmile:33,cmdspawn:159,cmdspellfirestorm:28,cmdstatu:[179,220,221],cmdstop:213,cmdstring:[33,58,150,154,167,168,170],cmdstyle:156,cmdtag:159,cmdtalk:214,cmdteleport:159,cmdtest:[29,42,91],cmdtestid:33,cmdtestinput:51,cmdtestmenu:[51,188,328],cmdticker:169,cmdtime:[62,169],cmdtrade:179,cmdtradebas:179,cmdtradehelp:179,cmdtunnel:159,cmdtutori:233,cmdtutorialgiveup:233,cmdtutoriallook:233,cmdtutorialsetdetail:233,cmdtweet:71,cmdtypeclass:159,cmdunban:157,cmdunconnectedconnect:[171,186],cmdunconnectedcr:[171,186],cmdunconnectedencod:171,cmdunconnectedhelp:[171,186],cmdunconnectedinfo:171,cmdunconnectedlook:[171,186],cmdunconnectedquit:[171,186],cmdunconnectedscreenread:171,cmduncov:182,cmdunlink:159,cmdunwield:218,cmduse:219,cmdusepuzzlepart:203,cmdwait:33,cmdwall:157,cmdwear:182,cmdwerewolf:25,cmdwest:233,cmdwhisper:165,cmdwho:156,cmdwield:218,cmdwipe:159,cmdwithdraw:221,cmset:153,cmsg:[43,164],cmud:24,cnf:[23,36],cnt:119,coast:[111,122],coastal:111,cockpit:21,code:[0,1,2,4,5,6,7,9,10,11,12,14,15,16,18,19,20,29,31,33,34,36,37,39,40,45,46,47,48,49,51,53,55,56,57,58,62,63,64,68,69,70,76,77,79,80,83,84,86,88,89,91,93,95,96,97,98,100,102,103,104,105,106,109,110,111,112,114,115,116,117,118,119,121,122,123,125,126,127,129,132,134,135,136,139,141,142,144,149,150,153,156,158,159,164,169,172,178,179,180,184,185,190,192,195,204,219,233,234,242,252,256,278,279,295,306,309,318,320,321,326,328,330,341,342,343,344,362,363,364],code_exec:322,codebas:[38,55,56,127,129,131,139,140,170],codeblock:[38,322],codec:321,codefunc:326,coder:[22,26,56,61,79,96,124,150,247,363],codestyl:37,coding_styl:[37,38,95],coerc:339,coexist:126,coin:[61,70,179],col:[3,16,329,330],cold:[12,110,169,252,257,261,305],cole:344,collabor:[4,61,64,90,131,166,364],collat:[83,251],collect:[11,26,31,136,150,152,203,259,344],collector:136,collectstat:[136,137,267,271],collid:[31,54,90,328],collis:[31,131],colon:[27,41,60,80,95,242],color:[16,18,20,33,38,43,49,51,53,58,59,63,69,74,79,95,109,111,114,124,129,137,139,154,156,183,190,206,215,230,234,251,272,279,287,290,295,296,321,330,336,338,343,345,364],color_ansi_bright_bg_extra_map:183,color_ansi_bright_bgs_extra_map:183,color_ansi_extra_map:183,color_markup:[141,142,178,364],color_no_default:183,color_typ:321,color_xterm256_extra_bg:183,color_xterm256_extra_fg:183,color_xterm256_extra_gbg:183,color_xterm256_extra_gfg:183,colorablecharact:81,colorback:343,colorcod:343,colour:[27,55,139,159,294,321,330,364],column:[16,38,46,49,58,64,69,86,111,137,154,156,235,329,330,344],com:[8,9,22,23,37,38,45,54,55,63,67,70,75,79,90,95,98,100,103,104,108,128,130,131,133,135,138,141,164,180,186,279,282,291,295,312,330,343,344,357],comb:1,combat:[11,14,25,28,31,46,55,63,64,73,79,102,108,109,111,117,122,124,125,131,139,153,217,218,219,220,221,231,256,364],combat_:[217,218,219,220,221],combat_cleanup:[217,218,219,220,221],combat_cmdset:116,combat_handl:116,combat_handler_:116,combat_movesleft:[217,218,219,220],combat_scor:123,combat_status_messag:221,combatcmdset:116,combathandl:116,combatscor:123,combatt:11,combin:[8,11,12,20,27,28,30,31,33,34,41,55,57,58,84,90,109,112,114,115,118,119,121,127,150,151,152,159,175,202,203,205,226,242,251,261,267,317,319,324,338,344],combo:105,come:[0,2,3,4,6,10,11,15,16,20,21,23,25,27,29,33,34,40,46,49,51,52,55,57,58,61,62,64,69,73,80,83,85,88,91,93,100,105,111,114,116,118,119,121,123,124,126,129,131,133,134,135,137,144,152,187,204,217,218,219,220,221,251,252,285,290,295,296,298,304,321,329,362],comet:[40,55,137,296],comfort:[15,55,69,91,131,364],comlist:[43,164],comm:[33,34,41,43,47,53,64,68,71,141,142,149,155,324,362,364],comma:[20,46,86,95,114,134,159,167,168,198,199,242,247,336],command:[0,2,4,6,8,9,10,11,12,13,15,18,19,20,21,23,24,26,27,34,36,38,40,46,47,48,49,50,51,52,55,56,57,59,61,63,64,65,66,69,72,74,75,76,77,79,80,82,83,86,87,89,90,92,93,95,96,98,102,103,104,105,106,108,109,110,111,112,113,114,117,118,119,120,122,124,125,126,128,129,130,131,136,137,138,139,140,141,142,144,146,174,175,178,179,180,181,182,185,186,187,188,189,191,194,196,199,202,203,206,210,212,213,214,215,217,218,219,220,221,224,226,227,230,231,232,233,234,235,236,239,241,242,247,251,252,256,264,267,272,276,277,285,287,290,291,295,296,298,299,305,306,318,320,321,324,326,328,329,338,341,344,362,364],command_default_arg_regex:33,command_default_class:25,command_pars:151,commandhandl:[74,153,168],commandmeta:154,commandnam:[33,74,83,234,267,276,306,308],commandset:[5,80,89,153,181],commandtest:[127,170,196],comment:[8,9,13,14,24,25,37,41,48,51,60,90,96,118,125,138,322,328,364],commerc:79,commerci:[90,106],commerror:176,commit:[15,23,25,36,37,38,64,66,98,100,108,128,130,209,315,364],commmand:[212,217,218,219,220,221],common:[1,6,10,12,15,16,20,26,27,30,33,38,40,41,51,53,59,60,61,62,63,64,68,69,73,74,79,80,83,88,90,91,97,105,107,109,112,113,115,116,119,123,124,125,131,133,139,152,159,171,179,205,206,213,242,245,256,272,295,299,317,327,329,339,341,344,350,362],commonli:[23,63,64,83,86,87,96,104,105,107,115,119,128,247],commonmark:38,commun:[8,22,23,33,40,41,45,47,53,55,57,60,64,70,72,79,83,88,90,91,92,103,106,113,114,137,139,161,171,172,174,175,176,177,199,230,246,264,276,277,287,288,290,291,292,293,306,308,324,325,340,364],compact:[85,134],compani:[64,88],compar:[4,9,13,15,27,28,29,31,41,44,58,73,83,85,91,97,116,119,123,127,131,136,170,203,205,217,218,219,220,221,241,242,252,321,344],comparison:[13,93,241,252,328],compartment:58,compass:20,compat:[14,21,51,159,330,337],compet:[15,88],compil:[9,33,38,47,56,63,75,76,90,95,108,159,165,166,171,174,182,206,321,326,343],compilemessag:76,complain:[42,60,86,91,110,128],complement:[26,107],complementari:113,complet:[2,10,11,13,14,15,22,23,25,27,31,33,36,37,44,49,50,58,59,61,62,64,67,70,77,81,85,88,89,90,95,96,102,104,105,107,109,110,111,122,123,127,128,131,139,144,152,153,154,167,169,174,183,187,188,190,195,218,233,247,260,267,269,277,278,295,322,327,328,329,336,341,344,357,364],complete_task:195,completli:227,complex:[11,14,15,20,31,33,38,51,59,61,62,64,73,76,77,86,93,96,100,104,108,111,115,116,123,127,138,153,196,204,214,252,299,364],complianc:[24,187],compliant:[39,291],complic:[0,10,22,29,41,49,69,90,91,111,133,134,171,186,188,215],compon:[29,33,38,40,49,58,90,93,96,102,110,114,116,124,127,135,137,138,139,159,169,175,176,177,184,203,205,245,252,253,256,259,267,296,324,327,341,344,364],componentid:137,componentnam:137,componentst:[137,138],compos:[100,188],composit:[293,317],comprehens:[34,55,63,80,93,96,103,124,125,127],compress:[74,272,276,280,340],compress_object:340,compris:144,compromis:[103,209],comput:[10,12,37,49,56,60,63,64,72,73,100,113,115,124,131,132,157,169,206,344,345,364],computation:115,comsystem:[164,177],con:[43,58,79,171,186],concaten:[67,321,336],concept:[11,37,38,39,40,46,57,61,69,76,77,92,96,115,124,131,139,181,202,364],conceptu:[49,51],concern:[25,44,63,76,88,95,96,152,204,239],conch:[287,290,298],conclud:[96,179,328,364],conclus:364,concurr:23,conda:9,conder:322,condit:[8,46,49,55,61,73,85,91,93,96,123,124,150,185,206,219,242,247,259,266,267,312,344,364],condition:25,condition_result:185,condition_tickdown:219,conditional_flush:334,conduct:136,conductor:121,conect:308,conf:[4,8,9,23,25,35,36,38,40,41,47,54,62,65,67,69,74,76,80,81,86,90,93,102,103,109,114,120,121,127,130,131,133,134,135,139,144,183,267,273,274,313,322,364],confer:[79,344],confid:[37,39,42],config:[2,4,9,36,40,59,63,90,98,106,130,131,138,139,263,267,269,273,274,285,364],config_1:2,config_2:2,config_3:2,config_color:81,config_fil:67,configcmd:81,configdict:[287,308],configur:[0,2,7,25,36,38,45,47,54,59,62,63,64,69,90,100,103,114,120,124,127,136,138,139,144,148,151,156,209,210,234,269,274,285,308,312,313,317,357,364],configut:106,configvalu:59,confirm:[8,33,63,103,137,159,186,203,291,294,362],conflict:[41,42,126],confus:[10,22,26,31,44,58,59,60,64,77,80,87,90,91,93,97,114,119,126,131,136,137,140,186,362],congratul:364,conid:286,conjur:220,conn:[43,171,186],conn_tim:105,connect:[0,2,4,7,8,9,11,12,13,17,18,23,24,25,31,33,34,40,41,43,46,47,49,55,57,60,63,64,65,66,67,69,72,74,76,77,80,83,85,88,89,91,92,93,96,98,100,101,102,103,104,105,107,110,111,114,120,123,125,126,127,136,137,139,144,146,147,148,156,157,159,164,171,175,177,186,190,192,193,195,210,213,246,247,253,262,264,267,269,276,277,278,279,280,285,286,287,290,295,296,298,299,305,306,307,308,309,312,316,318,324,340,364],connection_cr:107,connection_screen:[35,104],connection_screen_modul:186,connection_set:54,connection_tim:[144,247],connection_wizard:[141,142,262,364],connectiondon:269,connectionlost:[269,276,277,287,290,298],connectionmad:[264,276,287,290,298],connectionwizard:265,connector:[264,278,279,285,308],consecut:51,consequ:[90,153],consid:[0,4,10,12,13,14,23,26,27,31,33,37,39,40,44,46,51,55,57,61,63,64,70,74,78,80,82,85,86,90,93,96,97,102,103,105,109,112,113,114,115,119,121,125,131,133,134,135,144,152,153,188,203,205,206,221,234,247,252,256,272,287,290,317,322,323,328,329],consider:[68,86,104,111,118,241,252],consist:[2,11,17,33,38,44,46,48,51,68,80,86,92,95,96,109,110,114,116,122,123,135,137,144,151,167,176,179,203,205,236,242,250,252,291,296,306,315,316,318,324,330,344,362],consol:[9,19,23,26,38,42,60,63,64,75,83,90,93,95,96,97,100,106,114,123,137,138,169,206,267],conson:205,constant:[0,88,276,342],constantli:[96,117,233],constitu:[153,167,168],constraint:[0,23],construct:[20,29,34,36,51,64,119,133,138,252,311,321,329,357],constructor:[22,33,180,278],consum:[10,269,344],consumer_kei:[71,120],consumer_secret:[71,120],consumpt:[23,310],contact:[89,90,100],contain:[0,5,7,9,10,11,13,14,16,17,18,20,21,22,25,26,31,33,34,37,38,39,40,41,43,46,47,51,53,55,56,57,62,63,64,68,69,70,75,79,80,86,89,91,95,96,97,101,102,104,105,114,118,119,122,123,124,126,127,128,129,133,134,136,137,138,139,141,142,144,146,147,149,150,151,152,153,155,158,159,166,172,180,188,189,192,193,194,195,196,198,203,204,205,206,210,211,213,215,219,224,232,234,235,238,240,247,249,250,251,252,260,262,266,270,272,298,311,312,316,317,318,319,320,321,322,325,327,328,329,330,341,343,344,345,355,362,363,364],container:100,contempl:56,content:[3,4,13,16,17,21,27,38,39,48,49,51,56,58,69,77,79,82,85,89,90,91,93,95,96,117,119,121,123,125,131,133,134,137,138,139,154,157,159,206,245,246,247,306,315,316,319,321,322,323,326,328,330,341,346,355,364],contentof:330,contents_cach:246,contents_get:[119,247],contents_set:247,contentshandl:246,context:[46,51,55,69,91,114,119,126,133,180,195,255,288,292,350,362],contextu:112,continu:[7,10,11,21,27,29,33,37,42,45,46,49,51,55,58,60,69,71,75,85,86,90,95,96,112,114,115,116,119,123,124,127,136,139,265,276,312,316,328,337,344,364],contrari:[0,41,62,169,319],contrast:[56,90,96,113,138,291],contrib:[4,13,14,20,38,47,53,57,58,62,63,73,78,102,116,122,141,142,144,145,147,148,173,237,244,254,263,309,315,322,349,357,362,364],contribrpcharact:206,contribrpobject:206,contribrproom:206,contribut:[1,4,22,26,45,55,64,70,78,82,124,127,131,136,139,178,179,181,182,183,185,187,199,203,204,206,209,210,212,213,214,234,363,364],contributor:[78,180],control:[2,5,7,9,11,12,13,14,19,20,21,31,33,34,36,37,38,42,47,50,51,52,53,55,57,58,61,63,64,67,68,73,74,80,81,83,86,89,90,92,93,96,102,103,105,108,109,110,114,118,121,123,124,128,135,138,139,144,146,156,158,159,164,179,181,194,206,227,231,233,235,241,247,256,267,306,308,318,328,357,364],convei:[206,247],convenei:107,conveni:[8,9,10,11,21,34,36,38,40,41,51,55,57,59,69,74,80,86,89,96,98,102,106,108,109,110,125,127,133,140,144,159,169,180,199,247,310,322,323,328,329,337,340,341],convent:[0,31,86,96,107,119,126],convention:[41,154,174,247,318],convers:[51,87,121,127,138,205,214,295,296,321,344,363],convert:[11,27,39,40,49,51,59,62,64,79,81,83,85,87,88,103,109,113,114,119,126,128,147,157,184,185,188,215,241,245,251,252,255,257,276,278,287,290,291,308,312,321,325,328,329,330,331,336,340,343,344,363,364],convert_linebreak:343,convert_url:343,convinc:[51,90],cool:[3,9,21,22,26,61,79,159],cool_gui:80,cooldown:[29,116,124,139,364],coord:39,coordi:39,coordin:[49,124,137,139,221,235,364],coordx:39,coordz:39,cope:220,copi:[0,1,4,13,14,20,25,26,33,36,43,47,48,50,51,62,64,81,90,93,96,100,104,105,109,111,123,128,131,133,135,136,137,138,158,159,182,195,217,218,219,220,221,233,245,247,255,267,276,313,321,337,362,364],copy_object:[245,247],copy_script:255,copyright:[78,90],cor:138,core:[19,37,47,49,76,78,88,89,96,104,106,125,127,131,139,144,148,169,177,178,199,239,241,246,247,256,262,274,284,291,305,316,318,319,322,329,335,357,362,364],corner:[17,39,57,79,138,235,330],corner_bottom_left_char:330,corner_bottom_right_char:330,corner_char:330,corner_top_left_char:330,corner_top_right_char:330,corpu:205,correct:[10,11,14,21,23,27,30,31,33,37,38,48,50,60,80,91,113,114,121,123,126,150,156,159,176,187,203,228,242,282,285,287,293,307,321,344],correctli:[4,8,9,27,29,33,36,38,42,44,49,50,51,61,62,72,77,80,85,90,91,97,110,112,115,121,122,123,126,144,148,153,156,257,276,312,340],correl:252,correspond:[20,33,80,83,85,105,135,184,203,215,315,357],correspondingli:128,corrupt:56,cosi:111,cosin:344,cosmet:235,cost:[28,85,90,220,235],cottag:[111,114],could:[0,1,2,3,4,5,6,9,10,11,12,13,14,15,19,20,21,22,25,28,29,30,31,33,34,36,37,38,39,40,41,42,44,46,47,48,49,51,55,57,58,60,61,62,63,64,65,68,69,71,72,73,79,80,81,82,83,84,85,86,87,88,89,90,91,93,95,96,98,102,106,108,109,111,112,113,114,115,116,117,118,119,120,121,123,125,126,127,128,129,132,133,135,136,138,140,144,153,159,166,176,177,179,180,185,190,198,204,206,213,215,233,235,241,242,247,272,291,296,312,318,321,322,326,330,331,334,339,344],couldn:[11,19,39,44,64,76,91,126,134,140,204],count:[64,102,104,116,119,120,147,152,182,215,219,247,259,281,285,298,302,308,310,317,321,328,337,344],count_loggedin:285,count_queri:302,countdown:[20,29],counter:[6,22,29,69,85,105,116,128,146,233,285,298,299,306,328],counterpart:[13,114,272,308,325],countless:95,countnod:51,countri:157,coupl:[22,48,69,100,117,131,213],cours:[0,4,9,12,15,21,22,26,33,38,41,46,57,61,64,77,78,91,93,106,108,114,115,122,123,124,132,140,218,221,230],courtesi:12,cousin:[91,129],cover:[6,8,9,13,14,23,29,37,40,48,51,57,59,63,79,80,86,90,95,96,120,127,131,182,187,233,247,324,344,363],coverag:127,coveral:127,cpanel:90,cpattr:[43,159],cprofil:364,cpu:[12,90,103,169],cpython:93,crack:[61,86],craft:[29,80,111,188],crank:[115,258],crash:[26,60,61,79,103,111,271],crate:[20,87,124],crawl:103,crawler:[171,281],cre:[43,171,186],creat:[4,9,11,13,14,15,16,19,22,23,25,26,29,31,34,35,37,38,39,40,41,42,43,44,46,47,49,50,54,55,56,57,58,60,61,62,63,64,65,66,67,68,70,71,72,73,75,76,77,78,79,80,81,83,85,87,90,91,93,95,96,102,103,104,105,106,107,108,109,112,116,117,118,119,120,122,124,127,129,130,131,132,134,135,136,137,138,139,140,141,142,144,145,146,147,148,150,151,152,153,154,156,159,164,165,166,167,168,169,170,171,174,175,177,179,180,181,182,184,185,186,187,188,189,194,195,196,198,199,202,203,204,205,206,210,212,214,215,217,218,219,220,221,223,224,226,227,230,231,232,233,234,235,239,242,244,245,246,247,249,250,251,252,256,259,260,261,264,267,271,272,277,279,280,285,287,288,292,299,307,308,312,316,317,318,319,320,322,323,326,327,328,330,331,336,337,344,360,362,363,364],create_:[89,125],create_account:[27,53,107,125,141,324],create_cal:144,create_channel:[27,34,53,141,174,175,271,324],create_charact:[144,247],create_delai:260,create_exit:[159,212],create_exit_cmdset:247,create_forward_many_to_many_manag:[148,177,239,246,256,316,318,319,335],create_game_directori:267,create_grid:49,create_help_entri:[27,53,68,141,324],create_kwarg:252,create_match:151,create_messag:[27,34,53,141,324],create_object:[13,27,53,80,85,89,111,123,125,133,141,247,252,271,322,324],create_prototyp:[251,252],create_script:[27,53,56,102,116,125,141,259,322,324],create_secret_kei:267,create_settings_fil:267,create_superus:267,create_tag:317,create_wild:235,created_on:192,createnpc:364,creater:53,createview:362,creation:[11,14,20,21,38,47,51,58,60,61,79,80,81,86,89,97,105,111,123,125,131,133,139,140,141,144,145,148,159,166,175,181,203,206,210,212,217,218,219,220,221,232,233,239,244,246,252,256,261,300,315,318,324,326,327,328,330,357,362,363],creation_:324,creativ:[79,108],creator:[51,53,79,80,111,123,140,166,175,217,218,219,220,221,247,330],creatur:364,cred:[131,287],credenti:[90,103,131,144,287],credentialinterfac:287,credit:[90,103,131,343,344,364],creset:131,crew:119,criteria:[51,119,176,194,204,251,317,341],criterion:[119,131,144,179,206,238,245,247,255,258,341,344],critic:[19,26,31,60,63,67,97,102,105,114,128,242,266,267,337],critici:318,cron:67,crontab:67,crop:[58,114,159,327,330,336,344,364],crop_str:330,cross:[111,138,233,330],crossbario:295,crossbow:29,crossroad:111,crowd:[61,103],crt:[8,67],crucial:[91,115],crude:0,cruft:1,crumblingwal:232,crumblingwall_cmdset:232,crush:21,cryptic:138,cryptocurr:103,cscore:123,csessid:[285,295,296,308],csession:[295,296],csrf_token:133,css:[17,55,124,135,136,137,343],cssclass:137,ctrl:[48,63,67,90,93,95,100,110,298],culpa:52,cumbersom:[51,121,128,215],cumul:299,cup:70,cupidatat:52,cur_valu:190,cure:[219,220],cure_condit:219,curi:49,curiou:108,curli:[41,96,183],curly_color_ansi_bright_bg_extra_map:183,curly_color_ansi_bright_bgs_extra_map:183,curly_color_ansi_extra_map:183,curly_color_xterm256_extra_bg:183,curly_color_xterm256_extra_fg:183,curly_color_xterm256_extra_gbg:183,curly_color_xterm256_extra_gfg:183,curr_sess:308,curr_tim:187,currenc:[85,120],current:[0,2,9,11,12,13,14,19,20,21,22,25,27,28,29,31,33,41,43,46,48,49,50,51,58,59,60,64,68,74,76,77,79,80,85,86,89,97,100,102,104,105,106,112,114,115,116,119,120,121,123,124,127,128,131,133,137,138,144,147,148,150,151,153,154,156,157,159,164,165,166,168,169,171,175,179,180,182,187,188,190,195,198,202,204,206,212,213,215,217,218,219,220,221,230,232,233,235,238,245,246,247,252,256,260,261,267,272,277,283,284,287,288,299,306,308,310,317,318,326,328,330,331,337,338,341,344,362,364],current_choic:180,current_cmdset:159,current_coordin:235,current_kei:[250,251],current_us:133,current_weath:102,currentroom:121,curriculum:79,curs:42,curv:[55,56],curx:49,custom:[0,2,6,11,12,14,15,16,17,18,20,21,25,26,27,30,31,33,34,35,43,49,53,55,56,58,60,61,64,65,66,68,69,71,73,74,78,79,83,85,86,87,89,90,97,100,102,104,109,110,112,114,116,117,118,119,121,122,123,125,126,132,133,136,138,139,140,144,145,146,147,148,150,152,153,154,159,164,165,166,171,174,175,179,181,182,184,185,187,188,189,195,198,203,205,206,209,210,230,232,233,235,238,241,245,247,249,250,251,252,255,261,263,267,271,273,276,298,307,318,323,328,329,330,334,336,338,339,343,344,349,362,364],custom_add:195,custom_cal:[195,198],custom_gametim:[62,141,142,178,364],custom_kei:251,custom_pattern:[3,4,69,133,134],customfunc:83,customis:235,customiz:[17,41,180,188,190,206],customlog:8,cut:[20,40,49,50,55,91,111,123,252],cute:136,cutoff:344,cvcc:205,cvccv:205,cvccvcv:205,cvcvcc:205,cvcvccc:205,cvcvccvv:205,cvcvcvcvv:205,cvcvvcvvcc:205,cvv:205,cvvc:205,cwho:[43,164],cyan:[114,126],cyberspac:79,cycl:[13,14,25,56,61,62,132,217,218,219,220,221],cyril:15,daemon:[8,67,93,100,103,110,284,312],dai:[25,27,36,56,61,62,100,103,108,120,126,131,132,139,147,184,187,331,337,344,345],daili:87,dailylogfil:337,dali:205,dalnet:164,dam:56,damag:[14,21,28,61,73,85,103,116,122,217,218,219,220,221,231,232],damage_rang:220,damage_taken:56,damage_valu:[217,218,219,220,221],damnedscholar:48,dandi:140,danger:[13,31,38,82,97,105,152],dare:33,dark:[13,14,17,31,73,79,111,114,122,126,153,187,224,233,241,256,322],darkcmdset:233,darker:[114,126],darkgrai:126,darkroom:233,darkroom_cmdset:233,darkstat:233,dash:[38,119,204,215],dashcount:215,data:[2,10,13,15,22,23,25,27,56,57,58,59,61,64,75,83,86,87,88,90,93,96,97,100,102,104,109,112,113,119,125,128,133,134,135,137,138,139,144,145,146,154,159,169,175,188,190,194,195,206,209,210,237,244,246,247,249,253,259,261,264,265,269,273,274,276,277,278,279,280,285,286,287,288,290,291,292,294,295,296,298,299,300,305,306,307,308,314,315,316,317,318,319,321,322,323,324,325,327,328,329,330,333,337,338,339,340,357,362,364],data_in:[40,83,210,276,278,279,285,286,290,295,296,306,307,308],data_out:[40,210,285,287,290,291,296,306,307,308],data_to_port:264,data_to_serv:277,databa:267,databas:[0,4,5,6,7,11,12,13,15,17,19,20,21,23,25,27,28,29,31,34,36,38,39,45,47,55,56,57,58,59,60,61,63,64,74,77,80,84,87,89,91,93,100,101,102,104,105,107,110,111,112,115,116,119,123,124,125,127,131,133,134,135,136,138,139,140,144,147,148,152,153,159,166,169,173,174,175,176,177,187,194,195,206,220,233,236,238,239,241,244,245,246,247,250,251,253,254,255,256,257,260,261,267,271,273,284,298,305,306,314,315,316,317,318,319,322,324,325,326,329,332,334,340,341,344,346,364],datareceiv:[269,276,290,298],dataset:251,datastor:86,datbas:119,date:[7,11,12,23,34,49,62,68,75,76,86,126,128,131,133,138,145,153,157,209,331,337,345],date_appli:133,date_cr:[125,144,148,177,256,316,318],date_join:[145,148],date_s:34,datetim:[62,125,133,316,331,337,338,344,345],datetime_format:344,datetimefield:[86,133,145,148,177,246,256,316,318,344],david:79,day_rot:337,db3:[23,111,128,131],db_:[84,86,119,125,206,245,247,257,272,341],db_account:[182,244,245,246,256],db_account__db_kei:244,db_account_id:[246,256],db_account_subscript:[173,177],db_attribut:[107,119,145,148,177,244,246,256,318],db_attrtyp:316,db_attryp:87,db_categori:[86,315,316,319],db_category__iequ:86,db_channel:173,db_cmdset_storag:[145,148,182,244,246],db_data:[315,319],db_date_cr:[86,148,173,177,182,246,256,316,318],db_desc:256,db_destin:[182,244,246],db_destination__isnul:120,db_destination_id:246,db_entrytext:[237,239],db_header:177,db_help_categori:[237,239],db_hide_from_account:177,db_hide_from_channel:177,db_hide_from_object:177,db_hide_from_receiv:177,db_hide_from_send:177,db_home:[182,244,246],db_home_id:246,db_index:86,db_interv:[254,256],db_is_act:256,db_is_bot:[145,148],db_is_connect:[145,148],db_kei:[69,84,86,119,125,145,173,182,194,237,239,244,254,257,263,274,315,316,318,319,357],db_key__contain:125,db_key__icontain:86,db_key__istartswith:119,db_key__startswith:[119,125],db_locat:[84,119,182,244,246],db_location__db_tags__db_kei:119,db_location__isnul:120,db_location_id:246,db_lock_storag:[145,173,177,182,237,239,244,316,318],db_messag:[173,177],db_model:[316,319],db_obj:[254,256,325],db_obj_id:256,db_object_subscript:[173,177],db_permiss:[86,145],db_persist:[254,256],db_properti:272,db_protototyp:251,db_receiv:173,db_receivers_account:177,db_receivers_channel:177,db_receivers_object:177,db_receivers_script:177,db_repeat:[254,256],db_sender:173,db_sender_account:177,db_sender_extern:177,db_sender_object:177,db_sender_script:177,db_sessid:[182,244,245,246],db_staff_onli:[237,239],db_start_delai:[254,256],db_strvalu:316,db_tag:[119,145,148,177,237,239,244,246,256,318,319],db_tags__db_categori:[39,119],db_tags__db_kei:[39,119,173],db_tags__db_key__in:39,db_tagtyp:[315,319],db_text:86,db_typeclass_path:[86,120,145,182,244,246,254,318,344],db_valu:[84,87,263,274,316],dbef:[255,341],dbhandler:357,dbid:[125,146,164,318],dbid_to_obj:344,dbmodel:317,dbobj:[11,316],dbobject:[11,317,318],dbprototyp:[169,251],dbref:[12,13,20,58,66,80,109,111,116,119,121,122,125,128,144,147,148,157,159,164,169,176,188,203,206,212,233,235,241,245,246,247,250,251,252,255,256,258,317,318,324,341,344,364],dbref_search:[147,245,255,317],dbref_to_obj:344,dbrefmax:159,dbrefmin:159,dbsafe_decod:340,dbsafe_encod:340,dbserial:[11,97,141,142,257,320,364],dbshell:[23,86,110,128],dbunseri:325,ddesc:56,deactiv:[63,64,81,117,164,187,227,231,328],deactivatebuttonev:227,dead:[112,231,232,305,308,334],deadli:122,deal:[10,11,12,15,41,51,64,69,73,91,103,105,112,113,116,124,126,127,131,134,138,139,144,179,180,184,188,217,218,219,220,221,246,247,306,318,321,338,362,364],dealt:[167,168,219,220],dealth:219,death:[51,73,120],death_msg:231,death_pac:231,debat:91,debian:[8,23,63,67,131],debug:[14,27,45,51,59,72,74,91,95,102,106,135,139,150,154,158,169,188,230,249,267,272,278,279,290,312,322,328,337,344,364],debugg:[15,42,110,141,364],decemb:90,decend:[51,150],decent:[93,205],decic:205,decid:[4,14,15,25,33,41,46,58,61,69,73,85,86,88,90,103,105,112,114,116,126,138,150,179,217,242,329],deciph:48,decis:[73,115],declar:[114,340],declared_field:[145,237,244,315,357],declin:[51,179],decod:[15,291,321,344],decode_gmcp:291,decode_msdp:291,decoded_text:344,decompos:133,decompress:[276,340],deconstruct:[122,228,293,342],decor:[0,29,33,46,107,131,148,246,256,264,276,277,318,324,328,329,344,364],decoupl:[9,251],decoupled_mut:11,decreas:[220,233,326],decrease_ind:326,dedent:[50,344,364],dedic:[73,90,127,364],deduc:326,deduce_ind:326,deduct:[73,85,217,218,219,220,221],deem:[37,57,129,131,178,362],deep:79,deeper:[41,215],deepest:159,deepli:11,deepsiz:344,def:[1,3,4,5,6,10,11,21,22,25,27,28,29,30,31,33,38,39,40,41,42,44,48,49,50,51,56,57,58,60,62,69,71,73,74,79,80,81,82,84,85,89,91,95,96,102,107,109,111,114,116,117,118,119,120,121,123,125,127,132,133,134,180,187,234,235,250,296,309,328,329,336,344],def_down_mod:219,defalt_cmdset:71,default_access:[1,11,316,324],default_categori:238,default_channel:34,default_charact:189,default_cmd:[5,21,22,25,28,29,30,41,44,53,57,58,62,81,116,119,141,180,182,187,199],default_cmdset:[5,22,25,30,35,41,44,57,58,60,62,81,82,105,123,153,180,181,182,187,188,202,212,215,217,218,219,220,221],default_command:25,default_confirm:[159,203],default_error_messag:340,default_hom:[59,109],default_in:137,default_out:137,default_pass:324,default_screen_width:33,default_set:[3,127],default_transaction_isol:23,default_unload:137,defaultaccount:[2,41,53,64,125,141,144,146,160,247,342,357,362],defaultchannel:[6,53,125,141,175,362],defaultcharact:[5,6,22,25,53,57,58,60,62,73,81,86,89,96,123,125,127,141,144,161,180,182,189,206,217,218,219,220,221,247,342,357,362],defaultcmdset:[185,224],defaultdict:257,defaultexit:[6,53,85,89,125,141,212,213,232,235,247,342],defaultguest:[53,141,144],defaultlock:241,defaultmod:337,defaultobject:[5,6,26,38,53,60,64,82,85,86,89,96,111,117,119,121,125,141,144,182,206,214,218,221,226,232,247,318,342,357,362,364],defaultpath:344,defaultroom:[6,39,49,53,56,85,89,125,132,141,187,206,233,235,247,342],defaultscript:[53,56,102,116,120,121,125,141,146,179,184,195,203,204,205,217,218,219,220,221,223,227,235,251,258,259,300,331,342],defaultsess:162,defaulttyp:312,defaultunloggedin:163,defeat:[73,116,122,217,218,219,220,221,231],defeat_msg:231,defeat_msg_room:231,defend:[51,116,122,217,218,219,220,221,232,247],defens:[116,217,218,219,220,221],defense_valu:[217,218,219,220,221],defer:[10,27,29,33,133,145,148,150,177,187,213,239,246,247,256,260,264,274,276,277,308,312,316,318,319,335,337],deferredlist:312,defin:[0,2,4,5,10,11,12,13,14,20,21,22,25,27,30,35,36,40,42,44,46,49,50,53,55,56,57,58,59,61,62,64,68,69,73,74,77,78,81,83,85,88,89,91,95,96,97,104,106,109,111,113,114,115,117,119,121,123,125,126,127,129,133,135,136,137,138,139,141,143,145,148,150,152,153,154,156,159,165,167,168,169,170,171,173,175,176,177,180,182,183,184,185,187,188,194,195,198,203,204,205,206,214,215,219,220,223,224,227,232,233,236,237,238,239,240,241,242,243,244,245,246,247,251,252,255,256,259,261,262,264,267,274,277,298,299,306,307,308,311,314,316,317,318,319,321,322,323,326,328,331,335,336,339,341,344,346,357,362,364],define_charact:51,definit:[0,2,5,10,12,14,20,33,34,39,41,42,55,60,61,68,69,82,83,87,88,89,109,114,115,124,127,152,154,159,164,167,168,192,203,226,232,240,242,246,251,252,258,322,324,328,336,340,364],defit:51,deflist:312,degrad:127,deindent:344,del:[11,12,29,43,58,80,116,122,157,159,187,202,203,250,318],del_callback:[193,195],del_detail:187,del_pid:267,delai:[0,28,33,45,120,184,188,195,213,232,260,261,279,285,308,323,344,364],delaliaschan:[43,164],delayed_import:308,delchanalia:[43,164],delcom:[43,58,164],deleg:[148,177,239,246,256,316,318,319,335],delet:[2,4,7,11,12,13,20,22,23,31,43,50,51,63,66,68,80,87,89,98,100,102,105,107,111,112,116,122,128,131,144,153,156,157,158,159,164,165,166,169,174,175,177,187,192,193,195,196,199,202,203,212,227,232,239,242,247,251,255,257,258,259,260,261,273,285,306,315,316,318,321,322,328,334,360,362],delete_default:[31,153],delete_prototyp:251,delete_script:255,deletet:187,deleteview:362,deliber:[11,42,129],delic:182,delimit:[91,167,168,322,364],delin:48,deliv:[90,199,206],delpart:203,delresult:203,deltatim:344,delux:90,demand:[30,58,61,73,90,115,117,144,175,187,247,309,323],demo:[22,55,79,138,229,230,328,364],democommandsetcomm:230,democommandsethelp:230,democommandsetroom:230,demon:109,demonin:344,demonstr:[0,4,22,126,133,180,188,209,219],demowiki:4,deni:[8,103,194,198],denot:[56,114,134,322],denounc:327,depart:49,depend:[0,4,10,11,12,14,15,16,22,27,31,33,34,37,40,46,49,51,55,57,58,61,63,64,69,72,73,74,75,83,85,88,90,93,95,97,100,102,103,104,105,106,111,114,115,116,118,123,125,131,133,134,137,138,143,150,152,154,156,169,175,180,181,185,187,193,205,235,242,247,251,259,261,267,287,290,296,298,306,308,316,318,319,326,328,329,344,364],deplet:219,deploi:[38,46,90,103,364],deploy:[36,38,79,90,100,106],depmsg:337,deprec:[27,51,94,109,141,142,252,262,328,337,344,364],deprecationwarn:266,depreci:321,depth:[16,17,36,95,114,122,124,215,252],dequ:[11,310],deriv:[23,56,63,67,100,108,119,125,127,234,321,345],desc:[14,20,21,22,34,41,43,57,58,60,69,74,80,84,85,89,102,109,111,116,120,134,153,156,159,170,180,182,187,202,203,212,215,220,235,256,265,322,324,327,328,357,362,364],desc_al:231,desc_dead:231,desc_lamp_broken:226,desc_lid_clos:226,desc_lid_open:226,descend:[119,357],descer:364,describ:[5,9,11,13,14,20,21,22,30,31,33,37,38,43,46,51,55,58,62,63,64,68,69,71,75,76,79,80,85,86,88,90,92,96,102,109,110,111,113,114,116,124,125,127,128,131,133,135,137,139,152,159,163,164,165,177,182,184,187,204,206,220,226,244,252,259,264,285,287,290,300,328,343,344,363],descripion:231,descript:[0,14,15,20,21,22,34,38,39,41,46,49,51,54,55,57,58,60,61,68,74,77,85,90,96,102,109,111,112,126,129,131,133,134,135,139,145,156,159,164,165,175,179,180,182,187,202,204,206,212,215,226,230,231,232,233,234,235,237,241,244,247,256,322,324,328,338,339,364],description_str:111,descvalidateerror:202,deseri:[11,97,338],deserunt:52,design:[14,16,23,26,33,37,39,41,55,57,61,79,89,91,108,109,111,112,117,118,119,124,129,133,138,153,159,180,194,206,209,232,247,322,338,344,364],desir:[1,4,27,28,29,49,57,58,59,91,108,112,114,115,119,121,123,133,137,159,183,205,242,267,312,324,330,345],desired_perm:242,desktop:[15,16,138],despit:[11,13,57,63,64,79,81,105,233],dest:[234,247],destin:[0,22,25,33,49,74,77,85,89,91,109,111,119,121,159,209,212,213,217,218,219,220,221,232,233,241,246,247,252,324,362],destinations_set:246,destroi:[0,20,43,89,103,116,127,144,146,159,164,203,219,247],destroy:212,destruct:[31,152],detach:106,detail:[2,5,9,12,15,19,20,22,26,30,33,34,37,38,41,46,51,58,60,61,63,64,80,88,89,90,91,93,95,96,105,109,111,114,116,118,122,124,125,128,129,131,134,135,136,139,145,153,154,159,175,180,187,203,204,206,218,233,235,239,244,251,252,260,269,270,306,308,318,321,336,344,360,362,364],detail_color:159,detailkei:[187,233],detailview:362,detect:[31,33,36,38,61,81,88,89,103,105,118,151,154,168,175,279],determ:317,determin:[2,4,13,15,20,27,29,31,33,34,39,44,49,50,51,52,63,73,80,82,83,85,87,93,102,109,110,116,123,136,137,144,145,152,153,154,156,167,173,175,179,205,206,213,215,217,218,219,220,221,232,239,242,244,247,251,291,316,317,318,321,326,329,344],detour:[21,83,308],dev:[1,23,37,55,57,63,64,67,71,76,79,90,95,98,138],develop:[3,9,15,16,19,20,25,26,27,33,36,37,38,42,48,54,55,56,58,60,61,63,64,68,70,71,72,76,77,80,86,88,90,91,93,96,97,99,104,106,108,109,111,114,123,126,131,133,135,136,137,138,139,157,158,164,169,175,192,193,198,209,227,239,247,252,313,318,322,328,363,364],devoid:321,dex:[11,51,58,327],dexter:[217,218,219,220,221],diagnos:[30,97],diagram:125,dialog:137,dialogu:[0,124,139,364],dice:[63,73,91,116,141,142,178,364],dicecmdset:185,dicenum:185,dicetyp:185,dict:[0,11,13,25,31,46,51,53,88,107,109,119,127,144,146,152,154,159,175,182,184,187,188,192,195,198,205,206,209,210,215,219,221,233,247,249,250,251,252,259,261,264,265,267,272,276,277,278,280,285,287,290,295,296,307,308,310,317,322,323,325,327,328,329,336,339,344,357,362],dictat:[31,62,117],dictionari:[0,10,11,13,25,31,49,55,56,62,69,73,80,96,97,102,109,116,124,134,138,157,159,182,184,187,188,192,195,198,205,206,209,210,211,215,219,220,233,235,242,252,260,272,285,294,306,307,308,310,317,321,323,327,328,334,338,339,340,344,357,362,364],did:[2,21,22,29,57,60,64,68,91,95,96,104,111,123,131,144,179,247,260,319,340,344],did_declin:179,didn:[5,20,22,38,41,42,44,49,51,58,59,61,72,80,91,100,104,119,121,126,127,133,136,140],die:[73,91,106,114,117,185,205,308],dies:231,diff:[75,131,185,252],differ:[0,2,8,9,11,13,14,15,16,19,20,21,22,24,25,27,31,33,37,38,39,40,41,42,44,46,47,49,50,51,54,55,57,58,61,62,63,64,66,68,69,70,73,79,80,82,83,84,87,88,91,93,95,96,100,102,103,105,106,107,109,110,111,112,113,114,115,116,118,119,120,121,124,126,127,129,131,133,136,137,138,139,140,141,144,145,150,152,153,156,159,168,169,171,175,180,184,185,186,195,196,199,204,206,213,215,217,218,219,220,221,224,234,235,245,247,249,251,252,255,256,259,261,265,269,291,296,298,315,316,318,322,324,328,337,340,344,362,364],differenti:[56,57,58,182,206,215,247],differet:61,difficult:[4,39,93,103,133,220,221],difficulti:133,dig:[0,20,31,33,40,43,57,58,89,93,96,109,121,123,140,159,212,299],digit:[12,90,114,127,204,311,321,337],digitalocean:[67,90],diku:[55,64,124,139,364],dikumud:129,dime:108,dimens:[49,55],dimension:58,diminish:114,dimli:111,dinner:46,dip:96,dir:[9,21,23,36,38,54,58,63,64,67,75,79,90,96,100,102,127,128,130,131,134,337,344],direct:[0,3,8,10,11,12,20,22,31,44,45,49,51,58,70,74,88,90,100,109,111,116,118,119,121,128,137,138,139,159,194,210,235,242,245,267,328,330,337,341,344,364],directli:[2,5,8,13,14,20,21,23,27,29,30,33,37,38,40,42,44,46,50,51,55,56,58,59,61,62,64,72,80,88,89,90,93,95,96,100,102,104,109,110,111,114,116,118,119,123,125,128,131,137,138,147,154,170,175,176,179,180,181,185,198,206,215,220,221,227,233,234,238,242,245,246,247,251,255,256,273,278,287,290,295,300,306,316,318,322,324,328,329,342,344],director:206,directori:[4,8,9,13,20,25,27,36,37,45,58,59,62,63,64,69,75,76,95,96,100,106,123,125,127,128,130,131,133,134,135,136,137,139,159,209,267,287,288,312,322,337,344,364],directorylist:312,dirnam:267,dirti:55,disabl:[0,4,24,25,50,63,80,81,106,114,127,137,154,170,188,206,215,234,242,290,329,334,345,364],disableloc:290,disableremot:290,disadvantag:[58,90,116,221],disambigu:[41,72,119,154,174,247,318],disappear:103,discard:[175,321],disconcert:41,disconnect:[2,11,12,40,41,55,57,60,92,97,105,107,110,112,116,123,128,137,144,156,159,164,167,169,175,247,277,278,279,285,286,287,290,295,296,299,305,306,307,308],disconnect_al:285,disconnect_all_sess:308,disconnect_duplicate_sess:308,disconnect_session_from_account:144,discontinu:24,discord:[9,63,72,79],discordia:108,discourag:[64,75],discov:[91,122],discoveri:210,discrimin:103,discuss:[1,4,25,26,33,37,45,48,55,63,69,70,116,138,139,364],discworld:88,disengag:[116,144,217,218,219,220,221],disk:[11,27,86,100,108,110,205,209,249],dislik:57,disonnect:11,dispatch:37,dispel:126,displai:[0,17,22,25,30,31,33,38,42,46,50,51,58,59,60,61,68,69,73,80,81,82,83,85,88,89,91,93,101,102,103,104,111,114,116,119,123,124,133,134,135,136,137,138,139,145,154,156,159,166,169,171,173,175,179,180,182,186,187,188,190,193,195,199,206,215,230,232,233,234,235,237,247,251,252,254,265,267,284,302,305,310,318,319,326,327,328,329,330,338,339,340,343,344,345,357,362,364],display:261,display_buff:326,display_choic:180,display_formdata:188,display_help:326,display_helptext:[249,328],display_len:344,display_met:190,display_nodetext:328,display_titl:180,dispos:[111,203],disput:116,disregard:33,dist:[63,130],distanc:[6,27,39,46,49,64,125,205,220,221,247,344],distance_inc:221,distance_to_room:39,distant:[49,138,187,233],distinct:[55,64,105,140,221],distinguish:[22,154,215,221],distribut:[8,9,15,23,31,34,42,63,64,78,96,97,124,127,128,175,177,206,321,324],distribute_messag:175,distributor:34,distro:[8,23,63,67,72],disturb:[27,140],distutil:63,distutilserror:63,ditto:63,div:[3,16,17,38,109,137,250],dive:[22,41,63],diverg:83,divid:[13,64,69,184,233,344],dividend:184,divisiblebi:69,divisor:184,django:[2,3,4,9,12,15,23,25,36,39,55,63,69,73,76,79,86,101,103,104,107,112,113,120,124,125,127,128,134,136,137,139,144,145,147,148,171,173,175,177,186,237,239,244,245,246,251,254,255,256,263,266,267,273,274,287,293,295,296,303,309,311,312,315,316,318,319,322,325,329,333,334,335,340,342,344,346,349,352,357,362,364],django_admin:360,django_nyt:4,djangonytconfig:4,djangoproject:[23,357],djangowebroot:312,dmg:73,dnf:[8,63,67],do_attack:231,do_flush:[318,334],do_gmcp:291,do_hunt:231,do_mccp:280,do_msdp:291,do_mssp:281,do_mxp:282,do_naw:283,do_nested_lookup:159,do_not_exce:25,do_noth:230,do_patrol:231,do_pickl:325,do_task:[260,344],do_unpickl:325,do_xterm256:321,doabl:[14,138],doc:[16,17,23,25,33,45,51,53,60,64,68,70,79,86,95,96,109,110,125,129,130,136,139,141,159,204,234,278,344,357,363,364],docker:[7,63,79,90,139,364],dockerfil:100,dockerhub:100,docstr:[1,5,25,41,68,74,96,154,159,170,180,193,205,206,215,233,234,328,364],documen:96,document:[0,3,5,6,9,16,17,20,22,23,24,25,26,29,41,43,46,47,48,52,55,57,58,60,64,68,70,76,79,83,86,90,96,103,104,106,111,114,118,121,122,123,124,125,127,131,133,135,136,139,153,167,180,204,234,316,319,327,334,362,364],dodg:218,doe:[2,4,5,9,11,20,21,23,24,25,26,29,31,33,37,38,39,40,41,49,51,54,55,56,57,58,60,61,63,64,68,69,73,78,80,85,88,89,91,95,96,100,102,104,109,110,111,112,113,114,116,117,118,119,121,123,125,126,127,129,131,132,133,136,137,138,140,144,146,156,164,167,169,171,174,181,182,183,186,187,202,203,215,217,218,219,220,221,232,233,234,235,247,251,252,259,260,266,267,271,272,273,276,279,287,288,294,306,316,318,323,328,336,337,340,344,349,357,362],doesn:[0,4,9,11,13,15,22,25,26,29,33,36,37,39,44,46,49,51,57,60,61,63,69,71,72,73,75,76,78,86,88,89,90,91,95,96,103,110,111,121,123,125,126,127,128,133,136,137,138,153,177,181,187,194,195,206,219,242,267,280,287,291,321,328,339,344],doesnotexist:[144,146,148,175,177,179,182,184,187,189,195,203,204,205,206,212,213,214,217,218,219,220,221,223,226,227,231,232,233,235,239,246,247,251,256,259,274,300,316,319,324,331,335],doff:218,dog:[27,96],doing:[2,4,10,11,27,29,31,33,36,38,39,43,46,49,51,57,58,59,60,61,64,69,70,79,80,89,90,95,96,97,105,110,114,115,119,125,126,127,133,134,137,138,144,156,179,182,194,206,215,217,218,219,220,221,226,231,232,235,241,247,261,298,328,334,340],dolor:52,dom:137,domain:[8,55,67,90,103,138,324],domexcept:90,dominion:9,dompc:9,don:[0,1,3,4,6,9,10,11,20,21,22,23,25,26,27,29,30,31,33,34,37,38,39,41,42,44,46,47,50,51,54,58,59,61,62,63,64,67,68,69,70,72,73,75,80,81,82,83,85,86,88,90,91,93,95,96,97,102,103,104,105,106,111,114,116,119,122,123,125,126,127,128,131,132,133,134,135,136,138,140,144,146,152,153,159,164,165,166,167,168,171,174,175,180,185,194,198,205,206,218,219,220,224,227,233,234,235,242,247,251,252,261,271,272,279,284,285,290,292,299,306,313,318,321,322,328,334,337,340,344,357,362,364],donald:93,donat:[70,90,364],done:[1,4,6,9,10,11,20,21,22,25,29,30,31,33,34,36,37,38,39,41,44,49,51,55,56,57,58,59,61,62,63,64,67,69,70,73,76,80,82,85,87,90,91,93,100,107,108,110,115,116,117,118,119,120,121,123,126,128,131,133,136,137,144,154,156,174,175,179,185,205,221,227,235,242,246,247,259,260,261,267,280,284,286,288,292,296,302,305,306,308,313,316,321,322,329,334,344,362],donoth:259,dont:289,doom:252,door:[0,20,22,27,49,61,80,85,89,103,159,212],doorwai:212,dot:[22,119,153,159,322,344],dotal:[321,343],dotpath:344,doubl:[22,38,57,97,119,133,152,171,343,344],doublet:[152,153],doubt:[22,138,234],down:[0,4,6,11,12,21,22,29,31,33,36,38,39,41,49,50,51,55,57,58,61,63,73,81,85,86,90,91,93,96,100,102,103,104,106,108,111,114,119,122,123,136,137,144,159,169,195,209,215,218,219,232,235,241,247,252,259,261,267,269,276,277,284,285,305,306,308,321,329,330,344,364],download:[5,9,23,26,63,64,72,75,79,90,100,101,128,130,131,139],downtim:[29,103,331],downward:156,dozen:[25,55,108],drag:137,draggabl:138,dragon:56,dramat:[11,61],drape:182,draw:[14,38,39,49,73,119,330],draw_room_on_map:49,drawback:[14,23,28,29,51,58,73,86,138,181,322],drawn:[49,58,111],drawtext:73,dream:[26,55,61,129],dress:182,drink:[316,318],drive:[9,19,21,61,63,64,96,100,121,131,133],driven:[25,79,123,214,249],driver:23,drizzl:[102,132],drop:[6,9,14,20,21,23,25,33,37,40,43,55,57,58,60,69,70,73,80,85,86,87,88,89,90,117,118,121,128,137,138,159,165,171,182,203,214,218,221,226,241,247,276,318,322,344],drop_whitespac:330,dropdown:[106,138],droplet:67,droplock:241,dropper:[218,221,247],drum:90,dry:67,dtobj:344,duck:[27,95],duckclient:24,due:[5,6,12,22,29,31,33,40,58,60,62,63,64,76,90,91,93,95,96,104,107,125,126,140,153,169,246,247,269,305,308,315,321,337],duh:108,dull:[20,26,111],dumb:[20,138,308,321],dummi:[9,33,54,59,80,93,127,206,242,267,272,285,298,299,306,364],dummycli:298,dummyfactori:298,dummyrunn:[141,142,262,267,285,297,299,301,364],dummyrunner_act:298,dummyrunner_actions_modul:298,dummyrunner_set:[93,141,142,262,267,297,364],dummyrunner_settings_modul:93,dummysess:308,dump:[34,209,276],dungeon:[55,77,112],dupic:31,duplic:[31,37,96,152,159,261,318,337],durat:[10,28,132,139,219,338,345,364],dure:[9,11,29,31,38,40,55,60,61,63,66,68,79,80,95,97,100,102,105,107,116,123,132,135,136,137,140,144,152,164,170,187,203,227,231,233,234,242,244,255,258,260,276,286,322,324,328,337,357,364],duti:64,dwarf:111,dying:[217,218,219,220,221],dynam:[2,3,34,38,68,82,86,90,111,114,115,124,133,137,138,139,144,148,154,166,169,170,174,177,188,206,215,217,218,219,220,221,239,246,247,256,261,316,318,319,324,326,328,335,338,362,364],dynamical:344,dyndns_system:90,e_char_typeclass:120,each:[0,1,2,4,5,10,11,13,19,20,22,27,29,31,33,34,36,38,39,40,42,48,49,51,55,56,57,58,59,61,62,64,69,73,77,80,82,83,85,86,95,96,97,100,102,104,105,108,109,111,112,114,115,116,119,121,123,124,125,126,127,132,133,136,137,138,140,144,151,152,153,157,159,168,175,179,181,182,183,187,188,203,205,206,215,217,219,220,221,228,235,239,242,246,247,252,255,258,261,269,272,285,287,290,294,299,306,307,308,316,318,319,321,322,324,326,327,328,329,330,334,336,344],eaoiui:205,earli:[36,138,217,218,219,220,221,269,364],earlier:[3,9,13,31,36,51,54,58,60,61,62,64,74,85,95,96,106,119,121,123,131,134,272],earn:124,earnest:124,earth:[82,103],eas:[31,33,39,86,90,100,126],easi:[0,5,10,13,17,22,23,26,29,33,38,39,46,51,55,56,61,62,67,68,69,72,73,76,79,81,82,85,88,89,90,100,102,106,108,111,113,116,118,123,125,126,127,128,131,133,134,138,140,153,157,164,182,188,215,328,334],easier:[1,4,10,11,12,22,25,37,39,47,51,55,56,57,58,61,62,69,73,86,90,91,95,96,102,109,126,136,205,215,217,218,219,220,221,232,309,319],easiest:[0,5,12,15,25,27,30,46,58,63,67,70,76,123,128,131,133,135,209,318],easili:[0,3,4,11,12,13,14,17,20,25,27,28,33,34,37,38,39,46,48,49,51,55,58,60,61,62,63,68,70,73,80,83,85,88,90,91,96,98,100,103,105,106,107,108,109,111,112,119,122,123,131,133,136,137,138,140,166,177,179,180,182,188,190,194,205,212,215,217,218,219,220,221,234,238,241,261,322,328,339],east:[25,44,49,111,159,233],east_exit:233,east_west:111,eastern:[62,111],eastward:233,echo1:29,echo2:29,echo3:29,echo:[5,10,12,20,26,27,28,29,33,36,38,44,49,50,55,59,65,71,90,95,96,98,100,104,109,110,116,118,123,132,140,144,146,157,159,164,169,182,185,206,226,231,232,233,247,265,272,287,290,326,344,364],echotest:5,econom:[55,79,86],economi:[61,73,102,108,120,179],ecosystem:100,ect:96,edg:[16,27,131,330,344],edgi:49,edit:[0,1,4,5,6,9,11,13,14,23,25,26,30,33,35,37,40,41,43,46,48,54,56,58,59,60,61,62,67,68,69,70,75,76,79,80,81,86,95,96,97,100,101,104,106,109,111,114,128,133,134,135,136,137,138,157,159,166,169,180,186,188,192,193,195,196,202,203,237,242,244,247,249,251,252,316,326,357,362,364],edit_callback:[193,195],edit_handl:159,editcmd:22,editnpc:364,editor:[0,5,9,15,21,22,33,38,45,46,53,57,60,63,67,76,79,95,96,97,108,109,111,131,139,159,166,168,169,180,202,256,322,326,364],editor_command_group:326,editorcmdset:326,editsheet:58,edu:124,effect:[6,10,11,14,27,28,29,31,35,38,39,56,57,58,61,73,87,95,104,107,110,111,114,115,116,117,124,126,127,128,129,138,140,141,142,144,152,153,159,168,185,195,218,219,220,226,227,231,233,240,245,247,253,256,280,336,344,364],effici:[11,26,28,29,39,55,56,64,76,79,86,87,93,95,103,112,115,119,125,132,179,206,213,242,247,261,316,317,319,326,329],effort:[37,56,131,134,362],egg:75,egg_info:63,egi:269,egiven:245,either:[0,4,9,12,13,17,23,27,29,31,33,34,37,39,41,44,46,49,51,56,57,58,69,73,80,83,90,91,93,95,97,102,103,105,109,110,111,112,114,116,119,121,122,123,125,126,128,131,137,138,144,146,152,153,154,164,169,174,175,176,180,192,198,199,205,206,212,215,217,220,221,242,247,251,252,256,258,259,261,265,276,288,292,299,317,318,319,328,330,336,337,339,341,344],elabor:[4,22,38,85,91,123],electr:[90,124],eleg:37,element:[16,17,22,41,51,55,91,114,151,156,180,184,204,205,247,252,316,317,319,322,327,328,329,344],elev:[46,82,124,139,364],elif:[0,41,49,51,58,73,102,116,117,123],elimin:[96,100,321],ellipsi:96,ellow:114,els:[0,1,2,5,9,10,12,19,20,21,22,23,25,27,29,30,33,38,39,41,42,46,48,49,51,58,60,68,69,73,80,81,82,84,85,90,91,95,102,103,111,114,115,116,117,120,121,123,127,131,133,134,137,179,182,188,204,217,218,219,220,221,235,246,296,318,328,344],elsewher:[2,29,31,58,70,96,112,133,138,153,233,267,308,316],elvish:205,emac:[14,79],email:[63,64,67,131,144,145,147,186,324,338,344,345,357],email_login:[141,142,178,364],emailaddress:344,emailfield:[145,357],emb:[38,58,109,114,187,252],embark:121,embed:[109,114,125,138,250,327,336],emerg:[76,80,103],emi:205,emit:[25,34,108,137,153,157,175,189,247,306,337],emit_to_obj:[153,247],emitt:83,emo:21,emoji:24,emot:[33,41,43,55,68,116,165,179,205,206],emoteerror:206,emoteexcept:206,emphas:[38,61],emphasi:38,emploi:345,empti:[0,2,3,6,9,10,14,25,31,33,38,41,42,47,49,51,54,58,60,63,64,69,73,77,84,86,88,89,91,96,97,100,114,115,117,119,123,125,127,128,131,134,137,138,150,151,157,159,170,180,190,192,206,251,252,265,272,276,298,299,315,322,324,328,330,341,344],empty_color:190,empty_permit:[145,237,244,357],empty_threadpool:312,emptyset:31,emul:[64,75,105,123,129,169,364],enabl:[8,24,71,100,103,106,114,126,134,137,144,175,188,290,345],enable_recog:206,enableloc:290,enableremot:290,encamp:46,encapsul:338,encarnia:79,encas:326,enclos:[35,50,171,186,336],encod:[7,27,43,58,111,139,171,278,291,295,296,321,340,344,364],encode_gmcp:291,encode_msdp:291,encoded_text:344,encompass:27,encount:[60,95,153,245,345],encourag:[3,22,24,39,70,91],encrypt:[7,8,83,103,164,287,288,292,364],end:[1,5,6,8,9,10,11,13,14,19,20,21,22,23,25,27,28,29,31,33,34,38,39,40,47,50,51,54,55,58,60,62,64,65,67,69,73,76,80,81,83,86,87,88,90,91,93,95,96,100,105,107,108,109,114,116,118,119,121,122,123,126,128,131,133,134,135,137,138,140,144,146,152,153,159,165,166,174,179,181,182,185,190,202,206,214,215,217,218,219,220,221,233,238,250,271,278,279,287,290,291,301,306,310,312,317,321,322,324,328,329,330,336,337,344,362,364],end_convers:51,end_turn:116,endblock:[3,69,133,134],endclr:[114,336],endfor:[69,133,134],endhour:25,endif:[69,133,134],endlessli:103,endpoint:103,endsep:344,endswith:321,enemi:[11,29,51,61,109,116,122,219,220,221,231,232,233],enemynam:51,enforc:[10,33,41,61,73,80,114,126,138,287,290,329,330,362],enforce_s:330,engag:[55,221,231],engin:[22,23,33,36,55,56,64,68,73,77,79,89,102,103,104,122,127,131,136,140,150,153,168,169,210,233,238,255,267,278,284,287,290,295,305,307,322,324,364],english:[15,76,79,97,113,139,171],enhanc:[59,81,114,209,321,362],enigmat:20,enjoi:[61,63,91,106,364],enough:[4,6,21,29,38,39,41,42,51,55,57,58,61,63,64,69,70,80,84,85,87,90,91,96,108,112,115,119,123,126,136,153,159,204,205,226,235,328,329,330],ensur:[49,69,100,106,117,126,127,215,342,362],ensure_ascii:296,enter:[0,1,3,5,9,12,13,14,15,20,21,22,23,25,26,27,29,31,33,35,36,41,42,44,46,51,58,62,63,64,66,69,75,77,80,83,85,87,89,91,95,96,100,109,111,114,116,117,119,122,123,124,128,129,131,133,135,138,139,141,144,151,153,158,167,168,169,174,179,180,182,187,188,198,215,217,218,219,220,221,231,233,235,241,247,252,256,265,306,326,328,357,364],enter_guild:51,enter_nam:51,enter_wild:235,enterlock:241,enterpris:36,entir:[10,11,13,14,19,22,27,29,33,46,49,50,51,60,61,69,80,86,90,91,108,111,114,115,123,125,127,136,180,205,206,215,234,241,242,247,251,252,318,322,328,330,334,336,344,362,364],entireti:[51,73,188,328],entit:324,entiti:[6,11,27,34,47,51,53,55,59,61,64,80,84,87,89,102,105,107,109,112,116,119,125,126,139,143,144,154,159,169,175,176,177,206,212,241,245,247,249,250,251,252,253,256,257,259,261,308,316,317,319,324,328,329,333,341,344],entitii:107,entitl:90,entranc:111,entri:[4,5,11,15,24,25,27,31,33,34,47,48,51,54,58,59,63,69,70,72,77,80,83,91,95,107,119,121,131,138,139,144,154,166,167,170,190,204,215,217,218,219,220,221,236,237,238,239,242,247,261,286,299,316,322,324,326,328,330,337,338,341,344,345,362,364],entriest:156,entrust:59,entrypoint:100,entrytext:[69,239,324],enul:8,enumar:344,enumer:134,env:[267,277],environ:[4,7,9,13,25,36,38,45,59,61,63,64,65,82,90,95,100,103,128,169,170,228,230,267,277,293,302,322,328,342,360],environment:267,eof:287,epic:79,epilog:234,epoch:[27,62,331],epollreactor:312,epub:79,equal:[0,16,19,20,25,31,33,39,46,91,93,96,97,114,121,152,187,206,217,218,219,220,221,247,344],equip:[14,57,114,182,217,218,220,221],equival:[10,11,13,40,47,63,87,88,101,103,104,110,114,128,143,147,159,238,245,255,285,291,316,344,362],eras:[9,95,221],err:[58,298,322],err_travers:[89,247],errback:[10,264,267,276,277,344],errmessag:152,errmsg:[123,337],erron:[113,123,276,330],error:[1,5,6,8,9,10,11,14,15,20,22,23,24,26,27,31,33,37,38,42,51,56,57,58,59,60,63,64,67,71,74,75,76,80,83,86,87,89,90,91,97,103,104,105,109,111,113,114,118,119,120,122,123,125,127,128,131,133,135,139,144,150,152,153,159,171,175,195,204,206,215,227,232,234,242,245,247,250,251,259,260,264,266,267,269,271,272,276,290,298,318,321,322,324,327,328,336,337,340,344,345,364],error_check_python_modul:267,error_class:[145,237,244,357],error_cmd:44,error_msg:310,errorlist:[145,237,244,357],errorlog:8,escal:[2,19,80,156,241],escap:[69,114,165,169,234,250,321,336,343,357],escript:[22,180],especi:[1,8,15,22,23,29,60,61,63,67,80,105,111,112,124,190,205,322],ess:52,essai:79,essenti:[28,49,56,67,75,79,106,113,176,267,324],est:52,establish:[33,61,73,105,144,217,247,264,276,278,285,287,290,295,298,305,307],estim:[30,252,334],esult:247,etc:[2,5,6,8,11,12,20,22,23,25,27,29,30,33,35,38,40,41,47,48,49,51,53,55,56,57,58,61,62,63,64,67,73,79,80,83,84,86,87,88,89,95,96,100,102,103,105,107,108,109,110,112,116,119,120,125,126,127,131,132,137,138,144,148,150,151,152,153,156,158,159,167,168,169,171,175,179,183,184,188,190,203,205,206,212,218,220,224,227,234,247,250,251,252,285,287,290,294,295,296,306,307,315,316,318,321,322,324,325,326,327,328,336,337,344,362],etern:51,ev_channel:146,eval:[109,179,250,364],evalstr:242,evalu:[33,38,51,119,151,179,242,250,328],evbot:[164,308],evcast:79,evcel:[327,330],evcolor:79,evcolumn:330,eve:344,eveditor:[22,43,45,53,139,141,142,180,320,364],eveditorcmdset:326,even:[1,4,6,9,11,12,14,19,21,22,25,26,27,29,31,37,39,41,42,46,49,50,51,54,55,56,57,58,60,61,62,63,64,69,70,73,77,80,85,86,90,91,93,97,102,103,105,106,108,110,114,115,116,118,119,122,123,125,126,129,131,135,138,152,154,157,182,184,187,188,205,217,218,219,220,221,233,234,247,252,290,328,330,334,344],evenli:[27,184],evenn:100,evenna:9,evenni:4,evennia:[0,1,2,3,6,10,11,12,13,14,15,17,19,20,21,22,24,27,28,29,30,31,33,34,35,36,37,39,40,43,44,48,49,50,51,52,53,59,60,61,62,63,64,65,66,68,69,70,72,73,74,78,80,81,82,83,84,85,86,87,88,89,92,93,94,97,98,99,101,102,103,104,105,107,108,111,112,113,114,115,116,117,118,119,120,121,122,123,125,129,130,132,133,134,135,136,138,139,364],evennia_access:8,evennia_admin:362,evennia_channel:[65,72,98,164],evennia_dir:344,evennia_error:8,evennia_launch:[106,141,142,262,265,364],evennia_logo:136,evennia_patreon_100x100:70,evennia_runn:106,evennia_vers:267,evennia_websocket_webcli:295,evennia_wsgi_apach:8,evenniacreateview:362,evenniadeleteview:362,evenniadetailview:362,evenniaform:357,evenniagameindexcli:269,evenniagameindexservic:270,evenniaindexview:362,evennialogfil:337,evenniapasswordvalid:311,evenniareverseproxyresourc:312,evenniatest:[170,196,211,228,293,342,360,364],evenniaupdateview:362,evenniausernameavailabilityvalid:[144,311],evenniawebtest:360,event:[51,64,73,103,107,137,139,141,146,179,184,194,195,196,198,206,209,227,256,259,309,364],event_nam:[194,198],eventdict:337,eventfunc:[0,141,142,178,191,195,364],eventhandl:195,eventi:[154,180,234],eventu:[4,11,12,19,25,29,33,41,58,61,70,76,80,83,88,90,110,116,119,123,133,136,144,150,151,168,170,185,205,206,233,242,247,251,252,264,272,298,306,307,319,323,324,328,330,355],evenv:[4,36,63,64,75,97,106],evenwidth:330,ever:[11,12,13,14,15,22,23,25,33,41,57,64,73,86,91,102,105,110,111,112,113,118,125,128,131,138,241,261,278,279,285,316,328],everi:[0,4,6,11,13,20,21,25,26,27,28,31,33,36,37,38,39,41,46,48,49,51,57,62,63,64,69,73,74,75,77,85,86,90,91,96,100,102,104,108,109,111,112,113,114,115,116,119,120,121,122,123,125,127,128,130,131,132,133,134,135,136,138,144,159,164,182,188,195,205,206,215,217,218,219,220,221,223,227,230,235,247,252,255,259,261,272,289,299,305,314,315,318,328,329,330,364],everror:195,everybodi:41,everyon:[19,21,24,33,34,51,58,61,64,71,73,77,78,80,87,98,102,110,112,114,116,121,123,127,128,131,132,159,165,166,185,217,218,219,220,221,247,285],everyth:[9,11,19,21,26,28,31,36,38,42,47,49,51,55,58,61,63,64,67,69,72,73,75,79,80,81,83,85,87,90,91,97,100,103,104,109,110,111,113,115,116,119,122,127,128,131,135,136,137,138,139,149,154,164,165,167,168,169,170,171,181,186,233,241,246,256,271,298,306,318,322,328,336,364],everywher:[9,56],evform:[27,45,53,141,142,320,364],evgam:164,evgamedir:38,evid:72,evil:[14,93,226,252],evmenu:[22,27,33,45,53,58,85,124,139,141,142,180,188,214,215,230,249,320,364],evmenucmdset:328,evmenuerror:328,evmenugotoabortmessag:328,evmenugotomessag:328,evmor:[43,45,139,141,142,251,320,364],evtabl:[27,33,45,49,53,82,111,141,142,154,188,251,320,327,329,344,364],evtable_arg:329,evtable_kwarg:329,exact:[33,41,51,80,93,95,96,119,129,138,144,147,151,159,168,176,206,221,238,245,247,251,252,317,318,340,341,344],exactli:[2,10,19,20,38,40,42,46,58,62,63,64,69,73,76,83,86,91,95,96,100,102,110,111,114,115,123,128,131,136,138,206,245,247,267,318,341],exam:[43,159],examin:[2,11,12,20,22,33,43,58,60,73,80,83,85,91,96,106,115,122,123,131,137,140,144,159,179,224,232,233,299,364],exampl:[0,2,4,5,6,8,10,11,13,14,15,17,19,20,21,22,25,27,28,29,30,31,33,36,37,38,40,41,43,44,48,49,55,56,57,58,59,60,61,62,63,64,67,68,71,74,77,81,82,84,85,86,87,88,89,91,93,95,96,97,98,100,103,104,105,106,109,110,111,112,114,115,117,118,119,121,122,123,124,125,126,129,130,131,132,133,135,136,138,139,140,144,148,151,152,153,154,157,158,159,164,165,166,167,168,170,174,176,177,179,180,182,184,185,187,188,189,190,199,203,204,205,206,209,212,213,214,215,217,218,219,220,221,223,226,227,231,233,234,235,239,242,246,247,252,256,259,261,272,287,290,291,296,299,308,312,315,316,318,319,321,322,323,327,328,329,330,331,335,336,337,338,341,342,344,345,357,362,363,364],example_batch_cod:[13,141,142,178,222,364],exapmpl:5,excalibur:85,exce:[82,217,218,219,220,221,310,334],exceed:310,excel:[56,67,79,80,102,108],excempt:152,except:[4,9,10,11,14,19,20,21,22,27,28,29,31,33,38,39,41,46,50,58,63,64,75,80,83,89,90,91,95,97,102,109,111,114,116,118,119,120,121,123,126,133,134,144,146,148,150,153,154,167,168,175,176,177,179,182,184,187,189,194,195,198,202,203,204,205,206,212,213,214,217,218,219,220,221,223,226,227,231,232,233,234,235,239,241,242,245,246,247,251,256,259,260,267,272,274,276,288,290,292,296,300,312,316,319,321,324,327,328,329,330,331,335,336,337,339,344],excepteur:52,excerpt:50,excess:[22,80,109,167,168,246,322],exchang:[13,90,102,179,325],excit:[20,35,54],exclam:21,exclud:[64,119,120,123,182,203,233,245,246,247,326,328],exclude_channel_messag:176,exclude_cov:182,excluded_typeclass_path:169,excludeobj:245,exclus:[51,61,80,83,255,256,317,328],exclusiv:324,exe:[63,106,128],exec:[51,85,109,252,328],exec_kwarg:328,exec_str:302,execcgi:8,execut:[0,9,10,12,13,14,19,22,25,28,29,31,33,36,45,46,47,50,51,55,62,63,64,69,75,83,85,87,89,91,95,102,106,109,111,114,119,127,128,137,139,144,146,148,149,150,154,157,158,166,167,169,170,177,180,195,206,215,233,234,239,241,242,246,247,251,252,253,256,260,264,272,274,277,278,284,287,290,295,299,302,305,306,316,318,319,322,328,329,335,336,344,364],execute_cmd:[2,33,89,117,118,123,144,146,154,247,272,306],execute_command:33,executor:36,exemplifi:[28,40,122],exercis:[21,41,42,58,85,95,96,111,116,123,132,293,303,335,364],exhaust:22,exhaustedgener:204,exidbobj:247,exis:44,exist:[0,2,3,5,11,12,13,20,21,22,25,27,31,33,35,36,39,40,41,43,44,46,48,49,51,56,57,58,60,61,64,65,68,69,70,72,76,80,86,96,97,100,102,105,109,111,112,115,116,117,123,124,128,131,134,136,138,139,143,144,145,146,152,153,154,159,164,166,167,168,169,175,180,181,187,192,194,195,198,199,202,203,205,206,213,220,232,235,241,242,246,247,249,252,259,260,267,271,273,287,288,292,300,305,306,308,316,317,318,319,322,324,326,327,328,330,337,339,344,364],existen:306,exit:[20,21,22,23,31,39,41,45,49,50,51,53,55,58,63,80,85,86,91,100,106,109,111,119,121,122,123,124,125,128,139,141,150,152,153,159,169,179,180,196,212,213,215,221,231,232,233,234,235,241,245,246,247,252,287,299,316,324,326,328,329,342,360,364],exit_alias:[159,212],exit_back:58,exit_cmd:[51,329],exit_command:247,exit_nam:[49,159,212],exit_on_lastpag:329,exit_ther:58,exit_to_her:159,exit_to_ther:159,exit_typeclass:[235,342,360],exitbuildingmenu:22,exitcmdset:[31,247],exitcommand:247,exitnam:212,exitobject:44,exixt:285,exot:33,exp:327,expand:[0,1,4,5,6,20,21,23,49,55,57,58,61,64,70,74,81,85,89,90,104,111,114,117,120,123,124,131,132,135,139,140,159,186,212,217,218,219,220,221,247,321,330,364],expand_tab:330,expandtab:[321,330],expans:[44,61],expect:[0,1,6,9,10,33,34,37,38,47,56,58,61,67,75,80,83,87,88,89,90,91,95,96,97,107,113,114,115,122,123,124,126,127,128,134,138,159,167,168,180,192,194,204,227,235,241,247,251,252,265,315,318,328,329,334,349,362],expected_return:127,expedit:96,expens:[90,115,119,245,341],experi:[26,42,51,57,60,61,62,63,73,77,81,90,95,100,111,122,131,135,139,164],experienc:[51,61,64,79,95],experienced_betray:51,experienced_viol:51,experiment:[74,169,173,244],explain:[20,22,33,39,43,48,51,55,58,64,71,79,86,119,121,124,126,127,129,131,134,136,139],explan:[25,31,33,39,64,69,77,114,124,139,311,364],explicit:[0,1,22,31,38,40,48,69,71,88,91,104,129,136,204,267,289,316],explicitli:[4,9,21,30,31,58,59,63,68,80,83,84,85,86,87,96,97,109,112,114,115,124,125,153,154,159,204,247,252,261,318,321,324,340],explor:[0,2,10,20,42,59,63,69,83,95,104,111,116,122,125,169,364],expos:[103,134],express:[3,33,38,51,56,80,109,119,127,134,135,140,159,184,204,221,250,344],ext:51,extend:[1,3,5,27,34,38,39,55,56,69,73,79,85,86,108,109,111,117,118,125,133,134,148,154,166,170,175,181,183,187,195,198,235,244,246,247,318,321,338,357,362],extended_room:[141,142,178,364],extendedloopingcal:261,extendedroom:187,extendedroomcmdset:187,extens:[1,3,9,23,38,51,55,56,61,63,64,88,96,97,104,111,114,127,138,148,210,217,282,290,324,333,343],extent:[22,56,73],exter:164,extern:[8,15,23,34,40,41,54,55,57,63,65,72,90,98,106,108,109,111,124,139,141,153,164,172,175,177,209,251,265,267,269,364],external_discord_hello:272,extra:[1,6,8,14,16,21,23,25,29,31,33,37,38,41,51,57,58,80,89,90,93,95,96,107,114,119,123,125,126,127,134,136,137,138,144,145,148,154,166,179,187,189,202,206,233,247,250,251,261,264,315,317,321,322,326,328,329,330,337,338,339,343,344],extra_environ:322,extra_spac:344,extract:[11,41,56,91,96,97,107,138,154,206,210,242,281,295,344],extract_goto_exec:328,extrainfoauthserv:287,extran:188,extrem:[26,56,91,110,128,217,218,220,221,280,338],eye:[60,97,111,114,252,329],eyed:136,eyes:[33,37,57],eyesight:[58,80,114],f6d4ca9b2b22:100,face:[90,103,122,171,189,311,328],facil:337,fact:[10,11,14,21,29,33,55,57,58,61,76,83,89,103,106,114,117,123,125,126,134,138,140,308,336],facter:138,factor:[0,62,82,114,218,220,264,278,279],factori:[40,96,264,269,277,278,279,285,286,287,288,290,298],factory_path:146,fade:[108,205],fail:[4,9,10,11,12,13,14,24,27,31,41,51,60,61,63,89,91,103,107,109,110,113,116,117,121,127,153,168,175,185,206,212,232,241,242,247,251,259,264,265,267,271,278,279,289,310,315,316,318,338,340,344,362],failmsg:310,failtext:73,failur:[10,14,63,73,119,127,144,233,269,276,278,279,298,310,321,344],failure_teleport_msg:233,failure_teleport_to:233,faint:102,fair:[73,185],fairli:[39,69,75,182,188,215,218],fake:[183,298,308],fall:[26,31,38,60,62,64,73,97,102,111,113,141,144,168,189,206,233,328,344,357,362],fall_exit:233,fallback:[44,49,55,150,154,177,187,242,259,267,291,296,316,328,339,344],fals:[1,2,4,6,11,20,21,22,25,27,29,31,33,41,44,49,50,51,58,62,68,74,77,80,81,84,86,89,96,102,103,115,116,118,120,121,123,125,127,133,137,144,145,148,150,151,152,153,154,159,166,175,176,177,179,180,182,183,184,185,188,192,195,199,205,206,212,215,217,218,219,220,221,230,234,235,237,238,239,241,242,244,245,246,247,249,251,252,255,256,257,258,259,260,261,264,267,269,273,276,277,284,285,286,287,290,296,304,305,306,308,310,312,315,316,317,318,319,321,322,324,326,328,329,330,331,334,336,339,340,341,343,344,345,357],falsestr:188,falter:61,fame:122,famili:[9,51,57],familiar:[3,9,20,29,31,33,39,58,60,63,85,90,91,95,96,111,119,124,125,133,364],famou:[52,326],fan:79,fanci:[15,17,36,73,138,182],fanclub:119,faq:[38,45,124,139,289,364],far:[0,13,20,21,22,31,33,39,41,44,46,49,51,54,55,57,59,61,75,88,90,91,95,96,100,106,111,114,119,131,138,152,221,235,241,269,294,326,334],fashion:111,fast:[11,15,23,26,27,29,56,62,64,82,89,108,115,131,157],faster:[23,62,93,119,175,177,179,316,364],fastest:[5,38],fatal:267,faulti:95,favor:27,favorit:[21,37],fear:27,featgmcp:291,featur:[0,4,12,15,17,20,22,25,26,27,31,33,34,36,37,38,42,45,46,47,48,49,50,56,57,59,61,62,63,64,70,72,78,81,85,91,96,103,107,109,111,114,119,122,123,124,125,128,129,131,138,139,144,153,154,187,195,206,215,234,261,284,305,309,318,326,344,362,364],februari:62,fed:[10,33,80,285,325,327],fedora:[8,63,67,131],feed:[7,15,49,51,55,73,98,109,128,139,146,164,269,286,287,318,329],feedback:[37,42,61,70,89,118,176,226,326],feedpars:[98,286],feedread:146,feel:[0,10,17,22,37,38,39,46,55,57,60,61,63,64,69,70,71,73,77,90,91,108,118,122,123,125,131,133,138,205,215,218,224,233,364],feend78:199,feint:116,felin:27,fellow:327,felt:[102,132],femal:189,fetch:[11,63,90,100,128,131,133,329,362],few:[0,4,6,9,10,11,15,17,20,23,31,33,34,36,38,41,42,49,50,55,59,60,61,64,66,73,74,79,80,86,88,89,91,103,110,114,116,119,121,122,123,126,127,131,138,169,184,205,227,246,282,291,310,321,330,344,362],fewer:[108,308,317],fg_colormap:343,fgstart:343,fgstop:343,fiction:[51,55,62,77,328],fictional_word:205,fictiv:205,fiddl:233,fido:96,fie:102,field:[3,11,23,34,54,56,58,74,84,86,87,89,102,106,107,112,119,125,128,133,135,145,148,173,177,188,192,206,221,231,237,239,241,244,245,246,247,251,252,254,256,257,261,274,315,316,317,318,319,327,335,340,341,357,359,362,364],field_class:357,field_or_argnam:74,field_ord:357,fieldevmenu:188,fieldfil:[141,142,178,364],fieldnam:[58,84,188,257,318,334,357],fieldset:[145,173,237,244,254],fieldtyp:188,fifi:96,fifo:344,fifth:49,fight:[29,31,61,116,122,217,218,219,220,221,232],fighter:[217,218,219,220,221],figur:[3,12,26,33,37,42,49,80,83,90,91,93,96,97,119,121,131,133,138,179,181,184,206,251,267],file:[2,3,4,5,6,8,9,19,20,21,22,23,25,26,27,31,34,36,37,40,41,42,43,44,47,48,54,56,57,58,59,60,62,63,64,65,66,67,68,69,72,75,76,79,80,81,82,83,85,86,90,93,95,96,97,98,100,102,103,106,109,110,111,114,117,119,120,121,123,128,130,133,134,135,136,137,138,139,141,142,144,145,158,166,175,180,182,183,184,186,205,209,234,235,237,241,244,252,266,267,287,288,291,292,299,300,301,305,312,313,315,320,327,328,337,340,341,344,357,362,364],file_end:[322,344],filelogobserv:337,filenam:[27,60,131,322,327,337],filename1:267,filename2:267,filesystem:[63,100,103],fill:[36,41,49,50,58,61,65,70,106,111,114,119,133,135,188,250,315,316,321,327,329,330,344,364],fill_char:330,fill_color:190,fillabl:188,fillchar:[114,321,336,344],filo:344,filter:[31,34,39,69,86,106,114,119,120,125,133,138,152,157,175,180,187,206,247,344,362],filter_famili:[119,125],filthi:78,final_valu:10,find:[0,3,4,6,10,11,12,13,14,17,20,21,22,23,24,25,26,27,29,31,33,34,37,38,40,41,42,43,46,47,48,49,50,55,56,57,58,60,61,62,63,67,68,69,70,73,74,75,76,78,79,80,84,86,87,89,90,91,93,95,96,97,100,102,103,108,109,110,112,114,119,122,123,124,125,127,128,131,133,134,135,136,139,140,144,151,159,176,184,187,206,212,215,233,234,247,251,252,255,258,267,281,316,317,321,323,341,364],find_apropo:238,find_topicmatch:238,find_topics_with_categori:238,find_topicsuggest:238,fine:[12,15,20,33,38,41,44,46,64,85,86,89,95,105,112,115,118,122,123,138,146,233,324,344],finer:12,finish:[10,14,29,33,38,58,59,61,100,107,122,123,124,128,133,136,141,144,154,156,167,169,171,179,187,203,232,233,247,267,271,279,290,305,312,323,328,344],finish_chargen:51,finit:91,fire:[2,20,21,27,28,29,33,46,51,58,61,96,102,106,107,111,115,118,120,132,139,146,150,195,219,220,247,250,252,259,267,276,278,295,328,329,334],firebreath:58,firefox:72,firestorm:28,firestorm_lastcast:28,firewal:[67,90,364],first:[2,3,4,5,6,7,9,10,11,12,13,14,15,16,19,20,21,23,24,26,27,29,31,33,35,38,39,40,41,42,45,48,49,50,51,55,56,58,59,61,62,63,65,68,69,70,71,73,75,76,77,80,81,83,85,86,89,90,91,93,96,97,98,100,102,103,104,105,106,107,108,109,110,113,114,116,118,119,120,121,122,123,125,126,127,128,131,132,133,134,135,136,137,138,139,144,146,148,151,152,159,167,168,171,175,177,179,180,182,183,184,186,187,204,205,206,212,214,217,218,219,220,221,223,227,231,232,233,234,235,239,241,246,247,251,252,256,259,267,271,272,274,285,287,290,291,295,296,298,299,305,308,316,318,319,321,322,324,326,327,328,330,331,334,335,336,343,344,363,364],first_lin:123,first_nam:145,firsthand:80,firstli:[9,89,90,96,97],fish:[73,153,203],fist:252,fit:[11,23,39,47,51,58,80,88,121,129,130,133,218,221,327,329,330,344],five:[28,33,90,111,119,153,215,344,345],fix:[13,14,16,26,27,33,37,42,51,57,60,61,63,64,70,75,78,83,85,90,95,96,97,109,110,121,123,125,127,138,205,267,327,329,330,340,363,364],fix_sentence_end:330,fixer:119,fixing_strange_bug:131,fixtur:[228,293,303,335,342],flag:[9,13,14,20,28,29,30,31,33,40,41,51,58,61,74,76,83,86,108,115,123,131,144,150,152,154,159,231,241,242,247,267,274,278,287,290,295,306,326,328,344],flame:[28,220],flash:[14,227],flat:[22,26,27,45,47,48,53,56,59,60,96,125,141,252],flatfil:56,flaticon:79,flatten:252,flatten_diff:252,flatten_prototyp:252,flattened_diff:252,flatul:102,flavor:[20,90,220],flavour:[87,126],flaw:121,fled:[116,231],fledg:[15,90,108,123,133,158,185],flee:[116,117,221,231],fleevalu:116,flesh:[20,58],flexibl:[1,13,21,22,29,39,51,57,59,73,88,90,102,108,109,111,116,134,138,148,159,179,180,188,215,241,316,328,362,364],flick:345,flip:[43,51,81,171],flood:[27,50],floor:[0,82,206],flow:[17,36,40,55,61,83,86,115,131,137,324,328,336],flower:[12,20,61,87,89,119,159],flowerpot:[12,57],fluent:79,fluid:[16,17],flurri:206,flush:[23,33,111,128,169,259,318,334],flush_cach:334,flush_cached_inst:334,flush_from_cach:334,flush_instance_cach:334,flusher:334,flushmem:169,fly:[3,12,21,27,31,33,34,51,55,64,85,102,109,119,138,144,165,167,168,175,177,239,247,261,274,285,288,292,316,322,331,344,362],focu:[4,61,70,116,124],focus:[56,57,77,79,106,123,124,221],foe:218,fold:215,folder:[3,5,8,13,14,21,27,30,38,47,49,55,57,58,60,63,64,69,73,75,76,86,95,96,100,103,106,110,111,116,117,118,123,127,128,130,133,134,135,136,137,217,218,219,220,221,267,364],folder_nam:64,foldernam:60,follow:[0,2,4,5,7,8,9,10,11,13,14,16,17,19,20,22,23,25,31,33,34,37,38,39,40,41,42,46,47,48,49,50,51,54,58,60,61,62,63,65,67,68,69,71,73,74,75,76,79,80,82,85,86,88,89,90,91,93,95,96,97,100,102,103,106,110,112,114,116,117,119,120,121,123,125,127,128,131,133,134,135,137,144,146,148,150,151,154,159,167,168,170,175,177,180,182,183,185,189,195,199,206,215,219,220,226,233,239,241,242,246,247,250,251,252,256,257,271,272,282,291,295,296,299,309,316,318,321,322,324,327,328,329,330,336,337,344],follwo:242,follwow:51,fond:62,font:[25,38,111,137],foo:[33,40,51,83,84,88,95,107,112,119,127,215,328,342],foo_bar:88,foobarfoo:12,foolish:226,footer:[69,133,154,329],footnot:[15,38],footprint:169,footwear:57,for_cont:247,forai:96,forbid:41,forbidden:131,forc:[0,6,8,10,31,33,58,60,63,73,81,82,91,100,103,110,116,121,123,125,127,138,146,153,157,159,164,179,187,189,203,205,206,242,247,251,258,278,279,285,290,308,329,330,334],force_init:247,force_repeat:[102,116,259],force_restart:259,force_str:340,forcibl:[102,258],fore:305,forebod:187,foreground:[42,100,114,126,183,267,336,364],foreign:125,foreignkei:[148,246,256,315,318,335],forens:210,forest:[13,111,112,140,187],forest_meadow:112,forest_room:112,forestobj:140,forev:[61,102],forget:[3,9,10,13,25,27,33,41,54,62,72,82,85,86,95,96,100,123,131,206,322,364],forgo:232,forgotten:[28,49,77,85],fork:[9,79,364],forloop:69,form:[11,13,27,31,33,34,38,45,51,53,55,58,59,61,64,68,70,74,76,77,80,83,88,89,93,96,97,109,112,113,114,115,116,118,123,124,125,127,129,135,141,142,144,145,146,151,153,154,157,159,167,168,170,173,175,176,177,179,188,189,205,206,210,237,239,241,242,244,245,247,251,252,254,257,259,261,265,285,287,291,295,306,308,315,316,317,318,321,322,324,325,327,328,330,336,337,340,341,344,345,346,356,362,364],form_char:327,form_class:362,form_template_to_dict:188,form_url:145,form_valid:362,formal:[61,80,96,138,247,291],format:[0,14,17,19,22,23,27,31,33,37,38,41,42,46,48,55,58,62,68,69,76,79,81,83,88,96,98,103,108,109,111,113,114,119,124,129,131,133,138,152,154,156,159,166,168,170,174,175,180,182,183,184,188,198,206,209,215,219,230,234,235,239,247,249,251,252,257,267,272,282,287,307,309,316,318,321,322,324,326,328,329,330,331,336,337,339,344,345,363,364],format_attribut:159,format_available_protfunc:251,format_callback:192,format_diff:252,format_extern:175,format_help:234,format_help_entri:166,format_help_list:166,format_messag:175,format_output:159,format_send:175,format_t:344,format_text:180,format_usag:234,formatt:[188,251,328,329],formatted_list:175,formcallback:188,formchar:[58,327],formdata:188,former:[17,23,64,126,328],formfield:340,formhelptext:188,formset:315,formstr:58,formtempl:188,formul:134,forth:[27,131,159,220],fortress:111,fortun:[4,33,39,48,69,122,128],forum:[1,9,37,48,55,57,63,90,98,128,364],forward:[13,14,20,42,45,50,51,62,69,90,121,126,144,148,177,199,209,239,246,256,312,316,318,319,327,329,335],forwardfor:67,forwardmanytoonedescriptor:[246,256,335],forwardonetoonedescriptor:[246,256,335],foul:109,found:[2,4,6,9,10,13,14,15,20,22,23,25,27,31,33,38,39,40,41,42,49,51,55,57,58,59,63,68,73,74,76,78,80,83,85,89,90,91,97,103,104,109,112,116,119,122,123,125,127,128,134,135,137,138,141,144,147,149,150,151,152,154,159,167,168,171,175,179,180,192,194,195,206,233,239,242,245,247,250,251,252,255,258,261,266,267,273,282,285,296,306,308,316,317,318,321,322,323,328,330,334,336,339,341,344,346],foundat:[49,55,77,79,217],four:[4,14,27,38,39,40,68,73,82,86,87,111,114,119,153,177,187,242],fourth:39,fqdn:90,fractal:56,fraction:127,frame:[137,138],framework:[3,16,64,124,133,136,137,170,217,220,340,364],frankli:129,free:[0,22,29,37,48,55,57,60,61,64,76,77,79,90,106,112,116,123,124,126,130,133,139,179,206,215,218,251],freedn:90,freedom:[14,26,44,63],freeform:[73,116,182],freeli:[55,77,100,103,322],freenod:[9,63,70,72,79,90,146,164,308],freepik:79,freetext:[176,341],freez:[29,33,42,194],frequenc:205,frequent:[91,180],fresh:[11,31,58,128,267],freshli:111,fri:12,friarzen:138,friend:[37,58,61,82,103],friendli:[22,38,78,95,133,138,148],friendlier:[175,247],frighten:219,from:[0,2,3,5,6,8,9,10,11,12,13,14,15,16,17,19,21,22,23,26,27,28,29,30,31,33,34,35,36,37,38,39,40,41,42,44,46,47,48,49,50,52,54,56,57,58,59,61,62,63,64,66,67,68,69,70,71,72,73,74,75,76,79,80,81,82,83,84,85,86,87,89,91,92,93,95,97,98,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,134,135,136,139,140,141,142,144,146,147,148,149,150,151,152,153,154,156,157,158,159,164,165,166,167,168,169,170,171,173,174,175,176,177,179,180,181,182,183,184,185,186,187,188,189,194,195,198,199,202,203,204,205,206,209,210,211,212,213,215,217,218,219,220,221,224,226,227,231,232,233,234,235,238,239,241,242,243,245,246,247,251,252,255,256,257,258,259,260,261,264,267,272,273,274,276,277,278,279,280,284,285,286,287,290,295,296,299,301,305,306,307,308,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,329,330,331,334,335,336,337,338,340,341,343,344,345,357,362,363,364],from_channel:146,from_db_valu:340,from_nod:[51,328],from_obj:[81,83,118,144,146,154,189,247],from_pickl:325,from_tz:345,frombox:276,fromstr:276,fromtimestamp:331,front:[8,13,20,73,80,85,96,103,109,131,137,139,364],frontend:215,frozen:[29,33,122,195],fruit:203,ftabl:344,ftp:343,fuel:[21,220],fugiat:52,fulfil:267,full:[4,9,13,14,15,16,17,20,21,23,24,25,26,27,33,37,38,43,51,55,57,58,59,60,61,64,73,75,80,84,88,89,90,95,96,97,100,101,102,105,108,109,110,111,115,116,117,119,121,123,124,125,127,128,131,133,134,135,136,146,151,153,154,158,159,164,168,169,170,179,180,185,187,190,202,205,206,215,220,230,234,242,245,252,257,279,285,298,308,309,316,318,322,326,328,330,344,364],full_justifi:[109,250],full_nam:87,full_result:185,fullchain:67,fuller:58,fullfil:245,fullhost:67,fulli:[4,11,19,33,51,55,58,59,61,63,85,86,90,93,103,110,122,144,205,242,247,259,295,307,324,344],fun:[20,26,61,79,81,111,136],func1:[159,242,299],func2:[159,242,299],func:[5,10,21,22,25,28,29,30,33,38,42,44,50,51,56,58,60,62,71,73,80,81,82,83,85,91,116,119,121,123,150,154,156,157,158,159,164,165,166,167,168,169,170,171,174,179,180,181,182,184,185,186,187,188,189,193,199,202,203,206,212,213,214,215,217,218,219,220,221,224,231,232,233,234,241,242,247,278,299,303,312,326,328,329,331,344,362,364],funciton:220,funcnam:[74,114,242,250,261,328,336],functioncal:276,functionnam:[276,336],functool:63,fund:70,fundament:[33,57,77,89,95,96,112,247],furnitur:[13,112,125],further:[0,9,11,27,31,34,38,42,44,49,57,83,85,86,90,91,96,100,102,104,105,106,109,110,111,119,124,125,130,131,138,153,159,181,205,219,221,252,267,291,344,364],furthermor:[37,38,124,126],fuss:100,futur:[9,10,11,20,23,38,45,50,55,58,60,61,62,63,76,87,95,100,123,139,156,195,232,235,272,317,338,345,364],futurist:62,fuzzi:[76,147,238,245,341,344],fuzzy_import_from_modul:344,gadget:70,gag:24,gain:[11,29,61,73,93,154,177,206,242,247],galosch:205,gambl:185,game:[0,2,3,4,5,6,8,9,10,11,13,14,15,17,18,19,20,21,22,23,24,25,28,29,30,31,33,34,35,36,37,38,41,42,43,44,46,50,51,52,53,56,60,63,64,65,66,67,68,69,71,72,75,76,77,78,79,80,81,83,85,86,87,88,89,91,92,93,95,96,97,98,101,102,103,104,105,106,107,108,109,110,112,113,114,115,116,117,118,119,121,122,125,129,130,132,133,134,135,136,137,138,139,140,143,144,145,146,147,148,150,152,153,154,156,157,158,159,163,164,165,166,169,170,171,172,174,175,176,177,178,179,180,181,182,184,185,186,187,188,190,193,194,195,196,199,204,205,206,213,215,217,218,219,220,221,229,230,233,234,239,241,243,246,247,255,256,258,259,262,267,269,270,271,272,278,279,284,286,287,290,291,298,299,300,305,306,308,315,317,318,319,322,323,324,326,327,328,331,334,336,337,344,363,364],game_dir:[337,344],game_epoch:[27,331],game_index_cli:[141,142,262,364],game_index_en:54,game_index_list:54,game_nam:[54,350],game_slogan:[9,350],game_statu:54,game_templ:47,game_websit:54,gamedir:[51,100,109,267,313,364],gamedirnam:58,gameindexcli:270,gameplai:[90,145,364],gamer:[65,72],gamesrc:27,gametim:[27,53,59,139,141,142,184,187,195,320,364],gametime_to_realtim:184,gametimescript:184,gammon:[79,282],gandalf:51,gap:364,garden:79,garment:182,gatewai:[110,296],gather:[24,33,48,83,119,127,132,136,150,151,233,265,269,324,341],gave:[5,21,60,64,91,102,126],gbg:321,gcc:63,gear:[90,106,136,146,153,171,186],gemer:204,gen:17,gender:189,gendercharact:189,gendersub:[141,142,178,364],gener:[0,1,5,9,10,11,12,20,23,25,29,31,33,34,36,37,38,43,48,49,51,55,57,58,59,60,62,63,64,68,70,73,76,80,83,86,87,88,90,93,96,104,105,106,109,111,112,114,116,126,127,134,137,138,139,141,142,144,146,147,149,154,155,156,159,166,167,168,170,171,174,175,179,180,181,182,185,186,187,188,189,195,199,202,204,205,206,209,210,212,213,214,215,217,218,219,220,221,224,230,231,233,234,239,242,245,247,249,252,255,278,287,290,291,295,306,307,308,312,319,320,321,323,324,326,329,330,337,338,339,340,344,349,357,362,364],general_context:[141,142,346,348,364],generic_mud_communication_protocol:291,genericbuildingcmd:180,genericbuildingmenu:180,genesi:90,geniu:203,genr:[37,64,281],geoff:234,geograph:140,geographi:39,geoip:209,geometr:111,geometri:111,get:[0,1,2,3,5,6,7,8,9,10,11,12,13,15,17,21,22,23,25,26,28,29,30,31,33,38,39,40,41,42,43,44,45,46,47,48,49,50,54,55,56,57,58,59,60,61,62,64,65,68,69,71,72,73,74,75,76,77,80,81,82,83,84,85,86,87,88,90,91,92,93,95,96,97,100,102,103,104,105,106,107,110,111,112,114,116,118,121,122,123,125,126,127,128,130,131,133,134,135,136,137,138,139,144,146,147,148,152,153,154,156,157,159,160,164,165,171,173,174,176,177,180,182,185,192,194,195,198,199,203,204,206,213,214,215,217,218,219,220,221,223,224,232,233,235,238,239,241,242,245,246,247,249,251,252,255,256,258,259,261,265,267,272,276,277,281,285,287,290,291,293,295,296,304,306,307,308,310,316,317,318,319,321,322,323,326,328,330,331,333,334,336,337,338,339,341,344,357,362,363,364],get_abl:60,get_absolute_url:[134,175,239,318],get_account:[242,306],get_account_from_email:147,get_account_from_nam:147,get_account_from_uid:147,get_al:306,get_alia:317,get_all_cached_inst:334,get_all_categori:238,get_all_channel:176,get_all_cmd_keys_and_alias:152,get_all_mail:199,get_all_puppet:144,get_all_script:255,get_all_scripts_on_obj:255,get_all_sync_data:308,get_all_top:238,get_all_typeclass:344,get_and_merge_cmdset:153,get_attack:[217,218,219,220,221],get_attr:159,get_attribut:317,get_buff:326,get_by_alia:317,get_by_attribut:317,get_by_nick:317,get_by_permiss:317,get_by_tag:317,get_cached_inst:334,get_callback:195,get_channel:[41,176],get_charact:306,get_client_opt:[272,364],get_client_s:306,get_client_sess:[295,296],get_client_sessid:296,get_cmdset:174,get_command_info:[154,167],get_connected_account:147,get_cont:245,get_context_data:362,get_damag:[217,218,219,220,221],get_db_prep_lookup:340,get_db_prep_valu:340,get_dbref_rang:[147,245,255,317],get_def:260,get_default:340,get_defens:[217,218,219,220,221],get_display_nam:[22,42,46,58,206,235,247,318],get_err_msg:[6,20,80],get_ev:195,get_evennia_pid:344,get_evennia_vers:344,get_event_handl:198,get_extra_info:[41,154,174,247,318],get_famili:[119,125],get_fieldset:244,get_form:244,get_formset:315,get_game_dir_path:344,get_god_account:271,get_height:330,get_help:[33,68,69,154,170,193,234,328],get_help_text:311,get_id:[133,245,255,260,317],get_info_dict:[284,305],get_initi:362,get_input:[328,364],get_inputfunc:[88,272,291,308,364],get_internal_typ:340,get_kwarg:360,get_location_nam:235,get_mass:82,get_message_by_id:176,get_messages_by_channel:176,get_messages_by_receiv:176,get_messages_by_send:176,get_min_height:330,get_min_width:330,get_new:286,get_new_coordin:235,get_next_by_date_join:148,get_next_by_db_date_cr:[148,177,246,256,316,318],get_next_wait:198,get_nick:317,get_nicklist:[146,279],get_numbered_nam:247,get_obj_coordin:235,get_object:362,get_object_with_account:[245,341],get_objs_at_coordin:235,get_objs_with_attr:245,get_objs_with_attr_match:245,get_objs_with_attr_valu:245,get_objs_with_db_properti:245,get_objs_with_db_property_match:245,get_objs_with_db_property_valu:245,get_objs_with_key_and_typeclass:245,get_objs_with_key_or_alia:245,get_oth:179,get_permiss:317,get_pid:267,get_player_count:281,get_previous_by_date_join:148,get_previous_by_db_date_cr:[148,177,246,256,316,318],get_puppet:[2,144,306],get_puppet_or_account:306,get_queryset:362,get_rang:221,get_recently_connected_account:147,get_recently_created_account:147,get_redirect_url:362,get_regex_tupl:206,get_respons:351,get_room_at:39,get_rooms_around:39,get_sess:308,get_statu:277,get_subscript:176,get_success_url:362,get_sync_data:307,get_system_cmd:152,get_tag:317,get_time_and_season:187,get_typeclass_tot:317,get_uptim:281,get_username_valid:144,get_valu:[88,272,291,364],get_vari:[192,195],get_width:330,get_worn_cloth:182,getattr:84,getchild:312,getclientaddress:[40,287],getel:137,getenv:[267,277],getfromlock:241,getgl:137,getinput:328,getkeypair:287,getloadavg:75,getpeer:287,getpid:344,getsizof:334,getsslcontext:[288,292],getston:33,getter:[148,177,182,206,218,221,246,247,274,316],gettext:76,gfg:321,ghostli:233,giant:[21,124],gid:[45,100,299],gidcount:298,gif:70,gift:69,gist:[205,344],git:[9,23,25,36,38,45,47,63,75,76,79,86,90,100,108,124,128,130,364],github:[9,25,37,38,41,45,57,63,70,75,76,79,95,96,98,104,130,131,138,180,295,312,344,364],gitignor:131,give:[0,1,2,3,4,5,9,10,11,12,13,15,18,19,20,21,22,23,25,26,27,30,33,38,39,41,43,46,48,51,52,55,57,58,59,60,61,62,63,64,68,69,73,75,77,79,80,82,85,88,89,90,91,93,96,98,100,102,103,105,107,109,110,111,112,113,114,115,116,117,118,119,122,123,124,125,127,128,133,134,136,138,139,140,150,152,153,156,165,167,168,169,174,176,180,181,182,187,204,205,214,215,217,218,219,220,221,224,233,235,241,245,247,255,256,293,306,312,318,321,330,341,342,344,363,364],givelock:241,given:[0,2,4,10,11,12,13,14,20,21,22,25,27,31,33,34,38,39,42,46,49,50,51,58,62,64,70,73,74,80,83,84,85,86,88,89,90,93,97,100,102,105,109,110,113,114,115,116,117,119,122,123,125,126,127,131,133,134,135,138,140,144,150,151,152,153,154,156,157,159,164,166,168,169,170,175,176,177,180,181,182,184,185,186,187,188,189,190,192,194,198,203,204,205,206,212,215,217,218,219,220,221,232,233,234,241,242,245,247,249,250,251,252,255,257,258,259,261,265,267,272,273,276,285,290,291,296,299,302,306,307,308,309,311,312,316,317,318,319,321,322,324,325,326,327,328,329,330,331,334,336,337,339,340,341,342,344,349,362,364],given_class:359,giver:[218,221,247],glad:91,glanc:[22,27,31,33,39,48,58,61,91,96,180,206],glance_exit:22,glass:[203,224,226,227],glob:[51,165,328],global:[13,22,33,34,35,45,51,56,61,64,67,74,85,89,100,104,105,108,109,114,115,120,125,131,132,137,138,140,159,187,195,204,206,212,241,245,247,250,252,253,255,256,260,264,267,272,274,277,298,299,322,323,324,328,331,336,341,342,344,350,364],global_script:[102,141,323],global_search:[13,22,27,58,91,144,206,247,317],globalscript:[43,169],globalscriptcontain:323,globalth:342,globe:[90,136],gloss:61,glossari:[63,139,364],glow:111,glu:92,glyph:276,gmcp:[55,74,83,291,364],gmsheet:58,gmud:24,gno:22,gnome:24,gnu:14,go_back:[51,215,328],go_back_func:51,go_up_one_categori:215,goal:[38,61,76,79,91,102,103,122,124,205],goals_of_input_valid:357,goblin:[51,109,159,252],goblin_arch:252,goblin_archwizard:252,goblin_shaman:109,goblin_wizard:252,goblinwieldingclub:109,god:[20,80,271],godhood:364,godlik:206,goe:[0,5,9,22,26,29,33,37,40,42,49,64,69,73,75,86,90,95,96,118,121,122,123,139,152,153,221,235,247,287,290,305,306,343,344,362],goff:204,going:[0,3,20,25,26,40,45,46,49,51,58,61,62,65,69,70,82,88,90,91,95,96,100,111,116,121,127,133,138,139,180,206,217,218,219,220,221,230,233,235,247,264,269,321,328],goings:269,gold:[51,82,85,109,322],gold_valu:85,golden:138,goldenlayout:[138,364],goldenlayout_config:[137,138],goldenlayout_default_config:[137,138],gone:[5,12,77,80,85,100,102,131,259],good:[0,2,4,5,9,11,12,14,20,21,22,25,26,27,31,33,37,38,39,40,41,46,48,49,51,54,55,56,57,60,61,63,69,70,72,73,79,80,85,87,90,91,93,95,96,97,100,102,103,104,106,109,110,111,114,119,121,123,125,126,127,131,133,134,138,144,152,153,154,170,179,194,206,290,328],goodby:287,goodgui:242,googl:[38,75,79,90,164,330,364],googleusercont:70,googli:136,gossip:[65,79,164],got:[10,13,95,96,116,128,138,215,232],goto_cal:[51,328],goto_cleanup_cmdset:230,goto_command_demo_comm:230,goto_command_demo_help:230,goto_command_demo_room:230,goto_kwarg:328,goto_next_room:121,goto_node2:51,goto_str_or_cal:51,gotostr_or_func:328,gotten:[55,95,131,221,232,247,294],graaah:117,grab:[20,33,43,73,133,165,175,232,362],gracefulli:[26,156,169,206,247,267],gradual:[13,14,29,61,79,96,205],grai:[114,126],grain:[115,324],gram:82,grammar:205,grammat:205,grand:11,grant:[19,23,80,131,177,217,218,219,220,221,241,242,251,316,364],granular:221,grapevin:[7,139,141,142,146,164,262,275,364],grapevine2chan:[43,65,164],grapevine_:164,grapevine_channel:[65,146,164],grapevine_client_id:65,grapevine_client_secret:65,grapevine_en:[65,164],grapevinebot:146,grapevinecli:278,graph:[49,131],graphic:[42,58,80,83,84,93,111,128,135,141,186,190,291],grasp:[126,133],grave:60,grayscal:183,great:[0,4,14,16,21,22,29,37,39,51,57,61,69,70,73,77,79,91,95,107,108,123,127,131,134,180,188,312],greater:[22,31,73,80,97,105,119,241,328],greatli:78,greek:15,green:[31,80,109,114,126,131,159,169,232],greenskin:252,greet:[9,35,46,95,104,105,117],greetjack:87,greg:79,grei:[109,126],grenad:89,grep:[75,131],greyscal:114,greyskinnedgoblin:109,griatch:[21,70,86,119,179,181,183,184,185,186,187,189,199,202,205,206,212,213,214,230,232,327,334,340,343,364],grid:[7,16,111,123,139,221,235,364],grief:12,griefer:134,grin:[33,41],grip:38,gritti:33,ground:[20,21,55,111],group:[4,9,10,12,19,21,26,33,37,41,43,46,55,64,68,70,79,91,100,102,109,112,125,127,139,140,145,148,155,159,165,176,187,203,232,233,247,251,252,276,315,316,319,321,324,364],grow:[13,25,26,61,63,79,110,278,279,330,344],grown:[9,25,51,129],grudg:73,grumbl:60,grungies1138:[199,214],grunt:[159,252],gthi:81,guarante:[11,37,61,67,80,86,90,102,185,195,251,306,318],guard:51,guess:[15,22,46,50,69,91,103,113,138,180,252],guest1:66,guest9:66,guest:[7,53,80,139,144,364],guest_en:[66,80],guest_hom:[66,133],guest_list:66,guest_start_loc:66,guestaccount:112,gui:[45,57,83,137,199,364],guid:[36,37,45,81,95,96,128,133,136,364],guidelin:[37,38,79],guild:[79,86,112,118],guild_memb:51,gun:[21,77],guru:55,habit:56,habitu:115,hack:[55,73,116,276],hacker:[79,103],had:[8,9,14,15,19,20,21,29,31,37,55,61,90,95,96,100,102,119,123,128,135,138,158,182,232,251,252,256,259,267,318,322,329,357],hadn:[61,62,131],half:[108,138,239],hall:49,hallwai:49,halt:[102,111],hand:[1,15,37,40,51,55,56,57,58,61,70,73,87,89,96,105,108,119,134,154,165,167,168,169,179],handi:[42,75,119,133,219],handl:[0,2,4,5,7,8,9,11,13,15,22,24,27,33,34,37,40,41,44,47,49,50,51,53,55,56,60,61,62,64,67,68,74,75,80,83,85,86,87,88,89,91,93,95,97,100,104,105,108,115,116,117,124,125,126,128,129,131,132,137,138,139,144,146,149,150,152,153,159,160,164,165,168,174,179,186,187,195,198,206,210,212,214,215,217,218,219,220,221,226,232,233,234,236,246,247,250,251,252,256,257,260,264,267,271,272,276,277,279,280,287,290,291,294,296,298,307,308,315,316,318,321,322,324,325,326,328,329,330,331,334,343,344,351,364],handle_egd_respons:269,handle_eof:287,handle_error:[195,260],handle_ff:287,handle_foo_messag:[51,328],handle_int:287,handle_messag:[51,328],handle_message2:51,handle_numb:[51,328],handle_quit:287,handle_setup:271,handler:[2,11,31,33,41,47,64,73,80,83,84,86,87,89,102,104,105,112,115,125,139,144,150,153,168,172,174,177,179,192,195,196,198,206,231,235,241,242,246,247,252,257,258,260,261,272,284,285,305,306,308,314,315,316,318,319,323,327,328,329,338,339,344,364],handlertyp:319,handshak:[24,52,83,277,283,285,290],handshake_don:290,hang:[3,38,61,70,124],hangout:119,happen:[0,6,12,19,20,26,27,31,33,37,39,41,42,44,51,54,55,57,58,60,61,62,64,72,73,77,80,83,86,88,90,91,95,96,97,102,105,107,108,110,111,114,115,116,119,122,123,126,127,128,131,133,138,144,152,153,164,175,184,213,217,218,219,220,221,227,231,233,235,247,250,252,260,269,276,279,299,304,306,307,308,318,328,329,334,336,337,344],happend:252,happi:[13,119],happier:91,happili:96,haproxi:[90,139,364],hard:[9,10,11,13,15,19,26,27,31,33,38,40,41,58,61,63,64,76,79,88,90,93,96,97,100,102,109,112,115,119,121,127,131,133,138,139,168,188,215,256,267,316,318,328,364],hardcod:[57,58,77,100,111,140,316],harden:63,harder:[12,56,61,93,119,127,232],hardwar:[90,280],hare:79,harm:[11,29,219],harmless:255,harri:59,harvest:362,has:[0,2,4,8,9,10,11,12,13,14,15,16,19,20,21,22,23,25,27,28,29,31,33,34,36,37,38,39,40,41,42,44,46,47,49,50,51,53,54,56,57,58,59,60,61,62,63,64,65,67,68,69,70,71,74,75,76,77,78,79,80,83,85,86,87,88,89,90,91,93,95,96,97,100,101,102,103,104,105,107,109,110,112,113,114,115,116,117,118,119,121,122,123,125,126,127,128,129,131,132,133,134,135,136,137,138,139,143,144,145,146,151,152,153,154,156,158,159,164,167,168,169,170,171,174,175,176,179,180,184,185,186,187,188,195,199,203,204,206,215,217,218,219,220,221,223,231,232,233,234,235,239,241,242,245,246,247,251,252,256,259,260,261,267,269,271,272,276,279,281,285,289,294,295,299,305,306,307,308,310,315,316,317,318,324,326,327,328,330,334,336,337,338,341,344,357,360,362],has_account:[89,231,241,246,247],has_attribut:316,has_cmdset:153,has_connect:[41,175],has_drawn:49,has_nattribut:[306,316],has_nick:316,has_par:344,has_perm:[167,242],has_sub:175,has_thorn:11,hasattr:[28,33],hash:[14,90,109,252,261,295,299,308,317],hasn:[22,49,204,232,315,362],hassl:62,hast:219,hat:[37,70,182],hau:[65,146,164,278],have:[0,1,2,3,4,5,6,9,10,11,12,13,14,15,16,19,20,21,22,23,25,26,27,28,29,30,31,33,34,35,36,37,38,39,40,41,42,44,46,47,48,49,50,51,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,80,81,82,83,84,85,86,87,88,89,90,91,92,93,95,96,97,98,100,102,103,104,105,106,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,144,146,150,152,153,154,156,159,161,164,167,168,169,170,171,175,176,177,179,180,181,182,184,186,187,188,189,194,195,198,202,204,205,206,209,210,215,217,218,219,220,221,224,227,233,234,238,239,241,245,246,247,250,251,252,253,255,256,258,259,260,261,272,277,280,281,285,287,290,291,305,306,307,308,310,313,314,315,316,317,318,319,321,322,323,324,325,327,328,330,336,337,340,341,342,344,345,357,362,363,364],haven:[4,22,29,42,62,67,77,109,111,117,118,120,127,128,133,134,138,310],hdict_cmd:166,hdict_db:166,head:[20,21,31,46,69,76,77,96,106,119,121,123,138,139,364],headach:[61,138],header:[9,13,14,27,34,37,38,63,89,95,103,129,138,154,175,177,199,206,247,291,322,324,329,330],header_color:159,header_line_char:330,headi:330,heading1:[38,330],heading2:[38,330],heading3:38,headless:[96,247],headlong:63,heal:[219,220,233],healing_rang:220,health:[30,61,73,84,88,90,109,116,190,252,291],health_bar:[141,142,178,364],hear:[29,46,61],heard:[111,122,241],heart:126,heartbeat:[115,278],heavi:[6,11,20,23,27,33,64,73,80,82,96,116,123,179,206,218,280,344],heavier:218,heavili:[9,27,37,40,57,75,86,104,180,217,218,219,220,221,318],heed:[105,242],heh:138,hei:[20,179,199],height:[52,74,137,141,154,272,287,306,327,330],held:[1,31,48,116,241],hello:[0,29,34,41,46,51,72,74,83,87,88,91,96,105,108,123,129,165,174,206,272,321,364],hello_funct:95,hello_valu:108,hello_world:[95,96,108],helmet:[29,77],help:[0,1,4,5,12,13,14,15,19,22,23,27,29,32,33,35,38,39,41,42,43,44,45,46,47,48,49,50,51,53,57,58,60,61,63,64,67,71,72,76,77,79,80,86,90,91,93,96,105,107,108,109,110,111,112,113,116,119,122,123,124,126,127,131,133,137,138,139,141,142,149,150,152,154,155,156,167,168,170,171,177,179,184,186,188,192,193,195,199,205,209,217,218,219,220,221,224,230,233,234,241,245,249,260,265,267,269,270,278,285,287,288,290,292,295,296,298,299,306,317,321,324,325,326,328,329,336,339,340,341,342,351,357,362,363,364],help_categori:[5,22,33,41,58,60,68,69,71,85,116,123,154,156,157,158,159,164,165,166,167,168,169,170,171,174,179,180,181,182,185,186,187,188,189,193,199,202,203,206,212,213,214,215,217,218,219,220,221,224,231,232,233,234,238,239,247,326,328,329,341],help_cateogori:326,help_detail:362,help_entri:326,help_kei:159,help_list:362,help_mor:166,help_system:69,help_text:[166,195,357],helpact:234,helpdetailview:362,helpentri:[69,80,237,238,239,324,362],helpentry_db_tag:237,helpentry_set:319,helpentryadmin:237,helpentryform:237,helpentrymanag:[238,239],helper:[19,41,51,58,67,80,109,119,141,144,153,156,159,166,173,176,180,184,205,247,251,252,264,276,277,296,308,322,328,329,337,342,343,344],helpfil:166,helplistview:362,helpmixin:362,helptaginlin:237,helptext:[51,249,328],helptext_formatt:[51,249,328],henc:[0,22,46,76,95,106,233,234,241,322],henceforth:[13,44,60,66,80,90,95,97,102,105,111,123,131,132,140,308],henddher:203,her:[122,127,182,189],herbal:327,herd:23,here:[0,2,3,4,5,9,10,11,13,14,15,16,17,19,20,21,22,23,24,25,27,29,30,33,36,37,38,39,40,41,42,43,44,46,47,48,49,51,53,56,57,58,59,61,62,63,64,65,67,69,70,71,72,73,74,75,76,77,79,80,81,83,84,85,86,87,88,89,91,92,95,98,100,101,102,103,104,105,106,107,108,109,110,111,113,114,115,116,117,118,119,120,121,123,125,126,127,128,129,130,131,133,134,135,136,137,144,146,152,153,154,159,167,168,169,171,175,179,180,181,182,184,185,186,194,195,204,205,206,213,217,218,219,220,223,224,227,231,232,233,234,235,239,242,245,247,251,252,259,267,269,276,278,284,285,287,290,305,306,308,314,315,316,318,321,324,328,330,334,336,346,362,364],hesit:[22,39],hfill_char:330,hidden:[11,49,61,64,96,122,131,137,177,182,185,234],hide:[9,11,20,31,33,34,41,61,73,80,96,111,138,166,177,185,206,224,232],hide_from:[34,177],hide_from_accounts_set:148,hide_from_channels_set:177,hide_from_objects_set:246,hieararci:241,hierarch:[2,19,80,156],hierarchi:[4,19,22,43,61,66,69,80,119,139,165,182,241,364],high:[4,8,20,31,55,63,80,122,152,220,247,309],higher:[7,19,25,31,41,44,51,56,58,62,63,73,80,90,105,108,119,123,128,144,152,156,169,205,217,218,219,220,221,233,241,269,328,344],highest:[31,58,321,344],highest_protocol:340,highli:[9,17,51,55,56,64,80,86,107,115,117,190,322,334],highlight:[14,38,57,58,114,126],hijack:134,hilight:343,hilit:343,hill:87,him:[41,46,51,189,206],hint:[1,25,38,55,63,79,93,95,109,110,123,124,128,136,139,184,313,364],hire:[85,103],his:[46,51,58,77,96,109,127,182,189,206,329,343],histogram:344,histor:[62,129,266,337],histori:[4,23,34,41,50,58,64,95,100,131,137,138,139,153,174,188,337],hit:[6,9,21,29,52,61,73,116,119,122,131,146,217,218,219,220,221,231,232,265,306,337,340],hit_msg:231,hite:114,hmm:138,hnow:114,hobbi:[61,90],hobbit:62,hobbyist:90,hoc:55,hold:[2,6,9,13,14,16,21,26,31,34,36,38,41,47,49,51,58,61,63,64,66,73,77,80,85,89,96,97,100,102,104,105,106,109,111,112,114,116,119,123,125,131,133,136,140,152,153,178,180,182,185,204,214,215,217,218,219,220,221,229,231,232,236,241,242,251,252,253,257,262,274,276,285,295,296,298,308,318,319,320,324,327,328,330,332,337,344,346],holder:[9,69,90,306],home:[8,16,26,43,63,64,66,70,79,89,90,103,109,131,133,139,153,159,165,231,241,245,246,247,252,324,344],home_loc:159,homepag:[27,63,79,90,93],homes_set:246,homogen:[27,164,251,252,256],homogenize_prototyp:251,honor:206,hood:[20,33,51,57,60,61,64,86,87,119,122,125,128,206,234],hook:[2,25,30,33,49,55,60,61,73,74,76,80,81,89,96,102,107,110,115,116,117,118,120,121,123,127,132,144,150,152,154,156,159,165,167,169,171,173,175,182,187,195,203,204,206,210,217,218,219,220,221,228,230,231,232,233,235,244,247,254,256,259,261,271,278,290,293,295,303,305,306,307,309,318,326,329,334,335,338,342,344,357,362,364],hooligan:12,hop:55,hope:[42,58,91],hopefulli:[8,26,41,49,90,111,133,137],horizon:62,horizont:[138,232,330],hors:27,host1plu:90,host:[7,12,23,26,27,61,64,67,89,98,100,102,103,131,135,205,312,344,364],host_os_i:344,hostnam:67,hotbutton:137,hotel:90,hotspot:103,hour:[27,62,132,184,331,344],hous:[90,109,159,364],housecat:27,hover:138,how:[0,1,3,4,5,6,7,8,10,11,12,13,14,15,17,19,20,21,22,25,26,27,28,29,30,31,35,37,38,39,40,41,42,43,44,45,46,48,49,51,55,56,57,60,61,62,63,64,66,68,69,72,73,75,77,80,81,82,83,84,85,86,87,88,90,91,93,95,96,97,102,103,104,105,106,108,109,110,111,112,116,117,118,119,120,123,124,126,127,128,130,131,132,133,134,135,136,137,138,139,140,145,146,147,151,153,154,168,169,170,173,174,175,180,182,184,185,189,204,205,206,213,215,219,220,221,226,231,235,237,241,246,247,252,255,256,261,267,272,277,281,286,291,294,298,305,306,307,308,312,315,318,322,326,328,329,330,337,338,343,344,357,363,364],howev:[0,2,4,5,10,11,12,13,14,15,17,20,22,23,29,30,31,33,37,38,40,41,44,46,50,55,58,59,60,62,70,73,77,80,85,88,90,91,108,109,110,111,113,114,115,120,123,125,128,129,131,132,135,153,154,159,166,169,170,180,188,190,195,204,215,220,227,241,321],howto:38,hpad_char:330,href:[17,69,133],hrs:184,htm:282,html5:55,html:[24,38,55,64,69,79,83,96,103,114,134,135,136,137,138,145,169,175,204,234,239,289,291,295,296,312,318,340,343,362,364],htmlchar:343,htop:110,http404:[69,134],http:[3,4,9,22,23,36,37,38,45,54,55,63,65,69,70,75,83,90,95,98,103,104,107,108,124,128,130,131,133,134,135,137,138,141,146,164,180,204,234,269,276,278,279,280,281,282,283,289,291,294,295,296,312,321,330,343,344,357,364],http_request:[103,135],httpchannel:312,httpchannelwithxforwardedfor:312,httpd:8,httprequest:144,httprespons:[145,173,244],httpresponseredirect:133,huawei:90,hub:[79,100,139,324],hue:114,huge:[3,16,21,29,39,61,62,86,127,235,329],huh:[22,33],human:[4,12,40,51,57,61,64,73,85,93,96,117,133,362],humanizeconfig:4,hundr:[72,113,133],hungri:86,hunt:[73,231],hunting_pac:231,hunting_skil:73,hurdl:49,hurt:30,huzzah:9,hwejfpoiwjrpw09:9,hybrid:73,i18n:[47,76],iac:88,icon:[79,106,138],id_:[145,237,244,357],id_str:84,idcount:298,idea:[0,9,12,26,33,37,38,39,45,49,55,56,60,61,63,69,71,72,73,77,80,85,106,107,108,119,121,123,127,131,133,134,139,154,166,167,170,179,205,252,334,343,362,364],ideal:[1,6,33,37,46,48,90,129,138,148,242],idenfi:152,ident:[9,31,33,44,57,61,83,96,97,110,114,144,167,168,206,212,242,245,247,255,321,322],identif:[27,115,308],identifi:[0,8,23,28,30,31,33,39,41,42,49,50,51,58,61,69,74,83,84,88,93,97,102,109,115,116,119,125,134,138,151,154,159,164,167,168,170,174,176,180,187,205,206,215,233,242,247,251,255,258,261,264,267,272,274,277,291,295,304,306,308,316,317,321,326,327,328,336],identify_object:176,idl:[12,105,144,146,231,247,299,306,308],idle_command:33,idle_tim:[144,247],idle_timeout:146,idmap:334,idmapp:[86,125,141,142,169,177,239,274,300,316,317,318,320,364],idnum:176,ids:[12,58,121,187,298,308,327],idstr:[84,115,257,261,304],idtifi:176,idx:121,ietf:283,ifconfig:67,ifram:[137,138],ignor:[6,14,20,23,27,29,31,33,34,38,42,51,58,73,74,80,83,86,90,91,95,96,105,114,117,121,122,125,131,144,151,152,153,154,159,175,187,206,241,246,247,261,267,272,278,279,294,295,296,318,321,322,327,328,336,339,344,345],ignore_error:144,ignorecas:[159,165,166,171,174,182,321,326,343],ignoredext:312,illumin:111,illus:[10,96],imag:[4,17,63,69,70,90,101,106,133,135,136,137,138,364],imagesconfig:4,imagin:[14,29,31,46,48,51,61,77,116,117,122,132,138,322],imaginari:[21,79],imc2:34,imeplement:235,img:[17,70],immedi:[0,5,15,27,29,33,48,49,51,64,70,74,83,90,95,100,102,109,116,120,133,134,150,157,169,231,278,322,324,328,329],immobil:25,immort:231,immut:[11,261],imo:1,impact:126,impati:63,imper:102,implement:[1,6,11,21,25,26,28,29,31,33,34,37,40,41,49,51,55,56,57,58,60,61,78,79,80,81,86,88,89,96,97,108,111,112,114,115,116,117,118,119,120,123,124,125,127,128,131,135,137,138,139,140,145,147,148,152,153,156,157,158,159,160,161,164,165,166,167,168,169,171,176,177,179,181,182,184,185,187,189,202,205,206,210,212,213,214,215,217,218,221,224,231,232,233,235,238,239,241,242,245,246,247,255,256,258,261,273,278,280,281,282,283,284,285,287,289,290,291,294,295,296,298,305,312,316,317,318,319,321,322,325,326,328,329,335,336,339,340,343,344,362,364],impli:[22,112],implicit:[91,114,126],implicit_keep:252,impmement:242,import_cmdset:153,importantli:[51,133,242],importerror:[4,9,344],impos:[55,79,310],imposs:[15,19,38,49,51,90,111,113,121,133,138,330],impract:[33,109,252],imprecis:334,impress:[42,111],improv:[0,11,37,61,70,76,91,128,364],in_game_error:[26,103],in_templ:[316,336],inabl:[63,103],inaccess:[0,80],inact:[102,231],inactiv:169,inadvert:221,inadyn:90,inarticul:108,inbuilt:[67,112,123],incant:75,incarn:357,incid:210,includ:[2,4,6,9,12,13,16,20,21,22,27,30,31,33,36,37,38,39,41,43,44,48,51,53,55,58,60,61,62,63,64,69,73,74,75,78,79,80,84,85,88,89,91,93,95,96,100,101,102,104,105,106,107,108,109,111,112,114,115,116,119,121,125,127,131,133,134,135,136,137,138,144,150,151,152,154,157,158,159,167,168,170,174,179,182,187,188,189,195,205,206,210,215,217,218,219,220,221,224,227,233,234,235,241,247,267,285,287,290,291,304,307,316,317,318,319,322,323,324,325,327,328,330,331,337,344],include_account:316,include_children:317,include_par:317,include_prefix:151,include_unloggedin:[285,308],inclus:[317,336],incoher:126,incol:[58,327,330],incom:[33,40,88,90,96,104,139,145,146,151,168,173,210,218,244,254,276,280,283,286,290,291,295,296,298,306,307,308,312,328,329,336],incomplet:[154,213],inconsist:[10,97,204],incorpor:[156,330],incorrect:176,increas:[25,62,73,80,103,114,119,125,179,218,220,221,233,279,285,299,326,328],increase_ind:326,incred:[215,269],increment:63,incur:27,indata:[40,316],inde:[9,55,90,91],indefinit:[102,219,232,324],indent:[0,9,13,14,27,38,50,51,57,60,95,129,137,296,322,326,328,344],independ:[0,56,64,102,126,179,209],indetermin:269,index:[7,38,49,56,61,68,79,85,86,90,108,121,135,136,151,165,179,215,232,245,265,269,270,312,319,321,329,330,357,360,362,364],index_to_select:215,indexerror:[134,235,317],indextest:360,indic:[0,8,22,38,49,51,62,64,85,91,95,111,119,146,159,166,167,168,189,210,215,256,278,279,287,294,295,308,310,312,322,328,329,344],individu:[0,11,13,14,18,21,22,33,34,41,46,48,49,55,57,58,59,71,73,78,85,88,90,96,109,111,132,153,157,174,185,192,195,220,227,241,249,250,252,306,319,321,330,336,338,339],ineffici:[115,117,321],infact:33,infinit:[0,61,63,146,235,251],inflict:[102,219],inflict_condit:219,influenc:[10,16,22,46,51,102,123,179,344],influenti:79,info1:214,info2:214,info3:214,info:[3,5,11,13,16,17,20,23,24,25,26,27,33,35,37,43,52,55,58,59,63,64,68,78,86,88,89,95,100,101,102,104,105,106,112,124,125,131,138,139,144,146,148,156,157,159,169,171,175,178,179,181,186,187,190,199,233,239,247,267,272,276,284,285,305,306,308,317,318,319,324,327,337,344,364],infomsg:337,inforamt:[206,235,247,318],inform:[0,2,3,6,8,9,18,20,22,23,25,27,28,33,34,36,38,41,46,48,51,55,60,65,66,68,69,73,83,84,85,86,91,95,96,100,102,103,104,105,109,112,114,116,117,119,120,123,124,127,131,132,133,134,135,136,137,138,139,144,146,154,157,159,164,165,169,174,177,180,185,204,206,210,211,219,220,221,239,247,259,267,272,281,282,283,285,294,307,308,317,318,321,324,326,337,344,357,364],infrastructur:[38,64,83,90,103,150,277,364],infrequ:46,ing:[9,14,58,185],ingame_python:[141,142,178,364],ingame_tim:62,ingo:[31,51,58,74,114,152,245,279,336,364],inher:[4,10,87,108],inherit:[2,5,6,22,27,30,31,33,36,40,42,57,60,64,69,81,86,89,96,102,109,114,117,119,123,125,127,148,152,154,159,167,169,170,175,177,179,180,182,187,189,203,206,213,217,218,219,220,221,230,231,233,234,243,246,247,252,256,258,307,314,317,318,326,329,330,334,342,344,362,364],inheritng:252,inherits_from:[117,134,169,344,364],inifinit:251,init:[6,9,22,38,40,47,49,58,60,63,75,83,95,104,106,131,137,138,179,180,188,224,246,255,258,267,285,286,296,308,344],init_delayed_messag:188,init_django_pagin:329,init_evt:329,init_f_str:329,init_fill_field:188,init_game_directori:267,init_iter:329,init_menu:230,init_mod:[153,255,258],init_new_account:344,init_pag:[251,329],init_pars:234,init_queryset:329,init_rang:221,init_sess:[40,307],init_spawn_valu:251,init_str:329,init_tree_select:215,init_tru:153,initi:[5,9,11,21,29,33,38,47,49,50,51,58,60,61,64,68,73,85,97,105,107,110,120,123,127,130,131,133,137,138,144,145,146,153,154,170,174,175,177,179,186,188,192,196,198,205,206,215,217,218,219,220,221,230,231,232,237,244,245,246,247,251,255,257,260,261,264,265,267,269,270,271,276,277,278,280,281,282,283,285,286,287,288,289,290,291,292,294,295,296,298,306,307,308,315,316,321,323,326,327,328,329,336,339,340,344,351,357,362,364],initial_formdata:188,initial_ind:330,initial_setup:[141,142,262,305,364],initialdelai:[264,278,279],initialize_for_combat:[217,218,219,220,221],initialize_nick_templ:[316,336],initil:295,inject:[96,103,306,328],inlin:[18,57,85,104,137,145,173,237,244,254,265,315,336,364],inlinefunc:[45,83,104,109,141,142,250,308,320,364],inlinefunc_en:[114,336],inlinefunc_modul:[114,336],inlinefunc_stack_maxs:114,inlinefuncerror:336,inlinefunct:[114,336],inmemorysavehandl:339,inner:77,innoc:[12,157],innocu:103,inobject:276,inp:[51,159,176,251,265,329,344],inpect:51,input:[1,5,9,10,14,15,17,20,22,27,30,31,40,41,50,53,55,57,58,70,74,79,83,87,91,95,96,104,105,109,110,111,113,114,115,118,127,131,133,135,137,138,144,149,150,151,154,159,164,166,167,168,169,170,174,176,180,185,188,205,206,210,215,220,232,238,247,250,251,252,265,272,276,287,295,306,308,316,317,319,326,327,328,329,330,336,338,340,344,345,357,364],input_cmdset:328,input_func_modul:[74,272],input_str:328,input_validation_cheat_sheet:357,inputcmdset:328,inputcommand:[74,83,88],inputcompon:137,inputdebug:[74,272],inputfunc:[40,45,104,139,141,142,146,262,295,306,308,364],inputfunc_nam:295,inputfunct:74,inputhandl:141,inputlin:[87,165,316,317],insecur:90,insensit:[51,174,187,206,233,245,317,349],insert:[13,14,25,38,50,51,58,64,71,87,96,109,114,138,153,189,202,250,322,328,330,336,344],insid:[0,5,10,11,13,15,19,20,21,23,25,27,28,31,33,38,42,46,47,51,57,59,63,64,67,68,69,71,72,73,80,82,83,85,86,88,89,91,92,93,95,96,100,102,105,106,108,109,110,111,114,117,121,123,125,127,132,133,134,135,136,139,141,146,169,180,187,190,194,195,206,231,233,235,241,246,247,250,267,284,305,312,322,323,326,336,344,364],inside_rec:241,insiderecurs:241,insight:[20,41,42,122,136],insist:[90,91],inspect:[12,23,51,85,144,159,169,179,265,267,328],inspectdb:86,inspir:[33,41,73,116,127,129,181,189,330,344],instac:[154,247,306],instal:[0,3,5,14,20,26,37,38,41,42,46,47,54,55,57,58,59,60,64,65,76,77,79,95,96,97,98,101,103,106,108,110,124,127,128,130,134,138,139,141,179,181,182,183,185,186,187,199,202,203,206,210,212,213,217,218,219,220,221,363,364],installed_app:[4,69,86,127,133,134],instanc:[0,2,3,8,11,16,17,22,25,27,28,29,39,41,42,46,50,51,56,57,58,59,60,61,62,64,69,76,84,85,91,95,96,97,102,103,105,107,109,116,119,121,126,127,131,136,137,144,145,148,150,151,152,153,154,163,166,168,169,173,175,177,180,195,198,204,215,234,235,237,239,244,246,247,251,252,254,255,256,260,261,264,267,276,277,278,279,280,281,282,283,285,289,290,294,298,299,307,308,312,315,316,318,319,321,324,325,328,330,334,335,340,344,345,357,364],instanci:180,instant:136,instanti:[33,86,127,144,153,170,224,258,261,284,305,308,327],instantli:315,instead:[0,3,6,9,10,11,12,14,16,19,20,21,22,23,25,26,27,29,30,31,33,34,37,38,39,41,46,48,49,51,57,58,60,62,63,64,67,79,80,83,84,85,86,89,90,91,93,95,96,100,102,103,104,105,106,109,110,111,112,114,116,117,118,119,121,123,125,126,127,128,131,132,133,134,135,136,138,139,144,146,153,154,156,157,159,161,164,168,169,171,180,185,186,188,198,206,213,215,217,218,219,220,221,226,230,232,234,235,241,242,245,247,252,261,267,295,296,306,310,315,316,318,319,324,328,329,334,337,339,340,341,344,357,362],instig:157,instil:[140,219],instnac:260,instr:[276,344],instruct:[0,8,9,13,14,23,27,30,37,38,42,46,47,55,57,58,60,61,63,67,74,75,77,79,83,85,90,93,96,97,100,106,119,124,131,139,144,154,169,206,210,252,261,264,267,277,279,285,290,291,295,296,298,306,308,328,338,364],integ:[25,31,33,39,85,91,105,109,114,123,125,151,182,184,185,188,217,218,219,220,221,233,241,247,250,317,336,340,344,345],integerfield:[133,357],integr:[4,7,41,45,61,64,76,79,103,134,137,139,170,206,270,272,328,364],intellig:[73,83,91,103,134,153,298],intend:[13,17,20,22,27,31,33,34,37,42,55,61,90,103,108,109,111,112,114,122,126,131,136,137,144,164,179,180,206,227,239,247,252,285,317,319,324,325,327,330,336,341,342,344,345,362],intens:[79,93,114],intent:[51,76,96,103,205,344],inter:13,interact:[2,20,23,29,33,38,40,42,43,51,55,56,59,61,77,79,100,106,108,110,116,122,133,138,141,158,221,226,267,284,322,337,344,364],intercept:308,interchang:[116,328,362],interest:[0,1,4,11,14,20,21,22,26,33,37,40,42,46,49,55,57,60,61,70,79,86,90,91,93,96,103,109,114,119,120,121,123,136,153,168,179,184,233,235],interf:63,interfac:[9,21,22,23,25,36,40,42,63,64,69,70,79,80,90,96,97,101,104,119,133,135,137,138,139,156,159,173,175,245,247,259,278,307,312,319,321,362,364],interfaceclass:287,interfer:[23,97,251],interim:[29,115],interlink:[284,305],intermediari:[206,242,257,328],intern:[10,11,15,27,34,40,51,63,76,80,87,88,90,100,102,103,104,105,107,109,110,112,113,116,128,144,146,174,177,186,189,206,235,245,247,251,258,295,296,316,318,319,321,325,328,330,336,344,364],internal:328,internal_port:90,internation:[7,113,139,364],internet:[10,12,16,33,40,63,67,72,90,103,124,157,264,269,277,278,279,287,290,298,312],interpret:[33,42,56,59,60,91,93,96,102,103,104,109,134,154,158,159,251,252,295,321,336,340,364],interrupt:[63,150,154,170,192,195,198,287],interruptcommand:[33,91,141,150,154],interruptev:198,intersect:[31,152],interv:[64,74,102,115,116,120,121,132,146,147,184,195,217,218,219,220,221,223,226,227,231,233,250,255,256,259,261,272,324,331],interval1:261,intim:[31,33],intimid:58,intoexit:159,intpropv:123,intricaci:62,intrigu:54,intro:[4,69,122,124,134,230,233],intro_menu:[141,142,178,229,364],introduc:[26,29,31,57,73,97,123,124,127,131,139,206],introduct:[3,13,14,15,18,19,20,45,60,61,63,124,127,131,139,180,363,364],introductori:[55,63],introroom:233,introspect:203,intrus:126,intuit:[22,51,61,86,91,131,139,152],intxt:27,inv:[31,43,82,165,182],invalid:[11,41,60,91,109,144,188,206,227,251,330,340,344,345],invalid_formchar:327,inventori:[20,21,25,27,31,43,80,85,91,97,119,138,165,182,206,241,247,318,364],invers:[80,114,126,206,293,343],invert:[114,126],invis:24,invit:[0,10,61,77],invitingli:20,invok:[11,13,14,102,209,241],involv:[40,56,61,68,75,80,89,105,107,116,123,188,221,318,319,321],ioerror:322,ipregex:157,ipstart:[63,100,110],iptabl:103,ipython:[26,58,59,96],irc2chan:[43,72,164],irc:[7,9,26,34,55,60,63,70,79,98,131,138,139,141,142,146,164,172,262,272,275,285,308,363,364],irc_botnam:146,irc_channel:146,irc_en:[72,164,241],irc_network:146,irc_port:146,irc_rpl_endofnam:279,irc_rpl_namrepli:279,irc_ssl:146,ircbot:[146,279],ircbotfactori:[146,279],ircclient:[279,308],ircclientfactori:285,irchannel:[72,164],ircnetwork:[72,164],ircstatu:[43,164],iron:179,ironrealm:291,irregular:[223,231,233],irregular_echo:231,irrelev:[103,276],irur:52,is_account_object:56,is_act:[145,256],is_aggress:117,is_anonym:[4,69],is_anyon:4,is_authent:133,is_ban:144,is_bot:148,is_build:4,is_categori:215,is_channel:[33,41,174],is_connect:[148,247],is_craft:29,is_exit:[33,154],is_fight:29,is_full_moon:25,is_giving_light:232,is_gm:58,is_in_chargen:123,is_in_combat:[217,218,219,220,221],is_inst:27,is_it:344,is_iter:344,is_lit:[232,233],is_next:[148,177,246,256,316,318],is_o:344,is_ouch:11,is_prototype_bas:251,is_run:255,is_sai:118,is_staff:145,is_subprocess:344,is_superus:[2,4,144,145,148,242,247,324],is_thief:166,is_turn:[217,218,219,220,221],is_typeclass:[48,144,318],is_valid:[102,121,133,179,227,256,259],is_valid_coordin:235,isalnum:321,isalpha:321,isbinari:[278,295],isclos:137,isconnect:137,isdigit:[58,114,321],isfiremag:28,isinst:[39,344],isleaf:296,islow:321,isn:[0,4,17,22,41,42,46,50,56,62,63,69,91,119,138,180,192,196,221,233,234,269,315,321,338,349],isnul:340,iso:[15,113,171],isol:[13,37,38,61,63,64,91,95,100,127],isp:[90,103],isspac:321,issu:[7,8,10,11,13,14,21,22,23,29,31,33,37,38,42,43,45,48,54,58,60,63,70,79,85,89,90,93,103,108,123,125,126,127,131,138,140,164,171,251,267,298,299,330,363,364],istart:[42,110,141],istep:299,istitl:321,isub:116,isupp:321,ital:364,itch:[61,63],item:[20,38,47,51,59,63,68,69,82,85,86,116,117,138,165,179,182,188,206,219,224,235,247,286,316,336,344],item_consum:219,item_func:219,item_kwarg:219,item_selfonli:219,item_us:219,itemcoordin:235,itemfunc:219,itemfunc_add_condit:219,itemfunc_attack:219,itemfunc_cure_condit:219,itemfunc_h:219,itend:344,iter:[11,49,51,59,97,112,119,138,144,206,235,247,252,259,296,298,316,318,321,322,325,329,344],iter_cal:329,iter_to_str:344,itl:[22,180],its:[0,2,3,5,9,11,12,14,15,16,20,21,22,23,25,27,29,31,33,37,38,39,40,41,42,44,49,50,51,52,55,56,57,58,60,61,62,63,64,65,68,69,70,72,73,75,80,81,82,83,84,85,86,88,89,90,91,93,95,96,98,100,101,102,103,104,105,109,111,114,115,117,118,119,121,122,123,124,125,126,127,128,129,130,131,133,134,135,136,137,138,139,144,145,146,148,150,151,152,153,154,157,159,167,168,169,175,176,179,180,188,189,195,203,205,206,213,215,217,218,219,220,221,226,227,231,232,234,235,241,245,246,247,252,259,260,261,267,272,276,280,293,294,295,296,299,307,308,312,313,315,316,317,318,319,322,327,328,330,334,336,337,338,339,340,341,344,357,362],itself:[0,4,9,11,15,17,20,21,22,23,25,27,29,33,36,37,38,40,41,44,45,46,47,49,51,55,60,63,64,68,75,77,78,80,82,85,86,89,96,104,105,106,111,114,115,116,118,119,122,123,125,127,131,133,134,135,136,144,146,174,175,180,185,188,198,204,206,215,220,223,232,233,235,236,241,245,247,249,252,260,267,291,296,308,312,315,316,319,321,324,326,328,339,341,344,346,357,362],iusernamepassword:287,iwar:85,iwebsocketclientchannelfactori:278,iwth:261,jack:87,jail:[12,13],jamochamud:24,jan:[12,62,364],januari:62,jarin:90,javascript:[55,83,88,103,135,136,137,138,295,296],jenkin:[123,182,188,190,215,217,218,219,220,221],jet:220,jetbrain:[79,106],jnwidufhjw4545_oifej:9,job:[33,41,67,69,80,144],jobfusc:205,john:[58,214],johnni:[209,210,364],johnsson:87,join:[9,22,34,49,58,61,63,65,72,96,112,116,119,123,133,144,164,175,179,205,321,344,364],join_fight:[217,218,219,220,221],join_rangefield:221,joiner:175,jointli:[64,153],joke:59,joker_kei:[22,180],jqueri:138,json:[83,88,137,138,209,278,291,295,296,325],jsondata:88,jsonencod:296,jsonifi:296,judgement:73,jumbotron:364,jump:[13,14,21,41,44,49,51,52,55,61,63,77,89,108,131,139,215,265],junk:276,just:[0,1,3,4,5,6,9,10,11,12,13,14,15,17,19,20,21,22,23,25,26,27,28,29,30,31,33,34,37,38,39,40,41,42,44,46,47,48,49,51,52,54,56,57,58,59,60,61,62,63,64,68,69,70,73,74,76,77,79,80,81,83,85,86,87,88,89,90,91,93,95,96,97,100,101,102,105,106,107,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,125,126,127,128,131,132,133,134,135,136,137,138,140,144,152,153,154,157,159,167,168,169,170,174,179,180,182,185,187,192,194,195,206,214,215,217,218,219,220,221,224,227,231,233,235,241,242,247,251,252,257,272,285,295,305,312,316,317,318,321,322,325,326,328,330,339,340,344,345,362,364],justif:[329,344],justifi:[96,109,250,321,329,344],justifii:329,justify_kwarg:329,kavir:291,kcachegrind:93,keen:37,keep:[0,1,4,7,9,11,13,14,15,16,20,25,26,29,30,33,34,42,45,48,51,56,57,58,60,61,62,63,64,68,69,73,75,76,77,78,81,82,85,91,92,95,96,97,100,105,109,116,118,121,122,126,128,131,132,133,134,138,146,153,187,190,195,204,209,227,232,233,251,252,255,258,269,310,328,330,344],keep_log:[34,175,324],keepal:[105,290,296],keeper:85,keepint:64,kei:[0,1,5,8,9,10,11,13,21,25,26,27,28,29,30,31,33,34,38,39,41,42,44,49,50,52,56,57,58,60,62,69,71,74,80,81,82,84,85,86,88,89,91,95,96,97,102,107,111,112,114,115,116,119,120,121,123,125,127,129,131,133,137,138,144,146,147,148,150,152,153,154,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,174,175,176,179,180,181,182,184,185,186,187,188,189,193,194,199,202,203,205,206,212,213,214,215,217,218,219,220,221,224,230,231,232,233,234,235,239,241,245,246,247,250,251,252,255,256,257,258,259,260,261,265,267,272,273,274,276,285,288,291,292,294,295,296,299,306,307,308,310,316,317,318,319,323,324,326,327,328,329,337,338,339,341,344,357,362,364],kept:[33,57,80,91,119,127,159,194,195,252],kept_opt:215,key1:202,key2:[51,202,247],key_mergetyp:[31,152,224],keyboard:138,keydown:137,keyerror:[251,261,339],keyfil:[288,292],keynam:[175,252,324],keypair:287,keys_go_back:[22,180],keystr:319,keystrok:287,keywod:330,keyword:[0,1,5,10,11,22,25,27,29,30,33,34,50,51,52,58,62,74,80,81,83,86,91,93,95,102,107,109,114,115,119,123,125,127,134,144,146,150,154,159,165,175,182,184,187,192,194,195,198,205,206,210,217,218,219,220,221,233,234,242,245,247,250,251,252,257,260,261,265,267,272,276,278,279,285,286,287,290,295,296,305,306,307,308,310,316,317,318,324,327,328,329,330,334,336,338,340,341,344,362,364],keyword_ev:198,kick:[12,31,51,58,90,146,152,157,164,171,186,247,329],kildclient:24,kill:[20,27,51,61,75,93,100,102,105,116,169,179,231,232,255,257,258,259,261,267,305,312,364],killsign:267,kilogram:82,kind:[0,11,37,38,40,80,91,97,104,116,118,119,121,133,138,217,218,219,220,242,318,345],kinda:138,kindli:126,kitchen:[44,159],knew:95,knock:51,knot:182,know:[0,2,5,6,8,10,11,13,14,15,16,20,21,22,23,26,29,31,33,37,38,39,40,41,42,44,48,49,51,54,56,57,58,60,61,64,67,69,70,72,73,74,79,80,81,82,83,84,85,86,89,90,91,93,95,96,97,98,100,102,104,105,110,111,113,114,116,117,118,119,121,125,126,127,128,131,132,133,134,136,138,139,154,158,159,167,168,170,174,179,194,199,205,215,220,232,246,247,272,306,308,315,322,323,328,344,362,363,364],knowledg:[13,15,33,55,77,289,308],known:[7,20,24,33,50,73,79,80,87,92,96,109,114,115,125,134,137,143,168,220,329,364],knuth:93,kobold:61,koster:79,kovash:51,kovitiku:364,kwarg:[1,10,25,29,33,40,41,51,58,59,74,80,81,83,84,88,96,107,109,114,115,118,121,125,132,134,137,144,145,146,147,148,150,154,156,157,158,159,164,165,166,167,168,169,170,171,174,175,176,177,179,180,181,182,184,185,186,187,188,189,192,193,194,195,199,202,203,204,205,206,210,212,213,214,215,217,218,219,220,221,223,224,226,227,230,231,232,233,234,235,238,239,241,242,244,245,246,247,249,250,251,252,255,256,257,259,260,261,264,265,272,273,274,276,277,278,279,284,285,286,287,288,290,291,292,295,296,300,306,307,308,309,310,312,315,316,317,318,319,321,326,327,328,329,330,331,333,334,336,337,338,339,340,341,342,344,345,357,362],label:[48,64,70,86,112,133,140,357],label_suffix:[145,237,244,357],laborum:52,lack:[13,38,56,61,70,129,206,247,344],ladder:58,lag:[49,63],lai:[1,48],lair:14,lambda:[10,39,51,69,109,195,252],lamp:[111,224,226,227],land:[91,116,231,232],landscap:[103,111],lang:205,langcod:206,langnam:206,languag:[7,15,38,40,47,55,56,57,58,64,79,91,95,103,108,113,114,118,124,125,127,129,130,137,139,205,206,364],language_cod:76,languageerror:[205,206],languageexistserror:205,languagehandl:205,larg:[10,11,13,14,16,20,23,37,51,55,56,61,86,90,96,97,108,109,122,127,205,235,251,285,322,327,329,334],larger:[14,20,38,49,57,61,68,80,82,86,108,187,293,321,334,344],largesword:86,laser:77,last:[4,11,13,14,22,26,29,31,33,34,36,38,42,48,51,54,58,60,69,74,76,86,87,89,91,95,96,105,107,110,116,121,122,126,127,131,134,136,137,147,150,151,153,159,164,165,179,184,187,195,206,215,217,218,219,220,221,227,247,271,321,322,323,328,329,330,331,337,344,364],last_cmd:33,last_initial_setup_step:305,last_login:145,last_nam:145,last_step:271,lastcast:28,lastli:[81,83,111,133,150],lastsit:25,late:[251,323],later:[0,2,9,11,12,13,22,23,33,34,40,46,55,58,60,61,63,64,69,73,74,76,81,83,84,86,90,95,97,109,111,114,115,117,120,121,123,125,131,133,138,139,140,152,156,157,159,167,168,184,203,206,252,261,287,319,344],latest:[20,21,27,31,36,38,58,63,64,75,83,98,131,159,164,169,247,252,286,310,328,337,363,364],latin:[15,113,171,344,364],latinifi:344,latter:[6,27,29,34,64,77,80,89,91,95,115,126,206,256,258,319],launch:[14,21,54,63,75,85,90,93,102,106,110,122,127,138,153,224,266,267,277,279,298,326,344,364],launcher:[93,106,266,267,276,277,298],law:79,layer:[22,31,246,318],layout:[27,49,56,58,96,119,125,128,137,138,235,364],lazi:344,lazy_properti:344,lazyencod:296,lazyset:337,lc_messag:76,lcnorth:114,ldesc:56,ldflag:75,lead:[0,11,13,17,20,22,23,31,37,49,51,56,60,61,64,69,79,83,86,102,103,111,121,144,151,152,159,169,195,198,204,212,247,252,291,306,318,328,330,336,344],leak:135,lean:206,leap:[62,118],learn:[0,15,16,17,20,22,29,31,33,42,46,49,56,57,60,63,68,69,79,80,81,95,96,106,108,118,122,124,126,127,131,134,136,139,205,220,364],learnspel:220,least:[3,8,33,39,42,47,49,51,55,57,58,61,67,73,80,86,90,96,102,106,121,138,144,153,176,179,205,238,247,252,259,321,327,330,341,344],leasur:231,leather:85,leav:[0,2,20,21,22,25,58,60,73,74,77,85,93,95,102,103,116,123,137,138,156,158,159,164,175,179,180,233,235,241,247,260,295,296,328,334,364],leavelock:241,leaver:175,left:[22,27,33,36,38,39,41,57,69,74,80,85,86,91,101,102,109,111,114,137,138,144,159,165,167,168,190,217,218,219,220,221,232,235,242,250,252,318,321,330,344,363],left_justifi:[109,250],leg:304,legaci:[88,109,144,206],legal:[90,103],legend:[24,49,50],leisur:345,len:[25,49,58,71,85,109,114,116,119,120,121,151,168,184,344],lend:50,length:[22,23,25,49,62,66,68,71,83,86,90,91,95,122,151,184,188,190,198,205,206,245,269,310,316,321,330,344,362],lengthi:[1,25],lengthier:363,lenient:109,less:[22,34,44,51,56,61,64,73,86,90,91,106,108,116,119,132,133,139,184,218,220,316],let:[0,3,5,7,8,9,11,12,14,15,20,21,22,25,28,31,33,37,38,39,40,41,44,46,48,49,51,56,57,58,60,61,62,63,64,65,70,72,73,74,75,77,80,81,82,83,85,89,91,93,95,96,98,103,106,111,114,115,117,118,119,121,123,124,126,127,131,133,134,136,137,140,144,154,159,165,166,170,174,179,182,185,188,190,215,227,235,242,247,277,296,308,324,328,338,343,357,362,363,364],letsencrypt:[67,90],letter:[15,22,38,39,76,90,95,111,113,114,119,123,133,156,165,171,180,204,311,344],level:[2,11,13,19,20,22,26,27,30,36,38,40,41,47,50,51,55,57,58,61,63,66,69,71,73,79,80,85,90,95,96,104,105,108,111,112,119,122,125,130,133,138,139,140,144,156,161,162,180,181,184,199,205,215,241,247,251,252,269,306,316,318,324,326,331,336,344,362],lever:[33,125],leverag:[3,38],levi:86,lhs:[25,58,167,168],lhslist:[167,168],lib:[63,67,75,97],libapache2:8,libcrypt:75,libjpeg:75,librari:[6,13,26,45,53,56,57,63,64,75,76,78,79,91,95,100,103,108,109,125,127,128,133,136,137,138,178,204,234,251,252,280,318,330,344,364],licenc:321,licens:[37,45,79,106,139,204,321,364],lid:[224,226,227],lidclosedcmdset:224,lidopencmdset:224,lie:111,lies:[33,131],life:[11,37,62,87,95,126,184,231,364],lift:[20,73,80,96,123,221,242,364],lifter:80,light:[14,23,27,38,61,102,108,122,153,218,232,233,241,252,260,321],lightabl:232,lighter:[114,218],lightest:27,lightli:[16,218],lightsail:90,lightsourc:232,lightsource_cmdset:232,like:[0,2,3,5,6,8,9,10,11,12,14,15,16,17,19,20,21,22,23,25,26,27,28,29,30,31,33,34,35,36,37,38,39,40,41,42,44,45,46,48,49,51,52,53,54,55,57,58,59,60,61,62,63,64,65,67,68,69,70,71,72,73,74,75,76,77,79,80,81,83,84,85,86,88,89,90,91,93,95,96,97,100,102,103,104,105,106,107,108,109,111,112,114,115,116,117,118,119,120,121,125,126,127,128,129,131,132,133,134,135,136,137,138,139,140,144,146,148,149,151,152,153,156,158,159,164,167,168,171,172,175,176,179,180,182,186,187,188,189,190,198,204,205,206,212,213,215,217,218,219,220,221,224,227,233,234,235,239,241,242,245,246,247,250,251,252,272,280,296,301,305,307,308,316,317,318,321,322,324,327,328,329,330,331,334,338,340,341,344,357,362,364],limbo:[0,9,13,14,20,22,27,59,63,66,104,111,121,122,134,159,180,233,271],limbo_exit:111,limit:[0,2,6,11,16,19,20,25,26,27,28,31,33,34,37,46,51,53,55,58,61,64,68,71,80,86,90,91,95,102,104,109,112,116,123,125,126,127,138,140,144,147,156,157,158,159,175,176,182,195,206,215,217,219,220,227,238,239,242,245,247,252,255,256,259,261,272,285,310,316,317,318,319,322,324,326,337,341,344,362,364],limit_valu:144,limitedsizeordereddict:344,line:[0,4,5,9,10,13,14,15,19,22,23,25,26,27,29,30,31,33,34,36,38,39,41,45,46,48,51,53,54,56,57,58,59,60,61,62,63,67,69,74,76,81,83,86,87,89,90,91,92,93,95,96,97,98,100,104,108,109,110,111,114,119,121,123,125,127,128,133,134,137,138,139,141,144,150,153,159,166,168,169,180,185,186,188,202,205,206,215,234,235,251,267,272,287,290,295,306,318,322,326,327,328,329,330,337,344,357,362,364],linear:49,linebreak:[69,343],lineeditor:326,lineend:343,lineno:38,linenum:326,liner:279,linereceiv:[287,290],linesend:296,lingo:[57,86,105,135],linguist:344,link:[2,3,4,9,14,17,18,20,22,25,29,31,33,37,39,40,43,46,48,49,51,54,55,57,63,64,69,70,72,85,89,90,96,98,105,111,119,121,123,124,128,131,133,134,139,144,148,159,164,192,224,227,234,241,242,247,256,265,267,278,282,287,290,318,343,344,364],link_ok:241,linklock:241,linknam:[38,54],linktext:38,linod:90,linux:[4,8,9,23,25,38,64,67,72,75,87,90,93,97,100,106,131,209,344,364],liquid:318,list:[0,1,2,3,4,6,7,11,12,13,14,15,20,22,23,25,27,31,33,34,37,39,40,41,43,45,46,48,49,51,54,55,57,58,59,60,61,63,66,68,69,70,72,73,74,76,77,79,80,82,85,86,88,89,90,91,93,95,96,97,98,102,103,105,106,109,110,111,112,113,114,116,118,119,121,123,124,125,128,129,131,133,134,135,137,138,139,144,146,147,148,151,152,153,154,156,157,158,159,164,165,166,167,168,169,170,174,175,176,177,179,180,181,182,183,187,188,189,190,192,193,195,196,198,199,202,203,204,205,206,209,210,215,217,218,219,220,221,230,231,232,235,238,241,242,245,246,247,250,251,252,255,257,258,259,261,265,267,272,273,276,277,279,281,283,285,286,291,296,299,306,308,310,312,315,316,317,318,319,321,322,323,324,325,328,329,330,336,337,338,341,344,362,363,364],list_attribut:159,list_callback:193,list_displai:[145,173,237,244,254,263,315],list_display_link:[173,237,244,254,263],list_filt:[244,315],list_nod:[328,364],list_of_all_rose_attribut:11,list_of_all_rose_ndb_attr:11,list_of_lycanthrop:119,list_of_myscript:102,list_prototyp:251,list_select_rel:[173,237,244,254,263],list_set:267,list_styl:156,list_task:193,list_to_str:344,listabl:159,listaccount:[43,169],listcmdset:159,listcmset:[43,159],listen:[2,12,34,41,67,80,103,105,124,137,139,164,175,205,206,224,241,362,364],listing_contact:54,listobj:[43,169],listobject:[43,169],listscript:[43,169],liststr:344,listview:362,lit:[232,233],liter:[13,20,57,66,109,165,250,321,340,344],literal_ev:[51,251,315,328],literatur:364,littl:[0,4,9,10,15,20,21,25,28,33,34,38,41,42,57,58,60,64,69,70,71,85,90,91,96,100,102,109,110,111,117,118,119,125,131,134,136,138,139,218,230,233,302,328,344,357],live:[8,23,38,60,63,67,70,79,90,100,106,364],ljust:321,lne:215,load:[6,11,12,13,15,26,29,31,33,44,50,51,56,57,58,60,61,69,73,82,83,97,103,106,109,111,121,123,127,136,137,138,148,153,165,166,169,177,187,195,205,239,242,246,247,256,260,271,274,276,307,316,318,319,322,323,326,335,338,339,342,344,355],load_buff:326,load_data:323,load_kwarg:339,load_module_prototyp:251,load_sync_data:307,loader:[51,318,344],loadfunc:[50,326,339],loc:159,local0:67,local:[23,25,36,37,47,59,62,64,67,72,76,97,100,103,106,114,131,133,138,192,195,206,252,290,364],local_and_global_search:245,localevenniatest:342,localhost:[3,4,9,23,24,63,67,69,75,90,95,133,134,135,137,296],localstorag:138,locat:[0,2,4,6,8,9,11,12,13,20,21,25,27,30,31,33,35,38,39,43,46,47,48,49,51,57,58,59,63,64,66,73,74,77,80,85,89,90,91,96,100,102,103,109,111,112,114,117,118,119,121,122,123,125,127,128,131,133,135,136,137,140,144,150,159,165,169,176,180,181,182,187,203,206,212,231,233,235,241,245,246,247,252,296,305,317,318,319,322,324,328,330,337,341],location_nam:235,location_set:119,locations_set:[119,246],locattr:[232,241],lock:[4,6,10,12,19,20,21,22,23,25,28,29,31,33,34,39,41,43,44,45,47,48,53,58,60,62,68,71,82,85,89,90,96,104,109,110,112,123,125,133,138,139,141,142,144,145,154,156,157,158,159,164,165,166,168,169,170,171,175,177,179,180,181,182,185,186,187,189,192,193,195,196,199,202,203,206,212,214,224,231,232,233,235,237,239,246,247,251,252,255,312,316,318,324,326,328,338,345,364],lock_definit:242,lock_func_modul:[80,242],lock_storag:[154,156,157,158,159,164,165,166,167,168,169,170,171,174,177,179,180,181,182,185,186,187,188,189,193,199,202,203,206,212,213,214,215,217,218,219,220,221,224,231,232,233,234,239,247,316,318,326,328,329],lock_typ:80,lockabl:[58,212],lockablethreadpool:312,lockdown:[80,316,364],lockdown_mod:90,lockexcept:242,lockfunc1:80,lockfunc2:80,lockfunc:[25,33,53,80,104,121,141,142,159,240,364],lockhandl:[11,48,80,125,141,142,154,180,234,240,241,364],lockset:247,lockstr:[4,11,33,80,97,109,159,164,166,175,177,212,241,242,247,252,316,324],locktest:136,locktyp:[152,252],log:[2,4,5,6,8,10,11,12,20,21,23,24,25,33,34,35,36,39,44,45,47,51,53,55,57,58,59,60,63,64,65,66,67,71,72,73,74,75,76,83,86,89,90,93,100,101,102,105,106,107,110,111,114,121,122,123,128,130,131,133,134,135,137,138,144,153,157,171,175,181,186,188,209,210,247,256,260,267,272,276,277,281,284,285,287,290,298,299,300,306,308,310,312,318,324,336,337,344,362,364],log_dep:[27,337],log_depmsg:337,log_dir:209,log_err:[27,337],log_errmsg:337,log_fil:[27,337],log_info:[27,337],log_infomsg:337,log_msg:337,log_sec:337,log_secmsg:337,log_serv:337,log_trac:[27,102,118,120,337],log_tracemsg:337,log_typ:337,log_typemsg:337,log_warn:[27,337],log_warnmsg:337,logdir:36,logentry_set:148,logfil:[267,337,362,364],logged_in:105,loggedin:285,logger:[27,53,102,118,120,141,142,209,279,320,364],logic:[0,4,10,39,41,42,44,49,69,97,111,134,205,246,250,271,328,345],login:[2,4,7,9,25,33,35,51,55,69,70,80,90,97,101,105,107,131,133,139,144,156,171,186,242,271,272,287,290,295,296,299,308,344,349,351,360,364],login_func:299,loginrequiredmixin:362,logintest:360,logo:364,logout:[298,299,360],logout_func:299,logouttest:360,logprefix:[277,287,290,312],lone:[61,111,159],long_descript:54,long_running_funct:10,long_text:52,longer:[0,21,25,29,33,41,50,52,54,58,69,79,86,91,102,115,124,125,126,129,152,157,175,182,205,206,213,217,218,219,220,221,257,260,326,330,364],longest:[27,206],longrun:33,loo:[154,170],look:[0,3,4,6,9,10,11,12,13,14,15,16,17,19,20,21,22,23,25,26,27,29,30,31,33,35,36,37,38,39,40,41,42,43,44,46,49,51,55,57,58,60,61,62,63,64,67,68,69,70,71,73,74,75,76,77,80,81,82,83,85,86,87,88,89,90,91,96,97,100,103,105,108,109,110,111,112,114,116,117,118,119,121,122,124,125,126,127,131,133,134,135,136,138,139,144,146,151,153,154,156,159,165,167,168,170,171,174,181,182,186,187,188,194,202,203,205,206,215,219,224,230,232,233,235,238,241,242,244,246,247,249,252,255,272,287,288,295,299,316,318,322,328,329,330,338,341,343,357,364],look_str:144,lookaccount:58,lookat:33,looker:[49,58,60,123,182,187,206,235,241,247,318],lookm:33,lookstr:247,lookup:[11,33,80,86,97,112,119,150,165,209,245,246,286,319,321,333,334,340,341,344,345,364],lookup_typ:340,lookup_usernam:51,lookuperror:321,loom:111,loop:[0,5,6,11,21,46,49,55,60,64,69,85,93,96,116,118,119,124,125,141,146,217,252,285],loopingcal:[259,270],loos:[14,37,144,182,221,238,287,298,322],loot:61,lop:119,lore:58,lose:[11,56,61,100,105,110,116,123,138,209,219,278,279,287,290],lost:[0,38,39,56,79,91,110,111,125,135,139,164,213,264,277,278,279,287,290,295,321],lot:[0,4,10,13,15,22,26,27,28,34,37,39,41,42,46,53,55,57,58,59,61,62,63,67,69,70,73,79,80,86,90,91,93,95,96,108,109,111,112,114,119,121,123,125,127,131,133,135,138,180,184,186,188,206,214,218,232,235,312],loud:21,love:137,low:[31,40,46,66,90,95,152],lower:[2,10,19,25,29,31,33,41,49,51,58,62,80,85,86,90,93,114,122,137,151,152,156,167,169,206,272,321],lower_channelkei:[41,174],lowercas:[38,95,154,321],lowest:[66,90,241,321],lpmud:129,lpthw:77,lsarmedpuzzl:203,lspuzzlerecip:203,lst:[49,324],lstart:50,lstrip:[91,119,321],ltto:114,luc:327,luciano:79,luck:[8,51,91,96],luckili:[60,80,111,127,131],lue:114,lug:55,lunch:46,luxuri:[112,314],lycanthrop:119,lying:111,m2m:319,m2m_chang:107,m_len:344,mac:[9,23,24,38,64,93,100,106,131,344,364],machin:[13,25,100,106,131,231,364],macport:[63,131],macro:[4,116],macrosconfig:4,mad:131,made:[3,11,19,20,21,25,26,35,36,38,51,56,58,59,61,79,80,90,96,98,103,104,109,111,121,123,131,134,150,152,164,169,179,182,188,215,219,220,221,242,260,269,313,321,322,328,344],mag:[60,127,327],magazin:79,mage:51,mage_guild_block:51,mage_guild_welcom:51,magenta:126,magic:[30,60,61,80,112,121,122,140,179,190,220,269,364],magic_meadow:112,magicalforest:140,magnific:51,mai:[0,4,6,8,9,10,11,13,19,20,21,23,25,27,28,29,31,33,34,37,38,40,41,42,48,51,54,56,57,60,62,63,64,66,67,69,70,71,73,75,77,79,80,81,83,84,86,87,88,89,90,93,95,96,97,100,102,103,104,105,106,108,109,110,111,114,115,116,118,119,120,123,125,127,128,130,131,133,134,135,136,144,146,150,151,152,154,156,157,159,164,169,175,176,178,179,181,182,184,188,190,205,206,217,218,219,220,221,224,232,233,241,242,245,247,250,251,252,253,269,299,306,308,309,313,315,318,319,321,323,324,325,326,328,330,331,336,338,341,344,362],mail:[9,34,37,51,55,57,60,61,70,79,93,116,128,141,142,176,177,178,241,363,364],mailbox:[34,199],maillock:241,main:[13,14,15,20,21,22,30,31,33,34,37,40,49,51,54,56,64,68,69,76,79,80,81,83,84,85,86,89,90,91,92,100,104,105,109,110,112,115,116,119,122,124,125,131,133,134,135,137,138,139,144,145,148,150,156,159,170,177,180,188,195,199,205,206,235,239,246,252,254,256,267,271,272,274,279,284,286,291,305,307,312,318,319,328,329,332,341,343,344,364],mainli:[10,12,33,34,51,57,79,83,89,93,96,105,156,236,316,322,336,344],maintain:[4,19,23,37,38,41,53,56,68,90,93,100,108,115,119,169,171,186,261,363],mainten:[90,103],major:[14,15,23,45,57,60,63,64,119,121,133],make:[0,1,2,4,5,6,7,8,9,10,11,12,13,14,15,16,19,22,23,24,25,26,28,29,30,31,33,36,37,38,39,40,41,42,43,44,46,47,48,49,50,51,54,55,56,59,61,62,63,64,68,70,71,72,73,74,75,77,78,79,80,81,83,85,86,87,89,90,91,93,95,96,97,100,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,122,124,125,126,128,130,132,133,134,136,137,138,139,140,144,146,148,151,152,153,154,156,157,159,164,167,170,174,175,176,179,180,182,187,188,190,196,199,205,206,211,212,213,215,217,218,219,220,223,224,226,227,231,232,233,238,241,242,245,247,251,252,255,258,259,261,267,271,279,284,298,299,305,306,308,309,311,312,315,316,317,318,319,321,322,323,324,325,326,328,330,331,334,336,341,343,344,360,362,363,364],make_it:344,make_shared_login:351,make_uniqu:152,makeconnect:276,makefactori:287,makefil:38,makeit:298,makemessag:76,makemigr:[36,86,133],male:189,malevol:14,malform:345,malici:103,malign:242,man2x1:108,man:[87,90,108,129,165,199,206],mana:[28,30],manaag:237,manag:[2,7,9,11,31,39,40,53,56,57,59,80,83,85,86,89,93,96,100,102,105,110,115,119,125,127,128,131,133,138,141,142,143,144,148,169,170,172,174,175,177,202,206,221,227,233,236,239,243,246,247,251,253,256,261,262,267,274,306,314,316,318,319,320,323,324,332,335,337,341,344,360,362,364],manager_nam:306,manchest:344,mandat:357,mandatori:[0,22,107,109,129],maneuv:215,mangl:293,mango:203,manhol:287,manhole_ssh:287,mani:[0,1,2,4,5,9,10,11,12,14,15,17,20,26,27,30,31,33,34,38,40,44,49,51,55,56,57,58,61,62,63,64,66,68,70,72,73,76,77,85,86,88,89,90,91,93,95,96,98,102,103,104,105,107,108,109,110,111,113,114,115,116,118,119,120,121,122,123,124,125,126,127,128,129,131,133,134,135,140,147,148,152,154,159,170,177,179,182,186,188,206,213,214,215,219,220,224,231,234,239,241,242,245,246,252,255,256,261,267,281,289,291,310,316,318,319,321,328,329,334,335,337,362],manifest:97,manipul:[0,11,22,31,41,44,51,64,86,102,109,123,147,159,166,169,176,187,192,238,245,247,255,273,324,329],manner:[14,175,206,235,247,285,318],manpow:37,manual:[4,6,14,20,21,23,30,33,34,38,40,55,58,60,61,63,68,79,80,85,86,89,90,97,102,109,110,111,114,117,119,121,122,124,125,128,131,134,139,140,141,146,159,215,224,227,230,234,247,252,259,267,284,291,328,363,364],manual_paus:259,manual_transl:205,manual_unpaus:259,manytomanydescriptor:[148,177,239,246,256,316,318,319],manytomanyfield:[148,177,239,246,256,316,318,319],map:[0,15,25,39,46,51,57,58,61,64,67,87,88,97,100,124,135,138,139,156,164,183,184,205,206,235,247,251,252,291,316,318,321,327,328,336,344,364],map_modul:111,map_str:[49,111,235],mapbuild:[141,142,178,364],mapper:[334,364],mapprovid:235,march:[79,337],margin:17,mariadb:364,mark:[13,14,20,21,33,38,49,51,58,63,72,76,80,90,95,114,119,131,135,137,138,140,151,158,187,195,204,215,308,318,322,327,328,336,340],mark_categori:215,markdown:[1,4,38,48,54],marker:[13,20,33,51,64,87,114,138,165,187,189,206,215,247,279,287,290,295,296,316,319,321,327,329,336],market:90,markup:[81,114,136,139,183,321,343],mask:[203,206,210,211],maskout_protodef:203,mass:[61,124,139,364],massiv:[28,55],master:[7,9,37,38,57,61,63,73,95,98,100,104,116,118,134,313,364],match:[9,11,20,22,27,31,33,39,41,44,49,51,57,58,62,68,74,76,80,83,86,87,88,89,91,102,104,105,109,111,112,114,118,119,125,128,131,133,134,135,136,137,138,144,147,150,151,152,153,154,157,159,165,166,168,170,174,176,180,183,184,187,188,198,199,202,203,206,220,235,238,241,242,245,247,251,252,255,258,261,272,273,285,298,308,316,317,318,319,321,326,328,330,336,339,341,343,344,345,362,364],match_index:151,matched_charact:188,matcher:51,matches2:86,matchobject:[321,343],mate:64,math:39,mathemat:152,matplotlib:300,matrix:330,matt:102,matter:[0,4,9,11,25,31,36,41,51,57,61,62,63,69,73,76,84,91,95,103,105,107,108,116,117,127,136,152,221,231,246,272,316],matur:[108,128,129],maverick:64,max:[16,25,49,71,114,116,188,206,310,337,344],max_damag:219,max_dbref:317,max_depth:344,max_dist:49,max_heal:219,max_l:49,max_length:[49,86,133,206],max_lin:330,max_num:145,max_num_lin:362,max_popular:362,max_rmem:334,max_siz:337,max_valu:[190,357],max_w:49,max_width:49,maxconn:67,maxdelai:[264,278,279],maxdepth:252,maxdiff:352,maximum:[16,39,71,86,91,111,114,144,188,190,217,218,219,220,221,247,252,312,321,328,330,336,344],maxlengthvalid:144,maxnum:344,maxrotatedfil:337,maxsplit:321,maxthread:312,maxval:336,maxvalu:336,maxwidth:330,may_use_red_door:109,mayb:[6,9,11,13,14,21,22,25,27,31,33,38,44,48,49,54,61,63,68,69,70,73,82,85,86,90,109,116,119,122,138,140,153,179,198,285],mccp:[24,55,74,141,142,262,272,275,364],mccp_compress:280,meadow:[22,112,140],mean:[0,5,10,11,12,13,14,15,20,22,23,27,28,31,33,34,37,40,41,42,46,49,51,55,57,58,60,61,62,64,68,73,74,77,78,80,81,83,84,85,86,87,88,90,93,95,96,97,100,102,103,104,105,110,111,112,113,114,116,117,119,121,122,123,125,126,127,128,131,134,135,136,138,144,146,147,153,159,175,185,195,205,226,227,232,234,241,245,247,251,252,257,261,267,291,307,316,318,321,326,328,330,334,337,340,341],meaning:[154,170],meaningless:123,meant:[16,20,22,31,34,44,54,62,68,76,83,96,102,125,126,137,138,140,152,180,189,206,214,217,218,219,220,221,227,233,235,247,272,322],meantim:1,meanwhil:96,measur:[90,93,123,151,168,344],meat:133,mech:[124,139,364],mechan:[27,28,33,39,50,51,55,58,61,67,69,73,91,102,109,116,122,123,125,126,139,144,146,150,187,206,220,240,252,259,261,267,271,277,285,296,307,318,326,329,332,339,362,364],mechcmdset:21,mechcommand:21,mechcommandset:21,meck:21,media:[16,145,173,237,244,254,263,295,312,315,340,357],median:49,mediat:73,medium:16,mediumbox:276,meet:[25,36,61,122,194,235,311],mele:221,mem:169,member:[9,11,70,86,165,167,168,247,344],membership:[4,9,119],memori:[6,12,23,28,31,33,56,75,86,90,93,113,125,135,144,169,175,227,247,260,261,300,310,320,329,334,339,344],memoryerror:63,memoryusag:300,memplot:[141,142,262,297,364],meni:180,mental:126,mention:[6,9,10,11,13,14,15,21,29,33,40,41,49,56,57,61,63,70,74,80,90,102,108,113,115,126,127,153,186],menu:[11,25,31,38,45,46,47,53,54,55,63,65,69,105,106,109,110,123,128,138,139,141,142,159,180,188,214,215,230,248,252,265,267,320,338,364],menu_cmdset:328,menu_data:51,menu_edit:180,menu_login:[141,142,178,364],menu_modul:328,menu_module_path:328,menu_quit:180,menu_setattr:180,menu_start_nod:214,menu_templ:328,menuchoic:[51,328],menudata:[188,230,249,328],menudebug:[51,328],menufil:328,menunode_fieldfil:188,menunode_inspect_and_bui:85,menunode_shopfront:85,menunode_treeselect:215,menunodename1:51,menunodename2:51,menunodename3:51,menuopt:215,menutre:[51,328],merchant:46,mercuri:108,mere:[117,169,190],merg:[3,5,22,33,37,38,44,51,57,62,64,97,131,139,150,151,152,153,166,224,233,235,252,256,291,328,336,364],merge_prior:328,merger:[5,31,37,111,152,153],mergetyp:[31,51,116,152,224,233,326,328],mess:[11,19,27,38,90,93,131,138,215],messag:[5,6,8,10,13,15,20,21,22,27,28,29,33,34,38,40,41,44,45,46,50,51,52,53,55,58,59,60,61,62,63,64,65,70,71,73,74,76,80,81,82,85,89,90,91,92,95,96,101,102,103,104,105,110,111,113,116,118,119,123,124,127,128,131,132,137,138,139,140,144,146,150,153,154,157,159,164,165,166,168,170,172,174,175,176,177,179,180,182,188,189,193,195,199,203,204,206,210,217,218,219,220,221,223,224,226,228,230,231,232,233,234,241,247,267,269,276,278,279,285,286,287,290,291,293,295,304,306,308,310,312,324,326,328,329,336,337,341,344,364],message_rout:137,message_search:176,message_transform:175,messagepath:[139,364],messagewindow:137,meta:[104,125,145,237,244,315,318,334,357],metaclass:[86,96,125,154,318],metadata:[210,269],metavar:234,meteor:82,meter:190,metho:174,method:[1,2,5,6,9,10,11,22,25,27,28,29,30,31,34,38,39,40,42,46,48,49,51,55,58,59,60,62,64,68,69,73,77,80,83,86,88,89,91,95,96,102,104,105,107,109,111,112,114,115,116,117,118,119,120,121,123,125,127,131,132,133,134,137,139,144,147,148,150,152,153,154,156,159,160,164,166,167,168,169,170,173,174,175,176,177,179,180,184,187,192,195,202,203,204,205,206,209,210,212,217,218,219,220,221,227,228,230,231,232,233,234,235,238,239,241,242,245,247,255,259,260,261,264,269,272,273,274,276,277,278,279,280,285,287,290,293,295,296,299,303,305,306,307,308,310,315,316,318,321,322,324,326,328,329,330,331,334,335,336,337,338,339,341,342,343,344,362,364],methodnam:[170,196,211,228,261,293,303,335,342,352,360],metric:82,microsecond:11,microsoft:[63,111],mid:[29,108,121],middl:[29,33,49,90,218,321],middlewar:[141,142,346,348,364],midnight:[25,62],midst:122,midwai:114,mighht:91,might:[0,4,8,10,11,12,14,15,17,20,22,23,25,26,27,28,29,30,31,33,34,39,40,41,42,46,51,52,55,58,60,61,62,63,69,70,73,75,76,77,80,81,82,85,89,90,91,95,96,97,98,100,102,103,104,105,110,111,114,115,116,119,120,122,123,124,126,127,131,132,133,136,138,153,157,159,179,204,210,213,217,218,219,220,234,247,255,296,318,321,326,337,338,344,357,363],mighti:[29,111],migrat:[9,23,36,38,63,75,86,107,110,111,127,131,133,252,364],mike:159,mileston:139,million:[23,25,133],mime:324,mimic:[23,34,50,55,73,93,177,306,326],mimick:[50,64,73,138,298,326,329],mimim:319,min:[49,62,102,114,184,188,331,336],min_damag:219,min_dbref:317,min_heal:219,min_height:330,min_shortcut:[22,180],min_valu:357,min_width:330,mind:[10,12,13,14,37,41,45,51,54,55,56,57,60,61,122,126,134,138,179,190,195,204,269,344,364],mindex:151,mine:[46,103,138],mini:[55,111,124,364],miniatur:[61,122],minim:[61,103,105,116,138,205,252],minimalist:[33,58,108],minimap:364,minimum:[22,58,64,73,105,188,217,218,219,220,221,272,312,318,330,336,339,344],mininum:330,minlengthvalid:144,minor:[41,153,363],mint:[63,67,131],minthread:312,minu:[86,331],minut:[25,27,28,62,79,91,100,102,116,164,179,184,310,331,344],minval:336,mirc:279,mirror:[72,79,105],mis:57,misanthrop:119,misc:138,miscelan:320,miscellan:[47,364],mislead:41,mismatch:[74,344],miss:[49,57,60,63,70,90,95,97,217,218,219,220,221,251,272],missil:[21,220],mission:[41,69],mistak:[38,60,363],misus:90,mit:[79,124,321],mitig:[57,103,362],mix:[11,30,33,34,51,53,114,126,133,144,179,206,251,252,311,319,322,330],mixin:[251,301,362],mixtur:81,mkdir:[9,36,63],mktime:62,mob0:56,mob:[14,55,56,61,80,105,122,141,142,153,159,178,229,233,252,322,364],mob_data:56,mob_db:56,mob_vnum_1:56,mobcmdset:231,mobdb:56,mobil:[14,71,109,122,138,231,241],moboff:231,mobon:231,mock:[127,260,342],mock_channeldb:170,mock_get_vers:352,mock_random:228,mock_set:352,mock_tim:303,mockdeferlat:342,mockdelai:342,mocked_idmapp:303,mocked_o:303,mocked_open:303,mockup:138,mockval:342,mod:[8,251],mod_import:344,mod_import_from_path:344,mod_prototype_list:251,mod_proxi:364,mod_proxy_http:8,mod_proxy_wstunnel:8,mod_secur:103,mod_ssl:364,mod_sslj:8,mod_wsgi:364,mode:[2,8,31,41,42,43,50,51,67,69,74,79,93,100,103,106,116,117,123,133,135,138,141,158,169,171,175,181,199,231,247,251,255,258,267,272,277,284,295,296,305,322,326,328,337,344,364],mode_clos:296,mode_init:296,mode_input:296,mode_keepal:296,mode_rec:296,model:[9,11,34,38,41,45,59,64,69,73,80,87,96,104,112,115,119,125,132,135,136,139,141,142,143,144,145,147,172,173,175,176,236,237,243,244,247,253,254,257,261,262,263,273,314,315,316,317,319,320,324,325,332,333,335,340,341,344,357,362,364],model_inst:340,modeladmin:[173,237,244,254,263,315],modelbackend:349,modelbas:334,modelchoicefield:244,modelclass:[11,112],modelform:[145,237,244,315,357],modelmultiplechoicefield:[145,237,244],modelnam:[175,239,318],moder:[4,39,179],modern:[10,11,15,30,79,103,108,126,138,280],modif:[0,8,25,33,37,46,83,91,100,123,131,138,313,357,364],modifi:[0,2,4,11,20,22,25,26,31,33,34,38,39,40,44,46,51,53,55,56,57,58,60,68,73,78,85,89,93,96,100,104,105,109,110,111,114,118,119,122,123,125,128,131,135,137,138,139,140,144,145,153,175,180,185,187,189,195,203,206,213,217,218,219,220,221,232,234,239,245,247,252,261,318,322,328,334,340,343,357,362,364],modified_text:114,modul:[3,5,6,11,13,15,20,21,26,27,29,31,33,35,37,38,40,45,47,50,51,55,56,57,58,59,60,62,65,68,74,75,80,81,82,83,85,89,93,96,97,98,102,103,104,105,107,108,110,111,114,117,119,121,122,123,124,125,127,135,138,139,150,151,153,154,159,161,162,163,164,168,170,174,179,180,181,182,183,184,185,186,187,188,190,192,193,194,196,204,205,206,211,212,213,215,217,218,219,220,221,224,231,232,233,234,241,242,246,247,250,251,252,257,259,260,261,264,266,267,271,272,276,284,286,287,290,291,294,296,298,299,300,305,307,308,309,316,318,319,320,322,323,324,325,326,327,328,329,331,336,342,344,364],modular:55,modulepath:276,moifi:187,mollit:52,moment:[21,31,46,57,76,85,91,96,115,135,139,144,250,256],monei:[9,61,70,86,90,241],monetari:[37,179],monitor:[53,84,88,93,139,257,272,291,334,364],monitor_handl:[53,84,141,257],monitorhandl:[45,74,139,141,142,253,364],mono:25,monster:[29,57,61,64,89,109,159,252],month:[37,62,67,90,184,331,337,344],monthli:62,montorhandl:84,moo:[55,57,79,108,129],mood:[46,122],moon:[25,61,62,82],moor:122,moral:97,more:[0,1,2,3,4,5,9,10,11,12,13,14,15,17,19,20,21,22,23,25,26,27,28,31,33,34,35,36,37,39,40,41,42,44,46,49,50,51,52,55,56,58,59,60,61,62,63,64,66,67,68,69,70,71,72,73,74,75,76,77,79,83,85,86,87,88,89,90,91,93,95,96,97,100,102,103,104,105,108,109,110,111,112,113,114,115,116,118,119,121,122,123,124,125,126,127,131,132,133,134,136,137,138,141,143,144,145,148,151,152,153,158,159,165,169,171,174,178,179,180,181,182,184,186,187,190,195,198,204,205,206,213,214,215,217,218,219,220,221,226,231,232,233,234,235,241,244,245,247,250,251,252,277,279,282,298,299,308,313,316,317,321,322,324,325,326,327,328,329,330,334,341,344,345,357,362,364],more_command:329,moreov:[90,102],morn:[187,188],most:[0,4,6,8,9,10,11,13,17,22,23,25,27,30,31,33,35,37,38,39,40,41,42,46,47,48,49,51,56,57,58,59,60,61,62,63,64,69,73,74,77,80,82,83,86,88,89,90,91,93,95,96,97,100,103,104,105,107,108,111,113,114,115,116,117,119,121,123,125,126,128,129,133,137,138,140,144,148,152,153,156,159,167,177,180,190,205,206,213,217,218,219,220,221,239,241,242,246,247,251,252,256,260,290,295,305,316,317,318,319,328,329,334,335,344,362,364],mostli:[40,51,57,69,73,90,91,95,114,123,125,137,138,145,152,171,185,205,219,235,287,321],motiv:[13,14,37,55,61,70,89,278,279,285,286,287,290,295,296,307,308],mount:100,mountain:[108,111],mous:[114,137,328],move:[0,4,9,14,15,21,22,23,29,33,34,41,44,46,49,50,51,52,54,58,61,63,69,77,79,82,85,89,91,95,96,111,116,117,122,126,133,134,138,153,159,165,179,180,188,194,213,217,218,219,220,221,231,232,233,235,238,241,247,299,318,322,329,364],move_hook:247,move_obj:235,move_to:[0,85,89,121,213,247],movecommand:44,moved_obj:[233,235,247],moved_object:247,movement:[58,109,121,213,217,218,219,220,221,247],mover:221,mptt:4,mratio:[151,168],msdp:[55,83,272,291,364],msdp_list:272,msdp_report:272,msdp_send:272,msdp_unreport:272,msdp_var:291,msg:[0,2,5,10,11,13,22,25,27,28,29,30,33,38,40,41,42,44,46,50,51,52,53,56,58,59,60,62,71,73,80,82,84,85,86,88,89,91,95,96,105,111,112,114,116,118,119,121,123,127,129,137,138,141,144,146,154,156,160,164,170,173,175,176,177,189,199,210,234,242,247,278,279,306,315,322,324,328,329,337,341,344,364],msg_all:116,msg_all_sess:[33,154],msg_arriv:0,msg_content:[0,21,27,33,46,62,73,89,102,118,121,123,132,247],msg_help:166,msg_leav:0,msg_locat:247,msg_other:179,msg_receiv:247,msg_self:247,msg_set:319,msgadmin:173,msglauncher2port:[267,276],msgmanag:[176,177],msgobj:[34,175],msgportal2serv:276,msgreturn:170,msgserver2port:276,msgstatu:[267,276],mssp:[55,104,141,142,262,275,364],mtt:294,much:[0,4,10,11,13,14,15,20,22,23,25,26,29,37,39,41,42,49,51,56,59,61,62,63,64,67,69,73,76,79,80,82,89,90,91,93,96,109,111,113,115,116,119,120,121,125,127,132,133,134,138,148,153,158,167,180,184,185,206,215,221,224,232,307,321,322,323,330,344],muck:57,mud:[8,15,21,22,23,24,30,40,49,55,56,60,61,63,64,72,73,74,80,87,88,90,91,92,95,97,98,100,101,104,105,108,110,111,114,115,116,117,122,124,126,128,132,135,137,138,140,148,153,156,221,230,264,280,281,282,287,290,291,294,322,331,364],mudbyt:79,mudconnector:[79,171],mudderi:79,muddev:63,mudform:327,mudinfo:[34,171],mudlab:79,mudlet:[24,96,101,282],mudmast:24,mudramm:24,mudstat:171,muhammad:343,mukluk:24,mul:250,mult:[109,250],multi:[10,22,31,38,51,55,61,95,96,100,104,105,119,122,123,151,169,206,215,308,328,364],multiaccount_mod:97,multidesc:[141,142,178,364],multilin:343,multimatch:[31,151,206,247,344],multimatch_str:[144,206,247,344],multimedia:137,multipl:[6,12,14,22,23,27,30,31,33,40,51,55,58,61,62,64,73,79,84,88,89,90,95,96,104,105,107,108,109,114,115,122,123,125,131,138,144,150,152,157,158,159,164,168,169,183,185,186,187,189,190,196,202,206,215,217,218,219,220,233,242,245,247,250,251,252,261,265,269,272,276,291,299,315,316,317,322,328,329,330,341,344,364],multiplay:[55,57,79],multipleobjectsreturn:[144,146,148,175,177,179,182,184,187,189,195,203,204,205,206,212,213,214,217,218,219,220,221,223,226,227,231,232,233,235,239,246,247,251,256,259,274,300,316,319,331,335],multipli:250,multisess:[41,69,328,364],multisession_mod:[2,24,33,64,69,105,123,133,144,156,160,181,189,247,308],multisession_modd:51,multitud:[57,111,114],multumatch:247,mundan:21,murri:344,muse:79,mush:[9,36,55,60,73,79,108,116,124,139,183,202,364],mushclient:[24,74,96,272,282],musher:79,mushman:108,mushpark:90,musoapbox:[57,79],must:[0,1,2,4,5,8,10,11,15,24,25,29,31,33,37,38,40,48,49,50,51,56,58,61,62,63,64,65,67,71,72,74,76,80,81,83,84,85,87,89,90,93,95,96,97,100,103,104,109,110,112,113,114,115,116,117,119,123,125,127,128,131,133,135,136,137,140,146,151,152,154,159,164,169,170,174,175,176,179,182,183,184,186,203,205,206,210,215,217,218,219,220,221,226,227,230,232,233,239,241,247,250,251,257,261,267,272,285,287,290,307,309,310,315,316,317,318,321,322,323,324,325,326,327,328,329,331,336,338,339,340,341,343,344,345,362],must_be_default:153,mutabl:[325,364],mute:[17,41,174,175],mutelist:[41,175],mutltidesc:202,mutual:317,mux2:[129,171],mux:[20,21,33,34,41,45,55,58,103,108,139,141,142,149,167,168,183,240,364],mux_color_ansi_extra_map:183,mux_color_xterm256_extra_bg:183,mux_color_xterm256_extra_fg:183,mux_color_xterm256_extra_gbg:183,mux_color_xterm256_extra_gfg:183,muxaccountcommand:[167,199],muxaccountlookcommand:156,muxcommand:[5,25,28,29,30,33,44,53,58,119,123,141,142,149,155,156,157,158,159,164,165,166,168,169,171,174,182,185,186,187,193,199,202,203,212,214,219,220,233,247,326,364],mvattr:[43,159],mxp:[24,55,74,114,141,142,262,272,275,287,290,321,328,343,344,364],mxp_pars:282,mxp_re:321,mxp_sub:321,my_callback:309,my_datastor:86,my_funct:29,my_github_password:131,my_github_usernam:131,my_identsystem:87,my_number_handl:51,my_object:29,my_port:40,my_portal_plugin:40,my_script:102,my_server_plugin:40,my_servic:40,my_word_fil:205,myaccount:112,myapp:86,myarx:9,myattr:[11,144],myawesomegam:67,mybot:164,mycallable1:51,mycar2:87,mychair:112,mychan:34,mychannel:[12,164],mycharact:81,mychargen:51,myclass:60,mycmd:[33,68],mycmdset:[5,31,33],mycommand1:31,mycommand2:31,mycommand3:31,mycommand:[30,31,33,83],mycompon:137,myconf:36,mycontrib:127,mycss:137,mycssdiv:137,mycustom_protocol:40,mycustomcli:40,mycustomview:135,mydatastor:86,mydhaccount:100,mydhaccountt:100,mydhacct:100,myevennia:72,myevilcmdset:[31,152],myevmenu:51,myfix:131,myfunc:[10,115,127,344],myfunct:51,mygam:[2,3,5,6,9,13,14,21,23,25,26,27,30,31,35,38,40,42,44,47,49,51,53,54,56,57,58,60,62,63,65,67,69,71,73,74,75,76,80,81,82,85,86,89,90,93,95,96,100,102,104,106,109,110,111,114,116,118,119,120,121,123,125,127,128,131,133,134,135,136,137,180,181,183,187,199,202,212,213,292,342,344],mygamedir:38,mygamegam:81,myglobaleconomi:102,mygotocal:51,mygrapevin:164,myhandl:107,myhdaccount:100,myhousetypeclass:159,myinstanc:86,myircchan:164,mykwarg:51,mylayout:137,mylink:38,mylist2:11,mylist:[6,11,97,318],mylog:27,mymenu:51,mymethod:56,mymodul:115,mymud:[8,106],mymudgam:90,mynam:100,mynestedlist:325,mynod:51,mynoinputcommand:33,mynpc:123,myobj1:112,myobj2:112,myobj:[11,27,80,102,261],myobject:[5,11],myobjectcommand:25,myothercmdset:31,myownfactori:40,myownprototyp:109,mypassw:186,mypath:127,myplugin:137,myproc:40,myproc_en:40,myprotfunc:109,myroom:[56,102,112,159],myros:89,myscript:[102,112,125],myscriptpath:102,myserv:186,myservic:40,mysess:105,mysql:[36,55,64,128,344,364],mysqlclient:23,myst:364,mysteri:[75,87],mytag1:137,mytag2:137,mythic:122,mytick:261,mytickerhandl:261,mytickerpool:261,mytop:20,mytup1:11,mytup:11,myvar:33,myview:135,naccount:308,naiv:[175,235,239,318],nake:33,name1:159,name2:159,name:[0,2,3,4,5,6,9,10,11,13,14,15,19,20,22,23,24,25,29,31,33,34,36,38,40,41,42,43,44,46,47,49,51,52,53,54,55,56,57,58,59,60,61,62,64,65,66,67,68,69,71,72,73,74,75,76,79,80,81,82,83,84,85,86,87,89,90,91,93,95,96,100,102,103,104,105,106,107,109,110,111,112,113,114,116,117,119,121,123,125,126,127,128,130,131,132,133,134,135,136,137,138,139,140,141,142,144,146,147,148,150,151,152,153,154,156,157,159,164,165,166,167,168,169,170,171,174,175,176,177,180,181,182,184,186,188,192,194,195,198,203,204,205,206,212,215,219,220,231,233,234,235,238,239,240,245,246,247,251,252,255,256,257,259,261,267,270,272,273,274,276,277,279,284,287,290,291,294,295,296,299,306,308,312,315,316,317,318,319,321,322,323,324,326,327,328,329,334,335,336,337,338,340,341,343,344,345,349,357,362,364],namecolor:215,namedtupl:192,nameerror:[42,95],namelist:199,namesak:97,namespac:[69,125,137,195,234,252,322],napoleon:38,narg:[114,234],narr:221,narrow:91,nativ:[34,38,42,51,88,102,209,312,362],nattempt:51,nattribut:[11,51,116,125,159,252,306,316,318,324,328],nattributehandl:[306,316],natur:[11,15,27,55,79,88,112,146,330],natural_height:330,natural_kei:316,natural_width:330,navig:[9,38,48,49,51,106,111,128,133,134,221,362],naw:[24,52,141,142,262,275,364],nbsp:343,nchar:120,nclient:298,ncolumn:330,ncurs:141,ndb:[6,13,22,25,29,33,51,102,105,116,125,144,148,169,246,256,306,316,318,328,364],ndb_:[109,159,252],ndb_del:306,ndb_get:306,ndb_set:306,ndbholder:306,ndk:75,nearbi:[119,152,153,154,221],nearli:321,neat:[0,3,138,357],neatli:[108,344],necess:[40,95],necessari:[0,4,22,36,39,40,51,57,58,59,61,77,91,108,110,114,118,121,125,131,138,153,154,177,181,195,210,233,234,252,296,315,322,328,330,338,340,344],necessarili:[38,41,57,88,90,109,344],necessit:309,neck:[109,182],necklac:182,need:[1,2,3,4,5,6,8,9,10,11,13,14,15,19,20,21,22,23,25,26,27,28,29,30,31,33,34,35,36,37,38,39,40,41,42,44,45,46,48,49,50,51,54,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,79,80,81,82,83,84,85,86,87,88,89,90,91,93,95,96,97,98,100,102,103,104,105,106,109,110,111,112,113,114,115,116,117,118,119,121,122,123,124,125,126,127,128,130,131,133,134,135,136,137,138,140,144,146,148,152,154,156,159,164,165,167,170,174,175,179,180,186,187,189,193,194,195,196,203,204,205,206,215,217,218,219,220,221,227,231,232,233,234,235,241,242,246,247,251,252,255,259,267,269,271,272,276,284,291,296,298,306,307,308,312,315,316,318,321,322,324,328,329,330,331,336,338,339,341,344,362,364],need_gamedir:267,needl:203,neg:[62,126,152,326],negat:[114,119,242],negoti:[55,179,281,283,285,294,308],negotiate_s:283,neighbor:39,neither:[11,54,61,73,97,110,185,251,291,316,319,345],nenter:51,nest:[11,14,33,51,114,144,159,206,215,241,247,250,252,291,325,336,364],nested_mut:11,nested_r:159,nestl:111,net:[9,57,63,72,79,90,146,164,171,280,281,291,294,308],netrc:131,network:[40,53,55,64,65,70,71,72,79,90,103,113,139,146,164,278,279,284,305,308],neu:180,neutral:189,never:[12,14,26,27,31,33,51,54,56,60,61,62,64,80,86,88,91,95,96,104,114,115,118,119,121,125,127,131,133,144,194,205,206,220,221,231,242,247,306,325,336,344],nevertheless:[26,51,86,126,156,180],new_alias:[154,245],new_arriv:233,new_channel:58,new_charact:231,new_coordin:235,new_datastor:86,new_destin:245,new_goto:328,new_hom:245,new_kei:[107,154,245,247,255],new_loc:[159,245],new_lock:[245,255],new_menu:180,new_nam:[107,159],new_name2:159,new_obj:[80,247,252,255],new_obj_lockstr:159,new_object:[109,252],new_permiss:245,new_raw_str:151,new_room_lockstr:159,new_ros:89,new_script:102,new_typeclass:[144,318],new_typeclass_path:125,new_valu:84,newbi:[25,48,55,124,174],newcom:[96,117],newer:9,newindex:215,newli:[46,58,60,66,131,133,159,175,180,199,204,234,247,252,259,324],newlin:[33,137,166,322,330],newnam:[33,159,318],newpassword:157,newstr:137,nexist:22,nexit:[120,127],next:[0,4,5,6,9,10,11,12,13,14,20,21,22,23,25,28,29,30,31,33,36,38,39,41,42,46,49,50,51,52,56,58,60,61,62,64,65,68,72,73,75,76,77,79,80,81,83,85,86,89,90,95,96,98,100,102,103,106,110,111,114,116,119,121,122,123,127,131,133,134,137,138,180,184,202,215,217,218,219,220,221,232,242,259,267,322,328,329,331,336,344,362,364],next_nod:51,next_turn:[217,218,219,220,221],nextrpi:79,nexu:45,nfkc:144,ng2:330,nginx:8,nice:[0,12,22,27,49,54,58,61,62,68,70,81,90,96,100,111,119,127,138,140,159,179,182,206,251,364],nicer:[20,60,96],niceti:159,nick:[2,11,43,45,57,74,79,89,129,139,144,146,159,165,206,241,246,247,279,316,317,336,364],nick_typ:87,nickhandl:[11,87,316],nicklist:[146,164,279],nicknam:[43,87,89,129,131,165,206,246,247,279,316,317],nickreplac:316,nicktemplateinvalid:[316,336],nicktyp:[206,247],nifti:8,night:[58,61,132,138,187],nine:66,nineti:345,nit:[60,62],nline:337,no_channel:[31,33,152,328],no_default:[125,144,318],no_exit:[31,33,116,152,224,230,328],no_gmcp:291,no_log:153,no_match:180,no_mccp:280,no_more_weapons_msg:232,no_msdp:291,no_mssp:281,no_mxp:282,no_naw:283,no_obj:[31,152,224,230,328],no_superuser_bypass:[144,175,242,247,318],no_tel:80,noansi:170,nobj:120,nocaptcha:133,nocaptcha_recaptcha:133,nocolor:[81,272,287,290,295,296],nodaemon:106,node1:[51,328],node2:[51,328],node3:[51,328],node:[13,85,109,188,215,230,249,265,328,364],node_abort:51,node_apply_diff:249,node_attack:51,node_background:51,node_betrayal_background:51,node_border_char:328,node_destin:249,node_examine_ent:249,node_exit:51,node_formatt:[51,188,328],node_four:51,node_game_index_field:265,node_game_index_start:265,node_hom:249,node_index:[249,328],node_kei:249,node_loc:249,node_login:51,node_matching_the_choic:51,node_mssp_start:265,node_mylist:51,node_on:51,node_parse_input:51,node_password:51,node_prototype_desc:249,node_prototype_kei:249,node_prototype_sav:249,node_prototype_spawn:249,node_readus:51,node_select:51,node_set_nam:51,node_start:265,node_test:51,node_text:51,node_usernam:51,node_validate_prototyp:249,node_view_and_apply_set:265,node_view_sheet:51,node_violent_background:51,node_with_other_nam:328,nodefunc1:51,nodefunc2:51,nodefunc:328,nodekei:328,nodenam:[51,328],nodename_to_goto:51,nodestartfunc:51,nodetext:[51,188,249,328],nodetext_formatt:[51,188,249,328],noecho:169,noerror:247,nofound_str:[144,206,247,344],nogoahead:289,nohom:324,nois:21,noisi:[90,264,269,277,287,290,312],noloc:159,nomarkup:[74,81],nomatch:[22,168,180,326,336,344],nomatch_exit:22,nomatch_single_exit:22,nomigr:127,nomin:362,non:[4,6,14,15,20,22,27,29,31,33,38,44,49,50,52,55,58,61,62,63,64,65,68,70,74,82,86,88,102,105,109,110,114,122,124,125,126,131,137,139,140,144,146,147,148,150,152,159,169,171,175,177,185,195,204,212,214,215,232,238,246,247,251,252,255,256,257,258,259,261,267,276,290,291,305,306,308,316,318,321,324,325,326,328,330,336,341,344,364],nonc:295,nondatabas:[11,306,318],none:[0,1,2,10,11,13,14,15,22,25,30,31,33,34,39,40,41,42,44,49,50,51,56,58,60,62,64,69,74,77,80,81,83,84,85,86,87,88,91,96,102,105,111,112,114,116,118,119,121,123,144,145,146,147,150,151,152,153,154,156,159,160,161,162,163,166,167,168,170,173,174,175,176,177,179,180,181,182,185,187,188,189,192,194,195,198,203,204,205,206,212,214,215,217,218,219,220,221,224,230,231,232,233,234,235,237,238,241,242,244,245,246,247,249,250,251,252,254,255,257,258,259,260,261,264,265,267,269,273,276,277,278,279,286,287,295,296,306,307,308,310,311,312,315,316,317,318,319,321,322,323,324,325,326,327,328,329,330,331,334,336,337,339,340,341,344,345,349,352,357,362],nonpc:123,nonsens:205,noon:[20,60,73,76,80,96],nop:[290,364],nopkeepal:[24,290],nor:[11,13,29,31,42,54,106,108,116,126,185,186,234,251,291,316,319],norecapcha:133,norecaptcha_secret_kei:133,norecaptcha_site_kei:133,norecaptchafield:133,normal:[2,3,5,6,9,10,11,13,14,15,19,20,21,23,25,27,29,30,31,33,34,38,44,46,49,51,55,56,57,58,60,62,64,66,68,69,72,74,75,76,80,81,82,83,85,86,87,88,90,93,96,97,100,102,104,105,109,110,111,112,113,114,116,119,121,122,123,125,126,127,128,134,135,137,138,140,144,146,148,150,151,152,153,154,156,159,166,169,174,175,179,184,185,217,218,219,220,221,231,234,235,246,247,249,252,259,261,267,276,279,280,281,283,285,299,306,308,314,316,317,318,321,322,325,328,329,334,336,341,343,344,346],normal_turn_end:116,normalize_usernam:144,north:[0,20,22,44,46,49,89,111,114,121,159,180,213,299],north_south:111,northeast:[20,159,235],northern:[22,111],northwest:159,not_don:312,not_error:267,not_found:159,notabl:[6,9,10,40,63,97,131,154,159,170,179,318,325,326,336,344],notat:[119,159,321,344],notdatabas:125,note:[0,1,2,4,5,6,9,11,12,13,19,20,21,23,24,25,27,29,41,42,43,48,49,57,58,59,60,61,62,63,64,69,70,73,74,75,76,80,83,85,86,88,89,90,93,95,96,100,102,103,105,106,107,109,110,113,114,115,116,117,119,121,123,124,125,126,128,130,131,133,134,135,136,137,141,144,146,151,152,153,154,156,159,160,161,165,166,167,169,170,171,174,175,176,179,181,182,183,184,185,186,187,189,194,195,198,202,203,204,205,206,212,213,215,217,218,219,220,221,224,226,227,233,234,235,241,242,245,246,247,251,252,255,259,261,264,267,272,276,277,279,280,284,285,286,287,290,291,292,294,295,298,300,301,306,308,312,313,316,317,318,319,321,322,323,324,325,326,327,328,329,330,331,334,336,337,339,340,341,342,344,350,364],notepad:63,noteworthi:38,notfound:344,notgm:58,noth:[0,10,11,14,20,22,27,29,33,34,42,56,57,60,62,83,85,89,95,108,111,115,116,127,144,159,168,215,217,220,221,231,235,247,259,279,318,328],nother:120,notic:[0,10,12,13,20,22,23,29,33,36,37,39,41,42,46,62,69,70,91,96,117,121,126,127,131,180,223,280,362],notif:[4,75,131,137,138,199],notifi:[98,164,217,218,219,220,221,233,251],notificationsconfig:4,notimplementederror:290,notion:[62,115,116],noun:[205,206],noun_postfix:205,noun_prefix:205,noun_transl:205,now:[0,2,3,5,6,9,10,11,12,14,20,21,22,23,25,27,28,29,31,33,36,39,41,46,48,49,51,55,56,57,58,60,61,62,63,64,65,67,69,71,72,73,75,76,77,79,80,81,82,83,85,86,89,90,91,95,96,97,98,100,102,103,105,106,108,109,110,111,114,115,117,118,119,121,123,125,126,127,128,131,133,134,135,136,137,138,140,153,179,184,188,195,215,226,235,242,247,279,287,308,340,342,344],nowher:[95,111],noxterm256:290,npc:[9,33,46,51,61,64,73,111,119,124,139,179,214,241,247,364],npcname:118,npcshop:85,nprot:120,nr_start:[255,258],nr_stop:255,nroom:[22,120],nroom_desc:127,nrow:330,ntf:63,nuanc:114,nudg:[78,224,227,312],nuisanc:103,nulla:52,num:[49,80,206,247],num_lines_to_append:337,num_object:119,num_objects__gt:119,num_tag:119,num_total_account:147,number:[0,6,10,11,12,13,20,21,23,25,26,27,31,33,34,36,38,41,49,50,51,57,58,60,61,62,64,71,73,77,81,85,87,90,93,95,96,97,98,100,102,104,105,107,111,112,114,115,116,119,120,122,123,125,127,131,134,135,140,141,144,146,147,151,152,153,157,159,164,165,174,176,177,182,184,185,188,190,192,194,195,198,204,205,206,215,217,218,219,220,221,245,247,250,251,252,258,259,265,267,272,278,279,281,285,298,308,310,312,316,317,319,321,322,324,326,328,329,330,331,334,336,337,341,344,357,364],number_of_dummi:267,number_tweet_output:120,numbertweetoutput:120,numer:[61,73,97,190,321],numpi:300,o_o:138,obelisk:232,obfusc:[205,206],obfuscate_languag:[205,206],obfuscate_whisp:[205,206],obj1:[11,80,97,109,159,203,221],obj2:[11,80,97,109,127,159,203,221,322],obj3:[11,109,159],obj4:11,obj5:11,obj:[2,6,10,11,22,25,27,31,33,41,42,48,56,58,59,60,80,82,84,86,87,89,91,96,102,109,112,115,117,119,121,125,127,139,144,145,152,153,154,157,159,165,167,168,169,170,173,174,176,180,182,187,188,189,192,194,195,198,199,203,206,215,217,218,219,220,221,224,227,232,233,235,241,242,244,246,247,250,252,254,255,256,257,258,259,296,298,299,306,315,316,317,318,319,322,324,325,329,339,340,341,344],obj_desc:220,obj_detail:233,obj_kei:220,obj_prototyp:252,obj_to_chang:125,obj_typeclass:220,objattr:[232,241],objclass:[334,344],object1:33,object2:[33,179,247],object:[0,2,9,10,12,13,14,15,18,19,21,22,23,26,29,30,31,33,34,36,38,39,40,41,42,43,44,45,46,47,49,50,51,52,53,55,56,57,58,62,69,73,74,77,79,81,83,84,85,86,87,88,91,93,95,102,103,104,107,108,109,110,114,115,116,117,118,120,122,123,125,127,129,132,133,134,135,137,138,139,140,141,142,143,144,145,146,147,148,150,151,152,153,154,156,157,158,159,160,161,164,165,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,186,187,188,189,192,193,194,195,196,198,199,203,204,206,209,210,211,212,213,214,215,217,218,219,220,221,223,224,226,227,229,230,231,233,234,235,237,238,239,241,242,249,250,251,252,253,254,255,256,257,258,259,260,261,265,267,269,271,272,273,274,276,277,280,281,282,283,284,285,286,287,289,291,294,296,298,299,305,306,307,308,310,311,312,315,316,317,318,319,321,322,323,324,325,326,327,328,329,330,334,335,336,338,339,340,341,342,343,344,345,349,351,357,360,362,364],object_confirm_delet:362,object_detail:362,object_from_modul:344,object_id:134,object_search:[134,245],object_subscription_set:246,object_tot:[147,245,255,317],object_typeclass:[342,360],objectattributeinlin:244,objectcr:357,objectcreateform:244,objectcreateview:362,objectdb:[11,53,59,96,112,119,120,125,133,141,244,246,247,252,314,315,316,324,329,341],objectdb_db_attribut:244,objectdb_db_tag:[244,315],objectdb_set:[148,316,319],objectdbadmin:244,objectdbmanag:[245,246],objectdeleteview:362,objectdetailview:362,objectdoesnotexist:[148,177,239,246,256,274,316,319,335],objecteditform:244,objectform:357,objectmanag:[245,247,317],objectnam:[6,58],objects_objectdb:86,objectsessionhandl:[2,247],objecttaginlin:244,objectupd:357,objectupdateview:362,objid:80,objlist:[109,250],objlocattr:[232,241],objmanip:159,objmanipcommand:159,objnam:[27,125,159],objparam:252,objs2:112,objsparam:252,objtag:241,objtyp:176,obnoxi:269,obs:318,obscur:[48,72,82,205,206],observ:[13,14,20,81,88,159,165,187,206,223,227,233,291,322,344],obtain:[0,33,39,63,77,90,91,93,100,180,232,364],obviou:[0,59,61,103,121,128,138,190,362],obvious:[0,4,14,49,55,105,108,121,319],occaecat:52,occas:128,occasion:[90,119],occation:330,occur:[9,10,25,33,42,57,60,102,137,168,175,204,219,234,242,247,260,299,328,337],occurr:[46,91,123,321],ocean:[90,122],oct:364,octob:364,ocw:124,odd:[22,49,61,103,126],odor:58,off:[0,11,14,20,23,24,29,31,33,36,40,41,49,50,51,55,61,64,66,74,80,81,86,88,90,100,103,107,108,110,114,115,123,126,135,138,139,144,154,164,169,170,171,174,175,182,188,206,227,231,233,242,247,272,280,287,290,306,318,321,322,324,326,328,329,330,336,337,345,364],off_bal:29,offend:12,offer:[1,4,11,14,22,26,28,31,33,34,37,39,40,44,50,51,55,56,57,59,62,64,72,73,74,76,83,86,87,89,90,91,96,102,106,108,109,111,114,115,116,123,124,127,128,129,131,132,137,138,152,153,158,159,169,179,180,187,205,233,249,257,308,326,328],offernam:179,offici:[38,72,100,103,127,131,337,364],officia:52,offlin:[9,15,79,90,109,158,175,322],offscreen:9,offset:[206,326,337],often:[2,5,10,11,15,22,26,28,31,33,38,40,41,42,46,48,49,51,57,59,61,62,64,76,86,88,90,91,93,95,96,97,102,103,104,105,112,114,115,116,119,128,131,146,152,157,167,168,169,175,180,215,217,218,219,220,221,224,226,242,246,256,258,267,272,286,306,316,318,322,324,330,337],ohloh:37,okai:[41,42,48,49,51,58,75,77,111,123,128,198],olc:[43,47,159,249,252,364],olcmenu:249,old:[0,1,5,9,21,25,27,31,38,39,50,51,55,56,58,60,63,80,81,85,88,90,105,106,111,114,122,123,125,126,128,138,144,152,153,156,159,174,179,206,242,247,252,276,317,318,321,324,363],old_default_set:127,old_kei:[107,247],old_nam:107,older:[2,9,24,55,63,64,79,105,159],oldnam:318,oliv:114,omiss:60,omit:[91,100,109],ommand:150,on_:180,on_bad_request:269,on_ent:[22,180],on_leav:[22,180],on_nomatch:[22,180],onam:245,onbeforeunload:[83,137],onbuild:100,onc:[0,2,5,6,9,10,13,16,21,22,23,25,33,34,37,38,39,40,41,42,46,47,49,51,55,57,58,60,61,62,63,64,67,72,79,80,83,85,89,90,93,95,96,97,100,102,105,108,114,116,119,121,122,125,126,128,131,133,137,144,146,151,154,159,164,167,168,170,175,179,180,188,189,195,199,203,205,212,215,217,218,219,220,221,223,227,231,232,233,234,235,247,251,256,259,272,277,290,294,305,321,328,329,337,342,344],onclos:[40,278,295],onconnectionclos:[83,137],oncustomfunc:83,ond:319,ondefault:83,one:[0,1,2,3,4,5,9,10,11,12,13,14,15,16,19,20,21,22,23,25,26,27,28,29,31,33,34,35,36,37,41,42,44,46,47,48,49,50,51,52,54,55,56,57,58,59,60,61,62,63,64,65,67,68,69,70,72,73,74,76,77,79,80,81,82,83,85,86,87,88,89,90,91,92,93,95,96,97,98,100,102,103,104,105,106,108,109,111,112,113,114,115,116,118,119,121,122,123,125,126,127,128,131,132,133,134,135,136,137,138,140,143,144,148,151,152,153,154,156,157,159,165,168,170,174,175,176,177,179,180,182,185,187,189,195,198,199,204,205,206,214,215,217,218,219,220,221,224,227,230,232,233,234,235,238,239,241,242,244,245,246,247,249,250,251,252,255,256,261,267,269,271,272,277,278,279,287,290,291,306,307,308,312,314,316,317,318,321,322,324,325,326,327,328,329,330,331,334,335,336,337,339,340,341,342,344,345,357,360,362,364],ones:[4,9,14,20,22,27,31,33,43,57,58,65,72,74,80,81,83,90,95,100,103,109,114,116,126,127,135,152,153,154,177,180,195,217,218,219,220,221,241,251,252,271,276,308,321,330,338],onewai:159,ongo:[28,91,116,179,213],ongotopt:[83,137],onkeydown:[83,137],onli:[0,2,4,5,6,9,10,11,12,13,14,15,19,20,21,22,24,25,26,27,28,29,31,33,34,37,39,40,41,42,44,46,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,67,68,69,71,72,73,74,77,79,80,81,82,83,85,86,87,88,89,90,91,93,95,96,100,102,103,104,105,106,107,109,111,112,114,116,117,118,119,121,122,123,124,125,126,127,130,131,132,133,134,135,136,137,138,140,141,144,145,146,147,150,151,152,153,154,156,157,158,159,164,165,166,167,168,169,170,171,175,176,177,179,180,181,182,185,187,188,190,195,199,205,206,214,215,217,218,219,220,221,223,226,227,232,233,234,235,239,241,242,245,247,250,251,252,255,256,258,259,260,261,267,271,272,279,282,284,285,287,290,299,305,306,308,310,311,312,315,316,317,318,319,321,322,323,324,326,328,329,330,334,336,337,339,340,341,342,344,357,362,364],onlin:[7,12,15,21,37,41,55,57,58,60,61,64,65,68,69,70,71,73,77,79,89,96,98,101,104,108,116,123,128,129,139,141,156,164,175,180,188,281,322,364],onloggedin:[83,137],onlook:247,only_tim:[255,341],only_valid:252,onmessag:[40,278,295],onopen:[40,278,295],onoptionsui:137,onprompt:[83,137],onsend:[83,137],onset:[5,11],onsil:83,ontabcr:137,ontext:[83,137],onto:[25,31,33,44,55,60,61,72,90,95,121,137,153,224,233,246,279,325,328],onunknowncmd:137,onward:107,oob:[24,30,33,45,83,104,137,138,139,144,146,166,189,247,272,290,291,295,296,308,328,364],oobfunc:104,oobhandl:334,oobobject:102,ooc:[2,43,53,58,102,105,114,123,144,148,156,159,160,164,167,177,181,199,247],ooccmdsetchargen:181,ooclook:[105,181,329],opaqu:[15,103],open:[0,3,4,5,9,20,22,23,26,31,34,37,38,42,43,46,50,55,57,58,60,63,64,65,67,69,70,71,72,73,75,79,80,90,95,96,103,105,106,111,114,116,123,130,131,133,134,138,159,166,169,179,180,188,212,213,221,224,226,227,232,241,310,316,324,337,363,364],open_lid:226,open_parent_menu:180,open_submenu:[22,180],open_wal:232,openhatch:79,openlidst:227,openlock:241,opensoci:70,opensourc:321,oper:[9,11,12,14,22,27,33,41,42,46,51,57,59,60,61,63,64,67,72,74,80,82,88,89,90,95,96,97,102,109,110,112,115,119,124,126,131,137,139,144,150,152,154,156,159,164,169,175,180,185,206,227,232,242,247,250,252,261,264,267,276,277,281,283,287,289,290,296,298,299,306,307,317,318,321,324,328,329,330,334,344,364],opinion:[1,48],oppon:[11,73,218,220,231],opportun:[0,4,22,91,133,221],oppos:[27,89,103,110,114,306,319],opposit:[41,58,111,121,159,224],opt:[58,137,234],optim:[23,27,33,34,39,56,64,86,93,115,119,154,251,252,302,305,316,329],option100:51,option10:51,option11:51,option12:51,option13:51,option14:51,option1:51,option2:51,option3:51,option4:51,option5:51,option6:51,option7:51,option8:51,option9:51,option:[2,4,7,8,10,11,17,20,23,24,25,27,29,31,33,34,36,38,41,42,43,47,50,54,55,57,62,63,64,74,76,79,80,81,83,85,86,96,100,102,104,106,108,109,111,112,113,114,116,117,123,127,129,133,134,135,137,138,139,141,144,145,146,147,150,151,152,153,154,156,157,159,164,167,168,170,171,173,174,175,176,177,179,180,181,182,184,185,187,188,189,190,192,194,195,199,203,204,205,206,214,215,219,221,230,233,234,235,237,238,241,242,244,245,246,247,249,251,252,254,255,256,257,258,259,260,261,263,264,265,267,269,272,273,276,277,279,280,281,282,283,284,285,286,287,289,290,291,294,295,296,298,299,306,308,310,315,316,317,318,319,321,322,323,324,326,327,328,329,330,331,334,336,337,338,339,340,341,343,344,345,349,364],option_class:[141,323],option_dict:328,option_gener:328,option_kei:345,option_str:234,option_typ:339,option_valu:339,optiona:[144,264,318],optionalposit:1,optionclass:[141,142,320,323,364],optioncontain:323,optionhandl:[141,142,320,338,364],optionlist:[51,230,249,328],options2:137,options_dict:339,options_formatt:[51,188,230,249,328],optionsl:251,optionslist:230,optionstext:[51,188,328],optlist:215,optlist_to_menuopt:215,optuon:205,oracl:[23,344],orang:[114,203,234],orc:[57,61,109,117],orc_shaman:109,orchestr:100,order:[0,2,5,6,9,10,11,13,14,22,27,31,33,36,37,39,44,49,50,51,58,60,61,62,63,64,68,69,70,71,80,84,87,89,93,100,102,104,109,111,113,114,116,119,121,122,123,126,127,128,131,133,134,136,137,138,144,150,153,154,160,165,166,169,170,173,179,180,181,182,183,185,188,203,204,206,217,218,219,220,221,227,231,232,233,234,237,241,242,244,245,247,252,254,263,278,290,295,299,306,316,318,321,322,328,329,330,337,341,344,362],order_bi:119,order_clothes_list:182,ordered_clothes_list:182,ordered_permutation_regex:206,ordered_plugin:83,ordereddict:[11,344],ordin:321,org:[37,38,90,204,234,283,289,295,321,344,357],organ:[5,6,9,22,38,69,73,80,89,102,108,111,112,119,124,129,131,132,154,170],organiz:102,orient:[55,57,64,96,124],origin:[0,4,9,21,25,29,41,49,51,55,57,60,75,76,79,81,89,91,96,102,103,105,106,119,131,136,138,146,152,159,180,199,205,206,234,245,247,251,252,255,276,318,321,328,336,340,343,363],original_object:245,original_script:255,oscar:[175,239,318],osnam:344,oss:106,ostr:[144,147,176,238,245,255,341],osx:[63,131],other:[0,1,2,4,5,6,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,25,27,28,29,31,34,36,37,38,39,40,41,44,46,47,48,49,50,51,53,55,57,58,59,60,61,62,63,64,65,68,69,70,71,73,74,76,80,81,82,83,85,86,87,88,89,91,95,96,97,100,102,103,105,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,123,124,125,126,127,128,131,133,134,135,136,137,138,139,140,144,150,151,152,153,154,159,165,166,167,170,171,176,179,182,184,186,188,194,199,205,206,210,212,215,217,218,219,220,221,224,227,233,234,235,239,242,246,247,251,252,255,257,259,261,265,271,272,276,278,279,285,287,290,299,306,307,309,316,318,320,321,322,324,326,327,328,329,330,336,338,339,341,344,345,362,364],otherroom:212,otherwis:[0,4,11,15,23,25,27,29,31,33,37,39,41,42,51,59,62,68,69,76,78,83,86,89,90,91,95,97,100,102,103,105,109,114,121,123,131,135,141,147,151,152,156,159,175,179,183,187,188,192,195,206,217,218,219,220,221,235,242,247,250,251,252,259,260,267,278,279,287,306,310,311,315,321,328,329,336,337,341,342,344,362],otypeclass_path:245,our:[2,3,4,8,9,11,14,16,20,21,23,25,26,30,31,33,36,37,38,39,40,41,42,43,44,46,49,55,57,58,59,60,61,62,63,64,67,70,72,73,75,77,78,79,80,81,82,83,85,88,90,91,98,100,101,103,111,115,116,117,119,123,124,127,128,129,131,132,134,135,136,137,138,140,148,153,167,168,187,215,231,232,235,242,257,312,315,337,363,364],ourself:123,ourselv:[0,20,58,80,87,118,132,138,144,181,280,281,283,294],out:[0,1,3,6,8,9,10,12,13,14,15,16,17,19,20,21,22,23,26,28,29,33,34,37,38,39,41,42,44,45,46,47,48,49,51,53,54,55,56,57,59,60,61,62,63,64,66,69,70,71,77,79,80,86,88,89,90,91,93,95,96,97,100,102,104,105,108,109,111,114,116,117,118,119,121,122,123,126,127,129,131,133,135,137,138,139,143,144,151,152,156,158,159,179,181,184,186,188,199,205,206,209,210,212,213,217,218,219,220,221,227,230,232,241,251,252,259,267,269,291,295,296,298,307,308,315,316,325,327,328,330,336,343,344,357,364],out_templ:[316,336],outcom:[38,73,86,152,185,242,247,251],outdat:8,outdata:[40,308],outdoor:[112,119,122,132,233],outer:330,outermost:[11,29,74],outerwear:182,outfunc_nam:40,outgo:[67,88,90,96,105,146,247,279,291,307,344,364],outgoing_port:90,outlet:90,outlin:[36,43,111,133,278],outmessag:247,output:[4,14,20,22,26,27,34,40,51,52,58,74,79,88,91,95,96,100,105,106,108,110,111,113,114,116,120,121,123,126,128,129,135,137,138,141,142,154,159,166,169,170,171,178,180,184,189,207,208,210,217,218,219,220,221,251,267,272,287,291,299,306,321,328,329,337,340,344,364],outputcmd:291,outputcommand:[74,83,364],outputfunc:[40,59,83,247,272,278,364],outputfunc_nam:[40,272],outputfunct:83,outrank:317,outright:[12,90,363],outro:[122,233],outroroom:233,outsid:[0,13,15,20,21,38,39,57,64,67,73,88,96,100,104,108,109,110,112,121,134,204,220,231,241,291,306,307,316,319,330,364],outtempl:[316,336],outtxt:27,outward:[49,90],over:[1,6,8,11,13,14,15,16,17,27,28,31,33,34,36,37,38,39,40,45,48,49,51,54,57,58,59,60,61,73,77,81,83,85,88,90,93,96,97,100,103,105,108,111,112,113,114,115,116,118,119,125,126,127,128,129,133,136,137,138,144,153,164,174,176,188,212,215,217,218,219,220,221,227,233,261,271,285,287,290,292,296,298,300,313,318,322,329,334,340,362,363],overal:[10,56,57,68,71,86,90,152,167,168,218,364],overcom:111,overhead:[23,27,34,113,132,206,235,316],overhear:205,overlap:[31,62,205,321,330],overload:[5,22,30,31,33,40,44,47,51,55,57,60,74,76,89,96,97,104,114,115,117,123,136,144,152,154,168,175,180,181,187,189,203,206,212,213,217,218,219,220,221,230,231,232,233,234,247,252,261,271,290,307,326,328,329,330,338,364],overrid:[1,3,4,9,20,21,22,25,31,36,51,53,54,68,69,80,83,91,96,102,105,107,109,117,118,121,135,137,144,154,159,166,170,175,176,180,187,195,205,219,221,233,234,242,247,252,259,290,308,312,315,328,329,334,337,338,341,362,364],overridden:[4,40,96,136,138,144,159,180,234,329,362],override_set:107,overriden:[144,166,206],overrod:16,overrul:[2,80,144,153,206,247,330],overseen:73,overshadow:61,overshoot:344,oversight:57,overview:[15,16,18,23,45,46,57,68,77,96,103,139,364],overwhelm:[46,61],overwrit:[5,76,136,138,159,166,285,317,362],overwritten:[33,134,233,319],owasp:357,own:[1,3,4,5,6,8,9,10,11,13,17,19,20,21,22,25,26,27,29,30,31,34,37,38,41,43,45,47,51,55,57,61,62,63,64,67,68,71,72,75,76,77,78,80,81,83,85,86,87,88,91,93,95,96,98,101,102,103,104,105,107,108,109,111,112,114,119,121,122,123,124,125,127,128,129,131,132,133,134,135,136,138,139,148,150,151,152,153,159,164,167,182,184,187,188,199,205,206,210,217,218,219,220,221,232,234,235,241,242,247,252,272,299,307,318,321,322,323,329,330,334,337,338,342,344,362,364],owner:[4,19,80,85,144,242,338],owner_object:80,ownership:[90,100],p_id:133,pace:[122,231],pack:[83,276],packag:[8,9,23,38,41,47,63,64,72,75,78,88,90,93,96,97,100,108,127,128,135,141,143,149,155,172,178,229,236,240,243,253,262,267,276,291,295,314,320,346],package_nam:64,packagenam:64,packed_data:276,packeddict:[97,318],packedlist:[97,318],packet:[83,287],pad:[17,114,321,330,336,344],pad_bottom:330,pad_char:330,pad_left:330,pad_right:330,pad_top:330,pad_width:330,page:[7,8,9,12,13,14,16,17,20,21,23,25,26,28,31,33,36,37,38,40,43,45,48,51,52,55,57,58,59,60,61,64,67,70,72,73,75,76,77,79,80,81,88,89,90,94,96,99,100,101,103,104,106,108,110,124,125,126,127,129,130,131,133,134,137,138,139,164,165,175,239,241,244,251,254,296,315,318,328,329,344,346,355,362,363,364],page_back:329,page_ban:[12,164],page_end:329,page_formatt:[251,329],page_next:329,page_quit:329,page_titl:362,page_top:329,pagelock:241,pageno:[251,329],pager:[52,139,329],pages:[51,328],pagin:[251,329],paginag:329,paginate_bi:362,paginated_db_queri:251,paginator_django:329,paginator_index:329,paginator_slic:329,pai:[56,85,90,103,232,241],paid:90,pain:[90,138],painstakingli:13,pair:[31,83,116,137,138,144,152,182,241,247,306,308,357,362],pal:87,palett:126,pallet:111,palm:188,pane:[88,137,138,171,186,230],panel:[67,106],panic:109,paper:[61,79,116],paperback:73,par:23,paradigm:[9,61,118,218],paragraph:[14,27,202,322,330,344],parallel:[57,62,69,317],paralyz:219,param:[67,159,247,259,261,269,279,312,337,345],paramat:[144,154,247,306],paramet:[0,22,24,31,36,39,42,46,49,62,91,100,106,119,127,141,144,145,146,147,150,151,152,153,154,159,166,173,174,175,176,177,179,180,182,184,185,187,188,189,190,192,193,194,195,198,199,204,205,206,209,210,212,215,217,218,219,220,221,226,230,233,234,235,238,242,244,245,246,247,249,251,252,254,255,257,258,259,260,261,264,265,266,267,269,271,272,273,274,276,277,278,279,280,281,282,283,284,285,286,287,289,290,291,292,294,295,296,298,304,305,306,307,308,310,311,312,316,317,318,319,321,322,323,324,325,326,327,328,329,330,331,334,336,337,338,339,341,342,343,344,345,349],paramount:127,paramt:345,paremt:252,parent1:109,parent2:109,parent:[2,6,22,25,27,31,33,38,40,43,44,60,64,81,89,96,109,114,118,121,123,125,140,148,156,159,167,169,180,206,215,234,241,246,247,251,252,256,317,318,326,335,337,344,362,364],parent_categori:215,parent_kei:[22,180],parent_model:[145,173,237,244,254,315],parentesi:336,parenthes:95,parentlock:241,pari:[79,90],pariatur:52,paricular:33,park:180,parlanc:3,parri:[116,232],parrot:118,pars:[3,15,31,33,38,40,50,51,63,81,83,88,97,104,108,109,114,123,124,129,134,139,149,150,151,154,159,165,166,167,168,169,170,174,179,180,185,186,187,199,206,209,210,211,215,232,233,234,242,247,250,251,252,272,279,282,291,295,296,316,321,322,326,327,328,336,343,344,364],parse_ansi:321,parse_ansi_to_irc:279,parse_fil:322,parse_html:343,parse_inlinefunc:336,parse_input:328,parse_irc_to_ansi:279,parse_languag:206,parse_menu_templ:[51,328],parse_nick_templ:[316,336],parse_opt:215,parse_sdescs_and_recog:206,parseabl:251,parsed_str:279,parseerror:234,parser:[33,38,41,47,79,104,108,109,134,150,151,156,159,167,168,169,171,174,186,187,203,205,206,232,233,234,250,251,286,321,336,343],parsestack:336,part1:203,part2:203,part:[1,4,5,9,11,13,14,15,16,20,22,23,26,29,33,36,37,38,39,40,41,42,44,45,46,48,49,51,57,58,60,61,68,69,70,73,76,80,85,86,88,90,91,92,95,102,105,106,111,114,116,117,119,122,123,124,125,127,131,135,136,137,138,139,140,151,152,154,167,168,170,175,179,180,185,203,206,215,220,233,238,241,242,250,251,267,271,296,307,310,312,317,321,322,326,328,336,344,364],part_a:179,part_b:179,parth:292,parti:[8,9,13,23,27,37,42,64,72,75,90,101,114,128,134,177,179,185,364],partial:[25,68,205,245,251,269,282,308,339,341,344,345],particip:[41,103,217,218,219,220,221],particular:[5,8,12,13,14,20,22,28,31,38,40,41,44,48,58,59,64,68,70,72,74,75,79,80,83,85,88,89,93,96,97,104,105,107,112,113,114,118,119,121,124,125,131,132,135,139,144,147,151,152,159,176,187,210,219,220,227,238,241,242,247,255,256,308,310,318,334,341,362],particularli:[0,4,12,38,39,51,55,127,154,167,170,206,252,271],partit:321,partli:[11,31,47,86,129,152],party_oth:179,pass:[4,10,21,23,25,27,28,29,30,33,36,40,49,51,52,62,69,74,80,82,83,85,88,90,91,95,96,100,102,105,107,109,110,111,115,117,119,121,125,127,130,134,138,139,144,146,152,171,182,184,185,188,189,194,209,210,212,215,217,218,219,220,221,232,241,242,247,251,257,260,261,265,277,285,287,290,295,296,306,312,316,318,327,328,329,330,336,337,338,339,340,343,344,362],passag:[83,116,182,232,233,331],passant:126,passavataridterminalrealm:287,passiv:[29,116,133],passthrough:[1,31,259],password1:[145,357],password2:[145,357],password:[4,9,12,23,35,36,43,51,64,74,80,103,131,139,144,145,156,157,171,186,204,210,272,287,290,311,324,349,357],password_chang:360,passwordresettest:360,past:[0,13,20,26,37,46,50,58,62,69,96,104,108,111,116,123,133,137,147,219,313,322,331,362],pastebin:37,pat:245,patch:[125,342,364],patch_:37,path:[0,2,4,8,14,20,21,22,27,29,38,39,40,45,48,51,59,60,63,64,66,67,69,74,80,85,86,88,89,90,95,96,100,102,105,106,109,114,117,118,119,121,123,124,125,134,135,136,138,139,144,146,148,151,152,153,158,159,160,161,162,163,164,169,175,177,179,180,181,182,184,185,187,189,195,198,203,204,205,206,212,213,214,217,218,219,220,221,223,224,226,227,230,231,232,233,235,239,245,246,247,251,252,255,256,258,259,261,267,274,276,285,292,298,300,304,308,312,316,317,318,322,324,326,327,328,329,331,334,335,341,344,362,364],path_or_typeclass:198,pathnam:342,patient:[20,70],patreon:70,patrol:231,patrolling_pac:231,patron:[37,70],pattern:[3,4,16,51,69,87,127,133,134,135,140,157,206,311,344],paul:125,paus:[10,39,46,51,100,102,110,116,194,259,260,364],pausabl:344,pauseproduc:269,pax:364,paxboard:79,payload:[278,295],paypal:[37,70],paypalobject:70,pdb:[139,141,364],pdbref:[80,241],pdf:79,peac:117,peek:[20,26,51,91],peer:[278,295],peform:272,peg:103,pem:67,pemit:[108,157],penalti:[86,219],pend:312,pennmush:[57,108,129,171],pentagon:103,peopl:[2,20,21,26,37,54,55,58,61,64,68,71,72,73,79,80,81,85,90,95,96,97,103,108,114,116,119,139,165,186,206,232,233,315,324],pep8:26,per:[2,4,11,19,33,38,41,47,51,58,60,62,64,69,83,86,89,93,100,105,109,112,116,119,123,138,144,175,187,205,217,218,219,220,221,231,245,251,280,281,283,291,294,310,328,329,330,334,337,338],perceiv:62,percent:33,percentag:[116,317],perception_method_test:303,perfect:[50,55,61,75,100,131],perfectli:[4,69,96,112,129,138,321],perform:[11,13,14,22,23,25,39,41,42,51,52,55,59,71,74,75,80,89,91,93,97,102,103,114,116,117,123,133,134,150,152,156,159,164,175,180,182,188,194,195,206,209,215,217,218,219,220,221,227,245,247,250,256,257,276,290,298,299,316,317,318,325,328,329,336,338,341,344,345,357],perhap:[16,22,42,46,62,69,77,91,97,108,138],period:[90,95,96,100,103,127,128,130,344],perist:[34,125],perm:[4,11,12,19,22,25,33,58,68,71,80,85,109,112,123,133,148,157,158,159,164,165,166,169,187,193,203,212,233,239,241,242,246,247,256,316,318],perm_abov:[80,241],perm_us:157,perman:[4,5,12,21,24,25,31,51,85,90,96,122,123,138,144,152,153,156,159,164,165,169,205,247,260,318],permiss:[2,4,7,8,9,11,12,18,20,21,23,25,31,41,45,66,68,70,71,75,93,108,109,123,133,139,144,145,147,148,152,154,156,157,158,159,165,167,168,175,193,206,221,239,241,242,246,247,251,252,256,316,317,318,319,322,324,337,341,362,364],permission_account_default:[80,298],permission_func_modul:241,permission_guest_default:66,permission_hierarchi:[19,80,241,242],permissionerror:251,permissionhandl:[133,319],permissionshandl:315,permit:[41,78,159,311],permstr:[80,144,318,324],permut:206,perpetu:[93,364],persis:29,persist:[0,6,21,22,27,31,33,34,51,55,56,57,60,64,79,84,86,89,102,104,105,109,110,115,116,121,123,125,144,148,159,169,175,176,177,180,184,188,195,205,206,213,215,217,218,219,220,221,227,230,232,239,246,247,249,250,251,255,256,257,258,259,260,261,272,273,274,305,306,314,318,324,326,328,330,331,344,364],person:[12,21,61,63,70,73,90,102,105,118,129,139,144,159,165,179,185,206,226,364],persona:96,perspect:[73,76,77,105],pertain:[103,126,136,350],pertin:[68,133],perus:137,peski:85,pester:[57,61],phase:[49,61],philosophi:80,phone:[16,64,75,139,204],phone_gener:204,phonem:205,php:[108,357],phrase:[46,198],phrase_ev:198,physic:[2,49,220,231],pick:[6,9,13,15,20,21,31,33,35,37,39,51,55,62,68,72,73,80,85,90,95,96,100,102,104,106,111,119,132,151,156,159,165,167,168,174,182,190,206,221,224,232,233,247,251,299],pickl:[11,29,83,115,257,261,264,274,276,277,316,317,325,326,328,340],pickle_protocol:340,pickledfield:[245,340],pickledformfield:[315,340],pickledobject:340,pickledobjectfield:340,pickledwidget:340,picklefield:[141,142,315,320,364],pickpocket:166,pickup:[221,247],pictur:[21,40,57,106,138,364],pid:[36,80,100,110,131,133,241,247,267,277,344],piddir:36,pidfil:267,piec:[10,13,59,61,64,93,122,203,294,322,329],pierc:232,piggyback:144,pile:[153,322],pillow:75,ping:[146,164,267,279],pink:119,pip:[9,23,26,38,42,47,59,63,65,71,75,93,96,97,98,100,127,128,130,133,141,364],pipe:[105,279,325],pitfal:[14,26,114,126],pixel:24,pizza:[148,177,239,246,256,316,318,319],pkg:75,pki:8,place:[0,2,3,4,5,8,9,11,14,15,20,21,25,26,30,37,41,46,49,51,55,62,63,64,69,71,73,75,76,80,83,89,90,91,95,96,100,102,103,104,105,109,111,121,123,124,126,128,129,131,132,133,135,136,138,144,157,159,165,179,180,182,184,188,203,206,209,217,218,219,220,221,232,233,235,247,255,259,276,285,290,306,307,308,322,323,325,328,344,364],placehold:[134,242,247,330],plai:[0,2,11,14,19,22,29,39,46,55,58,61,64,68,73,75,81,83,90,91,95,105,111,114,116,121,122,123,124,132,133,138,144,217,221,291,308,324,364],plain:[13,14,38,58,86,88,123,179,180,202,252,272,298,325,362],plaintext:210,plan:[9,14,15,40,41,42,45,55,56,90,96,100,124,125,127,139,322,364],plane:121,planet:[62,79],plant:234,plate:[82,125,204],platform:[9,16,56,63,90,102,106,131],playabl:[133,360],player:[9,10,11,12,19,20,21,22,25,29,31,34,40,41,51,53,54,55,58,60,61,64,65,68,71,73,77,80,81,83,85,90,91,93,95,97,98,105,108,110,111,112,113,116,117,118,119,120,121,122,123,124,133,138,139,153,156,159,164,169,176,179,180,188,190,198,199,203,205,206,210,214,215,220,221,233,234,235,238,255,256,281,290,307,322,327,344,357,362,364],playernam:71,playerornpc:9,pleas:[4,5,8,16,17,26,31,37,51,63,70,71,72,75,78,90,93,109,111,114,117,118,120,124,125,127,131,133,169,269,298,334,340,357,363],pleasur:16,plenti:[14,55,60,129],plot:300,plu:[22,27,64,73,106,169],pluck:33,plug:[96,103,107,136,235],plugin:[4,40,45,47,53,55,72,79,83,104,108,138,206,265,364],plugin_handl:[83,137],plugin_manag:137,plural:[19,58,80,220,247],png:[70,136],po1x1jbkiv_:37,pobject:226,pocoo:344,point:[0,2,4,5,8,13,14,15,20,21,22,25,27,29,31,33,34,36,37,38,39,42,49,51,55,56,60,61,62,63,67,69,73,75,81,83,85,86,88,89,90,91,93,95,97,100,102,104,105,106,112,113,115,116,121,123,125,127,130,131,133,134,135,136,138,139,144,150,154,159,167,168,169,179,189,206,212,217,233,234,235,247,249,251,261,267,271,285,287,295,306,308,315,316,318,322,328,336,344,362,364],pointer:[26,49,56,91],pointless:[6,10,89,115],poison:[219,252],poke:119,pole:203,polici:[43,45,90,103,139,210,239,311,316,364],polit:103,poll:[40,136,156,231,267,296],pong:279,pool:[23,31,115,261,312,325],poor:[48,58],poorli:103,pop:[10,23,25,38,48,58,85,106,138],popen:277,popul:[22,23,36,41,57,61,62,81,124,135,138,152,160,161,162,163,180,182,187,203,206,214,217,218,219,220,221,224,230,231,232,233,260,261,315,322,326,327,329,336],popular:[9,57,64,79,103,108,362],popup:[137,138],port:[0,8,9,23,36,54,55,63,67,72,100,101,110,146,164,276,279,287,299,308,312,364],portal:[40,45,47,53,79,88,89,90,93,103,104,106,110,121,128,137,139,141,142,146,169,183,262,264,267,305,306,307,308,331,337,344,364],portal_connect:308,portal_disconnect:308,portal_disconnect_al:308,portal_l:277,portal_pid:[277,344],portal_receive_adminserver2port:277,portal_receive_launcher2port:277,portal_receive_server2port:277,portal_receive_statu:277,portal_reset_serv:308,portal_restart_serv:308,portal_run:267,portal_service_plugin_modul:40,portal_services_plugin:[40,104],portal_services_plugin_modul:40,portal_sess:40,portal_session_sync:308,portal_sessions_sync:308,portal_shutdown:308,portal_st:267,portal_uptim:331,portallogobserv:337,portalsess:[40,105,285,364],portalsessiondata:308,portalsessionhandl:[40,141,142,262,275,286,308,364],portalsessionsdata:308,portion:[77,180,190],pose:[29,43,58,116,165,175,195,206],pose_transform:175,poser:175,posgresql:23,posit:[13,20,22,39,49,51,91,111,116,126,127,137,138,139,153,171,180,186,202,221,232,233,234,235,247,260,321,322,325,326,330,344,345,364],positive_integ:345,positiveinteg:338,posix:[337,344],possess:[7,77,189],possibl:[0,5,9,10,11,22,23,25,26,31,33,34,37,38,39,46,50,55,57,58,63,64,66,73,74,75,76,80,91,93,100,102,104,105,109,111,112,114,116,123,126,127,128,131,134,136,138,141,144,147,148,150,152,159,167,168,179,187,194,203,205,206,214,227,231,233,235,241,242,245,247,250,251,252,257,261,272,292,296,306,308,317,319,321,324,326,327,328,330,340,341,344,364],post:[5,31,34,37,55,57,58,61,63,69,70,71,80,98,107,111,120,133,136,210,296,362,364],post_delet:107,post_init:107,post_join_channel:175,post_leave_channel:175,post_migr:107,post_sav:107,post_send_messag:175,post_text:190,post_url_continu:[145,173,244],postfix:205,postgr:[23,64],postgresql:[55,344,364],postgresql_psycopg2:23,postinit:[83,137],posttext:188,postupd:[71,120],pot:12,potato:[24,234],potenti:[10,11,13,26,41,82,83,90,98,111,114,116,123,154,176,210,211,241,242,247,251,338,341,344],potion:[77,318],power:[15,19,20,29,30,31,33,42,46,50,51,55,56,58,61,64,80,89,96,109,111,116,122,123,137,138,152,153,158,159,215,220,234,322,328,344],powerfulli:0,pperm:[12,41,71,80,133,156,164,203,241,247],pperm_abov:241,pprofil:267,pprogram:267,practial:15,practic:[0,13,14,22,26,29,33,34,36,37,57,58,63,64,70,80,89,90,96,105,109,119,124,126,131,139,322,364],pre:[33,47,49,54,61,63,71,89,90,111,114,138,144,159,166,205,242,247,251,252,295,296,326,340],pre_delet:107,pre_init:107,pre_join_channel:175,pre_leave_channel:175,pre_migr:107,pre_sav:[107,340],pre_send_messag:175,pre_text:190,preced:[19,31,41,96,109,114,119,152,154,174,215,247,252,317,330],precend:150,precis:[11,96,126,321],predefin:[121,311],predict:[125,133],prefac:119,prefer:[21,22,23,31,37,47,55,57,71,80,90,91,96,106,109,111,123,131,137,138,152,154,157,175,180,206,218,231,238,245,247,318],prefix:[20,22,23,42,76,86,97,103,125,145,151,168,175,190,205,237,244,245,272,279,315,321,336,337,341,344,357],prefix_str:25,prematur:[27,93,179,259],prepai:90,prepar:[3,49,57,87,109,127,136,144,206,217,218,219,220,221,231,256,325,340,363,364],prepars:38,prepend:[199,206,247,321,322,328,344],prepopul:[315,362],preprocess:159,prerequisit:[9,36,364],prescrib:[55,57],preselect:138,presenc:[9,17,23,55,56,90,122,124,126,136,144,247,312,346],present:[1,4,8,22,42,46,48,49,51,62,69,77,85,91,96,97,104,105,116,123,131,138,180,188,190,204,205,214,215,234,252,326,344],preserv:[126,167,168,318,321,322,337,344],press:[9,14,15,22,26,31,33,42,51,63,80,83,88,95,96,100,106,110,180,224,226,227,232,265,328],press_button:226,pressabl:227,pressur:82,presto:20,presum:[62,73,153,337,338],pretend:75,pretext:188,pretti:[0,22,25,26,37,38,39,41,60,64,67,72,85,88,89,90,116,121,123,126,131,133,138,154,182,204,236,242,251,327,329,338,344],prettier:[0,357],prettifi:[57,344],prettili:62,pretty_corn:330,prettyt:[27,330],prev:[51,329],prev_entri:51,prevent:[11,20,33,38,46,62,95,194,221,234,315,329,362,364],preview:38,previou:[0,10,11,14,16,22,29,31,33,41,42,51,52,58,60,62,69,80,85,86,87,91,95,96,100,104,107,114,119,123,126,174,215,233,249,259,328,337,362],previous:[20,31,34,49,50,67,72,74,91,102,104,114,119,127,133,136,154,157,159,179,272,288,292,299,308,319],prgmr:90,price:[90,232],primari:[17,100,125,133,206,245,247,316,341],primarili:[2,12,34,36,37,38,55,61,108,144,179,206,238,285,325,344],primary_kei:133,prime:[150,179],primit:[61,159,251],princess:[111,122],principl:[2,9,19,26,30,33,37,38,40,51,55,57,60,80,85,89,90,96,98,119,123,132,138,153,156,179,233],print:[4,9,10,11,21,25,26,27,40,42,50,51,58,59,83,86,91,95,96,97,110,113,125,156,185,205,234,251,266,267,327,328,329,330,336,337,344],print_debug_info:328,print_help:234,print_usag:234,printabl:293,printout:290,prio:[25,31,33,150,233],prior:[117,194],priorit:205,prioriti:[4,25,31,33,44,51,97,116,152,156,160,161,162,163,167,168,180,230,232,233,247,326,328,329],privat:[4,8,38,57,61,69,90,131,164,165,176,279,292],private_set:9,privatestaticroot:312,privileg:[21,23,60,63,65,72,98,123,165,206,235,247,318,364],privkei:67,privkeyfil:287,privmsg:279,prize:122,proactiv:115,probabl:[4,5,11,16,21,22,23,25,29,33,37,46,48,51,55,57,61,64,67,69,85,86,89,90,96,108,116,119,121,128,133,134,136,138,180,198,204,233,269,279,287,334,344,345],problem:[11,13,15,21,22,23,24,25,26,27,36,43,56,61,64,69,70,75,77,80,90,95,97,100,103,110,111,113,127,138,140,144,153,195,247,276,322,364],problemat:[25,344],proce:[14,15,100,121,126,294,362],procedud:51,procedur:[138,215,287,290],proceed:[131,344],process:[0,4,8,9,11,13,14,15,22,23,25,29,33,36,38,39,41,42,49,51,55,59,61,64,67,73,75,76,83,88,89,90,91,92,93,100,106,122,131,133,138,139,144,150,152,159,169,179,206,215,234,240,242,247,251,257,260,267,272,276,277,284,287,290,295,296,305,306,308,321,322,325,328,338,343,344,345,362,364],process_languag:206,process_recog:206,process_sdesc:206,processed_result:344,processj:[316,336],processor:[18,43,93,110,111,124,139,141,142,158,169,320,364],procpool:344,produc:[33,51,96,114,123,131,156,159,203,205,232,235,247,251,252,266,298,316,318,327,328,336,344],produce_weapon:232,producion:27,product:[23,26,36,90,93,103,106,128,131,135,298,301,328,364],production_set:9,prof:93,profession:[3,57,64,108],profil:[45,65,139,141,142,145,148,188,262,364],profile_templ:188,profit:138,profunc:109,prog:234,progmat:56,program:[2,10,15,23,53,56,57,63,64,67,70,75,77,79,86,90,92,93,95,96,100,103,106,108,110,114,124,127,128,169,171,234,262,267,290,296,298,364],programm:[91,95],programmat:[114,138],progress:[70,73,79,85,131,217,218,219,220,221,326,364],proident:52,project:[4,15,25,37,49,64,70,72,77,79,91,99,108,111,124,127,131,135,136,338,364],projectil:220,promis:26,promisqu:126,prompt:[9,12,23,24,26,42,54,63,64,75,83,88,96,100,111,124,125,137,139,154,215,265,279,290,295,296,322,328,364],promptli:14,prone:[1,128,153,318],pronoun:189,prop:61,propag:[8,152,271,340],proper:[15,21,23,27,36,39,44,56,57,61,64,85,91,96,100,103,116,123,127,131,133,135,137,138,159,179,180,196,205,327],properli:[9,29,58,62,69,84,106,108,117,125,126,127,128,131,133,140,154,179,211,233,241,260,261,287,344,362],properti:[5,6,13,22,25,39,53,55,56,57,59,61,68,73,80,81,84,86,87,96,97,104,109,110,111,115,116,119,121,123,126,127,144,145,146,148,154,156,159,167,169,170,173,175,177,180,188,192,194,203,206,215,217,219,220,221,231,232,233,234,235,237,239,241,242,244,245,246,247,251,252,254,255,256,258,259,260,263,272,274,279,285,299,306,307,308,315,316,318,319,323,325,328,338,339,340,341,344,357,362,364],property_nam:245,property_valu:245,propnam:123,propos:[50,138],proprietari:23,propval:123,propvalu:123,prosimii:[133,134],prospect:61,prot:252,prot_func_modul:[109,250],protect:[6,31,90,159,306],protfunc:[141,142,248,251,364],protfunc_modul:251,protfunc_pars:251,protfunct:251,protkei:[109,250,251],proto:[276,287],proto_def:203,protocol:[24,27,33,47,53,64,72,74,79,83,90,92,101,103,104,105,110,137,139,144,146,154,157,189,210,247,262,264,267,269,272,276,277,278,279,280,281,282,283,285,286,287,289,290,291,292,294,295,296,298,305,306,307,308,326,340,344,364],protocol_flag:[289,290,294,306],protocol_kei:307,protocol_path:[285,308],protodef:203,prototocol:169,protototyp:[249,251,252],protototype_tag:109,prototoyp:250,prototyp:[45,46,47,53,55,120,139,141,142,159,169,203,218,219,232,364],prototype1:252,prototype2:252,prototype_:109,prototype_desc:[109,252],prototype_dict:159,prototype_diff:252,prototype_diff_from_object:252,prototype_from_object:252,prototype_kei:[109,159,251,252],prototype_keykei:159,prototype_lock:[109,252],prototype_modul:[109,159,251,252],prototype_pagin:251,prototype_par:[109,159,252],prototype_tag:252,prototype_to_str:251,prototypeevmor:251,prototypefunc:252,protpar:[251,252],protpart:251,provid:[0,3,4,11,12,16,17,22,25,29,33,36,38,41,47,55,69,75,77,90,91,96,97,100,102,103,108,109,119,124,125,126,127,131,133,134,136,137,138,144,154,159,164,171,175,180,182,188,190,193,203,204,215,217,218,219,220,221,234,235,241,247,259,287,310,317,328,338,339,340,344,345,357,362],provok:[42,79],proxi:[47,60,67,70,103,125,312,315,364],proxypass:8,proxypassrevers:8,prudent:36,prune:31,pseudo:[40,49,91,108,204,205],psionic:220,psql:23,psycopg2:23,pty:9,pub:41,pubkeyfil:287,publicli:[54,61,79,364],publish:[21,36,79,100],pudb:[141,364],puff:56,pull:[25,31,33,36,37,38,64,100,128,131,136,198,227,232,269],pullrequest:37,punch:31,punish:221,puppet:[2,9,19,21,22,31,33,39,40,41,43,55,57,58,62,74,80,96,97,105,107,114,118,123,133,143,144,150,156,159,167,181,199,241,247,306,308,318,336,360,362,364],puppet_object:[2,144],purchas:[67,85],pure:[46,56,88,114,125,126,256,267,316,321],pure_ascii:344,purg:[11,110,125,169],purpos:[4,11,67,83,90,92,95,112,119,123,126,133,146,150,154,185,194,287,316,325,328,344],pursu:[122,231],push:[22,38,76,100,103,126,198,224,226,227,232,364],pushd:63,put:[0,2,3,5,6,10,12,13,14,19,20,21,23,25,33,37,38,42,46,49,50,51,57,58,60,61,64,70,73,77,79,80,83,85,86,87,89,90,95,96,102,103,104,105,106,109,111,114,116,121,122,123,125,127,129,133,135,136,138,153,156,157,159,161,165,181,182,188,190,206,215,217,218,219,220,221,223,227,242,276,290,329,330,364],putti:90,puzzl:[79,122,141,142,178,232,233,364],puzzle_desc:232,puzzle_kei:233,puzzle_nam:203,puzzle_valu:233,puzzleedit:203,puzzlerecip:203,puzzlesystemcmdset:203,pwd:100,py3:276,pyc:[47,95],pycharm:[38,45,139,364],pyflak:26,pylint:26,pyopenssl:65,pypath:344,pypath_prefix:344,pypath_to_realpath:344,pypi:[64,79,90,93,321],pypiwin32:[9,63],pyprof2calltre:93,pyramid:235,pyramidmapprovid:235,python2:[9,63,97],python37:63,python3:[63,75],python:[0,2,3,4,9,10,11,12,14,15,19,20,21,22,23,27,29,31,33,37,38,39,42,45,46,47,49,50,51,53,56,58,60,62,63,64,65,66,69,72,73,75,76,80,82,83,85,86,89,90,91,93,97,98,100,102,103,104,106,108,109,110,111,113,114,116,118,119,123,124,125,127,128,130,133,134,135,139,151,153,158,159,163,169,170,180,185,192,193,194,195,196,198,204,234,235,242,245,246,250,251,252,258,261,267,269,276,280,285,295,306,308,312,314,317,318,321,322,324,325,326,327,328,330,331,334,337,340,344,363,364],python_execut:64,python_path:[153,344],pythonista:79,pythonpath:[153,267,277,322],pytz:345,qualiti:[61,151],quell:[2,6,20,43,121,156,212,364],quell_color:159,queri:[11,16,34,39,56,64,83,86,109,112,131,148,177,206,238,239,245,246,247,250,251,252,256,274,287,302,316,317,318,319,329,335,341,344,345,364],quersyet:119,query_info:267,query_statu:267,queryset:[64,102,112,119,147,176,199,238,245,251,255,273,315,317,329,362],queryset_maxs:329,quest:[55,57,61,63,117,122,139,233],question:[8,10,22,26,33,34,50,51,57,61,63,67,70,73,90,96,124,131,135,159,246,264,265,316,326,328,344],queu:267,queue:[36,116,312],qui:52,quick:[5,18,22,31,33,38,39,48,55,61,70,79,90,91,95,97,108,112,116,119,124,138,140,146,159,180,205,252,272,316,319,330,364],quicker:[0,37,86,87],quickli:[10,11,15,25,33,34,39,48,51,86,89,96,112,114,120,128,136,139,159,180,205,319,322],quickstart:[90,95,139,364],quiescentcallback:269,quiet:[25,85,144,157,159,164,180,182,206,247,329,344,364],quiethttp11clientfactori:269,quietli:[29,83,88,316],quirk:[24,45,139,153,364],quit:[0,2,4,10,17,21,22,23,30,33,38,39,40,42,43,46,50,51,54,55,57,60,67,75,85,93,96,105,119,127,128,133,156,171,180,186,188,194,220,287,326,328,329],quitfunc:[50,326],quitfunc_arg:326,quitsave_yesno:326,quo:115,quot:[23,27,35,50,51,80,95,96,109,114,118,159,171,186,206,326,328,336,340,344],qux:215,ra4d24e8a3cab:35,race:[8,55,56,61,73,79,117,133,344],rack:232,radiu:[39,49,111],rage:122,rail:[64,121],railroad:121,rain:[102,119,122,132],raini:233,rais:[10,15,27,33,69,73,77,83,91,109,119,134,144,146,176,180,185,187,192,194,195,204,205,206,242,250,251,259,261,266,267,285,290,296,311,316,317,321,322,324,327,328,330,336,337,338,339,340,344,345],raise_error:[339,344],raise_except:[1,316],ram:[11,90],ramalho:79,ran:[13,36,42,127],rand:102,randint:[73,91,109,116,120,123,217,218,219,220,221,250,252],random:[9,20,35,46,60,73,90,91,102,104,109,114,116,120,123,132,204,205,217,218,219,220,221,223,224,226,228,232,233,235,250,252,298,299,336,344],random_string_from_modul:344,random_string_gener:[141,142,178,364],randomli:[86,93,102,120,132,217,218,219,220,221,231,232,250,267,299],randomstringgener:204,randomstringgeneratorscript:204,rang:[24,31,39,42,49,50,56,59,63,88,91,93,103,109,111,116,118,120,122,127,159,184,188,218,221,317,326,336,357,362],rank:[19,241],raph:79,rapidli:153,raptur:291,rare:[10,22,33,34,38,63,86,104,106,115,128,242,324],rascal:112,rate:[33,37,64,90,164,261,267,286,344],rather:[2,3,11,13,20,22,25,26,29,33,37,38,39,41,47,55,57,60,61,64,71,86,89,91,93,95,97,102,104,110,111,112,115,116,127,128,129,131,134,135,138,144,148,152,156,159,160,164,167,169,179,190,194,202,206,217,218,219,220,221,236,241,247,249,251,252,315,316,318,321,330,336,339,340,343,362],ration:179,raw:[3,12,20,33,38,41,51,56,64,74,83,86,95,109,114,119,144,151,154,159,167,168,170,206,210,234,247,272,287,290,295,296,306,321,326,328,329,336,338],raw_cmdnam:[151,168],raw_desc:187,raw_id_field:[173,244,254],raw_input:[85,328],raw_nick:87,raw_str:[33,51,85,144,146,150,151,154,170,188,215,230,247,249,306,316,328],raw_templ:87,rawstr:[154,170],rcannot:22,re_bg:343,re_bgfg:343,re_blink:343,re_bold:343,re_color:343,re_dblspac:343,re_double_spac:343,re_fg:343,re_format:321,re_hilit:343,re_invers:343,re_mxplink:343,re_norm:343,re_str:343,re_ulin:343,re_underlin:343,re_unhilit:343,re_url:343,reach:[20,22,39,51,73,87,88,90,95,101,121,122,141,154,188,192,221,241,287,291,310,328,329,336,341],reachabl:[64,115],react:[51,115,117,118,231,247],reactiv:169,reactor:[278,305,312,342],read:[0,1,4,5,8,9,11,13,15,16,17,20,22,23,25,27,29,31,33,34,37,38,39,41,46,51,55,56,58,59,60,64,69,70,71,72,76,77,79,80,85,86,88,90,91,93,95,96,102,103,104,105,109,114,119,122,123,124,126,127,128,131,133,134,138,139,144,148,158,166,177,180,187,190,198,199,204,206,232,233,239,246,247,251,252,256,274,276,299,316,318,319,322,323,327,329,335,337,362,363,364],read_batchfil:322,read_default_fil:36,readabl:[1,27,38,49,51,93,96,108,114,115,125,232,321,328],readable_text:232,reader:[38,48,58,74,79,81,98,133,164,190,221,272,286],readi:[2,10,12,15,20,25,29,36,37,40,42,54,63,75,77,80,83,89,93,106,121,131,136,138,144,154,166,206,217,218,219,220,221,247,296,329,338],readili:[23,111],readin:327,readlin:337,readm:[14,37,47,53,130,131,178,210],readonlypasswordhashfield:145,readthedoc:[79,83],real:[2,10,21,22,27,31,38,39,42,46,55,58,59,62,63,66,67,72,73,89,90,93,95,100,108,109,110,111,116,119,123,125,126,131,148,153,177,179,184,205,206,219,241,298,322,331,364],real_address:2,real_nam:2,real_seconds_until:[184,331],real_word:205,realis:77,realist:[127,132],realiti:[21,55,56,61,77,79,126],realiz:[48,96,126,131],realli:[4,10,11,12,13,14,19,20,22,25,26,31,33,39,42,51,58,62,64,67,72,77,80,85,89,91,96,98,104,108,110,111,112,115,118,119,121,127,128,138,139,154,170,179,180,181,215,234,242,276,321,322,328,340],realm:287,realnam:89,realtim:[58,184],realtime_to_gametim:184,reason:[8,9,11,12,13,22,25,29,34,37,38,39,40,41,44,49,51,56,57,58,60,61,63,64,69,73,80,82,83,86,87,89,93,97,102,103,104,106,109,114,115,116,119,122,126,129,131,138,144,157,159,164,169,186,204,205,245,247,251,257,264,269,276,277,278,279,285,286,287,290,295,296,298,306,307,308,318,326,337,344,362],reasourc:109,reassign:49,reattach:[106,278,279],rebas:131,reboot:[11,27,28,43,50,55,67,84,86,90,100,102,105,115,116,128,144,153,164,169,183,188,231,232,247,255,256,257,258,259,261,267,307,308,326,328,364],reboot_evennia:267,rebuild:[58,63,100,128,279],rebuilt:33,rec:206,recach:[233,306],recal:[95,138,232,362],recapcha:364,recaptcha:133,receipt:[103,269],receiv:[31,33,34,37,41,42,51,52,58,77,83,87,91,105,113,114,127,133,137,138,144,152,153,170,171,175,176,177,186,199,206,210,247,269,272,276,278,279,285,295,296,305,306,324,329,341,344,364],receive_functioncal:276,receive_status_from_port:267,receivelock:241,receiver_account_set:148,receiver_object_set:246,receiver_script_set:256,recent:[4,17,25,60,67,123,147,310],recev:296,recip:[0,28,115,203],recipi:[34,58,144,176,199,276],reckon:9,reclaim:102,recog:[87,206],recog_regex:206,recogerror:206,recoghandl:206,recogn:[16,20,63,74,83,89,90,96,110,127,134,206,312],recognit:[206,316,336],recommend:[9,12,23,24,25,26,36,37,38,51,55,58,59,60,61,63,69,73,79,86,88,89,90,93,95,108,109,122,124,125,127,131,135,169,190,194,209,234,242,245,247,269,322,328,341],reconfigur:90,reconnect:[144,146,164,264,267,276,278,279,305,308],reconnectingclientfactori:[264,278,279],record:[15,23,90,123,210,221,310,357],recours:12,recov:[27,28,29,56,217,218,219,220,221,242,344],recoveri:116,recreat:[23,63,102,111,128,146,153,322,323],rectangl:327,rectangular:[58,327],recur:64,recurs:[11,241,251],red:[13,14,20,31,59,80,87,95,109,114,126,159,169,224,226,227,232,336,345],red_bal:59,red_button:[13,14,20,87,141,142,159,178,222,224,227,364],red_button_script:[141,142,178,222,226,364],red_kei:80,redbutton:[13,14,20,87,159,224,226,227],redbuttonblind:227,redbuttonclos:227,redbuttondefault:224,redbuttonopen:227,redd:103,reddit:103,redefin:[22,33,55,89,247,357],redhat:[63,67],redirect:[8,22,40,69,96,105,133,135,180,328,362],redirectview:362,redistribut:34,redit:180,redo:[50,61,326],redon:271,redraw:287,reduc:[116,217,218,219,220,221,280,364],redund:321,reel:153,reen:114,ref:[23,38,125,206,344,357],refactor:[45,57,139,363,364],refer:[0,8,9,13,19,20,22,31,33,34,37,40,46,48,49,51,56,57,62,64,69,73,79,80,86,87,88,89,90,95,96,100,104,105,106,109,110,111,116,118,119,124,125,126,127,129,130,131,133,134,144,153,159,164,168,179,188,204,206,217,218,219,220,221,241,247,258,260,261,269,279,299,307,315,317,328,334,340,341,344,362,364],referenc:[38,56,89,104,109,159,175,206,239,318,344],referenti:344,referr:90,refin:[49,119],reflect:[96,362],reflow:16,reformat:[252,330,337],reformat_cel:330,reformat_column:[111,330],refresh:[26,134,287],refus:12,regain:29,regard:[48,126,127,138,204],regardless:[12,19,31,33,58,73,80,81,83,102,105,114,119,121,125,127,138,144,152,179,189,206,224,227,247,259,261,284,287,290,305,307,316,319,322,334,337],regener:219,regex:[33,50,51,87,127,137,154,157,170,183,204,206,311,316,328,336,344],regex_nick:87,regex_tupl:206,regex_tuple_from_key_alia:206,regexfield:145,region:[58,90,140,157],regist:[65,71,83,103,104,116,120,131,133,135,137,138,144,147,164,169,198,231,232,257,267,278,279,285,308,312,321,336,360,362,364],register_error:321,register_ev:198,registercompon:137,registertest:360,registr:[65,362],registrar:67,registri:[204,312],regress:251,regul:242,regular:[3,17,33,38,51,69,79,90,96,105,115,127,132,134,135,146,152,182,203,204,226,227,233,242,255,261,319,334,344,363],regulararticl:335,regulararticle_set:335,regularcategori:335,regularli:[67,85,98,102,120,128,132,184,226,231,233,258,259,261,270,300,331],reilli:79,reinforc:79,reiniti:110,reinstal:63,reinvent:57,reject:[188,204],rejectedregex:204,rel:[10,13,14,19,22,31,38,49,51,82,104,123,131,133,184,221,322,328],relai:[27,33,72,105,144,164,179,189,247,285,308,328,329,344],relat:[28,31,33,34,47,51,56,57,72,79,96,102,103,104,110,125,132,137,138,139,145,148,149,152,166,167,172,176,177,184,198,210,217,218,219,220,221,230,233,239,246,247,255,256,259,261,272,308,315,316,318,319,321,328,335,337,346,350,357,364],related_field:[145,173,237,244,254,315],related_nam:[148,177,239,246,256,316,318,319,335],relationship:[34,49,119,125],relay:146,releas:[9,28,37,55,63,78,79,90,96,169,364],releg:1,relev:[3,9,11,14,22,30,33,37,38,47,58,62,79,80,89,96,107,112,114,116,119,123,124,125,133,135,140,144,145,150,152,179,180,241,242,258,259,281,299,306,307,308,315,321,326,328,338,364],relevant_choic:180,reli:[9,34,41,51,62,70,81,85,86,88,91,114,115,119,126,127,135,189,206,227,233,267,318,328,364],reliabl:[13,23,25,29,125,334],reload:[0,2,3,5,6,7,12,13,14,19,21,22,26,27,28,29,31,33,35,36,39,40,41,42,43,44,48,50,51,55,57,58,60,62,63,65,66,68,69,71,73,74,81,92,95,96,98,102,104,105,106,115,116,117,118,121,123,125,128,133,134,135,136,139,144,146,153,158,159,169,175,180,181,185,186,187,195,202,206,212,213,232,233,235,242,247,255,257,258,259,261,267,276,277,279,281,305,308,312,322,324,326,327,328,331,344,364],reload_evennia:267,remain:[13,19,30,31,33,50,51,58,77,90,91,96,97,107,109,110,113,151,153,159,161,165,175,181,184,187,217,218,219,220,221,231,247,259,267,295,296,328,329,336,344],remaind:[21,33,184],remaining_repeat:[102,259],remap:[316,336],remark:364,remedi:60,rememb:[0,1,4,5,11,12,13,21,22,28,29,31,33,39,41,48,49,51,54,56,58,61,62,63,69,77,80,86,88,90,91,93,95,96,97,111,112,114,115,119,123,126,128,131,137,139,157,159,181,194,247,257,322,341],remind:[0,4,38,50,364],remit:157,remnisc:57,remot:[25,100,103,164,276,278,290,364],remov:[0,1,4,9,11,12,21,22,27,31,36,39,41,48,50,51,55,58,69,80,81,84,85,87,89,91,93,98,102,115,116,122,127,128,131,133,136,138,141,152,153,157,159,164,165,166,169,174,175,177,180,182,187,188,192,196,203,204,205,206,215,217,218,219,220,221,224,242,246,247,252,255,257,260,261,267,285,296,306,308,310,316,319,321,325,328,334,340,342,343,344,364],remove_backspac:343,remove_bel:343,remove_charact:116,remove_default:[31,153],remove_non_persist:255,remove_receiv:177,remove_send:177,removeth:316,renam:[9,20,43,58,81,136,159,165,247,255,318],render:[3,22,38,69,81,102,107,133,134,136,145,166,190,237,244,312,315,329,338,340,355,357,362],render_post:296,renew:[29,58,67],reorgan:[45,47],repair:[21,61],repeat:[0,42,61,62,75,88,93,102,110,111,116,118,121,136,139,144,146,179,184,204,215,256,259,267,272,291,316,324,328,331,364],repeatedli:[14,42,62,74,102,139,231,256,259,261,267,272,298],repeatlist:74,repetit:[62,116,204],replac:[5,6,9,22,23,25,29,30,31,33,36,38,41,50,51,57,69,74,80,87,89,95,96,100,104,105,109,111,114,116,119,134,135,136,137,138,144,151,152,153,154,157,165,166,170,179,181,183,186,187,188,192,195,202,203,205,206,224,227,230,233,234,242,247,249,251,252,279,282,295,296,306,316,321,326,327,328,330,336,343,344],replace_data:330,replace_timeslot:187,replace_whitespac:330,replacement_str:165,replacement_templ:165,replenish:[217,218,219,220,221],repli:[33,51,65,70,139,146,179,199,265,289,290,296,308,328],replic:[22,114,136],repo:[38,47,57,79,106,131,139,344,364],report:[22,24,26,33,37,43,61,63,70,73,75,84,91,93,97,102,103,104,115,116,127,131,136,138,159,192,195,206,234,267,272,279,282,283,290,291,295,306,308,321,324,328,344],report_to:324,repositori:[8,9,23,25,36,38,63,76,78,96,100,130,252,364],repositri:76,repr:[91,329,344],reprehenderit:52,repres:[0,2,9,20,21,22,25,31,33,40,46,49,53,56,61,62,64,69,77,86,89,95,96,105,107,113,116,119,125,126,127,133,136,144,150,174,176,182,188,190,192,198,204,206,210,212,215,219,232,233,234,247,252,260,261,264,278,279,295,296,306,307,308,312,316,317,321,323,324,328,329,330,340,344],represent:[2,11,28,40,58,64,73,77,86,87,88,105,113,119,126,176,192,195,206,245,251,256,276,295,296,319,325,331],reprocess:103,reproduc:[10,96,247],reput:209,reqhash:[317,344],reqiur:188,request:[3,8,26,37,40,51,63,69,80,90,103,107,119,123,131,133,134,135,139,144,145,146,157,173,179,195,244,247,251,254,267,269,276,279,281,286,287,289,296,312,315,319,328,349,350,351,355,362],request_finish:107,request_start:107,requestavatarid:287,requestfactori:312,requestor:[144,310],requir:[1,4,8,9,10,11,14,15,22,23,33,36,37,38,46,47,49,50,51,54,58,60,61,67,68,69,70,71,75,77,78,79,80,84,85,86,89,90,93,102,109,110,111,114,115,116,118,119,125,126,127,129,132,133,134,136,137,145,147,158,159,164,169,176,177,185,186,187,188,202,204,206,215,219,220,233,234,237,238,241,244,245,247,251,260,267,278,279,292,300,311,315,317,322,327,328,329,330,334,339,340,341,344,357,362,364],require_singl:251,requr:109,rerout:[138,156,160,279],rerun:[13,14,51,122],resart:259,research:[79,194],resembl:[25,55,129],resend:33,reserv:[1,10,33,95,96,111,251,311,317,336,344],reset:[0,7,12,15,17,23,27,29,31,33,43,44,50,60,66,73,81,102,104,105,111,114,116,121,123,125,126,139,144,146,153,159,169,174,184,195,206,227,232,242,255,258,259,267,271,277,287,305,316,319,322,330,331,336,342,344,364],reset_cach:[316,319],reset_callcount:[102,259],reset_gametim:[27,331],reset_serv:271,reset_tim:187,reshuffl:364,resid:[47,96,108,227,242],residu:[169,219],resist:[252,344],resiz:[58,138,327,330],resolut:[114,116],resolv:[26,29,38,42,60,70,90,95,104,116,131,203,217,218,219,220,221],resolve_attack:[217,218,219,220,221],resolve_combat:116,resort:[33,38,54,58,164,206,344],resourc:[9,23,26,28,38,41,47,48,53,56,90,95,96,103,108,115,124,135,136,139,220,257,265,296,312,323,342,364],respect:[0,6,23,33,48,58,80,104,105,123,125,157,159,166,179,199,203,206,213,224,242,247,306,307,318,319,322,324,330,341,344,357],respond:[0,46,51,61,83,84,107,110,117,118,126,294,298],respons:[7,10,16,17,37,49,51,60,63,64,70,85,88,90,91,118,120,121,144,146,153,164,175,233,235,239,247,265,267,269,276,299,308,318,338,340,344],response_add:[145,173,244],resport:344,rest:[17,29,33,38,51,56,63,73,82,85,86,87,104,106,111,122,123,151,167,168,217,218,219,220,221,321,330],restart:[12,42,43,58,60,76,90,92,93,102,103,104,106,110,116,128,131,135,138,141,144,169,175,180,183,195,227,247,255,257,259,260,261,271,284,305,306,307,344,364],restartingwebsocketserverfactori:[146,278],restock:85,restor:[0,31,102,126,180,220,227,257,261],restrain:[159,241,327,344],restrict:[4,8,11,19,20,47,59,68,73,80,90,109,111,115,125,134,137,159,164,182,204,220,221,237,242,245,252,255,324,326,328,330,341],restructur:[38,56],result1:203,result2:[51,203],result:[10,11,23,27,30,31,33,38,44,48,51,58,59,73,80,88,90,91,95,96,97,104,105,109,114,115,116,118,119,123,124,126,127,131,134,135,136,144,147,151,152,154,159,166,175,177,179,185,188,203,204,205,206,209,217,218,219,220,221,233,238,242,245,247,250,251,252,255,259,267,276,299,316,318,321,326,327,328,330,334,336,337,338,341,344,345],result_nam:203,resum:[29,33,102,260],resurrect:231,resync:[146,276,306],ret:33,ret_index:344,retain:[10,27,31,51,97,111,138,189,252,313,318,322,324,337,344],retext:38,retract:179,retreat:221,retri:267,retriev:[0,33,69,74,86,96,97,108,112,119,123,139,140,144,148,150,153,159,169,174,176,187,194,238,241,251,265,272,273,279,285,294,316,319,325,334,339,341,344,362,364],retriv:[146,323],retroact:[58,125],retur:52,return_appear:[48,49,60,122,123,182,187,206,232,247],return_cmdset:166,return_detail:[187,233],return_iter:251,return_key_and_categori:319,return_list:[1,316,319],return_map:111,return_minimap:111,return_obj:[1,11,87,316,319,339],return_par:252,return_prototyp:120,return_puppet:144,return_tagobj:319,return_tupl:[87,185,306,316],returnv:33,returnvalu:10,reus:[25,334],reusabl:122,rev342453534:344,reveal:182,revers:[29,31,33,39,111,114,121,126,134,148,177,235,239,246,256,312,316,318,319,321,335],reverseerror:[267,276],reversemanytoonedescriptor:[148,246,335],reverseproxyresourc:312,revert:[90,126,131,156,238],review:[0,31,37,41,64,70,128,135],revis:61,revisit:[36,328],reviu:51,revok:58,revolutionari:131,rework:[29,61],rewritemim:70,rfc1073:283,rfc858:289,rfc:[283,289],rfind:321,rgb:114,rgbmatch:321,rhel:8,rhost:171,rhostmush:[57,108,129],rhs:[25,58,167,168,170],rhs_split:[159,165,167,168],rhslist:[167,168],ricardo:344,riccardomurri:344,rich:[22,57,78,79,325],richard:79,rick:109,rid:[56,119,139],riddanc:12,ridden:[1,96],riddick:188,ride:121,right:[0,5,8,10,14,20,21,23,25,28,29,33,38,39,41,42,46,51,55,56,57,58,60,61,63,68,74,75,76,80,85,87,90,91,96,101,102,109,111,114,117,119,121,123,126,127,128,133,134,137,138,145,153,156,159,167,168,169,171,175,181,187,188,190,195,196,203,221,224,227,231,232,233,235,242,250,252,256,307,321,322,326,330,344,345],right_justifi:[109,250],rigid:57,rindex:321,ring:205,ripe:96,rise:[31,62],risen:62,risk:[38,57,63,90,123,138,158,169],rival:111,rjust:321,rm_attr:159,rnormal:114,rnote:169,road:[31,46,111,121,152],roadmap:[45,139,364],roam:[122,153,231],roar:111,robot:[77,133],robust:[85,91,103],rock:[6,60,86,116,124,153],rocki:122,rod:153,role:[17,23,55,57,61,73,91,217,364],roleplai:[9,11,57,61,68,73,79,116,123,139,185,206,364],roll1:73,roll2:73,roll:[11,58,61,63,73,91,114,116,123,185,217,218,219,220,221,310],roll_challeng:73,roll_dic:185,roll_dmg:73,roll_hit:73,roll_init:[217,218,219,220,221],roll_result:185,roll_skil:73,roller:[73,116,185,364],rom:[79,364],roof:159,room1:127,room56:13,room:[9,12,13,14,15,20,21,22,27,31,33,42,44,45,46,53,55,56,57,59,62,63,64,73,77,80,85,91,96,102,104,108,109,111,112,116,117,118,119,120,121,122,123,124,125,127,129,132,133,140,141,142,150,151,152,153,157,159,165,170,178,180,182,185,187,194,206,212,213,214,217,218,219,220,221,229,230,231,232,234,235,241,247,256,271,299,322,342,360,364],room_count:119,room_flag:56,room_lava:56,room_typeclass:[235,342,360],roombuildingmenu:[22,180],roomnam:[58,159],roomobj:119,roomref:121,root:[9,13,22,23,36,38,47,53,63,64,69,75,78,80,81,86,89,90,93,96,97,100,106,128,130,134,135,136,232,247,252,267,312,325,344],rose:[11,87,89,125],rostdev:90,roster:[9,217,218,219,220,221],rosterentri:9,rot:127,rotat:337,rotatelength:337,rough:38,roughli:[58,61,96,344],round:[17,205,221,330],rounder:205,rout:[5,20,49,56,121,137,144],router:90,routin:[206,245,302,341,344],row:[0,3,16,25,38,49,58,64,69,86,111,114,116,126,137,330,344],rpcharact:206,rpcommand:206,rpg:[58,60,73,124,185,221],rpi:79,rplanguag:[141,142,178,206,364],rpm:63,rpobject:206,rpsystem:[38,141,142,178,202,205,364],rpsystemcmdset:206,rred:321,rsa:[287,288],rspli8t:91,rsplit:[123,321],rsrc:70,rss2chan:[43,98,164],rss:[7,55,79,128,139,141,142,146,164,172,262,272,275,285,364],rss_enabl:[98,164],rss_rate:146,rss_update_interv:164,rss_url:[98,146,164],rssbot:146,rssbotfactori:286,rsschan:164,rssfactori:286,rssreader:286,rst:364,rstrip:[91,321],rsyslog:209,rtest2:114,rtext:85,rthe:22,rthi:114,rtype:312,rubbish:156,rubi:64,rudimentari:231,ruin:[122,187,233],rule:[12,13,14,21,33,47,55,58,61,68,77,79,80,96,114,124,126,127,131,139,180,204,205,217,218,221,239,322,364],rulebook:116,rumour:122,run:[0,2,3,5,6,8,9,10,11,13,14,15,20,21,23,24,26,27,28,29,31,35,36,38,40,45,46,47,51,53,54,56,57,59,60,61,62,63,64,67,68,69,72,73,76,79,80,81,83,85,86,90,91,92,93,95,96,97,101,102,103,104,109,110,111,115,119,121,122,123,124,125,126,128,130,131,132,133,134,136,137,138,139,141,144,146,150,151,153,154,158,159,164,165,166,169,170,174,175,195,196,206,209,213,215,217,218,219,220,221,227,230,235,241,242,247,251,252,255,256,258,259,260,261,267,271,273,277,284,292,296,298,301,305,306,310,312,318,321,322,326,328,329,331,337,341,342,344,362,363,364],run_async:[10,344],run_connect_wizard:267,run_dummyrunn:267,run_exec:328,run_exec_then_goto:328,run_init_hook:305,run_initial_setup:305,run_menu:267,run_start_hook:[60,125,318],runexec:328,runexec_kwarg:328,runnabl:109,runner:[36,106,232,298,364],runsnak:93,runtest:[170,196,211,228,293,303,335,342,352,360],runtim:[12,27,33,62,154,180,234,331,344],runtimeerror:[73,144,146,192,195,198,204,205,251,259,285,316,328,336,344],runtimewarn:251,rusernam:51,rush:29,rusti:85,ruv:36,ryou:22,sad:[133,290],sadli:171,safe:[11,26,30,31,46,56,60,64,82,89,97,104,131,133,144,156,179,227,242,261,276,308,312,318,322,325,334,344],safefunc:326,safer:[12,13],safest:[0,90,105,318],safeti:[2,56,89,90,123,125,139,159,179,246,322,364],sage:364,sai:[0,5,6,10,12,14,17,20,22,25,26,27,29,31,33,39,40,41,43,44,46,51,56,57,58,60,61,62,63,64,69,73,77,78,80,89,90,91,93,96,109,114,116,117,118,119,123,125,126,127,128,129,131,137,138,139,140,153,165,179,181,185,188,198,205,206,215,227,233,247,328],said:[0,4,10,22,26,44,46,49,51,57,83,91,96,111,112,118,127,134,151,164,168,206,235,247,279,318,328],sake:[13,57,126,135,171,186,362],sale:85,same:[0,2,5,6,9,10,11,12,13,14,15,16,19,20,21,22,23,26,27,28,29,31,33,34,37,38,40,41,42,44,50,55,57,58,59,60,61,62,63,64,66,69,73,74,78,80,81,83,84,85,86,88,89,90,91,95,96,97,98,100,102,104,105,106,108,109,110,111,112,113,114,115,116,119,121,123,125,126,127,128,131,133,134,136,138,144,150,151,152,153,154,157,159,167,168,169,170,171,175,180,182,184,187,190,194,195,199,204,205,206,212,214,215,217,218,219,220,221,224,231,233,234,235,241,247,251,252,256,257,261,271,276,288,291,292,306,307,308,310,312,315,316,317,318,319,321,322,324,328,329,330,331,337,338,344,357,362,364],sampl:[8,36,56,100,215],san:190,sand:62,sandi:111,sane:[38,61,79,96,362],sanit:[357,362],saniti:[9,49,111,127,139,338],sarah:[129,165],sat:[21,140],satisfi:[108,167],satur:103,save:[0,1,9,15,21,22,24,27,29,33,34,36,41,42,46,48,50,51,54,56,64,67,84,86,87,89,95,97,100,102,103,105,107,109,110,112,115,116,123,125,127,131,133,138,144,145,156,159,169,173,175,176,177,180,195,242,244,246,247,249,251,252,254,257,259,260,261,265,272,285,299,300,305,306,312,315,316,318,325,326,334,338,339,340,344,364],save_a:[173,237,244,254,263],save_as_new:315,save_buff:326,save_data:338,save_for_next:[33,154],save_handl:338,save_kwarg:339,save_model:[145,173,244,254],save_nam:261,save_on_top:[173,237,244,254,263],save_prototyp:251,save_recip:203,savefunc:[50,326,339],savehandl:339,saver:325,saverdict:325,saverlist:325,saverset:325,saveyesnocmdset:326,saw:[10,46,69],say_text:118,saytext:206,scale:[23,38,57,61,73,106,114,205],scalewai:90,scan:[8,150,231,233],scarf:182,scatter:[219,322],scedul:331,scenario:58,scene:[11,21,55,59,61,73,74,97,109,112,114,116,122,126,204,233,256,261,334],schedul:[27,62,184,195,260,331],schema:[4,64,86,125,131,344,364],scheme:[28,33,63,86,114,159,169,321],scienc:[49,124],scientif:79,scissor:116,scm:9,scope:[29,55,64,74,124,134,138,204,324],score:[58,60,344,364],scraper:362,scratch:[40,46,57,58,61,63,123,124,128,136,139],scream:122,screen:[7,16,18,33,51,52,61,66,74,81,85,97,100,101,104,105,109,114,127,133,138,139,145,171,186,190,221,250,272,287,329,344,364],screenheight:[74,154,272],screenread:[43,74,171,272,295,296],screenshot:[55,133,139,364],screenwidth:[74,154,272],script:[6,11,13,14,20,27,36,43,45,47,53,55,56,57,59,61,62,63,71,80,84,85,86,89,90,93,103,104,105,106,107,108,109,110,112,115,116,117,119,120,122,125,130,132,133,137,138,139,141,142,144,146,158,159,169,177,178,179,184,187,191,192,198,203,204,205,213,217,218,219,220,221,223,224,226,227,233,235,241,246,247,251,252,267,300,305,322,323,324,331,339,341,342,344,360,364],script_copi:255,script_path:159,script_search:[59,255],script_typeclass:[228,342,360],scriptattributeinlin:254,scriptbas:259,scriptclass:258,scriptdb:[53,119,125,141,254,256,314],scriptdb_db_attribut:254,scriptdb_db_tag:254,scriptdb_set:[148,246,316,319],scriptdbadmin:254,scriptdbmanag:[255,256],scripthandl:[141,142,253,364],scriptkei:159,scriptmanag:255,scriptnam:323,scripttaginlin:254,scroll:[20,45,52,63,77,95,96,97,123,138,329],scrub:308,scrypt:102,sdesc:[56,202,206],sdesc_regex:206,sdescerror:206,sdeschandl:206,sdk:63,sea:[111,122],seamless:206,seamlessli:[92,102],search:[0,2,9,13,21,22,30,33,41,42,43,48,50,55,58,59,60,64,68,70,73,76,87,89,96,102,104,109,116,123,124,125,127,131,134,136,139,140,141,142,144,147,150,152,154,159,166,169,176,179,194,199,203,206,217,218,219,220,221,233,235,238,241,245,247,250,251,255,258,273,316,317,318,319,320,321,324,326,344,363,364],search_:[27,59],search_account:[27,53,58,107,119,141,147,247,341],search_account_attribut:119,search_account_tag:[119,341],search_at_multimatch_input:247,search_at_result:[206,247],search_attribute_object:119,search_channel:[27,41,53,119,141,176,341],search_channel_tag:[119,341],search_field:[173,237,244,254,263,315],search_for_obj:159,search_help:[27,53,119,141,238],search_help_entri:341,search_helpentri:238,search_messag:[27,53,119,141,176,341],search_mod:206,search_multimatch_regex:247,search_object:[11,13,27,53,111,119,121,125,141,144,245,341],search_object_attribut:119,search_object_by_tag:53,search_objects_with_prototyp:251,search_prototyp:251,search_script:[27,53,59,102,119,141,255,341],search_script_tag:[119,341],search_tag:[27,48,112,119,140,141,341],search_tag_account:112,search_tag_script:112,search_target:199,searchabl:194,searchdata:[144,206,245,247,341],searchstr:68,season:[61,187],sec:[10,29,62,74,184,279,331],secmsg:337,second:[0,10,11,14,16,21,22,25,27,29,31,33,38,39,41,51,62,63,69,80,85,86,88,90,91,95,100,102,103,104,109,110,114,115,116,119,120,121,123,126,127,128,132,134,144,146,151,159,184,194,195,198,206,213,217,218,219,220,221,223,227,231,241,247,252,259,260,261,267,272,281,286,299,310,321,324,328,331,337,344,345],secondari:[81,307],secondli:89,secreci:131,secret:[9,65,71,185,267,364],secret_kei:9,secret_set:[4,9,23,65,267],sect_insid:49,section:[1,4,9,11,15,18,21,22,23,25,26,29,31,33,35,36,38,39,40,48,51,58,60,62,63,68,69,75,77,80,86,89,90,93,95,96,100,111,113,119,124,125,127,133,137,138,139,187,205,252,321,322,328,345],sector:49,sector_typ:49,secur:[7,11,13,22,26,37,41,57,63,80,85,90,96,108,109,114,123,133,134,139,141,142,158,169,175,178,239,247,287,318,337,357,364],secure_attr:80,sed:36,see:[0,1,2,3,4,5,8,9,10,11,12,13,14,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34,35,37,38,39,40,41,42,44,46,48,49,50,51,52,53,55,56,57,58,59,60,61,62,63,64,65,68,70,71,72,74,75,76,80,81,82,83,86,87,88,89,90,91,93,95,96,98,100,101,102,103,104,105,106,108,109,110,111,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,130,131,132,133,134,135,136,137,138,139,144,154,156,158,159,164,165,166,167,168,170,171,175,177,178,179,180,186,190,192,199,203,204,205,206,210,213,214,215,217,218,219,220,221,223,224,226,231,233,234,235,241,246,247,260,265,267,269,270,278,279,280,281,283,287,288,290,292,294,295,296,298,299,306,307,308,312,316,321,324,325,326,327,330,336,339,340,344,351,357,362,364],seek:[122,242,337],seem:[4,22,24,31,39,41,56,61,63,75,109,110,119,121,122,123,137,138,171,316,322],seen:[0,22,29,31,34,38,40,46,49,51,57,58,69,81,91,95,96,102,105,111,119,120,121,126,127,131,180,251,279,330],sefsefiwwj3:9,segment:[121,312],seldomli:[154,170],select:[2,20,22,27,31,51,54,63,69,77,80,85,86,104,105,106,111,119,120,123,131,133,137,138,140,151,152,157,166,215,218,318,326,328,364],selet:328,self:[0,1,2,5,6,9,10,11,13,20,21,22,25,27,28,29,30,31,33,38,39,40,41,42,44,49,50,51,56,57,58,59,60,62,63,71,72,73,76,77,80,81,82,85,86,87,89,95,96,102,109,115,116,117,118,119,120,121,123,125,127,129,132,134,144,146,148,150,152,153,154,156,159,160,164,167,168,169,170,171,174,175,177,179,180,181,182,185,187,188,192,199,202,203,206,215,217,218,219,220,221,223,224,227,230,231,232,233,234,235,241,247,259,260,265,267,269,270,274,278,279,285,287,288,290,292,294,295,296,306,307,308,316,318,319,321,326,328,329,334,336,338,339,340,344,351,364],self_pid:344,selfaccount:58,sell:[78,85,179],semi:[93,132,138,205],semicolon:[80,242,324],send:[2,12,22,25,27,29,33,34,41,51,52,58,59,61,64,67,70,71,73,74,76,80,81,83,89,91,93,95,96,102,103,105,107,110,113,114,115,116,118,120,123,126,133,137,138,139,140,144,146,150,153,154,157,164,168,174,175,176,177,179,188,189,199,206,210,221,223,230,231,241,247,260,261,264,267,269,270,272,276,277,278,279,280,282,285,286,287,289,290,291,293,295,296,298,306,307,308,309,321,324,325,328,330,344,364],send_:[40,83,285],send_adminportal2serv:277,send_adminserver2port:264,send_authent:278,send_channel:[278,279],send_default:[40,83,278,279,285,287,290,295,296],send_defeated_to:231,send_emot:206,send_functioncal:276,send_game_detail:269,send_heartbeat:278,send_instruct:267,send_mail:199,send_msgportal2serv:277,send_msgserver2port:264,send_p:279,send_privmsg:279,send_prompt:[83,287,290,295,296],send_random_messag:223,send_reconnect:279,send_request_nicklist:279,send_status2launch:277,send_subscrib:278,send_testing_tag:230,send_text:[40,83,287,290,295,296],send_unsubscrib:278,sender:[34,41,107,144,146,164,175,176,177,179,206,247,278,309,334,341],sender_account_set:148,sender_extern:177,sender_object:309,sender_object_set:246,sender_script_set:256,sender_str:175,sendernam:164,senderobj:324,sendlin:[287,290,295],sendmessag:[40,188],sens:[1,10,22,31,37,56,58,80,86,89,96,102,121,138,152,224,324,325,328],sensibl:[90,271],sensit:[11,51,58,80,147,176,180,184,187,195,210,211,238,317,331,341],sensivit:204,sent:[25,34,51,58,69,74,83,88,91,105,107,113,114,119,137,138,144,146,150,164,166,170,175,176,177,180,186,188,195,199,210,228,234,247,264,267,269,272,276,277,278,279,287,291,295,306,308,316,328,336,341,364],sentenc:[46,91,198,205,206],sep:321,sep_kei:[22,180],separ:[8,11,13,14,20,23,29,31,33,37,38,40,46,48,51,57,58,61,62,64,71,72,75,77,80,84,85,86,87,89,91,92,93,95,96,98,101,102,103,105,106,112,114,115,119,121,123,126,129,131,133,136,137,138,140,151,153,154,159,165,166,167,168,169,175,180,195,198,199,205,206,215,217,218,219,220,221,224,233,235,238,242,245,246,247,251,257,261,286,291,296,308,321,322,324,327,329,336,341,344,364],separatli:29,sept:364,seq:87,sequenc:[10,13,14,15,33,38,64,80,81,87,89,113,126,154,158,184,206,242,265,271,321,322,328,330,343,344],seri:[51,61,79,114,131,136,138,226,330],serial:[11,83,138,250,260,261,285,325,338,340,344],serializ:296,seriou:[39,110],serious:63,serv:[45,49,55,64,83,101,103,104,111,135,152,219,296,312,322,324,355],server:[0,2,4,9,10,11,12,13,15,19,21,25,26,27,28,29,31,33,34,35,36,37,38,40,41,43,45,47,51,53,54,55,56,57,58,59,60,62,63,64,65,66,67,69,70,71,72,73,74,75,78,79,80,81,83,84,86,88,89,91,93,95,96,97,100,101,102,103,106,107,109,110,111,113,114,115,116,118,121,122,124,125,127,128,130,131,133,134,135,136,137,138,139,141,142,144,146,153,157,159,164,169,171,175,178,180,183,186,187,195,202,206,207,208,209,212,213,227,231,232,233,235,247,255,256,257,258,259,261,313,318,322,324,325,326,328,331,334,337,338,344,346,363,364],server_connect:285,server_disconnect:285,server_disconnect_al:285,server_epoch:[27,331],server_l:277,server_logged_in:285,server_nam:104,server_pid:[277,344],server_receive_adminportal2serv:264,server_receive_msgportal2serv:264,server_receive_statu:264,server_reload:[257,261],server_run:267,server_runn:305,server_servic:344,server_services_plugin:[40,104],server_services_plugin_modul:40,server_session_class:105,server_session_sync:285,server_st:267,server_twistd_cmd:277,server_twisted_cmd:277,serverconf:[157,261,364],serverconfig:[260,261,273,274],serverconfigadmin:263,serverconfigmanag:[273,274],serverfactori:[277,287,290],serverload:[43,169],serverlogobserv:337,servermsg:337,servernam:[4,8,9,54,74,90,104],serverprocess:[43,169],serversess:[40,105,114,141,142,210,242,262,285,308,364],serversessionhandl:[40,105,308,364],serverset:[80,164,241],servic:[12,23,40,43,45,67,71,90,100,103,104,110,131,133,141,142,169,262,264,267,268,276,277,284,305,312,344,364],sessdata:[307,308],sessid:[2,33,105,123,246,247,264,276,277,285,308],session:[2,12,15,24,31,33,40,43,45,47,51,53,57,74,81,84,88,89,91,96,100,107,114,123,127,138,139,141,142,144,146,147,148,150,151,152,154,156,157,160,162,166,167,171,186,188,189,209,210,211,230,246,247,249,250,251,257,262,264,272,276,277,278,279,285,286,287,290,295,296,305,306,308,310,326,328,329,336,344,345,364],session_data:308,session_from_account:308,session_from_sessid:308,session_handl:[53,105,141],session_portal_partial_sync:308,session_portal_sync:308,sessioncmdset:[31,43,162],sessionhandl:[40,83,141,142,144,247,262,272,278,279,285,286,306,307,364],sessionid:285,sessionobject:336,sessions_from_account:308,sessions_from_charact:308,sessions_from_csessid:[285,308],sessions_from_puppet:308,sesslen:247,set:[0,2,3,6,7,8,10,11,12,13,14,15,16,17,19,20,21,22,23,24,25,26,27,29,30,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,50,52,53,55,56,57,58,59,60,61,63,64,66,67,68,69,71,74,75,76,82,83,85,86,87,89,91,93,95,96,97,100,102,105,107,108,109,110,111,112,113,114,116,117,119,120,121,124,125,126,128,129,130,133,134,135,136,137,138,139,141,143,144,146,148,150,151,152,153,154,156,157,159,160,161,162,163,164,166,167,168,170,171,172,174,180,181,182,183,184,185,186,187,188,189,193,195,198,202,203,205,206,209,212,213,215,217,218,219,220,221,224,226,227,228,230,231,232,233,234,235,237,241,242,245,246,247,250,251,252,255,258,259,260,261,264,266,267,271,272,273,274,277,278,280,281,283,284,287,289,290,292,293,298,299,301,303,305,306,307,308,310,312,313,315,316,317,318,319,321,322,323,324,325,326,327,328,329,330,331,334,335,336,337,338,339,340,341,342,343,344,345,350,357,360,364],set_active_coordin:235,set_al:231,set_alias:154,set_attr:159,set_class_from_typeclass:318,set_dead:231,set_descript:51,set_detail:[187,233],set_game_name_and_slogan:350,set_gamedir:267,set_kei:154,set_nam:51,set_password:144,set_task:195,set_trac:[42,141],set_webclient_set:350,setcolor:81,setdesc:[43,57,165,212],setgend:189,sethelp:[20,43,68,166],sethom:[43,159],setlock:212,setnam:40,setobjalia:[43,159],setperm:157,setpow:364,setspe:213,sett:98,settabl:[74,86,290],setter:39,settestattr:50,settingnam:80,settings_chang:107,settings_default:[4,5,34,47,53,104,109,127,141,142,337,344,364],settings_ful:104,settings_mixin:[141,142,262,297,364],settl:[111,116],setup:[5,15,18,26,38,40,47,61,63,67,71,85,93,96,100,116,120,127,129,131,138,139,144,156,164,170,184,196,224,228,230,233,247,259,271,284,293,298,302,303,305,312,318,334,335,342,360,364],setup_str:302,setuptool:[63,75],sever:[0,11,14,19,22,29,31,33,36,38,41,42,48,50,52,55,56,57,59,62,69,79,80,102,104,109,113,116,119,125,137,158,159,167,168,169,187,194,195,231,233,247,293,294,319,324,364],sex:189,shall:[126,134],shaman:[57,109],shape:[20,22,39,58,61,111,235,330],sharabl:109,share:[9,25,31,36,37,42,46,57,59,63,64,65,80,86,90,102,103,105,112,116,119,125,133,135,145,194,195,252,261,298,317,319,330,344,351,364],sharedloginmiddlewar:351,sharedmemorymanag:[317,333],sharedmemorymodel:[177,239,316,318,334,335,364],sharedmemorymodelbas:[148,177,239,246,256,316,318,334,335],sharedmemorystest:335,shaw:[77,79],she:[0,22,33,56,91,126,180,189,205],sheer:159,sheet:[23,38,51,133,134,137,327,364],sheet_lock:58,shell:[7,23,25,26,36,57,58,59,60,63,75,86,87,90,100,103,108,110,125,128,287,364],shield:[29,77,86],shift:[14,15,27,108,195,232,238,344],shiftroot:232,shine:[21,233],shini:344,ship:[55,64,75,111],shire:62,shirt:182,shoe:182,shoot:[21,220,221,327],shop:[51,57,108,124,139,364],shop_exit:85,shopcmdset:85,shopnam:85,shopper:85,short_descript:54,shortcom:85,shortcut:[0,3,22,23,27,29,31,33,38,47,59,69,91,96,100,107,116,119,125,129,133,134,141,146,153,154,159,180,192,235,242,247,338,344,364],shorten:[42,46,125,252],shorter:[38,40,61,104,108,117,118,125,132,175,205,317,324,337],shortest:[39,206],shorthand:[89,126,159],shortli:[0,22,77],shot:220,should:[0,1,2,3,4,5,6,8,9,10,11,12,13,14,15,16,19,20,22,23,24,25,26,27,29,31,33,34,37,38,39,40,41,42,46,47,48,51,55,57,58,59,60,61,62,63,64,65,66,67,68,69,72,73,74,75,76,77,80,81,82,83,85,86,88,89,90,91,93,95,96,97,98,100,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,119,121,122,123,124,125,126,127,128,129,130,131,133,134,135,136,137,138,139,140,144,146,148,150,152,153,154,156,158,159,160,163,166,167,169,170,171,174,175,177,180,182,184,187,192,195,198,199,202,203,204,205,206,209,217,218,219,220,221,224,227,230,231,233,234,241,242,246,247,249,250,251,252,255,256,258,259,260,261,265,266,267,271,274,278,284,287,290,291,293,295,296,299,305,306,307,308,311,313,315,316,318,319,321,322,324,325,326,328,329,330,331,336,337,338,339,340,342,344,345,357,360,362],should_join:175,should_leav:175,should_list_cmd:166,shoulddrop:[221,247],shoulder:[58,182],shouldget:[221,247],shouldgiv:[221,247],shouldmov:[217,218,219,220,221,247],shouldn:[0,13,21,22,29,41,48,58,93,126,166,180,195,198,220,298],shouldrot:337,shout:29,shove:21,show:[0,12,13,14,20,22,24,26,27,30,33,35,37,38,39,40,42,46,48,49,52,54,55,57,58,60,61,62,63,64,68,69,70,71,73,81,82,85,86,90,91,95,96,97,98,101,102,103,104,105,106,110,111,114,116,117,118,119,120,122,124,126,127,128,129,131,133,134,136,137,138,139,144,156,157,159,164,165,167,169,171,179,181,182,185,186,187,188,190,202,215,220,221,226,233,234,235,247,249,251,252,265,267,276,326,328,337,338,339,344,357,364],show_foot:329,show_map:49,show_non_edit:251,show_non_us:251,show_valu:190,show_version_info:267,show_warn:267,showcas:[31,111],shown:[0,4,9,22,25,29,35,41,49,51,54,57,62,68,109,114,121,133,138,154,157,164,166,168,170,180,182,204,206,232,247,267,328,329],showtim:62,shrink:330,shrug:46,shrunk:101,shuffl:27,shun:[26,90,108],shut:[0,4,29,93,100,102,104,137,144,169,247,259,261,267,269,276,277,284,285,305,308,364],shutdown:[12,19,31,43,58,93,102,105,110,144,146,169,261,267,276,277,284,305,306,318,324,328],shy:[26,61,129],sibl:[10,57,96,102],sid:157,side:[0,1,11,24,36,38,48,49,58,73,74,83,91,105,112,119,126,127,133,137,138,144,146,148,165,167,168,177,179,185,212,239,246,256,264,276,277,285,288,291,292,295,306,307,308,316,318,319,321,330,335],sidebar:364,sidestep:19,sidewai:330,sigint:267,sign:[0,14,20,46,83,90,91,106,115,123,132,187,247,261,321,345],signal:[45,93,110,139,141,142,217,218,219,220,221,262,267,290,296,298,334,364],signal_acccount_post_first_login:107,signal_account_:107,signal_account_post_connect:107,signal_account_post_cr:107,signal_account_post_last_logout:107,signal_account_post_login:107,signal_account_post_login_fail:107,signal_account_post_logout:107,signal_account_post_renam:107,signal_channel_post_cr:107,signal_helpentry_post_cr:107,signal_object_:107,signal_object_post_cr:107,signal_object_post_puppet:107,signal_object_post_unpuppet:107,signal_script_post_cr:107,signal_typed_object_post_renam:107,signatur:[33,73,154,177,192,260,265,267,269,270,278,287,288,290,292,295,296,306,321,328,336,339,340,351],signed_integ:345,signedinteg:338,signedon:279,signifi:[14,241],signific:97,significantli:50,signup:4,silenc:269,silenced_system_check:127,silent:[10,62,118,157,164,271,279],silli:[60,89,96,109],silvren:[55,90],similar:[0,11,13,20,21,22,25,33,41,48,51,55,58,64,67,68,73,77,86,89,90,96,102,106,121,125,129,136,137,140,144,154,156,170,180,188,205,217,218,219,220,221,235,239,247,306,308,316,319,324,328,344,362],similarli:[58,62,90,112,218,234,315],simpl:[0,2,4,5,6,9,10,13,14,15,17,25,26,28,30,31,33,35,38,39,40,41,46,49,50,55,56,57,58,59,60,61,64,67,69,70,73,74,76,77,81,85,86,88,89,90,91,95,96,98,100,103,105,108,109,111,112,116,117,118,119,120,122,123,124,126,132,133,135,139,159,174,179,180,181,186,187,188,189,194,199,203,204,206,212,213,214,215,217,218,219,220,221,223,224,231,232,233,235,236,246,247,250,252,259,277,286,288,322,323,354,355,357,364],simpledoor:[141,142,178,364],simplemu:24,simpler:[10,15,51,56,158,159,325,362],simpleresponsereceiv:269,simplest:[6,29,58,73,90,116,153,322,345],simpli:[5,8,11,12,13,17,20,21,22,23,25,29,31,37,38,39,40,41,47,49,51,55,58,59,61,63,71,72,73,80,81,83,85,96,102,103,104,109,112,114,118,121,123,125,127,128,131,132,138,140,144,152,153,154,170,171,174,175,180,186,187,196,206,213,215,217,218,219,220,221,224,232,239,247,318,322,323,327,329,344],simplic:[22,39,55,126,171,186,232],simplif:[45,116],simplifi:[10,69,100,111,116,118,192],simplist:[116,123,132,137,205,214],simul:[33,73,93,213],simultan:[58,88,116,245,344],sinc:[0,1,3,4,5,6,9,10,11,13,14,19,21,22,23,25,26,27,28,29,31,33,34,35,38,39,40,41,42,44,47,48,49,50,51,54,55,56,57,58,59,60,61,62,64,69,74,76,80,83,84,85,86,88,89,90,91,94,96,97,100,102,104,110,111,114,115,116,118,119,121,122,123,125,126,127,131,133,134,135,138,144,146,148,152,153,154,159,167,168,169,175,176,179,180,181,184,187,199,206,215,217,218,219,220,221,227,232,233,241,245,247,251,252,255,257,260,261,267,269,272,284,289,291,299,305,306,308,315,316,317,318,322,323,324,326,328,331,334,337,340,341,342,344,357],singl:[0,5,10,14,16,22,23,31,33,37,38,44,48,51,55,57,58,59,61,64,67,73,77,83,87,88,90,95,96,105,108,111,112,114,119,122,125,127,128,129,139,144,150,157,159,165,176,177,180,204,209,215,217,218,219,220,221,233,234,235,247,251,252,255,260,261,299,306,308,317,319,321,322,327,328,330,336,341,344,357,364],single_type_count:182,singleton:[84,105,115,174,257,260,323,364],singular:[38,58,61,247],sink:26,sint:52,sir:46,sit:[11,14,29,33,47,55,63,80,83,90,95,96,119,121,123,125,167,198,199,206,224,232,233,242,258,261,280,324,339,342],sitabl:125,sitat:233,site:[8,16,17,23,37,69,71,79,80,90,97,98,100,101,103,111,133,134,145,312,362,364],site_nam:59,sitekei:364,situ:[11,318,325],situat:[0,6,11,22,33,37,42,46,62,76,83,86,102,105,119,125,131,153,154,159,194,334],six:[73,91,185,215],sixti:62,size:[16,24,42,49,58,97,101,108,111,137,138,141,235,269,283,321,327,329,330,334,337,344],size_limit:344,skeleton:123,sketch:[116,138],skill:[28,29,30,55,60,61,70,73,79,110,116,121,127,133,134,205,206,327],skill_combat:73,skillnam:73,skin:109,skip:[31,33,41,49,51,61,62,75,88,100,106,109,115,131,144,158,159,247,255,316,325],skipkei:296,skippabl:129,skull:109,sky:[102,132],slack:79,slam:188,slash:[20,41,55,73,116,122,232],slate:111,sleep:[10,29,33,73],slew:[61,73,75,322],slice:[119,156,321,329],slice_bright_bg:156,slice_bright_fg:156,slice_dark_bg:156,slice_dark_fg:156,slight:[8,91,184,195],slightli:[42,62,63,79,116,123,145,177,187,218,234,362],slightly_smiling_fac:138,slip:343,slogan:9,sloppi:38,slot:[58,134,187,188,218,220,252],slow:[27,116,176,213,231,235,251,280,286,321,341,344],slow_exit:[141,142,178,364],slower:[62,77,90,93],slowexit:213,slowli:79,slug:[175,239,318,362],slugifi:362,small:[4,14,15,16,25,30,33,37,55,57,58,61,63,69,70,79,81,85,90,91,93,96,97,98,108,111,122,123,124,127,128,139,185,220,224,235,290,326,327,330,344],smaller:[13,14,16,38,101,330],smallest:[58,62,80,90,184,327],smallshield:86,smart:[41,77,91,235],smarter:109,smash:[61,224,227],smell:61,smelli:109,smile:[33,165],smith:327,smithi:29,smoothi:203,smoothli:134,smush:48,snake:136,snap:82,snapshot:131,snazzi:78,sneak:242,snippet:[10,13,21,31,55,64,80,109,114,139,169,276,291,343,344],snoop:103,snuff:26,social:[55,71],socializechat:299,soft:[4,64,139,205,364],softcod:[129,139,364],softli:78,softwar:[36,63,90,131],solar:62,soldier:85,sole:[57,69,146],solid:[49,55,114],solo:[20,63,124],solut:[0,9,14,25,27,29,39,56,69,73,85,90,91,103,111,115,118,121,122,125,127,138,168,242,364],solv:[21,27,44,49,61,63,77,97,111,203,232],some:[0,3,4,5,6,8,9,11,12,13,14,15,16,20,21,22,23,24,25,26,27,28,29,31,33,36,37,38,40,42,43,45,46,48,49,50,51,55,57,58,60,61,62,63,64,67,69,70,72,73,74,75,77,78,79,80,82,83,85,86,87,89,90,91,95,96,97,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,121,122,123,124,125,126,127,128,131,133,134,136,137,138,139,144,153,154,159,161,165,168,169,171,175,176,179,180,181,186,195,198,204,205,212,215,218,219,220,221,226,227,230,232,233,234,235,242,247,251,252,256,269,271,276,279,305,318,321,322,327,328,331,334,337,338,344,357,362,364],some_long_text_output:329,somebodi:[0,138],somehow:[33,40,73,80,87,90,113,140,182,326],someon:[0,1,29,33,46,48,49,58,60,80,85,90,96,103,107,115,117,118,119,138,144,165,182,226,231,232,247],somepassword:23,someplac:231,someth:[0,3,4,6,8,9,10,11,12,14,20,22,23,25,27,29,30,33,38,39,40,41,44,46,49,51,52,56,57,58,59,60,61,62,64,65,67,68,69,70,71,72,73,75,80,82,83,85,86,89,90,91,93,95,96,102,104,107,108,109,111,114,115,119,123,125,127,128,129,133,134,135,137,138,139,144,152,154,159,165,166,167,179,180,182,189,198,204,206,213,217,218,219,220,221,232,233,234,235,242,247,252,306,318,322,328,329,338,344,362],sometim:[6,22,27,33,40,42,50,51,60,62,64,80,86,91,93,95,96,102,109,110,119,136,138,166,245],somewhat:[4,22,41,57,127,138,180],somewher:[0,12,37,73,80,90,109,121,125,131,159,175,239,318,344,364],soon:[42,61,69,72,96,100,105,127,226,296,344],sophist:[10,27,55,108,116],sorl:4,sorri:[80,242],sort:[3,6,11,31,39,49,59,61,64,69,73,83,84,90,105,110,112,116,117,135,140,179,190,217,218,219,220,221,233,247,252,256,317,318,328,357,362],sort_kei:296,sought:[144,151,175,239,247,316,318],soul:111,sound:[22,29,37,58,61,80,82,83,102,104,111,115,131,138,205,291],sourc:[0,4,9,10,12,15,16,17,20,21,22,23,27,31,36,37,43,46,47,55,57,60,63,64,67,68,72,75,76,79,88,89,96,97,108,122,127,128,130,131,134,139,141,144,145,146,147,148,150,151,152,153,154,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,173,174,175,176,177,179,180,181,182,184,185,186,187,188,189,190,192,193,194,195,196,198,199,202,203,204,205,206,209,210,211,212,213,214,215,217,218,219,220,221,223,224,226,227,228,230,231,232,233,234,235,237,238,239,241,242,244,245,246,247,249,250,251,252,254,255,256,257,258,259,260,261,263,264,265,266,267,269,270,271,272,273,274,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,298,299,300,302,303,304,305,306,307,308,310,311,312,315,316,317,318,319,321,322,323,324,325,326,327,328,329,330,331,333,334,335,336,337,338,339,340,341,342,343,344,345,349,350,351,352,355,357,359,360,362,363,364],source_loc:[25,77,96,117,232,233,235,247],source_object:[171,174,186],sourceforg:[280,281,291,294],sourceurl:279,south:[0,22,44,49,111,121,159,299],south_north:111,southeast:159,southern:111,southwest:[20,159],space:[9,20,21,22,25,33,35,38,41,46,48,49,51,57,68,80,87,91,95,102,109,111,114,116,118,126,129,137,138,151,154,159,165,167,168,170,171,174,202,205,206,221,232,247,250,311,318,321,322,327,328,330,336,343,344,364],spaceship:121,spacestart:343,spaghetti:[13,328],spam:[12,28,103,116,138,310],spammi:[12,116],span:[16,17,108],spanish:76,spare:[217,218,219,220,221],spars:310,spatial:111,spawen:203,spawn:[43,47,53,55,93,120,122,137,138,141,157,159,203,218,219,249,250,251,252,364],spawner:[18,45,89,120,139,141,142,159,219,220,248,364],spd:134,speak:[0,15,19,41,46,60,96,113,117,118,126,133,165,206,241,247],speaker:[46,205,206],spear:109,special:[2,10,11,13,14,15,19,20,25,26,27,30,31,33,35,37,38,41,42,51,58,60,61,64,69,76,77,80,81,83,85,86,88,89,95,102,103,104,107,111,112,113,114,116,119,122,123,125,127,131,134,137,146,148,150,153,165,168,187,189,206,215,219,220,232,233,235,242,244,247,271,272,295,316,318,322,328,343],specif:[0,2,4,9,11,12,22,23,24,25,26,27,31,33,36,37,38,39,40,41,42,46,47,50,51,53,55,56,59,61,62,64,67,69,77,78,79,80,82,87,88,89,90,91,95,96,100,105,107,110,111,112,115,116,119,121,122,123,124,125,126,127,131,132,133,134,135,137,138,144,145,147,150,157,159,169,171,175,177,178,179,180,192,193,194,195,199,204,206,238,241,245,247,255,257,267,272,279,295,296,306,316,318,321,322,326,328,329,330,362,364],specifi:[3,11,12,16,19,21,22,27,29,31,38,39,46,49,51,54,58,62,63,68,83,84,86,88,90,91,98,100,102,103,105,109,111,112,114,115,119,123,127,134,136,150,151,159,166,175,180,182,183,185,187,188,192,194,195,199,203,204,206,215,218,219,220,235,241,242,247,250,251,252,257,278,304,319,321,322,324,327,328,331,338,339,340,344,357,362],spectacular:42,speech:247,speechlock:241,speed:[11,47,62,82,86,87,93,116,134,213,245,252,285,319,341],spell:[15,19,28,57,60,109,112,215,220,252],spell_attack:220,spell_conjur:220,spell_heal:220,spell_nam:220,spellnam:220,spend:[39,89,91,119,217,218,219,220,221],spend_act:[217,218,219,220,221],spend_item_us:219,spent:220,sphinx:38,spin:[62,90],spit:[3,60,116],splashscreen:186,split:[9,25,31,33,41,58,91,104,105,111,118,121,123,131,136,138,151,167,168,184,232,235,249,293,308,321,322,329,331],split_2:138,split_nested_attr:159,splithandl:138,spoken:[0,46,72,205,206,247],spoof:315,spool:63,sport:87,spot:[57,64,144],spread:[70,73,109,364],spring:[82,124,187],sprint:213,sprofil:267,spunki:77,spuriou:364,spyrit:24,sql:[7,36,56,57,64,86,125,139,302,364],sqlite3:[25,55,64,86,123,127,128,131,344,364],sqlite3_prep:305,sqlite:[23,86,128,305],sqllite:36,sqrt:39,squar:[38,39,129],squeez:[38,86],src:[10,17,20,59,75,80,89,100,102,133,137,139,210],srcobj:[154,167],srun:271,srv:36,ssessionhandl:83,ssh:[9,25,40,55,64,83,90,105,110,141,142,262,275,306,307,364],ssh_interfac:90,ssh_port:90,sshd_config:103,sshfactori:287,sshprotocol:287,sshserverfactori:287,sshuserauthserv:287,ssl:[7,8,55,64,67,83,88,141,142,146,164,262,275,279,292,307,364],ssl_context:[288,292],ssl_interfac:90,ssl_port:90,sslcertificatefil:8,sslcertificatekeyfil:8,sslciphersuit:8,sslengin:8,ssllab:8,sslprotocol:[8,288,292],ssltest:8,sslv3:67,sta:327,stab:[29,122,232],stabil:[61,170,205],stabl:[37,40,56,60,100],stabli:[97,261],stack:[13,31,61,121,137,145,152,153,227,247,251,308,328,336],stackedinlin:145,stackexchang:127,stackful:336,stacktrac:[251,336],staf:108,staff:[9,19,25,57,61,68,73,80,108,109,111,123,133,152,252,322],staff_onli:239,staffer:9,staffernam:9,stage:[2,36,56,61,77,111,123,131,133,145,173,244],stagger:279,stai:[1,31,49,51,63,90,91,121,125,126,138,235],stale:[100,125,260],stale_timeout:260,stalker:362,stamina:[30,190,220],stamp:[27,96,105,125,137,144,148,157,169,246,256,299,304,318],stanc:[116,206],stand:[13,17,20,21,22,25,29,38,49,56,61,63,72,73,80,86,90,95,96,111,116,121,122,123,127,131,133,138,165,179,206,231,247,256,261,298,306,316,319,322,324,330],standalon:[67,103],standard:[0,1,6,8,9,15,21,27,30,41,50,57,58,59,63,64,79,83,88,91,95,103,113,114,116,120,126,131,136,139,141,144,156,185,186,206,234,241,247,287,289,294,311,316,321,330,331,336,345,364],stanza:277,star:159,stare:131,start:[0,1,2,3,4,5,7,12,13,14,15,16,18,20,21,23,25,26,27,29,31,33,34,38,39,40,41,42,44,45,47,48,49,50,51,54,55,57,59,60,61,62,64,65,66,67,69,70,72,73,74,75,76,77,79,80,83,84,86,87,90,91,93,95,96,97,98,101,102,103,104,105,106,107,108,109,111,114,116,119,120,121,123,124,125,127,128,130,131,132,133,136,137,138,139,144,146,151,152,158,159,164,165,167,168,169,170,174,179,180,185,187,188,189,190,195,205,206,215,217,218,219,220,221,226,227,230,231,233,235,247,249,250,251,255,256,258,259,260,261,264,267,269,271,272,277,278,279,280,284,285,286,291,292,298,304,305,308,312,317,321,322,323,324,326,328,329,330,331,336,337,344,363,364],start_all_dummy_cli:298,start_attack:231,start_bot_sess:308,start_delai:[102,116,120,121,227,256,259,261,324],start_driv:121,start_evennia:267,start_hunt:231,start_idl:231,start_lines1:267,start_lines2:267,start_loc_on_grid:49,start_olc:249,start_only_serv:267,start_ov:51,start_patrol:231,start_plugin_servic:40,start_portal_interact:267,start_serv:277,start_server_interact:267,start_sunrise_ev:62,start_text:215,start_turn:[217,218,219,220,221],startapp:[69,86,133,134],startclr:[114,336],startedconnect:[264,278,279],starter:[9,136],starthour:25,startnod:[51,85,188,230,249,328],startnode_input:[51,188,230,249,328],startproduc:269,startservic:[270,312],startset:233,startswith:[41,84,159,321],starttupl:287,startup:[11,35,40,60,62,90,102,104,136,247,256,296,305,337,344],stat:[17,43,60,61,71,85,116,123,133,134,136,139,169,179,217,218,219,220,221,364],state:[11,13,14,31,33,42,43,50,51,55,56,64,80,95,100,102,105,110,114,116,121,122,126,127,131,137,138,144,150,152,153,156,163,171,174,212,217,218,219,220,221,224,227,231,233,252,256,258,259,261,267,287,316,326,328],state_unlog:163,statefultelnetprotocol:[290,298],statement:[10,13,14,27,31,42,49,51,55,58,59,83,86,95,96,118,119,124,247,322,343],static_overrid:[135,136,137],static_root:136,statict:169,station:121,stationari:231,statist:[3,12,104,105,120,124,135,169,190,255,300,317,334],statu:[20,29,51,58,61,88,90,104,105,115,131,169,175,179,219,220,221,231,261,265,267,276,277,278,281,295,364],status:61,status_cod:269,stderr:234,stdin_open:100,stdout:[59,100,234,267,337],steadi:64,steal:[85,166],steer:121,step1:29,step2:29,step3:29,step:[0,4,7,8,13,14,21,23,29,31,33,36,38,39,41,45,46,50,51,58,63,69,73,77,82,85,86,91,97,100,102,106,108,121,122,123,126,127,128,134,138,139,158,180,233,255,259,261,271,283,294,298,299,308,318,322,325,326,328,329,363,364],stick:[15,33,38,51,63,113,157],still:[0,1,4,6,9,11,13,14,15,19,20,22,25,26,29,31,33,37,38,39,40,41,49,55,57,58,60,62,63,64,77,78,79,83,91,95,96,102,103,105,106,107,108,110,114,121,122,123,125,126,128,131,134,138,152,159,164,166,171,186,215,217,218,219,220,221,230,233,235,245,247,251,255,258,299,328,330,331,340,344],sting:111,stock:[34,55,85,101,210,357],stolen:[103,321],stone:[20,33,60],stoni:60,stop:[7,9,10,12,14,20,25,27,29,34,41,42,49,51,57,58,62,63,67,74,77,80,82,89,90,93,95,96,100,102,104,105,106,108,115,116,120,121,123,128,137,139,156,159,164,169,179,184,194,196,206,212,213,218,221,226,227,247,255,258,259,260,261,266,267,269,272,284,285,305,306,312,321,322,324,344,364],stop_driv:121,stop_evennia:267,stop_serv:277,stop_server_onli:267,stopproduc:269,stopservic:[270,312],storag:[11,13,23,28,29,33,47,56,64,73,85,86,96,102,125,133,138,148,169,174,177,198,205,235,242,246,247,251,252,256,259,261,274,306,310,314,316,318,323,338,339,364],storage_modul:323,storagecontain:102,storagescript:102,store:[0,2,9,13,15,21,23,27,28,29,31,33,34,37,39,40,41,44,46,47,49,50,55,56,57,58,60,61,64,69,73,75,80,82,85,86,87,89,91,95,97,100,102,104,105,112,113,115,116,119,121,123,125,127,128,131,133,134,135,136,137,138,139,144,146,148,153,156,157,159,160,162,167,168,174,177,179,187,188,195,202,204,205,206,210,213,214,219,223,232,233,235,241,242,246,250,251,252,253,257,258,259,260,261,267,271,272,273,274,277,279,280,281,283,291,294,299,305,306,307,308,310,312,316,317,318,319,321,323,324,325,326,327,328,329,334,336,338,339,340,344,357,362,364],store_kei:261,store_result:48,store_tru:234,stored_obj:25,storekei:[85,261],storenam:85,storeroom:85,storeroom_exit:85,storeroom_kei:85,storeroom_key_nam:85,stori:[3,9,97,133],storm:[28,119],storypag:3,storytel:123,stove:247,str:[0,10,11,22,25,27,39,40,50,51,58,59,60,73,74,84,91,96,113,114,119,125,127,133,134,141,144,146,147,150,151,152,153,154,159,166,170,174,175,176,177,179,180,182,184,187,188,189,190,192,193,194,195,198,199,204,205,206,210,212,215,217,218,219,220,221,230,233,234,235,238,239,242,245,247,250,251,252,255,257,258,259,261,264,265,267,272,273,274,276,277,278,279,280,282,285,286,287,290,291,292,295,296,298,304,305,306,307,308,310,311,312,315,316,317,318,319,321,322,323,324,326,327,328,329,330,336,337,338,339,340,341,342,343,344,345,349,362],straight:[49,68,126],straightforward:[25,41,85,91,121,123],strang:[6,8,14,29,41,56,131,153,171],strangl:90,strategi:[42,221],strattr:[1,11,316],strawberri:234,stream:[106,276,280,306],streamlin:[36,179],strength:[11,57,58,60,61,73,80,116,134],stress:[93,298],stretch:[38,111],stribg:344,strict:[10,251,321],stricter:251,strictli:[19,51,59,77,133,186,220,330],strike:[51,82,116,165,214,220,221],string1:344,string2:344,string:[5,9,11,12,13,15,19,20,22,23,25,27,29,31,33,34,35,38,41,42,49,50,54,55,57,58,59,60,62,68,71,76,82,83,84,86,87,88,89,90,93,95,96,97,104,109,111,112,113,114,115,116,119,124,125,127,129,133,134,137,138,139,141,142,144,146,147,148,150,151,154,157,159,165,166,167,168,169,170,174,175,176,177,179,180,182,186,188,198,199,203,204,205,206,210,211,215,217,218,219,220,221,230,231,235,238,239,240,241,242,245,246,247,250,251,252,255,256,259,261,267,269,272,276,279,287,290,291,293,299,304,306,308,311,315,316,317,318,319,320,321,324,325,326,327,329,330,336,337,338,340,341,342,343,344,345,362,364],string_from_modul:344,string_partial_match:[245,344],string_similar:344,string_suggest:344,stringproduc:269,strip:[21,22,33,38,41,51,58,74,81,85,108,109,114,118,123,151,159,167,168,206,245,252,272,287,290,291,321,322,328,336,344,364],strip_ansi:[81,321,343],strip_control_sequ:344,strip_mxp:321,strip_raw_ansi:321,strip_raw_cod:321,strippabl:328,stroll:213,strong:[80,114,123,343],strongest:80,strongli:[64,73,95,124,205],strr:204,struct:56,structur:[9,11,33,37,41,45,47,48,49,51,55,56,59,63,64,68,69,80,83,88,95,96,109,119,133,134,136,138,159,206,247,250,251,252,291,296,319,325,328,354,361,362,364],strvalu:[11,316,317],stuck:[51,63],studi:[59,364],stuff:[3,9,11,21,29,31,37,38,47,49,51,57,61,67,73,80,85,96,102,105,107,109,119,138,153,159,189,234,261,305,350,364],stumbl:97,stupidli:34,sturdi:327,stutter:108,style:[3,16,20,21,27,33,37,38,40,41,43,45,51,55,57,58,61,79,87,95,106,111,114,116,122,124,129,138,148,154,156,167,182,183,188,199,217,234,247,251,326,330,344,364],styled_foot:154,styled_head:[33,154],styled_separ:154,styled_t:[33,154],sub:[9,11,36,37,38,57,65,69,88,90,108,109,116,119,137,138,143,149,172,173,178,180,206,234,236,238,240,243,250,252,253,262,314,320,321,343,346,364],sub_ansi:321,sub_app:133,sub_brightbg:321,sub_dblspac:343,sub_mxp_link:343,sub_text:343,sub_xterm256:321,subclass:[27,64,105,109,118,119,125,159,180,235,246,251,256,277,290,296,315,318,335,340,344,364],subdir:127,subdirectori:[37,127],subdomain:[8,90,103],subfold:[47,86,95,96,134,135],subhead:38,subject:[36,39,81,86,90,124,189,199,364],submarin:121,submenu:[106,180,249],submenu_class:180,submenu_obj:180,submiss:[188,357],submit:[17,37,103,133,171,188,357,362],submitcmd:188,submodul:291,subnegoti:291,subnet:[12,157],subpackag:[88,127],subprocess:[25,344],subreddit:79,subscrib:[12,33,34,41,53,58,64,80,115,128,132,146,164,174,175,176,219,261,278,309],subscript:[33,58,79,115,132,164,173,176,177,261],subsequ:[10,11,33,95,116,164,322,344],subsequent_ind:330,subset:[56,112,127],subsid:125,substitut:[51,71,87,106,247,321,343],substr:321,subsubhead:38,subsubsubhead:38,subsystem:[9,63,86,242],subtitl:17,subtract:[85,250],subturn:116,subword:344,succ:241,succe:[61,116,185],succeed:[185,234],success:[73,116,123,134,144,175,179,185,217,218,219,220,221,224,232,233,242,251,260,267,271,318,326,338,344,362],success_teleport_msg:233,success_teleport_to:233,success_url:362,successfuli:203,successfulli:[10,28,33,36,60,77,110,111,130,144,203,232,235,247,259,260,267,279,311,318,362],suddenli:[26,97,318],sudo:[63,67,100,103],suffic:[17,57,61],suffici:[86,90,119],suffix:[27,97,114,321,336,337,344],suggest:[1,23,25,37,38,48,51,52,55,61,68,70,90,95,97,125,138,140,151,166,179,206,233,247,344],suggestion_cutoff:166,suggestion_maxnum:166,suit:[29,34,55,64,117,139,170,344,362,364],suitabl:[21,25,33,37,55,63,64,80,83,87,88,90,112,131,152,242,301,308,324,328],sum:[37,82,91,139,153],summar:[0,79,139],summari:[0,7,46,79,96,110,123,180,364],summer:187,sun:62,sunris:62,sunt:52,super_long_text:329,superclass:145,superfici:205,superflu:343,supersus:242,superus:[2,4,5,6,9,12,13,14,19,20,21,23,25,41,58,60,63,81,95,96,111,122,134,144,148,158,169,175,182,212,231,241,242,247,252,267,318,322,324,364],supplement:51,suppli:[10,11,27,34,37,51,58,59,63,68,72,74,84,88,93,102,105,109,112,114,115,116,123,127,148,153,154,157,159,164,169,170,176,180,184,186,187,190,245,246,247,251,255,256,261,278,308,318,326,329,331,341,344],supporst:294,support:[2,4,7,8,9,11,23,26,33,37,38,40,42,44,47,49,50,51,56,57,58,61,63,64,65,66,70,74,75,76,81,83,86,87,90,91,94,98,100,103,109,110,113,114,123,126,139,156,165,183,184,185,187,198,234,241,247,250,251,252,261,272,280,281,282,283,287,289,290,291,292,294,296,307,316,321,325,328,329,330,336,341,344,349,364],supports_set:[74,272],suppos:[0,33,51,61,76,83,109,119,138,144,180,255],supposedli:[67,205,291],suppress:[24,289],suppress_ga:[141,142,262,275,364],suppressga:289,supress:289,sur:79,sure:[0,2,4,5,8,9,11,12,13,14,15,19,20,21,23,25,28,29,30,31,33,36,37,38,41,42,44,49,51,57,58,60,61,62,63,67,71,72,73,75,78,80,81,86,87,89,90,91,93,95,96,97,100,102,105,106,109,110,111,112,113,115,116,118,123,125,126,127,128,131,133,134,136,137,138,140,144,146,152,153,154,156,159,164,167,174,176,180,182,196,204,205,206,211,215,220,223,227,231,232,233,238,241,242,247,251,252,255,258,259,267,271,277,279,284,305,311,312,313,315,317,318,321,323,325,328,334,340,341,343,344,360,362],surfac:[58,82,103],surpris:[22,39,69,80,91],surround:[31,33,111,116,119,129,157,231,340,344,364],surviv:[5,11,27,28,31,50,51,84,102,105,115,116,126,146,153,169,180,256,257,261,324,326,328],suscept:[27,56,242],suspect:133,suspend:[100,103,106],suspens:102,suspici:51,suspicion:133,svn:[36,108],swallow:[96,118,272,276,343],swap:[43,114,127,137,138,159,187,202,318,326,364],swap_autoind:326,swap_object:318,swap_typeclass:[60,125,144,318],swapcas:321,swapcont:138,swapper:318,swedish:76,sweep:102,swiftli:10,swing:[28,29,33,82],switch1:129,switch2:129,switch_opt:[156,157,158,159,164,165,166,167,168,169,187],sword:[20,28,33,61,73,77,85,86,119,179,206,245,252,341,344],symbol:[14,15,33,49,75,106,108,119,171,215,235,329],symlink:[38,63],symmetr:330,sync:[64,83,105,131,174,285,290,305,306,307,308,316,325],sync_port:308,syncdata:[307,308],syncdb:127,synchron:[337,364],syntact:[242,344],syntax:[5,6,13,14,15,21,22,23,29,33,41,46,48,51,55,58,60,62,76,80,91,97,114,119,123,129,134,141,142,154,158,159,167,168,170,180,185,187,188,234,242,247,267,279,306,318,320,321,336,364],syntaxerror:60,sys_cmd:152,sys_game_tim:59,syscmdkei:[33,53,141],syscommand:[141,142,149,155,247,364],syslog:209,sysroot:75,system:[0,2,4,5,9,10,11,19,21,22,23,26,27,28,29,31,34,36,37,38,39,40,41,43,44,46,47,49,53,55,56,59,60,62,63,64,67,74,75,76,77,79,81,83,84,85,86,87,90,93,95,97,102,103,104,105,107,108,109,110,111,112,114,115,119,121,122,125,126,127,128,129,131,132,134,136,138,139,140,141,142,145,146,148,149,150,152,154,155,156,158,166,168,170,172,175,176,177,179,180,182,186,193,194,195,196,198,199,202,203,205,206,209,210,211,215,217,218,219,220,221,226,230,233,235,236,239,241,242,246,247,249,252,253,259,267,290,296,304,314,318,322,324,327,328,337,363,364],system_command:33,systemat:39,systemctl:8,systemd:67,systemmultimatch:168,systemnoinput:168,systemnomatch:168,systemsendtochannel:168,tab:[9,14,26,30,36,59,69,95,96,106,114,137,138,321,330,343],tabl:[0,4,13,15,45,46,48,53,58,59,64,69,82,88,97,111,113,114,119,125,128,134,154,156,166,169,188,291,310,321,327,329,330,341,344,364],table_char:327,table_format:156,table_lin:330,table_str:58,tablea:327,tableb:327,tablechar:[58,327],tableclos:[88,291],tablecol:330,tabledata:329,tableevmor:329,tableopen:[88,291],tablet:16,tabletop:[58,73,79,124,217,221],tabsiz:[321,330],tabstop:343,tabularinlin:315,tack:[20,119,153],tackl:37,tactic:[73,116],taction:116,tag:[9,12,13,18,20,24,33,43,45,48,51,55,57,58,73,74,86,87,88,95,96,100,109,114,119,124,125,134,136,137,138,139,140,141,142,145,159,173,177,183,187,189,199,204,206,209,230,232,239,241,244,251,252,254,282,296,304,314,315,317,318,321,324,327,330,341,364],tag_categori:315,tag_data:315,tag_kei:315,tag_typ:315,tagadmin:315,tagform:315,tagformset:315,taghandl:[112,125,315,319],taginlin:[145,173,237,244,254,315],tagkei:[241,319,324],taglin:17,tagnam:252,tagstr:[252,319],tagtyp:[112,317,319,341],tail:[76,90,100,267,337],tail_log_fil:[267,337],tail_log_funct:337,tailor:[4,69,357],take:[0,3,4,9,10,11,13,14,15,16,17,19,20,21,22,25,26,27,28,29,31,33,37,38,40,42,46,49,51,52,55,56,57,58,62,64,69,70,74,75,76,77,79,80,83,85,90,91,95,96,103,104,105,106,108,109,111,114,116,119,121,122,123,124,125,126,127,133,134,136,138,139,144,146,151,152,156,168,174,177,179,182,184,187,188,203,204,206,209,213,215,217,218,219,220,221,230,231,233,242,250,252,271,287,295,307,308,317,318,321,326,327,328,329,338,344,345],taken:[31,56,64,103,116,120,121,123,165,186,209,217,218,219,220,221,287,311,321,324],takeov:309,taladan:48,tale:3,talk:[23,27,33,34,37,40,41,46,58,60,90,91,131,138,165,179,205,206,214,233,264],talker:[55,61],talki:64,talking_npc:[141,142,178,364],talkingcmdset:214,talkingnpc:214,tall:[129,165,206],tallman:165,tandem:61,tantal:14,target1:220,target2:220,target:[21,25,28,29,30,33,34,40,58,73,88,103,114,116,119,123,127,136,138,144,154,159,164,165,169,177,182,185,187,199,215,217,218,219,220,221,231,235,247,317,321,324,328,344],target_loc:[213,233,235,247],target_obj:242,targetlist:199,task:[0,27,36,40,41,91,93,102,110,112,138,193,195,215,260,261,344],task_handl:[141,260,344],task_id:[195,260],taskhandl:[141,142,253,344,364],taskhandlertask:[260,344],tast:[22,34,133],tavern:206,tax:[75,93],taylor:79,tb_basic:[141,142,178,216,364],tb_equip:[141,142,178,216,364],tb_filenam:322,tb_item:[141,142,178,216,364],tb_iter:322,tb_magic:[141,142,178,216,364],tb_rang:[141,142,178,216,364],tbbasiccharact:217,tbbasicturnhandl:217,tbearmor:218,tbequipcharact:218,tbequipturnhandl:218,tbeweapon:218,tbitemscharact:219,tbitemscharactertest:219,tbitemsturnhandl:219,tbmagiccharact:220,tbmagicturnhandl:220,tbodi:134,tbrangecharact:221,tbrangeobject:221,tbrangeturnhandl:221,tchar:116,tcp:[55,103],tcpserver:[40,312],teach:124,team:[33,36,61,64,70,108,131],teamciti:364,teardown:[127,196,228,293,342],teaser:90,tech:[79,364],technic:[4,6,9,10,11,19,20,23,39,40,51,64,70,83,90,108,112,114,119,125,139,179,364],techniqu:[29,139,321],tediou:[1,106,111],teenag:[21,103],tehom:[9,119],tehomcd:9,tel:[0,12,43,58,63,91,121,159],teleport:[12,14,20,43,58,85,122,140,159,165,233,241,322],teleportroom:233,televis:31,tell:[0,3,5,8,10,12,13,19,21,22,23,26,29,31,33,41,42,43,46,49,51,58,59,60,61,69,73,74,75,76,77,80,83,86,87,90,91,93,95,96,100,102,103,109,110,116,117,121,127,128,130,131,132,134,135,139,146,156,164,165,176,177,185,206,233,247,267,285,296,308,326,362],telnet:[9,15,25,30,40,55,63,64,75,79,83,100,101,103,105,110,114,137,138,141,142,169,262,275,280,281,282,283,287,288,289,291,292,294,298,306,307,343,364],telnet_:90,telnet_hostnam:54,telnet_interfac:90,telnet_oob:[88,141,142,262,275,364],telnet_port:[9,36,54,90,299],telnet_ssl:[141,142,262,275,364],telnetoob:291,telnetprotocol:[288,290,292],telnetserverfactori:290,teloutlock:241,temp:177,tempat:188,templat:[2,3,4,5,27,31,47,64,81,87,104,107,109,123,125,131,134,135,136,137,138,141,142,145,165,167,188,230,267,296,306,307,316,320,327,336,355,362,364],template2menu:[51,328],template_nam:362,template_overrid:[4,135,136,137],template_regex:[316,336],template_rend:107,template_str:[51,87],templates_overrid:135,templatestr:327,templatetag:[141,142,346,356,364],templateview:362,tempmsg:[175,177,364],temporari:[6,11,110,122,127,131,153,177,198,217,218,219,220,221,261,328,364],temporarili:[20,26,31,51,60,90,97,102,127,164,169,175,195,203],tempt:[61,95,104,157],ten:[29,90,111],tend:[41,57,61,64,73,76,86,90,97,103,119,121,124,129,138,159,205,209],tent:[45,111,139],terabyt:25,term:[0,10,31,62,63,64,69,90,91,96,126,139,154,204],term_siz:[42,141],termin:[4,23,26,27,38,42,47,59,60,63,64,75,90,93,95,96,97,100,103,106,110,114,123,126,131,138,139,141,194,215,217,218,219,220,221,266,267,287,294,310,362],terminalrealm:287,terminals:287,terminalsessiontransport:287,terminalsessiontransport_getp:287,termux:364,terrain:49,terribl:280,ters:102,test1:[11,74,330],test2:[11,33,74,114],test3:[11,330],test4:[11,330],test5:11,test6:11,test7:11,test8:11,test:[0,5,10,11,13,14,15,17,19,20,21,22,23,24,25,29,31,33,36,37,38,41,42,45,46,50,51,56,58,60,61,62,63,65,67,68,69,72,73,74,79,80,81,85,89,90,91,95,96,98,106,107,109,111,115,116,120,124,130,131,132,133,137,138,139,141,142,149,151,155,156,158,166,169,178,182,185,187,188,191,207,208,215,217,218,219,220,221,222,223,230,250,251,262,269,272,275,296,297,298,302,318,320,321,322,324,328,332,342,344,346,348,350,356,364],test_:127,test_about:170,test_accept:196,test_access:170,test_add:196,test_add_valid:196,test_all_com:170,test_alternative_cal:127,test_amp_in:293,test_amp_out:293,test_at_repeat:228,test_attribute_command:170,test_audit:211,test_ban:170,test_batch_command:170,test_bold:293,test_c_creates_button:303,test_c_creates_obj:303,test_c_dig:303,test_c_examin:303,test_c_help:303,test_c_login:303,test_c_login_no_dig:303,test_c_logout:303,test_c_look:303,test_c_mov:303,test_c_move_:303,test_c_move_n:303,test_c_soci:303,test_cal:196,test_cas:127,test_cboot:170,test_cdesc:170,test_cdestroi:170,test_cemit:170,test_channel:170,test_channelcommand:170,test_char_cr:170,test_char_delet:170,test_clock:170,test_color:293,test_color_test:170,test_copi:170,test_creat:170,test_cwho:170,test_data_in:293,test_data_out:293,test_del:196,test_desc:170,test_desc_default_to_room:170,test_destroi:170,test_destroy_sequ:170,test_dig:170,test_do_nested_lookup:170,test_edit:196,test_edit_valid:196,test_emit:170,test_empty_desc:170,test_examin:170,test_exit:196,test_exit_command:170,test_find:170,test_forc:170,test_general_context:352,test_get:360,test_get_and_drop:170,test_get_authent:360,test_get_dis:360,test_giv:170,test_handl:196,test_help:170,test_hom:170,test_ic:170,test_ic__nonaccess:170,test_ic__other_object:170,test_ident:293,test_idl:303,test_info_command:170,test_interrupt_command:170,test_invalid_access:360,test_inventori:170,test_ital:293,test_large_msg:293,test_list:196,test_list_cmdset:170,test_lock:[170,196],test_look:170,test_mask:211,test_memplot:303,test_menu:215,test_messag:304,test_mudlet_ttyp:293,test_multimatch:170,test_mux_command:170,test_mycmd_char:127,test_mycmd_room:127,test_nam:170,test_nested_attribute_command:170,test_nick:170,test_object:170,test_object_search:127,test_ooc:170,test_ooc_look:170,test_opt:170,test_pag:170,test_password:170,test_perm:170,test_pi:170,test_plain_ansi:293,test_pos:170,test_quel:170,test_queri:[141,142,262,297,364],test_quit:170,test_resourc:[127,141,142,170,196,211,228,293,320,360,364],test_return_valu:127,test_sai:170,test_script:170,test_send_random_messag:228,test_server_load:170,test_sess:170,test_set_game_name_and_slogan:352,test_set_help:170,test_set_hom:170,test_set_obj_alia:170,test_set_webclient_set:352,test_simpl:127,test_simple_default:170,test_spawn:170,test_split_nested_attr:170,test_start:196,test_tag:170,test_teleport:170,test_toggle_com:170,test_tunnel:170,test_tunnel_exit_typeclass:170,test_typeclass:170,test_upp:127,test_valid_access:360,test_valid_access_multisession_0:360,test_valid_access_multisession_2:360,test_valid_char:360,test_wal:170,test_whisp:170,test_who:170,test_without_migr:127,testabl:127,testaccount:170,testadmin:170,testampserv:293,testapp:133,testbatchprocess:170,testbodyfunct:228,testbuild:170,testcas:[127,293,303,335,342,352],testcmdcallback:196,testcomm:170,testcommand:51,testdefaultcallback:196,testdummyrunnerset:303,testdynamic:127,tester:[90,119,285],testeventhandl:196,testform:327,testgener:170,testgeneralcontext:352,testhelp:170,testid:33,testinterruptcommand:170,testirc:293,testmemplot:303,testmenu:[188,328],testmixedrefer:335,testmod:308,testmymodel:127,testnod:51,testobj:127,testobject:127,testobjectdelet:335,testok:91,testregularrefer:335,testresult:251,testset:127,testsharedmemoryrefer:335,teststr:127,testsystem:170,testsystemcommand:170,testtelnet:293,testunconnectedcommand:170,testvalu:11,testwebsocket:293,text2html:[141,142,320,364],text:[0,1,2,5,7,9,10,13,14,15,17,18,21,22,24,26,30,33,34,35,37,40,43,45,46,48,50,52,53,55,56,57,58,59,60,63,68,72,73,76,77,78,79,80,81,83,85,86,87,88,90,91,95,96,97,98,100,108,109,110,111,112,118,121,123,124,126,127,131,133,137,138,139,144,146,151,154,156,158,165,166,167,171,175,176,177,179,180,187,188,189,190,193,195,199,202,205,206,210,215,221,232,233,234,239,242,247,249,250,252,256,264,265,272,278,279,282,285,286,287,290,291,295,296,306,307,308,311,312,316,317,319,321,322,324,326,327,328,329,330,336,338,341,343,344,345,357,364],text_:38,text_color:190,text_exit:[22,180],text_single_exit:22,textarea:[340,357],textbook:40,textbox:357,textfield:[86,133],textstr:74,texttag:[81,126,139,364],texttohtmlpars:343,textual:39,textwrap:330,textwrapp:330,than:[0,2,4,6,8,11,13,14,16,19,23,25,26,29,31,33,35,37,38,39,42,46,47,49,51,52,54,55,57,58,60,61,62,64,68,69,71,73,76,80,82,86,89,90,91,93,95,97,103,104,105,106,109,110,112,113,114,115,116,119,122,123,125,126,127,128,129,131,134,135,137,138,139,144,148,151,152,153,156,157,158,159,160,164,167,169,179,180,181,184,190,195,204,205,206,213,215,217,218,219,220,221,232,234,241,245,247,249,250,251,267,293,308,313,315,316,317,318,321,322,328,329,330,334,336,337,339,340,341,343,344,362],thank:[4,102,134,138,199,312],thankfulli:133,the_next_node_to_go_to:328,thead:134,thei:[0,1,2,4,5,6,8,9,10,11,12,13,14,15,16,17,19,20,21,22,23,25,27,29,30,31,33,34,37,38,39,40,41,42,44,46,48,51,55,56,57,58,61,63,64,66,68,69,73,75,77,78,80,81,83,85,86,88,89,90,91,92,93,95,96,97,102,103,105,106,107,108,109,110,111,112,113,114,116,118,119,121,122,123,124,125,126,127,131,132,134,136,137,138,139,140,144,145,152,153,156,158,159,164,165,167,168,169,174,179,180,182,185,187,189,194,205,206,217,218,219,220,221,232,233,234,235,241,242,246,247,250,251,252,253,255,256,258,259,261,267,287,288,290,291,292,296,299,305,306,307,308,310,315,316,321,322,323,325,328,330,336,344,345,357,362],theirs:[116,181,189],them:[0,2,4,5,6,9,10,11,12,13,14,15,16,21,22,23,25,26,27,28,29,30,31,33,34,35,37,38,39,40,41,46,48,50,51,54,55,57,58,59,60,61,62,64,66,68,69,71,73,74,75,76,77,80,82,83,85,86,87,88,89,90,91,95,96,97,98,102,103,104,105,106,109,110,111,112,113,114,115,116,118,119,121,122,123,124,125,126,127,128,131,133,134,135,136,137,138,139,140,144,150,151,152,154,156,158,159,164,166,167,168,170,175,181,182,183,187,188,189,190,192,194,203,204,206,215,217,218,219,220,221,224,231,233,234,238,242,247,252,258,261,267,285,287,290,298,302,305,306,308,315,316,318,319,321,322,324,328,336,340,343,362],themat:61,theme:[61,134],themself:219,themselv:[0,11,19,21,28,31,33,38,49,51,55,58,69,72,73,80,81,85,89,97,102,107,113,119,121,123,125,127,132,138,140,159,206,247,256,259,267,317,319,340],theoret:[31,108],theori:[31,42,57,79,123,139,144,152,364],thereaft:87,therefor:[0,49,62,68,91,102,122,127,158,180,192],therein:[15,33,156,167,169,171,187,203,233],thereof:[206,247],thess:245,thi:[0,1,2,3,4,5,6,8,9,10,11,12,13,14,15,16,18,19,20,21,22,23,24,25,26,27,28,29,30,31,33,34,35,36,37,38,39,40,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,70,71,72,73,74,75,76,77,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,192,193,194,195,198,199,202,203,204,205,206,209,210,212,213,214,215,217,218,219,220,221,223,224,226,227,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,250,251,252,253,254,255,256,257,258,259,260,261,262,264,265,266,267,269,271,272,273,274,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,294,295,296,298,299,300,301,302,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,334,335,336,337,338,339,340,341,342,343,344,345,346,349,350,354,355,357,361,362,363,364],thie:51,thief:61,thieveri:166,thin:[10,22,29,111,182,337],thing:[0,1,3,4,5,6,8,9,10,11,12,13,15,19,20,21,22,25,26,27,28,29,30,31,33,34,37,39,40,41,46,47,48,49,50,51,55,58,59,60,61,63,64,67,69,70,71,73,74,75,76,79,80,82,83,85,86,89,90,91,93,95,96,97,100,102,103,104,105,107,108,109,110,111,114,115,116,118,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,144,152,153,159,175,179,180,187,195,205,206,215,221,227,230,233,234,241,242,246,247,250,271,276,280,312,315,316,318,321,322,336,340,362,364],think:[1,20,29,31,34,37,46,48,51,55,59,61,62,67,70,73,79,81,91,95,96,97,109,111,112,114,115,135,138,139,308,362],third:[0,8,9,23,27,37,38,39,42,51,64,69,72,75,90,96,101,114,121,127,128,134,159,321,364],thirdnod:51,this_sign:309,thoma:[12,87,157],thorn:[11,89],thorough:26,those:[2,3,4,6,9,11,13,14,15,19,20,21,23,28,30,31,33,35,36,44,47,48,51,55,56,57,58,60,61,62,64,68,71,73,77,78,79,80,81,85,86,88,89,90,95,96,103,105,109,110,111,112,114,118,119,121,123,124,125,127,128,130,131,135,136,138,153,154,156,159,165,166,170,176,180,206,210,215,217,232,233,242,250,251,252,255,260,290,295,317,318,328,329,330,338,339,342,344,357,362],though:[2,10,11,12,13,14,15,22,23,26,27,30,31,37,39,41,51,57,59,60,62,63,64,69,72,75,79,81,89,90,91,96,97,100,102,103,104,110,116,119,121,122,123,126,127,128,129,130,131,138,144,154,180,181,190,217,218,220,221,226,233,234,247,252,321,328,344],thought:[23,39,61,79,80,84,138],thousand:[39,90,111,133],thread:[23,27,55,79,110,286,312,337,344],threadpool:312,threadsaf:315,threat:103,three:[0,4,12,13,16,22,25,31,33,38,46,51,69,80,83,85,87,89,90,114,133,134,135,151,215,220,242,255,258,321,328],threshold:[228,310,322],thrill:85,throttl:[141,142,144,262,272,285,364],through:[0,1,2,5,9,13,14,17,23,25,27,30,31,33,34,38,39,40,41,44,46,48,51,52,55,56,57,58,59,60,61,62,64,68,69,70,71,76,77,80,83,85,87,88,89,90,91,93,96,97,98,99,103,104,105,106,107,108,109,110,114,116,117,119,121,122,124,136,138,139,140,141,144,153,159,166,174,179,187,192,210,212,217,218,219,220,221,235,240,242,246,247,251,255,257,258,261,267,269,274,283,287,290,296,299,304,306,307,315,317,318,322,324,327,328,329,336,343,344,357,362,364],throughout:[11,20,49,51,55,104,219],throughput:[175,324],thrown:116,thrust:232,thu:[14,19,31,33,39,44,51,54,57,58,73,80,83,86,96,108,111,114,121,122,123,125,134,135,136,156,160,181,205,242,247,261,299,313,316,317,324],thud:189,thumb:[114,131],thumbnail:4,thunder:23,thunderstorm:122,thusli:75,tick:[23,33,38,51,64,115,131,132,139,219,231,233,261,299],ticker1:[115,261],ticker2:[115,261],ticker:[43,53,55,74,102,132,139,146,169,231,233,257,261,272,364],ticker_class:261,ticker_handl:[53,115,132,141,261],ticker_pool_class:261,ticker_storag:261,tickerhandl:[27,45,102,116,132,139,141,142,169,213,219,233,253,364],tickerpool:261,tickerpool_layout:261,tidbit:55,tidi:100,tie:[83,116,138,364],tied:[64,119,153,166,182,224,227,239,255],tier:90,ties:[49,135,161],tight:182,tightli:103,tim:[182,188,190,215,217,218,219,220,221],time:[0,1,2,4,5,6,8,9,10,11,12,13,14,17,20,21,22,23,25,26,28,29,30,31,34,36,37,39,40,41,42,43,45,49,51,52,53,54,55,56,58,59,60,61,63,64,65,66,67,69,70,72,73,75,80,83,86,88,89,90,91,93,95,96,100,104,105,106,109,110,113,114,115,116,117,119,121,122,123,124,125,127,128,129,131,132,133,135,138,139,144,146,148,150,151,153,154,157,164,169,175,177,179,184,185,187,194,195,198,203,204,212,213,215,217,218,219,220,221,223,227,231,232,233,239,246,247,250,252,253,255,256,259,260,261,267,269,271,273,274,279,285,290,292,299,300,304,305,306,308,310,315,316,318,319,321,322,323,324,329,331,334,335,337,340,344,363,364],time_ev:198,time_factor:[27,62,184,331],time_format:[59,344,364],time_game_epoch:[27,62,331],time_to_tupl:184,time_unit:[62,184],time_until_next_repeat:[102,259],timedelai:[29,260,342,344],timedelta:[338,345],timeeventscript:195,timefactor:62,timeformat:[337,344],timeit:93,timeout:[63,67,116,120,290,310,334],timer:[20,27,33,47,56,64,83,102,115,116,187,219,223,226,232,253,255,259,260,261,298,306,341,364],timerobject:102,timescript:331,timeslot:187,timestamp:[25,27,310,331],timestep:299,timestr:337,timetrac:[141,142,262,297,364],timetupl:62,timezon:[23,337,338,345],tini:[23,39,81],tinker:97,tintin:[24,280,281,291,294],tinyfugu:24,tinymud:[57,108],tinymush:[57,108,129,171],tinymux:[57,108],tip:[12,37,70,79,103,112,364],tire:[20,153],titeuf87:[235,364],titl:[17,22,34,38,48,69,98,137,164,166,180,238,321,324,362],titlebar:137,titleblock:69,tlen:71,tls:8,tlsv10:67,tlsv1:8,tmp:[36,63],to_be_impl:362,to_byt:[344,364],to_cur:219,to_displai:180,to_dupl:152,to_execut:344,to_exit:0,to_fil:209,to_init:221,to_non:247,to_obj:[144,154,247],to_object:176,to_pickl:325,to_str:[344,364],to_syslog:209,tobox:276,toc:363,todai:[138,190],todo:58,toe:108,togeth:[0,3,8,9,14,22,29,31,33,38,48,49,57,58,61,64,68,71,73,83,89,90,92,116,119,122,123,124,125,126,127,131,138,150,159,161,175,187,202,203,205,206,232,233,245,246,252,276,295,308,315,321,322,336,341,364],toggl:[81,290],toggle_nop_keepal:290,togglecolor:81,toint:[109,250],token:[71,247,287,290,322,336],told:[44,59,90,91,95,113,114,123,128,340],tolkien:62,tom:[58,87,123,129,159,165,189,206,327],tommi:[19,80,87],ton:[57,82],tone:114,tonon:159,too:[0,4,6,9,11,12,13,14,17,20,21,22,25,27,29,33,38,39,41,42,46,47,48,49,51,57,58,59,60,61,63,69,73,80,83,84,85,91,93,96,106,114,116,121,122,123,125,128,131,133,138,157,159,178,215,220,224,241,245,259,272,276,310,312,322,327,328,329,330,341,344],took:[127,344],tool:[4,6,7,8,23,29,53,57,62,63,64,86,90,96,100,108,109,111,112,114,119,136,139,364],toolbox:79,tooltip:137,top:[5,9,13,22,26,29,31,33,38,39,47,48,50,52,57,58,59,60,63,68,69,75,79,85,93,95,96,101,102,104,110,111,112,117,123,125,130,131,133,134,138,139,148,153,177,180,182,184,202,206,215,234,235,239,246,256,267,309,316,318,319,322,329,330,337],topcistr:238,topic:[4,10,20,31,33,40,42,55,68,69,86,93,105,119,126,166,217,218,219,220,221,238,341,357,362],topicstr:238,tos:241,tostr:276,total:[27,62,80,82,91,93,102,104,105,114,118,139,147,169,185,304,329,330,331],total_num:334,touch:[8,38,54,60,96,97,103,104,114],tour:91,toward:[22,33,40,42,91,102,111,190,221,231],tower:[111,187,233],tportlock:241,trace:[83,96,195,304,328],traceback:[6,13,27,57,60,95,97,102,110,114,123,127,133,135,195,202,250,276,318,322,337,344,364],tracemessag:304,track:[11,27,30,49,57,61,64,73,77,82,86,95,98,99,100,102,105,116,121,128,132,133,138,144,153,221,257,278,279,284,287,290,305,310,318,325,326,338,364],tracker:[43,61,70,131],trade:[46,179],tradehandl:179,trader:46,tradetimeout:179,tradit:[10,15,36,73,74,83,90,103,114,116,138,235,290,306,329],tradition:[57,83],traffic:[8,103,280],train:[79,364],traindriv:121,traindrivingscript:121,training_dummi:73,trainobject:121,trainscript:121,trainstop:121,trainstoppedscript:121,trait:[27,73,252],transact:179,transfer:[85,133,153,278,288,292,330],transform:[36,175],transit:[89,124],translat:[14,40,45,79,87,88,113,114,126,205,206,252,269,321,364],transmiss:209,transmit:113,transpar:[67,105,126,137,138,245,246,261],transport:[276,287,296],transportfactori:287,transpos:126,trap:[14,82,122],traumat:51,travel:[49,82,83,88,96,213,235],travers:[11,44,49,80,85,89,121,212,213,231,232,235,241,247],traverse_:33,traversing_object:[212,213,235,247],travi:[45,139,364],treasur:[9,235],treat:[10,14,33,64,95,96,105,111,112,119,125,138,144,150,153,175,189,245,247,252,308,328,330,341,364],tree:[3,11,33,38,47,51,61,63,64,77,80,96,131,140,180,206,215,234,247,252,267,296,312,328,344],tree_select:[141,142,178,364],treestr:215,treshold:334,tri:[11,12,14,24,29,33,51,58,61,80,83,87,90,91,105,107,113,116,119,133,138,151,169,179,181,188,224,232,233,271,310,344,345],trial:[106,293],tribal:111,trick:[8,22,51,79,138,318,357,364],tricki:[109,126,127,138],trickier:[9,69],trigger:[21,24,31,33,36,42,46,49,51,56,57,69,74,83,84,89,100,105,107,114,115,116,117,118,121,134,135,138,144,146,150,151,154,156,170,175,180,198,231,233,246,247,252,259,261,269,272,276,298,305,309,324,336],trim:321,trip:96,tripl:[27,38,96,114,336,344],trivial:[27,33,40,42,91,93,138],troll:12,troubl:[5,8,9,23,41,46,58,63,70,75,91,105,131,139,363],troubleshoot:[9,364],troublesom:[12,13,14],trove:9,truestr:188,truli:[0,12,39,41,105,187],trust:[19,51,57,169,322],truth:42,truthfulli:33,truthi:260,try_num_prefix:151,ttarget:116,tto:290,tty:[9,100],ttype:[55,141,142,262,275,287,290,364],ttype_step:294,tuck:[111,224],tun:[43,159],tune:[67,126],tunnel:[0,20,22,43,44,49,58,121,159,292],tup:[39,206],tupl:[11,39,41,42,51,59,60,80,86,87,88,90,109,116,119,134,141,144,151,157,159,167,168,176,179,180,184,185,189,192,206,219,220,230,235,241,242,247,250,251,252,255,261,264,267,276,277,287,288,292,299,306,316,319,321,323,324,326,328,329,331,336,337,339,344],tupled:337,turbo:75,turkish:144,turn:[0,10,12,27,31,33,38,41,50,51,57,58,64,66,77,79,80,81,83,88,90,96,102,105,107,110,111,114,117,118,121,122,126,127,131,133,135,138,139,144,154,164,169,170,175,198,206,215,217,218,219,220,221,231,233,247,252,267,272,280,287,290,298,308,314,315,318,322,324,328,329,330,336,344,364],turn_act:116,turn_end_check:[217,218,219,220,221],turnbattl:[141,142,178,364],turnchar:219,tut:[122,233],tutor:230,tutori:[3,4,10,16,17,20,22,25,26,28,29,31,32,33,35,37,38,39,41,42,45,48,49,51,55,57,58,60,61,63,64,70,71,77,79,81,82,90,91,95,102,111,112,114,115,126,133,135,139,180,213,218,232,233,363,364],tutorial_bridge_posist:233,tutorial_cmdset:233,tutorial_exampl:[13,14,20,102,141,142,178,364],tutorial_info:233,tutorial_world:[20,22,63,122,141,142,178,364],tutorialclimb:232,tutorialevmenu:230,tutorialobject:[231,232],tutorialread:232,tutorialroom:[231,233],tutorialroomcmdset:233,tutorialroomlook:233,tutorialworld:[232,233],tweak:[8,9,25,57,58,67,97,102,109,117,119,125,138,312,321,364],tweet:[124,139,364],tweet_output:120,tweet_stat:120,tweetstat:120,twenti:58,twice:[25,51,62,116,195,221,328],twist:[10,27,29,33,40,63,72,75,79,97,103,247,260,264,267,269,270,276,277,278,279,284,287,290,293,295,296,298,305,308,312,337,364],twistd:[63,106,110,284,305],twistedcli:40,twistedweb:103,twitch:[41,116],twitter:[7,55,120,139,364],twitter_api:71,two:[0,4,11,13,14,15,16,19,22,23,25,26,27,28,29,31,33,34,38,39,40,41,44,46,47,49,50,51,57,58,64,65,67,68,69,73,74,76,80,83,84,85,86,88,89,90,91,92,95,97,100,102,103,104,105,108,109,110,111,112,113,116,119,121,122,123,125,126,127,129,131,133,134,135,137,138,139,140,152,159,177,179,180,185,199,204,212,213,215,219,221,224,233,234,247,249,267,296,307,308,317,319,322,328,330,336,337,344,345,364],twowai:159,txt:[9,38,40,50,75,78,90,96,146,205,283,291,326,328],tyepclass:245,tying:90,typclass:206,type:[0,8,12,14,16,17,19,20,21,22,24,25,26,27,28,29,31,33,34,35,37,41,42,43,44,46,47,49,50,51,55,56,57,58,59,61,62,64,73,75,77,79,80,81,82,83,86,87,88,90,91,95,96,97,102,103,105,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,125,126,128,133,137,138,139,144,146,154,159,166,169,170,171,175,176,177,180,182,186,188,192,195,198,199,206,213,217,218,219,220,221,227,232,233,234,239,241,242,246,247,251,252,260,261,265,267,269,270,276,278,279,285,287,288,290,291,292,294,295,296,298,306,308,312,315,316,317,318,319,321,322,324,325,328,329,330,336,339,340,341,344,345,351,357,364],type_count:182,typecalss:195,typeclass:[0,2,5,9,11,12,13,20,21,22,25,26,27,33,34,38,39,43,44,47,49,56,58,60,61,62,66,69,73,76,77,80,82,83,84,85,89,91,96,102,105,107,109,111,112,116,117,118,120,121,122,123,127,132,133,134,139,141,142,144,145,146,147,148,153,159,164,173,175,176,177,178,182,187,191,194,195,198,203,206,212,213,214,217,218,219,220,221,226,233,235,237,238,241,242,244,245,246,247,251,252,254,255,256,257,259,261,305,323,324,341,342,344,360,362,364],typeclass_path:[102,119,125,148,159,256,317,318],typeclass_search:[147,245,255,317],typeclassbas:96,typeclassmanag:[147,176,245,255],typeclassmixin:362,typedobject:[41,125,148,154,174,177,206,235,246,247,256,317,318,319,339,344],typedobjectmanag:[147,176,238,245,255,317],typeerror:[42,185,296],typenam:[22,144,146,148,175,177,179,182,184,187,189,195,203,204,205,206,212,213,214,217,218,219,220,221,223,226,227,231,232,233,235,239,246,247,251,256,259,274,300,316,318,331,334,335],typeobject:319,types_count:182,typic:[27,55,91,127,220,221,362],typo:[37,38,70,103,363],ubbfwiuvdezxc0m_2pm6ywo:37,ubuntu:[8,63,67,90,97,103,131],uemail:147,ufw:103,ugli:[56,109,137,338],uid:[100,147,148,279,286,307,308],uit:[22,180],ulrik:58,ultima:79,umlaut:15,unabl:[71,190],unaccept:33,unaffect:[51,116,219,260],unam:147,unarm:218,unarmor:218,unassign:138,unauthenticated_respons:360,unavoid:115,unban:[12,157],unbias:185,unbroken:327,uncal:260,uncas:321,uncategor:341,unchang:[87,97,127,205,252,344],unclear:[30,363],uncolor:[81,114],uncom:[67,90],uncommit:131,uncompress:280,unconnect:[171,186],uncov:182,undefin:[36,86,112],under:[6,9,20,24,33,36,38,41,42,46,48,51,57,60,61,63,64,73,75,77,78,79,86,90,93,100,106,108,110,119,122,123,125,128,133,134,135,136,137,154,156,159,188,215,234,242,259,267,294,321,328,329,330,344,346,362,364],undergar:182,undergon:195,underli:[57,61,64,80,119,124,131],underlin:[330,343],underneath:[9,318],underscor:[0,38,51,74,88,95,97,114,119,152,344],underscror:152,understand:[4,10,15,24,25,26,29,30,31,33,37,38,39,41,42,44,48,49,55,60,61,63,79,81,83,91,95,96,103,104,105,109,111,113,114,123,124,127,131,133,134,136,139,151,152,204,205,206,312,321,364],understood:[83,91,111,127,295,296],undestand:25,undo:[50,103,326],undon:156,undoubtedli:57,unexpect:[91,126,127,328],unexpectedli:334,unfamiliar:[63,74,80,88,90,118,124],unformat:[51,328,331],unfortun:[4,41,61],ungm:364,unhandl:60,unhappi:9,unhilit:343,unicod:[15,83,113,144,321,344],unicodeencodeerror:321,unicorn:119,unifi:[133,307],uniform:105,uninform:8,uninstal:[63,364],uninstati:344,unintent:234,union:[31,51,152,224,328],uniqu:[2,12,13,20,31,33,35,36,38,40,46,51,55,57,60,61,64,71,80,83,84,90,95,96,102,105,109,112,119,123,125,127,137,138,144,150,152,154,159,164,169,171,175,176,181,184,186,194,204,205,206,212,215,218,219,231,233,238,247,251,252,255,261,264,276,277,285,298,299,307,308,317,318,319,324,326,338,341],unit:[27,31,34,36,37,45,47,55,62,64,79,82,107,124,130,139,176,184,198,219,269,324,331,344,350,364],unittest:[25,127,170,308,324,342],univers:[14,15,62,164],unix:[24,38,52,63,87,165,234,329,337,344],unixcommand:[141,142,178,364],unixcommandpars:234,unixtim:337,unjoin:179,unknown:[41,56,69,137,251,336,344],unleash:28,unless:[4,5,11,12,21,22,23,27,29,33,38,51,72,78,80,84,88,89,90,96,102,110,115,123,138,140,144,152,153,157,159,164,167,174,175,194,204,205,206,221,227,232,237,241,242,247,252,265,280,296,308,316,318,341,345],unlik:[37,51,64,73,90,107,127,144,180,219,318],unlimit:[235,259],unlink:[43,159],unload:342,unload_modul:342,unlock:[58,77,80,316],unlocks_red_chest:80,unlog:[157,162,163,171,175,186,308],unloggedin:[105,141,142,149,155,308,364],unloggedincmdset:[35,43,105,163,186],unlucki:12,unmask:206,unmodifi:[151,168,187,328],unmonitor:[272,364],unmut:[174,175],unnam:[112,152],unneccesari:113,unnecessari:[36,61],unneed:235,unpaced_data:276,unpack:[91,241],unpars:[74,87,151,295,296,336],unpaus:[100,102,259,260],unpickl:[83,276,316,325,340],unplay:[25,105],unpredict:344,unprivileg:252,unprogram:73,unpuppet:[43,96,107,123,156],unpuppet_al:144,unpuppet_object:[2,144],unquel:[20,43,80,122,156],unreal:79,unregist:135,unrel:[51,131,145],unrepeat:[272,364],unreport:272,unsaf:[110,152,233],unsatisfactori:111,unsav:326,unsel:85,unset:[33,49,58,89,116,157,206,231,242,247,251,252,259,261,324,328,329,330,336,337],unsign:345,unsigned_integ:[338,345],unsignedinteg:338,unstabl:100,unstrip:151,unsubscrib:[58,115,164,261,278],unsuit:[19,251,319],unsur:[15,37,63,71,76,90,116,138,213],untag:137,untest:[24,61,63,127],until:[5,8,10,11,12,13,20,26,29,30,31,33,36,48,51,61,63,64,86,87,93,95,97,102,114,115,119,123,126,131,136,137,138,139,179,182,184,198,217,218,219,220,221,231,232,233,247,259,260,267,296,298,321,322,329,331,344],untouch:321,untrust:13,unus:[33,81,144,150,154,175,187,215,221,233,247,259,290,306,311,317,326],unusu:[103,119],unwant:139,unwield:218,unwieldli:153,upcom:54,updat:[2,4,5,8,9,11,13,14,20,23,24,28,29,30,33,36,38,39,43,45,49,51,55,57,58,61,62,63,64,68,71,73,75,76,79,81,83,84,86,88,89,90,91,95,97,98,100,102,115,116,123,127,133,134,135,136,137,138,139,145,146,153,154,159,164,167,169,170,174,175,183,187,195,206,220,233,239,242,246,247,249,250,252,257,283,285,286,291,305,306,308,310,315,318,325,326,327,328,329,330,334,344,357,360,362,364],update_buff:326,update_cached_inst:334,update_charsheet:58,update_current_descript:187,update_default:305,update_flag:306,update_po:49,update_session_count:306,update_undo:326,update_weath:233,updated_bi:192,updated_on:192,updatemethod:[137,138],updateview:362,upfir:106,upgrad:[63,64,75,364],upload:[4,63,64,90,100,364],upon:[14,29,61,80,86,90,96,100,103,113,117,123,188,210,217,218,219,220,221,258,269,278,310,329,362],upp:233,upper:[29,39,86,101,114,127,138,156,321],uppercas:[114,321],upping:114,ups:7,upsel:90,upsid:[41,235],upstart:[40,255,258],upstream:[26,64,104,128,364],upt:153,uptim:[12,27,43,62,169,281,331],urfgar:109,uri:[175,239,318],url:[8,64,70,90,98,131,134,135,136,138,141,142,146,164,175,239,286,296,312,318,343,346,353,356,362,364],url_nam:360,url_or_ref:38,url_to_online_repo:131,urlencod:69,urlpattern:[3,4,69,133,134,135],usabl:[4,66,114,123,159,180,190,219,241,310,328],usag:[0,5,12,21,22,23,28,29,30,33,38,41,42,51,58,60,64,68,71,73,81,82,85,90,91,93,109,115,116,119,121,123,124,129,154,156,157,158,159,164,165,166,169,170,171,174,179,180,181,182,184,185,186,187,188,189,199,202,203,205,206,210,212,213,214,217,218,219,220,221,224,230,231,232,233,234,235,241,250,260,267,326,328,330,334,364],use:[0,2,3,4,5,6,8,9,10,11,12,13,14,15,16,17,19,20,21,22,23,24,25,26,27,28,29,31,33,34,35,36,37,38,39,40,41,42,46,47,48,49,50,51,52,54,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,76,79,80,81,82,83,84,85,86,87,88,89,90,91,93,95,96,98,100,102,103,104,105,106,107,108,109,111,112,113,114,116,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,144,145,146,148,150,151,152,153,154,156,159,160,164,165,167,168,169,170,171,174,175,177,179,180,181,182,185,187,189,190,194,198,199,202,203,204,205,206,212,214,215,217,218,219,220,221,223,224,226,230,231,232,233,234,235,241,242,246,247,251,252,259,260,261,265,272,276,289,291,292,295,298,299,306,307,308,315,316,317,318,319,321,322,323,324,326,327,328,329,330,334,336,337,338,340,344,345,362,364],use_dbref:[206,245,247,341],use_destin:247,use_evt:329,use_i18n:76,use_item:219,use_nick:[144,206,247],use_required_attribut:[145,237,244,357],use_success_location_messag:203,use_success_messag:203,use_xterm256:321,useabl:235,used:[0,2,3,7,9,10,11,13,15,16,17,19,20,22,23,24,27,29,30,31,34,35,38,40,41,46,47,48,50,51,52,54,55,56,57,58,59,60,62,63,64,67,68,69,72,73,74,79,80,82,83,84,85,86,87,88,89,90,91,93,95,96,100,102,103,104,105,107,108,109,110,111,112,113,114,115,116,118,119,120,121,122,123,124,125,126,127,128,129,131,133,134,135,136,137,139,141,144,145,146,150,152,153,154,156,159,164,166,167,168,169,170,171,175,179,180,182,184,186,187,188,189,190,192,194,195,198,199,204,205,206,213,215,217,218,219,220,221,231,232,233,234,235,238,240,241,242,244,245,247,250,251,252,255,258,259,260,261,262,264,265,269,272,273,276,277,278,279,280,281,282,283,284,285,287,289,290,291,294,295,296,299,306,308,309,315,316,317,318,319,320,321,322,324,325,326,328,329,330,336,337,338,339,340,341,344,345,350,357,362,363,364],used_kei:80,useful:[0,1,4,5,10,11,12,13,14,15,16,17,18,19,20,22,23,25,26,27,28,29,30,31,34,36,37,38,39,41,42,46,47,48,50,51,53,57,58,59,60,63,64,66,69,70,80,81,87,89,90,91,93,95,96,102,104,107,109,110,111,112,114,115,116,119,120,123,124,125,127,131,132,133,138,139,150,152,153,154,156,158,159,166,167,168,170,178,179,180,194,195,199,205,206,210,233,234,235,241,247,251,252,259,267,287,316,318,322,328,331,340,344,364],useless:231,uselock:241,user:[2,4,7,8,10,11,12,13,14,20,22,23,25,28,29,30,31,35,36,37,40,41,42,49,50,51,52,55,60,63,64,65,66,67,68,70,71,72,74,75,76,77,79,80,81,85,87,88,90,91,93,95,97,98,100,101,104,105,107,109,113,114,119,121,122,123,125,126,127,133,134,135,136,137,138,139,144,145,146,148,151,154,157,159,164,169,174,175,176,177,180,182,187,189,193,195,206,209,210,215,219,221,227,233,235,239,241,242,247,252,259,262,265,271,279,286,287,290,295,296,306,308,311,316,318,321,326,328,329,330,336,338,344,345,349,357,362,364],user_change_password:145,user_input:51,user_permiss:[145,148],useradmin:145,userauth:287,userchangeform:145,usercreationform:[145,357],usermanag:147,usernam:[2,4,12,35,51,74,100,107,119,131,134,144,145,148,186,287,311,349,357],username__contain:119,usernamefield:357,userpassword:[12,157],uses:[0,5,9,13,15,16,17,22,23,29,30,31,33,34,39,40,44,57,64,68,69,80,81,86,88,90,98,107,109,112,113,114,115,119,124,125,127,130,131,136,137,152,179,185,187,199,206,219,226,227,233,234,235,242,245,256,261,276,296,319,336,337,338,344],uses_databas:344,using:[2,4,5,6,8,9,10,11,12,13,14,15,19,20,21,22,23,24,25,26,27,28,29,30,31,33,34,36,37,38,39,41,43,45,46,47,49,50,51,53,55,56,57,58,59,60,61,62,63,64,67,68,70,71,72,73,74,77,78,79,80,81,83,85,86,87,88,89,90,91,93,95,96,97,100,101,102,103,105,107,108,109,110,111,112,114,115,116,117,118,120,121,122,123,124,125,126,128,129,131,132,133,134,137,138,139,140,144,148,150,153,154,156,158,159,164,167,168,169,174,179,180,181,184,185,187,188,190,194,203,205,206,212,213,214,215,217,218,219,220,221,224,230,231,233,234,235,242,245,247,250,251,252,256,259,260,261,278,279,280,285,286,290,296,299,309,310,312,316,318,319,321,322,326,328,329,331,336,337,338,339,340,341,342,344,346,357,362,363,364],usr:[63,64,75,100],usual:[0,2,4,5,6,8,9,11,19,20,21,22,23,25,26,27,29,30,31,33,34,37,38,40,41,46,47,50,51,52,57,59,60,62,63,64,67,72,74,80,81,87,89,90,91,93,95,96,97,100,102,105,106,109,110,112,114,115,119,124,125,126,127,131,133,136,144,146,151,152,153,154,156,159,165,169,170,174,175,177,184,194,195,198,204,205,206,233,234,242,245,246,247,252,259,267,269,274,299,306,315,316,318,323,324,328,329,337,339,341,344],utc:[23,345],utf8:[23,36],utf:[15,24,58,74,111,113,171,272,278,295,330,344],util:[8,10,11,13,14,16,34,41,45,47,48,49,50,51,52,57,58,59,62,63,81,82,85,86,89,96,97,102,103,111,114,117,124,127,133,134,137,139,141,142,145,158,170,175,177,178,184,187,188,191,195,196,211,213,220,228,230,237,239,244,245,247,249,251,259,260,274,293,298,315,316,317,318,346,357,360,364],utilis:328,uyi:205,v21:63,vagu:21,val1:250,val2:250,val:[11,88,144,156,250,291,344],valid:[1,11,13,26,30,31,33,42,44,51,58,60,67,69,88,89,90,91,95,96,97,102,103,109,110,114,119,123,133,134,141,142,144,147,151,153,159,167,168,169,176,179,180,188,192,195,196,204,206,215,220,226,227,232,233,234,235,242,247,249,250,251,252,255,257,258,259,260,261,262,265,267,291,295,306,317,319,322,324,328,329,338,339,340,341,343,344,345,357,362,364],valid_handl:338,validate_email_address:344,validate_onli:242,validate_password:[51,144],validate_prototyp:251,validate_sess:308,validate_usernam:144,validationerror:[144,251,311,338,340],validator_config:144,validator_kei:338,validatorfunc:[141,142,320,364],valign:330,valu:[0,2,4,6,10,11,12,17,20,22,25,27,28,31,33,38,39,41,42,49,50,58,59,60,61,62,64,67,69,73,74,77,80,81,82,84,85,86,87,88,90,97,102,111,114,115,116,123,125,126,127,128,133,134,137,138,139,144,148,150,152,154,156,157,159,170,175,177,180,182,185,188,189,190,192,195,196,203,204,205,206,211,217,218,219,220,221,227,228,233,235,239,241,242,245,246,247,250,251,252,255,256,258,259,260,261,265,272,273,274,276,285,290,291,306,307,308,313,316,317,318,319,321,323,324,325,326,327,328,334,335,336,338,339,340,341,344,345,350,357,362,364],valuabl:122,value1:[38,109],value2:[38,109],value3:38,value_from_datadict:340,value_to_obj:251,value_to_obj_or_ani:251,value_to_str:340,valueerror:[41,91,109,123,180,202,204,316,321,324,336,345],valuei:111,values_list:119,valuex:111,vanilla:[9,26,49,56,58,86,101,125],vaniti:51,vari:[30,40,60,64,82,108,114,125,131,193,205,221,306,316,318],variabl:[0,3,5,11,13,28,31,33,38,41,46,49,51,55,56,58,64,66,69,80,83,88,91,95,96,97,100,103,104,106,109,113,121,124,133,134,135,137,138,144,148,150,154,156,159,164,167,168,169,170,171,183,187,188,192,194,195,198,203,233,241,246,247,251,252,264,267,277,280,281,283,287,289,299,306,313,321,322,328,344,350,364],variable_from_modul:344,variable_nam:[192,195],variablenam:344,varianc:205,variant:[11,55,112,153,180,186,213,278],variat:[62,73,116,152,187,205,227],varieti:[55,82,116,120,219,220],variou:[5,6,11,15,33,37,40,41,46,47,48,53,57,62,67,69,73,77,81,88,89,90,93,97,102,103,105,109,110,112,114,115,116,123,124,125,127,137,139,152,168,184,205,206,215,219,220,231,232,242,246,247,252,253,261,299,324,330,341,342],varnam:291,vast:[23,60,86,108,111,119],vastli:64,vcc:205,vccv:205,vccvccvc:205,vcpython27:9,vcv:205,vcvccv:205,vcvcvcc:205,vcvcvvccvcvv:205,vcvvccvvc:205,vector:344,vehicl:[21,124,139,364],velit:52,venu:[131,176],venv:[63,75],verb:[25,303],verbal:247,verbatim:364,verbos:[26,116,127,206],verbose_nam:[133,318],veri:[0,2,4,5,6,8,9,10,11,13,14,17,20,21,22,23,26,27,28,29,31,33,35,37,38,39,40,41,42,46,49,50,51,52,55,56,57,58,60,61,64,67,68,70,72,73,74,77,78,79,80,85,86,88,90,91,93,95,96,97,104,107,108,109,110,111,112,114,115,116,119,121,122,123,125,127,128,129,131,132,134,137,138,139,140,144,146,152,154,170,175,177,180,182,194,195,204,205,206,212,213,214,215,220,227,231,234,235,238,246,251,271,317,319,324,326,328,329,344,362],verif:90,verifi:[36,51,63,90,131,159,188,220,292],verify_online_play:188,verify_or_create_ssl_key_and_cert:292,verify_ssl_key_and_cert:288,verifyfunc:188,versa:[40,61,88,105,116,164,276],version:[2,4,7,11,13,14,20,21,23,24,29,30,31,33,35,36,37,41,43,47,51,54,57,60,61,63,64,74,75,76,79,81,86,87,90,91,95,96,100,108,111,114,123,124,125,126,128,136,137,139,159,167,169,171,181,182,186,187,206,218,219,220,221,224,232,247,252,267,272,286,306,310,315,316,321,329,344,357,363,364],version_info:267,versionad:38,versionchang:38,versu:[55,364],vertic:[138,232,330,344],very_strong:242,very_weak:80,vest:103,vet:109,veteran:79,vfill_char:330,vhost:364,via:[10,11,27,37,40,51,52,55,56,57,63,70,73,74,83,85,86,90,92,93,101,103,108,109,114,119,123,125,126,131,137,172,176,177,209,246,256,316,319,321,335],viabl:231,vice:[40,61,88,105,116,164,276],vicin:[33,165,187,233],video:[79,95,114,137],vienv:9,view:[1,4,17,27,34,38,41,42,50,51,52,55,58,60,63,64,72,80,82,86,90,96,101,102,110,111,115,116,123,124,131,136,139,141,142,144,156,157,159,164,165,166,169,174,175,182,206,217,218,219,220,221,235,237,239,247,249,302,318,329,346,350,353,356,357,364],view_attr:159,viewabl:[53,55,166],viewer:[25,38,69,206,235,241,247,318],viewport:42,vim:[14,50,79,326],vincent:[180,187,204,234],violent:51,virtual:[4,41,55,57,59,63,79,90,124,169,187,331],virtual_env:75,virtualenv:[9,23,26,36,38,63,75,76,90,93,95,96,97,100,106,110,128,364],virtualhost:8,viru:63,visibl:[13,25,31,36,38,48,54,61,63,67,69,81,90,96,105,114,123,125,131,139,165,206,241,247,279,312,328,344],visiblelock:241,vision:[11,58,61],visit:[22,49,90,111,133,134,234,328],visitor:[103,134,135],vista:63,visual:[25,57,63,93,114,137,144,166,190,363],vital:91,vlgeoff:184,vniftg:63,vnum:56,vocabulari:[46,344],voic:[33,46,124,139,364],volatil:251,volum:[21,61,100],volund:119,voluntari:37,volupt:52,vowel:[119,205],vpad_char:330,vulner:[29,103],vvc:205,vvcc:205,vvccv:205,vvccvvcc:205,w001:127,wai:[0,2,5,6,9,10,11,12,13,14,15,19,20,21,22,23,27,28,30,31,33,37,38,39,40,41,42,44,46,48,49,54,55,56,57,58,61,62,63,64,68,69,70,72,73,74,75,79,80,82,83,84,85,86,87,88,89,90,91,92,93,95,96,97,98,102,103,104,105,106,107,109,110,111,112,113,114,115,116,117,118,119,121,122,123,124,125,126,127,128,129,131,132,133,136,138,139,140,144,151,152,159,166,175,179,184,185,187,188,190,194,198,205,212,213,215,217,218,219,220,221,224,230,231,232,234,242,247,251,259,261,267,272,276,287,306,308,312,313,314,316,317,319,322,327,328,330,334,337,338,340,362,364],wail:49,waist:182,wait:[0,10,20,25,27,28,29,33,42,51,102,121,138,146,194,198,217,218,219,220,221,259,267,277,296,298,310,324,344],wait_for_disconnect:277,wait_for_server_connect:277,wait_for_statu:267,wait_for_status_repli:267,waiter:267,wake:188,walias:159,walk:[0,14,21,31,39,46,49,60,62,85,139,213,214,215,235,322],walki:64,wall:[111,157,165,187,232,233],wanna:[37,179],want:[0,2,3,4,5,6,8,9,10,11,12,13,14,15,19,20,21,22,23,24,25,26,27,28,29,30,31,33,34,35,37,38,39,40,41,42,44,46,48,49,50,51,54,57,58,60,61,62,63,64,66,67,68,69,70,71,72,73,74,75,76,77,78,80,81,82,83,84,85,86,87,88,89,90,91,93,95,96,97,98,102,103,104,105,106,107,108,109,110,111,113,114,115,118,119,121,122,123,125,126,127,128,131,132,133,134,135,136,137,138,140,144,152,153,154,156,165,170,171,174,179,180,186,187,188,190,204,206,209,215,217,218,219,220,221,227,233,235,237,241,242,247,252,259,261,283,285,291,298,308,313,315,316,318,326,328,329,334,340,344,357,362,363,364],wanted_id:80,ware:85,warehous:[209,322],wari:[114,235,247,318],warm:[102,110,271],warn:[8,23,27,31,59,60,63,64,90,91,93,104,105,111,128,134,138,140,152,174,210,266,267,292,337,364],warnmsg:337,warrior:[28,57,58,61,122,123],wasclean:[278,295],wasn:[0,42,134],wast:[6,14,115],watch:[14,84,106,139],water:[153,203],waterballon:203,wave:111,wcach:169,wcactu:220,wcommandnam:234,wcure:220,wdestin:159,weak:252,weakref:334,weaksharedmemorymodel:[274,334],weaksharedmemorymodelbas:[274,334],weakvalu:334,wealth:85,weapon:[29,51,61,64,73,77,82,85,86,109,116,122,218,231,232,252],weapon_ineffective_msg:231,weapon_prototyp:232,weaponrack:232,weaponrack_cmdset:232,wear:[82,182,206,218],wearabl:182,wearer:182,wearstyl:182,weather:[30,61,73,102,111,112,115,122,124,139,140,233,364],weather_script:102,weatherroom:[132,233],web:[4,8,9,16,17,23,25,30,38,47,53,55,61,63,64,67,69,72,75,76,79,80,83,95,101,109,110,119,139,141,142,173,269,271,281,285,291,295,296,306,310,312,319,325,364],web_client_url:54,web_get_admin_url:[175,239,318],web_get_create_url:[175,239,318],web_get_delete_url:[175,239,318],web_get_detail_url:[175,239,318],web_get_puppet_url:318,web_get_update_url:[175,239,318],webclient:[24,30,40,45,53,54,64,67,69,83,88,95,103,105,110,114,135,139,141,142,169,230,262,272,275,291,296,307,328,346,350,351,360,364],webclient_ajax:[137,141,142,262,275,364],webclient_en:103,webclient_gui:[83,364],webclient_opt:272,webclientdata:296,webclienttest:360,webpag:[8,17,77,90,354],webport:36,webserv:[3,7,8,9,23,40,47,55,67,90,100,101,104,135,139,141,142,262,346,364],webserver_en:103,webserver_interfac:[67,90],webserver_port:[36,90],webservic:103,websit:[3,9,17,38,53,55,57,64,67,69,79,90,98,101,103,124,133,136,137,138,139,141,142,145,296,312,346,351,364],websocket:[40,55,64,90,100,137,278,284,295,307,364],websocket_client_interfac:[67,90],websocket_client_port:[67,90],websocket_client_url:[8,67,90],websocket_clos:295,websocketcli:295,websocketclientfactori:278,websocketclientprotocol:278,websocketserverfactori:284,websocketserverprotocol:295,weed:[26,119,152],week:[62,184,337,345],weeklylogfil:337,weigh:[82,298],weight:[23,38,61,108,124,139,190,205,317,364],weirdli:96,welcom:[3,4,22,35,37,63,72,76,85],well:[2,4,6,9,11,12,16,17,19,21,22,23,25,26,33,37,38,39,40,41,43,44,45,46,49,50,51,52,55,57,58,61,62,64,66,68,69,71,74,75,81,85,88,89,91,96,98,103,104,105,106,108,109,113,116,118,119,120,123,124,125,127,128,131,133,134,135,136,138,148,152,153,154,159,164,169,172,179,182,187,194,202,205,206,215,219,220,221,226,231,247,256,262,267,276,278,279,285,302,310,315,316,317,321,325,328,331,340],went:[57,110,127,131,257,261],were:[1,10,11,13,24,31,33,37,42,44,51,58,59,64,69,77,82,85,86,91,100,102,104,108,109,119,123,125,126,127,137,144,151,152,153,204,215,247,251,314,318,322,341,344],weren:62,werewolf:25,werewolv:119,werkzeug:344,west:[20,25,44,49,111,159,233],west_east:111,west_exit:233,western:111,westward:233,wether:[179,324],wevennia:22,wflame:220,wflushmem:169,wfull:220,what:[0,1,2,4,8,9,10,12,13,14,19,20,21,22,23,25,26,27,29,31,33,38,39,40,42,44,45,46,48,49,51,56,57,58,60,61,62,63,64,67,68,69,70,72,73,74,77,78,79,80,81,83,85,86,88,89,90,93,95,96,97,98,102,103,104,105,108,109,110,111,113,114,115,116,117,118,119,121,122,123,124,125,126,127,128,129,131,132,133,134,136,138,139,140,144,150,152,153,154,156,159,166,170,175,195,203,204,206,209,214,219,220,224,227,231,233,239,242,247,251,252,267,269,272,279,291,296,311,313,316,318,319,321,322,328,338,339,344,345,349,357,362,364],whatev:[2,11,14,21,22,23,27,33,40,46,48,51,56,58,61,64,67,78,82,89,91,100,102,111,123,127,131,133,134,138,144,146,153,159,188,220,231,232,247,252,256,257,278,287,290,295,308,329,338,362],whatnot:138,wheel:[57,63,75,115,258],whelp:234,when:[0,2,3,4,5,6,8,9,10,11,12,13,14,15,17,19,20,21,22,23,24,26,27,29,30,31,33,34,35,36,37,38,39,40,41,42,44,46,47,49,50,51,52,56,57,58,59,60,61,62,63,64,65,66,67,68,69,73,74,75,76,77,78,79,80,82,83,84,85,86,87,88,89,90,91,93,95,96,97,98,100,102,103,104,105,106,107,108,109,110,111,112,113,114,116,117,118,119,120,121,122,123,124,125,126,127,128,129,131,132,133,136,137,138,139,141,144,146,148,150,152,153,154,156,158,159,164,165,167,168,169,171,175,176,177,179,180,181,182,184,185,186,187,188,189,190,195,196,198,199,202,203,204,205,206,212,214,215,217,218,219,220,221,223,224,226,227,228,230,231,232,233,234,235,238,239,241,242,246,247,249,251,252,256,257,259,260,261,264,267,269,273,274,276,277,278,279,280,281,282,283,285,287,288,289,290,291,292,295,296,298,299,305,306,307,308,309,310,316,318,319,321,322,324,325,326,327,328,329,330,334,335,336,337,339,344,357,362,364],when_stop:267,whenev:[6,10,11,22,25,33,46,64,66,74,76,80,84,87,90,95,98,100,102,106,107,109,111,113,117,119,128,144,153,174,175,231,232,233,245,247,257,259,269,286,306,307,308],where:[0,1,3,6,9,10,11,12,13,14,20,21,22,25,26,29,31,33,36,38,39,40,41,42,46,48,49,50,51,52,56,57,58,59,61,62,64,69,73,75,76,80,83,85,86,88,90,91,95,100,102,103,104,105,108,109,111,113,114,117,118,119,121,122,123,124,125,127,131,133,134,135,136,137,138,139,151,152,157,159,165,168,175,176,181,185,199,205,206,210,219,232,233,235,241,242,245,247,250,251,252,255,257,267,269,272,276,291,299,304,308,315,318,321,322,326,328,329,330,336,338,339,344,362,364],wherea:[11,12,13,19,21,26,31,33,34,40,42,55,56,61,80,81,85,86,93,97,103,105,109,113,114,116,125,128,147,205,224,227,245,255,261,296,316,334],whereabout:122,wherebi:220,wherev:[11,63,64,67,100,111,127,180,209,219],whether:[0,12,39,46,51,55,62,69,77,121,144,146,153,159,164,166,175,188,215,217,218,219,220,221,241,247,261,278,295,310,317,321,336,338,340,344],whewiu:9,which:[0,1,3,4,5,6,9,10,11,12,13,14,15,19,20,22,24,25,26,27,28,29,30,31,33,34,36,37,38,39,40,41,42,43,44,46,49,51,52,56,57,58,59,60,61,62,63,64,65,66,67,69,71,72,73,74,76,77,80,81,82,83,85,86,87,88,89,90,91,93,95,96,97,100,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,125,126,127,131,132,133,134,135,136,137,138,139,140,144,146,150,152,153,154,156,157,159,165,167,168,169,170,171,174,175,176,177,179,180,181,182,183,184,187,188,190,198,199,202,206,209,210,212,214,215,217,218,219,220,221,226,227,231,232,233,234,235,239,242,245,246,247,251,252,255,256,257,259,261,264,266,267,271,272,279,285,287,295,296,298,299,306,307,308,310,313,315,316,317,318,319,321,322,324,325,328,329,330,331,334,336,337,338,340,341,342,344,349,350,357,362],whichev:[27,90,103,233],whilst:[77,111,328],whim:139,whisp:205,whisper:[43,46,165,198,205,206,247],white:[48,74,114,126,344],whitelist:74,whitepag:[1,48,138,364],whitespac:[14,27,33,58,81,119,123,167,168,202,206,321,322,330,344],who:[4,10,11,12,21,34,41,43,46,49,51,55,56,58,61,73,80,87,95,103,109,114,116,119,121,123,124,125,127,132,133,138,146,154,156,159,164,174,175,179,188,195,206,217,218,219,220,221,232,239,241,242,247,252,318,326,328,364],whoever:133,whole:[4,16,49,51,55,57,60,61,67,87,96,111,112,122,123,129,138,152,159,169,221,330],wholist:175,whome:159,whomev:[73,114,121],whose:[88,114,119,125,144,154,170,195,206,215,217,218,219,220,221,255,272,323,328,344],whould:328,why:[0,11,12,20,22,25,39,41,44,46,51,55,60,63,64,82,91,95,96,103,111,123,125,126,139,157,204,217,220,221,264,265],wide:[16,25,27,39,58,61,73,86,91,138,157,219,220,235,327,330,344],widen:12,wider:[12,25,39,157,330],widest:344,widget:[145,237,244,315,340,357],width:[16,17,25,27,33,49,74,109,111,114,141,154,250,272,287,306,321,326,327,329,330,336,344],wield:[61,82,109,218],wifi:[90,103],wiki:[1,9,33,37,45,48,55,58,70,79,108,111,124,125,138,180,295,363,364],wiki_account_handl:4,wiki_account_signup_allow:4,wiki_can:4,wiki_can_admin:4,wiki_can_assign:4,wiki_can_assign_own:4,wiki_can_change_permiss:4,wiki_can_delet:4,wiki_can_moder:4,wiki_can_read:4,wiki_can_writ:4,wikiconfig:4,wikimedia:37,wikipedia:[15,37,55,113,127,131,295],wild:[108,126,131],wildcard:[12,57,87,157,159,344],wildcard_to_regexp:344,wilder:[141,142,178,364],wildernessexit:235,wildernessmap:235,wildernessmapprovid:235,wildernessroom:235,wildernessscript:235,wildli:205,will_suppress_ga:289,will_ttyp:294,willing:[58,61,79,364],win10:63,win7:63,win8:63,win:[9,24,91,116,122],wind:[122,132],window:[4,23,25,31,38,44,45,49,52,64,72,76,83,88,89,93,95,96,97,101,105,106,110,128,131,137,138,154,166,267,283,306,310,329,344,364],windowid:306,windows10:63,wingd:111,winpti:9,winter:187,wintext:73,wip:38,wipe:[9,13,23,43,111,138,152,159,169,219,318],wire:[27,40,64,83,88,90,113,138,168,264,276,277,308,321],wis:58,wisdom:[60,93],wise:[6,11,13,14,15,26,58,60,80,96,118,131,135],wise_text:60,wiseobject:60,wiser:20,wiseword:60,wish:[33,36,39,75,120,131,136,180,221,321,343,357],with_metaclass:96,with_tag:203,withdraw:[116,221],withdrawl:221,within:[1,8,9,10,11,22,24,26,31,33,37,38,39,47,49,51,56,58,64,90,95,97,100,114,115,116,117,118,119,120,124,126,131,134,136,137,138,144,148,150,159,179,187,190,192,210,238,247,252,260,310,316,317,321,336,337,344,357,362],without:[0,8,11,12,13,14,16,20,21,22,23,25,27,29,30,31,33,35,37,38,40,42,44,46,49,50,51,55,57,58,59,60,61,63,64,66,67,76,80,86,88,90,91,92,93,96,97,100,101,104,107,108,109,114,115,118,119,121,123,125,126,127,128,129,131,133,136,138,139,144,146,151,154,156,157,159,164,165,167,168,169,170,177,179,181,182,187,192,195,205,206,212,215,217,220,221,231,233,242,245,247,250,251,252,259,260,276,287,290,291,298,306,308,309,316,318,321,322,324,325,326,328,329,336,340,341],withstand:80,wiz:58,wizard:[109,233,252,265,267,364],wkei:159,wlocat:159,wlock:159,wmagic:220,wmass:220,wndb_:159,won:[0,2,4,10,11,12,13,15,21,22,23,29,31,38,41,42,46,57,61,63,69,73,78,81,83,85,86,91,95,96,100,111,114,119,123,125,127,134,137,138,153,188,204,223,224,226,227,312,321,336,340],wonder:[9,16,56,82,119,138],wont_suppress_ga:289,wont_ttyp:294,wooden:109,woosh:21,word:[14,27,33,46,49,50,62,69,70,72,76,88,89,91,93,95,96,97,111,119,122,126,131,136,151,167,168,171,186,198,205,206,245,279,326,341,344,364],word_fil:205,word_length_vari:205,wordi:205,work:[0,2,4,5,8,9,10,11,13,14,15,16,20,21,22,23,24,25,26,27,28,29,31,34,36,37,38,41,42,43,44,48,49,51,56,57,58,59,60,61,62,63,64,66,67,70,71,72,75,80,81,83,84,85,86,89,90,93,95,96,97,102,103,105,106,108,109,111,112,114,115,116,117,119,122,123,124,126,127,128,129,132,133,134,136,138,139,150,153,154,156,159,164,165,167,169,171,174,175,179,180,181,187,202,203,206,212,215,219,220,221,233,234,235,239,241,242,247,251,252,267,271,272,284,299,312,314,318,322,327,328,329,330,338,344,362,363,364],workaround:[63,100,131,364],workflow:[61,145],world:[9,10,11,13,14,15,21,27,31,33,34,39,41,47,49,51,55,57,58,60,62,63,64,68,72,73,78,79,80,82,86,90,96,104,108,109,111,113,116,117,121,123,124,127,131,139,144,158,159,166,174,179,184,202,206,217,218,219,220,221,232,233,235,239,256,306,308,321,322,331,363,364],world_map:111,worm:49,worm_has_map:49,worn:[182,218],worri:[0,11,15,36,39,41,51,55,104,113,114,123,127,138,179,227],worst:61,worth:[0,8,21,29,51,61,70,79,91,93,124,125,133,179],worthi:61,worthless:90,would:[0,1,4,6,8,9,10,11,13,14,15,16,19,20,21,22,25,27,29,31,33,36,39,41,42,44,46,48,49,51,55,56,57,58,60,61,62,63,64,68,69,73,77,80,81,82,85,86,88,89,90,91,93,95,96,100,102,105,106,109,111,112,114,115,116,117,118,119,121,123,125,126,127,128,133,134,135,136,138,140,144,151,152,153,159,168,175,179,184,195,205,215,224,227,234,235,239,241,242,251,252,279,315,318,321,322,325,328,336,339,340,342,344],wouldn:[39,126,138],wound:220,wow:[69,138],wpermiss:159,wprototype_desc:159,wprototype_kei:159,wprototype_lock:159,wprototype_par:159,wprototype_tag:159,wrap:[10,30,49,51,59,96,102,109,119,136,182,188,206,274,314,330,344],wrap_conflictual_object:340,wrapper:[10,27,29,51,74,86,93,105,119,125,144,148,175,176,177,212,239,246,247,256,260,272,274,306,315,316,318,319,321,330,334,335,337,344,362],wresid:169,write:[0,4,10,11,14,15,16,20,22,23,25,27,31,33,34,37,38,41,44,46,48,51,56,58,62,63,65,68,69,71,72,87,88,91,93,96,108,123,124,125,129,131,138,159,166,174,180,209,210,234,247,280,337,342,362,363,364],writeabl:75,written:[15,27,38,54,56,57,58,79,103,109,127,133,134,166,209,322,362],wrong:[26,41,42,60,63,81,85,95,110,127,152,159,169,206],wserver:169,wservic:164,wsgi:[8,312],wsgi_resourc:312,wsgiwebserv:312,wsl:[38,63],wss:[8,67,90],wtypeclass:159,wwhere:247,www:[8,9,22,38,55,70,90,108,128,133,141,282,283,289,291,343,357],wyou:82,x1b:[321,343],x2x:58,x4x:327,x5x:327,x6x:327,x7x:327,x8x:327,x9x:327,x_r:39,xcode:63,xenial:130,xforward:312,xgettext:76,xit:[22,180],xmlcharrefreplac:321,xp_gain:73,xpo:330,xterm256:[55,74,81,83,137,156,183,190,272,287,290,321,364],xterm256_bg:321,xterm256_bg_sub:321,xterm256_fg:321,xterm256_fg_sub:321,xterm256_gbg:321,xterm256_gbg_sub:321,xterm256_gfg:321,xterm256_gfg_sub:321,xterm:[114,126],xterms256:114,xval:33,xxx:[25,42,204],xxxx:204,xxxxx1xxxxx:327,xxxxx3xxxxx:327,xxxxxxx2xxxxxxx:327,xxxxxxxxxx3xxxxxxxxxxx:58,xxxxxxxxxx4xxxxxxxxxxx:58,xxxxxxxxxxx:327,xxxxxxxxxxxxxx1xxxxxxxxxxxxxxx:58,xxxxxxxxxxxxxxxxxxxxxx:58,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:58,xyz:87,y_r:39,yan:114,yank:50,yeah:138,year:[25,55,61,62,88,90,108,184,331,337,344,357],yearli:[62,90],yellow:[114,126,131,232],yep:138,yes:[10,33,38,39,46,51,126,138,159,198,265,326,344],yesno:[38,51,326],yet:[0,2,4,12,14,22,25,28,35,36,41,42,46,49,51,54,60,63,64,67,76,79,86,90,96,105,109,111,119,121,128,130,131,133,134,138,144,171,179,186,195,242,246,260,285,308,312,321,362],yield:[10,23,33,80,108,159,210,330,344,364],yml:[100,130],yogurt:203,you:[0,1,2,3,4,5,6,8,9,10,11,12,13,14,15,16,17,19,20,21,22,23,24,25,27,28,29,30,31,33,34,35,36,37,38,39,40,41,42,44,46,47,48,49,50,51,54,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,144,153,154,156,159,164,165,166,167,168,169,170,171,174,179,180,181,182,183,184,187,188,190,193,194,195,198,199,202,203,204,205,206,209,210,212,213,214,215,217,218,219,220,221,223,224,226,227,232,233,234,235,237,241,242,247,252,258,259,260,261,269,278,279,280,296,298,308,310,312,313,316,318,321,322,324,327,328,330,331,340,341,344,357,362,363,364],young:77,your:[0,1,3,5,6,7,8,9,10,11,12,13,14,15,16,17,21,22,23,25,27,29,30,31,34,35,36,37,38,41,42,44,45,46,47,48,49,50,51,54,55,56,57,58,59,61,62,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,82,83,85,87,88,91,93,95,96,98,101,102,104,105,106,107,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,129,130,132,134,135,136,138,139,140,144,148,151,153,154,156,157,159,164,165,169,170,171,179,180,182,183,184,185,186,187,188,190,194,204,205,206,209,210,213,215,217,218,219,220,221,223,232,233,234,235,241,242,246,298,315,318,321,326,328,330,340,341,342,344,345,357,362,364],your_email:131,yourgam:209,yourhostnam:67,yournam:8,yourpassword:23,yourrepo:106,yourself:[0,2,5,6,14,16,19,22,23,26,31,37,38,42,51,55,58,63,69,70,73,78,80,86,89,90,91,96,102,108,111,119,123,125,130,131,135,159,165,179,189,206,212,220,223,328,364],yoursit:133,yourusernam:131,yourwebsit:133,yousuck:12,yousuckmor:12,youth:188,youtub:131,ypo:330,yrs:184,ythi:114,yum:[8,67,131],yvonn:58,z_r:39,zed:[77,79],zero:[20,27,109,206,247,321],zip:103,zlib:[75,276,280],zmud:[24,282],zone:[18,46,55,56,70,79,112,119,122,124,139,319,337,364],zope:97,zopeinterfac:63,zuggsoft:282,zy1rozgc6mq:45},titles:["A voice operated elevator using events","API refactoring","Accounts","Add a simple new web page","Add a wiki on your website","Adding Command Tutorial","Adding Object Typeclass Tutorial","Administrative Docs","Apache Config","Arxcode installing help","Async Process","Attributes","Banning","Batch Code Processor","Batch Command Processor","Batch Processors","Bootstrap & Evennia","Bootstrap Components and Utilities","Builder Docs","Building Permissions","Building Quickstart","Building a mech tutorial","Building menus","Choosing An SQL Server","Client Support Grid","Coding FAQ","Coding Introduction","Coding Utils","Command Cooldown","Command Duration","Command Prompt","Command Sets","Command System","Commands","Communications","Connection Screen","Continuous Integration","Contributing","Contributing to Evennia Docs","Coordinates","Custom Protocols","Customize channels","Debugging","Default Commands","Default Exit Errors","Developer Central","Dialogues in events","Directory Overview","Docs refactoring","Dynamic In Game Map","EvEditor","EvMenu","EvMore","API Summary","Evennia Game Index","Evennia Introduction","Evennia for Diku Users","Evennia for MUSH Users","Evennia for roleplaying sessions","Execute Python Code","First Steps Coding","Game Planning","Gametime Tutorial","Getting Started","Glossary","Grapevine","Guest Logins","HAProxy Config (Optional)","Help System","Help System Tutorial","How To Get And Give Help","How to connect Evennia to Twitter","IRC","Implementing a game rule system","Inputfuncs","Installing on Android","Internationalization","Learn Python for Evennia The Hard Way","Licensing","Links","Locks","Manually Configuring Color","Mass and weight for objects","Messagepath","MonitorHandler","NPC shop Tutorial","New Models","Nicks","OOB","Objects","Online Setup","Parsing command arguments, theory and best practices","Portal And Server","Profiling","Python 3","Python basic introduction","Python basic tutorial part two","Quirks","RSS","Roadmap","Running Evennia in Docker","Screenshot","Scripts","Security","Server Conf","Sessions","Setting up PyCharm","Signals","Soft Code","Spawner and Prototypes","Start Stop Reload","Static In Game Map","Tags","Text Encodings","TextTags","TickerHandler","Turn based Combat System","Tutorial Aggressive NPCs","Tutorial NPCs listening","Tutorial Searching For Objects","Tutorial Tweeting Game Stats","Tutorial Vehicles","Tutorial World Introduction","Tutorial for basic MUSH like game","Tutorials","Typeclasses","Understanding Color Tags","Unit Testing","Updating Your Game","Using MUX as a Standard","Using Travis","Version Control","Weather Tutorial","Web Character Generation","Web Character View Tutorial","Web Features","Web Tutorial","Webclient","Webclient brainstorm","Wiki Index","Zones","evennia","evennia","evennia.accounts","evennia.accounts.accounts","evennia.accounts.admin","evennia.accounts.bots","evennia.accounts.manager","evennia.accounts.models","evennia.commands","evennia.commands.cmdhandler","evennia.commands.cmdparser","evennia.commands.cmdset","evennia.commands.cmdsethandler","evennia.commands.command","evennia.commands.default","evennia.commands.default.account","evennia.commands.default.admin","evennia.commands.default.batchprocess","evennia.commands.default.building","evennia.commands.default.cmdset_account","evennia.commands.default.cmdset_character","evennia.commands.default.cmdset_session","evennia.commands.default.cmdset_unloggedin","evennia.commands.default.comms","evennia.commands.default.general","evennia.commands.default.help","evennia.commands.default.muxcommand","evennia.commands.default.syscommands","evennia.commands.default.system","evennia.commands.default.tests","evennia.commands.default.unloggedin","evennia.comms","evennia.comms.admin","evennia.comms.channelhandler","evennia.comms.comms","evennia.comms.managers","evennia.comms.models","evennia.contrib","evennia.contrib.barter","evennia.contrib.building_menu","evennia.contrib.chargen","evennia.contrib.clothing","evennia.contrib.color_markups","evennia.contrib.custom_gametime","evennia.contrib.dice","evennia.contrib.email_login","evennia.contrib.extended_room","evennia.contrib.fieldfill","evennia.contrib.gendersub","evennia.contrib.health_bar","evennia.contrib.ingame_python","evennia.contrib.ingame_python.callbackhandler","evennia.contrib.ingame_python.commands","evennia.contrib.ingame_python.eventfuncs","evennia.contrib.ingame_python.scripts","evennia.contrib.ingame_python.tests","evennia.contrib.ingame_python.typeclasses","evennia.contrib.ingame_python.utils","evennia.contrib.mail","evennia.contrib.mapbuilder","evennia.contrib.menu_login","evennia.contrib.multidescer","evennia.contrib.puzzles","evennia.contrib.random_string_generator","evennia.contrib.rplanguage","evennia.contrib.rpsystem","evennia.contrib.security","evennia.contrib.security.auditing","evennia.contrib.security.auditing.outputs","evennia.contrib.security.auditing.server","evennia.contrib.security.auditing.tests","evennia.contrib.simpledoor","evennia.contrib.slow_exit","evennia.contrib.talking_npc","evennia.contrib.tree_select","evennia.contrib.turnbattle","evennia.contrib.turnbattle.tb_basic","evennia.contrib.turnbattle.tb_equip","evennia.contrib.turnbattle.tb_items","evennia.contrib.turnbattle.tb_magic","evennia.contrib.turnbattle.tb_range","evennia.contrib.tutorial_examples","evennia.contrib.tutorial_examples.bodyfunctions","evennia.contrib.tutorial_examples.cmdset_red_button","evennia.contrib.tutorial_examples.example_batch_code","evennia.contrib.tutorial_examples.red_button","evennia.contrib.tutorial_examples.red_button_scripts","evennia.contrib.tutorial_examples.tests","evennia.contrib.tutorial_world","evennia.contrib.tutorial_world.intro_menu","evennia.contrib.tutorial_world.mob","evennia.contrib.tutorial_world.objects","evennia.contrib.tutorial_world.rooms","evennia.contrib.unixcommand","evennia.contrib.wilderness","evennia.help","evennia.help.admin","evennia.help.manager","evennia.help.models","evennia.locks","evennia.locks.lockfuncs","evennia.locks.lockhandler","evennia.objects","evennia.objects.admin","evennia.objects.manager","evennia.objects.models","evennia.objects.objects","evennia.prototypes","evennia.prototypes.menus","evennia.prototypes.protfuncs","evennia.prototypes.prototypes","evennia.prototypes.spawner","evennia.scripts","evennia.scripts.admin","evennia.scripts.manager","evennia.scripts.models","evennia.scripts.monitorhandler","evennia.scripts.scripthandler","evennia.scripts.scripts","evennia.scripts.taskhandler","evennia.scripts.tickerhandler","evennia.server","evennia.server.admin","evennia.server.amp_client","evennia.server.connection_wizard","evennia.server.deprecations","evennia.server.evennia_launcher","evennia.server.game_index_client","evennia.server.game_index_client.client","evennia.server.game_index_client.service","evennia.server.initial_setup","evennia.server.inputfuncs","evennia.server.manager","evennia.server.models","evennia.server.portal","evennia.server.portal.amp","evennia.server.portal.amp_server","evennia.server.portal.grapevine","evennia.server.portal.irc","evennia.server.portal.mccp","evennia.server.portal.mssp","evennia.server.portal.mxp","evennia.server.portal.naws","evennia.server.portal.portal","evennia.server.portal.portalsessionhandler","evennia.server.portal.rss","evennia.server.portal.ssh","evennia.server.portal.ssl","evennia.server.portal.suppress_ga","evennia.server.portal.telnet","evennia.server.portal.telnet_oob","evennia.server.portal.telnet_ssl","evennia.server.portal.tests","evennia.server.portal.ttype","evennia.server.portal.webclient","evennia.server.portal.webclient_ajax","evennia.server.profiling","evennia.server.profiling.dummyrunner","evennia.server.profiling.dummyrunner_settings","evennia.server.profiling.memplot","evennia.server.profiling.settings_mixin","evennia.server.profiling.test_queries","evennia.server.profiling.tests","evennia.server.profiling.timetrace","evennia.server.server","evennia.server.serversession","evennia.server.session","evennia.server.sessionhandler","evennia.server.signals","evennia.server.throttle","evennia.server.validators","evennia.server.webserver","evennia.settings_default","evennia.typeclasses","evennia.typeclasses.admin","evennia.typeclasses.attributes","evennia.typeclasses.managers","evennia.typeclasses.models","evennia.typeclasses.tags","evennia.utils","evennia.utils.ansi","evennia.utils.batchprocessors","evennia.utils.containers","evennia.utils.create","evennia.utils.dbserialize","evennia.utils.eveditor","evennia.utils.evform","evennia.utils.evmenu","evennia.utils.evmore","evennia.utils.evtable","evennia.utils.gametime","evennia.utils.idmapper","evennia.utils.idmapper.manager","evennia.utils.idmapper.models","evennia.utils.idmapper.tests","evennia.utils.inlinefuncs","evennia.utils.logger","evennia.utils.optionclasses","evennia.utils.optionhandler","evennia.utils.picklefield","evennia.utils.search","evennia.utils.test_resources","evennia.utils.text2html","evennia.utils.utils","evennia.utils.validatorfuncs","evennia.web","evennia.web.urls","evennia.web.utils","evennia.web.utils.backends","evennia.web.utils.general_context","evennia.web.utils.middleware","evennia.web.utils.tests","evennia.web.webclient","evennia.web.webclient.urls","evennia.web.webclient.views","evennia.web.website","evennia.web.website.forms","evennia.web.website.templatetags","evennia.web.website.templatetags.addclass","evennia.web.website.tests","evennia.web.website.urls","evennia.web.website.views","Evennia Documentation","<no title>"],titleterms:{"2017":138,"2019":[1,48,138],"3rd":138,"9th":138,"case":0,"class":[22,27,33,41,51,96,125,127],"default":[5,6,25,30,43,44,55,60,74,80,137,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171],"final":[49,75],"function":[22,42,51,53,80,89,95,102,114],"goto":51,"import":[26,38,41,95],"new":[3,4,6,58,60,69,86,97,102,114,125,127,133],"public":54,"return":[51,59,105],"static":111,"super":19,"switch":41,"try":41,Adding:[0,4,5,6,9,20,25,31,39,40,41,44,74,86,112,121,133],And:[70,92],For:119,NOT:77,PMs:58,TLS:8,The:[3,10,11,13,14,16,18,19,22,26,29,41,46,47,49,50,51,58,69,77,83,85,93,96,109,116,123,135],USE:77,Use:[26,103],Using:[49,52,84,86,90,93,109,112,127,129,130,140],Will:25,Yes:51,abort:29,about:[29,115,125,128],abus:12,access_typ:80,account:[2,58,64,97,143,144,145,146,147,148,156],activ:[57,133],actual:[33,125],add:[3,4,23,25,60],add_choic:22,addclass:359,adding:127,addit:[9,39,41,44,100],address:25,admin:[64,97,135,145,157,173,237,244,254,263,315],administr:7,advanc:[18,29,53,87,110],affect:241,aggress:117,alia:97,alias:112,all:[25,51,67,69],alpha:61,altern:[9,106],amp:276,amp_client:264,amp_serv:277,analyz:93,android:75,ani:[13,55],annot:119,anoth:[38,41,119],ansi:[27,114,126,321],apach:8,api:[1,38,45,53,137],app:[69,133],arbitrari:51,area:[111,123],arg:91,arg_regex:33,argument:[1,51,91],arm:21,arx:9,arxcod:9,ascii:27,ask:[33,51],assign:[19,33],assort:[10,14,31,33,40,51,112,118],async:10,asynchron:10,attach:[106,107],attack:[73,123],attribut:[11,64,97,316],attributehandl:11,audit:[208,209,210,211],aug:[1,48],auto:68,automat:25,avail:[35,59,107],backend:349,ban:12,barter:179,base:[25,109,116],basic:[4,13,14,18,55,71,95,96,123,127,136],batch:[13,14,15,322],batchcod:13,batchprocess:158,batchprocessor:322,befor:26,best:91,beta:61,between:[13,51,125],block:[13,29,38],blockquot:38,bodyfunct:223,bold:38,boot:12,bootstrap:[16,17],border:17,bot:146,brainstorm:[45,138],branch:[51,131],bridg:77,brief:[55,69],briefli:88,bug:97,build:[18,19,20,21,22,38,49,58,61,85,111,124,159],builder:18,building_menu:[22,180],busi:85,button:[17,20],calendar:62,call:33,callabl:51,callback:[0,46,137],callbackhandl:192,caller:51,can:[11,22,55],capcha:133,card:17,care:103,caveat:[13,14,75,114,125],central:45,certif:67,chainsol:138,chang:[0,5,6,25,38,58,60,76,97,103,108,128,131,136],channel:[25,34,41,58,64],channelhandl:174,charact:[6,24,25,46,58,60,61,64,73,82,89,96,123,133,134],chargen:[123,181],chat:138,cheat:42,check:[11,80],checker:26,checkpoint:133,choic:22,choos:23,clean:9,clickabl:114,client:[24,83,88,90,135,137,269],client_opt:74,clone:[9,131],cloth:182,cloud9:90,cmdhandler:150,cmdparser:151,cmdset:[5,152],cmdset_account:160,cmdset_charact:161,cmdset_red_button:224,cmdset_sess:162,cmdset_unloggedin:163,cmdsethandl:153,code:[8,13,22,25,26,27,38,41,42,50,59,60,61,73,85,87,108,124,128,131,322],collabor:57,color:[17,25,27,81,126],color_markup:183,colour:114,combat:[116,123],comfort:100,comm:[164,172,173,174,175,176,177],command:[5,14,22,25,28,29,30,31,32,33,35,41,42,43,44,45,53,58,60,62,68,71,73,81,85,88,91,97,100,116,121,123,127,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,193,322],comment:[44,49],commit:131,commun:[13,34],complet:80,complex:[22,119],compon:[17,45],comput:90,concept:[45,49,116],conclud:[39,123],conclus:[22,41,91,111],condit:[25,119],conf:104,config:[8,53,67,81],configur:[8,23,65,67,71,72,81,98,106,131,133],congratul:61,connect:[35,54,71,90,97],connection_wizard:265,contain:[100,323],content:[25,55],continu:36,contrib:[22,37,64,124,127,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235],contribut:[37,38,53],control:131,convert:91,cooldown:28,coordin:39,copi:8,core:[45,53,56,64],cprofil:93,creat:[0,2,3,5,6,12,20,21,27,33,36,51,53,69,86,89,97,100,111,121,123,125,133,324],createnpc:123,creatur:100,credit:79,crop:27,current:[42,62],custom:[4,5,7,10,22,40,41,51,57,62,80,81,105,113,124,127,135,137],custom_gametim:184,data:[6,11,40,51,105,106],databas:[9,53,68,86,97,109,128],dbref:25,dbserial:325,deal:102,debug:[13,42,103],debugg:106,decor:[10,51],dedent:27,dedic:133,defaultobject:97,defin:[31,33,34,51,80,86,102,131],definit:80,delai:[10,27,29],delimit:25,demo:61,depend:[9,128],deploi:100,deprec:[38,266],desc:51,descer:57,descript:100,design:85,detail:[69,133],develop:[45,57,79,100,103,110,124,127],dialogu:46,dice:[58,185],dictionari:51,differ:[56,125],diku:56,direct:[38,106],directori:[47,90,104],disabl:103,discuss:79,displai:[24,27,49,62],django:[64,80,110,119,133,135],doc:[7,18,26,38,48],docker:100,docstr:38,document:[37,38,129,363],don:[13,55,100],donat:37,down:[20,110,121],dummi:73,dummyrunn:[93,298],dummyrunner_set:299,durat:29,dure:110,dynam:[33,49,51,127],earli:7,echo:74,edit:[22,38,50,123],editnpc:123,editor:50,effect:241,elev:0,email_login:186,emul:56,encod:[15,113],encrypt:90,end:41,engin:124,enjoi:8,enter:121,entir:0,entri:[20,68],error:[44,95,102,110],eval:38,eveditor:[50,326],evennia:[4,5,7,8,9,16,23,25,26,38,41,42,45,47,54,55,56,57,58,67,71,75,76,77,79,90,91,95,96,100,106,109,110,124,126,127,128,131,137,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363],evennia_launch:267,evenniatest:127,event:[0,46,62],eventfunc:194,everi:30,everyth:22,evform:[58,327],evmenu:[25,51,328],evmor:[52,329],evtabl:[25,58,330],examin:42,exampl:[39,42,46,50,51,73,80,83,90,102,108,116,127,137],example_batch_cod:225,execut:[42,59],exercis:77,exist:[6,125],exit:[0,6,25,33,44,89],expand:[116,121],explan:22,explor:[26,96],extended_room:187,extern:[38,103],familiar:[56,57],faq:25,faster:127,featur:[55,69,135],feel:56,field:64,fieldfil:188,file:[13,14,15,38,104,127,131,322],fill:27,find:[39,59],firewal:103,first:[0,22,46,57,60,95,124],fix:131,flexibl:38,folder:[9,26,131],foreground:110,forget:97,fork:[37,131],form:[17,133,357],format:51,forum:79,framework:79,from:[4,20,25,51,55,60,90,96,100,133,137,138,328],front:136,full:[22,41,69,83],func:41,further:[8,10,136],futur:[21,138],game:[7,26,27,39,45,47,49,54,55,57,58,59,61,62,73,90,100,111,120,123,124,127,128,131],game_index_cli:[268,269,270],gamedir:38,gameplai:122,gametim:[62,331],gap:77,gendersub:189,gener:[17,22,41,45,79,123,124,133,165,328],general_context:350,get:[20,51,63,67,70,119],get_client_opt:74,get_input:51,get_inputfunc:74,get_valu:74,git:[64,131],github:64,give:70,given:112,global:[53,91,102],glossari:64,gmcp:88,godhood:20,goldenlayout:137,googl:133,grant:58,grapevin:[65,278],griatch:[1,48,138],grid:[24,49],group:119,guest:66,gui:138,guid:9,handl:[12,69,103,110],handler:[53,107,116],haproxi:67,hard:77,have:123,head:38,health_bar:190,hello:95,help:[9,20,26,37,68,69,70,166,236,237,238,239],here:[26,55,60,96],hierarchi:58,hint:8,hook:125,host:90,hous:20,how:[2,33,58,70,71,89,100,113,121,125],html:[3,133],http:[8,67],idea:138,idmapp:[332,333,334,335],imag:[100,103],implement:73,improv:69,index:[54,69,133,139],info:[79,110],inform:[45,90],infrastructur:73,ingame_python:[191,192,193,194,195,196,197,198],ingo:83,inherit:140,inherits_from:27,initi:[6,23,25,116],initial_setup:271,inlin:114,inlinefunc:[114,336],input:[33,51,88],inputfunc:[74,83,88,272],insid:119,instal:[4,7,8,9,23,63,67,71,75,90,100,122,131,133],instanc:[33,86,125],instruct:88,integr:36,interact:[10,13,14,26],interfac:103,intern:38,internation:76,interpret:106,intro_menu:230,introduct:[9,26,49,51,55,93,95,111,122,133],inventori:82,irc:[72,279],issu:24,ital:38,jan:138,johnni:1,join:41,jumbotron:17,just:55,kei:[22,51,109],keyword:46,kill:110,know:[55,103],known:97,kovitiku:48,languag:[51,76],last:25,latest:[100,128],latin:25,launch:[50,51],layout:[16,41,47],learn:[26,55,77],leav:[41,121],let:[13,42,69,90],librari:[47,96],licens:78,life:7,lift:12,like:[13,56,123],limit:[13,14,119],line:[21,42,50],link:[38,79,114],linux:[36,63,110],list:[38,42],list_nod:51,listen:118,literatur:79,live:110,local:[38,90,91],lock:[11,80,121,240,241,242],lockdown:90,lockfunc:241,lockhandl:242,log:[9,27,69,95,103],logfil:106,logger:337,login:[66,74],logo:136,longer:46,look:[5,56,95,123],lookup:53,mac:[63,110],machin:90,magic:97,mail:[131,199],main:[38,53],make:[20,21,27,57,58,60,67,121,123,127,131],manag:[4,137,147,176,238,245,255,273,317,333],manual:[54,81],map:[49,111],mapbuild:200,mapper:49,mariadb:23,mass:82,master:[58,131],match:97,mccp:280,mech:21,mechan:124,memplot:300,menu:[22,27,51,85,249,328],menu_login:201,merg:31,messag:[0,25,83,88],messagepath:83,method:[33,41,81,97],middlewar:351,migrat:[4,64,128],mind:131,mini:127,minimap:111,miscellan:124,mob:231,mod_proxi:8,mod_ssl:8,mod_wsgi:8,mode:[13,14,64,90,105,110],model:[53,86,127,133,148,177,239,246,256,274,318,334],modif:58,modifi:[8,30],modul:[71,73,95,109,116],monitor:74,monitorhandl:[84,257],more:[16,29,38,53,57,80,81,128,135],most:26,move:[25,121],msdp:88,msg:[34,81,83],mssp:281,mud:79,multi:57,multidesc:[57,202],multipl:[11,119],multisess:[64,105],mush:[57,123],mutabl:[11,97],mux:[129,241],muxcommand:167,mxp:282,mysql:23,myst:38,name:[12,88,97,241],naw:283,ndb:11,need:[0,55],nest:22,next:[57,63,71],nice:67,nick:87,node:51,non:[11,25,28,54],nop:24,note:[8,10,14,15,31,33,38,40,51,87,112,118,122,127],npc:[85,117,118,123],number:91,object:[5,6,11,20,25,27,59,60,61,64,80,82,89,96,97,105,111,112,119,121,124,232,243,244,245,246,247],obtain:133,oct:138,octob:138,off:25,offici:79,olc:109,one:[38,39],onli:[38,110],onlin:[90,131],oob:88,open:85,oper:[0,10],option:[1,22,51,58,67,90,91,103,110],optionclass:338,optionhandl:339,other:[23,33,45,79,90,104],our:[0,22,69,95,96,108,121,133],out:[25,40,58],outgo:83,output:[59,127,209],outputcommand:88,outputfunc:88,outsid:[59,90],overal:73,overload:[81,125,135],overrid:97,overview:[36,47,86,116,136],own:[2,33,40,74,89,90,100,137],page:[3,4,69,135,136],parent:[57,86],pars:[25,41,91,95],part:96,parti:79,patch:37,path:[13,83],paus:[0,29,33],pax:9,pdb:42,permiss:[19,58,80,112,122],perpetu:61,persist:[11,28,29,50],person:20,picklefield:340,pictur:133,pip:[4,64],place:38,plai:67,plan:[26,61,111],player:57,plugin:137,point:26,polici:129,port:[90,103],portal:[83,92,105,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296],portalsess:83,portalsessionhandl:[83,285],posit:1,possibl:51,post:138,postgresql:23,practic:91,prepar:36,prerequisit:75,prevent:25,privileg:4,problem:108,process:[10,110],processor:[13,14,15,322],product:[21,100],profil:[93,297,298,299,300,301,302,303,304],program:[42,55],progress:77,project:[36,106],prompt:[30,51],properti:[2,11,31,33,34,51,64,89,102,105,112,125],protfunc:[109,250],protocol:[40,45,55,88],prototyp:[109,248,249,250,251,252],proxi:[8,90],publicli:131,pudb:42,puppet:64,push:[20,131],put:[67,69,131],puzzl:203,pycharm:106,python:[13,26,55,57,59,71,77,79,94,95,96],quell:[19,80,122],queri:[119,125],quick:[36,63],quickstart:20,quiet:91,quirk:97,random_string_gener:204,read:[10,26,135,136],real:13,reboot:110,recapcha:133,receiv:[40,88],red_button:226,red_button_script:227,reduc:1,refactor:[1,48],refer:[25,38],regist:90,relat:[45,62],releas:[38,61],relev:90,reli:13,reload:[8,25,97,110],remark:123,rememb:38,remind:69,remot:[90,131],remov:[25,112],repeat:[51,74],repo:9,repositori:[26,37,64,131],requir:63,reset:[110,128],reshuffl:20,resourc:79,restart:8,retriev:11,roadmap:99,role:58,roleplai:58,roller:58,rom:56,room:[0,6,25,39,49,58,61,82,89,233],rplanguag:205,rpsystem:206,rss:[98,286],rst:38,rule:[31,73,116],run:[4,7,25,33,42,55,75,100,106,127],runner:127,safeti:13,sage:48,same:[46,51],save:11,schema:128,score:123,screen:35,screenshot:101,script:[64,102,121,195,253,254,255,256,257,258,259,260,261],scripthandl:258,search:[27,31,39,53,86,91,112,119,341],secret:133,secur:[8,67,103,207,208,209,210,211],see:[69,97],select:25,self:91,send:[30,40,88],sent:30,separ:22,sept:[1,48],server:[7,8,23,76,90,92,104,105,123,210,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312],serverconf:104,serversess:[83,306],serversessionhandl:83,servic:270,session:[25,58,64,83,105,307],sessionhandl:[105,308],set:[4,5,9,31,49,51,54,62,65,72,80,81,90,98,103,104,106,123,127,131],setpow:123,settings_default:313,settings_mixin:301,setup:[8,9,23,36,90],sever:[39,46,91],share:131,sharedmemorymodel:86,sheet:[42,58],shell:96,shop:85,shortcut:[11,53],show:[51,123],shut:110,sidebar:38,signal:[107,309],simpl:[3,22,29,42,51,80,93,127],simpledoor:212,singl:11,singleton:53,site:[64,135],sitekei:133,slow_exit:213,soft:108,softcod:[57,108],solut:108,some:[39,41,56],somewher:55,sourc:[38,106],space:17,spawn:[57,109],spawner:[109,252],specif:5,spread:37,spuriou:24,sql:23,sqlite3:23,ssh:[88,103,287],ssl:[90,288],standard:[55,62,129],start:[9,58,63,85,100,110],stat:120,statu:110,step:[5,9,20,42,57,60,61,65,71,72,75,98,124,131,133],stop:110,storag:51,store:[6,11,25,51,109],string:[51,80,91,328],strip:91,structur:38,studi:0,stuff:[55,123],style:17,sub:22,subclass:89,subject:96,suit:127,summari:[12,53,55],superus:80,support:[24,55,88],suppress_ga:289,surround:42,swap:125,synchron:10,syntax:[26,38,57,110,322],syscommand:168,system:[16,32,33,45,61,68,69,73,80,116,123,124,169],tabl:[25,27,38,86],tag:[39,64,112,126,319],talking_npc:214,taskhandl:260,tb_basic:217,tb_equip:218,tb_item:219,tb_magic:220,tb_rang:221,teamciti:36,tech:61,technic:[38,55],telnet:[24,88,90,290],telnet_oob:291,telnet_ssl:292,templat:[36,51,69,133,328],templatetag:[358,359],tempmsg:34,temporari:51,termux:75,test:[55,59,93,123,127,170,196,211,228,293,303,335,352,360],test_queri:302,test_resourc:342,text2html:343,text:[27,38,51,74,113,114,136],texttag:114,theori:91,thi:[41,69],thing:[38,56,57,119],third:79,throttl:310,through:[37,42,100],ticker:[64,115],tickerhandl:[115,261],tie:58,time:[27,33,62,102,108],time_format:27,timer:93,timetrac:304,tip:131,titeuf87:138,to_byt:27,to_str:27,togeth:[67,69],tool:[12,27,79],traceback:26,track:131,train:[73,121],translat:76,travi:130,treat:13,tree_select:215,trick:131,troubleshoot:[60,63,75],ttype:294,turn:[25,97,116],turnbattl:[216,217,218,219,220,221],tutori:[0,5,6,18,21,46,62,69,85,96,116,117,118,119,120,121,122,123,124,127,132,134,136],tutorial_exampl:[222,223,224,225,226,227,228],tutorial_world:[229,230,231,232,233],tweak:[60,96],tweet:[71,120],twist:64,twitter:71,two:96,type:[2,5,6,11,60,89],typeclass:[6,45,53,57,64,81,97,119,124,125,140,197,314,315,316,317,318,319],under:131,understand:126,ungm:58,uninstal:122,unit:127,unixcommand:234,unloggedin:171,unmonitor:74,unrepeat:74,updat:[6,25,60,125,128,131],upgrad:128,upload:103,upstream:[97,131],url:[3,4,38,69,133,347,354,361],usag:[1,13,14,50],use:[55,97,115],used:[25,33],useful:[33,79],user:[19,33,56,57,69,103,124,131],using:[0,42,119,127],util:[17,27,29,33,53,79,106,119,198,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,348,349,350,351,352],valid:[80,311],validatorfunc:345,valu:[51,109,119],variabl:[42,59],vehicl:121,verbatim:38,version:[38,131],versu:10,vhost:8,view:[3,68,69,133,134,135,355,362],virtualenv:64,voic:0,wai:[29,51,77],want:[55,100],warn:38,weather:132,web:[3,45,88,90,97,103,124,133,134,135,136,137,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362],webclient:[137,138,295,353,354,355],webclient_ajax:296,webclient_gui:137,webserv:[103,312],websit:[4,135,356,357,358,359,360,361,362],websocket:[8,67],weight:82,what:[11,16,36,41,55,91,100],when:[25,115],where:[5,55,60,63,96],whitepag:45,who:33,wiki:[4,139],wilder:235,willing:55,window:[9,63],wizard:54,word:37,work:[7,33,55,69,77,91,100,121,125,131],workaround:24,world:[18,20,61,95,122],write:[40,127,137],xterm256:[114,126],yield:[29,51],you:[26,55],your:[2,4,19,20,26,33,39,40,60,74,86,89,90,97,100,103,108,128,131,133,137],yourself:[20,60,61],zone:140}}) \ No newline at end of file diff --git a/docs/0.9.5/toc.html b/docs/0.9.5/toc.html deleted file mode 100644 index 486ec01568..0000000000 --- a/docs/0.9.5/toc.html +++ /dev/null @@ -1,2888 +0,0 @@ - - - - - - - - - <no title> — Evennia 0.9.5 documentation - - - - - - - - - - - - - - - -
-
-
-
- -
- -
-
-
- - -
-
-
-
- -
-
- - - - \ No newline at end of file diff --git a/docs/1.0-dev/.buildinfo b/docs/1.0-dev/.buildinfo index bec6eec64e..1c5a767a90 100644 --- a/docs/1.0-dev/.buildinfo +++ b/docs/1.0-dev/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 958e407c7c05513a0ea7b451645f22a7 +config: 949d2bdd6fc7b28455c44d5411525350 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/1.0-dev/Coding/Coding-Introduction.html b/docs/1.0-dev/Coding/Coding-Introduction.html index 83d202c1ca..59c6f00ea8 100644 --- a/docs/1.0-dev/Coding/Coding-Introduction.html +++ b/docs/1.0-dev/Coding/Coding-Introduction.html @@ -246,7 +246,6 @@ chat are also there for you.

Versions

diff --git a/docs/1.0-dev/Coding/Coding-Overview.html b/docs/1.0-dev/Coding/Coding-Overview.html index 4f58d28a64..96f4847135 100644 --- a/docs/1.0-dev/Coding/Coding-Overview.html +++ b/docs/1.0-dev/Coding/Coding-Overview.html @@ -147,7 +147,6 @@ to you, but some things may still be useful.

Versions

diff --git a/docs/1.0-dev/Coding/Continuous-Integration.html b/docs/1.0-dev/Coding/Continuous-Integration.html index a18761d699..2a8beb7670 100644 --- a/docs/1.0-dev/Coding/Continuous-Integration.html +++ b/docs/1.0-dev/Coding/Continuous-Integration.html @@ -351,7 +351,6 @@ build steps could be added or removed at this point, adding some features like U

Versions

diff --git a/docs/1.0-dev/Coding/Debugging.html b/docs/1.0-dev/Coding/Debugging.html index 4b3bf3245b..3a749efe2b 100644 --- a/docs/1.0-dev/Coding/Debugging.html +++ b/docs/1.0-dev/Coding/Debugging.html @@ -380,7 +380,6 @@ topic here.

Versions

diff --git a/docs/1.0-dev/Coding/Flat-API.html b/docs/1.0-dev/Coding/Flat-API.html index 2889723251..515b947758 100644 --- a/docs/1.0-dev/Coding/Flat-API.html +++ b/docs/1.0-dev/Coding/Flat-API.html @@ -121,7 +121,6 @@ package imports from.

Versions

diff --git a/docs/1.0-dev/Coding/Profiling.html b/docs/1.0-dev/Coding/Profiling.html index 93fa73a3fd..52bf6e3adc 100644 --- a/docs/1.0-dev/Coding/Profiling.html +++ b/docs/1.0-dev/Coding/Profiling.html @@ -340,7 +340,6 @@ For this, actual real-game testing is required.

Versions

diff --git a/docs/1.0-dev/Coding/Quirks.html b/docs/1.0-dev/Coding/Quirks.html index 8a8797c645..0e8d0cab2e 100644 --- a/docs/1.0-dev/Coding/Quirks.html +++ b/docs/1.0-dev/Coding/Quirks.html @@ -139,7 +139,7 @@ Try to avoid doing so.

distributions (notably Ubuntu 16.04 LTS). Zope is a dependency of Twisted. The error manifests in the server not starting with an error that zope.interface is not found even though pip list shows it’s installed. The reason is a missing empty __init__.py file at the root of the zope -package. If the virtualenv is named “evenv” as suggested in the Setup Quickstart +package. If the virtualenv is named “evenv” as suggested in the Setup Quickstart instructions, use the following command to fix it:

touch evenv/local/lib/python2.7/site-packages/zope/__init__.py
 
@@ -204,7 +204,6 @@ instructions, use the following command to fix it:

Versions

diff --git a/docs/1.0-dev/Coding/Setting-up-PyCharm.html b/docs/1.0-dev/Coding/Setting-up-PyCharm.html index f955bfe439..74cad0f28e 100644 --- a/docs/1.0-dev/Coding/Setting-up-PyCharm.html +++ b/docs/1.0-dev/Coding/Setting-up-PyCharm.html @@ -212,7 +212,6 @@ still running in interactive mode.

Versions

diff --git a/docs/1.0-dev/Coding/Unit-Testing.html b/docs/1.0-dev/Coding/Unit-Testing.html index 29ffc3aef9..307a7f5c26 100644 --- a/docs/1.0-dev/Coding/Unit-Testing.html +++ b/docs/1.0-dev/Coding/Unit-Testing.html @@ -399,7 +399,6 @@ django-test-without-migrations package. To install it, simply:

Versions

diff --git a/docs/1.0-dev/Coding/Updating-Your-Game.html b/docs/1.0-dev/Coding/Updating-Your-Game.html index ab3db99a6e..f8b1467f4a 100644 --- a/docs/1.0-dev/Coding/Updating-Your-Game.html +++ b/docs/1.0-dev/Coding/Updating-Your-Game.html @@ -43,7 +43,7 @@

Updating Your Game

Fortunately, it’s extremely easy to keep your Evennia server up-to-date. If you haven’t already, see -the Getting Started guide and get everything running.

+the Getting Started guide and get everything running.

Updating with the latest Evennia code changes

Very commonly we make changes to the Evennia code to improve things. There are many ways to get told @@ -222,7 +222,6 @@ you then just run e

Versions

diff --git a/docs/1.0-dev/Coding/Using-Travis.html b/docs/1.0-dev/Coding/Using-Travis.html index f59ac3aef0..2614ff2229 100644 --- a/docs/1.0-dev/Coding/Using-Travis.html +++ b/docs/1.0-dev/Coding/Using-Travis.html @@ -115,7 +115,6 @@ fitting your game.

Versions

diff --git a/docs/1.0-dev/Coding/Version-Control.html b/docs/1.0-dev/Coding/Version-Control.html index a8aebf85b4..76822bbf03 100644 --- a/docs/1.0-dev/Coding/Version-Control.html +++ b/docs/1.0-dev/Coding/Version-Control.html @@ -567,7 +567,6 @@ template.

Versions

diff --git a/docs/1.0-dev/Components/Accounts.html b/docs/1.0-dev/Components/Accounts.html index 8f61c4dd0b..dfc8aefb0d 100644 --- a/docs/1.0-dev/Components/Accounts.html +++ b/docs/1.0-dev/Components/Accounts.html @@ -188,7 +188,6 @@ any.

Versions

diff --git a/docs/1.0-dev/Components/Attributes.html b/docs/1.0-dev/Components/Attributes.html index af7ecf9fe1..54ae469862 100644 --- a/docs/1.0-dev/Components/Attributes.html +++ b/docs/1.0-dev/Components/Attributes.html @@ -712,7 +712,6 @@ grand vision!

Versions

diff --git a/docs/1.0-dev/Components/Batch-Code-Processor.html b/docs/1.0-dev/Components/Batch-Code-Processor.html index b5636dfd59..18ea007964 100644 --- a/docs/1.0-dev/Components/Batch-Code-Processor.html +++ b/docs/1.0-dev/Components/Batch-Code-Processor.html @@ -325,7 +325,6 @@ executed. When the code runs it has no knowledge of what file those strings wher

Versions

diff --git a/docs/1.0-dev/Components/Batch-Command-Processor.html b/docs/1.0-dev/Components/Batch-Command-Processor.html index f182a3ff27..c3f05e62c9 100644 --- a/docs/1.0-dev/Components/Batch-Command-Processor.html +++ b/docs/1.0-dev/Components/Batch-Command-Processor.html @@ -269,7 +269,6 @@ mode instead, see its readme for install instructions.

Versions

diff --git a/docs/1.0-dev/Components/Batch-Processors.html b/docs/1.0-dev/Components/Batch-Processors.html index 9a12ae9be9..8fe00de5b1 100644 --- a/docs/1.0-dev/Components/Batch-Processors.html +++ b/docs/1.0-dev/Components/Batch-Processors.html @@ -162,7 +162,6 @@ allowed.

Versions

diff --git a/docs/1.0-dev/Components/Bootstrap-Components-and-Utilities.html b/docs/1.0-dev/Components/Bootstrap-Components-and-Utilities.html index 26f34cdd80..31709d88c1 100644 --- a/docs/1.0-dev/Components/Bootstrap-Components-and-Utilities.html +++ b/docs/1.0-dev/Components/Bootstrap-Components-and-Utilities.html @@ -196,7 +196,6 @@ over Versions diff --git a/docs/1.0-dev/Components/Channels.html b/docs/1.0-dev/Components/Channels.html index 38ac17210b..cf7f7f5502 100644 --- a/docs/1.0-dev/Components/Channels.html +++ b/docs/1.0-dev/Components/Channels.html @@ -465,7 +465,6 @@ subscribers (and thus wipe all their aliases).

Versions

diff --git a/docs/1.0-dev/Components/Coding-Utils.html b/docs/1.0-dev/Components/Coding-Utils.html index 97a8bbe2b2..80561b5346 100644 --- a/docs/1.0-dev/Components/Coding-Utils.html +++ b/docs/1.0-dev/Components/Coding-Utils.html @@ -398,7 +398,6 @@ instructions.

Versions

diff --git a/docs/1.0-dev/Components/Command-Sets.html b/docs/1.0-dev/Components/Command-Sets.html index 59f39e0fff..e3006e3179 100644 --- a/docs/1.0-dev/Components/Command-Sets.html +++ b/docs/1.0-dev/Components/Command-Sets.html @@ -463,7 +463,6 @@ commands having any combination of the keys and/or aliases “kick”, “punch

Versions

diff --git a/docs/1.0-dev/Components/Command-System.html b/docs/1.0-dev/Components/Command-System.html index 13f6094db4..c6174e68fd 100644 --- a/docs/1.0-dev/Components/Command-System.html +++ b/docs/1.0-dev/Components/Command-System.html @@ -94,7 +94,6 @@

Versions

diff --git a/docs/1.0-dev/Components/Commands.html b/docs/1.0-dev/Components/Commands.html index 528323491e..09341b3024 100644 --- a/docs/1.0-dev/Components/Commands.html +++ b/docs/1.0-dev/Components/Commands.html @@ -775,7 +775,6 @@ on.

Versions

diff --git a/docs/1.0-dev/Components/Communications.html b/docs/1.0-dev/Components/Communications.html index 9e4b8c8764..07ac5aeeb1 100644 --- a/docs/1.0-dev/Components/Communications.html +++ b/docs/1.0-dev/Components/Communications.html @@ -91,7 +91,6 @@ and is a building block for implementing other game systems. It’s used by the

Versions

diff --git a/docs/1.0-dev/Components/Components-Overview.html b/docs/1.0-dev/Components/Components-Overview.html index 73a7985215..6e80b15d8b 100644 --- a/docs/1.0-dev/Components/Components-Overview.html +++ b/docs/1.0-dev/Components/Components-Overview.html @@ -190,7 +190,6 @@ than, the doc-strings of each component in the Versions diff --git a/docs/1.0-dev/Components/Connection-Screen.html b/docs/1.0-dev/Components/Connection-Screen.html index 51e12e8a41..3d4af099e1 100644 --- a/docs/1.0-dev/Components/Connection-Screen.html +++ b/docs/1.0-dev/Components/Connection-Screen.html @@ -124,7 +124,6 @@ tutorial section on how to add new commands to a default command set.

Versions

diff --git a/docs/1.0-dev/Components/Default-Commands.html b/docs/1.0-dev/Components/Default-Commands.html index 69c7129b90..8237773949 100644 --- a/docs/1.0-dev/Components/Default-Commands.html +++ b/docs/1.0-dev/Components/Default-Commands.html @@ -191,7 +191,6 @@ with 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Components/EvEditor.html b/docs/1.0-dev/Components/EvEditor.html index 47347cb49e..201ec0d57a 100644 --- a/docs/1.0-dev/Components/EvEditor.html +++ b/docs/1.0-dev/Components/EvEditor.html @@ -269,7 +269,6 @@ editor can be useful if you want to test the code you have typed but add new lin

    Versions

    diff --git a/docs/1.0-dev/Components/EvMenu.html b/docs/1.0-dev/Components/EvMenu.html index fa16277a72..c67d134f96 100644 --- a/docs/1.0-dev/Components/EvMenu.html +++ b/docs/1.0-dev/Components/EvMenu.html @@ -1335,7 +1335,6 @@ until the exit node.

    Versions

    diff --git a/docs/1.0-dev/Components/EvMore.html b/docs/1.0-dev/Components/EvMore.html index 3ea04dd007..a7937481c2 100644 --- a/docs/1.0-dev/Components/EvMore.html +++ b/docs/1.0-dev/Components/EvMore.html @@ -123,7 +123,6 @@ paging.

    Versions

    diff --git a/docs/1.0-dev/Components/FuncParser.html b/docs/1.0-dev/Components/FuncParser.html index 79fea629e9..f719d28e59 100644 --- a/docs/1.0-dev/Components/FuncParser.html +++ b/docs/1.0-dev/Components/FuncParser.html @@ -466,7 +466,6 @@ all the defaults (like 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Components/Help-System.html b/docs/1.0-dev/Components/Help-System.html index f32740b413..58338dbf47 100644 --- a/docs/1.0-dev/Components/Help-System.html +++ b/docs/1.0-dev/Components/Help-System.html @@ -485,7 +485,6 @@ at that point).

    Versions

    diff --git a/docs/1.0-dev/Components/Inputfuncs.html b/docs/1.0-dev/Components/Inputfuncs.html index 0d034a3f0d..695920e123 100644 --- a/docs/1.0-dev/Components/Inputfuncs.html +++ b/docs/1.0-dev/Components/Inputfuncs.html @@ -285,7 +285,6 @@ add more. By default the following fields/attributes can be monitored:

    Versions

    diff --git a/docs/1.0-dev/Components/Locks.html b/docs/1.0-dev/Components/Locks.html index 25e952f6f4..c5eca26603 100644 --- a/docs/1.0-dev/Components/Locks.html +++ b/docs/1.0-dev/Components/Locks.html @@ -467,7 +467,6 @@ interface. It’s stand-alone from the permissions described above.

    Versions

    diff --git a/docs/1.0-dev/Components/MonitorHandler.html b/docs/1.0-dev/Components/MonitorHandler.html index 370ba0384f..d966921e97 100644 --- a/docs/1.0-dev/Components/MonitorHandler.html +++ b/docs/1.0-dev/Components/MonitorHandler.html @@ -159,7 +159,6 @@ the monitor to remove:

    Versions

    diff --git a/docs/1.0-dev/Components/Msg.html b/docs/1.0-dev/Components/Msg.html index 4889ad75bd..e44c59dd51 100644 --- a/docs/1.0-dev/Components/Msg.html +++ b/docs/1.0-dev/Components/Msg.html @@ -180,7 +180,6 @@ it.

    Versions

    diff --git a/docs/1.0-dev/Components/Nicks.html b/docs/1.0-dev/Components/Nicks.html index 0768a78f9d..817b951394 100644 --- a/docs/1.0-dev/Components/Nicks.html +++ b/docs/1.0-dev/Components/Nicks.html @@ -203,7 +203,6 @@ basically the unchanged strings you enter to the 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Components/Objects.html b/docs/1.0-dev/Components/Objects.html index 04ee15431e..2bed940d89 100644 --- a/docs/1.0-dev/Components/Objects.html +++ b/docs/1.0-dev/Components/Objects.html @@ -276,7 +276,6 @@ and display this as an error message. If this is not found, the Exit will instea

    Versions

    diff --git a/docs/1.0-dev/Components/Outputfuncs.html b/docs/1.0-dev/Components/Outputfuncs.html index c593cfe10a..6657bf033e 100644 --- a/docs/1.0-dev/Components/Outputfuncs.html +++ b/docs/1.0-dev/Components/Outputfuncs.html @@ -85,7 +85,6 @@

    Versions

    diff --git a/docs/1.0-dev/Components/Permissions.html b/docs/1.0-dev/Components/Permissions.html index 50f8daffbf..70e48ce962 100644 --- a/docs/1.0-dev/Components/Permissions.html +++ b/docs/1.0-dev/Components/Permissions.html @@ -288,7 +288,6 @@ affectable by locks.

    Versions

    diff --git a/docs/1.0-dev/Components/Portal-And-Server.html b/docs/1.0-dev/Components/Portal-And-Server.html index 5fcf4ab0c6..67e82c6d88 100644 --- a/docs/1.0-dev/Components/Portal-And-Server.html +++ b/docs/1.0-dev/Components/Portal-And-Server.html @@ -93,7 +93,6 @@ This allows the two programs to communicate seamlessly.

    Versions

    diff --git a/docs/1.0-dev/Components/Prototypes.html b/docs/1.0-dev/Components/Prototypes.html index 0a9514dd41..36baa764ed 100644 --- a/docs/1.0-dev/Components/Prototypes.html +++ b/docs/1.0-dev/Components/Prototypes.html @@ -422,7 +422,6 @@ the api docs.

    Versions

    diff --git a/docs/1.0-dev/Components/Scripts.html b/docs/1.0-dev/Components/Scripts.html index d727dbeb0b..d2e96e57d0 100644 --- a/docs/1.0-dev/Components/Scripts.html +++ b/docs/1.0-dev/Components/Scripts.html @@ -503,7 +503,6 @@ traceback occurred in your script.

    Versions

    diff --git a/docs/1.0-dev/Components/Server.html b/docs/1.0-dev/Components/Server.html index 51a71a3a30..62c389ad74 100644 --- a/docs/1.0-dev/Components/Server.html +++ b/docs/1.0-dev/Components/Server.html @@ -85,7 +85,6 @@

    Versions

    diff --git a/docs/1.0-dev/Components/Sessions.html b/docs/1.0-dev/Components/Sessions.html index b8f89e3252..1479aaa42a 100644 --- a/docs/1.0-dev/Components/Sessions.html +++ b/docs/1.0-dev/Components/Sessions.html @@ -275,7 +275,6 @@ module for details on the capabilities of the 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Components/Signals.html b/docs/1.0-dev/Components/Signals.html index 94bdbc4512..c7556cf302 100644 --- a/docs/1.0-dev/Components/Signals.html +++ b/docs/1.0-dev/Components/Signals.html @@ -202,7 +202,6 @@ decorator (only relevant for unit testing)

    Versions

    diff --git a/docs/1.0-dev/Components/Tags.html b/docs/1.0-dev/Components/Tags.html index 51a4a510ed..f6969147b5 100644 --- a/docs/1.0-dev/Components/Tags.html +++ b/docs/1.0-dev/Components/Tags.html @@ -249,7 +249,6 @@ is found in the Versions diff --git a/docs/1.0-dev/Components/TickerHandler.html b/docs/1.0-dev/Components/TickerHandler.html index 2db79715d9..a2d350c4c8 100644 --- a/docs/1.0-dev/Components/TickerHandler.html +++ b/docs/1.0-dev/Components/TickerHandler.html @@ -205,7 +205,6 @@ same time without input from something else.

    Versions

    diff --git a/docs/1.0-dev/Components/Typeclasses.html b/docs/1.0-dev/Components/Typeclasses.html index 55873f4b4d..22f2d3919b 100644 --- a/docs/1.0-dev/Components/Typeclasses.html +++ b/docs/1.0-dev/Components/Typeclasses.html @@ -413,7 +413,6 @@ comments for examples and solutions.

    Versions

    diff --git a/docs/1.0-dev/Components/Web-API.html b/docs/1.0-dev/Components/Web-API.html index d91c148388..3d8142c231 100644 --- a/docs/1.0-dev/Components/Web-API.html +++ b/docs/1.0-dev/Components/Web-API.html @@ -204,7 +204,6 @@ copy over evennia/w

    Versions

    diff --git a/docs/1.0-dev/Components/Web-Admin.html b/docs/1.0-dev/Components/Web-Admin.html index 0e7998d89c..e64c9aa42e 100644 --- a/docs/1.0-dev/Components/Web-Admin.html +++ b/docs/1.0-dev/Components/Web-Admin.html @@ -251,7 +251,6 @@ following to your m

    Versions

    diff --git a/docs/1.0-dev/Components/Webclient.html b/docs/1.0-dev/Components/Webclient.html index 9438ec9b01..f157db0fda 100644 --- a/docs/1.0-dev/Components/Webclient.html +++ b/docs/1.0-dev/Components/Webclient.html @@ -350,7 +350,6 @@ window.plugin_handler.add("myplugin", myplugin);

    Versions

    diff --git a/docs/1.0-dev/Components/Webserver.html b/docs/1.0-dev/Components/Webserver.html index 26eb637b76..4862d3e56e 100644 --- a/docs/1.0-dev/Components/Webserver.html +++ b/docs/1.0-dev/Components/Webserver.html @@ -164,7 +164,6 @@ come back or you reload it manually in your browser.

    Versions

    diff --git a/docs/1.0-dev/Components/Website.html b/docs/1.0-dev/Components/Website.html index 76ab4c6dd2..9e9727ee44 100644 --- a/docs/1.0-dev/Components/Website.html +++ b/docs/1.0-dev/Components/Website.html @@ -485,7 +485,6 @@ on the Django website - it covers all you need.

    Versions

    diff --git a/docs/1.0-dev/Concepts/Async-Process.html b/docs/1.0-dev/Concepts/Async-Process.html index 14e7c83481..43270e9909 100644 --- a/docs/1.0-dev/Concepts/Async-Process.html +++ b/docs/1.0-dev/Concepts/Async-Process.html @@ -301,7 +301,6 @@ your own liking.

    Versions

    diff --git a/docs/1.0-dev/Concepts/Banning.html b/docs/1.0-dev/Concepts/Banning.html index 846486a48a..badedda31a 100644 --- a/docs/1.0-dev/Concepts/Banning.html +++ b/docs/1.0-dev/Concepts/Banning.html @@ -240,7 +240,6 @@ objects on the fly. For advanced users.

    Versions

    diff --git a/docs/1.0-dev/Concepts/Bootstrap-&-Evennia.html b/docs/1.0-dev/Concepts/Bootstrap-&-Evennia.html index 527e1485f8..d4420997f4 100644 --- a/docs/1.0-dev/Concepts/Bootstrap-&-Evennia.html +++ b/docs/1.0-dev/Concepts/Bootstrap-&-Evennia.html @@ -192,7 +192,6 @@ started/introduction/) or read one of our other web tutorials.

    Versions

    diff --git a/docs/1.0-dev/Concepts/Building-Permissions.html b/docs/1.0-dev/Concepts/Building-Permissions.html index e3f0775207..90bbd42c91 100644 --- a/docs/1.0-dev/Concepts/Building-Permissions.html +++ b/docs/1.0-dev/Concepts/Building-Permissions.html @@ -162,7 +162,6 @@ levels. Note that you cannot escalate your permissions this way; If the Characte

    Versions

    diff --git a/docs/1.0-dev/Concepts/Change-Messages-Per-Receiver.html b/docs/1.0-dev/Concepts/Change-Messages-Per-Receiver.html index 1c04a694a4..fe33bbc375 100644 --- a/docs/1.0-dev/Concepts/Change-Messages-Per-Receiver.html +++ b/docs/1.0-dev/Concepts/Change-Messages-Per-Receiver.html @@ -444,7 +444,6 @@ worth checking out for inspiration.

    Versions

    diff --git a/docs/1.0-dev/Concepts/Clickable-Links.html b/docs/1.0-dev/Concepts/Clickable-Links.html index c40424f711..991420ce5c 100644 --- a/docs/1.0-dev/Concepts/Clickable-Links.html +++ b/docs/1.0-dev/Concepts/Clickable-Links.html @@ -101,7 +101,6 @@ will be shown.

    Versions

    diff --git a/docs/1.0-dev/Concepts/Colors.html b/docs/1.0-dev/Concepts/Colors.html index 17aa137708..3a7802cf02 100644 --- a/docs/1.0-dev/Concepts/Colors.html +++ b/docs/1.0-dev/Concepts/Colors.html @@ -269,7 +269,6 @@ use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags

    Versions

    diff --git a/docs/1.0-dev/Concepts/Concepts-Overview.html b/docs/1.0-dev/Concepts/Concepts-Overview.html index 9104302be0..a9cc0ace86 100644 --- a/docs/1.0-dev/Concepts/Concepts-Overview.html +++ b/docs/1.0-dev/Concepts/Concepts-Overview.html @@ -150,7 +150,6 @@

    Versions

    diff --git a/docs/1.0-dev/Concepts/Custom-Protocols.html b/docs/1.0-dev/Concepts/Custom-Protocols.html index 308a398d86..8c7bfc066d 100644 --- a/docs/1.0-dev/Concepts/Custom-Protocols.html +++ b/docs/1.0-dev/Concepts/Custom-Protocols.html @@ -315,7 +315,6 @@ ways.

    Versions

    diff --git a/docs/1.0-dev/Concepts/Guest-Logins.html b/docs/1.0-dev/Concepts/Guest-Logins.html index 5dc0f02250..e4bb14bc8a 100644 --- a/docs/1.0-dev/Concepts/Guest-Logins.html +++ b/docs/1.0-dev/Concepts/Guest-Logins.html @@ -109,7 +109,6 @@ of nine names from

    Versions

    diff --git a/docs/1.0-dev/Concepts/Internationalization.html b/docs/1.0-dev/Concepts/Internationalization.html index c0e28e8b84..c83be3b8a4 100644 --- a/docs/1.0-dev/Concepts/Internationalization.html +++ b/docs/1.0-dev/Concepts/Internationalization.html @@ -151,7 +151,7 @@ has translated it yet. Alternatively you might have the language but find the translation bad … You are welcome to help improve the situation!

    To start a new translation you need to first have cloned the Evennia repositry with GIT and activated a python virtualenv as described on the -Setup Quickstart page.

    +Setup Quickstart page.

    Go to evennia/evennia/ - that is, not your game dir, but inside the evennia/ repo itself. If you see the locale/ folder you are in the right place. Make sure your virtualenv is active so the evennia command is available. Then run

    @@ -275,7 +275,6 @@ Swedish: "Fel medan cmdset laddades: Ingen cmdset-klass med namn '{cla

    Versions

    diff --git a/docs/1.0-dev/Concepts/Messagepath.html b/docs/1.0-dev/Concepts/Messagepath.html index 5e7308a3e0..94c5ec26e7 100644 --- a/docs/1.0-dev/Concepts/Messagepath.html +++ b/docs/1.0-dev/Concepts/Messagepath.html @@ -304,7 +304,6 @@ may trigger changes in the GUI or play a sound etc.

    Versions

    diff --git a/docs/1.0-dev/Concepts/Multisession-modes.html b/docs/1.0-dev/Concepts/Multisession-modes.html index 7ce616eb90..cf01160a1b 100644 --- a/docs/1.0-dev/Concepts/Multisession-modes.html +++ b/docs/1.0-dev/Concepts/Multisession-modes.html @@ -85,7 +85,6 @@

    Versions

    diff --git a/docs/1.0-dev/Concepts/New-Models.html b/docs/1.0-dev/Concepts/New-Models.html index 35e98ee952..3df50dec29 100644 --- a/docs/1.0-dev/Concepts/New-Models.html +++ b/docs/1.0-dev/Concepts/New-Models.html @@ -335,7 +335,6 @@ lot more information about querying the database.

    Versions

    diff --git a/docs/1.0-dev/Concepts/OOB.html b/docs/1.0-dev/Concepts/OOB.html index 4ca32cb0f3..5367b16ca8 100644 --- a/docs/1.0-dev/Concepts/OOB.html +++ b/docs/1.0-dev/Concepts/OOB.html @@ -256,7 +256,6 @@ same example ("

    Versions

    diff --git a/docs/1.0-dev/Concepts/Soft-Code.html b/docs/1.0-dev/Concepts/Soft-Code.html index 2178551145..d0d1099ea1 100644 --- a/docs/1.0-dev/Concepts/Soft-Code.html +++ b/docs/1.0-dev/Concepts/Soft-Code.html @@ -176,7 +176,6 @@ pseudo-softcode plugin aimed at developers wanting to script their game from ins

    Versions

    diff --git a/docs/1.0-dev/Concepts/Text-Encodings.html b/docs/1.0-dev/Concepts/Text-Encodings.html index 9cc3d32498..43628fde7d 100644 --- a/docs/1.0-dev/Concepts/Text-Encodings.html +++ b/docs/1.0-dev/Concepts/Text-Encodings.html @@ -149,7 +149,6 @@ the Wikipedia article 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Concepts/TextTags.html b/docs/1.0-dev/Concepts/TextTags.html index 4e47e191d5..ddd2904832 100644 --- a/docs/1.0-dev/Concepts/TextTags.html +++ b/docs/1.0-dev/Concepts/TextTags.html @@ -95,7 +95,6 @@ circumstances. The parser is run on all outgoing messages if 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Concepts/Using-MUX-as-a-Standard.html b/docs/1.0-dev/Concepts/Using-MUX-as-a-Standard.html index 68930ce859..d845c12d80 100644 --- a/docs/1.0-dev/Concepts/Using-MUX-as-a-Standard.html +++ b/docs/1.0-dev/Concepts/Using-MUX-as-a-Standard.html @@ -168,7 +168,6 @@ something to the effect of

    Versions

    diff --git a/docs/1.0-dev/Concepts/Web-Features.html b/docs/1.0-dev/Concepts/Web-Features.html index d4afe20a46..19fd9568d0 100644 --- a/docs/1.0-dev/Concepts/Web-Features.html +++ b/docs/1.0-dev/Concepts/Web-Features.html @@ -216,7 +216,6 @@ implementation, the relevant django “applications” in default Evennia are Versions diff --git a/docs/1.0-dev/Concepts/Zones.html b/docs/1.0-dev/Concepts/Zones.html index 68d2f7d7bb..0a173a63e6 100644 --- a/docs/1.0-dev/Concepts/Zones.html +++ b/docs/1.0-dev/Concepts/Zones.html @@ -138,7 +138,6 @@ properly search the inheritance tree.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Arxcode-installing-help.html b/docs/1.0-dev/Contribs/Arxcode-installing-help.html index 341aa27581..a9071dea6b 100644 --- a/docs/1.0-dev/Contribs/Arxcode-installing-help.html +++ b/docs/1.0-dev/Contribs/Arxcode-installing-help.html @@ -62,13 +62,13 @@ better match with the vanilla Evennia install.

    Installing Evennia

    Firstly, set aside a folder/directory on your drive for everything to follow.

    You need to start by installing Evennia by following most of the -Getting Started Instructions for your OS. The difference is that you need to git clone https://github.com/TehomCD/evennia.git instead of Evennia’s repo because Arx uses TehomCD’s older +Getting Started Instructions for your OS. The difference is that you need to git clone https://github.com/TehomCD/evennia.git instead of Evennia’s repo because Arx uses TehomCD’s older Evennia 0.8 fork, notably still using Python2. This detail is important if referring to newer Evennia documentation.

    If you are new to Evennia it’s highly recommended that you run through the instructions in full - including initializing and starting a new empty game and connecting to it. That way you can be sure Evennia works correctly as a base line. If you have trouble, make sure to -read the Troubleshooting instructions for your +read the Troubleshooting instructions for your operating system. You can also drop into our forums, join #evennia on irc.freenode.net or chat from the linked Discord Server.

    @@ -338,7 +338,6 @@ on localhost at port 4000, and the webserver at Versions diff --git a/docs/1.0-dev/Contribs/Building-menus.html b/docs/1.0-dev/Contribs/Building-menus.html index 0a91548e77..fefabc6e8c 100644 --- a/docs/1.0-dev/Contribs/Building-menus.html +++ b/docs/1.0-dev/Contribs/Building-menus.html @@ -1292,7 +1292,6 @@ exhaustive but user-friendly.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-AWSStorage.html b/docs/1.0-dev/Contribs/Contrib-AWSStorage.html index 6d84b2ca3b..50c30df90a 100644 --- a/docs/1.0-dev/Contribs/Contrib-AWSStorage.html +++ b/docs/1.0-dev/Contribs/Contrib-AWSStorage.html @@ -314,7 +314,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Auditing.html b/docs/1.0-dev/Contribs/Contrib-Auditing.html index 7697922a9b..b81e234b55 100644 --- a/docs/1.0-dev/Contribs/Contrib-Auditing.html +++ b/docs/1.0-dev/Contribs/Contrib-Auditing.html @@ -161,7 +161,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Barter.html b/docs/1.0-dev/Contribs/Contrib-Barter.html index 569211026f..4bd818db67 100644 --- a/docs/1.0-dev/Contribs/Contrib-Barter.html +++ b/docs/1.0-dev/Contribs/Contrib-Barter.html @@ -200,7 +200,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Batchprocessor.html b/docs/1.0-dev/Contribs/Contrib-Batchprocessor.html index e83640c9e6..67525ac07e 100644 --- a/docs/1.0-dev/Contribs/Contrib-Batchprocessor.html +++ b/docs/1.0-dev/Contribs/Contrib-Batchprocessor.html @@ -126,7 +126,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Bodyfunctions.html b/docs/1.0-dev/Contribs/Contrib-Bodyfunctions.html index f328ffaae6..6777be84de 100644 --- a/docs/1.0-dev/Contribs/Contrib-Bodyfunctions.html +++ b/docs/1.0-dev/Contribs/Contrib-Bodyfunctions.html @@ -108,7 +108,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Building-Menu.html b/docs/1.0-dev/Contribs/Contrib-Building-Menu.html index 19688ade5c..2799a9b73f 100644 --- a/docs/1.0-dev/Contribs/Contrib-Building-Menu.html +++ b/docs/1.0-dev/Contribs/Contrib-Building-Menu.html @@ -206,7 +206,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Clothing.html b/docs/1.0-dev/Contribs/Contrib-Clothing.html index 769f125c2f..2535da2a61 100644 --- a/docs/1.0-dev/Contribs/Contrib-Clothing.html +++ b/docs/1.0-dev/Contribs/Contrib-Clothing.html @@ -176,7 +176,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Color-Markups.html b/docs/1.0-dev/Contribs/Contrib-Color-Markups.html index e9afde09ef..e4abe96c5d 100644 --- a/docs/1.0-dev/Contribs/Contrib-Color-Markups.html +++ b/docs/1.0-dev/Contribs/Contrib-Color-Markups.html @@ -147,7 +147,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Cooldowns.html b/docs/1.0-dev/Contribs/Contrib-Cooldowns.html index 8c20a57dd3..c533134bc3 100644 --- a/docs/1.0-dev/Contribs/Contrib-Cooldowns.html +++ b/docs/1.0-dev/Contribs/Contrib-Cooldowns.html @@ -145,7 +145,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Crafting.html b/docs/1.0-dev/Contribs/Contrib-Crafting.html index 9310630f30..4b9948fd61 100644 --- a/docs/1.0-dev/Contribs/Contrib-Crafting.html +++ b/docs/1.0-dev/Contribs/Contrib-Crafting.html @@ -370,7 +370,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Custom-Gametime.html b/docs/1.0-dev/Contribs/Contrib-Custom-Gametime.html index 69e6b08678..146df47307 100644 --- a/docs/1.0-dev/Contribs/Contrib-Custom-Gametime.html +++ b/docs/1.0-dev/Contribs/Contrib-Custom-Gametime.html @@ -138,7 +138,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Dice.html b/docs/1.0-dev/Contribs/Contrib-Dice.html index 91ac705fbe..25c5568758 100644 --- a/docs/1.0-dev/Contribs/Contrib-Dice.html +++ b/docs/1.0-dev/Contribs/Contrib-Dice.html @@ -155,7 +155,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Email-Login.html b/docs/1.0-dev/Contribs/Contrib-Email-Login.html index 1d9eca7277..f557236b33 100644 --- a/docs/1.0-dev/Contribs/Contrib-Email-Login.html +++ b/docs/1.0-dev/Contribs/Contrib-Email-Login.html @@ -121,7 +121,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Evscaperoom.html b/docs/1.0-dev/Contribs/Contrib-Evscaperoom.html index 87c449c05e..ff4fc74c84 100644 --- a/docs/1.0-dev/Contribs/Contrib-Evscaperoom.html +++ b/docs/1.0-dev/Contribs/Contrib-Evscaperoom.html @@ -211,7 +211,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Extended-Room.html b/docs/1.0-dev/Contribs/Contrib-Extended-Room.html index c198adbd42..c775ab829a 100644 --- a/docs/1.0-dev/Contribs/Contrib-Extended-Room.html +++ b/docs/1.0-dev/Contribs/Contrib-Extended-Room.html @@ -172,7 +172,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Fieldfill.html b/docs/1.0-dev/Contribs/Contrib-Fieldfill.html index 456c91ec06..20b82106a6 100644 --- a/docs/1.0-dev/Contribs/Contrib-Fieldfill.html +++ b/docs/1.0-dev/Contribs/Contrib-Fieldfill.html @@ -242,7 +242,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Gendersub.html b/docs/1.0-dev/Contribs/Contrib-Gendersub.html index bc59bd229f..d6516897fa 100644 --- a/docs/1.0-dev/Contribs/Contrib-Gendersub.html +++ b/docs/1.0-dev/Contribs/Contrib-Gendersub.html @@ -135,7 +135,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Health-Bar.html b/docs/1.0-dev/Contribs/Contrib-Health-Bar.html index c72a5014fb..e0fac3aeea 100644 --- a/docs/1.0-dev/Contribs/Contrib-Health-Bar.html +++ b/docs/1.0-dev/Contribs/Contrib-Health-Bar.html @@ -124,7 +124,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Ingame-Python-Tutorial-Dialogue.html b/docs/1.0-dev/Contribs/Contrib-Ingame-Python-Tutorial-Dialogue.html index fcb06ead46..61482829a2 100644 --- a/docs/1.0-dev/Contribs/Contrib-Ingame-Python-Tutorial-Dialogue.html +++ b/docs/1.0-dev/Contribs/Contrib-Ingame-Python-Tutorial-Dialogue.html @@ -318,7 +318,6 @@ events).

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Ingame-Python-Tutorial-Elevator.html b/docs/1.0-dev/Contribs/Contrib-Ingame-Python-Tutorial-Elevator.html index 5f1952b58e..bea91e9d7e 100644 --- a/docs/1.0-dev/Contribs/Contrib-Ingame-Python-Tutorial-Elevator.html +++ b/docs/1.0-dev/Contribs/Contrib-Ingame-Python-Tutorial-Elevator.html @@ -506,7 +506,6 @@ shown in the next tutorial.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Ingame-Python.html b/docs/1.0-dev/Contribs/Contrib-Ingame-Python.html index 15629a5daf..d2a13f95e7 100644 --- a/docs/1.0-dev/Contribs/Contrib-Ingame-Python.html +++ b/docs/1.0-dev/Contribs/Contrib-Ingame-Python.html @@ -999,7 +999,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Mail.html b/docs/1.0-dev/Contribs/Contrib-Mail.html index 7b75819007..32c8144b11 100644 --- a/docs/1.0-dev/Contribs/Contrib-Mail.html +++ b/docs/1.0-dev/Contribs/Contrib-Mail.html @@ -143,7 +143,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Mapbuilder.html b/docs/1.0-dev/Contribs/Contrib-Mapbuilder.html index 456b20afc7..083591eabf 100644 --- a/docs/1.0-dev/Contribs/Contrib-Mapbuilder.html +++ b/docs/1.0-dev/Contribs/Contrib-Mapbuilder.html @@ -372,7 +372,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Menu-Login.html b/docs/1.0-dev/Contribs/Contrib-Menu-Login.html index 4f37e06ac1..a16195baf5 100644 --- a/docs/1.0-dev/Contribs/Contrib-Menu-Login.html +++ b/docs/1.0-dev/Contribs/Contrib-Menu-Login.html @@ -115,7 +115,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Mirror.html b/docs/1.0-dev/Contribs/Contrib-Mirror.html index 9c5685cc27..e0f37f32d6 100644 --- a/docs/1.0-dev/Contribs/Contrib-Mirror.html +++ b/docs/1.0-dev/Contribs/Contrib-Mirror.html @@ -110,7 +110,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Multidescer.html b/docs/1.0-dev/Contribs/Contrib-Multidescer.html index 26da170a02..ad6c0f42bd 100644 --- a/docs/1.0-dev/Contribs/Contrib-Multidescer.html +++ b/docs/1.0-dev/Contribs/Contrib-Multidescer.html @@ -114,7 +114,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Mux-Comms-Cmds.html b/docs/1.0-dev/Contribs/Contrib-Mux-Comms-Cmds.html index ca43835973..f93778b387 100644 --- a/docs/1.0-dev/Contribs/Contrib-Mux-Comms-Cmds.html +++ b/docs/1.0-dev/Contribs/Contrib-Mux-Comms-Cmds.html @@ -160,7 +160,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Overview.html b/docs/1.0-dev/Contribs/Contrib-Overview.html index f2efed3d1f..d0a83c5ab2 100644 --- a/docs/1.0-dev/Contribs/Contrib-Overview.html +++ b/docs/1.0-dev/Contribs/Contrib-Overview.html @@ -586,7 +586,6 @@ will be overwritten.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Puzzles.html b/docs/1.0-dev/Contribs/Contrib-Puzzles.html index b4e6ad64e9..3297e06e37 100644 --- a/docs/1.0-dev/Contribs/Contrib-Puzzles.html +++ b/docs/1.0-dev/Contribs/Contrib-Puzzles.html @@ -163,7 +163,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-RPSystem.html b/docs/1.0-dev/Contribs/Contrib-RPSystem.html index 239a5d10d1..4752a6dd50 100644 --- a/docs/1.0-dev/Contribs/Contrib-RPSystem.html +++ b/docs/1.0-dev/Contribs/Contrib-RPSystem.html @@ -350,7 +350,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Random-String-Generator.html b/docs/1.0-dev/Contribs/Contrib-Random-String-Generator.html index 8fd06487a3..dfc458c1fd 100644 --- a/docs/1.0-dev/Contribs/Contrib-Random-String-Generator.html +++ b/docs/1.0-dev/Contribs/Contrib-Random-String-Generator.html @@ -150,7 +150,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Red-Button.html b/docs/1.0-dev/Contribs/Contrib-Red-Button.html index 02fcfe3783..ba7e33edd7 100644 --- a/docs/1.0-dev/Contribs/Contrib-Red-Button.html +++ b/docs/1.0-dev/Contribs/Contrib-Red-Button.html @@ -123,7 +123,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Simpledoor.html b/docs/1.0-dev/Contribs/Contrib-Simpledoor.html index ffdd6e2c94..dd885c9ca5 100644 --- a/docs/1.0-dev/Contribs/Contrib-Simpledoor.html +++ b/docs/1.0-dev/Contribs/Contrib-Simpledoor.html @@ -137,7 +137,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Slow-Exit.html b/docs/1.0-dev/Contribs/Contrib-Slow-Exit.html index b2c963756a..268e4b3b7c 100644 --- a/docs/1.0-dev/Contribs/Contrib-Slow-Exit.html +++ b/docs/1.0-dev/Contribs/Contrib-Slow-Exit.html @@ -149,7 +149,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Talking-Npc.html b/docs/1.0-dev/Contribs/Contrib-Talking-Npc.html index 1dbb4ccd9b..a6208b28f5 100644 --- a/docs/1.0-dev/Contribs/Contrib-Talking-Npc.html +++ b/docs/1.0-dev/Contribs/Contrib-Talking-Npc.html @@ -111,7 +111,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Traits.html b/docs/1.0-dev/Contribs/Contrib-Traits.html index afe47c5df9..9bb59c0d5c 100644 --- a/docs/1.0-dev/Contribs/Contrib-Traits.html +++ b/docs/1.0-dev/Contribs/Contrib-Traits.html @@ -521,7 +521,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Tree-Select.html b/docs/1.0-dev/Contribs/Contrib-Tree-Select.html index 3ca69c04fe..d76e044cd4 100644 --- a/docs/1.0-dev/Contribs/Contrib-Tree-Select.html +++ b/docs/1.0-dev/Contribs/Contrib-Tree-Select.html @@ -239,7 +239,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Turnbattle.html b/docs/1.0-dev/Contribs/Contrib-Turnbattle.html index d8dd0ee641..3b045b454e 100644 --- a/docs/1.0-dev/Contribs/Contrib-Turnbattle.html +++ b/docs/1.0-dev/Contribs/Contrib-Turnbattle.html @@ -137,7 +137,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Tutorial-World.html b/docs/1.0-dev/Contribs/Contrib-Tutorial-World.html index ac82e4d6f2..3ec804c70d 100644 --- a/docs/1.0-dev/Contribs/Contrib-Tutorial-World.html +++ b/docs/1.0-dev/Contribs/Contrib-Tutorial-World.html @@ -189,7 +189,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Unixcommand.html b/docs/1.0-dev/Contribs/Contrib-Unixcommand.html index 0ecde4c4da..9b95ff7137 100644 --- a/docs/1.0-dev/Contribs/Contrib-Unixcommand.html +++ b/docs/1.0-dev/Contribs/Contrib-Unixcommand.html @@ -155,7 +155,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-Wilderness.html b/docs/1.0-dev/Contribs/Contrib-Wilderness.html index 54d22a0933..e71d32c585 100644 --- a/docs/1.0-dev/Contribs/Contrib-Wilderness.html +++ b/docs/1.0-dev/Contribs/Contrib-Wilderness.html @@ -205,7 +205,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contribs/Contrib-XYZGrid.html b/docs/1.0-dev/Contribs/Contrib-XYZGrid.html index f3e9b4eeaf..2dc50019d7 100644 --- a/docs/1.0-dev/Contribs/Contrib-XYZGrid.html +++ b/docs/1.0-dev/Contribs/Contrib-XYZGrid.html @@ -1308,7 +1308,7 @@ know how to call find the pathfinder though:

  • xymap.get_shortest_path(start_xy, end_xy)

  • xymap.get_visual_range(xy, dist=2, **kwargs)

  • -

    See the XYMap documentation for +

    See the XYMap documentation for details.

    @@ -1556,7 +1556,6 @@ file will be overwritten, so edit that file rather than this one.

    Versions

    diff --git a/docs/1.0-dev/Contributing-Docs.html b/docs/1.0-dev/Contributing-Docs.html index 72ed89ab21..4b34fdc95f 100644 --- a/docs/1.0-dev/Contributing-Docs.html +++ b/docs/1.0-dev/Contributing-Docs.html @@ -824,7 +824,6 @@ available at 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Contributing.html b/docs/1.0-dev/Contributing.html index 50c0fb9f74..667e1e0ab8 100644 --- a/docs/1.0-dev/Contributing.html +++ b/docs/1.0-dev/Contributing.html @@ -287,7 +287,6 @@ PayPal yourself to use it).

    Versions

    diff --git a/docs/1.0-dev/Evennia-API.html b/docs/1.0-dev/Evennia-API.html index df104d5a65..5c3b1a57d6 100644 --- a/docs/1.0-dev/Evennia-API.html +++ b/docs/1.0-dev/Evennia-API.html @@ -232,7 +232,6 @@ game-specific contributions and plugins (1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Evennia-Introduction.html b/docs/1.0-dev/Evennia-Introduction.html index b19917f049..e43bdf3c64 100644 --- a/docs/1.0-dev/Evennia-Introduction.html +++ b/docs/1.0-dev/Evennia-Introduction.html @@ -19,7 +19,7 @@ - +

    What you need to know to work with Evennia

    -

    Assuming you have Evennia working (see the quick start instructions) and have +

    Assuming you have Evennia working (see the quick start instructions) and have gotten as far as to start the server and connect to it with the client of your choice, here’s what you need to know depending on your skills and needs.

    @@ -215,8 +215,8 @@ your own game, you will end up with a small (very small) game that you can build

    Evennia Documentation

    Next topic

    -

    Setup quickstart

    +

    Starting Tutorial (Part 1)

    @@ -254,7 +253,7 @@ your own game, you will end up with a small (very small) game that you can build modules |
  • - next |
  • Usage:

      -
    • virtualenv <name> - initialize a new virtualenv <name> in a new folder <name> in the current -location. Called evenv in these docs.

    • -
    • virtualenv -p path/to/alternate/python_executable <name> - create a virtualenv using another -Python version than default.

    • -
    • source <folder_name>/bin/activate(linux/mac) - activate the virtualenv in <folder_name>.

    • -
    • <folder_name>\Scripts\activate (windows)

    • +
    • python3.10 -m venv evenv - initialize a new virtualenv-folder evenv in the current +location. You can call this whatever you like. The Python-version you use for this call will be the one used +for everything inside the virtualenv.

    • +
    • source evenv/bin/activate (linux/mac) or evenv\Scripts\activate(windows) - this activates the +virtualenv.

    • deactivate - turn off the currently activated virtualenv.

    A virtualenv is ‘activated’ only for the console/terminal it was started in, but it’s safe to @@ -408,7 +407,7 @@ never have to actually deactivate or close the console/terminal.

    -

    So, when do I need to activate my virtualenv? If the virtualenv is not active, none of the Python +

    So, when do you need to activate my virtualenv? If the virtualenv is not active, none of the Python packages/programs you installed in it will be available to you. So at a minimum, it needs to be activated whenever you want to use the evennia command for any reason.

  • @@ -486,7 +485,6 @@ activated whenever you want to use the 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Howto/Add-a-wiki-on-your-website.html b/docs/1.0-dev/Howto/Add-a-wiki-on-your-website.html index 75a9b9f02f..06bce32076 100644 --- a/docs/1.0-dev/Howto/Add-a-wiki-on-your-website.html +++ b/docs/1.0-dev/Howto/Add-a-wiki-on-your-website.html @@ -326,7 +326,6 @@ necessary. If you’re interested in supporting this little project, you are mo

    Versions

    diff --git a/docs/1.0-dev/Howto/Building-a-mech-tutorial.html b/docs/1.0-dev/Howto/Building-a-mech-tutorial.html index 9b3d115400..534da08824 100644 --- a/docs/1.0-dev/Howto/Building-a-mech-tutorial.html +++ b/docs/1.0-dev/Howto/Building-a-mech-tutorial.html @@ -323,7 +323,6 @@ shooting goodness would be made available to you only when you enter it.

    Versions

    diff --git a/docs/1.0-dev/Howto/Coding-FAQ.html b/docs/1.0-dev/Howto/Coding-FAQ.html index 6a39387b69..5826be7115 100644 --- a/docs/1.0-dev/Howto/Coding-FAQ.html +++ b/docs/1.0-dev/Howto/Coding-FAQ.html @@ -453,7 +453,6 @@ discussion where some suitable fonts are suggested.

    Versions

    diff --git a/docs/1.0-dev/Howto/Command-Cooldown.html b/docs/1.0-dev/Howto/Command-Cooldown.html index d5cb1189ed..12655d00c0 100644 --- a/docs/1.0-dev/Howto/Command-Cooldown.html +++ b/docs/1.0-dev/Howto/Command-Cooldown.html @@ -183,7 +183,6 @@ other types of attacks for a while before the warrior can recover.

    Versions

    diff --git a/docs/1.0-dev/Howto/Command-Duration.html b/docs/1.0-dev/Howto/Command-Duration.html index 67b1dff2af..5f17f80b63 100644 --- a/docs/1.0-dev/Howto/Command-Duration.html +++ b/docs/1.0-dev/Howto/Command-Duration.html @@ -484,7 +484,6 @@ callback when the server comes back up (it will resume the countdown and ignore

    Versions

    diff --git a/docs/1.0-dev/Howto/Command-Prompt.html b/docs/1.0-dev/Howto/Command-Prompt.html index 5964437e68..4c38495194 100644 --- a/docs/1.0-dev/Howto/Command-Prompt.html +++ b/docs/1.0-dev/Howto/Command-Prompt.html @@ -208,7 +208,6 @@ directly the easiest way is to just wrap those with a multiple inheritance to yo

    Versions

    diff --git a/docs/1.0-dev/Howto/Coordinates.html b/docs/1.0-dev/Howto/Coordinates.html index 3c77386cb8..1101cabef3 100644 --- a/docs/1.0-dev/Howto/Coordinates.html +++ b/docs/1.0-dev/Howto/Coordinates.html @@ -429,7 +429,6 @@ square (E, G, M and O) are not in this circle. So we remove them.

    Versions

    diff --git a/docs/1.0-dev/Howto/Default-Exit-Errors.html b/docs/1.0-dev/Howto/Default-Exit-Errors.html index 7243a39a1a..3c7071aed6 100644 --- a/docs/1.0-dev/Howto/Default-Exit-Errors.html +++ b/docs/1.0-dev/Howto/Default-Exit-Errors.html @@ -204,7 +204,6 @@ matching “north” exit-command.

    Versions

    diff --git a/docs/1.0-dev/Howto/Dynamic-In-Game-Map.html b/docs/1.0-dev/Howto/Dynamic-In-Game-Map.html index d85d90eabc..dce9e23819 100644 --- a/docs/1.0-dev/Howto/Dynamic-In-Game-Map.html +++ b/docs/1.0-dev/Howto/Dynamic-In-Game-Map.html @@ -555,7 +555,6 @@ also look into up/down directions and figure out how to display that in a good w

    Versions

    diff --git a/docs/1.0-dev/Howto/Evennia-for-Diku-Users.html b/docs/1.0-dev/Howto/Evennia-for-Diku-Users.html index f6ce05f6f8..b5ff10e064 100644 --- a/docs/1.0-dev/Howto/Evennia-for-Diku-Users.html +++ b/docs/1.0-dev/Howto/Evennia-for-Diku-Users.html @@ -270,7 +270,6 @@ your mob.

    Versions

    diff --git a/docs/1.0-dev/Howto/Evennia-for-MUSH-Users.html b/docs/1.0-dev/Howto/Evennia-for-MUSH-Users.html index 582c7ed196..90f526c294 100644 --- a/docs/1.0-dev/Howto/Evennia-for-MUSH-Users.html +++ b/docs/1.0-dev/Howto/Evennia-for-MUSH-Users.html @@ -121,7 +121,7 @@ another which is again somewhat remniscent at least of the effect of < based inheritance of MUSH.

    There are other differences for sure, but that should give some feel for things. Enough with the theory. Let’s get down to more practical matters next. To install, see the -Getting Started instructions.

    +Getting Started instructions.

    A first step making things more familiar

    @@ -227,7 +227,7 @@ for-roleplaying-sessions) that can be of interest.

    An important aspect of making things more familiar for Players is adding new and tweaking existing commands. How this is done is covered by the [Tutorial on adding new commands](Adding-Command- Tutorial). You may also find it useful to shop through the evennia/contrib/ folder. The -Tutorial world is a small single-player quest you can try (it’s not very MUSH- +Tutorial world is a small single-player quest you can try (it’s not very MUSH- like but it does show many Evennia concepts in action). Beyond that there are many more tutorials to try out. If you feel you want a more visual overview you can also look at Evennia in pictures.

    @@ -293,7 +293,6 @@ or post a question in our 1.0-dev (develop branch) -
  • 0.9.5 (v0.9.5 branch)
  • diff --git a/docs/1.0-dev/Howto/Evennia-for-roleplaying-sessions.html b/docs/1.0-dev/Howto/Evennia-for-roleplaying-sessions.html index 9804d16876..0aabcdbff9 100644 --- a/docs/1.0-dev/Howto/Evennia-for-roleplaying-sessions.html +++ b/docs/1.0-dev/Howto/Evennia-for-roleplaying-sessions.html @@ -65,7 +65,7 @@ simultaneously, regardless of location.

    defaults for our particular use-case. Below we will flesh out these components from start to finish.

    Starting out

    -

    We will assume you start from scratch. You need Evennia installed, as per the Setup Quickstart +

    We will assume you start from scratch. You need Evennia installed, as per the Setup Quickstart instructions. Initialize a new game directory with evennia init <gamedirname>. In this tutorial we assume your game dir is simply named mygame. You can use the default database and keep all other settings to default for now. Familiarize yourself with the mygame folder before continuing. You might want to browse the @@ -813,7 +813,6 @@ when the message was sent.

    Versions

    diff --git a/docs/1.0-dev/Howto/Gametime-Tutorial.html b/docs/1.0-dev/Howto/Gametime-Tutorial.html index 2c1d07a775..2a4c36e2a7 100644 --- a/docs/1.0-dev/Howto/Gametime-Tutorial.html +++ b/docs/1.0-dev/Howto/Gametime-Tutorial.html @@ -375,7 +375,6 @@ same way as described for the default one above.

    Versions

    diff --git a/docs/1.0-dev/Howto/Help-System-Tutorial.html b/docs/1.0-dev/Howto/Help-System-Tutorial.html index e50d35a59e..80ec62067f 100644 --- a/docs/1.0-dev/Howto/Help-System-Tutorial.html +++ b/docs/1.0-dev/Howto/Help-System-Tutorial.html @@ -568,7 +568,6 @@ themselves links to display their details.

    Versions

    diff --git a/docs/1.0-dev/Howto/Howto-Overview.html b/docs/1.0-dev/Howto/Howto-Overview.html index ab53ac7843..cca5137b4a 100644 --- a/docs/1.0-dev/Howto/Howto-Overview.html +++ b/docs/1.0-dev/Howto/Howto-Overview.html @@ -65,7 +65,7 @@ in mind for your own game, this will give you a good start.

    1. Introduction & Overview

    2. Building stuff

    3. -
    4. The Tutorial World

    5. +
    6. The Tutorial World

    7. Python basics

    8. Game dir overview

    9. Python classes and objects

    10. @@ -246,7 +246,6 @@ in mind for your own game, this will give you a good start.

      Versions

      diff --git a/docs/1.0-dev/Howto/Manually-Configuring-Color.html b/docs/1.0-dev/Howto/Manually-Configuring-Color.html index e31835e894..9beac39ffb 100644 --- a/docs/1.0-dev/Howto/Manually-Configuring-Color.html +++ b/docs/1.0-dev/Howto/Manually-Configuring-Color.html @@ -248,7 +248,6 @@ regardless of if Evennia thinks their client supports it or not.

      Versions

      diff --git a/docs/1.0-dev/Howto/Mass-and-weight-for-objects.html b/docs/1.0-dev/Howto/Mass-and-weight-for-objects.html index 5c38e71409..c36cedfd28 100644 --- a/docs/1.0-dev/Howto/Mass-and-weight-for-objects.html +++ b/docs/1.0-dev/Howto/Mass-and-weight-for-objects.html @@ -180,7 +180,6 @@ the following message in the elevator’s appearance: 1.0-dev (develop branch) -
    11. 0.9.5 (v0.9.5 branch)
    12. diff --git a/docs/1.0-dev/Howto/NPC-shop-Tutorial.html b/docs/1.0-dev/Howto/NPC-shop-Tutorial.html index 1a1efa9bcb..0264865d99 100644 --- a/docs/1.0-dev/Howto/NPC-shop-Tutorial.html +++ b/docs/1.0-dev/Howto/NPC-shop-Tutorial.html @@ -414,7 +414,6 @@ it well stocked.

      Versions

      diff --git a/docs/1.0-dev/Howto/Parsing-commands-tutorial.html b/docs/1.0-dev/Howto/Parsing-commands-tutorial.html index db4073c396..c1c4137626 100644 --- a/docs/1.0-dev/Howto/Parsing-commands-tutorial.html +++ b/docs/1.0-dev/Howto/Parsing-commands-tutorial.html @@ -803,7 +803,6 @@ code.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/Adding-Commands.html b/docs/1.0-dev/Howto/Starting/Part1/Adding-Commands.html index b974e696b5..297dbac30d 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Adding-Commands.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Adding-Commands.html @@ -484,7 +484,6 @@ get into how we replace and extend Evennia’s default Commands.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/Building-Quickstart.html b/docs/1.0-dev/Howto/Starting/Part1/Building-Quickstart.html index ad93d8e665..54aa26ec12 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Building-Quickstart.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Building-Quickstart.html @@ -19,7 +19,7 @@ - + diff --git a/docs/1.0-dev/Howto/Starting/Part1/Django-queries.html b/docs/1.0-dev/Howto/Starting/Part1/Django-queries.html index fd492bfb8e..da84221d37 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Django-queries.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Django-queries.html @@ -487,7 +487,6 @@ to understand how to plan what our tutorial game will be about.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/Evennia-Library-Overview.html b/docs/1.0-dev/Howto/Starting/Part1/Evennia-Library-Overview.html index 8a85998da7..bf3dda861e 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Evennia-Library-Overview.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Evennia-Library-Overview.html @@ -67,7 +67,7 @@ for each thing.

      what you can download from us. The github repo is also searchable.

      Finally, you can clone the evennia repo to your own computer and read the sources locally. This is necessary if you want to help with Evennia’s development itself. See the -extended install instructions if you want to do this.

      +extended install instructions if you want to do this.

      Where is it?

      If Evennia is installed, you can import from it simply with

      @@ -237,7 +237,6 @@ to look it up in the docs:

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/Gamedir-Overview.html b/docs/1.0-dev/Howto/Starting/Part1/Gamedir-Overview.html index 96beb92376..82d73ace92 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Gamedir-Overview.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Gamedir-Overview.html @@ -263,7 +263,7 @@ people change and re-structure this in various ways to better fit their ideas.
    13. batch_cmds.ev - This is an .ev file, which is essentially just a list of Evennia commands to execute in sequence. This one is empty and ready to expand on. The -Tutorial World was built with such a batch-file.

    14. +Tutorial World was built with such a batch-file.

    15. prototypes.py - A prototype is a way to easily vary objects without changing their base typeclass. For example, one could use prototypes to tell that Two goblins, while both of the class ‘Goblin’ (so they follow the same code logic), should have different @@ -335,7 +335,6 @@ equipment, stats and looks.

    16. Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/Learning-Typeclasses.html b/docs/1.0-dev/Howto/Starting/Part1/Learning-Typeclasses.html index 4138dafc04..850163a308 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Learning-Typeclasses.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Learning-Typeclasses.html @@ -731,7 +731,6 @@ this tutorial. But that’s enough of them for now. It’s time to take some act

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/More-on-Commands.html b/docs/1.0-dev/Howto/Starting/Part1/More-on-Commands.html index a1737d95f1..27472e8300 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/More-on-Commands.html +++ b/docs/1.0-dev/Howto/Starting/Part1/More-on-Commands.html @@ -200,7 +200,7 @@ You hit smaug with sword!

      Adding a Command to an object

      The commands of a cmdset attached to an object with obj.cmdset.add() will by default be made available to that object but also to those in the same location as that object. If you did the Building introduction -you’ve seen an example of this with the “Red Button” object. The Tutorial world +you’ve seen an example of this with the “Red Button” object. The Tutorial world also has many examples of objects with commands on them.

      To show how this could work, let’s put our ‘hit’ Command on our simple sword object from the previous section.

      > self.search("sword").cmdset.add("commands.mycommands.MyCmdSet", persistent=True)
      @@ -607,7 +607,6 @@ command on ourselves.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/Python-basic-introduction.html b/docs/1.0-dev/Howto/Starting/Part1/Python-basic-introduction.html index 08db2998b0..8016774ee4 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Python-basic-introduction.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Python-basic-introduction.html @@ -20,7 +20,7 @@ - + @@ -757,7 +756,7 @@ Now let’s look at the rest of the stuff you’ve got going on inside that next |
    17. - previous |
    18. diff --git a/docs/1.0-dev/Howto/Starting/Part1/Python-classes-and-objects.html b/docs/1.0-dev/Howto/Starting/Part1/Python-classes-and-objects.html index b44b83ae62..42e11b2a5d 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Python-classes-and-objects.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Python-classes-and-objects.html @@ -503,7 +503,6 @@ provides. But first we need to learn just where to find everything.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/Searching-Things.html b/docs/1.0-dev/Howto/Starting/Part1/Searching-Things.html index c668820504..5bd2c2ea88 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Searching-Things.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Searching-Things.html @@ -377,7 +377,6 @@ Django queries and querysets in earnest.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part1/Starting-Part1.html b/docs/1.0-dev/Howto/Starting/Part1/Starting-Part1.html index 7a6fa0a46c..114b528af7 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Starting-Part1.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Starting-Part1.html @@ -20,7 +20,7 @@ - +

      Set up a game dir for the tutorial

      -

      Next you should make sure you have installed Evennia. If you followed the instructions +

      Next you should make sure you have installed Evennia. If you followed the instructions you will already have created a game-dir. You could use that for this tutorial or you may want to do the tutorial in its own, isolated game dir; it’s up to you.

        @@ -211,8 +211,8 @@ the log again just run

      Previous topic

      -

      Setup quickstart

      +

      Evennia Introduction

      Next topic

      Using the game and building stuff

      @@ -236,7 +236,6 @@ the log again just run

      Versions

      @@ -256,7 +255,7 @@ the log again just run

      next |
    19. - previous |
    20. diff --git a/docs/1.0-dev/Howto/Starting/Part1/Tutorial-World-Introduction.html b/docs/1.0-dev/Howto/Starting/Part1/Tutorial-World.html similarity index 89% rename from docs/1.0-dev/Howto/Starting/Part1/Tutorial-World-Introduction.html rename to docs/1.0-dev/Howto/Starting/Part1/Tutorial-World.html index 5e201500a3..75640f8643 100644 --- a/docs/1.0-dev/Howto/Starting/Part1/Tutorial-World-Introduction.html +++ b/docs/1.0-dev/Howto/Starting/Part1/Tutorial-World.html @@ -18,9 +18,7 @@ - - - + @@ -239,14 +223,7 @@ move on with how to access this power through code.

    21. modules |
    22. -
    23. - next |
    24. -
    25. - previous |
    26. -
      develop branch
      diff --git a/docs/1.0-dev/Howto/Starting/Part2/Game-Planning.html b/docs/1.0-dev/Howto/Starting/Part2/Game-Planning.html index b10cad2e44..e6068479f0 100644 --- a/docs/1.0-dev/Howto/Starting/Part2/Game-Planning.html +++ b/docs/1.0-dev/Howto/Starting/Part2/Game-Planning.html @@ -324,7 +324,6 @@ have made their dream game a reality!

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part2/Planning-Some-Useful-Contribs.html b/docs/1.0-dev/Howto/Starting/Part2/Planning-Some-Useful-Contribs.html index 15fef699d0..d63ed2d65d 100644 --- a/docs/1.0-dev/Howto/Starting/Part2/Planning-Some-Useful-Contribs.html +++ b/docs/1.0-dev/Howto/Starting/Part2/Planning-Some-Useful-Contribs.html @@ -333,7 +333,6 @@ back to your planning and adjust it as you learn what works and what does not.Versions diff --git a/docs/1.0-dev/Howto/Starting/Part2/Planning-The-Tutorial-Game.html b/docs/1.0-dev/Howto/Starting/Part2/Planning-The-Tutorial-Game.html index e2a097a8f3..938031104d 100644 --- a/docs/1.0-dev/Howto/Starting/Part2/Planning-The-Tutorial-Game.html +++ b/docs/1.0-dev/Howto/Starting/Part2/Planning-The-Tutorial-Game.html @@ -558,7 +558,6 @@ to code themselves. So in the next lesson we will check out what help we have fr

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part2/Planning-Where-Do-I-Begin.html b/docs/1.0-dev/Howto/Starting/Part2/Planning-Where-Do-I-Begin.html index 6b6760cab2..ad7298ff4d 100644 --- a/docs/1.0-dev/Howto/Starting/Part2/Planning-Where-Do-I-Begin.html +++ b/docs/1.0-dev/Howto/Starting/Part2/Planning-Where-Do-I-Begin.html @@ -248,7 +248,6 @@ then try to answer those questions for the sake of creating our little tutorial

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part2/Starting-Part2.html b/docs/1.0-dev/Howto/Starting/Part2/Starting-Part2.html index 131a6c5bce..fdbd40a6e1 100644 --- a/docs/1.0-dev/Howto/Starting/Part2/Starting-Part2.html +++ b/docs/1.0-dev/Howto/Starting/Part2/Starting-Part2.html @@ -138,7 +138,6 @@ and “what to think about” when creating a multiplayer online text game.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part3/A-Sittable-Object.html b/docs/1.0-dev/Howto/Starting/Part3/A-Sittable-Object.html index 12f17882ba..15745d8f5a 100644 --- a/docs/1.0-dev/Howto/Starting/Part3/A-Sittable-Object.html +++ b/docs/1.0-dev/Howto/Starting/Part3/A-Sittable-Object.html @@ -844,7 +844,6 @@ mixing them, or even try a third solution that better fits what you have in mind

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part3/Implementing-a-game-rule-system.html b/docs/1.0-dev/Howto/Starting/Part3/Implementing-a-game-rule-system.html index 4fd6ad2f35..950d31cb70 100644 --- a/docs/1.0-dev/Howto/Starting/Part3/Implementing-a-game-rule-system.html +++ b/docs/1.0-dev/Howto/Starting/Part3/Implementing-a-game-rule-system.html @@ -355,7 +355,6 @@ your rulesVersions diff --git a/docs/1.0-dev/Howto/Starting/Part3/Starting-Part3.html b/docs/1.0-dev/Howto/Starting/Part3/Starting-Part3.html index c334115e5b..f5f630f973 100644 --- a/docs/1.0-dev/Howto/Starting/Part3/Starting-Part3.html +++ b/docs/1.0-dev/Howto/Starting/Part3/Starting-Part3.html @@ -138,7 +138,6 @@ with using Evennia. This be of much use when doing your own thing later.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part3/Turn-based-Combat-System.html b/docs/1.0-dev/Howto/Starting/Part3/Turn-based-Combat-System.html index bfaf974557..9561c9b51f 100644 --- a/docs/1.0-dev/Howto/Starting/Part3/Turn-based-Combat-System.html +++ b/docs/1.0-dev/Howto/Starting/Part3/Turn-based-Combat-System.html @@ -598,7 +598,6 @@ show others what’s going on.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part3/Tutorial-for-basic-MUSH-like-game.html b/docs/1.0-dev/Howto/Starting/Part3/Tutorial-for-basic-MUSH-like-game.html index 25aa673fca..6baf99b1e1 100644 --- a/docs/1.0-dev/Howto/Starting/Part3/Tutorial-for-basic-MUSH-like-game.html +++ b/docs/1.0-dev/Howto/Starting/Part3/Tutorial-for-basic-MUSH-like-game.html @@ -652,7 +652,7 @@ generation.

      The simple “Power” game mechanic should be easily expandable to something more full-fledged and useful, same is true for the combat score principle. The +attack could be made to target a specific player (or npc) and automatically compare their relevant attributes to determine a result.

      -

      To continue from here, you can take a look at the Tutorial World. For +

      To continue from here, you can take a look at the Tutorial World. For more specific ideas, see the other tutorials and hints as well as the Evennia Component overview.

      @@ -725,7 +725,6 @@ as the 1.0-dev (develop branch) -
    27. 0.9.5 (v0.9.5 branch)
    28. diff --git a/docs/1.0-dev/Howto/Starting/Part4/Starting-Part4.html b/docs/1.0-dev/Howto/Starting/Part4/Starting-Part4.html index 60c36fce4e..dfad1f7b83 100644 --- a/docs/1.0-dev/Howto/Starting/Part4/Starting-Part4.html +++ b/docs/1.0-dev/Howto/Starting/Part4/Starting-Part4.html @@ -105,7 +105,6 @@ and batchcode processors.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part5/Add-a-simple-new-web-page.html b/docs/1.0-dev/Howto/Starting/Part5/Add-a-simple-new-web-page.html index 04f45e5dd3..15acc26f00 100644 --- a/docs/1.0-dev/Howto/Starting/Part5/Add-a-simple-new-web-page.html +++ b/docs/1.0-dev/Howto/Starting/Part5/Add-a-simple-new-web-page.html @@ -178,7 +178,6 @@ to.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part5/Starting-Part5.html b/docs/1.0-dev/Howto/Starting/Part5/Starting-Part5.html index 2a12053162..7f762220c6 100644 --- a/docs/1.0-dev/Howto/Starting/Part5/Starting-Part5.html +++ b/docs/1.0-dev/Howto/Starting/Part5/Starting-Part5.html @@ -103,7 +103,6 @@ to bring your game online so you can invite your first players.

      Versions

      diff --git a/docs/1.0-dev/Howto/Starting/Part5/Web-Tutorial.html b/docs/1.0-dev/Howto/Starting/Part5/Web-Tutorial.html index 8423831df0..179479c663 100644 --- a/docs/1.0-dev/Howto/Starting/Part5/Web-Tutorial.html +++ b/docs/1.0-dev/Howto/Starting/Part5/Web-Tutorial.html @@ -200,7 +200,6 @@ works and what possibilities exist.

      Versions

      diff --git a/docs/1.0-dev/Howto/Static-In-Game-Map.html b/docs/1.0-dev/Howto/Static-In-Game-Map.html index c3b2262e4b..21a521fe69 100644 --- a/docs/1.0-dev/Howto/Static-In-Game-Map.html +++ b/docs/1.0-dev/Howto/Static-In-Game-Map.html @@ -483,7 +483,6 @@ Tutorial), Versions diff --git a/docs/1.0-dev/Howto/Tutorial-Aggressive-NPCs.html b/docs/1.0-dev/Howto/Tutorial-Aggressive-NPCs.html index 6fba951ca0..fd041932c6 100644 --- a/docs/1.0-dev/Howto/Tutorial-Aggressive-NPCs.html +++ b/docs/1.0-dev/Howto/Tutorial-Aggressive-NPCs.html @@ -192,7 +192,6 @@ AI code).

      Versions

      diff --git a/docs/1.0-dev/Howto/Tutorial-NPCs-listening.html b/docs/1.0-dev/Howto/Tutorial-NPCs-listening.html index f61a7abdc5..954d96dfaf 100644 --- a/docs/1.0-dev/Howto/Tutorial-NPCs-listening.html +++ b/docs/1.0-dev/Howto/Tutorial-NPCs-listening.html @@ -190,7 +190,6 @@ Which way to go depends on the design requirements of your particular game.

      Versions

      diff --git a/docs/1.0-dev/Howto/Tutorial-Tweeting-Game-Stats.html b/docs/1.0-dev/Howto/Tutorial-Tweeting-Game-Stats.html index f0258d8900..545e5d509a 100644 --- a/docs/1.0-dev/Howto/Tutorial-Tweeting-Game-Stats.html +++ b/docs/1.0-dev/Howto/Tutorial-Tweeting-Game-Stats.html @@ -174,7 +174,6 @@ as mygame/typeclass

      Versions

      diff --git a/docs/1.0-dev/Howto/Tutorial-Vehicles.html b/docs/1.0-dev/Howto/Tutorial-Vehicles.html index 6bc2501a60..ffa1629aaf 100644 --- a/docs/1.0-dev/Howto/Tutorial-Vehicles.html +++ b/docs/1.0-dev/Howto/Tutorial-Vehicles.html @@ -476,7 +476,6 @@ direction to which room it goes.

      Versions

      diff --git a/docs/1.0-dev/Howto/Understanding-Color-Tags.html b/docs/1.0-dev/Howto/Understanding-Color-Tags.html index 383abca184..1a2e522098 100644 --- a/docs/1.0-dev/Howto/Understanding-Color-Tags.html +++ b/docs/1.0-dev/Howto/Understanding-Color-Tags.html @@ -251,7 +251,6 @@ push it over the limit, so to speak.

      Versions

      diff --git a/docs/1.0-dev/Howto/Weather-Tutorial.html b/docs/1.0-dev/Howto/Weather-Tutorial.html index 31ea7990d1..e5090fb7e8 100644 --- a/docs/1.0-dev/Howto/Weather-Tutorial.html +++ b/docs/1.0-dev/Howto/Weather-Tutorial.html @@ -128,7 +128,6 @@ weather came before it. Expanding it to be more realistic is a useful exercise.<

      Versions

      diff --git a/docs/1.0-dev/Howto/Web-Character-Generation.html b/docs/1.0-dev/Howto/Web-Character-Generation.html index d342925269..5e394229eb 100644 --- a/docs/1.0-dev/Howto/Web-Character-Generation.html +++ b/docs/1.0-dev/Howto/Web-Character-Generation.html @@ -733,7 +733,6 @@ to see what happens. And do the same while checking the checkbox!

      Versions

      diff --git a/docs/1.0-dev/Howto/Web-Character-View-Tutorial.html b/docs/1.0-dev/Howto/Web-Character-View-Tutorial.html index 16d383cea3..e5796826be 100644 --- a/docs/1.0-dev/Howto/Web-Character-View-Tutorial.html +++ b/docs/1.0-dev/Howto/Web-Character-View-Tutorial.html @@ -286,7 +286,6 @@ here.

      Versions

      diff --git a/docs/1.0-dev/Licensing.html b/docs/1.0-dev/Licensing.html index 0321e51ea2..220959ba33 100644 --- a/docs/1.0-dev/Licensing.html +++ b/docs/1.0-dev/Licensing.html @@ -104,7 +104,6 @@ as Evennia itself, unless the individual contributor has specifically defined ot

      Versions

      diff --git a/docs/1.0-dev/Links.html b/docs/1.0-dev/Links.html index edb9f0ce43..ee701393c3 100644 --- a/docs/1.0-dev/Links.html +++ b/docs/1.0-dev/Links.html @@ -310,7 +310,6 @@ free online programming curriculum for different skill levels

      Versions

      diff --git a/docs/1.0-dev/Setup/Apache-Config.html b/docs/1.0-dev/Setup/Apache-Config.html index b7bff228f4..286cf0847f 100644 --- a/docs/1.0-dev/Setup/Apache-Config.html +++ b/docs/1.0-dev/Setup/Apache-Config.html @@ -283,7 +283,6 @@ port but this should be applicable also to other types of proxies (like nginx).<

      Versions

      diff --git a/docs/1.0-dev/Setup/Changelog.html b/docs/1.0-dev/Setup/Changelog.html new file mode 100644 index 0000000000..d4dbb1774e --- /dev/null +++ b/docs/1.0-dev/Setup/Changelog.html @@ -0,0 +1,1003 @@ + + + + + + + + + Changelog — Evennia 1.0-dev documentation + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Changelog

      +
      +

      Evennia 1.0

      +
      +

      Not released yet +2019-2022 develop branch (WIP)

      +
      +

      Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10

      +
        +
      • New drop:holds() lock default to limit dropping nonsensical things. Access check +defaults to True for backwards-compatibility in 0.9, will be False in 1.0

      • +
      • REST API allows you external access to db objects through HTTP requests (Tehom)

      • +
      • Object.normalize_name and .validate_name added to (by default) enforce latinify +on character name and avoid potential exploits using clever Unicode chars (trhr)

      • +
      • New utils.format_grid for easily displaying long lists of items in a block.

      • +
      • 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 +replaceable via settings.py for modding enthusiasts. (volund)

      • +
      • 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 multi-match 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.

      • +
      • Add tags.has() method for checking if an object has a tag or tags (PR by ChrisLR)

      • +
      • Make IP throttle use Django-based cache system for optional persistence (PR by strikaco)

      • +
      • Renamed Tutorial classes “Weapon” and “WeaponRack” to “TutorialWeapon” and +“TutorialWeaponRack” to prevent collisions with classes in mygame

      • +
      • New crafting contrib, adding a full crafting subsystem (Griatch 2020)

      • +
      • The rplanguage contrib now auto-capitalizes sentences and retains ellipsis (…). This +change means that proper nouns at the start of sentences will not be treated as nouns.

      • +
      • Make MuxCommand lhs/rhslist always be lists, also if empty (used to be the empty string)

      • +
      • Fix typo in UnixCommand contrib, where help was given as --hel.

      • +
      • Latin (la) i18n translation (jamalainm)

      • +
      • Made the evennia dir possible to use without gamedir for purpose of doc generation.

      • +
      • Make Scripts’ timer component independent from script object deletion; can now start/stop +timer without deleting Script. The .persistent flag now only controls if timer survives +reload - Script has to be removed with .delete() like other typeclassed entities.

      • +
      • Add utils.repeat and utils.unrepeat as shortcuts to TickerHandler add/remove, similar +to how utils.delay is a shortcut for TaskHandler add.

      • +
      • Refactor the classic red_button example to use utils.delay/repeat and modern recommended +code style and paradigms instead of relying on Scripts for everything.

      • +
      • Expand CommandTest with ability to check multiple message-receivers; inspired by PR by +user davewiththenicehat. Also add new doc string.

      • +
      • Add central FuncParser as a much more powerful replacement for the old parse_inlinefunc +function.

      • +
      • Add evennia/utils/verb_conjugation for automatic verb conjugation (English only). This +is useful for implementing actor-stance emoting for sending a string to different targets.

      • +
      • New version of Italian translation (rpolve)

      • +
      • utils.evmenu.ask_yes_no is a helper function that makes it easy to ask a yes/no question +to the user and respond to their input. This complements the existing get_input helper.

      • +
      • Allow sending messages with page/tell without a = if target name contains no spaces.

      • +
      • New FileHelpStorage system allows adding help entries via external files.

      • +
      • sethelp command now warns if shadowing other help-types when creating a new +entry.

      • +
      • Help command now uses view lock to determine if cmd/entry shows in index and +read lock to determine if it can be read. It used to be view in the role +of the latter. Migration swaps these around.

      • +
      • In modules given by settings.PROTOTYPE_MODULES, spawner will now first look for a global +list PROTOTYPE_LIST of dicts before loading all dicts in the module as prototypes.

      • +
      • New Channel-System using the channel command and nicks. Removed the ChannelHandler and the +concept of a dynamically created ChannelCmdSet.

      • +
      • Add Msg.db_receiver_external field to allowe external, string-id message-receivers.

      • +
      • Renamed app.css to website.css for consistency. Removed old prosimii-css files.

      • +
      • Remove mygame/web/static_overrides and -template_overrides, reorganize website/admin/client/api +into a more consistent structure for overriding. Expanded webpage documentation considerably.

      • +
      • REST API list-view was shortened (#2401). New CSS/HTML. Add ReDoc for API autodoc page.

      • +
      • Update and fix dummyrunner with cleaner code and setup.

      • +
      • Made iter_to_str format prettier strings, using Oxford comma.

      • +
      • Added an MXP anchor tag to also support clickable web links.

      • +
      • New tasks command for managing tasks started with utils.delay (PR by davewiththenicehat)

      • +
      • Make help index output clickable for webclient/clients with MXP (PR by davewiththenicehat)

      • +
      • Custom evennia launcher commands (e.g. evennia mycmd foo bar). Add new commands as callables +accepting *args, as settings.EXTRA_LAUNCHER_COMMANDS = {'mycmd': 'path.to.callable', ...}.

      • +
      • New XYZGrid contrib, adding x,y,z grid coordinates with in-game map and +pathfinding. Controlled outside of the game via custom evennia launcher command.

      • +
      • Script.delete has new kwarg stop_task=True, that can be used to avoid +infinite recursion when wanting to set up Script to delete-on-stop.

      • +
      • Command executions now done on copies to make sure yield don’t cause crossovers. Add +Command.retain_instance flag for reusing the same command instance.

      • +
      • The typeclass command will now correctly search the correct database-table for the target +obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc).

      • +
      • Merged script and scripts commands into one, for both managing global- and +on-object Scripts. Moved CmdScripts and CmdObjects to commands/default/building.py.

      • +
      • Keep GMCP function case if outputfunc starts with capital letter (so cmd_name -> Cmd.Name +but Cmd_nAmE -> Cmd.nAmE). This helps e.g Mudlet’s legacy Client_GUI implementation)

      • +
      • Prototypes now allow setting prototype_parent directly to a prototype-dict. +This makes it easier when dynamically building in-module prototypes.

      • +
      • RPSystem contrib was expanded to support case, so /tall becomes ‘tall man’ +while /Tall becomes ‘Tall man’. One can turn this off if wanting the old style.

      • +
      • Change EvTable fixed-height rebalance algorithm to fill with empty lines at end of +column instead of inserting rows based on cell-size (could be mistaken for a bug).

      • +
      • Split return_appearance hook with helper methods and have it use a template +string in order to make it easier to override.

      • +
      • Add validation question to default account creation.

      • +
      • Add LOCALECHO client option to add server-side echo for clients that does +not support this (useful for getting a complete log).

      • +
      • Make @lazy_property decorator create read/delete-protected properties. This is +because it’s used for handlers, and e.g. self.locks=[] is a common beginner mistake.

      • +
      • Add $pron() inlinefunc for pronoun parsing in actor-stance strings using +msg_contents.

      • +
      • Update defauklt website to show Telnet/SSL/SSH connect info. Added new +SERVER_HOSTNAME setting for use in the server:port stanza.

      • +
      • Changed all at_before/after_* hooks to at_pre/post_* for consistency +across Evennia (the old names still work but are deprecated)

      • +
      • Change settings.COMMAND_DEFAULT_ARG_REGEX default from None to a regex meaning that +a space or / must separate the cmdname and args. This better fits common expectations.

      • +
      • Add confirmation question to ban/unban commands.

      • +
      • Check new teleport and teleport_here lock-types in teleport command to optionally +allow to limit teleportation of an object or to a specific destination.

      • +
      • Add settings.MXP_ENABLED=True and settings.MXP_OUTGOING_ONLY=True as sane defaults, +to avoid known security issues with players entering MXP links.

      • +
      • Add browser name to webclient CLIENT_NAME in session.protocol_flags, e.g. +"Evennia webclient (websocket:firefox)" or "evennia webclient (ajax:chrome)".

      • +
      • TagHandler.add/has(tag=...) kwarg changed to add/has(key=...) for consistency +with other handlers.

      • +
      • Make DefaultScript.delete, DefaultChannel.delete and DefaultAccount.delete return +bool True/False if deletion was successful (like DefaultObject.delete before them)

      • +
      • contrib.custom_gametime days/weeks/months now always starts from 1 (to match +the standard calendar form … there is no month 0 every year after all).

      • +
      • AttributeProperty/NAttributeProperty to allow managing Attributes/NAttributes +on typeclasses in the same way as Django fields.

      • +
      • Give build/system commands a @name to fall back to if the non-@ name is used +by another command (like open and @open. If no duplicate, @ is optional.

      • +
      • Move legacy channel-management commands (ccreate, addcom etc) to a contrib +since their work is now fully handled by the single channel command.

      • +
      • Expand examine command’s code to much more extensible and modular. Show +attribute categories and value types (when not strings).

      • +
      • AttributeHandler.remove(key, return_exception=False, category=None, ...) changed +to .remove(key, category=None, return_exception=False, ...) for consistency.

      • +
      • New command cooldown contrib for making it easier to manage commands using +dynamic cooldowns between uses (owllex)

      • +
      • Restructured contrib/ folder, placing all contribs as separate packages under +subfolders. All imports will need to be updated.

      • +
      • Made MonitorHandler.add/remove support category for monitoring Attributes +with a category (before only key was used, ignoring category entirely).

      • +
      • Move create_* functions into db managers, leaving utils.create only being +wrapper functions (consistent with utils.search). No change of api otherwise.

      • +
      • Add support for $dbref() and $search when assigning an Attribute value +with the set command. This allows assigning real objects from in-game.

      • +
      • Add ability to examine /script and /channel entities with examine command.

      • +
      • Homogenize manager search methods to return querysets and not lists.

      • +
      • Restructure unit tests to always honor default settings; make new parents in +on location for easy use in game dir.

      • +
      +
      +
      +

      Evennia 0.9.5

      +
      +

      2019-2020 +Released 2020-11-14. +Transitional release, including new doc system.

      +
      +

      Backported from develop: Python 3.8, 3.9 support. Django 3.2+ support, Twisted 21+ support.

      +
        +
      • is_typeclass(obj (Object), exact (bool)) now defaults to exact=False

      • +
      • py command now reroutes stdout to output results in-game client. py +without arguments starts a full interactive Python console.

      • +
      • Webclient default to a single input pane instead of two. Now defaults to no help-popup.

      • +
      • Webclient fix of prompt display

      • +
      • Webclient multimedia support for relaying images, video and sounds via +.msg(image=URL), .msg(video=URL) +and .msg(audio=URL)

      • +
      • Add Spanish translation (fermuch)

      • +
      • Expand GLOBAL_SCRIPTS container to always start scripts and to include all +global scripts regardless of how they were created.

      • +
      • Change settings to always use lists instead of tuples, to make mutable +settings easier to add to. (#1912)

      • +
      • Make new CHANNEL_MUDINFO setting for specifying the mudinfo channel

      • +
      • Make CHANNEL_CONNECTINFO take full channel definition

      • +
      • Make DEFAULT_CHANNELS list auto-create channels missing at reload

      • +
      • Webclient ANSI->HTML parser updated. Webclient line width changed from 1.6em to 1.1em +to better make ANSI graphics look the same as for third-party clients

      • +
      • AttributeHandler.get(return_list=True) will return [] if there are no +Attributes instead of [None].

      • +
      • Remove pillow requirement (install especially if using imagefield)

      • +
      • Add Simplified Korean translation (aceamro)

      • +
      • Show warning on start -l if settings contains values unsafe for production.

      • +
      • Make code auto-formatted with Black.

      • +
      • Make default set command able to edit nested structures (PR by Aaron McMillan)

      • +
      • Allow running Evennia test suite from core repo with make test.

      • +
      • Return store_key from TickerHandler.add and add store_key as a kwarg to +the TickerHandler.remove method. This makes it easier to manage tickers.

      • +
      • EvMore auto-justify now defaults to False since this works better with all types +of texts (such as tables). New justify bool. Old justify_kwargs remains +but is now only used to pass extra kwargs into the justify function.

      • +
      • EvMore text argument can now also be a list or a queryset. Querysets will be +sliced to only return the required data per page.

      • +
      • Improve performance of find and objects commands on large data sets (strikaco)

      • +
      • New CHANNEL_HANDLER_CLASS setting allows for replacing the ChannelHandler entirely.

      • +
      • Made py interactive mode support regular quit() and more verbose.

      • +
      • Made Account.options.get accept default=None kwarg to mimic other uses of get. Set +the new raise_exception boolean if ranting to raise KeyError on a missing key.

      • +
      • Moved behavior of unmodified Command and MuxCommand .func() to new +.get_command_info() method for easier overloading and access. (Volund)

      • +
      • Removed unused CYCLE_LOGFILES setting. Added SERVER_LOG_DAY_ROTATION +and SERVER_LOG_MAX_SIZE (and equivalent for PORTAL) to control log rotation.

      • +
      • Addded inside_rec lockfunc - if room is locked, the normal inside() lockfunc will +fail e.g. for your inventory objs (since their loc is you), whereas this will pass.

      • +
      • RPSystem contrib’s CmdRecog will now list all recogs if no arg is given. Also multiple +bugfixes.

      • +
      • Remove dummy@example.com as a default account email when unset, a string is no longer +required by Django.

      • +
      • Fixes to spawn, make updating an existing prototype/object work better. Add /raw switch +to spawn command to extract the raw prototype dict for manual editing.

      • +
      • list_to_string is now iter_to_string (but old name still works as legacy alias). It will +now accept any input, including generators and single values.

      • +
      • EvTable should now correctly handle columns with wider asian-characters in them.

      • +
      • Update Twisted requirement to >=2.3.0 to close security vulnerability

      • +
      • Add $random inlinefunc, supports minval,maxval arguments that can be ints and floats.

      • +
      • Add evennia.utils.inlinefuncs.raw(<str>) as a helper to escape inlinefuncs in a string.

      • +
      • Make CmdGet/Drop/Give give proper error if obj.move_to returns False.

      • +
      • Make Object/Room/Exit.create’s account argument optional. If not given, will set perms +to that of the object itself (along with normal Admin/Dev permission).

      • +
      • Make INLINEFUNC_STACK_MAXSIZE default visible in settings_default.py.

      • +
      • Change how ic finds puppets; non-priveleged users will use _playable_characters list as +candidates, Builders+ will use list, local search and only global search if no match found.

      • +
      • Make cmd.at_post_cmd() always run after cmd.func(), even when the latter uses delays +with yield.

      • +
      • EvMore support for db queries and django paginators as well as easier to override for custom +pagination (e.g. to create EvTables for every page instead of splittine one table)

      • +
      • Using EvMore pagination, dramatically improves performance of spawn/list and scripts listings +(100x speed increase for displaying 1000+ prototypes/scripts).

      • +
      • EvMenu now uses the more logically named .ndb._evmenu instead of .ndb._menutree to store itself. +Both still work for backward compatibility, but _menutree is deprecated.

      • +
      • EvMenu.msg(txt) added as a central place to send text to the user, makes it easier to override. +Default EvMenu.msg sends with OOB type=“menu” for use with OOB and webclient pane-redirects.

      • +
      • New EvMenu templating system for quickly building simpler EvMenus without as much code.

      • +
      • Add Command.client_height() method to match existing .client_width (stricako)

      • +
      • Include more Web-client info in session.protocol_flags.

      • +
      • Fixes in multi-match situations - don’t allow finding/listing multimatches for 3-box when +only two boxes in location.

      • +
      • Fix for TaskHandler with proper deferred returns/ability to cancel etc (PR by davewiththenicehat)

      • +
      • Add PermissionHandler.check method for straight string perm-checks without needing lockstrings.

      • +
      • Add evennia.utils.utils.strip_unsafe_input for removing html/newlines/tags from user input. The +INPUT_CLEANUP_BYPASS_PERMISSIONS is a list of perms that bypass this safety stripping.

      • +
      • Make default set and examine commands aware of Attribute categories.

      • +
      +
      +
      +

      Evennia 0.9

      +
      +

      2018-2019 +Released Oct 2019

      +
      +
      +

      Distribution

      +
        +
      • New requirement: Python 3.7 (py2.7 support removed)

      • +
      • Django 2.1

      • +
      • Twisted 19.2.1

      • +
      • Autobahn websockets (removed old tmwx)

      • +
      • Docker image updated

      • +
      +
      +
      +

      Commands

      +
        +
      • Remove @-prefix from all default commands (prefixes still work, optional)

      • +
      • Removed default @delaccount command, incorporating as @account/delete instead. Added confirmation +question.

      • +
      • Add new @force command to have another object perform a command.

      • +
      • Add the Portal uptime to the @time command.

      • +
      • Make the @link command first make a local search before a global search.

      • +
      • Have the default Unloggedin-look command look for optional connection_screen() callable in +mygame/server/conf/connection_screen.py. This allows for more flexible welcome screens +that are calculated on the fly.

      • +
      • @py command now defaults to escaping html tags in its output when viewing in the webclient. +Use new /clientraw switch to get old behavior (issue #1369).

      • +
      • Shorter and more informative, dynamic, listing of on-command vars if not +setting func() in child command class.

      • +
      • New Command helper methods

        +
          +
        • .client_width() returns client width of the session running the command.

        • +
        • .styled_table(*args, **kwargs) returns a formatted evtable styled by user’s options

        • +
        • .style_header(*args, **kwargs) creates styled header entry

        • +
        • .style_separator(*args, **kwargs) ” separator

        • +
        • .style_footer(*args, **kwargs) ” footer

        • +
        +
      • +
      +
      +
      +

      Web

      +
        +
      • Change webclient from old txws version to use more supported/feature-rich Autobahn websocket library

      • +
      +
      +

      Evennia game index

      +
        +
      • Made Evennia game index client a part of core - now configured from settings file (old configs +need to be moved)

      • +
      • The evennia connections command starts a wizard that helps you connect your game to the game index.

      • +
      • The game index now accepts games with no public telnet/webclient info (for early prototypes).

      • +
      +
      +
      +

      New golden-layout based Webclient UI (@friarzen)

      +
        +
      • Features

        +
          +
        • Much slicker behavior and more professional look

        • +
        • Allows tabbing as well as click and drag of panes in any grid position

        • +
        • Renaming tabs, assignments of data tags and output types are simple per-pane menus now

        • +
        • Any number of input panes, with separate histories

        • +
        • Button UI (disabled in JS by default)

        • +
        +
      • +
      +
      +
      +

      Web/Django standard initiative (@strikaco)

      +
        +
      • Features

        +
          +
        • Adds a series of web-based forms and generic class-based views

          +
            +
          • Accounts

            +
              +
            • Register - Enhances registration; allows optional collection of email address

            • +
            • Form - Adds a generic Django form for creating Accounts from the web

            • +
            +
          • +
          • Characters

            +
              +
            • Create - Authenticated users can create new characters from the website (requires associated form)

            • +
            • Detail - Authenticated and authorized users can view select details about characters

            • +
            • List - Authenticated and authorized users can browse a list of all characters

            • +
            • Manage - Authenticated users can edit or delete owned characters from the web

            • +
            • Form - Adds a generic Django form for creating characters from the web

            • +
            +
          • +
          • Channels

            +
              +
            • Detail - Authorized users can view channel logs from the web

            • +
            • List - Authorized users can browse a list of all channels

            • +
            +
          • +
          • Help Entries

            +
              +
            • Detail - Authorized users can view help entries from the web

            • +
            • List - Authorized users can browse a list of all help entries from the web

            • +
            +
          • +
          +
        • +
        • Navbar changes

          +
            +
          • Characters - Link to character list

          • +
          • Channels - Link to channel list

          • +
          • Help - Link to help entry list

          • +
          • Puppeting

            +
              +
            • Users can puppet their own characters within the context of the website

            • +
            +
          • +
          • Dropdown

            +
              +
            • Link to create characters

            • +
            • Link to manage characters

            • +
            • Link to quick-select puppets

            • +
            • Link to password change workflow

            • +
            +
          • +
          +
        • +
        +
      • +
      • Functions

        +
          +
        • Updates Bootstrap to v4 stable

        • +
        • Enables use of Django Messages framework to communicate with users in browser

        • +
        • Implements webclient/website _shared_login functionality as Django middleware

        • +
        • ‘account’ and ‘puppet’ are added to all request contexts for authenticated users

        • +
        • Adds unit tests for all web views

        • +
        +
      • +
      • Cosmetic

        +
          +
        • Prettifies Django ‘forgot password’ workflow (requires SMTP to actually function)

        • +
        • Prettifies Django ‘change password’ workflow

        • +
        +
      • +
      • Bugfixes

        +
          +
        • Fixes bug on login page where error messages were not being displayed

        • +
        • Remove strvalue field from admin; it made no sense to have here, being an optimization field +for internal use.

        • +
        +
      • +
      +
      +
      +
      +

      Prototypes

      +
        +
      • evennia.prototypes.save_prototype now takes the prototype as a normal +argument (prototype) instead of having to give it as **prototype.

      • +
      • evennia.prototypes.search_prototype has a new kwarg require_single=False that +raises a KeyError exception if query gave 0 or >1 results.

      • +
      • evennia.prototypes.spawner can now spawn by passing a prototype_key

      • +
      +
      +
      +

      Typeclasses

      +
        +
      • Add new methods on all typeclasses, useful specifically for object handling from the website/admin:

        +
          +
        • web_get_admin_url(): Returns the path to the object detail page in the Admin backend.

        • +
        • web_get_create_url(): Returns the path to the typeclass’ creation page on the website, if implemented.

        • +
        • web_get_absolute_url(): Returns the path to the object’s detail page on the website, if implemented.

        • +
        • web_get_update_url(): Returns the path to the object’s update page on the website, if implemented.

        • +
        • web_get_delete_url(): Returns the path to the object’s delete page on the website, if implemented.

        • +
        +
      • +
      • All typeclasses have new helper class method create, which encompasses useful functionality +that used to be embedded for example in the respective @create or @connect commands.

      • +
      • DefaultAccount now has new class methods implementing many things that used to be in unloggedin +commands (these can now be customized on the class instead):

        +
          +
        • is_banned(): Checks if a given username or IP is banned.

        • +
        • get_username_validators: Return list of validators for username validation (see +settings.AUTH_USERNAME_VALIDATORS)

        • +
        • authenticate: Method to check given username/password.

        • +
        • normalize_username: Normalizes names so (for Unicode environments) users cannot mimic existing usernames by replacing select characters with visually-similar Unicode chars.

        • +
        • validate_username: Mechanism for validating a username based on predefined Django validators.

        • +
        • validate_password: Mechanism for validating a password based on predefined Django validators.

        • +
        • set_password: Apply password to account, using validation checks.

        • +
        +
      • +
      • AttributeHandler.remove and TagHandler.remove can now be used to delete by-category. If neither +key nor category is given, they now work the same as .clear().

      • +
      +
      +
      +

      Protocols

      +
        +
      • Support for Grapevine MUD-chat network (“channels” supported)

      • +
      +
      +
      +

      Server

      +
        +
      • Convert ServerConf model to store its values as a Picklefield (same as +Attributes) instead of using a custom solution.

      • +
      • OOB: Add support for MSDP LIST, REPORT, UNREPORT commands (re-mapped to msdp_list, +msdp_report, msdp_unreport, inlinefuncs)

      • +
      • Added evennia.ANSIString to flat API.

      • +
      • Server/Portal log files now cycle to names on the form server_.log_19_03_08_ instead of server.log___19.3.8, retaining +unix file sorting order.

      • +
      • Django signals fire for important events: Puppet/Unpuppet, Object create/rename, Login, +Logout, Login fail Disconnect, Account create/rename

      • +
      +
      +
      +

      Settings

      +
        +
      • GLOBAL_SCRIPTS - dict defining typeclasses of global scripts to store on the new +evennia.GLOBAL_SCRIPTS container. These will auto-start when Evennia start and will always +exist.

      • +
      • OPTIONS_ACCOUNTS_DEFAULT - option dict with option defaults and Option classes

      • +
      • OPTION_CLASS_MODULES - classes representing an on-Account Option, on special form

      • +
      • VALIDATOR_FUNC_MODULES - (general) text validator functions, for verifying an input +is on a specific form.

      • +
      +
      +
      +

      Utils

      +
        +
      • evennia launcher now fully handles all django-admin commands, like running tests in parallel.

      • +
      • evennia.utils.create.account now also takes tags and attrs keywords.

      • +
      • evennia.utils.interactive decorator can now allow you to use yield(secs) to pause operation +in any function, not just in Command.func. Likewise, response = yield(question) will work +if the decorated function has an argument or kwarg caller.

      • +
      • Added many more unit tests.

      • +
      • Swap argument order of evennia.set_trace to set_trace(term_size=(140, 40), debugger='auto') +since the size is more likely to be changed on the command line.

      • +
      • utils.to_str(text, session=None) now acts as the old utils.to_unicode (which was removed). +This converts to the str() type (not to a byte-string as in Evennia 0.8), trying different +encodings. This function will also force-convert any object passed to it into a string (so +force_string flag was removed and assumed always set).

      • +
      • utils.to_bytes(text, session=None) replaces the old utils.to_str() functionality and converts +str to bytes.

      • +
      • evennia.MONITOR_HANDLER.all now takes keyword argument obj to only retrieve monitors from that specific +Object (rather than all monitors in the entire handler).

      • +
      • Support adding \f in command doc strings to force where EvMore puts page breaks.

      • +
      • Validation Functions now added with standard API to homogenize user input validation.

      • +
      • Option Classes added to make storing user-options easier and smoother.

      • +
      • evennia.VALIDATOR_CONTAINER and evennia.OPTION_CONTAINER added to load these.

      • +
      +
      +
      +

      Contribs

      +
        +
      • Evscaperoom - a full puzzle engine for making multiplayer escape rooms in Evennia. Used to make +the entry for the MUD-Coder’s Guild’s 2019 Game Jam with the theme “One Room”, where it ranked #1.

      • +
      • Evennia game-index client no longer a contrib - moved into server core and configured with new +setting GAME_INDEX_ENABLED.

      • +
      • The extended_room contrib saw some backwards-incompatible refactoring:

        +
          +
        • All commands now begin with CmdExtendedRoom. So before it was CmdExtendedLook, now +it’s CmdExtendedRoomLook etc.

        • +
        • The detail command was broken out of the desc command and is now a new, stand-alone command +CmdExtendedRoomDetail. This was done to make things easier to extend and to mimic how the detail +command works in the tutorial-world.

        • +
        • The detail command now also supports deleting details (like the tutorial-world version).

        • +
        • The new ExtendedRoomCmdSet includes all the extended-room commands and is now the recommended way +to install the extended-room contrib.

        • +
        +
      • +
      • Reworked menu_login contrib to use latest EvMenu standards. Now also supports guest logins.

      • +
      • Mail contrib was refactored to have optional Command classes CmdMail for OOC+IC mail (added +to the CharacterCmdSet and CmdMailCharacter for IC-only mailing between chars (added to CharacterCmdSet)

      • +
      +
      +
      +

      Translations

      +
        +
      • Simplified chinese, courtesy of user MaxAlex.

      • +
      +
      +
      +
      +

      Evennia 0.8

      +
      +

      2017-2018 +Released Nov 2018

      +
      +
      +

      Requirements

      +
        +
      • Up requirements to Django 1.11.x, Twisted 18 and pillow 5.2.0

      • +
      • Add inflect dependency for automatic pluralization of object names.

      • +
      +
      +
      +

      Server/Portal

      +
        +
      • Removed evennia_runner, completely refactor evennia_launcher.py (the ‘evennia’ program) +with different functionality).

      • +
      • Both Portal/Server are now stand-alone processes (easy to run as daemon)

      • +
      • Made Portal the AMP Server for starting/restarting the Server (the AMP client)

      • +
      • Dynamic logging now happens using evennia -l rather than by interactive mode.

      • +
      • Made AMP secure against erroneous HTTP requests on the wrong port (return error messages).

      • +
      • The evennia istart option will start/switch the Server in foreground (interactive) mode, where it logs +to terminal and can be stopped with Ctrl-C. Using evennia reload, or reloading in-game, will +return Server to normal daemon operation.

      • +
      • For validating passwords, use safe Django password-validation backend instead of custom Evennia one.

      • +
      • Alias evennia restart to mean the same as evennia reload.

      • +
      +
      +
      +

      Prototype changes

      +
        +
      • New OLC started from olc command for loading/saving/manipulating prototypes in a menu.

      • +
      • Moved evennia/utils/spawner.py into the new evennia/prototypes/ along with all new +functionality around prototypes.

      • +
      • A new form of prototype - database-stored prototypes, editable from in-game, was added. The old, +module-created prototypes remain as read-only prototypes.

      • +
      • All prototypes must have a key prototype_key identifying the prototype in listings. This is +checked to be server-unique. Prototypes created in a module will use the global variable name they +are assigned to if no prototype_key is given.

      • +
      • Prototype field prototype was renamed to prototype_parent to avoid mixing terms.

      • +
      • All prototypes must either have typeclass or prototype_parent defined. If using +prototype_parent, typeclass must be defined somewhere in the inheritance chain. This is a +change from Evennia 0.7 which allowed ‘mixin’ prototypes without typeclass/prototype_key. To +make a mixin now, give it a default typeclass, like evennia.objects.objects.DefaultObject and just +override in the child as needed.

      • +
      • Spawning an object using a prototype will automatically assign a new tag to it, named the same as +the prototype_key and with the category from_prototype.

      • +
      • The spawn command was extended to accept a full prototype on one line.

      • +
      • The spawn command got the /save switch to save the defined prototype and its key

      • +
      • The command spawn/menu will now start an OLC (OnLine Creation) menu to load/save/edit/spawn prototypes.

      • +
      +
      +
      +

      EvMenu

      +
        +
      • Added EvMenu.helptext_formatter(helptext) to allow custom formatting of per-node help.

      • +
      • Added evennia.utils.evmenu.list_node decorator for turning an EvMenu node into a multi-page listing.

      • +
      • A goto option callable returning None (rather than the name of the next node) will now rerun the +current node instead of failing.

      • +
      • Better error handling of in-node syntax errors.

      • +
      • Improve dedent of default text/helptext formatter. Right-strip whitespace.

      • +
      • Add debug option when creating menu - this turns off persistence and makes the menudebug +command available for examining the current menu state.

      • +
      +
      +
      +

      Webclient

      +
        +
      • Webclient now uses a plugin system to inject new components from the html file.

      • +
      • Split-windows - divide input field into any number of horizontal/vertical panes and +assign different types of server messages to them.

      • +
      • Lots of cleanup and bug fixes.

      • +
      • Hot buttons plugin (friarzen) (disabled by default).

      • +
      +
      +
      +

      Locks

      +
        +
      • New function evennia.locks.lockhandler.check_lockstring. This allows for checking an object +against an arbitrary lockstring without needing the lock to be stored on an object first.

      • +
      • New function evennia.locks.lockhandler.validate_lockstring allows for stand-alone validation +of a lockstring.

      • +
      • New function evennia.locks.lockhandler.get_all_lockfuncs gives a dict {“name”: lockfunc} for +all available lock funcs. This is useful for dynamic listings.

      • +
      +
      +
      +

      Utils

      +
        +
      • Added new columnize function for easily splitting text into multiple columns. At this point it +is not working too well with ansi-colored text however.

      • +
      • Extend the dedent function with a new baseline_index kwarg. This allows to force all lines to +the indentation given by the given line regardless of if other lines were already a 0 indentation. +This removes a problem with the original textwrap.dedent which will only dedent to the least +indented part of a text.

      • +
      • Added exit_cmd to EvMore pager, to allow for calling a command (e.g. ‘look’) when leaving the pager.

      • +
      • get_all_typeclasses will return dict {"path": typeclass, ...} for all typeclasses available +in the system. This is used by the new @typeclass/list subcommand (useful for builders etc).

      • +
      • evennia.utils.dbserialize.deserialize(obj) is a new helper function to completely disconnect +a mutable recovered from an Attribute from the database. This will convert all nested _Saver* +classes to their plain-Python counterparts.

      • +
      +
      +
      +

      General

      +
        +
      • Start structuring the CHANGELOG to list features in more detail.

      • +
      • Docker image evennia/evennia:develop is now auto-built, tracking the develop branch.

      • +
      • Inflection and grouping of multiple objects in default room (an box, three boxes)

      • +
      • evennia.set_trace() is now a shortcut for launching pdb/pudb on a line in the Evennia event loop.

      • +
      • Removed the enforcing of MAX_NR_CHARACTERS=1 for MULTISESSION_MODE 0 and 1 by default.

      • +
      • Add evennia.utils.logger.log_sec for logging security-related messages (marked SS in log).

      • +
      +
      +
      +

      Contribs

      +
        +
      • Auditing (Johnny): Log and filter server input/output for security purposes

      • +
      • Build Menu (vincent-lg): New @edit command to edit object properties in a menu.

      • +
      • Field Fill (Tim Ashley Jenkins): Wraps EvMenu for creating submittable forms.

      • +
      • Health Bar (Tim Ashley Jenkins): Easily create colorful bars/meters.

      • +
      • Tree select (Fluttersprite): Wrap EvMenu to create a common type of menu from a string.

      • +
      • Turnbattle suite (Tim Ashley Jenkins)- the old turnbattle.py was moved into its own +turnbattle/ package and reworked with many different flavors of combat systems:

      • +
      • tb_basic - The basic turnbattle system, with initiative/turn order attack/defense/damage.

      • +
      • tb_equip - Adds weapon and armor, wielding, accuracy modifiers.

      • +
      • tb_items - Extends tb_equip with item use with conditions/status effects.

      • +
      • tb_magic - Extends tb_equip with spellcasting.

      • +
      • tb_range - Adds system for abstract positioning and movement.

      • +
      • The extended_room contrib saw some backwards-incompatible refactoring:

        +
          +
        • All commands now begin with CmdExtendedRoom. So before it was CmdExtendedLook, now +it’s CmdExtendedRoomLook etc.

        • +
        • The detail command was broken out of the desc command and is now a new, stand-alone command +CmdExtendedRoomDetail. This was done to make things easier to extend and to mimic how the detail +command works in the tutorial-world.

        • +
        • The detail command now also supports deleting details (like the tutorial-world version).

        • +
        • The new ExtendedRoomCmdSet includes all the extended-room commands and is now the recommended way +to install the extended-room contrib.

        • +
        +
      • +
      • Updates and some cleanup of existing contribs.

      • +
      +
      +
      +

      Internationalization

      +
        +
      • Polish translation by user ogotai

      • +
      +
      +
      +
      +
      +

      Overview-Changelogs

      +
      +

      These are changelogs from a time before we used formal version numbers.

      +
      +
      +

      Sept 2017:

      +

      Release of Evennia 0.7; upgrade to Django 1.11, change ‘Player’ to +‘Account’, rework the website template and a slew of other updates. +Info on what changed and how to migrate is found here: +https://groups.google.com/forum/#!msg/evennia/0JYYNGY-NfE/cDFaIwmPBAAJ

      +
      +
      +

      Feb 2017:

      +

      New devel branch created, to lead up to Evennia 0.7.

      +
      +
      +

      Dec 2016:

      +

      Lots of bugfixes and considerable uptick in contributors. Unittest coverage +and PEP8 adoption and refactoring.

      +
      +
      +

      May 2016:

      +

      Evennia 0.6 with completely reworked Out-of-band system, making +the message path completely flexible and built around input/outputfuncs. +A completely new webclient, split into the evennia.js library and a +gui library, making it easier to customize.

      +
      +
      +

      Feb 2016:

      +

      Added the new EvMenu and EvMore utilities, updated EvEdit and cleaned up +a lot of the batchcommand functionality. Started work on new Devel branch.

      +
      +
      +

      Sept 2015:

      +

      Evennia 0.5. Merged devel branch, full library format implemented.

      +
      +
      +

      Feb 2015:

      +

      Development currently in devel/ branch. Moved typeclasses to use +django’s proxy functionality. Changed the Evennia folder layout to a +library format with a stand-alone launcher, in preparation for making +an ‘evennia’ pypy package and using versioning. The version we will +merge with will likely be 0.5. There is also work with an expanded +testing structure and the use of threading for saves. We also now +use Travis for automatic build checking.

      +
      +
      +

      Sept 2014:

      +

      Updated to Django 1.7+ which means South dependency was dropped and +minimum Python version upped to 2.7. MULTISESSION_MODE=3 was added +and the web customization system was overhauled using the latest +functionality of django. Otherwise, mostly bug-fixes and +implementation of various smaller feature requests as we got used +to github. Many new users have appeared.

      +
      +
      +

      Jan 2014:

      +

      Moved Evennia project from Google Code to github.com/evennia/evennia.

      +
      +
      +

      Nov 2013:

      +

      Moved the internal webserver into the Server and added support for +out-of-band protocols (MSDP initially). This large development push +also meant fixes and cleanups of the way attributes were handled. +Tags were added, along with proper handlers for permissions, nicks +and aliases.

      +
      +
      +

      May 2013:

      +

      Made players able to control more than one Character at the same +time, through the MULTISESSION_MODE=2 addition. This lead to a lot +of internal changes for the server.

      +
      +
      +

      Oct 2012:

      +

      Changed Evennia from the Modified Artistic 1.0 license to the more +standard and permissive BSD license. Lots of updates and bug fixes as +more people start to use it in new ways. Lots of new caching and +speed-ups.

      +
      +
      +

      March 2012:

      +

      Evennia’s API has changed and simplified slightly in that the +base-modules where removed from game/gamesrc. Instead admins are +encouraged to explicitly create new modules under game/gamesrc/ when +they want to implement their game - gamesrc/ is empty by default +except for the example folders that contain template files to use for +this purpose. We also added the ev.py file, implementing a new, flat +API. Work is ongoing to add support for mud-specific telnet +extensions, notably the MSDP and GMCP out-of-band extensions. On the +community side, evennia’s dev blog was started and linked on planet +Mud-dev aggregator.

      +
      +
      +

      Nov 2011:

      +

      After creating several different proof-of-concept game systems (in +contrib and privately) as well testing lots of things to make sure the +implementation is basically sound, we are declaring Evennia out of +Alpha. This can mean as much or as little as you want, admittedly - +development is still heavy but the issue list is at an all-time low +and the server is slowly stabilizing as people try different things +with it. So Beta it is!

      +
      +
      +

      Aug 2011:

      +

      Split Evennia into two processes: Portal and Server. After a lot of +work trying to get in-memory code-reloading to work, it’s clear this +is not Python’s forte - it’s impossible to catch all exceptions, +especially in asynchronous code like this. Trying to do so results in +hackish, flakey and unstable code. With the Portal-Server split, the +Server can simply be rebooted while players connected to the Portal +remain connected. The two communicates over twisted’s AMP protocol.

      +
      +
      +

      May 2011:

      +

      The new version of Evennia, originally hitting trunk in Aug2010, is +maturing. All commands from the pre-Aug version, including IRC/IMC2 +support works again. An ajax web-client was added earlier in the year, +including moving Evennia to be its own webserver (no more need for +Apache or django-testserver). Contrib-folder added.

      +
      +
      +

      Aug 2010:

      +

      Evennia-griatch-branch is ready for merging with trunk. This marks a +rather big change in the inner workings of the server, such as the +introduction of TypeClasses and Scripts (as compared to the old +ScriptParents and Events) but should hopefully bring everything +together into one consistent package as code development continues.

      +
      +
      +

      May 2010:

      +

      Evennia is currently being heavily revised and cleaned from +the years of gradual piecemeal development. It is thus in a very +‘Alpha’ stage at the moment. This means that old code snippets +will not be backwards compatabile. Changes touch almost all +parts of Evennia’s innards, from the way Objects are handled +to Events, Commands and Permissions.

      +
      +
      +

      April 2010:

      +

      Griatch takes over Maintainership of the Evennia project from +the original creator Greg Taylor.

      +
      +
      +
      +

      Older

      +

      Earlier revisions, with previous maintainer, used SVN on Google Code +and have no changelogs.

      +

      First commit (Evennia’s birthday) was November 20, 2006.

      +
      + + +
      +
      +
      +
      + +
      +
      + + + + \ No newline at end of file diff --git a/docs/1.0-dev/Setup/Choosing-An-SQL-Server.html b/docs/1.0-dev/Setup/Choosing-An-SQL-Server.html index d8f6dcdcf5..1197518a0b 100644 --- a/docs/1.0-dev/Setup/Choosing-An-SQL-Server.html +++ b/docs/1.0-dev/Setup/Choosing-An-SQL-Server.html @@ -408,7 +408,6 @@ others. If you try other databases out, consider expanding this page with instru

      Versions

      diff --git a/docs/1.0-dev/Setup/Client-Support-Grid.html b/docs/1.0-dev/Setup/Client-Support-Grid.html index 729f020edd..86b21c7e3c 100644 --- a/docs/1.0-dev/Setup/Client-Support-Grid.html +++ b/docs/1.0-dev/Setup/Client-Support-Grid.html @@ -251,7 +251,6 @@ parameter to disable it for that Evennia account permanently.

      Versions

      diff --git a/docs/1.0-dev/Setup/Evennia-Game-Index.html b/docs/1.0-dev/Setup/Evennia-Game-Index.html index 3f8fa0502b..4c30d0bd09 100644 --- a/docs/1.0-dev/Setup/Evennia-Game-Index.html +++ b/docs/1.0-dev/Setup/Evennia-Game-Index.html @@ -161,7 +161,6 @@ if you are not ready for players yet.

      Versions

      diff --git a/docs/1.0-dev/Setup/Extended-Installation.html b/docs/1.0-dev/Setup/Extended-Installation.html deleted file mode 100644 index c888c710c6..0000000000 --- a/docs/1.0-dev/Setup/Extended-Installation.html +++ /dev/null @@ -1,634 +0,0 @@ - - - - - - - - - Getting Started — Evennia 1.0-dev documentation - - - - - - - - - - - - - - - -
      -
      -
      -
      - -
      -

      Getting Started

      -

      This will help you download, install and start Evennia for the first time.

      -
      -

      Note: You don’t need to make anything visible to the ‘net in order to run and -test out Evennia. Apart from downloading and updating you don’t even need an -internet connection until you feel ready to share your game with the world.

      -
      - -
      -

      Quick Start

      -

      For the impatient. If you have trouble with a step, you should jump on to the -more detailed instructions for your platform.

      -
        -
      1. Install Python, GIT and python-virtualenv. Start a Console/Terminal.

      2. -
      3. cd to some place you want to do your development (like a folder -/home/anna/muddev/ on Linux or a folder in your personal user directory on Windows).

      4. -
      5. git clone https://github.com/evennia/evennia.git (a new folder evennia is created)

      6. -
      7. python -m venv evenv (a new folder evenv is created)

      8. -
      9. source evenv/bin/activate (Linux, Mac), evenv\Scripts\activate (Windows)

      10. -
      11. pip install -e evennia

      12. -
      13. evennia --init mygame

      14. -
      15. cd mygame

      16. -
      17. evennia migrate

      18. -
      19. evennia start (make sure to make a superuser when asked) -Evennia should now be running and you can connect to it by pointing a web browser to -http://localhost:4001 or a MUD telnet client to localhost:4000 (use 127.0.0.1 if your OS does -not recognize localhost).

      20. -
      -

      We also release Docker images -based on master and develop branches.

      -
      -
      -

      Requirements

      -

      Any system that supports Python3.7+ should work. We’ll describe how to install -everything in the following sections.

      -
        -
      • Linux/Unix

      • -
      • Windows (Vista, Win7, Win8, Win10)

      • -
      • Mac OSX (>=10.5 recommended)

      • -
      • Python (v3.7, 3.8 and 3.9 are tested)

        -
          -
        • virtualenv for making isolated -Python environments. Installed with pip install virtualenv.

        • -
        -
      • -
      • GIT - version control software for getting and -updating Evennia itself - Mac users can use the -git-osx-installer or the -MacPorts version.

      • -
      • Twisted (v19.0+)

        -
          -
        • ZopeInterface (v3.0+) - usually included in -Twisted packages

        • -
        • Linux/Mac users may need the gcc and python-dev packages or equivalent.

        • -
        • Windows users need MS Visual C++ and maybe -pypiwin32.

        • -
        -
      • -
      • Django (v2.2.x), be warned that latest dev -version is usually untested with Evennia)

      • -
      -
      -
      -

      Linux Install

      -

      If you run into any issues during the installation and first start, please -check out Linux Troubleshooting.

      -

      For Debian-derived systems (like Ubuntu, Mint etc), start a terminal and -install the dependencies:

      -
      sudo apt-get update
      -sudo apt-get install python3 python3-pip python3-dev python3-setuptools python3-git
      -python3-virtualenv gcc
      -
      -# If you are using an Ubuntu version that defaults to Python3, like 18.04+, use this instead:
      -sudo apt-get update
      -sudo apt-get install python3.7 python3-pip python3.7-dev python3-setuptools virtualenv gcc
      -
      -
      -
      -

      Note that, the default Python version for your distribution may still not be Python3.7 after this. -This is ok - we’ll specify exactly which Python to use later. -You should make sure to not be root after this step, running as root is a -security risk. Now create a folder where you want to do all your Evennia -development:

      -
      mkdir muddev
      -cd muddev
      -
      -
      -

      Next we fetch Evennia itself:

      -
      git clone https://github.com/evennia/evennia.git
      -
      -
      -

      A new folder evennia will appear containing the Evennia library. This only -contains the source code though, it is not installed yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a virtualenv. If you are unsure about what a -virtualenv is and why it’s useful, see the Glossary entry on -virtualenv.

      -

      Run python -V to see which version of Python your system defaults to.

      -
      # If your Linux defaults to Python3.7+:
      -virtualenv evenv
      -
      -# If your Linux defaults to Python2 or an older version
      -# of Python3, you must instead point to Python3.7+ explicitly:
      -virtualenv -p /usr/bin/python3.7 evenv
      -
      -
      -

      A new folder evenv will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system (or the Linux distro lagging behind -on Python package versions). It will also always use the right version of Python. -Activate the virtualenv:

      -
      source evenv/bin/activate
      -
      -
      -

      The text (evenv) should appear next to your prompt to show that the virtual -environment is active.

      -
      -

      Remember that you need to activate the virtualenv like this every time you -start a new terminal to get access to the Python packages (notably the -important evennia program) we are about to install.

      -
      -

      Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the evennia/ and evenv/ -folders) and run

      -
      pip install -e evennia
      -
      -
      -

      For more info about pip, see the Glossary entry on pip. If -install failed with any issues, see Linux Troubleshooting.

      -

      Next we’ll start our new game, here called “mygame”. This will create yet -another new folder where you will be creating your new game:

      -
      evennia --init mygame
      -
      -
      -

      Your final folder structure should look like this:

      -
      ./muddev
      -    evenv/
      -    evennia/
      -    mygame/
      -
      -
      -

      You can configure Evennia extensively, for example -to use a different database. For now we’ll just stick -to the defaults though.

      -
      cd mygame
      -evennia migrate      # (this creates the database)
      -evennia start        # (create a superuser when asked. Email is optional.)
      -
      -
      -
      -

      Server logs are found in mygame/server/logs/. To easily view server logs -live in the terminal, use evennia -l (exit the log-view with Ctrl-C).

      -
      -

      Your game should now be running! Open a web browser at http://localhost:4001 -or point a telnet client to localhost:4000 and log in with the user you -created. Check out where to go next.

      -
      -
      -

      Mac Install

      -

      The Evennia server is a terminal program. Open the terminal e.g. from -Applications->Utilities->Terminal. Here is an introduction to the Mac -terminal -if you are unsure how it works. If you run into any issues during the -installation, please check out Mac Troubleshooting.

      -
        -
      • Python should already be installed but you must make sure it’s a high enough version. -(This discusses -how you may upgrade it). Remember that you need Python3.7, not Python2.7!

      • -
      • GIT can be obtained with -git-osx-installer or via -MacPorts as described -here.

      • -
      • If you run into issues with installing Twisted later you may need to -install gcc and the Python headers.

      • -
      -

      After this point you should not need sudo or any higher privileges to install anything.

      -

      Now create a folder where you want to do all your Evennia development:

      -
      mkdir muddev
      -cd muddev
      -
      -
      -

      Next we fetch Evennia itself:

      -
      git clone https://github.com/evennia/evennia.git
      -
      -
      -

      A new folder evennia will appear containing the Evennia library. This only -contains the source code though, it is not installed yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a virtualenv. If you are unsure about what a -virtualenv is and why it’s useful, see the Glossary entry on virtualenv.

      -

      Run python -V to check which Python your system defaults to.

      -
      # If your Mac defaults to Python3:
      -virtualenv evenv
      -
      -# If your Mac defaults to Python2 you need to specify the Python3.7 binary explicitly:
      -virtualenv -p /path/to/your/python3.7 evenv
      -
      -
      -

      A new folder evenv will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system. Activate the virtualenv:

      -
      source evenv/bin/activate
      -
      -
      -

      The text (evenv) should appear next to your prompt to show the virtual -environment is active.

      -
      -

      Remember that you need to activate the virtualenv like this every time you -start a new terminal to get access to the Python packages (notably the -important evennia program) we are about to install.

      -
      -

      Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the evennia/ and evenv/ -folders) and run

      -
      pip install --upgrade pip   # Old pip versions may be an issue on Mac.
      -pip install --upgrade setuptools   # Ditto concerning Mac issues.
      -pip install -e evennia
      -
      -
      -

      For more info about pip, see the Glossary entry on pip. If -install failed with any issues, see Mac Troubleshooting.

      -

      Next we’ll start our new game. We’ll call it “mygame” here. This creates a new -folder where you will be creating your new game:

      -
      evennia --init mygame
      -
      -
      -

      Your final folder structure should look like this:

      -
      ./muddev
      -    evenv/
      -    evennia/
      -    mygame/
      -
      -
      -

      You can configure Evennia extensively, for example -to use a different database. We’ll go with the -defaults here.

      -
      cd mygame
      -evennia migrate  # (this creates the database)
      -evennia start    # (create a superuser when asked. Email is optional.)
      -
      -
      -
      -

      Server logs are found in mygame/server/logs/. To easily view server logs -live in the terminal, use evennia -l (exit the log-view with Ctrl-C).

      -
      -

      Your game should now be running! Open a web browser at http://localhost:4001 -or point a telnet client to localhost:4000 and log in with the user you -created. Check out where to go next.

      -
      -
      -

      Windows Install

      -

      If you run into any issues during the installation, please check out -Windows Troubleshooting.

      -
      -

      If you are running Windows10, consider using the Windows Subsystem for Linux -(WSL) instead. -You should then follow the Linux install instructions above.

      -
      -

      The Evennia server itself is a command line program. In the Windows launch -menu, start All Programs -> Accessories -> command prompt and you will get -the Windows command line interface. Here is one of many tutorials on using the Windows command -line -if you are unfamiliar with it.

      -
        -
      • Install Python from the Python homepage. You will -need to be a -Windows Administrator to install packages. You want Python version 3.7.0 (latest verified -version), usually -the 64-bit version (although it doesn’t matter too much). When installing, make sure -to check-mark all install options, especially the one about making Python -available on the path (you may have to scroll to see it). This allows you to -just write python in any console without first finding where the python -program actually sits on your hard drive.

      • -
      • You need to also get GIT and install it. You -can use the default install options but when you get asked to “Adjust your PATH -environment”, you should select the second option “Use Git from the Windows -Command Prompt”, which gives you more freedom as to where you can use the -program.

      • -
      • Finally you must install the Microsoft Visual C++ compiler for -Python. Download and run the linked installer and -install the C++ tools. Keep all the defaults. Allow the install of the “Win10 SDK”, even if you are -on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due -to a failure to build the “Twisted wheels”, this is where you are missing things.

      • -
      • You may need the pypiwin32 Python headers. Install -these only if you have issues.

      • -
      -

      You can install Evennia wherever you want. cd to that location and create a -new folder for all your Evennia development (let’s call it muddev).

      -
      mkdir muddev
      -cd muddev
      -
      -
      -
      -

      Hint: If cd isn’t working you can use pushd instead to force the -directory change.

      -
      -

      Next we fetch Evennia itself:

      -
      git clone https://github.com/evennia/evennia.git
      -
      -
      -

      A new folder evennia will appear containing the Evennia library. This only -contains the source code though, it is not installed yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a virtualenv. If you are unsure about what a -virtualenv is and why it’s useful, see the Glossary entry on virtualenv.

      -

      In your console, try python -V to see which version of Python your system -defaults to.

      -
      pip install virtualenv
      -
      -# If your setup defaults to Python3.7:
      -virtualenv evenv
      -
      -# If your setup defaults to Python2, specify path to python3.exe explicitly:
      -virtualenv -p C:\Python37\python.exe evenv
      -
      -# If you get an infinite spooling response, press CTRL + C to interrupt and try using:
      -python -m venv evenv
      -
      -
      -
      -

      A new folder evenv will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system. Activate the virtualenv:

      -
      # If you are using a standard command prompt, you can use the following:
      -evenv\scripts\activate.bat
      -
      -# If you are using a PS Shell, Git Bash, or other, you can use the following:
      -.\evenv\scripts\activate
      -
      -
      -
      -

      The text (evenv) should appear next to your prompt to show the virtual -environment is active.

      -
      -

      Remember that you need to activate the virtualenv like this every time you -start a new console window if you want to get access to the Python packages -(notably the important evennia program) we are about to install.

      -
      -

      Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the evennia and evenv -folders when you use the dir command) and run

      -
      pip install -e evennia
      -
      -
      -

      For more info about pip, see the Glossary entry on pip. If -the install failed with any issues, see [Windows Troubleshooting](#windows- -troubleshooting). -Next we’ll start our new game, we’ll call it “mygame” here. This creates a new folder where you will -be -creating your new game:

      -
      evennia --init mygame
      -
      -
      -

      Your final folder structure should look like this:

      -
      path\to\muddev
      -    evenv\
      -    evennia\
      -    mygame\
      -
      -
      -

      You can configure Evennia extensively, for example -to use a different database. We’ll go with the -defaults here.

      -
      cd mygame
      -evennia migrate    # (this creates the database)
      -evennia start      # (create a superuser when asked. Email is optional.)
      -
      -
      -
      -

      Server logs are found in mygame/server/logs/. To easily view server logs -live in the terminal, use evennia -l (exit the log-view with Ctrl-C).

      -
      -

      Your game should now be running! Open a web browser at http://localhost:4001 -or point a telnet client to localhost:4000 and log in with the user you -created. Check out where to go next.

      -
      -
      -

      Non-interactive setup

      -

      When you first run evennia start after having created the database, you will be asked -to interactively insert the superuser username, email and password. If you need to do -this in an automated faction (such as in an automated build flow), you can supply those -values as environment variables, EVENNIA_SUPERUSER_USERNAME, EVENNIA_SUPERUSER_EMAIL and -EVENNIA_SUPERUSER_PASSWORD. The email can be left out and will then be set to be the -empty string.

      -

      Use this to start Evennia (the envvars will be ignored on subsequent starts):

      -
      EVENNIA_SUPERUSER_USERNAME=Foo EVENNIA_SUPERUSER_PASSWORD=MygreatPwd evennia start
      -
      -
      -
      -
      -
      -

      Where to Go Next

      -

      Welcome to Evennia! Your new game is fully functioning, but empty. If you just -logged in, stand in the Limbo room and run

      -
      @batchcommand tutorial_world.build
      -
      -
      -

      to build Evennia’s tutorial world - it’s a small solo quest to -explore. Only run the instructed @batchcommand once. You’ll get a lot of text scrolling by as the -tutorial is built. Once done, the tutorial exit will have appeared out of Limbo - just write -tutorial to enter it.

      -

      Once you get back to Limbo from the tutorial (if you get stuck in the tutorial quest you can do -@tel #2 to jump to Limbo), a good idea is to learn how to [start, stop and reload](Start-Stop- -Reload) the Evennia server. You may also want to familiarize yourself with some -commonly used terms in our Glossary. After that, why not experiment with -creating some new items and build some new rooms out from Limbo.

      -

      From here on, you could move on to do one of our introductory tutorials or simply dive -headlong into Evennia’s comprehensive manual. While -Evennia has no major game systems out of the box, we do supply a range of optional contribs that -you can use or borrow from. They range from dice rolling and alternative color schemes to barter and -combat systems. You can find the growing list of contribs -here.

      -

      If you have any questions, you can always ask in the developer -chat -#evennia on irc.freenode.net or by posting to the Evennia -forums. You can also join the Discord -Server.

      -

      Finally, if you are itching to help out or support Evennia (awesome!) have an -issue to report or a feature to request, see here.

      -

      Enjoy your stay!

      -
      -
      -

      Troubleshooting

      -

      If you have issues with installing or starting Evennia for the first time, -check the section for your operating system below. If you have an issue not -covered here, please report it -so it can be fixed or a workaround found!

      -

      Remember, the server logs are in mygame/server/logs/. To easily view server logs in the terminal, -you can run evennia -l, or (in the future) start the server with evennia start -l.

      -
      -

      Linux Troubleshooting

      -
        -
      • If you get an error when installing Evennia (especially with lines mentioning -failing to include Python.h) then try sudo apt-get install python3-setuptools python3-dev. -Once installed, run pip install -e evennia again.

      • -
      • Under some not-updated Linux distributions you may run into errors with a -too-old setuptools or missing functools. If so, update your environment -with pip install --upgrade pip wheel setuptools. Then try pip install -e evennia again.

      • -
      • If you get an setup.py not found error message while trying to pip install, make sure you are -in the right directory. You should be at the same level of the evenv directory, and the -evennia git repository. Note that there is an evennia directory inside of the repository too.

      • -
      • One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/ with errors -like distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('incremental>=16.10.1'). This appears possible to solve by simply updating Ubuntu -with sudo apt-get update && sudo apt-get dist-upgrade.

      • -
      • Users of Fedora (notably Fedora 24) has reported a gcc error saying the directory -/usr/lib/rpm/redhat/redhat-hardened-cc1 is missing, despite gcc itself being installed. The -confirmed work-around seems to be to -install the redhat-rpm-config package with e.g. sudo dnf install redhat-rpm-config.

      • -
      • Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues -with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to -yourself?)

      • -
      -
      -
      -

      Mac Troubleshooting

      -
        -
      • Mac users have reported a critical MemoryError when trying to start Evennia on Mac with a Python -version below 2.7.12. If you get this error, update to the latest XCode and Python2 version.

      • -
      • Some Mac users have reported not being able to connect to localhost (i.e. your own computer). If -so, try to connect to 127.0.0.1 instead, which is the same thing. Use port 4000 from mud clients -and port 4001 from the web browser as usual.

      • -
      -
      -
      -

      Windows Troubleshooting

      -
        -
      • If you installed Python but the python command is not available (even in a new console), then -you might have missed installing Python on the path. In the Windows Python installer you get a list -of options for what to install. Most or all options are pre-checked except this one, and you may -even have to scroll down to see it. Reinstall Python and make sure it’s checked.

      • -
      • If your MUD client cannot connect to localhost:4000, try the equivalent 127.0.0.1:4000 -instead. Some MUD clients on Windows does not appear to understand the alias localhost.

      • -
      • If you run virtualenv evenv and get a 'virtualenv' is not recognized as an internal or external command, operable program or batch file. error, you can mkdir evenv, cd evenv and then python -m virtualenv . as a workaround.

      • -
      • Some Windows users get an error installing the Twisted ‘wheel’. A wheel is a pre-compiled binary -package for Python. A common reason for this error is that you are using a 32-bit version of Python, -but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a -slightly older Twisted version. So if, say, version 18.1 failed, install 18.0 manually with pip install twisted==18.0. Alternatively you could try to get a 64-bit version of Python (uninstall the -32bit one). If so, you must then deactivate the virtualenv, delete the evenv folder and recreate -it anew (it will then use the new Python executable).

      • -
      • If your server won’t start, with no error messages (and no log files at all when starting from -scratch), try to start with evennia ipstart instead. If you then see an error about system cannot find the path specified, it may be that the file evennia/evennia/server/twistd.bat has the wrong -path to the twistd executable. This file is auto-generated, so try to delete it and then run -evennia start to rebuild it and see if it works. If it still doesn’t work you need to open it in a -text editor like Notepad. It’s just one line containing the path to the twistd.exe executable as -determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and -you should update the line to the real location.

      • -
      • Some users have reported issues with Windows WSL and anti-virus software during Evennia -development. Timeout errors and the inability to run evennia connections may be due to your anti- -virus software interfering. Try disabling or changing your anti-virus software settings.

      • -
      -
      -
      -
      - - -
      -
      -
      -
      - -
      -
      - - - - \ No newline at end of file diff --git a/docs/1.0-dev/Setup/Grapevine.html b/docs/1.0-dev/Setup/Grapevine.html index b27f91ef19..2973821b03 100644 --- a/docs/1.0-dev/Setup/Grapevine.html +++ b/docs/1.0-dev/Setup/Grapevine.html @@ -152,7 +152,6 @@ it to your channel in-game.

      Versions

      diff --git a/docs/1.0-dev/Setup/HAProxy-Config.html b/docs/1.0-dev/Setup/HAProxy-Config.html index 8c4b17170f..1c2846375b 100644 --- a/docs/1.0-dev/Setup/HAProxy-Config.html +++ b/docs/1.0-dev/Setup/HAProxy-Config.html @@ -318,7 +318,6 @@ uncommented in the config file, it will now start as a background process.

      Versions

      diff --git a/docs/1.0-dev/Setup/How-to-connect-Evennia-to-Twitter.html b/docs/1.0-dev/Setup/How-to-connect-Evennia-to-Twitter.html index 0bd97e0d31..fdfcf11fcc 100644 --- a/docs/1.0-dev/Setup/How-to-connect-Evennia-to-Twitter.html +++ b/docs/1.0-dev/Setup/How-to-connect-Evennia-to-Twitter.html @@ -192,7 +192,6 @@ help.

      Versions

      diff --git a/docs/1.0-dev/Setup/IRC.html b/docs/1.0-dev/Setup/IRC.html index 91303ec3f6..d2945d139f 100644 --- a/docs/1.0-dev/Setup/IRC.html +++ b/docs/1.0-dev/Setup/IRC.html @@ -171,7 +171,6 @@ name of the IRC channel you used (#evennia here).

      Versions

      diff --git a/docs/1.0-dev/Setup/Installing-on-Android.html b/docs/1.0-dev/Setup/Installation-Android.html similarity index 95% rename from docs/1.0-dev/Setup/Installing-on-Android.html rename to docs/1.0-dev/Setup/Installation-Android.html index 8b97418f30..54e8c7c233 100644 --- a/docs/1.0-dev/Setup/Installing-on-Android.html +++ b/docs/1.0-dev/Setup/Installation-Android.html @@ -45,6 +45,10 @@

      This page describes how to install and run the Evennia server on an Android phone. This will involve installing a slew of third-party programs from the Google Play store, so make sure you are okay with this before starting.

      +
      Android installation is experimental and not tested with later versions of Android. 
      +Report your findings.
      +
      +

      Install Termux

      The first thing to do is install a terminal emulator that allows a “full” version of linux to be @@ -113,7 +117,7 @@ see

      Final steps

      At this point, Evennia is installed on your phone! You can now continue with the original -Setup Quickstart instruction, we repeat them here for clarity.

      +Setup Quickstart instruction, we repeat them here for clarity.

      To start a new game:

      (evenv) $ evennia --init mygame
       (evenv) $ ls
      @@ -139,7 +143,7 @@ virtualenv as well as are in your game’s directory. You can then run evennia s
       (evenv) $ evennia start
       
      -

      You may wish to look at the Linux Instructions for more.

      +

      You may wish to look at the Linux Instructions for more.

      Caveats

      @@ -205,7 +209,7 @@ killed if your phone is heavily taxed. Termux seems to keep a notification up to

      Links

      @@ -221,8 +225,7 @@ killed if your phone is heavily taxed. Termux seems to keep a notification up to

      Versions

      diff --git a/docs/1.0-dev/Setup/Running-Evennia-in-Docker.html b/docs/1.0-dev/Setup/Installation-Docker.html similarity index 98% rename from docs/1.0-dev/Setup/Running-Evennia-in-Docker.html rename to docs/1.0-dev/Setup/Installation-Docker.html index d8c7bb632d..31be3b3b7a 100644 --- a/docs/1.0-dev/Setup/Running-Evennia-in-Docker.html +++ b/docs/1.0-dev/Setup/Installation-Docker.html @@ -71,7 +71,7 @@ the docker container:

      This is a normal shell prompt. We are in the /usr/src/game location inside the docker container. If you had anything in the folder you started from, you should see it here (with ls) since we mounted the current directory to usr/src/game (with -v above). You have the evennia command -available and can now proceed to create a new game as per the Setup Quickstart +available and can now proceed to create a new game as per the Setup Quickstart instructions (you can skip the virtualenv and install ‘globally’ in the container though).

      You can run Evennia from inside this container if you want to, it’s like you are root in a little isolated Linux environment. To exit the container and all processes in there, press Ctrl-D. If you @@ -358,7 +358,7 @@ line.

      Links

      @@ -374,8 +374,7 @@ line.

      Versions

      diff --git a/docs/1.0-dev/Setup/Installation-Git.html b/docs/1.0-dev/Setup/Installation-Git.html new file mode 100644 index 0000000000..1ff4f58948 --- /dev/null +++ b/docs/1.0-dev/Setup/Installation-Git.html @@ -0,0 +1,390 @@ + + + + + + + + + Installing with GIT — Evennia 1.0-dev documentation + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Installing with GIT

      +

      This installs and runs Evennia from its sources. This is required if you want to contribute to Evennia +itself or have an easier time exploring the code. See the basic Installation for +a quick installation of the library. See the troubleshooting if you run +into trouble.

      + +
      +

      Summary

      +

      For the impatient. If you have trouble with a step, you should jump on to the +more detailed instructions for your platform.

      +
        +
      1. Install Python, GIT and python-virtualenv. Start a Console/Terminal.

      2. +
      3. cd to some place you want to do your development (like a folder +/home/anna/muddev/ on Linux or a folder in your personal user directory on Windows).

      4. +
      5. git clone https://github.com/evennia/evennia.git (a new folder evennia is created)

      6. +
      7. python3.10 -m venv evenv (a new folder evenv is created)

      8. +
      9. source evenv/bin/activate (Linux, Mac), evenv\Scripts\activate (Windows)

      10. +
      11. pip install -e evennia

      12. +
      13. evennia --init mygame

      14. +
      15. cd mygame

      16. +
      17. evennia migrate

      18. +
      19. evennia start (make sure to make a superuser when asked) +Evennia should now be running and you can connect to it by pointing a web browser to +http://localhost:4001 or a MUD telnet client to localhost:4000 (use 127.0.0.1 if your OS does +not recognize localhost).

      20. +
      +
      +
      +

      Linux Install

      +

      If you run into any issues during the installation and first start, please +check out Linux Troubleshooting.

      +

      For Debian-derived systems (like Ubuntu, Mint etc), start a terminal and +install the dependencies:

      +
      sudo apt-get update
      +sudo apt-get install python3.10 python3.10-venv python3.10-dev gcc
      +
      +
      +
      +

      You should make sure to not be root after this step, running as root is a +security risk. Now create a folder where you want to do all your Evennia +development:

      +
      mkdir muddev
      +cd muddev
      +
      +
      +

      Next we fetch Evennia itself:

      +
      git clone https://github.com/evennia/evennia.git
      +
      +
      +

      A new folder evennia will appear containing the Evennia library. This only +contains the source code though, it is not installed yet. To isolate the +Evennia install and its dependencies from the rest of the system, it is good +Python practice to install into a virtualenv. If you are unsure about what a +virtualenv is and why it’s useful, see the Glossary entry on +virtualenv.

      +
      python3.10 -m venv evenv
      +
      +
      +

      A new folder evenv will appear (we could have called it anything). This +folder will hold a self-contained setup of Python packages without interfering +with default Python packages on your system (or the Linux distro lagging behind +on Python package versions). It will also always use the right version of Python. +Activate the virtualenv:

      +
      source evenv/bin/activate
      +
      +
      +

      The text (evenv) should appear next to your prompt to show that the virtual +environment is active.

      +
      +

      Remember that you need to activate the virtualenv like this every time you +start a new terminal to get access to the Python packages (notably the +important evennia program) we are about to install.

      +
      +

      Next, install Evennia into your active virtualenv. Make sure you are standing +at the top of your mud directory tree (so you see the evennia/ and evenv/ +folders) and run

      +
      pip install -e evennia
      +
      +
      +

      Test that you can run the evennia command everywhere while your virtualenv (evenv) is active.

      +

      Next you can continue initializing your game from the regular Installation instructions.

      +
      +
      +

      Mac Install

      +

      The Evennia server is a terminal program. Open the terminal e.g. from +Applications->Utilities->Terminal. Here is an introduction to the Mac +terminal +if you are unsure how it works. If you run into any issues during the +installation, please check out Mac Troubleshooting.

      +
        +
      • Python should already be installed but you must make sure it’s a high enough version - go for +3.10. +(This discusses +how you may upgrade it).

      • +
      • GIT can be obtained with +git-osx-installer or via MacPorts as described +here.

      • +
      • If you run into issues with installing Twisted later you may need to +install gcc and the Python headers.

      • +
      +

      After this point you should not need sudo or any higher privileges to install anything.

      +

      Now create a folder where you want to do all your Evennia development:

      +
      mkdir muddev
      +cd muddev
      +
      +
      +

      Next we fetch Evennia itself:

      +
      git clone https://github.com/evennia/evennia.git
      +
      +
      +

      A new folder evennia will appear containing the Evennia library. This only +contains the source code though, it is not installed yet. To isolate the +Evennia install and its dependencies from the rest of the system, it is good +Python practice to install into a virtualenv. If you are unsure about what a +virtualenv is and why it’s useful, see the Glossary entry on virtualenv.

      +
      python3.10 -m venv evenv 
      +
      +
      +
      +

      A new folder evenv will appear (we could have called it anything). This +folder will hold a self-contained setup of Python packages without interfering +with default Python packages on your system. Activate the virtualenv:

      +
      source evenv/bin/activate
      +
      +
      +

      The text (evenv) should appear next to your prompt to show the virtual +environment is active.

      +
      +

      Remember that you need to activate the virtualenv like this every time you +start a new terminal to get access to the Python packages (notably the +important evennia program) we are about to install.

      +
      +

      Next, install Evennia into your active virtualenv. Make sure you are standing +at the top of your mud directory tree (so you see the evennia/ and evenv/ +folders) and run

      +
      pip install --upgrade pip   # Old pip versions may be an issue on Mac.
      +pip install --upgrade setuptools   # Ditto concerning Mac issues.
      +pip install -e evennia
      +
      +
      +

      Test that you can run the evennia command everywhere while your virtualenv (evenv) is active.

      +

      Next you can continue initializing your game from the regular Installation instructions.

      +
      +
      +

      Windows Install

      +
      +

      If you are running Windows10, consider using the Windows Subsystem for Linux +(WSL) instead. +Just set up WSL with an Ubuntu image and follow the Linux install instructions above.

      +
      +

      The Evennia server itself is a command line program. In the Windows launch +menu, start All Programs -> Accessories -> command prompt and you will get +the Windows command line interface. Here is one of many tutorials on using the Windows command +line +if you are unfamiliar with it.

      +
        +
      • Install Python from the Python homepage. You will +need to be a +Windows Administrator to install packages. Get Python any version of Python 3.10, usually +the 64-bit version (although it doesn’t matter too much). When installing, make sure +to check-mark all install options, especially the one about making Python +available on the path (you may have to scroll to see it). This allows you to +just write python in any console without first finding where the python +program actually sits on your hard drive.

      • +
      • You need to also get GIT and install it. You +can use the default install options but when you get asked to “Adjust your PATH +environment”, you should select the second option “Use Git from the Windows +Command Prompt”, which gives you more freedom as to where you can use the +program.

      • +
      • Finally you must install the Microsoft Visual C++ compiler for +Python. Download and run the linked installer and +install the C++ tools. Keep all the defaults. Allow the install of the “Win10 SDK”, even if you are +on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due +to a failure to build the “Twisted wheels”, this is where you are missing things.

      • +
      • You may need the pypiwin32 Python headers. Install +these only if you have issues.

      • +
      +

      You can install Evennia wherever you want. cd to that location and create a +new folder for all your Evennia development (let’s call it muddev).

      +
      mkdir muddev
      +cd muddev
      +
      +
      +
      +

      Hint: If cd isn’t working you can use pushd instead to force the +directory change.

      +
      +

      Next we fetch Evennia itself:

      +
      git clone https://github.com/evennia/evennia.git
      +
      +
      +

      A new folder evennia will appear containing the Evennia library. This only +contains the source code though, it is not installed yet. To isolate the +Evennia install and its dependencies from the rest of the system, it is good +Python practice to install into a virtualenv. If you are unsure about what a +virtualenv is and why it’s useful, see the Glossary entry on virtualenv.

      +
      python3.10 -m venv evenv 
      +
      +
      +
      +

      A new folder evenv will appear (we could have called it anything). This +folder will hold a self-contained setup of Python packages without interfering +with default Python packages on your system. Activate the virtualenv:

      +
      # If you are using a standard command prompt, you can use the following:
      +evenv\scripts\activate.bat
      +
      +# If you are using a PS Shell, Git Bash, or other, you can use the following:
      +.\evenv\scripts\activate
      +
      +
      +
      +

      The text (evenv) should appear next to your prompt to show the virtual +environment is active.

      +
      +

      Remember that you need to activate the virtualenv like this every time you +start a new console window if you want to get access to the Python packages +(notably the important evennia program) we are about to install.

      +
      +

      Next, install Evennia into your active virtualenv. Make sure you are standing +at the top of your mud directory tree (so you see the evennia and evenv +folders when you use the dir command) and run

      +
      pip install -e evennia
      +
      +
      +

      Test that you can run the evennia command everywhere while your virtualenv (evenv) is active.

      +

      Next you can continue initializing your game from the regular Installation instructions.

      +
      +
      +

      Where to Go Next

      +

      Welcome to Evennia! Your new game is fully functioning, but empty. If you just +logged in, stand in the Limbo room and run

      +
      @batchcommand tutorial_world.build
      +
      +
      +

      to build Evennia’s tutorial world - it’s a small solo quest to +explore. Only run the instructed @batchcommand once. You’ll get a lot of text scrolling by as the +tutorial is built. Once done, the tutorial exit will have appeared out of Limbo - just write +tutorial to enter it.

      +

      Once you get back to Limbo from the tutorial (if you get stuck in the tutorial quest you can do +@tel #2 to jump to Limbo), a good idea is to learn how to [start, stop and reload](Start-Stop- +Reload) the Evennia server. You may also want to familiarize yourself with some +commonly used terms in our Glossary. After that, why not experiment with +creating some new items and build some new rooms out from Limbo.

      +

      From here on, you could move on to do one of our introductory tutorials or simply dive +headlong into Evennia’s comprehensive manual. While +Evennia has no major game systems out of the box, we do supply a range of optional contribs that +you can use or borrow from. They range from dice rolling and alternative color schemes to barter and +combat systems. You can find the growing list of contribs +here.

      +

      If you have any questions, you can always ask in the developer +chat +#evennia on irc.freenode.net or by posting to the Evennia +forums. You can also join the Discord +Server.

      +

      Finally, if you are itching to help out or support Evennia (awesome!) have an +issue to report or a feature to request, see here.

      +

      Enjoy your stay!

      +
      +
      + + +
      +
      +
      +
      + +
      +
      + + + + \ No newline at end of file diff --git a/docs/1.0-dev/Setup/Settings-File.html b/docs/1.0-dev/Setup/Installation-Non-Interactive.html similarity index 67% rename from docs/1.0-dev/Setup/Settings-File.html rename to docs/1.0-dev/Setup/Installation-Non-Interactive.html index 192482a76a..447f3dc928 100644 --- a/docs/1.0-dev/Setup/Settings-File.html +++ b/docs/1.0-dev/Setup/Installation-Non-Interactive.html @@ -6,7 +6,7 @@ - The Evennia Default Settings file — Evennia 1.0-dev documentation + Non-interactive setup — Evennia 1.0-dev documentation @@ -30,7 +30,7 @@ modules | - +
      develop branch
      @@ -40,9 +40,23 @@
      -
      -

      The Evennia Default Settings file

      -

      TODO

      +
      +

      Non-interactive setup

      +

      The first ime you run evennia start (just after having created the database), you will be asked +to interactively insert the superuser username, email and password. If you are deploying Evennia +as part of an automatic build script, you don’t want to enter this information manually.

      +

      You can have the superuser be created automatically by passing environment variables to your +build script:

      +
        +
      • EVENNIA_SUPERUSER_USERNAME

      • +
      • EVENNIA_SUPERUSER_PASSWORD

      • +
      • EVENNIA_SUPERUSER_EMAIL is optional. If not given, empty string is used.

      • +
      +

      These envvars will only be used on the very first server start and then ignored. For example:

      +
      EVENNIA_SUPERUSER_USERNAME=myname EVENNIA_SUPERUSER_PASSWORD=mypwd evennia start
      +
      +
      +
      @@ -68,7 +82,7 @@

      Links

      @@ -84,8 +98,7 @@

      Versions

      @@ -102,7 +115,7 @@ modules | - +
      develop branch
      diff --git a/docs/1.0-dev/Setup/Installation-Troubleshooting.html b/docs/1.0-dev/Setup/Installation-Troubleshooting.html new file mode 100644 index 0000000000..52722d382e --- /dev/null +++ b/docs/1.0-dev/Setup/Installation-Troubleshooting.html @@ -0,0 +1,244 @@ + + + + + + + + + Installation Troubleshooting — Evennia 1.0-dev documentation + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Installation Troubleshooting

      +

      If you have an issue not covered here, please report it +so it can be fixed or a workaround found!

      +

      The server logs are in mygame/server/logs/. To easily view server logs in the terminal, +you can run evennia -l, or (in the future) start the server with evennia start -l.

      +
      +

      Check your Requirements

      +

      Any system that supports Python3.9+ should work. We’ll describe how to install +everything in the following sections.

      +
        +
      • Linux/Unix

      • +
      • Windows (Win7, Win8, Win10, Win11)

      • +
      • Mac OSX (>10.5 recommended)

      • +
      • Python (v3.9 and 3.10 are tested)

        +
          +
        • virtualenv for making isolated +Python environments. Installed with pip install virtualenv.

        • +
        +
      • +
      • Twisted (v22.0+)

        +
          +
        • ZopeInterface (v3.0+) - usually included in +Twisted packages

        • +
        • Linux/Mac users may need the gcc and python-dev packages or equivalent.

        • +
        • Windows users need MS Visual C++ and maybe +pypiwin32.

        • +
        +
      • +
      • Django (v4.0.1+), be warned that latest dev +version is usually untested with Evennia.

      • +
      • GIT - version control software used if you want to install the sources +(but also useful to track your own code) - Mac users can use the +git-osx-installer or the +MacPorts version.

      • +
      +
      +
      +

      Confusion of location (GIT installation)

      +

      It’s common to be confused and install Evennia in the wrong location. After following the +git install instructions, the folder structure should look like this:

      +
      muddev/
      +    evenv/
      +    evennia/
      +    mygame/
      +
      +
      +

      The evennia code itself is found inside evennia/evennia/ (so two levels down). Your settings file +is mygame/server/conf/settings.py and the parent setting file is evennia/evennia/settings_default.py.

      +
      +
      +

      Localhost not found

      +

      If localhost doesn’t work when trying to connect to your local game, try 127.0.0.1, which is the same thing.

      +
      +
      +

      Linux Troubleshooting

      +
        +
      • If you get an error when installing Evennia (especially with lines mentioning +failing to include Python.h) then try sudo apt-get install python3-setuptools python3-dev. +Once installed, run pip install -e evennia again.

      • +
      • When doing a git install, some not-updated Linux distributions may give errors +about a too-old setuptools or missing functools. If so, update your environment +with pip install --upgrade pip wheel setuptools. Then try pip install -e evennia again.

      • +
      • One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/ with errors +like distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('incremental>=16.10.1'). This appears possible to solve by simply updating Ubuntu +with sudo apt-get update && sudo apt-get dist-upgrade.

      • +
      • Users of Fedora (notably Fedora 24) has reported a gcc error saying the directory +/usr/lib/rpm/redhat/redhat-hardened-cc1 is missing, despite gcc itself being installed. The +confirmed work-around seems to be to +install the redhat-rpm-config package with e.g. sudo dnf install redhat-rpm-config.

      • +
      • Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues +with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to +yourself?)

      • +
      +
      +
      +

      Mac Troubleshooting

      +
        +
      • Mac users have reported a critical MemoryError when trying to start Evennia on Mac with a Python +version below 2.7.12. If you get this error, update to the latest XCode and Python2 version.

      • +
      • Some Mac users have reported not being able to connect to localhost (i.e. your own computer). If +so, try to connect to 127.0.0.1 instead, which is the same thing. Use port 4000 from mud clients +and port 4001 from the web browser as usual.

      • +
      +
      +
      +

      Windows Troubleshooting

      +
        +
      • Install Python from the Python homepage. You will +need to be a Windows Administrator to install packages.

      • +
      • When installing Python, make sure to check-mark all install options, especially the one about making Python +available on the path (you may have to scroll to see it). This allows you to +just write python in any console without first finding where the python +program actually sits on your hard drive.

      • +
      • If you get a command not found when trying to run the evennia program after installation, try closing the +Console and starting it again (remember to re-activate the virtualenv!). Sometimes Windows is not updating +its environment properly.

      • +
      • If you installed Python but the python command is not available (even in a new console), then +you might have missed installing Python on the path. In the Windows Python installer you get a list +of options for what to install. Most or all options are pre-checked except this one, and you may +even have to scroll down to see it. Reinstall Python and make sure it’s checked.

      • +
      • If your MUD client cannot connect to localhost:4000, try the equivalent 127.0.0.1:4000 +instead. Some MUD clients on Windows does not appear to understand the alias localhost.

      • +
      • Some Windows users get an error installing the Twisted ‘wheel’. A wheel is a pre-compiled binary +package for Python. A common reason for this error is that you are using a 32-bit version of Python, +but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a +slightly older Twisted version. So if, say, version 22.1 failed, install 22.0 manually with pip install twisted==22.0. Alternatively you could check that you are using the 64-bit version of Python +and uninstall any 32bit one. If so, you must then deactivate the virtualenv, delete the evenv folder +and recreate it anew with your new Python.

      • +
      • If your server won’t start, with no error messages (and no log files at all when starting from +scratch), try to start with evennia ipstart instead. If you then see an error about system cannot find the path specified, it may be that the file evennia\evennia\server\twistd.bat has the wrong +path to the twistd executable. This file is auto-generated, so try to delete it and then run +evennia start to rebuild it and see if it works. If it still doesn’t work you need to open it in a +text editor like Notepad. It’s just one line containing the path to the twistd.exe executable as +determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and +you should update the line to the real location.

      • +
      • Some users have reported issues with Windows WSL and anti-virus software during Evennia +development. Timeout errors and the inability to run evennia connections may be due to your anti- +virus software interfering. Try disabling or changing your anti-virus software settings.

      • +
      +
      +
      + + +
      +
      +
      +
      + +
      +
      + + + + \ No newline at end of file diff --git a/docs/1.0-dev/Setup/Installation-Upgrade.html b/docs/1.0-dev/Setup/Installation-Upgrade.html new file mode 100644 index 0000000000..5e5c02e999 --- /dev/null +++ b/docs/1.0-dev/Setup/Installation-Upgrade.html @@ -0,0 +1,159 @@ + + + + + + + + + Upgrading an existing installation — Evennia 1.0-dev documentation + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Upgrading an existing installation

      +

      These are steps to take when you have an exiting game dir that you want to keep. +If you don’t, you can always just delete your old game dir and initialize a fresh one using +the normal install instructions.

      +
      +

      Evennia v0.9.5 to 1.0

      +

      Prior to 1.0, all Evennia installs were Git-installs. These instructions +assume that you have a cloned evennia repo and use a virtualenv (best practices).

      +
        +
      • Make sure to stop Evennia 0.9.5 entirely with evennia stop.

      • +
      • deactivate to leave your active virtualenv.

      • +
      • Make a backup of your entire mygame folder, just to be sure!

      • +
      • Delete the old evenv folder, or rename it (in case you want to keep using 0.9.5 for a while).

      • +
      • Install Python 3.10 (recommended) or 3.9. Follow the Git-installation for your OS if needed.

      • +
      • If using virtualenv, make a new one with python3.10 -m venv evenv, then activate with source evenv/bin/activate +(linux/mac) or \evenv\Script\activate (windows)

      • +
      • cd into your evennia/ folder (you want to see the docs/, bin/ directories as well as a nested evennia/ folder)

      • +
      • Prior to 1.0 release only - do git checkout develop to switch to the develop branch. After release, this will +be found on the default master branch.

      • +
      • git pull

      • +
      • pip install -e .

      • +
      • If you want the optional extra libs, do pip install -r requirements_extra.txt.

      • +
      • Test that you can run the evennia command.

      • +
      • Next, cd to your game folder (like mygame.)

      • +
      • If you have changed mygame/web, rename the folder to web_0.9.5. If you didn’t change anything (or don’t have +anything you want to keep), you can delete it entirely.

      • +
      • Copy evennia/evennia/game_template/web to mygame/ (e.g. using cp -Rf or a file manager). This new web folder +replaces the old one and has a very different structure.

      • +
      • evennia migrate

      • +
      • evennia start

      • +
      +

      If you made extensive work in your game dir, you may well find that you need to do some (hopefully minor) +changes to your code before it will start with Evennia 1.0. Some important points:

      +
        +
      • The evennia/contrib/ folder changed structure - there are now categorized sub-folders, so you have to update +your imports.

      • +
      • Any web changes need to be moved back from your backup into the new structure of web/ manually.

      • +
      • See the Evennia 1.0 Changelog for all changes.

      • +
      +
      +
      + + +
      +
      +
      +
      + +
      +
      + + + + \ No newline at end of file diff --git a/docs/1.0-dev/Setup/Setup-Quickstart.html b/docs/1.0-dev/Setup/Installation.html similarity index 59% rename from docs/1.0-dev/Setup/Setup-Quickstart.html rename to docs/1.0-dev/Setup/Installation.html index b36b8b7876..64b77b2b6f 100644 --- a/docs/1.0-dev/Setup/Setup-Quickstart.html +++ b/docs/1.0-dev/Setup/Installation.html @@ -6,7 +6,7 @@ - Setup quickstart — Evennia 1.0-dev documentation + Installation — Evennia 1.0-dev documentation @@ -18,9 +18,7 @@ - - - + @@ -48,36 +40,36 @@
      -
      -

      Setup quickstart

      -

      The Evennia server is installed, run and maintained from the terminal (console/CMD on Windows). Starting the -server doesn’t make anything visible online. Once you download everything you can in fact develop your game -in complete isolation if you want, without needing any access to the internet.

      -
      -

      Installation

      -

      Evennia supports Python 3.7 to 3.9. As with most Python packages, using a -virtualenv is recommended in order to keep your -installation independent from the system libraries. It’s not recommended -to install Evennia as superuser.

      +
      +

      Installation

      +

      The Evennia server is installed, run and maintained from the terminal (console/CMD on Windows). Starting the server +doesn’t make anything visible online. Once you download everything you can in fact develop your game in complete +isolation if you want, without needing any access to the internet.

      +

      Evennia requires Python 3.9 or 3.10. +Using a Python virtualenv is highly recommended in order to keep your +Evennia installation independent from the system libraries. Don’t install Evennia as +administrator or superuser.

      Warning

      -

      This is not yet available. Switch to the 0.9.5 version of these docs to install Evennia.

      +

      pip install evennia is not yet available in develop branch. Use the git installation.

      +
      +
      +

      Warning

      +

      If you are converting an existing game from a previous version, see here.

      pip install evennia
       
      -

      Make sure the evennia command works. Use evennia -h for usage help (or read on).

      -

      If you are having trouble, want to install in some other way (like with Docker) -or want to contribute to Evennia itself, check out the Extended Installation -instructions. It also has a troubleshooting -section for different operating -systems.

      -
      +

      Once installed, make sure the evennia command works. Use evennia -h for usage help. If you are using a +virtualenv, make sure it’s active whenever you need to use the evennia command.

      +

      Alternatively, you can install Evennia from github or use docker. +Check out installation troubleshooting if you run into problems. Some +users have also experimented with installing Evennia on Android.

      Initialize a new game

      Use cd to enter a folder where you want to do your game development. Here (and in the rest of the Evennia documentation) we call this folder mygame, but you should of course -name your game whatever you like:

      +name your game whatever you like.

      evennia --init mygame
       
      @@ -86,8 +78,8 @@ contains empty templates and all the default settings needed to start the server

      Start the new game

      -

      cd into your game folder (mygame in our case). Next, run

      -
      evennia migrate
      +
      cd mygame 
      +evennia migrate
       

      This will create the default database (Sqlite3). The database file ends up as mygame/server/evennia.db3. If you @@ -95,16 +87,33 @@ ever want to start from a fresh database, just delete this file and re-run

      evennia start
       
      -

      Set your user-name and password when prompted. This will be the “god user” or “superuser” in-game. The email is optional.

      +

      Set your user-name and password when prompted. This will be the “god user” or “superuser” in-game. +The email is optional.

      +
      +

      You can also automate the creation of the super user.

      +

      If all went well, the server is now up and running. Point a legacy MUD/telnet client to localhost:4000 or a web browser at http://localhost:4001 to play your new (if empty) game!

      -
      -

      If localhost doesn’t work on your computer, use 127.0.0.1, which is the same thing.

      -
      +

      Log in as a new account or use the superuser you just created.

      +
      +
      +

      Restarting and stopping

      +

      You can restart the server without disconnecting players:

      +
      evennia restart
      +
      +
      +

      To do a full stop and restart (will disconnect players):

      +
      evennia reboot
      +
      +
      +

      Full stop of the server (use evennia start to restart):

      +
      evennia stop
      +
      +

      See server logs

      -

      This will echol the server logs to the terminal as they come in:

      +

      Log files are in mygame/server/logs. You can tail them live with

      evennia --log
       
      @@ -112,30 +121,27 @@ a web browser at http
      evennia -l
       
      -

      You can also start logging immediately when running evennia commands, such as

      +

      You can start viewing the log immediately when running evennia commands, such as

      evennia start -l
       
      -

      To exit the log view, enter Ctrl-C (Cmd-C for Mac). This will not affect the server.

      +

      To exit the log tailing, enter Ctrl-C (Cmd-C for Mac). This will not affect the server.

      -
      -

      Restarting and stopping

      -

      You can restart the server without disconnecting any connected players:

      -
      evennia restart
      -
      -
      -

      To do a full stop and restart (will disconnect everyone):

      -
      evennia reboot
      -
      -
      -

      Full stop of the server (will need to use start to activate it again):

      -
      evennia stop
      -
      -
      +
      +

      Server configuration

      +

      The server configuration file is mygame/server/settings.py. It’s empty by default. Copy and change +only the settings you want from the default settings file.

      -
      -

      The Next step

      -

      Why not head into the Starting Tutorial to learn how to start making your new game!

      +
      +

      The Next steps

      +

      You are good to go!

      +

      Evennia comes with a small Tutorial World to experiment and learn from. After logging +in, you can create it by running

      +
      batchcommand tutorial_world.build
      +
      +
      +

      Next, why not head into the Starting Tutorial +to learn how to start making your new game!

      @@ -161,27 +167,21 @@ a web browser at http

      Table of Contents

      Versions

      @@ -214,14 +213,8 @@ a web browser at http
    29. modules |
    30. -
    31. - next |
    32. -
    33. - previous |
    34. - +
      develop branch
      diff --git a/docs/1.0-dev/Setup/Online-Setup.html b/docs/1.0-dev/Setup/Online-Setup.html index db966e83e8..0e0ad050a1 100644 --- a/docs/1.0-dev/Setup/Online-Setup.html +++ b/docs/1.0-dev/Setup/Online-Setup.html @@ -380,7 +380,7 @@ game stays online. Many services guarantee a certain level of up-time and also d for you. Make sure to check, some offer lower rates in exchange for you yourself being fully responsible for your data/backups.

    35. Usually offers a fixed domain name, so no need to mess with IP addresses.

    36. -
    37. May have the ability to easily deploy docker versions of evennia +

    38. May have the ability to easily deploy docker versions of evennia and/or your game.

    39. Disadvantages

      @@ -398,7 +398,7 @@ you have no choice but to sit it out (but you’ll hopefully be warned ahead of Docker) to deploy your game to the remote server; it will likely ease installation and deployment. Docker images may be a little confusing if you are completely new to them though.

      If not using docker, and assuming you know how to connect to your account over ssh/PuTTy, you should -be able to follow the Setup Quickstart instructions normally. You only need Python +be able to follow the Setup Quickstart instructions normally. You only need Python and GIT pre-installed; these should both be available on any servers (if not you should be able to easily ask for them to be installed). On a VPS or Cloud service you can install them yourself as needed.

      @@ -574,7 +574,6 @@ sufficient resources to operate a Cloud9 development environment without charge.

      Versions

      diff --git a/docs/1.0-dev/Setup/RSS.html b/docs/1.0-dev/Setup/RSS.html index 5b042f9062..feef0d3a24 100644 --- a/docs/1.0-dev/Setup/RSS.html +++ b/docs/1.0-dev/Setup/RSS.html @@ -138,7 +138,6 @@ same channels as 1.0-dev (develop branch) -
    40. 0.9.5 (v0.9.5 branch)
    41. diff --git a/docs/1.0-dev/Setup/Security.html b/docs/1.0-dev/Setup/Security.html index 6777122808..3e959700b2 100644 --- a/docs/1.0-dev/Setup/Security.html +++ b/docs/1.0-dev/Setup/Security.html @@ -250,7 +250,6 @@ ISP snooping.

      Versions

      diff --git a/docs/1.0-dev/Setup/Server-Conf.html b/docs/1.0-dev/Setup/Server-Conf.html index d7f6d44c5a..54723de7d7 100644 --- a/docs/1.0-dev/Setup/Server-Conf.html +++ b/docs/1.0-dev/Setup/Server-Conf.html @@ -48,7 +48,7 @@ ways to customize the server and expand it with your own plugins.

      Settings file

      The “Settings” file referenced throughout the documentation is the file mygame/server/conf/settings.py. This is automatically created on the first run of evennia --init -(see the Setup Quickstart page).

      +(see the Setup Quickstart page).

      Your new settings.py is relatively bare out of the box. Evennia’s core settings file is actually evennia/settings_default.py and is considerably more extensive (it is also heavily documented so you should refer to this file @@ -191,7 +191,6 @@ know about if you are an Evennia developer.

      Versions

      diff --git a/docs/1.0-dev/Setup/Settings-Default.html b/docs/1.0-dev/Setup/Settings-Default.html new file mode 100644 index 0000000000..9f4e475688 --- /dev/null +++ b/docs/1.0-dev/Setup/Settings-Default.html @@ -0,0 +1,1307 @@ + + + + + + + + + Evennia Default settings file — Evennia 1.0-dev documentation + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Evennia Default settings file

      +

      Master file is located at evennia/evennia/settings_default.py. Read +its comments to see what each setting does and copy only what you want +to change into mygame/server/conf/settings.py.

      +

      Example of accessing settings:

      +
      from django.conf import settings
      +
      +if settings.SERVERNAME == "Evennia":
      +    print("Yay!")
      +
      +
      +
      +
      """
      +Master configuration file for Evennia.
      +
      +NOTE: NO MODIFICATIONS SHOULD BE MADE TO THIS FILE!
      +
      +All settings changes should be done by copy-pasting the variable and
      +its value to <gamedir>/server/conf/settings.py.
      +
      +Hint: Don't copy&paste over more from this file than you actually want
      +to change.  Anything you don't copy&paste will thus retain its default
      +value - which may change as Evennia is developed. This way you can
      +always be sure of what you have changed and what is default behaviour.
      +
      +"""
      +from django.contrib.messages import constants as messages
      +from django.urls import reverse_lazy
      +
      +import os
      +import sys
      +
      +######################################################################
      +# Evennia base server config
      +######################################################################
      +
      +# This is the name of your game. Make it catchy!
      +SERVERNAME = "Evennia"
      +# Short one-sentence blurb describing your game. Shown under the title
      +# on the website and could be used in online listings of your game etc.
      +GAME_SLOGAN = "The Python MUD/MU* creation system"
      +# The url address to your server, like mymudgame.com. This should be the publicly
      +# visible location. This is used e.g. on the web site to show how you connect to the
      +# game over telnet. Default is localhost (only on your machine).
      +SERVER_HOSTNAME = "localhost"
      +# Lockdown mode will cut off the game from any external connections
      +# and only allow connections from localhost. Requires a cold reboot.
      +LOCKDOWN_MODE = False
      +# Activate telnet service
      +TELNET_ENABLED = True
      +# A list of ports the Evennia telnet server listens on Can be one or many.
      +TELNET_PORTS = [4000]
      +# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
      +TELNET_INTERFACES = ["0.0.0.0"]
      +# Activate Telnet+SSL protocol (SecureSocketLibrary) for supporting clients
      +SSL_ENABLED = False
      +# Ports to use for Telnet+SSL
      +SSL_PORTS = [4003]
      +# Telnet+SSL Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
      +SSL_INTERFACES = ["0.0.0.0"]
      +# OOB (out-of-band) telnet communication allows Evennia to communicate
      +# special commands and data with enabled Telnet clients. This is used
      +# to create custom client interfaces over a telnet connection. To make
      +# full use of OOB, you need to prepare functions to handle the data
      +# server-side (see INPUT_FUNC_MODULES). TELNET_ENABLED is required for this
      +# to work.
      +TELNET_OOB_ENABLED = False
      +# Activate SSH protocol communication (SecureShell)
      +SSH_ENABLED = False
      +# Ports to use for SSH
      +SSH_PORTS = [4004]
      +# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
      +SSH_INTERFACES = ["0.0.0.0"]
      +# Start the evennia django+twisted webserver so you can
      +# browse the evennia website and the admin interface
      +# (Obs - further web configuration can be found below
      +# in the section  'Config for Django web features')
      +WEBSERVER_ENABLED = True
      +# This is a security setting protecting against host poisoning
      +# attacks.  It defaults to allowing all. In production, make
      +# sure to change this to your actual host addresses/IPs.
      +ALLOWED_HOSTS = ["*"]
      +# The webserver sits behind a Portal proxy. This is a list
      +# of tuples (proxyport,serverport) used. The proxyports are what
      +# the Portal proxy presents to the world. The serverports are
      +# the internal ports the proxy uses to forward data to the Server-side
      +# webserver (these should not be publicly open)
      +WEBSERVER_PORTS = [(4001, 4005)]
      +# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
      +WEBSERVER_INTERFACES = ["0.0.0.0"]
      +# IP addresses that may talk to the server in a reverse proxy configuration,
      +# like NginX.
      +UPSTREAM_IPS = ["127.0.0.1"]
      +# The webserver uses threadpool for handling requests. This will scale
      +# with server load. Set the minimum and maximum number of threads it
      +# may use as (min, max) (must be > 0)
      +WEBSERVER_THREADPOOL_LIMITS = (1, 20)
      +# Start the evennia webclient. This requires the webserver to be running and
      +# offers the fallback ajax-based webclient backbone for browsers not supporting
      +# the websocket one.
      +WEBCLIENT_ENABLED = True
      +# Activate Websocket support for modern browsers. If this is on, the
      +# default webclient will use this and only use the ajax version if the browser
      +# is too old to support websockets. Requires WEBCLIENT_ENABLED.
      +WEBSOCKET_CLIENT_ENABLED = True
      +# Server-side websocket port to open for the webclient. Note that this value will
      +# be dynamically encoded in the webclient html page to allow the webclient to call
      +# home. If the external encoded value needs to be different than this, due to
      +# working through a proxy or docker port-remapping, the environment variable
      +# WEBCLIENT_CLIENT_PROXY_PORT can be used to override this port only for the
      +# front-facing client's sake.
      +WEBSOCKET_CLIENT_PORT = 4002
      +# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
      +WEBSOCKET_CLIENT_INTERFACE = "0.0.0.0"
      +# Actual URL for webclient component to reach the websocket. You only need
      +# to set this if you know you need it, like using some sort of proxy setup.
      +# If given it must be on the form "ws[s]://hostname[:port]". If left at None,
      +# the client will itself figure out this url based on the server's hostname.
      +# e.g. ws://external.example.com or wss://external.example.com:443
      +WEBSOCKET_CLIENT_URL = None
      +# This determine's whether Evennia's custom admin page is used, or if the
      +# standard Django admin is used.
      +EVENNIA_ADMIN = True
      +# The Server opens an AMP port so that the portal can
      +# communicate with it. This is an internal functionality of Evennia, usually
      +# operating between two processes on the same machine. You usually don't need to
      +# change this unless you cannot use the default AMP port/host for
      +# whatever reason.
      +AMP_HOST = "localhost"
      +AMP_PORT = 4006
      +AMP_INTERFACE = "127.0.0.1"
      +
      +
      +# Path to the lib directory containing the bulk of the codebase's code.
      +EVENNIA_DIR = os.path.dirname(os.path.abspath(__file__))
      +# Path to the game directory (containing the server/conf/settings.py file)
      +# This is dynamically created- there is generally no need to change this!
      +if EVENNIA_DIR.lower() == os.getcwd().lower() or (
      +    sys.argv[1] == "test" if len(sys.argv) > 1 else False
      +):
      +    # unittesting mode
      +    GAME_DIR = os.getcwd()
      +else:
      +    # Fallback location (will be replaced by the actual game dir at runtime)
      +    GAME_DIR = os.path.join(EVENNIA_DIR, "game_template")
      +    for i in range(10):
      +        gpath = os.getcwd()
      +        if "server" in os.listdir(gpath):
      +            if os.path.isfile(os.path.join("server", "conf", "settings.py")):
      +                GAME_DIR = gpath
      +                break
      +        os.chdir(os.pardir)
      +# Place to put log files, how often to rotate the log and how big each log file
      +# may become before rotating.
      +LOG_DIR = os.path.join(GAME_DIR, "server", "logs")
      +SERVER_LOG_FILE = os.path.join(LOG_DIR, "server.log")
      +SERVER_LOG_DAY_ROTATION = 7
      +SERVER_LOG_MAX_SIZE = 1000000
      +PORTAL_LOG_FILE = os.path.join(LOG_DIR, "portal.log")
      +PORTAL_LOG_DAY_ROTATION = 7
      +PORTAL_LOG_MAX_SIZE = 1000000
      +# The http log is usually only for debugging since it's very spammy
      +HTTP_LOG_FILE = os.path.join(LOG_DIR, "http_requests.log")
      +# if this is set to the empty string, lockwarnings will be turned off.
      +LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log")
      +# Number of lines to append to rotating channel logs when they rotate
      +CHANNEL_LOG_NUM_TAIL_LINES = 20
      +# Max size (in bytes) of channel log files before they rotate
      +CHANNEL_LOG_ROTATE_SIZE = 1000000
      +# Unused by default, but used by e.g. the MapSystem contrib. A place for storing
      +# semi-permanent data and avoid it being rebuilt over and over. It is created
      +# on-demand only.
      +CACHE_DIR = os.path.join(GAME_DIR, "server", ".cache")
      +# Local time zone for this installation. All choices can be found here:
      +# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
      +TIME_ZONE = "UTC"
      +# Activate time zone in datetimes
      +USE_TZ = True
      +# Authentication backends. This is the code used to authenticate a user.
      +AUTHENTICATION_BACKENDS = ["evennia.web.utils.backends.CaseInsensitiveModelBackend"]
      +# Language code for this installation. All choices can be found here:
      +# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
      +LANGUAGE_CODE = "en-us"
      +# How long time (in seconds) a user may idle before being logged
      +# out. This can be set as big as desired. A user may avoid being
      +# thrown off by sending the empty system command 'idle' to the server
      +# at regular intervals. Set <=0 to deactivate idle timeout completely.
      +IDLE_TIMEOUT = -1
      +# The idle command can be sent to keep your session active without actually
      +# having to spam normal commands regularly. It gives no feedback, only updates
      +# the idle timer. Note that "idle" will *always* work, even if a different
      +# command-name is given here; this is because the webclient needs a default
      +# to send to avoid proxy timeouts.
      +IDLE_COMMAND = "idle"
      +# The set of encodings tried. An Account object may set an attribute "encoding" on
      +# itself to match the client used. If not set, or wrong encoding is
      +# given, this list is tried, in order, aborting on the first match.
      +# Add sets for languages/regions your accounts are likely to use.
      +# (see http://en.wikipedia.org/wiki/Character_encoding)
      +# Telnet default encoding, unless specified by the client, will be ENCODINGS[0].
      +ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"]
      +# Regular expression applied to all output to a given session in order
      +# to strip away characters (usually various forms of decorations) for the benefit
      +# of users with screen readers. Note that ANSI/MXP doesn't need to
      +# be stripped this way, that is handled automatically.
      +SCREENREADER_REGEX_STRIP = r"\+-+|\+$|\+~|--+|~~+|==+"
      +# MXP support means the ability to show clickable links in the client. Clicking
      +# the link will execute a game command. It's a way to add mouse input to the game.
      +MXP_ENABLED = True
      +# If this is set, MXP can only be sent by the server and not added from the
      +# client side. Disabling this is a potential security risk because it could
      +# allow malevolent players to lure others to execute commands they did not
      +# intend to.
      +MXP_OUTGOING_ONLY = True
      +# Database objects are cached in what is known as the idmapper. The idmapper
      +# caching results in a massive speedup of the server (since it dramatically
      +# limits the number of database accesses needed) and also allows for
      +# storing temporary data on objects. It is however also the main memory
      +# consumer of Evennia. With this setting the cache can be capped and
      +# flushed when it reaches a certain size. Minimum is 50 MB but it is
      +# not recommended to set this to less than 100 MB for a distribution
      +# system.
      +# Empirically, N_objects_in_cache ~ ((RMEM - 35) / 0.0157):
      +#  mem(MB)   |  objs in cache   ||   mem(MB)   |   objs in cache
      +#      50    |       ~1000      ||      800    |     ~49 000
      +#     100    |       ~4000      ||     1200    |     ~75 000
      +#     200    |      ~10 000     ||     1600    |    ~100 000
      +#     500    |      ~30 000     ||     2000    |    ~125 000
      +# Note that the estimated memory usage is not exact (and the cap is only
      +# checked every 5 minutes), so err on the side of caution if
      +# running on a server with limited memory. Also note that Python
      +# will not necessarily return the memory to the OS when the idmapper
      +# flashes (the memory will be freed and made available to the Python
      +# process only). How many objects need to be in memory at any given
      +# time depends very much on your game so some experimentation may
      +# be necessary (use @server to see how many objects are in the idmapper
      +# cache at any time). Setting this to None disables the cache cap.
      +IDMAPPER_CACHE_MAXSIZE = 200  # (MB)
      +# This determines how many connections per second the Portal should
      +# accept, as a DoS countermeasure. If the rate exceeds this number, incoming
      +# connections will be queued to this rate, so none will be lost.
      +# Must be set to a value > 0.
      +MAX_CONNECTION_RATE = 2
      +# Determine how many commands per second a given Session is allowed
      +# to send to the Portal via a connected protocol. Too high rate will
      +# drop the command and echo a warning. Note that this will also cap
      +# OOB messages so don't set it too low if you expect a lot of events
      +# from the client! To turn the limiter off, set to <= 0.
      +MAX_COMMAND_RATE = 80
      +# The warning to echo back to users if they send commands too fast
      +COMMAND_RATE_WARNING = "You entered commands too fast. Wait a moment and try again."
      +# custom, extra commands to add to the `evennia` launcher. This is a dict
      +# of {'cmdname': 'path.to.callable', ...}, where the callable will be passed
      +# any extra args given on the command line. For example `evennia cmdname foo bar`.
      +EXTRA_LAUNCHER_COMMANDS = {}
      +
      +# Determine how large of a string can be sent to the server in number
      +# of characters. If they attempt to enter a string over this character
      +# limit, we stop them and send a message. To make unlimited, set to
      +# 0 or less.
      +MAX_CHAR_LIMIT = 6000
      +# The warning to echo back to users if they enter a very large string
      +MAX_CHAR_LIMIT_WARNING = (
      +    "You entered a string that was too long. " "Please break it up into multiple parts."
      +)
      +# If this is true, errors and tracebacks from the engine will be
      +# echoed as text in-game as well as to the log. This can speed up
      +# debugging. OBS: Showing full tracebacks to regular users could be a
      +# security problem -turn this off in a production game!
      +IN_GAME_ERRORS = True
      +# Broadcast "Server restart"-like messages to all sessions.
      +BROADCAST_SERVER_RESTART_MESSAGES = True
      +
      +######################################################################
      +# Evennia Database config
      +######################################################################
      +
      +# Database config syntax:
      +# ENGINE - path to the the database backend. Possible choices are:
      +#            'django.db.backends.sqlite3', (default)
      +#            'django.db.backends.mysql',
      +#            'django.db.backends.postgresql',
      +#            'django.db.backends.oracle' (untested).
      +# NAME - database name, or path to the db file for sqlite3
      +# USER - db admin (unused in sqlite3)
      +# PASSWORD - db admin password (unused in sqlite3)
      +# HOST - empty string is localhost (unused in sqlite3)
      +# PORT - empty string defaults to localhost (unused in sqlite3)
      +DATABASES = {
      +    "default": {
      +        "ENGINE": "django.db.backends.sqlite3",
      +        "NAME": os.getenv("TEST_DB_PATH", os.path.join(GAME_DIR, "server", "evennia.db3")),
      +        "USER": "",
      +        "PASSWORD": "",
      +        "HOST": "",
      +        "PORT": "",
      +    }
      +}
      +# How long the django-database connection should be kept open, in seconds.
      +# If you get errors about the database having gone away after long idle
      +# periods, shorten this value (e.g. MySQL defaults to a timeout of 8 hrs)
      +CONN_MAX_AGE = 3600 * 7
      +# When removing or renaming models, such models stored in Attributes may
      +# become orphaned and will return as None. If the change is a rename (that
      +# is, there is a 1:1 pk mapping between the old and the new), the unserializer
      +# can convert old to new when retrieving them. This is a list of tuples
      +# (old_natural_key, new_natural_key). Note that Django ContentTypes'
      +# natural_keys are themselves tuples (appname, modelname). Creation-dates will
      +# not be checked for models specified here. If new_natural_key does not exist,
      +# `None` will be returned and stored back as if no replacement was set.
      +ATTRIBUTE_STORED_MODEL_RENAME = [
      +    (("players", "playerdb"), ("accounts", "accountdb")),
      +    (("typeclasses", "defaultplayer"), ("typeclasses", "defaultaccount")),
      +]
      +# Default type of autofield (required by Django)
      +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
      +
      +######################################################################
      +# Evennia webclient options
      +######################################################################
      +
      +# default webclient options (without user changing it)
      +WEBCLIENT_OPTIONS = {
      +    # Gags prompts in output window and puts them on the input bar
      +    "gagprompt": True,
      +    # Shows help files in a new popup window instead of in-pane
      +    "helppopup": False,
      +    # Shows notifications of new messages as popup windows
      +    "notification_popup": False,
      +    # Plays a sound for notifications of new messages
      +    "notification_sound": False
      +}
      +
      +######################################################################
      +# Evennia pluggable modules
      +######################################################################
      +# Plugin modules extend Evennia in various ways. In the cases with no
      +# existing default, there are examples of many of these modules
      +# in contrib/examples.
      +
      +# The command parser module to use. See the default module for which
      +# functions it must implement
      +COMMAND_PARSER = "evennia.commands.cmdparser.cmdparser"
      +# On a multi-match when search objects or commands, the user has the
      +# ability to search again with an index marker that differentiates
      +# the results. If multiple "box" objects
      +# are found, they can by default be separated as 1-box, 2-box. Below you
      +# can change the regular expression used. The regex must have one
      +# have two capturing groups (?P<number>...) and (?P<name>...) - the default
      +# parser expects this. It should also involve a number starting from 1.
      +# When changing this you must also update SEARCH_MULTIMATCH_TEMPLATE
      +# to properly describe the syntax.
      +SEARCH_MULTIMATCH_REGEX = r"(?P<name>[^-]*)-(?P<number>[0-9]+)(?P<args>.*)"
      +# To display multimatch errors in various listings we must display
      +# the syntax in a way that matches what SEARCH_MULTIMATCH_REGEX understand.
      +# The template will be populated with data and expects the following markup:
      +# {number} - the order of the multimatch, starting from 1; {name} - the
      +# name (key) of the multimatched entity; {aliases} - eventual
      +# aliases for the entity; {info} - extra info like #dbrefs for staff. Don't
      +# forget a line break if you want one match per line.
      +SEARCH_MULTIMATCH_TEMPLATE = " {name}-{number}{aliases}{info}\n"
      +# The handler that outputs errors when using any API-level search
      +# (not manager methods). This function should correctly report errors
      +# both for command- and object-searches. This allows full control
      +# over the error output (it uses SEARCH_MULTIMATCH_TEMPLATE by default).
      +SEARCH_AT_RESULT = "evennia.utils.utils.at_search_result"
      +# Single characters to ignore at the beginning of a command. When set, e.g.
      +# cmd, @cmd and +cmd will all find a command "cmd" or one named "@cmd" etc. If
      +# you have defined two different commands cmd and @cmd you can still enter
      +# @cmd to exactly target the second one. Single-character commands consisting
      +# of only a prefix character will not be stripped. Set to the empty
      +# string ("") to turn off prefix ignore.
      +CMD_IGNORE_PREFIXES = "@&/+"
      +# The module holding text strings for the connection screen.
      +# This module should contain one or more variables
      +# with strings defining the look of the screen.
      +CONNECTION_SCREEN_MODULE = "server.conf.connection_screens"
      +# Delay to use before sending the evennia.syscmdkeys.CMD_LOGINSTART Command
      +# when a new session connects (this defaults the unloggedin-look for showing
      +# the connection screen). The delay is useful mainly for telnet, to allow
      +# client/server to establish client capabilities like color/mxp etc before
      +# sending any text. A value of 0.3 should be enough. While a good idea, it may
      +# cause issues with menu-logins and autoconnects since the menu will not have
      +# started when the autoconnects starts sending menu commands.
      +DELAY_CMD_LOGINSTART = 0.3
      +# A module that must exist - this holds the instructions Evennia will use to
      +# first prepare the database for use (create user #1 and Limbo etc). Only override if
      +# you really know what # you are doing. If replacing, it must contain a function
      +# handle_setup(stepname=None). The function will start being called with no argument
      +# and is expected to maintain a named sequence of steps. Once each step is completed, it
      +# should be saved with ServerConfig.objects.conf('last_initial_setup_step', stepname)
      +# on a crash, the system will continue by calling handle_setup with the last completed
      +# step. The last step in the sequence must be named 'done'. Once this key is saved,
      +# initialization will not run again.
      +INITIAL_SETUP_MODULE = "evennia.server.initial_setup"
      +# An optional module that, if existing, must hold a function
      +# named at_initial_setup(). This hook method can be used to customize
      +# the server's initial setup sequence (the very first startup of the system).
      +# The check will fail quietly if module doesn't exist or fails to load.
      +AT_INITIAL_SETUP_HOOK_MODULE = "server.conf.at_initial_setup"
      +# Module containing your custom at_server_start(), at_server_reload() and
      +# at_server_stop() methods. These methods will be called every time
      +# the server starts, reloads and resets/stops respectively.
      +AT_SERVER_STARTSTOP_MODULE = "server.conf.at_server_startstop"
      +# List of one or more module paths to modules containing a function start_
      +# plugin_services(application). This module will be called with the main
      +# Evennia Server application when the Server is initiated.
      +# It will be called last in the startup sequence.
      +SERVER_SERVICES_PLUGIN_MODULES = ["server.conf.server_services_plugins"]
      +# List of one or more module paths to modules containing a function
      +# start_plugin_services(application). This module will be called with the
      +# main Evennia Portal application when the Portal is initiated.
      +# It will be called last in the startup sequence.
      +PORTAL_SERVICES_PLUGIN_MODULES = ["server.conf.portal_services_plugins"]
      +# Module holding MSSP meta data. This is used by MUD-crawlers to determine
      +# what type of game you are running, how many accounts you have etc.
      +MSSP_META_MODULE = "server.conf.mssp"
      +# Module for web plugins.
      +WEB_PLUGINS_MODULE = "server.conf.web_plugins"
      +# Tuple of modules implementing lock functions. All callable functions
      +# inside these modules will be available as lock functions.
      +LOCK_FUNC_MODULES = ("evennia.locks.lockfuncs", "server.conf.lockfuncs")
      +# Module holding handlers for managing incoming data from the client. These
      +# will be loaded in order, meaning functions in later modules may overload
      +# previous ones if having the same name.
      +INPUT_FUNC_MODULES = ["evennia.server.inputfuncs", "server.conf.inputfuncs"]
      +# Modules that contain prototypes for use with the spawner mechanism.
      +PROTOTYPE_MODULES = ["world.prototypes"]
      +# Modules containining Prototype functions able to be embedded in prototype
      +# definitions from in-game.
      +PROT_FUNC_MODULES = ["evennia.prototypes.protfuncs"]
      +# Module holding settings/actions for the dummyrunner program (see the
      +# dummyrunner for more information)
      +DUMMYRUNNER_SETTINGS_MODULE = "evennia.server.profiling.dummyrunner_settings"
      +# Mapping to extend Evennia's normal ANSI color tags. The mapping is a list of
      +# tuples mapping the exact tag (not a regex!) to the ANSI convertion, like
      +# `(r"%c%r", ansi.ANSI_RED)` (the evennia.utils.ansi module contains all
      +# ANSI escape sequences). Default is to use `|` and `|[` -prefixes.
      +COLOR_ANSI_EXTRA_MAP = []
      +# Extend the available regexes for adding XTERM256 colors in-game. This is given
      +# as a list of regexes, where each regex must contain three anonymous groups for
      +# holding integers 0-5 for the red, green and blue components Default is
      +# is r'\|([0-5])([0-5])([0-5])', which allows e.g. |500 for red.
      +# XTERM256 foreground color replacement
      +COLOR_XTERM256_EXTRA_FG = []
      +# XTERM256 background color replacement. Default is \|\[([0-5])([0-5])([0-5])'
      +COLOR_XTERM256_EXTRA_BG = []
      +# Extend the available regexes for adding XTERM256 grayscale values in-game. Given
      +# as a list of regexes, where each regex must contain one anonymous group containing
      +# a single letter a-z to mark the level from white to black. Default is r'\|=([a-z])',
      +# which allows e.g. |=k for a medium gray.
      +# XTERM256 grayscale foreground
      +COLOR_XTERM256_EXTRA_GFG = []
      +# XTERM256 grayscale background. Default is \|\[=([a-z])'
      +COLOR_XTERM256_EXTRA_GBG = []
      +# ANSI does not support bright backgrounds, so Evennia fakes this by mapping it to
      +# XTERM256 backgrounds where supported. This is a list of tuples that maps the wanted
      +# ansi tag (not a regex!) to a valid XTERM256 background tag, such as `(r'{[r', r'{[500')`.
      +COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = []
      +# If set True, the above color settings *replace* the default |-style color markdown
      +# rather than extend it.
      +COLOR_NO_DEFAULT = False
      +
      +
      +######################################################################
      +# Default command sets and commands
      +######################################################################
      +
      +# Command set used on session before account has logged in
      +CMDSET_UNLOGGEDIN = "commands.default_cmdsets.UnloggedinCmdSet"
      +# (Note that changing these three following cmdset paths will only affect NEW
      +# created characters/objects, not those already in play. So if you want to
      +# change this and have it apply to every object, it's recommended you do it
      +# before having created a lot of objects (or simply reset the database after
      +# the change for simplicity)).
      +# Command set used on the logged-in session
      +CMDSET_SESSION = "commands.default_cmdsets.SessionCmdSet"
      +# Default set for logged in account with characters (fallback)
      +CMDSET_CHARACTER = "commands.default_cmdsets.CharacterCmdSet"
      +# Command set for accounts without a character (ooc)
      +CMDSET_ACCOUNT = "commands.default_cmdsets.AccountCmdSet"
      +
      +# Location to search for cmdsets if full path not given
      +CMDSET_PATHS = ["commands", "evennia", "evennia.contrib"]
      +# Fallbacks for cmdset paths that fail to load. Note that if you change the path for your
      +# default cmdsets, you will also need to copy CMDSET_FALLBACKS after your change in your
      +# settings file for it to detect the change.
      +CMDSET_FALLBACKS = {
      +    CMDSET_CHARACTER: "evennia.commands.default.cmdset_character.CharacterCmdSet",
      +    CMDSET_ACCOUNT: "evennia.commands.default.cmdset_account.AccountCmdSet",
      +    CMDSET_SESSION: "evennia.commands.default.cmdset_session.SessionCmdSet",
      +    CMDSET_UNLOGGEDIN: "evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet",
      +}
      +# Parent class for all default commands. Changing this class will
      +# modify all default commands, so do so carefully.
      +COMMAND_DEFAULT_CLASS = "evennia.commands.default.muxcommand.MuxCommand"
      +# Command.arg_regex is a regular expression desribing how the arguments
      +# to the command must be structured for the command to match a given user
      +# input. By default the command-name should end with a space or / (since the
      +# default commands uses MuxCommand and /switches). Note that the extra \n
      +# is necessary for use with batchprocessor.
      +COMMAND_DEFAULT_ARG_REGEX = r'^[ /]|\n|$'
      +# By default, Command.msg will only send data to the Session calling
      +# the Command in the first place. If set, Command.msg will instead return
      +# data to all Sessions connected to the Account/Character associated with
      +# calling the Command. This may be more intuitive for users in certain
      +# multisession modes.
      +COMMAND_DEFAULT_MSG_ALL_SESSIONS = False
      +# The default lockstring of a command.
      +COMMAND_DEFAULT_LOCKS = ""
      +
      +######################################################################
      +# Typeclasses and other paths
      +######################################################################
      +
      +# These are paths that will be prefixed to the paths given if the
      +# immediately entered path fail to find a typeclass. It allows for
      +# shorter input strings. They must either base off the game directory
      +# or start from the evennia library.
      +TYPECLASS_PATHS = [
      +    "typeclasses",
      +    "evennia",
      +    "evennia.contrib",
      +    "evennia.contrib.game_systems",
      +    "evennia.contrib.base_systems",
      +    "evennia.contrib.full_systems",
      +    "evennia.contrib.tutorials",
      +    "evennia.contrib.utils",
      +]
      +
      +# Typeclass for account objects (linked to a character) (fallback)
      +BASE_ACCOUNT_TYPECLASS = "typeclasses.accounts.Account"
      +# Typeclass and base for all objects (fallback)
      +BASE_OBJECT_TYPECLASS = "typeclasses.objects.Object"
      +# Typeclass for character objects linked to an account (fallback)
      +BASE_CHARACTER_TYPECLASS = "typeclasses.characters.Character"
      +# Typeclass for rooms (fallback)
      +BASE_ROOM_TYPECLASS = "typeclasses.rooms.Room"
      +# Typeclass for Exit objects (fallback).
      +BASE_EXIT_TYPECLASS = "typeclasses.exits.Exit"
      +# Typeclass for Channel (fallback).
      +BASE_CHANNEL_TYPECLASS = "typeclasses.channels.Channel"
      +# Typeclass for Scripts (fallback). You usually don't need to change this
      +# but create custom variations of scripts on a per-case basis instead.
      +BASE_SCRIPT_TYPECLASS = "typeclasses.scripts.Script"
      +# The default home location used for all objects. This is used as a
      +# fallback if an object's normal home location is deleted. Default
      +# is Limbo (#2).
      +DEFAULT_HOME = "#2"
      +# The start position for new characters. Default is Limbo (#2).
      +#  MULTISESSION_MODE = 0, 1 - used by default unloggedin create command
      +#  MULTISESSION_MODE = 2, 3 - used by default character_create command
      +START_LOCATION = "#2"
      +# Lookups of Attributes, Tags, Nicks, Aliases can be aggressively
      +# cached to avoid repeated database hits. This often gives noticeable
      +# performance gains since they are called so often. Drawback is that
      +# if you are accessing the database from multiple processes (such as
      +# from a website -not- running Evennia's own webserver) data may go
      +# out of sync between the processes. Keep on unless you face such
      +# issues.
      +TYPECLASS_AGGRESSIVE_CACHE = True
      +# These are fallbacks for BASE typeclasses failing to load. Usually needed only
      +# during doc building. The system expects these to *always* load correctly, so
      +# only modify if you are making fundamental changes to how objects/accounts
      +# work and know what you are doing
      +FALLBACK_ACCOUNT_TYPECLASS = "evennia.accounts.accounts.DefaultAccount"
      +FALLBACK_OBJECT_TYPECLASS = "evennia.objects.objects.DefaultObject"
      +FALLBACK_CHARACTER_TYPECLASS = "evennia.objects.objects.DefaultCharacter"
      +FALLBACK_ROOM_TYPECLASS = "evennia.objects.objects.DefaultRoom"
      +FALLBACK_EXIT_TYPECLASS = "evennia.objects.objects.DefaultExit"
      +FALLBACK_CHANNEL_TYPECLASS = "evennia.comms.comms.DefaultChannel"
      +FALLBACK_SCRIPT_TYPECLASS = "evennia.scripts.scripts.DefaultScript"
      +
      +
      +######################################################################
      +# Options and validators
      +######################################################################
      +
      +# Options available on Accounts. Each such option is described by a
      +# class available from evennia.OPTION_CLASSES, in turn making use
      +# of validators from evennia.VALIDATOR_FUNCS to validate input when
      +# the user changes an option. The options are accessed through the
      +# `Account.options` handler.
      +
      +# ("Description", 'Option Class name in evennia.OPTION_CLASS_MODULES', 'Default Value')
      +
      +OPTIONS_ACCOUNT_DEFAULT = {
      +    "border_color": ("Headers, footers, table borders, etc.", "Color", "n"),
      +    "header_star_color": ("* inside Header lines.", "Color", "n"),
      +    "header_text_color": ("Text inside Header lines.", "Color", "w"),
      +    "header_fill": ("Fill for Header lines.", "Text", "="),
      +    "separator_star_color": ("* inside Separator lines.", "Color", "n"),
      +    "separator_text_color": ("Text inside Separator lines.", "Color", "w"),
      +    "separator_fill": ("Fill for Separator Lines.", "Text", "-"),
      +    "footer_star_color": ("* inside Footer lines.", "Color", "n"),
      +    "footer_text_color": ("Text inside Footer Lines.", "Color", "n"),
      +    "footer_fill": ("Fill for Footer Lines.", "Text", "="),
      +    "column_names_color": ("Table column header text.", "Color", "w"),
      +    "help_category_color": ("Help category names.", "Color", "n"),
      +    "help_entry_color": ("Help entry names.", "Color", "n"),
      +    "timezone": ("Timezone for dates. @tz for a list.", "Timezone", "UTC"),
      +}
      +# Modules holding Option classes, responsible for serializing the option and
      +# calling validator functions on it. Same-named functions in modules added
      +# later in this list will override those added earlier.
      +OPTION_CLASS_MODULES = ["evennia.utils.optionclasses"]
      +# Module holding validator functions. These are used as a resource for
      +# validating options, but can also be used as input validators in general.
      +# Same-named functions in modules added later in this list will override those
      +# added earlier.
      +VALIDATOR_FUNC_MODULES = ["evennia.utils.validatorfuncs"]
      +
      +######################################################################
      +# Batch processors
      +######################################################################
      +
      +# Python path to a directory to be searched for batch scripts
      +# for the batch processors (.ev and/or .py files).
      +BASE_BATCHPROCESS_PATHS = [
      +    "world",
      +    "evennia.contrib",
      +    "evennia.contrib.tutorials",
      +]
      +
      +######################################################################
      +# Game Time setup
      +######################################################################
      +
      +# You don't actually have to use this, but it affects the routines in
      +# evennia.utils.gametime.py and allows for a convenient measure to
      +# determine the current in-game time. You can of course interpret
      +# "week", "month" etc as your own in-game time units as desired.
      +
      +# The time factor dictates if the game world runs faster (timefactor>1)
      +# or slower (timefactor<1) than the real world.
      +TIME_FACTOR = 2.0
      +# The starting point of your game time (the epoch), in seconds.
      +# In Python a value of 0 means Jan 1 1970 (use negatives for earlier
      +# start date). This will affect the returns from the utils.gametime
      +# module. If None, the server's first start-time is used as the epoch.
      +TIME_GAME_EPOCH = None
      +# Normally, game time will only increase when the server runs. If this is True,
      +# game time will not pause when the server reloads or goes offline. This setting
      +# together with a time factor of 1 should keep the game in sync with
      +# the real time (add a different epoch to shift time)
      +TIME_IGNORE_DOWNTIMES = False
      +
      +######################################################################
      +# Help system
      +######################################################################
      +# Help output from CmdHelp are wrapped in an EvMore call
      +# (excluding webclient with separate help popups). If continuous scroll
      +# is preferred, change 'HELP_MORE' to False. EvMORE uses CLIENT_DEFAULT_HEIGHT
      +HELP_MORE_ENABLED = True
      +# The help category of a command if not specified.
      +COMMAND_DEFAULT_HELP_CATEGORY = "general"
      +# The help category of a db or file-based help entry if not specified
      +DEFAULT_HELP_CATEGORY = "general"
      +# File-based help entries. These are modules containing dicts defining help
      +# entries. They can be used together with in-database entries created in-game.
      +FILE_HELP_ENTRY_MODULES = ["world.help_entries"]
      +# if topics listed in help should be clickable
      +# clickable links only work on clients that support MXP.
      +HELP_CLICKABLE_TOPICS = True
      +
      +######################################################################
      +# FuncParser
      +#
      +# Strings parsed with the FuncParser can contain 'callables' on the
      +# form $funcname(args,kwargs), which will lead to actual Python functions
      +# being executed.
      +######################################################################
      +# This changes the start-symbol for the funcparser callable. Note that
      +# this will make a lot of documentation invalid and there may also be
      +# other unexpected side effects, so change with caution.
      +FUNCPARSER_START_CHAR = '$'
      +# The symbol to use to escape Func
      +FUNCPARSER_ESCAPE_CHAR = '\\'
      +# This is the global max nesting-level for nesting functions in
      +# the funcparser. This protects against infinite loops.
      +FUNCPARSER_MAX_NESTING = 20
      +# Activate funcparser for all outgoing strings. The current Session
      +# will be passed into the parser (used to be called inlinefuncs)
      +FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = False
      +# Only functions defined globally (and not starting with '_') in
      +# these modules will be considered valid inlinefuncs. The list
      +# is loaded from left-to-right, same-named functions will overload
      +FUNCPARSER_OUTGOING_MESSAGES_MODULES = ["evennia.utils.funcparser", "server.conf.inlinefuncs"]
      +# Prototype values are also parsed with FuncParser. These modules
      +# define which $func callables are available to use in prototypes.
      +FUNCPARSER_PROTOTYPE_PARSING_MODULES = ["evennia.prototypes.protfuncs",
      +                                        "server.conf.prototypefuncs"]
      +
      +######################################################################
      +# Global Scripts
      +######################################################################
      +
      +# Global scripts started here will be available through
      +# 'evennia.GLOBAL_SCRIPTS.key'. The scripts will survive a reload and be
      +# recreated automatically if deleted. Each entry must have the script keys,
      +# whereas all other fields in the specification are optional. If 'typeclass' is
      +# not given, BASE_SCRIPT_TYPECLASS will be assumed.  Note that if you change
      +# typeclass for the same key, a new Script will replace the old one on
      +# `evennia.GLOBAL_SCRIPTS`.
      +GLOBAL_SCRIPTS = {
      +    # 'key': {'typeclass': 'typeclass.path.here',
      +    #         'repeats': -1, 'interval': 50, 'desc': 'Example script'},
      +}
      +
      +######################################################################
      +# Default Account setup and access
      +######################################################################
      +
      +# Different Multisession modes allow a player (=account) to connect to the
      +# game simultaneously with multiple clients (=sessions). In modes 0,1 there is
      +# only one character created to the same name as the account at first login.
      +# In modes 2,3 no default character will be created and the MAX_NR_CHARACTERS
      +# value (below) defines how many characters the default char_create command
      +# allow per account.
      +#  0 - single session, one account, one character, when a new session is
      +#      connected, the old one is disconnected
      +#  1 - multiple sessions, one account, one character, each session getting
      +#      the same data
      +#  2 - multiple sessions, one account, many characters, one session per
      +#      character (disconnects multiplets)
      +#  3 - like mode 2, except multiple sessions can puppet one character, each
      +#      session getting the same data.
      +MULTISESSION_MODE = 0
      +# The maximum number of characters allowed by the default ooc char-creation command
      +MAX_NR_CHARACTERS = 1
      +# The access hierarchy, in climbing order. A higher permission in the
      +# hierarchy includes access of all levels below it. Used by the perm()/pperm()
      +# lock functions, which accepts both plural and singular (Admin & Admins)
      +PERMISSION_HIERARCHY = [
      +    "Guest",  # note-only used if GUEST_ENABLED=True
      +    "Player",
      +    "Helper",
      +    "Builder",
      +    "Admin",
      +    "Developer",
      +]
      +# The default permission given to all new accounts
      +PERMISSION_ACCOUNT_DEFAULT = "Player"
      +# Default sizes for client window (in number of characters), if client
      +# is not supplying this on its own
      +CLIENT_DEFAULT_WIDTH = 78
      +# telnet standard height is 24; does anyone use such low-res displays anymore?
      +CLIENT_DEFAULT_HEIGHT = 45
      +# Set rate limits per-IP on account creations and login attempts. Set limits
      +# to None to disable.
      +CREATION_THROTTLE_LIMIT = 2
      +CREATION_THROTTLE_TIMEOUT = 10 * 60
      +LOGIN_THROTTLE_LIMIT = 5
      +LOGIN_THROTTLE_TIMEOUT = 5 * 60
      +# Certain characters, like html tags, line breaks and tabs are stripped
      +# from user input for commands using the `evennia.utils.strip_unsafe_input` helper
      +# since they can be exploitative. This list defines Account-level permissions
      +# (and higher) that bypass this stripping. It is used as a fallback if a
      +# specific list of perms are not given to the helper function.
      +INPUT_CLEANUP_BYPASS_PERMISSIONS = ['Builder']
      +
      +
      +######################################################################
      +# Guest accounts
      +######################################################################
      +
      +# This enables guest logins, by default via "connect guest". Note that
      +# you need to edit your login screen to inform about this possibility.
      +GUEST_ENABLED = False
      +# Typeclass for guest account objects (linked to a character)
      +BASE_GUEST_TYPECLASS = "typeclasses.accounts.Guest"
      +# The permission given to guests
      +PERMISSION_GUEST_DEFAULT = "Guests"
      +# The default home location used for guests.
      +GUEST_HOME = DEFAULT_HOME
      +# The start position used for guest characters.
      +GUEST_START_LOCATION = START_LOCATION
      +# The naming convention used for creating new guest
      +# accounts/characters. The size of this list also determines how many
      +# guests may be on the game at once. The default is a maximum of nine
      +# guests, named Guest1 through Guest9.
      +GUEST_LIST = ["Guest" + str(s + 1) for s in range(9)]
      +
      +######################################################################
      +# In-game Channels created from server start
      +######################################################################
      +
      +# The mudinfo channel is a read-only channel used by Evennia to replay status
      +# messages, connection info etc to staff. The superuser will automatically be
      +# subscribed to this channel. If set to None, the channel is disabled and
      +# status messages will only be logged (not recommended).
      +CHANNEL_MUDINFO = {
      +    "key": "MudInfo",
      +    "aliases": "",
      +    "desc": "Connection log",
      +    "locks": "control:perm(Developer);listen:perm(Admin);send:false()",
      +}
      +# Optional channel (same form as CHANNEL_MUDINFO) that will receive connection
      +# messages like ("<account> has (dis)connected"). While the MudInfo channel
      +# will also receieve this info, this channel is meant for non-staffers. If
      +# None, this information will only be logged.
      +CHANNEL_CONNECTINFO = None
      +# New accounts will auto-sub to the default channels given below (but they can
      +# unsub at any time). Traditionally, at least 'public' should exist. Entries
      +# will be (re)created on the next reload, but removing or updating a same-key
      +# channel from this list will NOT automatically change/remove it in the game,
      +# that needs to be done manually. Note: To create other, non-auto-subbed
      +# channels, create them manually in server/conf/at_initial_setup.py.
      +DEFAULT_CHANNELS = [
      +    {
      +        "key": "Public",
      +        "aliases": ("pub",),
      +        "desc": "Public discussion",
      +        "locks": "control:perm(Admin);listen:all();send:all()",
      +        "typeclass": BASE_CHANNEL_TYPECLASS,
      +    }
      +]
      +
      +######################################################################
      +# External Connections
      +######################################################################
      +
      +# Note: You do *not* have to make your MUD open to
      +# the public to use the external connections, they
      +# operate as long as you have an internet connection,
      +# just like stand-alone chat clients.
      +
      +# The Evennia Game Index is a dynamic listing of Evennia games. You can add your game
      +# to this list also if it is in closed pre-alpha development.
      +GAME_INDEX_ENABLED = False
      +# This dict
      +GAME_INDEX_LISTING = {
      +    "game_name": "Mygame",  # usually SERVERNAME
      +    "game_status": "pre-alpha",  # pre-alpha, alpha, beta or launched
      +    "short_description": "",  # could be GAME_SLOGAN
      +    "long_description": "",
      +    "listing_contact": "",  # email
      +    "telnet_hostname": "",  # mygame.com
      +    "telnet_port": "",  # 1234
      +    "game_website": "",  # http://mygame.com
      +    "web_client_url": "",  # http://mygame.com/webclient
      +}
      +# Evennia can connect to external IRC channels and
      +# echo what is said on the channel to IRC and vice
      +# versa. Obs - make sure the IRC network allows bots.
      +# When enabled, command @irc2chan will be available in-game
      +# IRC requires that you have twisted.words installed.
      +IRC_ENABLED = False
      +# RSS allows to connect RSS feeds (from forum updates, blogs etc) to
      +# an in-game channel. The channel will be updated when the rss feed
      +# updates. Use @rss2chan in game to connect if this setting is
      +# active. OBS: RSS support requires the python-feedparser package to
      +# be installed (through package manager or from the website
      +# http://code.google.com/p/feedparser/)
      +RSS_ENABLED = False
      +RSS_UPDATE_INTERVAL = 60 * 10  # 10 minutes
      +# Grapevine (grapevine.haus) is a network for listing MUDs as well as allow
      +# users of said MUDs to communicate with each other on shared channels. To use,
      +# your game must first be registered by logging in and creating a game entry at
      +# https://grapevine.haus. Evennia links grapevine channels to in-game channels
      +# with the @grapevine2chan command, available once this flag is set
      +# Grapevine requires installing the pyopenssl library (pip install pyopenssl)
      +GRAPEVINE_ENABLED = False
      +# Grapevine channels to allow connection to. See https://grapevine.haus/chat
      +# for the available channels. Only channels in this list can be linked to in-game
      +# channels later.
      +GRAPEVINE_CHANNELS = ["gossip", "testing"]
      +# Grapevine authentication. Register your game at https://grapevine.haus to get
      +# them. These are secret and should thus be overridden in secret_settings file
      +GRAPEVINE_CLIENT_ID = ""
      +GRAPEVINE_CLIENT_SECRET = ""
      +
      +######################################################################
      +# Django web features
      +######################################################################
      +
      +# While DEBUG is False, show a regular server error page on the web
      +# stuff, email the traceback to the people in the ADMINS tuple
      +# below. If True, show a detailed traceback for the web
      +# browser to display. Note however that this will leak memory when
      +# active, so make sure to turn it off for a production server!
      +DEBUG = False
      +# Emails are sent to these people if the above DEBUG value is False. If you'd
      +# rather prefer nobody receives emails, leave this commented out or empty.
      +ADMINS = ()  # 'Your Name', 'your_email@domain.com'),)
      +# These guys get broken link notifications when SEND_BROKEN_LINK_EMAILS is True.
      +MANAGERS = ADMINS
      +# This is a public point of contact for players or the public to contact
      +# a staff member or administrator of the site. It is publicly posted.
      +STAFF_CONTACT_EMAIL = None
      +# If using Sites/Pages from the web admin, this value must be set to the
      +# database-id of the Site (domain) we want to use with this game's Pages.
      +SITE_ID = 1
      +# The age for sessions.
      +# Default: 1209600 (2 weeks, in seconds)
      +SESSION_COOKIE_AGE = 1209600
      +# Session cookie domain
      +# Default: None
      +SESSION_COOKIE_DOMAIN = None
      +# The name of the cookie to use for sessions.
      +# Default: 'sessionid'
      +SESSION_COOKIE_NAME = "sessionid"
      +# Should the session expire when the browser closes?
      +# Default: False
      +SESSION_EXPIRE_AT_BROWSER_CLOSE = False
      +# If you set this to False, Django will make some optimizations so as not
      +# to load the internationalization machinery.
      +USE_I18N = False
      +
      +# Where to find locales (no need to change this, most likely)
      +LOCALE_PATHS = [os.path.join(EVENNIA_DIR, "locale/")]
      +# How to display time stamps in e.g. the admin
      +SHORT_DATETIME_FORMAT = 'Y-m-d H:i:s.u'
      +DATETIME_FORMAT = 'Y-m-d H:i:s'  # ISO 8601 but without T and timezone
      +# This should be turned off unless you want to do tests with Django's
      +# development webserver (normally Evennia runs its own server)
      +SERVE_MEDIA = False
      +# The master urlconf file that contains all of the sub-branches to the
      +# applications. Change this to add your own URLs to the website.
      +ROOT_URLCONF = "web.urls"
      +# Where users are redirected after logging in via contrib.auth.login.
      +LOGIN_REDIRECT_URL = "/"
      +# Where to redirect users when using the @login_required decorator.
      +LOGIN_URL = reverse_lazy("login")
      +# Where to redirect users who wish to logout.
      +LOGOUT_URL = reverse_lazy("logout")
      +# URL that handles the media served from MEDIA_ROOT.
      +# Example: "http://media.lawrence.com"
      +MEDIA_URL = "/media/"
      +# Absolute path to the directory that holds file uploads from web apps.
      +MEDIA_ROOT = os.path.join(GAME_DIR, "server", ".media")
      +# URL prefix for admin media -- CSS, JavaScript and images. Make sure
      +# to use a trailing slash. Admin-related files are searched under STATIC_URL/admin.
      +STATIC_URL = "/static/"
      +# Absolute path to directory where the static data will be gathered into to be
      +# served by webserver.
      +STATIC_ROOT = os.path.join(GAME_DIR, "server", ".static")
      +# Location of static data to overload the defaults from
      +# evennia/web/static.
      +STATICFILES_DIRS = [os.path.join(GAME_DIR, "web", "static")]
      +# Patterns of files in the static directories. Used here to make sure that
      +# its readme file is preserved but unused.
      +STATICFILES_IGNORE_PATTERNS = ["README.md"]
      +# The name of the currently selected web template. This corresponds to the
      +# directory names shown in the templates directory.
      +WEBSITE_TEMPLATE = "website"
      +WEBCLIENT_TEMPLATE = "webclient"
      +# We setup the location of the website template as well as the admin site.
      +TEMPLATES = [
      +    {
      +        "BACKEND": "django.template.backends.django.DjangoTemplates",
      +        "DIRS": [
      +            os.path.join(GAME_DIR, "web", "templates"),
      +            os.path.join(GAME_DIR, "web", "templates", WEBSITE_TEMPLATE),
      +            os.path.join(GAME_DIR, "web", "templates", WEBCLIENT_TEMPLATE),
      +            os.path.join(EVENNIA_DIR, "web", "templates"),
      +            os.path.join(EVENNIA_DIR, "web", "templates", WEBSITE_TEMPLATE),
      +            os.path.join(EVENNIA_DIR, "web", "templates", WEBCLIENT_TEMPLATE),
      +        ],
      +        "APP_DIRS": True,
      +        "OPTIONS": {
      +            "context_processors": [
      +                "django.template.context_processors.i18n",
      +                "django.template.context_processors.request",
      +                "django.contrib.auth.context_processors.auth",
      +                "django.template.context_processors.media",
      +                "django.template.context_processors.debug",
      +                "django.contrib.messages.context_processors.messages",
      +                "sekizai.context_processors.sekizai",
      +                "evennia.web.utils.general_context.general_context",
      +            ],
      +            # While true, show "pretty" error messages for template syntax errors.
      +            "debug": DEBUG,
      +        },
      +    }
      +]
      +# Django cache settings
      +# https://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache
      +CACHES = {
      +    'default': {
      +        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
      +    },
      +    'throttle': {
      +        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
      +        'TIMEOUT': 60 * 5,
      +        'OPTIONS': {
      +            'MAX_ENTRIES': 2000
      +        }
      +    }
      +}
      +# MiddleWare are semi-transparent extensions to Django's functionality.
      +# see http://www.djangoproject.com/documentation/middleware/ for a more detailed
      +# explanation.
      +MIDDLEWARE = [
      +    "django.middleware.common.CommonMiddleware",
      +    "django.contrib.sessions.middleware.SessionMiddleware",
      +    "django.contrib.messages.middleware.MessageMiddleware",  # 1.4?
      +    "django.contrib.auth.middleware.AuthenticationMiddleware",
      +    "django.middleware.csrf.CsrfViewMiddleware",
      +    "django.contrib.admindocs.middleware.XViewMiddleware",
      +    "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
      +    "evennia.web.utils.middleware.SharedLoginMiddleware",
      +]
      +
      +######################################################################
      +# Evennia components
      +######################################################################
      +
      +# Global and Evennia-specific apps. This ties everything together so we can
      +# refer to app models and perform DB syncs.
      +INSTALLED_APPS = [
      +    "django.contrib.auth",
      +    "django.contrib.contenttypes",
      +    "django.contrib.sessions",
      +    "django.contrib.admindocs",
      +    "django.contrib.flatpages",
      +    "django.contrib.sites",
      +    "django.contrib.staticfiles",
      +    "evennia.web.utils.adminsite.EvenniaAdminApp",  # replaces django.contrib.admin
      +    "django.contrib.messages",
      +    "rest_framework",
      +    "django_filters",
      +    "sekizai",
      +    "evennia.utils.idmapper",
      +    "evennia.server",
      +    "evennia.typeclasses",
      +    "evennia.accounts",
      +    "evennia.objects",
      +    "evennia.comms",
      +    "evennia.help",
      +    "evennia.scripts",
      +    "evennia.web",
      +]
      +# The user profile extends the User object with more functionality;
      +# This should usually not be changed.
      +AUTH_USER_MODEL = "accounts.AccountDB"
      +
      +# Password validation plugins
      +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
      +AUTH_PASSWORD_VALIDATORS = [
      +    {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
      +    {
      +        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
      +        "OPTIONS": {"min_length": 8},
      +    },
      +    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
      +    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
      +    {"NAME": "evennia.server.validators.EvenniaPasswordValidator"},
      +]
      +
      +# Username validation plugins
      +AUTH_USERNAME_VALIDATORS = [
      +    {"NAME": "django.contrib.auth.validators.ASCIIUsernameValidator"},
      +    {"NAME": "django.core.validators.MinLengthValidator", "OPTIONS": {"limit_value": 3},},
      +    {"NAME": "django.core.validators.MaxLengthValidator", "OPTIONS": {"limit_value": 30},},
      +    {"NAME": "evennia.server.validators.EvenniaUsernameAvailabilityValidator"},
      +]
      +
      +# Use a custom test runner that just tests Evennia-specific apps.
      +TEST_RUNNER = "evennia.server.tests.testrunner.EvenniaTestSuiteRunner"
      +
      +# Messages and Bootstrap don't classify events the same way; this setting maps
      +# messages.error() to Bootstrap 'danger' classes.
      +MESSAGE_TAGS = {messages.ERROR: "danger"}
      +
      +# Django REST Framework settings
      +REST_FRAMEWORK = {
      +    # django_filters allows you to specify search fields for models in an API View
      +    "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
      +    # whether to paginate results and how many per page
      +    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
      +    "PAGE_SIZE": 25,
      +    # require logged in users to call API so that access checks can work on them
      +    "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated",],
      +    # These are the different ways people can authenticate for API requests - via
      +    # session or with user/password. Other ways are possible, such as via tokens
      +    # or oauth, but require additional dependencies.
      +    "DEFAULT_AUTHENTICATION_CLASSES": [
      +        "rest_framework.authentication.BasicAuthentication",
      +        "rest_framework.authentication.SessionAuthentication",
      +    ],
      +    # default permission checks used by the EvenniaPermission class
      +    "DEFAULT_CREATE_PERMISSION": "builder",
      +    "DEFAULT_LIST_PERMISSION": "builder",
      +    "DEFAULT_VIEW_LOCKS": ["examine"],
      +    "DEFAULT_DESTROY_LOCKS": ["delete"],
      +    "DEFAULT_UPDATE_LOCKS": ["control", "edit"],
      +    # No throttle class set by default. Setting one also requires a cache backend to be specified.
      +}
      +
      +# To enable the REST api, turn this to True
      +REST_API_ENABLED = False
      +
      +######################################################################
      +# Networking Replaceables
      +######################################################################
      +# This allows for replacing the very core of the infrastructure holding Evennia
      +# together with your own variations. You should usually never have to touch
      +# this, and if so, you really need to know what you are doing.
      +
      +# The Base Session Class is used as a parent class for all Protocols such as
      +# Telnet and SSH.) Changing this could be really dangerous. It will cascade
      +# to tons of classes. You generally shouldn't need to touch protocols.
      +BASE_SESSION_CLASS = "evennia.server.session.Session"
      +
      +# Telnet Protocol inherits from whatever above BASE_SESSION_CLASS is specified.
      +# It is used for all telnet connections, and is also inherited by the SSL Protocol
      +# (which is just TLS + Telnet).
      +TELNET_PROTOCOL_CLASS = "evennia.server.portal.telnet.TelnetProtocol"
      +SSL_PROTOCOL_CLASS = "evennia.server.portal.ssl.SSLProtocol"
      +
      +# Websocket Client Protocol. This inherits from BASE_SESSION_CLASS. It is used
      +# for all webclient connections.
      +WEBSOCKET_PROTOCOL_CLASS = "evennia.server.portal.webclient.WebSocketClient"
      +
      +# Protocol for the SSH interface. This inherits from BASE_SESSION_CLASS.
      +SSH_PROTOCOL_CLASS = "evennia.server.portal.ssh.SshProtocol"
      +
      +# Server-side session class used. This will inherit from BASE_SESSION_CLASS.
      +# This one isn't as dangerous to replace.
      +SERVER_SESSION_CLASS = "evennia.server.serversession.ServerSession"
      +
      +# The Server SessionHandler manages all ServerSessions, handling logins,
      +# ensuring the login process happens smoothly, handling expected and
      +# unexpected disconnects. You shouldn't need to touch it, but you can.
      +# Replace it to implement altered game logic.
      +SERVER_SESSION_HANDLER_CLASS = "evennia.server.sessionhandler.ServerSessionHandler"
      +
      +# The Portal SessionHandler manages all incoming connections regardless of
      +# the protocol in use. It is responsible for keeping them going and informing
      +# the Server Session Handler of the connections and synchronizing them across the
      +# AMP connection. You shouldn't ever need to change this. But you can.
      +PORTAL_SESSION_HANDLER_CLASS = "evennia.server.portal.portalsessionhandler.PortalSessionHandler"
      +
      +
      +# These are members / properties / attributes kept on both Server and
      +# Portal Sessions. They are sync'd at various points, such as logins and
      +# reloads. If you add to this, you may need to adjust the class __init__
      +# so the additions have somewhere to go. These must be simple things that
      +# can be pickled - stuff you could serialize to JSON is best.
      +SESSION_SYNC_ATTRS = (
      +    "protocol_key",
      +    "address",
      +    "suid",
      +    "sessid",
      +    "uid",
      +    "csessid",
      +    "uname",
      +    "logged_in",
      +    "puid",
      +    "conn_time",
      +    "cmd_last",
      +    "cmd_last_visible",
      +    "cmd_total",
      +    "protocol_flags",
      +    "server_data",
      +    "cmdset_storage_string",
      +)
      +
      +# The following are used for the communications between the Portal and Server.
      +# Very dragons territory.
      +AMP_SERVER_PROTOCOL_CLASS = "evennia.server.portal.amp_server.AMPServerProtocol"
      +AMP_CLIENT_PROTOCOL_CLASS = "evennia.server.amp_client.AMPServerClientProtocol"
      +
      +# don't change this manually, it can be checked from code to know if
      +# being run from a unit test (set by the evennia.utils.test_resources.BaseEvenniaTest
      +# and BaseEvenniaTestCase unit testing parents)
      +_TEST_ENVIRONMENT = False
      +
      +######################################################################
      +# Django extensions
      +######################################################################
      +
      +# Django extesions are useful third-party tools that are not
      +# always included in the default django distro.
      +try:
      +    import django_extensions  # noqa
      +
      +    INSTALLED_APPS += ["django_extensions"]
      +except ImportError:
      +    # Django extensions are not installed in all distros.
      +    pass
      +
      +#######################################################################
      +# SECRET_KEY
      +#######################################################################
      +# This is the signing key for the cookies generated by Evennia's
      +# web interface.
      +#
      +# It is a fallback for the SECRET_KEY setting in settings.py, which
      +# is randomly seeded when settings.py is first created. If copying
      +# from here, make sure to change it!
      +SECRET_KEY = "changeme!(*#&*($&*(#*(&SDFKJJKLS*(@#KJAS"
      +
      +
      +
      +
      + + +
      +
      +
      +
      + +
      +
      + + + + \ No newline at end of file diff --git a/docs/1.0-dev/Setup/Setup-Overview.html b/docs/1.0-dev/Setup/Setup-Overview.html index f5f268b4f2..9351dd4ee9 100644 --- a/docs/1.0-dev/Setup/Setup-Overview.html +++ b/docs/1.0-dev/Setup/Setup-Overview.html @@ -54,10 +54,10 @@

      Installation & running

      @@ -70,7 +70,7 @@

      Configuring

        -
      • The settings file - how and where to change the main settings of the server

      • +
      • The settings file - how and where to change the main settings of the server

      • Change database engine - if you want to use something other than SQLite3

      • Evennia game index - register your upcoming game with the index to start the hype going

      • Chat on IRC - how to link your game’s channels to an external IRC channel

      • @@ -151,7 +151,6 @@

        Versions

        diff --git a/docs/1.0-dev/Setup/Start-Stop-Reload.html b/docs/1.0-dev/Setup/Start-Stop-Reload.html index a154498d9f..4b91356398 100644 --- a/docs/1.0-dev/Setup/Start-Stop-Reload.html +++ b/docs/1.0-dev/Setup/Start-Stop-Reload.html @@ -44,7 +44,7 @@

        Start Stop Reload

        You control Evennia from your game folder (we refer to it as mygame/ here), using the evennia program. If the evennia program is not available on the command line you must first install -Evennia as described in the Setup Quickstart page.

        +Evennia as described in the Setup Quickstart page.

        Hint: If you ever try the evennia command and get an error complaining that the command is not available, make sure your virtualenv is active.

        @@ -287,7 +287,6 @@ In-game you should now get the message that the Server has successfully restarte

        Versions

        diff --git a/docs/1.0-dev/Unimplemented.html b/docs/1.0-dev/Unimplemented.html index 4ef2efe9bc..152680dd2e 100644 --- a/docs/1.0-dev/Unimplemented.html +++ b/docs/1.0-dev/Unimplemented.html @@ -103,7 +103,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/django/conf.html b/docs/1.0-dev/_modules/django/conf.html index dfa1c0c871..d2cadd4d61 100644 --- a/docs/1.0-dev/_modules/django/conf.html +++ b/docs/1.0-dev/_modules/django/conf.html @@ -59,20 +59,22 @@ import django from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured -from django.utils.deprecation import RemovedInDjango40Warning +from django.utils.deprecation import RemovedInDjango50Warning from django.utils.functional import LazyObject, empty ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" -PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG = ( - 'The PASSWORD_RESET_TIMEOUT_DAYS setting is deprecated. Use ' - 'PASSWORD_RESET_TIMEOUT instead.' +# RemovedInDjango50Warning +USE_DEPRECATED_PYTZ_DEPRECATED_MSG = ( + 'The USE_DEPRECATED_PYTZ setting, and support for pytz timezones is ' + 'deprecated in favor of the stdlib zoneinfo module. Please update your ' + 'code to use zoneinfo and remove the USE_DEPRECATED_PYTZ setting.' ) -DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG = ( - 'The DEFAULT_HASHING_ALGORITHM transitional setting is deprecated. ' - 'Support for it and tokens, cookies, sessions, and signatures that use ' - 'SHA-1 hashing algorithm will be removed in Django 4.0.' +USE_L10N_DEPRECATED_MSG = ( + 'The USE_L10N setting is deprecated. Starting with Django 5.0, localized ' + 'formatting of data will always be enabled. For example Django will ' + 'display numbers and dates using the format of the current locale.' ) @@ -186,18 +188,25 @@ return self._wrapped is not empty @property - def PASSWORD_RESET_TIMEOUT_DAYS(self): + def USE_L10N(self): stack = traceback.extract_stack() # Show a warning if the setting is used outside of Django. # Stack index: -1 this line, -2 the caller. filename, _, _, _ = stack[-2] if not filename.startswith(os.path.dirname(django.__file__)): warnings.warn( - PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, - RemovedInDjango40Warning, + USE_L10N_DEPRECATED_MSG, + RemovedInDjango50Warning, stacklevel=2, ) - return self.__getattr__('PASSWORD_RESET_TIMEOUT_DAYS') + return self.__getattr__('USE_L10N') + + # RemovedInDjango50Warning. + @property + def _USE_L10N_INTERNAL(self): + # Special hook to avoid checking a traceback in internal use on hot + # paths. + return self.__getattr__('USE_L10N') class Settings: @@ -213,6 +222,7 @@ mod = importlib.import_module(self.SETTINGS_MODULE) tuple_settings = ( + 'ALLOWED_HOSTS', "INSTALLED_APPS", "TEMPLATE_DIRS", "LOCALE_PATHS", @@ -224,21 +234,20 @@ if (setting in tuple_settings and not isinstance(setting_value, (list, tuple))): - raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting) + raise ImproperlyConfigured("The %s setting must be a list or a tuple." % setting) setattr(self, setting, setting_value) self._explicit_settings.add(setting) - if self.is_overridden('PASSWORD_RESET_TIMEOUT_DAYS'): - if self.is_overridden('PASSWORD_RESET_TIMEOUT'): - raise ImproperlyConfigured( - 'PASSWORD_RESET_TIMEOUT_DAYS/PASSWORD_RESET_TIMEOUT are ' - 'mutually exclusive.' - ) - setattr(self, 'PASSWORD_RESET_TIMEOUT', self.PASSWORD_RESET_TIMEOUT_DAYS * 60 * 60 * 24) - warnings.warn(PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, RemovedInDjango40Warning) + if self.USE_TZ is False and not self.is_overridden('USE_TZ'): + warnings.warn( + 'The default value of USE_TZ will change from False to True ' + 'in Django 5.0. Set USE_TZ to False in your project settings ' + 'if you want to keep the current default behavior.', + category=RemovedInDjango50Warning, + ) - if self.is_overridden('DEFAULT_HASHING_ALGORITHM'): - warnings.warn(DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG, RemovedInDjango40Warning) + if self.is_overridden('USE_DEPRECATED_PYTZ'): + warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning) if hasattr(time, 'tzset') and self.TIME_ZONE: # When we can, attempt to validate the timezone. If we can't find @@ -252,6 +261,9 @@ os.environ['TZ'] = self.TIME_ZONE time.tzset() + if self.is_overridden('USE_L10N'): + warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning) + def is_overridden(self, setting): return setting in self._explicit_settings @@ -283,12 +295,11 @@ def __setattr__(self, name, value): self._deleted.discard(name) - if name == 'PASSWORD_RESET_TIMEOUT_DAYS': - setattr(self, 'PASSWORD_RESET_TIMEOUT', value * 60 * 60 * 24) - warnings.warn(PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, RemovedInDjango40Warning) - if name == 'DEFAULT_HASHING_ALGORITHM': - warnings.warn(DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG, RemovedInDjango40Warning) + if name == 'USE_L10N': + warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning) super().__setattr__(name, value) + if name == 'USE_DEPRECATED_PYTZ': + warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning) def __delattr__(self, name): self._deleted.add(name) @@ -348,7 +359,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/django/db/models/fields/related_descriptors.html b/docs/1.0-dev/_modules/django/db/models/fields/related_descriptors.html index 7b7ca4844b..b2dc90cb52 100644 --- a/docs/1.0-dev/_modules/django/db/models/fields/related_descriptors.html +++ b/docs/1.0-dev/_modules/django/db/models/fields/related_descriptors.html @@ -909,18 +909,17 @@ do_not_call_in_templates = True def _build_remove_filters(self, removed_vals): - filters = Q(**{self.source_field_name: self.related_val}) + filters = Q((self.source_field_name, self.related_val)) # No need to add a subquery condition if removed_vals is a QuerySet without # filters. removed_vals_filters = (not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()) if removed_vals_filters: - filters &= Q(**{'%s__in' % self.target_field_name: removed_vals}) + filters &= Q((f'{self.target_field_name}__in', removed_vals)) if self.symmetrical: - symmetrical_filters = Q(**{self.target_field_name: self.related_val}) + symmetrical_filters = Q((self.target_field_name, self.related_val)) if removed_vals_filters: - symmetrical_filters &= Q( - **{'%s__in' % self.source_field_name: removed_vals}) + symmetrical_filters &= Q((f'{self.source_field_name}__in', removed_vals)) filters |= symmetrical_filters return filters @@ -1280,7 +1279,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/django/db/models/manager.html b/docs/1.0-dev/_modules/django/db/models/manager.html index b92d564a8c..98b81c786d 100644 --- a/docs/1.0-dev/_modules/django/db/models/manager.html +++ b/docs/1.0-dev/_modules/django/db/models/manager.html @@ -278,7 +278,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/django/db/models/query.html b/docs/1.0-dev/_modules/django/db/models/query.html index 0e5dd162ff..6a43371e70 100644 --- a/docs/1.0-dev/_modules/django/db/models/query.html +++ b/docs/1.0-dev/_modules/django/db/models/query.html @@ -334,10 +334,14 @@ 'QuerySet indices must be integers or slices, not %s.' % type(k).__name__ ) - assert ((not isinstance(k, slice) and (k >= 0)) or - (isinstance(k, slice) and (k.start is None or k.start >= 0) and - (k.stop is None or k.stop >= 0))), \ - "Negative indexing is not supported." + if ( + (isinstance(k, int) and k < 0) or + (isinstance(k, slice) and ( + (k.start is not None and k.start < 0) or + (k.stop is not None and k.stop < 0) + )) + ): + raise ValueError('Negative indexing is not supported.') if self._result_cache is not None: return self._result_cache[k] @@ -523,7 +527,8 @@ # PostgreSQL via the RETURNING ID clause. It should be possible for # Oracle as well, but the semantics for extracting the primary keys is # trickier so it's not done yet. - assert batch_size is None or batch_size > 0 + if batch_size is not None and batch_size <= 0: + raise ValueError('Batch size must be a positive integer.') # Check that the parents share the same concrete model with the our # model to detect the inheritance pattern ConcreteGrandParent -> # MultiTableParent -> ProxyChild. Simply checking self.model._meta.proxy @@ -584,7 +589,7 @@ if any(f.primary_key for f in fields): raise ValueError('bulk_update() cannot be used with primary key fields.') if not objs: - return + return 0 # PK is used twice in the resulting update query, once in the filter # and once in the WHEN. Each field will also have one CAST. max_batch_size = connections[self.db].ops.bulk_batch_size(['pk', 'pk'] + fields, objs) @@ -606,9 +611,11 @@ case_statement = Cast(case_statement, output_field=field) update_kwargs[field.attname] = case_statement updates.append(([obj.pk for obj in batch_objs], update_kwargs)) + rows_updated = 0 with transaction.atomic(using=self.db, savepoint=False): for pks, update_kwargs in updates: - self.filter(pk__in=pks).update(**update_kwargs) + rows_updated += self.filter(pk__in=pks).update(**update_kwargs) + return rows_updated bulk_update.alters_data = True def get_or_create(self, defaults=None, **kwargs): @@ -697,19 +704,20 @@ "earliest() and latest() require either fields as positional " "arguments or 'get_latest_by' in the model's Meta." ) - - assert not self.query.is_sliced, \ - "Cannot change a query once a slice has been taken." obj = self._chain() obj.query.set_limits(high=1) - obj.query.clear_ordering(force_empty=True) + obj.query.clear_ordering(force=True) obj.query.add_ordering(*order_by) return obj.get() def earliest(self, *fields): + if self.query.is_sliced: + raise TypeError('Cannot change a query once a slice has been taken.') return self._earliest(*fields) def latest(self, *fields): + if self.query.is_sliced: + raise TypeError('Cannot change a query once a slice has been taken.') return self.reverse()._earliest(*fields) def first(self): @@ -727,8 +735,8 @@ Return a dictionary mapping each of the given IDs to the object with that ID. If `id_list` isn't provided, evaluate the entire QuerySet. """ - assert not self.query.is_sliced, \ - "Cannot use 'limit' or 'offset' with in_bulk" + if self.query.is_sliced: + raise TypeError("Cannot use 'limit' or 'offset' with in_bulk().") opts = self.model._meta unique_fields = [ constraint.fields[0] @@ -764,9 +772,8 @@ def delete(self): """Delete the records in the current QuerySet.""" self._not_support_combined_queries('delete') - assert not self.query.is_sliced, \ - "Cannot use 'limit' or 'offset' with delete." - + if self.query.is_sliced: + raise TypeError("Cannot use 'limit' or 'offset' with delete().") if self.query.distinct or self.query.distinct_fields: raise TypeError('Cannot call delete() after .distinct().') if self._fields is not None: @@ -782,7 +789,7 @@ # Disable non-supported fields. del_query.query.select_for_update = False del_query.query.select_related = False - del_query.query.clear_ordering(force_empty=True) + del_query.query.clear_ordering(force=True) collector = Collector(using=del_query.db) collector.collect(del_query) @@ -815,8 +822,8 @@ fields to the appropriate values. """ self._not_support_combined_queries('update') - assert not self.query.is_sliced, \ - "Cannot update a query once a slice has been taken." + if self.query.is_sliced: + raise TypeError('Cannot update a query once a slice has been taken.') self._for_write = True query = self.query.chain(sql.UpdateQuery) query.add_update_values(kwargs) @@ -835,8 +842,8 @@ code (it requires too much poking around at model internals to be useful at that level). """ - assert not self.query.is_sliced, \ - "Cannot update a query once a slice has been taken." + if self.query.is_sliced: + raise TypeError('Cannot update a query once a slice has been taken.') query = self.query.chain(sql.UpdateQuery) query.add_update_fields(values) # Clear any annotations so that they won't be present in subqueries. @@ -851,6 +858,27 @@ return self.query.has_results(using=self.db) return bool(self._result_cache) + def contains(self, obj): + """Return True if the queryset contains an object.""" + self._not_support_combined_queries('contains') + if self._fields is not None: + raise TypeError( + 'Cannot call QuerySet.contains() after .values() or ' + '.values_list().' + ) + try: + if obj._meta.concrete_model != self.model._meta.concrete_model: + return False + except AttributeError: + raise TypeError("'obj' must be a model instance.") + if obj.pk is None: + raise ValueError( + 'QuerySet.contains() cannot be used on unsaved objects.' + ) + if self._result_cache is not None: + return obj in self._result_cache + return self.filter(pk=obj.pk).exists() + def _prefetch_related_objects(self): # This method can only be called once the result cache has been filled. prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups) @@ -920,10 +948,10 @@ Return a list of date objects representing all available dates for the given field_name, scoped to 'kind'. """ - assert kind in ('year', 'month', 'week', 'day'), \ - "'kind' must be one of 'year', 'month', 'week', or 'day'." - assert order in ('ASC', 'DESC'), \ - "'order' must be either 'ASC' or 'DESC'." + if kind not in ('year', 'month', 'week', 'day'): + raise ValueError("'kind' must be one of 'year', 'month', 'week', or 'day'.") + if order not in ('ASC', 'DESC'): + raise ValueError("'order' must be either 'ASC' or 'DESC'.") return self.annotate( datefield=Trunc(field_name, kind, output_field=DateField()), plain_field=F(field_name) @@ -931,15 +959,20 @@ 'datefield', flat=True ).distinct().filter(plain_field__isnull=False).order_by(('-' if order == 'DESC' else '') + 'datefield') - def datetimes(self, field_name, kind, order='ASC', tzinfo=None, is_dst=None): + # RemovedInDjango50Warning: when the deprecation ends, remove is_dst + # argument. + def datetimes(self, field_name, kind, order='ASC', tzinfo=None, is_dst=timezone.NOT_PASSED): """ Return a list of datetime objects representing all available datetimes for the given field_name, scoped to 'kind'. """ - assert kind in ('year', 'month', 'week', 'day', 'hour', 'minute', 'second'), \ - "'kind' must be one of 'year', 'month', 'week', 'day', 'hour', 'minute', or 'second'." - assert order in ('ASC', 'DESC'), \ - "'order' must be either 'ASC' or 'DESC'." + if kind not in ('year', 'month', 'week', 'day', 'hour', 'minute', 'second'): + raise ValueError( + "'kind' must be one of 'year', 'month', 'week', 'day', " + "'hour', 'minute', or 'second'." + ) + if order not in ('ASC', 'DESC'): + raise ValueError("'order' must be either 'ASC' or 'DESC'.") if settings.USE_TZ: if tzinfo is None: tzinfo = timezone.get_current_timezone() @@ -992,10 +1025,8 @@ return self._filter_or_exclude(True, args, kwargs) def _filter_or_exclude(self, negate, args, kwargs): - if args or kwargs: - assert not self.query.is_sliced, \ - "Cannot filter a query once a slice has been taken." - + if (args or kwargs) and self.query.is_sliced: + raise TypeError('Cannot filter a query once a slice has been taken.') clone = self._chain() if self._defer_next_filter: self._defer_next_filter = False @@ -1031,7 +1062,7 @@ # Clone the query to inherit the select list and everything clone = self._chain() # Clear limits and ordering so they can be reapplied - clone.query.clear_ordering(True) + clone.query.clear_ordering(force=True) clone.query.clear_limits() clone.query.combined_queries = (self.query,) + tuple(qs.query for qs in other_qs) clone.query.combinator = combinator @@ -1185,10 +1216,10 @@ def order_by(self, *field_names): """Return a new QuerySet instance with the ordering changed.""" - assert not self.query.is_sliced, \ - "Cannot reorder a query once a slice has been taken." + if self.query.is_sliced: + raise TypeError('Cannot reorder a query once a slice has been taken.') obj = self._chain() - obj.query.clear_ordering(force_empty=False) + obj.query.clear_ordering(force=True, clear_default=False) obj.query.add_ordering(*field_names) return obj @@ -1197,8 +1228,8 @@ Return a new QuerySet instance that will select only distinct results. """ self._not_support_combined_queries('distinct') - assert not self.query.is_sliced, \ - "Cannot create distinct fields once a slice has been taken." + if self.query.is_sliced: + raise TypeError('Cannot create distinct fields once a slice has been taken.') obj = self._chain() obj.query.add_distinct_fields(*field_names) return obj @@ -1207,8 +1238,8 @@ order_by=None, select_params=None): """Add extra SQL fragments to the query.""" self._not_support_combined_queries('extra') - assert not self.query.is_sliced, \ - "Cannot change a query once a slice has been taken" + if self.query.is_sliced: + raise TypeError('Cannot change a query once a slice has been taken.') clone = self._chain() clone.query.add_extra(select, select_params, where, params, tables, order_by) return clone @@ -1336,7 +1367,7 @@ self._insert(item, fields=fields, using=self.db, ignore_conflicts=ignore_conflicts) return inserted_rows - def _chain(self, **kwargs): + def _chain(self): """ Return a copy of the current QuerySet that's ready for another operation. @@ -1345,7 +1376,6 @@ if obj._sticky_filter: obj.query.filter_is_sticky = True obj._sticky_filter = False - obj.__dict__.update(kwargs) return obj def _clone(self): @@ -1636,11 +1666,11 @@ def __getstate__(self): obj_dict = self.__dict__.copy() if self.queryset is not None: + queryset = self.queryset._chain() # Prevent the QuerySet from being evaluated - obj_dict['queryset'] = self.queryset._chain( - _result_cache=[], - _prefetch_done=True, - ) + queryset._result_cache = [] + queryset._prefetch_done = True + obj_dict['queryset'] = queryset return obj_dict def add_prefix(self, prefix): @@ -2089,7 +2119,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/django/db/models/query_utils.html b/docs/1.0-dev/_modules/django/db/models/query_utils.html index f4bd046a85..11be4fe058 100644 --- a/docs/1.0-dev/_modules/django/db/models/query_utils.html +++ b/docs/1.0-dev/_modules/django/db/models/query_utils.html @@ -51,13 +51,11 @@ import copy import functools import inspect -import warnings from collections import namedtuple -from django.core.exceptions import FieldDoesNotExist, FieldError +from django.core.exceptions import FieldError from django.db.models.constants import LOOKUP_SEP from django.utils import tree -from django.utils.deprecation import RemovedInDjango40Warning # PathInfo is used when converting lookups (fk__somecol). The contents # describe the relation in Model terms (model Options and Fields for both @@ -65,32 +63,6 @@ PathInfo = namedtuple('PathInfo', 'from_opts to_opts target_fields join_field m2m direct filtered_relation') -class InvalidQueryType(type): - @property - def _subclasses(self): - return (FieldDoesNotExist, FieldError) - - def __warn(self): - warnings.warn( - 'The InvalidQuery exception class is deprecated. Use ' - 'FieldDoesNotExist or FieldError instead.', - category=RemovedInDjango40Warning, - stacklevel=4, - ) - - def __instancecheck__(self, instance): - self.__warn() - return isinstance(instance, self._subclasses) or super().__instancecheck__(instance) - - def __subclasscheck__(self, subclass): - self.__warn() - return issubclass(subclass, self._subclasses) or super().__subclasscheck__(subclass) - - -class InvalidQuery(Exception, metaclass=InvalidQueryType): - pass - - def subclasses(cls): yield cls for subclass in cls.__subclasses__(): @@ -421,7 +393,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/django/utils/deconstruct.html b/docs/1.0-dev/_modules/django/utils/deconstruct.html deleted file mode 100644 index cf8b058e82..0000000000 --- a/docs/1.0-dev/_modules/django/utils/deconstruct.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - django.utils.deconstruct — Evennia 1.0-dev documentation - - - - - - - - - - - - - - - -
        -
        -
        -
        - -

        Source code for django.utils.deconstruct

        -from importlib import import_module
        -
        -from django.utils.version import get_docs_version
        -
        -
        -def deconstructible(*args, path=None):
        -    """
        -    Class decorator that allows the decorated class to be serialized
        -    by the migrations subsystem.
        -
        -    The `path` kwarg specifies the import path.
        -    """
        -    def decorator(klass):
        -        def __new__(cls, *args, **kwargs):
        -            # We capture the arguments to make returning them trivial
        -            obj = super(klass, cls).__new__(cls)
        -            obj._constructor_args = (args, kwargs)
        -            return obj
        -
        -        def deconstruct(obj):
        -            """
        -            Return a 3-tuple of class import path, positional arguments,
        -            and keyword arguments.
        -            """
        -            # Fallback version
        -            if path:
        -                module_name, _, name = path.rpartition('.')
        -            else:
        -                module_name = obj.__module__
        -                name = obj.__class__.__name__
        -            # Make sure it's actually there and not an inner class
        -            module = import_module(module_name)
        -            if not hasattr(module, name):
        -                raise ValueError(
        -                    "Could not find object %s in %s.\n"
        -                    "Please note that you cannot serialize things like inner "
        -                    "classes. Please move the object into the main module "
        -                    "body to use migrations.\n"
        -                    "For more information, see "
        -                    "https://docs.djangoproject.com/en/%s/topics/migrations/#serializing-values"
        -                    % (name, module_name, get_docs_version()))
        -            return (
        -                path or '%s.%s' % (obj.__class__.__module__, name),
        -                obj._constructor_args[0],
        -                obj._constructor_args[1],
        -            )
        -
        -        klass.__new__ = staticmethod(__new__)
        -        klass.deconstruct = deconstruct
        -
        -        return klass
        -
        -    if not args:
        -        return decorator
        -    return decorator(*args)
        -
        - -
        -
        -
        -
        - -
        -
        - - - - \ No newline at end of file diff --git a/docs/1.0-dev/_modules/django/utils/functional.html b/docs/1.0-dev/_modules/django/utils/functional.html index bc0060d9ed..feb3dfacee 100644 --- a/docs/1.0-dev/_modules/django/utils/functional.html +++ b/docs/1.0-dev/_modules/django/utils/functional.html @@ -162,8 +162,10 @@ setattr(cls, method_name, meth) cls._delegate_bytes = bytes in resultclasses cls._delegate_text = str in resultclasses - assert not (cls._delegate_bytes and cls._delegate_text), ( - "Cannot call lazy() with both bytes and text return types.") + if cls._delegate_bytes and cls._delegate_text: + raise ValueError( + 'Cannot call lazy() with both bytes and text return types.' + ) if cls._delegate_text: cls.__str__ = cls.__text_cast elif cls._delegate_bytes: @@ -498,7 +500,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia.html b/docs/1.0-dev/_modules/evennia.html index 9dbf05aefd..014901ddc0 100644 --- a/docs/1.0-dev/_modules/evennia.html +++ b/docs/1.0-dev/_modules/evennia.html @@ -507,7 +507,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/accounts/accounts.html b/docs/1.0-dev/_modules/evennia/accounts/accounts.html index eef4b89342..12dd94039d 100644 --- a/docs/1.0-dev/_modules/evennia/accounts/accounts.html +++ b/docs/1.0-dev/_modules/evennia/accounts/accounts.html @@ -1883,7 +1883,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/accounts/bots.html b/docs/1.0-dev/_modules/evennia/accounts/bots.html index 7e7436c5cd..fe4acafabe 100644 --- a/docs/1.0-dev/_modules/evennia/accounts/bots.html +++ b/docs/1.0-dev/_modules/evennia/accounts/bots.html @@ -656,7 +656,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/accounts/manager.html b/docs/1.0-dev/_modules/evennia/accounts/manager.html index 341ebabc93..0eb443e1c2 100644 --- a/docs/1.0-dev/_modules/evennia/accounts/manager.html +++ b/docs/1.0-dev/_modules/evennia/accounts/manager.html @@ -373,7 +373,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/accounts/models.html b/docs/1.0-dev/_modules/evennia/accounts/models.html index 2a65c21ef1..f833f484a8 100644 --- a/docs/1.0-dev/_modules/evennia/accounts/models.html +++ b/docs/1.0-dev/_modules/evennia/accounts/models.html @@ -258,7 +258,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/cmdhandler.html b/docs/1.0-dev/_modules/evennia/commands/cmdhandler.html index b330efcd00..5aa2906ab5 100644 --- a/docs/1.0-dev/_modules/evennia/commands/cmdhandler.html +++ b/docs/1.0-dev/_modules/evennia/commands/cmdhandler.html @@ -856,7 +856,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/cmdparser.html b/docs/1.0-dev/_modules/evennia/commands/cmdparser.html index 52d77fd4d7..f4d8d263c8 100644 --- a/docs/1.0-dev/_modules/evennia/commands/cmdparser.html +++ b/docs/1.0-dev/_modules/evennia/commands/cmdparser.html @@ -302,7 +302,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/cmdset.html b/docs/1.0-dev/_modules/evennia/commands/cmdset.html index 13877046b5..72608b5f8b 100644 --- a/docs/1.0-dev/_modules/evennia/commands/cmdset.html +++ b/docs/1.0-dev/_modules/evennia/commands/cmdset.html @@ -752,7 +752,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/cmdsethandler.html b/docs/1.0-dev/_modules/evennia/commands/cmdsethandler.html index 779cf0bad8..2f39e2cc91 100644 --- a/docs/1.0-dev/_modules/evennia/commands/cmdsethandler.html +++ b/docs/1.0-dev/_modules/evennia/commands/cmdsethandler.html @@ -742,7 +742,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/command.html b/docs/1.0-dev/_modules/evennia/commands/command.html index 1ef964a160..b7b445d4a5 100644 --- a/docs/1.0-dev/_modules/evennia/commands/command.html +++ b/docs/1.0-dev/_modules/evennia/commands/command.html @@ -820,7 +820,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/account.html b/docs/1.0-dev/_modules/evennia/commands/default/account.html index ab13e7bb7d..6e8504c192 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/account.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/account.html @@ -1131,7 +1131,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/admin.html b/docs/1.0-dev/_modules/evennia/commands/default/admin.html index 5cf22cbcba..e321a1c306 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/admin.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/admin.html @@ -681,7 +681,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/batchprocess.html b/docs/1.0-dev/_modules/evennia/commands/default/batchprocess.html index 1ea0d1e201..10c0e4e59c 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/batchprocess.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/batchprocess.html @@ -897,7 +897,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/building.html b/docs/1.0-dev/_modules/evennia/commands/default/building.html index 0bfac269db..27ccb54d4f 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/building.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/building.html @@ -2616,15 +2616,16 @@ if value: return f"{string}: T" return f"{string}: F" - return ", ".join( + txt = ", ".join( _truefalse(opt, getattr(cmdset, opt)) for opt in ("no_exits", "no_objs", "no_channels", "duplicates") if getattr(cmdset, opt) is not None - ) + ) + return ", " + txt if txt else ""
        [docs] def format_single_cmdset(self, cmdset): options = self.format_single_cmdset_options(cmdset) - return f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority}{options}"
        + return f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority}{options})"
        [docs] def format_stored_cmdsets(self, obj): if hasattr(obj, "cmdset"): @@ -4377,7 +4378,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/cmdset_account.html b/docs/1.0-dev/_modules/evennia/commands/default/cmdset_account.html index 128291e30f..78ce5d350b 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/cmdset_account.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/cmdset_account.html @@ -153,7 +153,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/cmdset_character.html b/docs/1.0-dev/_modules/evennia/commands/default/cmdset_character.html index 217ca704f9..a25f2acb0c 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/cmdset_character.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/cmdset_character.html @@ -168,7 +168,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/cmdset_session.html b/docs/1.0-dev/_modules/evennia/commands/default/cmdset_session.html index b7190c7d01..0d8557f151 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/cmdset_session.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/cmdset_session.html @@ -94,7 +94,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/cmdset_unloggedin.html b/docs/1.0-dev/_modules/evennia/commands/default/cmdset_unloggedin.html index 4e6e3241a1..79d5bbcde7 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/cmdset_unloggedin.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/cmdset_unloggedin.html @@ -103,7 +103,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/comms.html b/docs/1.0-dev/_modules/evennia/commands/default/comms.html index a82691da0d..959d7222ab 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/comms.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/comms.html @@ -1940,7 +1940,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/general.html b/docs/1.0-dev/_modules/evennia/commands/default/general.html index 2791e2d2d3..cf9c7592cc 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/general.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/general.html @@ -815,7 +815,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/help.html b/docs/1.0-dev/_modules/evennia/commands/default/help.html index 5e02b2ca81..7e4b9f6890 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/help.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/help.html @@ -1057,7 +1057,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/muxcommand.html b/docs/1.0-dev/_modules/evennia/commands/default/muxcommand.html index e07ac7e125..489aa61e88 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/muxcommand.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/muxcommand.html @@ -346,7 +346,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/syscommands.html b/docs/1.0-dev/_modules/evennia/commands/default/syscommands.html index 12f66dad60..32a7fc2374 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/syscommands.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/syscommands.html @@ -181,7 +181,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/system.html b/docs/1.0-dev/_modules/evennia/commands/default/system.html index 6632e116d9..3f159193df 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/system.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/system.html @@ -1231,7 +1231,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/tests.html b/docs/1.0-dev/_modules/evennia/commands/default/tests.html index c88ffe57ef..9b6209af1f 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/tests.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/tests.html @@ -2093,7 +2093,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/commands/default/unloggedin.html b/docs/1.0-dev/_modules/evennia/commands/default/unloggedin.html index c6f02505dd..a01d155f56 100644 --- a/docs/1.0-dev/_modules/evennia/commands/default/unloggedin.html +++ b/docs/1.0-dev/_modules/evennia/commands/default/unloggedin.html @@ -590,7 +590,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/comms/comms.html b/docs/1.0-dev/_modules/evennia/comms/comms.html index 19882d649b..35eef4a795 100644 --- a/docs/1.0-dev/_modules/evennia/comms/comms.html +++ b/docs/1.0-dev/_modules/evennia/comms/comms.html @@ -931,7 +931,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/comms/managers.html b/docs/1.0-dev/_modules/evennia/comms/managers.html index 97f455a405..489b26415d 100644 --- a/docs/1.0-dev/_modules/evennia/comms/managers.html +++ b/docs/1.0-dev/_modules/evennia/comms/managers.html @@ -580,7 +580,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/comms/models.html b/docs/1.0-dev/_modules/evennia/comms/models.html index 148da85094..cc6b463bb2 100644 --- a/docs/1.0-dev/_modules/evennia/comms/models.html +++ b/docs/1.0-dev/_modules/evennia/comms/models.html @@ -789,7 +789,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/awsstorage/aws_s3_cdn.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/awsstorage/aws_s3_cdn.html deleted file mode 100644 index 76e9939784..0000000000 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/awsstorage/aws_s3_cdn.html +++ /dev/null @@ -1,970 +0,0 @@ - - - - - - - - evennia.contrib.base_systems.awsstorage.aws_s3_cdn — Evennia 1.0-dev documentation - - - - - - - - - - - - - - - -
        -
        -
        -
        - -

        Source code for evennia.contrib.base_systems.awsstorage.aws_s3_cdn

        -"""
        -AWS Storage System
        -The Right Honourable Reverend (trhr) 2020
        -
        -ABOUT THIS PLUGIN:
        -
        -This plugin migrates the Web-based portion of Evennia, namely images,
        -javascript, and other items located inside staticfiles into Amazon AWS (S3) for hosting.
        -
        -Files hosted on S3 are "in the cloud," and while your personal
        -server may be sufficient for serving multimedia to a minimal number of users,
        -the perfect use case for this plugin would be:
        -
        -1) Servers supporting heavy web-based traffic (webclient, etc)
        -2) With a sizable number of users
        -3) Where the users are globally distributed
        -4) Where multimedia files are served to users as a part of gameplay
        -
        -Bottom line - if you're sending an image to a player every time they traverse a
        -map, the bandwidth reduction will be substantial. If not, probably skip
        -this one.
        -
        -Note that storing and serving files via S3 is not technically free outside of
        -Amazon's "free tier" offering, which you may or may not be eligible for;
        -evennia's base install currently requires 1.5MB of storage space on S3,
        -making the current total cost to install this plugin ~$0.0005 per year. If
        -you have substantial media assets and intend to serve them to many users,
        -caveat emptor on a total cost of ownership - check AWS's pricing structure.
        -
        -See the ./README.md file for details and install instructions.
        -
        -"""
        -
        -from django.core.exceptions import (
        -    ImproperlyConfigured,
        -    SuspiciousOperation,
        -    SuspiciousFileOperation,
        -)
        -
        -try:
        -    from django.conf import settings as ev_settings
        -
        -    if (
        -        not ev_settings.AWS_ACCESS_KEY_ID
        -        or not ev_settings.AWS_SECRET_ACCESS_KEY
        -        or not ev_settings.AWS_STORAGE_BUCKET_NAME
        -        or not ev_settings.AWS_S3_REGION_NAME
        -    ):
        -        raise ImproperlyConfigured(
        -            (
        -                "You must add AWS-specific settings"
        -                "to mygame/server/conf/secret_settings.py to use this plugin."
        -            )
        -        )
        -
        -    if "mygame-evennia" == ev_settings.AWS_STORAGE_BUCKET_NAME:
        -        raise ImproperlyConfigured(
        -            (
        -                "You must customize your AWS_STORAGE_BUCKET_NAME"
        -                "in mygame/server/conf/secret_settings.py;"
        -                "it must be unique among ALL other S3 users"
        -            )
        -        )
        -
        -except Exception as e:
        -    print(e)
        -
        -import io
        -import mimetypes
        -import os
        -import posixpath
        -import threading
        -from gzip import GzipFile
        -from tempfile import SpooledTemporaryFile
        -from django.core.files.base import File
        -from django.core.files.storage import Storage
        -from django.utils.deconstruct import deconstructible
        -from django.utils.encoding import filepath_to_uri, force_bytes, force_text, smart_text
        -from django.utils.timezone import is_naive, make_naive
        -
        -try:
        -    from django.utils.six.moves.urllib import parse as urlparse
        -except ImportError:
        -    from urllib import parse as urlparse
        -
        -try:
        -    import boto3.session
        -    from boto3 import __version__ as boto3_version
        -    from botocore.client import Config
        -    from botocore.exceptions import ClientError
        -except ImportError as e:
        -    raise ImproperlyConfigured("Couldn't load S3 bindings. %s Did you run 'pip install boto3?'" % e)
        -
        -boto3_version_info = tuple([int(i) for i in boto3_version.split(".")])
        -
        -
        -
        [docs]def setting(name, default=None): - """ - Helper function to get a Django setting by name. If setting doesn't exist - it will return a default. - - Args: - name (str): A Django setting name - - Returns: - The value of the setting variable by that name - - """ - return getattr(ev_settings, name, default)
        - - -
        [docs]def safe_join(base, *paths): - """ - Helper function, a version of django.utils._os.safe_join for S3 paths. - Joins one or more path components to the base path component - intelligently. Returns a normalized version of the final path. - The final path must be located inside of the base path component - (otherwise a ValueError is raised). Paths outside the base path - indicate a possible security sensitive operation. - - Args: - base (str): A path string to the base of the staticfiles - *paths (list): A list of paths as referenced from the base path - - Returns: - final_path (str): A joined path, base + filepath - - """ - base_path = force_text(base) - base_path = base_path.rstrip("/") - paths = [force_text(p) for p in paths] - - final_path = base_path + "/" - for path in paths: - _final_path = posixpath.normpath(posixpath.join(final_path, path)) - # posixpath.normpath() strips the trailing /. Add it back. - if path.endswith("/") or _final_path + "/" == final_path: - _final_path += "/" - final_path = _final_path - if final_path == base_path: - final_path += "/" - - # Ensure final_path starts with base_path and that the next character after - # the base path is /. - base_path_len = len(base_path) - if not final_path.startswith(base_path) or final_path[base_path_len] != "/": - raise ValueError("the joined path is located outside of the base path" " component") - - return final_path.lstrip("/")
        - - -
        [docs]def check_location(storage): - """ - Helper function to make sure that the storage location is configured correctly. - - Args: - storage (Storage): A Storage object (Django) - - Raises: - ImproperlyConfigured: If the storage location is not configured correctly, - this is raised. - - """ - if storage.location.startswith("/"): - correct = storage.location.lstrip("/") - raise ImproperlyConfigured( - "{}.location cannot begin with a leading slash. Found '{}'. Use '{}' instead.".format( - storage.__class__.__name__, storage.location, correct, - ) - )
        - - -
        [docs]def lookup_env(names): - """ - Helper function for looking up names in env vars. Returns the first element found. - - Args: - names (str): A list of environment variables - - Returns: - value (str): The value of the found environment variable. - - """ - for name in names: - value = os.environ.get(name) - if value: - return value
        - - -
        [docs]def get_available_overwrite_name(name, max_length): - """ - Helper function indicating files that will be overwritten during trunc. - - Args: - name (str): The name of the file - max_length (int): The maximum length of a filename - - Returns: - joined (path): A joined path including directory, file, and extension - """ - if max_length is None or len(name) <= max_length: - return name - - # Adapted from Django - dir_name, file_name = os.path.split(name) - file_root, file_ext = os.path.splitext(file_name) - truncation = len(name) - max_length - - file_root = file_root[:-truncation] - if not file_root: - raise SuspiciousFileOperation( - 'aws-s3-cdn tried to truncate away entire filename "%s". ' - "Please make sure that the corresponding file field " - 'allows sufficient "max_length".' % name - ) - return os.path.join(dir_name, "{}{}".format(file_root, file_ext))
        - - -
        [docs]@deconstructible -class S3Boto3StorageFile(File): - - """ - The default file object used by the S3Boto3Storage backend. - This file implements file streaming using boto's multipart - uploading functionality. The file can be opened in read or - write mode. - This class extends Django's File class. However, the contained - data is only the data contained in the current buffer. So you - should not access the contained file object directly. You should - access the data via this class. - Warning: This file *must* be closed using the close() method in - order to properly write the file to S3. Be sure to close the file - in your application. - """ - - buffer_size = setting("AWS_S3_FILE_BUFFER_SIZE", 5242880) - -
        [docs] def __init__(self, name, mode, storage, buffer_size=None): - """ - Initializes the File object. - - Args: - name (str): The name of the file - mode (str): The access mode ('r' or 'w') - storage (Storage): The Django Storage object - buffer_size (int): The buffer size, for multipart uploads - """ - if "r" in mode and "w" in mode: - raise ValueError("Can't combine 'r' and 'w' in mode.") - self._storage = storage - self.name = name[len(self._storage.location) :].lstrip("/") - self._mode = mode - self._force_mode = (lambda b: b) if "b" in mode else force_text - self.obj = storage.bucket.Object(storage._encode_name(name)) - if "w" not in mode: - # Force early RAII-style exception if object does not exist - self.obj.load() - self._is_dirty = False - self._raw_bytes_written = 0 - self._file = None - self._multipart = None - # 5 MB is the minimum part size (if there is more than one part). - # Amazon allows up to 10,000 parts. The default supports uploads - # up to roughly 50 GB. Increase the part size to accommodate - # for files larger than this. - if buffer_size is not None: - self.buffer_size = buffer_size - self._write_counter = 0
        - - @property - def size(self): - """ - Helper property to return filesize - """ - return self.obj.content_length - - def _get_file(self): - """ - Helper function to manage zipping and temporary files - """ - if self._file is None: - self._file = SpooledTemporaryFile( - max_size=self._storage.max_memory_size, - suffix=".S3Boto3StorageFile", - dir=setting("FILE_UPLOAD_TEMP_DIR"), - ) - if "r" in self._mode: - self._is_dirty = False - self.obj.download_fileobj(self._file) - self._file.seek(0) - if self._storage.gzip and self.obj.content_encoding == "gzip": - self._file = GzipFile(mode=self._mode, fileobj=self._file, mtime=0.0) - return self._file - - def _set_file(self, value): - self._file = value - - file = property(_get_file, _set_file) - -
        [docs] def read(self, *args, **kwargs): - """ - Checks if file is in read mode; then continues to boto3 operation - """ - if "r" not in self._mode: - raise AttributeError("File was not opened in read mode.") - return self._force_mode(super().read(*args, **kwargs))
        - -
        [docs] def readline(self, *args, **kwargs): - """ - Checks if file is in read mode; then continues to boto3 operation - """ - if "r" not in self._mode: - raise AttributeError("File was not opened in read mode.") - return self._force_mode(super().readline(*args, **kwargs))
        - -
        [docs] def write(self, content): - """ - Checks if file is in write mode or needs multipart handling, - then continues to boto3 operation. - """ - if "w" not in self._mode: - raise AttributeError("File was not opened in write mode.") - self._is_dirty = True - if self._multipart is None: - self._multipart = self.obj.initiate_multipart_upload( - **self._storage._get_write_parameters(self.obj.key) - ) - if self.buffer_size <= self._buffer_file_size: - self._flush_write_buffer() - bstr = force_bytes(content) - self._raw_bytes_written += len(bstr) - return super().write(bstr)
        - - @property - def _buffer_file_size(self): - pos = self.file.tell() - self.file.seek(0, os.SEEK_END) - length = self.file.tell() - self.file.seek(pos) - return length - - def _flush_write_buffer(self): - """ - Flushes the write buffer. - """ - if self._buffer_file_size: - self._write_counter += 1 - self.file.seek(0) - part = self._multipart.Part(self._write_counter) - part.upload(Body=self.file.read()) - self.file.seek(0) - self.file.truncate() - - def _create_empty_on_close(self): - """ - Attempt to create an empty file for this key when this File is closed if no bytes - have been written and no object already exists on S3 for this key. - This behavior is meant to mimic the behavior of Django's builtin FileSystemStorage, - where files are always created after they are opened in write mode: - f = storage.open("file.txt", mode="w") - f.close() - - Raises: - Exception: Raised if a 404 error occurs - """ - assert "w" in self._mode - assert self._raw_bytes_written == 0 - - try: - # Check if the object exists on the server; if so, don't do anything - self.obj.load() - except ClientError as err: - if err.response["ResponseMetadata"]["HTTPStatusCode"] == 404: - self.obj.put(Body=b"", **self._storage._get_write_parameters(self.obj.key)) - else: - raise - -
        [docs] def close(self): - """ - Manages file closing after multipart uploads - """ - if self._is_dirty: - self._flush_write_buffer() - parts = [ - {"ETag": part.e_tag, "PartNumber": part.part_number} - for part in self._multipart.parts.all() - ] - self._multipart.complete(MultipartUpload={"Parts": parts}) - else: - if self._multipart is not None: - self._multipart.abort() - if "w" in self._mode and self._raw_bytes_written == 0: - self._create_empty_on_close() - if self._file is not None: - self._file.close() - self._file = None
        - - -
        [docs]@deconstructible -class S3Boto3Storage(Storage): - """ - Amazon Simple Storage Service using Boto3 - This storage backend supports opening files in read or write - mode and supports streaming(buffering) data in chunks to S3 - when writing. - """ - - default_content_type = "application/octet-stream" - # If config provided in init, signature_version and addressing_style settings/args are ignored. - config = None - # used for looking up the access and secret key from env vars - access_key_names = ["AWS_S3_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"] - secret_key_names = ["AWS_S3_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY"] - security_token_names = ["AWS_SESSION_TOKEN", "AWS_SECURITY_TOKEN"] - security_token = None - - access_key = setting("AWS_S3_ACCESS_KEY_ID", setting("AWS_ACCESS_KEY_ID", "")) - secret_key = setting("AWS_S3_SECRET_ACCESS_KEY", setting("AWS_SECRET_ACCESS_KEY", "")) - file_overwrite = setting("AWS_S3_FILE_OVERWRITE", True) - object_parameters = setting("AWS_S3_OBJECT_PARAMETERS", {}) - bucket_name = setting("AWS_STORAGE_BUCKET_NAME") - auto_create_bucket = setting("AWS_AUTO_CREATE_BUCKET", False) - default_acl = setting("AWS_DEFAULT_ACL", "public-read") - bucket_acl = setting("AWS_BUCKET_ACL", default_acl) - querystring_auth = setting("AWS_QUERYSTRING_AUTH", True) - querystring_expire = setting("AWS_QUERYSTRING_EXPIRE", 3600) - signature_version = setting("AWS_S3_SIGNATURE_VERSION") - reduced_redundancy = setting("AWS_REDUCED_REDUNDANCY", False) - location = setting("AWS_LOCATION", "") - encryption = setting("AWS_S3_ENCRYPTION", False) - custom_domain = setting("AWS_S3_CUSTOM_DOMAIN") - addressing_style = setting("AWS_S3_ADDRESSING_STYLE") - secure_urls = setting("AWS_S3_SECURE_URLS", True) - file_name_charset = setting("AWS_S3_FILE_NAME_CHARSET", "utf-8") - gzip = setting("AWS_IS_GZIPPED", False) - preload_metadata = setting("AWS_PRELOAD_METADATA", False) - gzip_content_types = setting( - "GZIP_CONTENT_TYPES", - ( - "text/css", - "text/javascript", - "application/javascript", - "application/x-javascript", - "image/svg+xml", - ), - ) - url_protocol = setting("AWS_S3_URL_PROTOCOL", "http:") - endpoint_url = setting("AWS_S3_ENDPOINT_URL") - proxies = setting("AWS_S3_PROXIES") - region_name = setting("AWS_S3_REGION_NAME") - use_ssl = setting("AWS_S3_USE_SSL", True) - verify = setting("AWS_S3_VERIFY", None) - max_memory_size = setting("AWS_S3_MAX_MEMORY_SIZE", 0) - -
        [docs] def __init__(self, acl=None, bucket=None, **settings): - """ - Check if some of the settings we've provided as class attributes - need to be overwritten with values passed in here. - """ - for name, value in settings.items(): - if hasattr(self, name): - setattr(self, name, value) - - check_location(self) - - # Backward-compatibility: given the anteriority of the SECURE_URL setting - # we fall back to https if specified in order to avoid the construction - # of unsecure urls. - if self.secure_urls: - self.url_protocol = "https:" - - self._entries = {} - self._bucket = None - self._connections = threading.local() - - self.access_key, self.secret_key = self._get_access_keys() - self.security_token = self._get_security_token() - - if not self.config: - kwargs = dict( - s3={"addressing_style": self.addressing_style}, - signature_version=self.signature_version, - ) - - if boto3_version_info >= (1, 4, 4): - kwargs["proxies"] = self.proxies - self.config = Config(**kwargs)
        - - def __getstate__(self): - state = self.__dict__.copy() - state.pop("_connections", None) - state.pop("_bucket", None) - return state - - def __setstate__(self, state): - state["_connections"] = threading.local() - state["_bucket"] = None - self.__dict__ = state - - @property - def connection(self): - """ - Creates the actual connection to S3 - """ - connection = getattr(self._connections, "connection", None) - if connection is None: - session = boto3.session.Session() - self._connections.connection = session.resource( - "s3", - aws_access_key_id=self.access_key, - aws_secret_access_key=self.secret_key, - aws_session_token=self.security_token, - region_name=self.region_name, - use_ssl=self.use_ssl, - endpoint_url=self.endpoint_url, - config=self.config, - verify=self.verify, - ) - return self._connections.connection - - @property - def bucket(self): - """ - Get the current bucket. If there is no current bucket object - create it. - """ - if self._bucket is None: - self._bucket = self._get_or_create_bucket(self.bucket_name) - return self._bucket - - @property - def entries(self): - """ - Get the locally cached files for the bucket. - """ - if self.preload_metadata and not self._entries: - self._entries = { - self._decode_name(entry.key): entry - for entry in self.bucket.objects.filter(Prefix=self.location) - } - return self._entries - - def _get_access_keys(self): - """ - Gets the access keys to use when accessing S3. If none is - provided in the settings then get them from the environment - variables. - """ - access_key = self.access_key or lookup_env(S3Boto3Storage.access_key_names) - secret_key = self.secret_key or lookup_env(S3Boto3Storage.secret_key_names) - return access_key, secret_key - - def _get_security_token(self): - """ - Gets the security token to use when accessing S3. Get it from - the environment variables. - """ - security_token = self.security_token or lookup_env(S3Boto3Storage.security_token_names) - return security_token - - def _get_or_create_bucket(self, name): - """ - Retrieves a bucket if it exists, otherwise creates it. - """ - bucket = self.connection.Bucket(name) - if self.auto_create_bucket: - try: - # Directly call head_bucket instead of bucket.load() because head_bucket() - # fails on wrong region, while bucket.load() does not. - bucket.meta.client.head_bucket(Bucket=name) - except ClientError as err: - if err.response["ResponseMetadata"]["HTTPStatusCode"] == 301: - raise ImproperlyConfigured( - "Bucket %s exists, but in a different " - "region than we are connecting to. Set " - "the region to connect to by setting " - "AWS_S3_REGION_NAME to the correct region." % name - ) - - elif err.response["ResponseMetadata"]["HTTPStatusCode"] == 404: - # Notes: When using the us-east-1 Standard endpoint, you can create - # buckets in other regions. The same is not true when hitting region specific - # endpoints. However, when you create the bucket not in the same region, the - # connection will fail all future requests to the Bucket after the creation - # (301 Moved Permanently). - # - # For simplicity, we enforce in S3Boto3Storage that any auto-created - # bucket must match the region that the connection is for. - # - # Also note that Amazon specifically disallows "us-east-1" when passing bucket - # region names; LocationConstraint *must* be blank to create in US Standard. - - if self.bucket_acl: - bucket_params = {"ACL": self.bucket_acl} - else: - bucket_params = {} - region_name = self.connection.meta.client.meta.region_name - if region_name != "us-east-1": - bucket_params["CreateBucketConfiguration"] = { - "LocationConstraint": region_name - } - bucket.create(**bucket_params) - else: - raise - return bucket - - def _clean_name(self, name): - """ - Cleans the name so that Windows style paths work - """ - # Normalize Windows style paths - clean_name = posixpath.normpath(name).replace("\\", "/") - - # os.path.normpath() can strip trailing slashes so we implement - # a workaround here. - if name.endswith("/") and not clean_name.endswith("/"): - # Add a trailing slash as it was stripped. - clean_name += "/" - return clean_name - - def _normalize_name(self, name): - """ - Normalizes the name so that paths like /path/to/ignored/../something.txt - work. We check to make sure that the path pointed to is not outside - the directory specified by the LOCATION setting. - """ - try: - return safe_join(self.location, name) - except ValueError: - raise SuspiciousOperation("Attempted access to '%s' denied." % name) - - def _encode_name(self, name): - return smart_text(name, encoding=self.file_name_charset) - - def _decode_name(self, name): - return force_text(name, encoding=self.file_name_charset) - - def _compress_content(self, content): - """Gzip a given string content.""" - content.seek(0) - zbuf = io.BytesIO() - # The GZIP header has a modification time attribute (see http://www.zlib.org/rfc-gzip.html) - # Each time a file is compressed it changes even if the other contents don't change - # For S3 this defeats detection of changes using MD5 sums on gzipped files - # Fixing the mtime at 0.0 at compression time avoids this problem - zfile = GzipFile(mode="wb", fileobj=zbuf, mtime=0.0) - try: - zfile.write(force_bytes(content.read())) - finally: - zfile.close() - zbuf.seek(0) - # Boto 2 returned the InMemoryUploadedFile with the file pointer replaced, - # but Boto 3 seems to have issues with that. No need for fp.name in Boto3 - # so just returning the BytesIO directly - return zbuf - - def _open(self, name, mode="rb"): - """ - Opens the file, if it exists. - """ - name = self._normalize_name(self._clean_name(name)) - try: - f = S3Boto3StorageFile(name, mode, self) - except ClientError as err: - if err.response["ResponseMetadata"]["HTTPStatusCode"] == 404: - raise IOError("File does not exist: %s" % name) - raise # Let it bubble up if it was some other error - return f - - def _save(self, name, content): - """ - Stitches and cleans multipart uploads; normalizes file paths. - """ - cleaned_name = self._clean_name(name) - name = self._normalize_name(cleaned_name) - params = self._get_write_parameters(name, content) - - if ( - self.gzip - and params["ContentType"] in self.gzip_content_types - and "ContentEncoding" not in params - ): - content = self._compress_content(content) - params["ContentEncoding"] = "gzip" - - encoded_name = self._encode_name(name) - obj = self.bucket.Object(encoded_name) - if self.preload_metadata: - self._entries[encoded_name] = obj - - content.seek(0, os.SEEK_SET) - obj.upload_fileobj(content, ExtraArgs=params) - return cleaned_name - -
        [docs] def delete(self, name): - """ - Deletes a file from S3. - """ - name = self._normalize_name(self._clean_name(name)) - self.bucket.Object(self._encode_name(name)).delete() - - if name in self._entries: - del self._entries[name]
        - -
        [docs] def exists(self, name): - """ - Checks if file exists. - """ - name = self._normalize_name(self._clean_name(name)) - if self.entries: - return name in self.entries - try: - self.connection.meta.client.head_object(Bucket=self.bucket_name, Key=name) - return True - except ClientError: - return False
        - -
        [docs] def listdir(self, name): - """ - Translational function to go from S3 file paths to the format - Django's listdir expects. - """ - path = self._normalize_name(self._clean_name(name)) - # The path needs to end with a slash, but if the root is empty, leave - # it. - if path and not path.endswith("/"): - path += "/" - - directories = [] - files = [] - paginator = self.connection.meta.client.get_paginator("list_objects") - pages = paginator.paginate(Bucket=self.bucket_name, Delimiter="/", Prefix=path) - for page in pages: - for entry in page.get("CommonPrefixes", ()): - directories.append(posixpath.relpath(entry["Prefix"], path)) - for entry in page.get("Contents", ()): - files.append(posixpath.relpath(entry["Key"], path)) - return directories, files
        - -
        [docs] def size(self, name): - """ - Gets the filesize of a remote file. - """ - name = self._normalize_name(self._clean_name(name)) - if self.entries: - entry = self.entries.get(name) - if entry: - return entry.size if hasattr(entry, "size") else entry.content_length - return 0 - return self.bucket.Object(self._encode_name(name)).content_length
        - - def _get_write_parameters(self, name, content=None): - params = {} - - if self.encryption: - params["ServerSideEncryption"] = "AES256" - if self.reduced_redundancy: - params["StorageClass"] = "REDUCED_REDUNDANCY" - if self.default_acl: - params["ACL"] = self.default_acl - - _type, encoding = mimetypes.guess_type(name) - content_type = getattr(content, "content_type", None) - content_type = content_type or _type or self.default_content_type - - params["ContentType"] = content_type - if encoding: - params["ContentEncoding"] = encoding - - params.update(self.get_object_parameters(name)) - return params - -
        [docs] def get_object_parameters(self, name): - """ - Returns a dictionary that is passed to file upload. Override this - method to adjust this on a per-object basis to set e.g ContentDisposition. - By default, returns the value of AWS_S3_OBJECT_PARAMETERS. - Setting ContentEncoding will prevent objects from being automatically gzipped. - """ - return self.object_parameters.copy()
        - -
        [docs] def get_modified_time(self, name): - """ - Returns an (aware) datetime object containing the last modified time if - USE_TZ is True, otherwise returns a naive datetime in the local timezone. - """ - name = self._normalize_name(self._clean_name(name)) - entry = self.entries.get(name) - # only call self.bucket.Object() if the key is not found - # in the preloaded metadata. - if entry is None: - entry = self.bucket.Object(self._encode_name(name)) - if setting("USE_TZ"): - # boto3 returns TZ aware timestamps - return entry.last_modified - else: - return make_naive(entry.last_modified)
        - -
        [docs] def modified_time(self, name): - """Returns a naive datetime object containing the last modified time. - If USE_TZ=False then get_modified_time will return a naive datetime - so we just return that, else we have to localize and strip the tz - """ - mtime = self.get_modified_time(name) - return mtime if is_naive(mtime) else make_naive(mtime)
        - - def _strip_signing_parameters(self, url): - """ - Boto3 does not currently support generating URLs that are unsigned. Instead we - take the signed URLs and strip any querystring params related to signing and expiration. - Note that this may end up with URLs that are still invalid, especially if params are - passed in that only work with signed URLs, e.g. response header params. - The code attempts to strip all query parameters that match names of known parameters - from v2 and v4 signatures, regardless of the actual signature version used. - """ - split_url = urlparse.urlsplit(url) - qs = urlparse.parse_qsl(split_url.query, keep_blank_values=True) - blacklist = { - "x-amz-algorithm", - "x-amz-credential", - "x-amz-date", - "x-amz-expires", - "x-amz-signedheaders", - "x-amz-signature", - "x-amz-security-token", - "awsaccesskeyid", - "expires", - "signature", - } - filtered_qs = ((key, val) for key, val in qs if key.lower() not in blacklist) - # Note: Parameters that did not have a value in the original query string will have - # an '=' sign appended to it, e.g ?foo&bar becomes ?foo=&bar= - joined_qs = ("=".join(keyval) for keyval in filtered_qs) - split_url = split_url._replace(query="&".join(joined_qs)) - return split_url.geturl() - -
        [docs] def url(self, name, parameters=None, expire=None): - """ - Returns the URL of a remotely-hosted file - """ - # Preserve the trailing slash after normalizing the path. - name = self._normalize_name(self._clean_name(name)) - if self.custom_domain: - return "{}//{}/{}".format(self.url_protocol, self.custom_domain, filepath_to_uri(name)) - if expire is None: - expire = self.querystring_expire - - params = parameters.copy() if parameters else {} - params["Bucket"] = self.bucket.name - params["Key"] = self._encode_name(name) - url = self.bucket.meta.client.generate_presigned_url( - "get_object", Params=params, ExpiresIn=expire - ) - if self.querystring_auth: - return url - return self._strip_signing_parameters(url)
        - -
        [docs] def get_available_name(self, name, max_length=None): - """Overwrite existing file with the same name.""" - name = self._clean_name(name) - if self.file_overwrite: - return get_available_overwrite_name(name, max_length) - return super().get_available_name(name, max_length)
        -
        - -
        -
        -
        -
        - -
        -
        - - - - \ No newline at end of file diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/awsstorage/tests.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/awsstorage/tests.html index b456785099..75c095e747 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/awsstorage/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/awsstorage/tests.html @@ -684,7 +684,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/building_menu/building_menu.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/building_menu/building_menu.html index 8f30977124..14a977992d 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/building_menu/building_menu.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/building_menu/building_menu.html @@ -1346,7 +1346,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/building_menu/tests.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/building_menu/tests.html index dffad44667..ee41f30cca 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/building_menu/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/building_menu/tests.html @@ -253,7 +253,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/color_markups/tests.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/color_markups/tests.html index 73a3e0c85a..5224cfa005 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/color_markups/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/color_markups/tests.html @@ -142,7 +142,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/custom_gametime/custom_gametime.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/custom_gametime/custom_gametime.html index ecb659f0ce..a39bfc763f 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/custom_gametime/custom_gametime.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/custom_gametime/custom_gametime.html @@ -406,7 +406,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/custom_gametime/tests.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/custom_gametime/tests.html index 3da5acd851..970e1ccbec 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/custom_gametime/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/custom_gametime/tests.html @@ -130,7 +130,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/email_login/email_login.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/email_login/email_login.html index 8817659d80..84b0bc88c3 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/email_login/email_login.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/email_login/email_login.html @@ -441,7 +441,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/email_login/tests.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/email_login/tests.html index 323ddfd2dd..91decfaa3f 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/email_login/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/email_login/tests.html @@ -112,7 +112,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/callbackhandler.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/callbackhandler.html index 1364b874cd..918a40ae24 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/callbackhandler.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/callbackhandler.html @@ -301,7 +301,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/commands.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/commands.html index 2a5a4b06d6..da3a346f51 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/commands.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/commands.html @@ -658,7 +658,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/eventfuncs.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/eventfuncs.html index f5534e46fe..d00dd37534 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/eventfuncs.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/eventfuncs.html @@ -167,7 +167,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/scripts.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/scripts.html index 944b2e8005..325f926a11 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/scripts.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/scripts.html @@ -745,7 +745,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/tests.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/tests.html index 1efe3d51f0..4ffab2cbea 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/tests.html @@ -619,7 +619,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/utils.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/utils.html index 9c69f97e19..b38693589f 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/utils.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/ingame_python/utils.html @@ -336,7 +336,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.html index b2bc5ee6d1..d9b56e8840 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.html @@ -621,7 +621,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/mux_comms_cmds/tests.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/mux_comms_cmds/tests.html index 03d4892881..09b7d83849 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/mux_comms_cmds/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/mux_comms_cmds/tests.html @@ -162,7 +162,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/unixcommand/tests.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/unixcommand/tests.html index 21ac5f33c5..470476c543 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/unixcommand/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/unixcommand/tests.html @@ -126,7 +126,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/base_systems/unixcommand/unixcommand.html b/docs/1.0-dev/_modules/evennia/contrib/base_systems/unixcommand/unixcommand.html index ddf3dd3dc6..e22a7bfaee 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/base_systems/unixcommand/unixcommand.html +++ b/docs/1.0-dev/_modules/evennia/contrib/base_systems/unixcommand/unixcommand.html @@ -374,7 +374,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/commands.html b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/commands.html index 2e217256e4..fe39de3613 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/commands.html +++ b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/commands.html @@ -857,7 +857,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/menu.html b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/menu.html index 9069dcd96a..ff1c041da5 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/menu.html +++ b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/menu.html @@ -430,7 +430,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/objects.html b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/objects.html index db3440fc09..06f9e66efb 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/objects.html +++ b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/objects.html @@ -1157,7 +1157,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/room.html b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/room.html index 102dd2149b..039622a7f8 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/room.html +++ b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/room.html @@ -316,7 +316,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/scripts.html b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/scripts.html index 8e5d01e81c..835651d0c3 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/scripts.html +++ b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/scripts.html @@ -107,7 +107,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/state.html b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/state.html index 4a391f1f8c..cb19fdb810 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/state.html +++ b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/state.html @@ -384,7 +384,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/tests.html b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/tests.html index 0f42b558b8..861efb4098 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/tests.html @@ -381,7 +381,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/utils.html b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/utils.html index 83a004aafc..d49a931d38 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/utils.html +++ b/docs/1.0-dev/_modules/evennia/contrib/full_systems/evscaperoom/utils.html @@ -271,7 +271,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/barter/barter.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/barter/barter.html index 168952b22d..9b15a325b1 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/barter/barter.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/barter/barter.html @@ -975,7 +975,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/barter/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/barter/tests.html index b6f8b86b3c..bde6631e9f 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/barter/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/barter/tests.html @@ -223,7 +223,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/clothing/clothing.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/clothing/clothing.html index 2b0bcdb701..7929008a05 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/clothing/clothing.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/clothing/clothing.html @@ -820,7 +820,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/clothing/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/clothing/tests.html index 901c7141e6..77c80c60a8 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/clothing/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/clothing/tests.html @@ -209,7 +209,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/cooldowns/cooldowns.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/cooldowns/cooldowns.html index c956fdf222..8cfb578865 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/cooldowns/cooldowns.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/cooldowns/cooldowns.html @@ -284,7 +284,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/cooldowns/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/cooldowns/tests.html index 84ea20244b..ac202dcadd 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/cooldowns/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/cooldowns/tests.html @@ -223,7 +223,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/crafting.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/crafting.html index eaf177bdfc..961d09a3bd 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/crafting.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/crafting.html @@ -1145,7 +1145,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/example_recipes.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/example_recipes.html index e26a96c3a8..21b8cd428f 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/example_recipes.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/example_recipes.html @@ -607,7 +607,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/tests.html index 7a3e3f4d41..2de90368d5 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/crafting/tests.html @@ -765,7 +765,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/gendersub/gendersub.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/gendersub/gendersub.html index fb04f38539..efcefb770f 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/gendersub/gendersub.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/gendersub/gendersub.html @@ -235,7 +235,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/gendersub/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/gendersub/tests.html index 4b641a91fa..8bd7118472 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/gendersub/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/gendersub/tests.html @@ -104,7 +104,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/mail/mail.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/mail/mail.html index 42a8936221..cf6a9d296a 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/mail/mail.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/mail/mail.html @@ -436,7 +436,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/mail/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/mail/tests.html index 08ec530920..79b144636d 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/mail/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/mail/tests.html @@ -123,7 +123,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/multidescer/multidescer.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/multidescer/multidescer.html index 2240a7373e..963be95988 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/multidescer/multidescer.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/multidescer/multidescer.html @@ -345,7 +345,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/multidescer/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/multidescer/tests.html index 9fa3973a41..db6e0ce8dc 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/multidescer/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/multidescer/tests.html @@ -116,7 +116,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/puzzles/puzzles.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/puzzles/puzzles.html index 06c04023e5..7487eb3f11 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/puzzles/puzzles.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/puzzles/puzzles.html @@ -894,7 +894,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/puzzles/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/puzzles/tests.html index faa7711753..5f6df2f37a 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/puzzles/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/puzzles/tests.html @@ -1051,7 +1051,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_basic.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_basic.html index 9249c8f499..54141cd859 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_basic.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_basic.html @@ -45,7 +45,7 @@ """ Simple turn-based combat system -Contrib - Tim Ashley Jenkins 2017 +Contrib - Tim Ashley Jenkins 2017, Refactor by Griatch 2022 This is a framework for a simple turn-based combat system, similar to those used in D&D-style tabletop role playing games. It allows @@ -106,237 +106,235 @@ """ -
        [docs]def roll_init(character): +
        [docs]class BasicCombatRules: """ - Rolls a number between 1-1000 to determine initiative. + Stores all combat rules and helper methods. - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. """ - return randint(1, 1000)
        +
        [docs] def roll_init(self, character): + """ + Rolls a number between 1-1000 to determine initiative. -
        [docs]def get_attack(attacker, defender): - """ - Returns a value for an attack roll. + Args: + character (obj): The character to determine initiative for - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked + Returns: + initiative (int): The character's place in initiative - higher + numbers go first. - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. + Notes: + By default, does not reference the character and simply returns + a random integer from 1 to 1000. - Notes: - By default, returns a random integer from 1 to 100 without using any - properties from either the attacker or defender. + Since the character is passed to this function, you can easily reference + a character's stats to determine an initiative roll - for example, if your + character has a 'dexterity' attribute, you can use it to give that character + an advantage in turn order, like so: - This can easily be expanded to return a value based on characters stats, - equipment, and abilities. This is why the attacker and defender are passed - to this function, even though nothing from either one are used in this example. - """ - # For this example, just return a random integer up to 100. - attack_value = randint(1, 100) - return attack_value
        + return (randint(1,20)) + character.db.dexterity + This way, characters with a higher dexterity will go first more often. + """ + return randint(1, 1000)
        -
        [docs]def get_defense(attacker, defender): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. +
        [docs] def get_attack(self, attacker, defender): + """ + Returns a value for an attack roll. - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. + Returns: + attack_value (int): Attack roll value, compared against a defense value + to determine whether an attack hits or misses. - Notes: - By default, returns 50, not taking any properties of the defender or - attacker into account. + Notes: + By default, returns a random integer from 1 to 100 without using any + properties from either the attacker or defender. - As above, this can be expanded upon based on character stats and equipment. - """ - # For this example, just return 50, for about a 50/50 chance of hit. - defense_value = 50 - return defense_value
        + This can easily be expanded to return a value based on characters stats, + equipment, and abilities. This is why the attacker and defender are passed + to this function, even though nothing from either one are used in this example. + """ + # For this example, just return a random integer up to 100. + attack_value = randint(1, 100) + return attack_value
        +
        [docs] def get_defense(self, attacker, defender): + """ + Returns a value for defense, which an attack roll must equal or exceed in order + for an attack to hit. -
        [docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged + Returns: + defense_value (int): Defense value, compared against an attack roll + to determine whether an attack hits or misses. - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. + Notes: + By default, returns 50, not taking any properties of the defender or + attacker into account. - Notes: - By default, returns a random integer from 15 to 25 without using any - properties from either the attacker or defender. + As above, this can be expanded upon based on character stats and equipment. + """ + # For this example, just return 50, for about a 50/50 chance of hit. + defense_value = 50 + return defense_value
        - Again, this can be expanded upon. - """ - # For this example, just generate a number between 15 and 25. - damage_value = randint(15, 25) - return damage_value
        +
        [docs] def get_damage(self, attacker, defender): + """ + Returns a value for damage to be deducted from the defender's HP after abilities + successful hit. + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being damaged -
        [docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. + Returns: + damage_value (int): Damage value, which is to be deducted from the defending + character's HP. - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
        + Notes: + By default, returns a random integer from 15 to 25 without using any + properties from either the attacker or defender. + Again, this can be expanded upon. + """ + # For this example, just generate a number between 15 and 25. + damage_value = randint(15, 25) + return damage_value
        -
        [docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. +
        [docs] def apply_damage(self, defender, damage): + """ + Applies damage to a target, reducing their HP by the damage amount to a + minimum of 0. - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
        - - -
        [docs]def resolve_attack(attacker, defender, attack_value=None, defense_value=None): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Notes: - Even though the attack and defense values are calculated - extremely simply, they are separated out into their own functions - so that they are easier to expand upon. - """ - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents("%s's attack misses %s!" % (attacker, defender)) - else: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - attacker.location.msg_contents( - "%s hits %s for %i damage!" % (attacker, defender, damage_value) - ) - apply_damage(defender, damage_value) - # If defender HP is reduced to 0 or less, call at_defeat. + Args: + defender (obj): Character taking damage + damage (int): Amount of damage being taken + """ + defender.db.hp -= damage # Reduce defender's HP by the damage dealt. + # If this reduces it to 0 or less, set HP to 0. if defender.db.hp <= 0: - at_defeat(defender)
        + defender.db.hp = 0
        + +
        [docs] def at_defeat(self, defeated): + """ + Announces the defeat of a fighter in combat. + + Args: + defeated (obj): Fighter that's been defeated. + + Notes: + All this does is announce a defeat message by default, but if you + want anything else to happen to defeated fighters (like putting them + into a dying state or something similar) then this is the place to + do it. + """ + defeated.location.msg_contents("%s has been defeated!" % defeated)
        + +
        [docs] def resolve_attack(self, attacker, defender, attack_value=None, defense_value=None): + """ + Resolves an attack and outputs the result. + + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked + + Notes: + Even though the attack and defense values are calculated + extremely simply, they are separated out into their own functions + so that they are easier to expand upon. + """ + # Get an attack roll from the attacker. + if not attack_value: + attack_value = self.get_attack(attacker, defender) + # Get a defense value from the defender. + if not defense_value: + defense_value = self.get_defense(attacker, defender) + # If the attack value is lower than the defense value, miss. Otherwise, hit. + if attack_value < defense_value: + attacker.location.msg_contents("%s's attack misses %s!" % (attacker, defender)) + else: + damage_value = self.get_damage(attacker, defender) # Calculate damage value. + # Announce damage dealt and apply damage. + attacker.location.msg_contents( + "%s hits %s for %i damage!" % (attacker, defender, damage_value) + ) + self.apply_damage(defender, damage_value) + # If defender HP is reduced to 0 or less, call at_defeat. + if defender.db.hp <= 0: + self.at_defeat(defender)
        + +
        [docs] def combat_cleanup(self, character): + """ + Cleans up all the temporary combat-related attributes on a character. + + Args: + character (obj): Character to have their combat attributes removed + + Notes: + Any attribute whose key begins with 'combat_' is temporary and no + longer needed once a fight ends. + """ + for attr in character.attributes.all(): + if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... + character.attributes.remove(key=attr.key) # ...then delete it!
        + +
        [docs] def is_in_combat(self, character): + """ + Returns true if the given character is in combat. + + Args: + character (obj): Character to determine if is in combat or not + + Returns: + (bool): True if in combat or False if not in combat + """ + return bool(character.db.combat_turnhandler)
        + +
        [docs] def is_turn(self, character): + """ + Returns true if it's currently the given character's turn in combat. + + Args: + character (obj): Character to determine if it is their turn or not + + Returns: + (bool): True if it is their turn or False otherwise + """ + turnhandler = character.db.combat_turnhandler + currentchar = turnhandler.db.fighters[turnhandler.db.turn] + return bool(character == currentchar)
        + +
        [docs] def spend_action(self, character, actions, action_name=None): + """ + Spends a character's available combat actions and checks for end of turn. + + Args: + character (obj): Character spending the action + actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions + + Keyword Args: + action_name (str or None): If a string is given, sets character's last action in + combat to provided string + """ + if action_name: + character.db.combat_lastaction = action_name + if actions == "all": # If spending all actions + character.db.combat_actionsleft = 0 # Set actions to 0 + else: + character.db.combat_actionsleft -= actions # Use up actions. + if character.db.combat_actionsleft < 0: + character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions + character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
        -
        [docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
        - - -
        [docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
        - - -
        [docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
        - - -
        [docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
        - +COMBAT_RULES = BasicCombatRules() """ ---------------------------------------------------------------------------- @@ -350,6 +348,7 @@ A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands. """ + rules = COMBAT_RULES
        [docs] def at_object_creation(self): """ @@ -383,7 +382,7 @@ """ # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): + if self.rules.is_in_combat(self): self.msg("You can't exit a room while in combat!") return False # Returning false keeps the character from moving. if self.db.HP <= 0: @@ -411,6 +410,8 @@ remaining participants choose to end the combat with the 'disengage' command. """ + rules = COMBAT_RULES +
        [docs] def at_script_creation(self): """ Called once, when the script is created. @@ -432,9 +433,10 @@ # Add a reference to this script to the room self.obj.db.combat_turnhandler = self - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) + # Roll initiative and sort the list of fighters depending on who rolls highest to determine + # turn order. The initiative roll is determined by the roll_init method and can be + # customized easily. + ordered_by_roll = sorted(self.db.fighters, key=self.rules.roll_init, reverse=True) self.db.fighters = ordered_by_roll # Announce the turn order. @@ -452,7 +454,9 @@ Called at script termination. """ for fighter in self.db.fighters: - combat_cleanup(fighter) # Clean up the combat attributes for every fighter. + if fighter: + # Clean up the combat attributes for every fighter. + self.rules.combat_cleanup(fighter) self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
        [docs] def at_repeat(self): @@ -467,7 +471,7 @@ if self.db.timer <= 0: # Force current character to disengage if timer runs out. self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( + self.rules.spend_action( currentchar, "all", action_name="disengage" ) # Spend all remaining actions. return @@ -483,7 +487,8 @@ Args: character (obj): Character to initialize for combat. """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. + # Clean up leftover combat attributes beforehand, just in case. + self.rules.combat_cleanup(character) character.db.combat_actionsleft = ( 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 ) @@ -604,6 +609,9 @@ key = "fight" help_category = "combat" + rules = COMBAT_RULES + combat_handler_class = TBBasicTurnHandler +
        [docs] def func(self): """ This performs the actual command. @@ -614,7 +622,7 @@ if not self.caller.db.hp: # If you don't have any hp self.caller.msg("You can't start a fight if you've been defeated!") return - if is_in_combat(self.caller): # Already in a fight + if self.rules.is_in_combat(self.caller): # Already in a fight self.caller.msg("You're already in a fight!") return for thing in here.contents: # Test everything in the room to add it to the fight. @@ -629,8 +637,7 @@ return here.msg_contents("%s starts a fight!" % self.caller) # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler")
        - # Remember you'll have to change the path to the script if you copy this code to your own modules! + here.scripts.add(self.command_handler_class)
        [docs]class CmdAttack(Command): @@ -647,15 +654,17 @@ key = "attack" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): "This performs the actual command." "Set the attacker to the caller and the defender to the target." - if not is_in_combat(self.caller): # If not in combat, can't attack. + if not self.rules.is_in_combat(self.caller): # If not in combat, can't attack. self.caller.msg("You can only do that in combat. (see: help fight)") return - if not is_turn(self.caller): # If it's not your turn, can't attack. + if not self.rules.is_turn(self.caller): # If it's not your turn, can't attack. self.caller.msg("You can only do that on your turn.") return @@ -678,8 +687,8 @@ return "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender) - spend_action(self.caller, 1, action_name="attack") # Use up one action.
        + self.rules.resolve_attack(attacker, defender) + self.rules.spend_action(self.caller, 1, action_name="attack") # Use up one action.
        [docs]class CmdPass(Command): @@ -697,22 +706,25 @@ aliases = ["wait", "hold"] help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): """ This performs the actual command. """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. + if not self.rules.is_in_combat(self.caller): # Can only pass a turn in combat. self.caller.msg("You can only do that in combat. (see: help fight)") return - if not is_turn(self.caller): # Can only pass if it's your turn. + if not self.rules.is_turn(self.caller): # Can only pass if it's your turn. self.caller.msg("You can only do that on your turn.") return self.caller.location.msg_contents( "%s takes no further action, passing the turn." % self.caller ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
        + # Spend all remaining actions. + self.rules.spend_action(self.caller, "all", action_name="pass")
        [docs]class CmdDisengage(Command): @@ -731,20 +743,23 @@ aliases = ["spare"] help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): """ This performs the actual command. """ - if not is_in_combat(self.caller): # If you're not in combat + if not self.rules.is_in_combat(self.caller): # If you're not in combat self.caller.msg("You can only do that in combat. (see: help fight)") return - if not is_turn(self.caller): # If it's not your turn + if not self.rules.is_turn(self.caller): # If it's not your turn self.caller.msg("You can only do that on your turn.") return self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. + # Spend all remaining actions. + self.rules.spend_action(self.caller, "all", action_name="disengage") """ The action_name kwarg sets the character's last action to "disengage", which is checked by the turn handler script to see if all fighters have disengaged. @@ -765,10 +780,12 @@ key = "rest" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): "This performs the actual command." - if is_in_combat(self.caller): # If you're in combat + if self.rules.is_in_combat(self.caller): # If you're in combat self.caller.msg("You can't rest while you're in combat.") return @@ -792,17 +809,21 @@ topics related to the game. """ + rules = COMBAT_RULES + combat_help_text = ( + "Available combat commands:|/" + "|wAttack:|n Attack a target, attempting to deal damage.|/" + "|wPass:|n Pass your turn without further action.|/" + "|wDisengage:|n End your turn and attempt to end combat.|/" + ) + # Just like the default help command, but will give quick # tips on combat when used in a fight with no arguments.
        [docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack a target, attempting to deal damage.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - ) + # In combat and entered 'help' alone + if self.rules.is_in_combat(self.caller) and not self.args: + self.caller.msg(self.combat_help_text) else: super().func() # Call the default help command
        @@ -858,7 +879,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_equip.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_equip.html index aff5d7d41d..5fbc6a8cad 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_equip.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_equip.html @@ -45,7 +45,7 @@ """ Simple turn-based combat system with equipment -Contrib - Tim Ashley Jenkins 2017 +Contrib - Tim Ashley Jenkins 2017, Refactor by Griatch 2022 This is a version of the 'turnbattle' contrib with a basic system for weapons and armor implemented. Weapons can have unique damage ranges @@ -99,8 +99,8 @@ """ from random import randint -from evennia import DefaultCharacter, Command, default_cmds, DefaultScript, DefaultObject -from evennia.commands.default.help import CmdHelp +from evennia import Command, default_cmds, DefaultObject +from . import tb_basic """ ---------------------------------------------------------------------------- @@ -118,276 +118,156 @@ """ -
        [docs]def roll_init(character): +
        [docs]class EquipmentCombatRules(tb_basic.BasicCombatRules): """ - Rolls a number between 1-1000 to determine initiative. + Has all the methods of the basic combat, with the addition of equipment. - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. """ - return randint(1, 1000)
        +
        [docs] def get_attack(self, attacker, defender): + """ + Returns a value for an attack roll. -
        [docs]def get_attack(attacker, defender): - """ - Returns a value for an attack roll. + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked + Returns: + attack_value (int): Attack roll value, compared against a defense value + to determine whether an attack hits or misses. - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. + Notes: + In this example, a weapon's accuracy bonus is factored into the attack + roll. Lighter weapons are more accurate but less damaging, and heavier + weapons are less accurate but deal more damage. Of course, you can + change this paradigm completely in your own game. + """ + # Start with a roll from 1 to 100. + attack_value = randint(1, 100) + accuracy_bonus = 0 + # If armed, add weapon's accuracy bonus. + if attacker.db.wielded_weapon: + weapon = attacker.db.wielded_weapon + accuracy_bonus += weapon.db.accuracy_bonus + # If unarmed, use character's unarmed accuracy bonus. + else: + accuracy_bonus += attacker.db.unarmed_accuracy + # Add the accuracy bonus to the attack roll. + attack_value += accuracy_bonus + return attack_value
        - Notes: - In this example, a weapon's accuracy bonus is factored into the attack - roll. Lighter weapons are more accurate but less damaging, and heavier - weapons are less accurate but deal more damage. Of course, you can - change this paradigm completely in your own game. - """ - # Start with a roll from 1 to 100. - attack_value = randint(1, 100) - accuracy_bonus = 0 - # If armed, add weapon's accuracy bonus. - if attacker.db.wielded_weapon: - weapon = attacker.db.wielded_weapon - accuracy_bonus += weapon.db.accuracy_bonus - # If unarmed, use character's unarmed accuracy bonus. - else: - accuracy_bonus += attacker.db.unarmed_accuracy - # Add the accuracy bonus to the attack roll. - attack_value += accuracy_bonus - return attack_value
        +
        [docs] def get_defense(self, attacker, defender): + """ + Returns a value for defense, which an attack roll must equal or exceed in order + for an attack to hit. + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked -
        [docs]def get_defense(attacker, defender): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. + Returns: + defense_value (int): Defense value, compared against an attack roll + to determine whether an attack hits or misses. - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked + Notes: + Characters are given a default defense value of 50 which can be + modified up or down by armor. In this example, wearing armor actually + makes you a little easier to hit, but reduces incoming damage. + """ + # Start with a defense value of 50 for a 50/50 chance to hit. + defense_value = 50 + # Modify this value based on defender's armor. + if defender.db.worn_armor: + armor = defender.db.worn_armor + defense_value += armor.db.defense_modifier + return defense_value
        - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. +
        [docs] def get_damage(self, attacker, defender): + """ + Returns a value for damage to be deducted from the defender's HP after abilities + successful hit. - Notes: - Characters are given a default defense value of 50 which can be - modified up or down by armor. In this example, wearing armor actually - makes you a little easier to hit, but reduces incoming damage. - """ - # Start with a defense value of 50 for a 50/50 chance to hit. - defense_value = 50 - # Modify this value based on defender's armor. - if defender.db.worn_armor: - armor = defender.db.worn_armor - defense_value += armor.db.defense_modifier - return defense_value
        + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being damaged + Returns: + damage_value (int): Damage value, which is to be deducted from the defending + character's HP. -
        [docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - Damage is determined by the attacker's wielded weapon, or the attacker's - unarmed damage range if no weapon is wielded. Incoming damage is reduced - by the defender's armor. - """ - damage_value = 0 - # Generate a damage value from wielded weapon if armed - if attacker.db.wielded_weapon: - weapon = attacker.db.wielded_weapon - # Roll between minimum and maximum damage - damage_value = randint(weapon.db.damage_range[0], weapon.db.damage_range[1]) - # Use attacker's unarmed damage otherwise - else: - damage_value = randint( - attacker.db.unarmed_damage_range[0], attacker.db.unarmed_damage_range[1] - ) - # If defender is armored, reduce incoming damage - if defender.db.worn_armor: - armor = defender.db.worn_armor - damage_value -= armor.db.damage_reduction - # Make sure minimum damage is 0 - if damage_value < 0: + Notes: + Damage is determined by the attacker's wielded weapon, or the attacker's + unarmed damage range if no weapon is wielded. Incoming damage is reduced + by the defender's armor. + """ damage_value = 0 - return damage_value
        + # Generate a damage value from wielded weapon if armed + if attacker.db.wielded_weapon: + weapon = attacker.db.wielded_weapon + # Roll between minimum and maximum damage + damage_value = randint(weapon.db.damage_range[0], weapon.db.damage_range[1]) + # Use attacker's unarmed damage otherwise + else: + damage_value = randint( + attacker.db.unarmed_damage_range[0], attacker.db.unarmed_damage_range[1] + ) + # If defender is armored, reduce incoming damage + if defender.db.worn_armor: + armor = defender.db.worn_armor + damage_value -= armor.db.damage_reduction + # Make sure minimum damage is 0 + if damage_value < 0: + damage_value = 0 + return damage_value
        +
        [docs] def resolve_attack(self, attacker, defender, attack_value=None, defense_value=None): + """ + Resolves an attack and outputs the result. -
        [docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
        - - -
        [docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
        - - -
        [docs]def resolve_attack(attacker, defender, attack_value=None, defense_value=None): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Notes: - Even though the attack and defense values are calculated - extremely simply, they are separated out into their own functions - so that they are easier to expand upon. - """ - # Get the attacker's weapon type to reference in combat messages. - attackers_weapon = "attack" - if attacker.db.wielded_weapon: - weapon = attacker.db.wielded_weapon - attackers_weapon = weapon.db.weapon_type_name - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents( - "%s's %s misses %s!" % (attacker, attackers_weapon, defender) - ) - else: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - if damage_value > 0: + Notes: + Even though the attack and defense values are calculated + extremely simply, they are separated out into their own functions + so that they are easier to expand upon. + """ + # Get the attacker's weapon type to reference in combat messages. + attackers_weapon = "attack" + if attacker.db.wielded_weapon: + weapon = attacker.db.wielded_weapon + attackers_weapon = weapon.db.weapon_type_name + # Get an attack roll from the attacker. + if not attack_value: + attack_value = self.get_attack(attacker, defender) + # Get a defense value from the defender. + if not defense_value: + defense_value = self.get_defense(attacker, defender) + # If the attack value is lower than the defense value, miss. Otherwise, hit. + if attack_value < defense_value: attacker.location.msg_contents( - "%s's %s strikes %s for %i damage!" - % (attacker, attackers_weapon, defender, damage_value) + "%s's %s misses %s!" % (attacker, attackers_weapon, defender) ) else: - attacker.location.msg_contents( - "%s's %s bounces harmlessly off %s!" % (attacker, attackers_weapon, defender) - ) - apply_damage(defender, damage_value) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
        + damage_value = self.get_damage(attacker, defender) # Calculate damage value. + # Announce damage dealt and apply damage. + if damage_value > 0: + attacker.location.msg_contents( + "%s's %s strikes %s for %i damage!" + % (attacker, attackers_weapon, defender, damage_value) + ) + else: + attacker.location.msg_contents( + "%s's %s bounces harmlessly off %s!" % (attacker, attackers_weapon, defender) + ) + self.apply_damage(defender, damage_value) + # If defender HP is reduced to 0 or less, call at_defeat. + if defender.db.hp <= 0: + self.at_defeat(defender)
        -
        [docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
        - - -
        [docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
        - - -
        [docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
        - - -
        [docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
        - +COMBAT_RULES = EquipmentCombatRules() """ ---------------------------------------------------------------------------- @@ -396,7 +276,7 @@ """ -
        [docs]class TBEquipTurnHandler(DefaultScript): +
        [docs]class TBEquipTurnHandler(tb_basic.TBBasicTurnHandler): """ This is the script that handles the progression of combat through turns. On creation (when a fight is started) it adds all combat-ready characters @@ -407,174 +287,7 @@ Fights persist until only one participant is left with any HP or all remaining participants choose to end the combat with the 'disengage' command. """ - -
        [docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
        - -
        [docs] def at_stop(self): - """ - Called at script termination. - """ - for fighter in self.db.fighters: - combat_cleanup(fighter) # Clean up the combat attributes for every fighter. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
        - -
        [docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
        - -
        [docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. - - Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
        - -
        [docs] def start_turn(self, character): - """ - Readies a character for the start of their turn by replenishing their - available actions and notifying them that their turn has come up. - - Args: - character (obj): Character to be readied. - - Notes: - Here, you only get one action per turn, but you might want to allow more than - one per turn, or even grant a number of actions based on a character's - attributes. You can even add multiple different kinds of actions, I.E. actions - separated for movement, by adding "character.db.combat_movesleft = 3" or - something similar. - """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Prompt the character for their turn and give some information. - character.msg("|wIt's your turn! You have %i HP remaining.|n" % character.db.hp)
        - -
        [docs] def next_turn(self): - """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - newchar = self.db.fighters[self.db.turn] # Note the new character - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn.
        - -
        [docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. - - Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
        - -
        [docs] def join_fight(self, character): - """ - Adds a new character to a fight already in progress. - - Args: - character (obj): Character to be added to the fight. - """ - # Inserts the fighter to the turn order, right behind whoever's turn it currently is. - self.db.fighters.insert(self.db.turn, character) - # Tick the turn counter forward one to compensate. - self.db.turn += 1 - # Initialize the character like you do at the start. - self.initialize_for_combat(character)
        + rules = COMBAT_RULES
        """ @@ -587,7 +300,9 @@
        [docs]class TBEWeapon(DefaultObject): """ A weapon which can be wielded in combat with the 'wield' command. + """ + rules = COMBAT_RULES
        [docs] def at_object_creation(self): """ @@ -636,7 +351,7 @@ """ Can't drop in combat. """ - if is_in_combat(dropper): + if self.rules.is_in_combat(dropper): dropper.msg("You can't doff armor in a fight!") return False return True
        @@ -653,7 +368,7 @@ """ Can't give away in combat. """ - if is_in_combat(giver): + if self.rules.is_in_combat(giver): dropper.msg("You can't doff armor in a fight!") return False return True
        @@ -667,7 +382,7 @@ giver.location.msg_contents("%s removes %s." % (giver, self))
        -
        [docs]class TBEquipCharacter(DefaultCharacter): +
        [docs]class TBEquipCharacter(tb_basic.TBBasicCharacter): """ A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands. @@ -691,32 +406,7 @@ You may want to expand this to include various 'stats' that can be changed at creation and factor into combat calculations. - """
        - -
        [docs] def at_pre_move(self, destination): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
        + """ """ @@ -726,7 +416,7 @@ """ -
        [docs]class CmdFight(Command): +
        [docs]class CmdFight(tb_basic.CmdFight): """ Starts a fight with everyone in the same room as you. @@ -741,36 +431,11 @@ key = "fight" help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.game_systems.turnbattle.tb_equip.TBEquipTurnHandler")
        - # Remember you'll have to change the path to the script if you copy this code to your own modules! + rules = COMBAT_RULES + command_handler_class = TBEquipTurnHandler
        -
        [docs]class CmdAttack(Command): +
        [docs]class CmdAttack(tb_basic.CmdAttack): """ Attacks another character. @@ -784,42 +449,10 @@ key = "attack" help_category = "combat" -
        [docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender) - spend_action(self.caller, 1, action_name="attack") # Use up one action.
        + rules = COMBAT_RULES
        -
        [docs]class CmdPass(Command): +
        [docs]class CmdPass(tb_basic.CmdPass): """ Passes on your turn. @@ -834,25 +467,10 @@ aliases = ["wait", "hold"] help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
        + rules = COMBAT_RULES
        -
        [docs]class CmdDisengage(Command): +
        [docs]class CmdDisengage(tb_basic.CmdDisengage): """ Passes your turn and attempts to end combat. @@ -868,27 +486,10 @@ aliases = ["spare"] help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
        + rules = COMBAT_RULES
        -
        [docs]class CmdRest(Command): +
        [docs]class CmdRest(tb_basic.CmdRest): """ Recovers damage. @@ -902,21 +503,10 @@ key = "rest" help_category = "combat" -
        [docs] def func(self): - "This performs the actual command." - - if is_in_combat(self.caller): # If you're in combat - self.caller.msg("You can't rest while you're in combat.") - return - - self.caller.db.hp = self.caller.db.max_hp # Set current HP to maximum - self.caller.location.msg_contents("%s rests to recover HP." % self.caller) - """ - You'll probably want to replace this with your own system for recovering HP. - """
        + rules = COMBAT_RULES
        -
        [docs]class CmdCombatHelp(CmdHelp): +
        [docs]class CmdCombatHelp(tb_basic.CmdCombatHelp): """ View help or a list of topics @@ -929,19 +519,7 @@ topics related to the game. """ - # Just like the default help command, but will give quick - # tips on combat when used in a fight with no arguments. - -
        [docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack a target, attempting to deal damage.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - ) - else: - super().func() # Call the default help command
        + rules = COMBAT_RULES
        [docs]class CmdWield(Command): @@ -962,13 +540,15 @@ key = "wield" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): """ This performs the actual command. """ # If in combat, check to see if it's your turn. - if is_in_combat(self.caller): - if not is_turn(self.caller): + if self.rules.is_in_combat(self.caller): + if not self.rules.is_turn(self.caller): self.caller.msg("You can only do that on your turn.") return if not self.args: @@ -977,7 +557,8 @@ weapon = self.caller.search(self.args, candidates=self.caller.contents) if not weapon: return - if not weapon.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon", exact=True): + if not weapon.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon", + exact=True): self.caller.msg("That's not a weapon!") # Remember to update the path to the weapon typeclass if you move this module! return @@ -992,8 +573,8 @@ "%s lowers %s and wields %s." % (self.caller, old_weapon, weapon) ) # Spend an action if in combat. - if is_in_combat(self.caller): - spend_action(self.caller, 1, action_name="wield") # Use up one action.
        + if self.rules.is_in_combat(self.caller): + self.rules.spend_action(self.caller, 1, action_name="wield") # Use up one action.
        [docs]class CmdUnwield(Command): @@ -1010,13 +591,15 @@ key = "unwield" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): """ This performs the actual command. """ # If in combat, check to see if it's your turn. - if is_in_combat(self.caller): - if not is_turn(self.caller): + if self.rules.is_in_combat(self.caller): + if not self.rules.is_turn(self.caller): self.caller.msg("You can only do that on your turn.") return if not self.caller.db.wielded_weapon: @@ -1042,12 +625,14 @@ key = "don" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): """ This performs the actual command. """ # Can't do this in combat - if is_in_combat(self.caller): + if self.rules.is_in_combat(self.caller): self.caller.msg("You can't don armor in a fight!") return if not self.args: @@ -1056,7 +641,8 @@ armor = self.caller.search(self.args, candidates=self.caller.contents) if not armor: return - if not armor.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor", exact=True): + if not armor.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor", + exact=True): self.caller.msg("That's not armor!") # Remember to update the path to the armor typeclass if you move this module! return @@ -1087,12 +673,14 @@ key = "doff" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): """ This performs the actual command. """ # Can't do this in combat - if is_in_combat(self.caller): + if self.rules.is_in_combat(self.caller): self.caller.msg("You can't doff armor in a fight!") return if not self.caller.db.worn_armor: @@ -1214,7 +802,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_items.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_items.html index 6184f8f272..bcc05d6646 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_items.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_items.html @@ -111,11 +111,11 @@ """ from random import randint -from evennia import DefaultCharacter, Command, default_cmds, DefaultScript +from evennia import Command, default_cmds from evennia.commands.default.muxcommand import MuxCommand -from evennia.commands.default.help import CmdHelp from evennia.prototypes.spawner import spawn from evennia import TICKER_HANDLER as tickerhandler +from . import tb_basic """ ---------------------------------------------------------------------------- @@ -147,1217 +147,465 @@ """ -
        [docs]def roll_init(character): - """ - Rolls a number between 1-1000 to determine initiative. +
        [docs]class ItemCombatRules(tb_basic.BasicCombatRules): - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. - """ - return randint(1, 1000)
        - - -
        [docs]def get_attack(attacker, defender): - """ - Returns a value for an attack roll. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. - - Notes: - This is where conditions affecting attack rolls are applied, as well. - Accuracy Up and Accuracy Down are also accounted for in itemfunc_attack(), - so that attack items' accuracy is affected as well. - """ - # For this example, just return a random integer up to 100. - attack_value = randint(1, 100) - # Add to the roll if the attacker has the "Accuracy Up" condition. - if "Accuracy Up" in attacker.db.conditions: - attack_value += ACC_UP_MOD - # Subtract from the roll if the attack has the "Accuracy Down" condition. - if "Accuracy Down" in attacker.db.conditions: - attack_value += ACC_DOWN_MOD - return attack_value
        - - -
        [docs]def get_defense(attacker, defender): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. - - Notes: - This is where conditions affecting defense are accounted for. - """ - # For this example, just return 50, for about a 50/50 chance of hit. - defense_value = 50 - # Add to defense if the defender has the "Defense Up" condition. - if "Defense Up" in defender.db.conditions: - defense_value += DEF_UP_MOD - # Subtract from defense if the defender has the "Defense Down" condition. - if "Defense Down" in defender.db.conditions: - defense_value += DEF_DOWN_MOD - return defense_value
        - - -
        [docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - This is where conditions affecting damage are accounted for. Since attack items - roll their own damage in itemfunc_attack(), their damage is unaffected by any - conditions. - """ - # For this example, just generate a number between 15 and 25. - damage_value = randint(15, 25) - # Add to damage roll if attacker has the "Damage Up" condition. - if "Damage Up" in attacker.db.conditions: - damage_value += DMG_UP_MOD - # Subtract from the roll if the attacker has the "Damage Down" condition. - if "Damage Down" in attacker.db.conditions: - damage_value += DMG_DOWN_MOD - return damage_value
        - - -
        [docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. - - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
        - - -
        [docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
        - - -
        [docs]def resolve_attack( - attacker, - defender, - attack_value=None, - defense_value=None, - damage_value=None, - inflict_condition=[], -): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Options: - attack_value (int): Override for attack roll - defense_value (int): Override for defense value - damage_value (int): Override for damage value - inflict_condition (list): Conditions to inflict upon hit, a - list of tuples formated as (condition(str), duration(int)) - - Notes: - This function is called by normal attacks as well as attacks - made with items. - """ - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents("%s's attack misses %s!" % (attacker, defender)) - else: - if not damage_value: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - attacker.location.msg_contents( - "%s hits %s for %i damage!" % (attacker, defender, damage_value) - ) - apply_damage(defender, damage_value) - # Inflict conditions on hit, if any specified - for condition in inflict_condition: - add_condition(defender, attacker, condition[0], condition[1]) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
        - - -
        [docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
        - - -
        [docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
        - - -
        [docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
        - - -
        [docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
        - - -
        [docs]def spend_item_use(item, user): - """ - Spends one use on an item with limited uses. - - Args: - item (obj): Item being used - user (obj): Character using the item - - Notes: - If item.db.item_consumable is 'True', the item is destroyed if it - runs out of uses - if it's a string instead of 'True', it will also - spawn a new object as residue, using the value of item.db.item_consumable - as the name of the prototype to spawn. - """ - item.db.item_uses -= 1 # Spend one use - - if item.db.item_uses > 0: # Has uses remaining - # Inform the player - user.msg("%s has %i uses remaining." % (item.key.capitalize(), item.db.item_uses)) - - else: # All uses spent - - if not item.db.item_consumable: # Item isn't consumable - # Just inform the player that the uses are gone - user.msg("%s has no uses remaining." % item.key.capitalize()) - - else: # If item is consumable - if item.db.item_consumable == True: # If the value is 'True', just destroy the item - user.msg("%s has been consumed." % item.key.capitalize()) - item.delete() # Delete the spent item - - else: # If a string, use value of item_consumable to spawn an object in its place - residue = spawn({"prototype": item.db.item_consumable})[0] # Spawn the residue - residue.location = item.location # Move the residue to the same place as the item - user.msg("After using %s, you are left with %s." % (item, residue)) - item.delete() # Delete the spent item
        - - -
        [docs]def use_item(user, item, target): - """ - Performs the action of using an item. - - Args: - user (obj): Character using the item - item (obj): Item being used - target (obj): Target of the item use - """ - # If item is self only and no target given, set target to self. - if item.db.item_selfonly and target == None: - target = user - - # If item is self only, abort use if used on others. - if item.db.item_selfonly and user != target: - user.msg("%s can only be used on yourself." % item) - return - - # Set kwargs to pass to item_func - kwargs = {} - if item.db.item_kwargs: - kwargs = item.db.item_kwargs - - # Match item_func string to function - try: - item_func = ITEMFUNCS[item.db.item_func] - except KeyError: # If item_func string doesn't match to a function in ITEMFUNCS - user.msg("ERROR: %s not defined in ITEMFUNCS" % item.db.item_func) - return - - # Call the item function - abort if it returns False, indicating an error. - # This performs the actual action of using the item. - # Regardless of what the function returns (if anything), it's still executed. - if item_func(item, user, target, **kwargs) == False: - return - - # If we haven't returned yet, we assume the item was used successfully. - # Spend one use if item has limited uses - if item.db.item_uses: - spend_item_use(item, user) - - # Spend an action if in combat - if is_in_combat(user): - spend_action(user, 1, action_name="item")
        - - -
        [docs]def condition_tickdown(character, turnchar): - """ - Ticks down the duration of conditions on a character at the start of a given character's turn. - - Args: - character (obj): Character to tick down the conditions of - turnchar (obj): Character whose turn it currently is - - Notes: - In combat, this is called on every fighter at the start of every character's turn. Out of - combat, it's instead called when a character's at_update() hook is called, which is every - 30 seconds by default. - """ - - for key in character.db.conditions: - # The first value is the remaining turns - the second value is whose turn to count down on. - condition_duration = character.db.conditions[key][0] - condition_turnchar = character.db.conditions[key][1] - # If the duration is 'True', then the condition doesn't tick down - it lasts indefinitely. - if not condition_duration is True: - # Count down if the given turn character matches the condition's turn character. - if condition_turnchar == turnchar: - character.db.conditions[key][0] -= 1 - if character.db.conditions[key][0] <= 0: - # If the duration is brought down to 0, remove the condition and inform everyone. - character.location.msg_contents( - "%s no longer has the '%s' condition." % (str(character), str(key)) - ) - del character.db.conditions[key]
        - - -
        [docs]def add_condition(character, turnchar, condition, duration): - """ - Adds a condition to a fighter. - - Args: - character (obj): Character to give the condition to - turnchar (obj): Character whose turn to tick down the condition on in combat - condition (str): Name of the condition - duration (int or True): Number of turns the condition lasts, or True for indefinite - """ - # The first value is the remaining turns - the second value is whose turn to count down on. - character.db.conditions.update({condition: [duration, turnchar]}) - # Tell everyone! - character.location.msg_contents("%s gains the '%s' condition." % (character, condition))
        - - -""" ----------------------------------------------------------------------------- -CHARACTER TYPECLASS ----------------------------------------------------------------------------- -""" - - -
        [docs]class TBItemsCharacter(DefaultCharacter): - """ - A character able to participate in turn-based combat. Has attributes for current - and maximum HP, and access to combat commands. - """ - -
        [docs] def at_object_creation(self): +
        [docs] def get_attack(self, attacker, defender): """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - """ - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - self.db.conditions = {} # Set empty dict for conditions - # Subscribe character to the ticker handler - tickerhandler.add(NONCOMBAT_TURN_TIME, self.at_update, idstring="update") - """ - Adds attributes for a character's current and maximum HP. - We're just going to set this value at '100' by default. - - An empty dictionary is created to store conditions later, - and the character is subscribed to the Ticker Handler, which - will call at_update() on the character, with the interval - specified by NONCOMBAT_TURN_TIME above. This is used to tick - down conditions out of combat. - - You may want to expand this to include various 'stats' that - can be changed at creation and factor into combat calculations. - """
        - -
        [docs] def at_pre_move(self, destination): - """ - Called just before starting to move this object to - destination. + Returns a value for an attack roll. Args: - destination (Object): The object we are moving to + attacker (obj): Character doing the attacking + defender (obj): Character being attacked Returns: - shouldmove (bool): If we should move or not. + attack_value (int): Attack roll value, compared against a defense value + to determine whether an attack hits or misses. Notes: - If this method returns False/None, the move is cancelled - before it is even started. - + This is where conditions affecting attack rolls are applied, as well. + Accuracy Up and Accuracy Down are also accounted for in itemfunc_attack(), + so that attack items' accuracy is affected as well. """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
        + # For this example, just return a random integer up to 100. + attack_value = randint(1, 100) + # Add to the roll if the attacker has the "Accuracy Up" condition. + if "Accuracy Up" in attacker.db.conditions: + attack_value += ACC_UP_MOD + # Subtract from the roll if the attack has the "Accuracy Down" condition. + if "Accuracy Down" in attacker.db.conditions: + attack_value += ACC_DOWN_MOD + return attack_value
        -
        [docs] def at_turn_start(self): +
        [docs] def get_defense(self, attacker, defender): """ - Hook called at the beginning of this character's turn in combat. - """ - # Prompt the character for their turn and give some information. - self.msg("|wIt's your turn! You have %i HP remaining.|n" % self.db.hp) - - # Apply conditions that fire at the start of each turn. - self.apply_turn_conditions()
        - -
        [docs] def apply_turn_conditions(self): - """ - Applies the effect of conditions that occur at the start of each - turn in combat, or every 30 seconds out of combat. - """ - # Regeneration: restores 4 to 8 HP at the start of character's turn - if "Regeneration" in self.db.conditions: - to_heal = randint(REGEN_RATE[0], REGEN_RAGE[1]) # Restore HP - if self.db.hp + to_heal > self.db.max_hp: - to_heal = self.db.max_hp - self.db.hp # Cap healing to max HP - self.db.hp += to_heal - self.location.msg_contents("%s regains %i HP from Regeneration." % (self, to_heal)) - - # Poisoned: does 4 to 8 damage at the start of character's turn - if "Poisoned" in self.db.conditions: - to_hurt = randint(POISON_RATE[0], POISON_RATE[1]) # Deal damage - apply_damage(self, to_hurt) - self.location.msg_contents("%s takes %i damage from being Poisoned." % (self, to_hurt)) - if self.db.hp <= 0: - # Call at_defeat if poison defeats the character - at_defeat(self) - - # Haste: Gain an extra action in combat. - if is_in_combat(self) and "Haste" in self.db.conditions: - self.db.combat_actionsleft += 1 - self.msg("You gain an extra action this turn from Haste!") - - # Paralyzed: Have no actions in combat. - if is_in_combat(self) and "Paralyzed" in self.db.conditions: - self.db.combat_actionsleft = 0 - self.location.msg_contents("%s is Paralyzed, and can't act this turn!" % self) - self.db.combat_turnhandler.turn_end_check(self)
        - -
        [docs] def at_update(self): - """ - Fires every 30 seconds. - """ - if not is_in_combat(self): # Not in combat - # Change all conditions to update on character's turn. - for key in self.db.conditions: - self.db.conditions[key][1] = self - # Apply conditions that fire every turn - self.apply_turn_conditions() - # Tick down condition durations - condition_tickdown(self, self)
        - - -
        [docs]class TBItemsCharacterTest(TBItemsCharacter): - """ - Just like the TBItemsCharacter, but doesn't subscribe to the TickerHandler. - This makes it easier to run unit tests on. - """ - -
        [docs] def at_object_creation(self): - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - self.db.conditions = {} # Set empty dict for conditions
        - - -""" ----------------------------------------------------------------------------- -SCRIPTS START HERE ----------------------------------------------------------------------------- -""" - - -
        [docs]class TBItemsTurnHandler(DefaultScript): - """ - This is the script that handles the progression of combat through turns. - On creation (when a fight is started) it adds all combat-ready characters - to its roster and then sorts them into a turn order. There can only be one - fight going on in a single room at a time, so the script is assigned to a - room as its object. - - Fights persist until only one participant is left with any HP or all - remaining participants choose to end the combat with the 'disengage' command. - """ - -
        [docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
        - -
        [docs] def at_stop(self): - """ - Called at script termination. - """ - for fighter in self.db.fighters: - combat_cleanup(fighter) # Clean up the combat attributes for every fighter. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
        - -
        [docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
        - -
        [docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. + Returns a value for defense, which an attack roll must equal or exceed in order + for an attack to hit. Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
        + attacker (obj): Character doing the attacking + defender (obj): Character being attacked -
        [docs] def start_turn(self, character): - """ - Readies a character for the start of their turn by replenishing their - available actions and notifying them that their turn has come up. - - Args: - character (obj): Character to be readied. + Returns: + defense_value (int): Defense value, compared against an attack roll + to determine whether an attack hits or misses. Notes: - Here, you only get one action per turn, but you might want to allow more than - one per turn, or even grant a number of actions based on a character's - attributes. You can even add multiple different kinds of actions, I.E. actions - separated for movement, by adding "character.db.combat_movesleft = 3" or - something similar. + This is where conditions affecting defense are accounted for. """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Call character's at_turn_start() hook. - character.at_turn_start()
        + # For this example, just return 50, for about a 50/50 chance of hit. + defense_value = 50 + # Add to defense if the defender has the "Defense Up" condition. + if "Defense Up" in defender.db.conditions: + defense_value += DEF_UP_MOD + # Subtract from defense if the defender has the "Defense Down" condition. + if "Defense Down" in defender.db.conditions: + defense_value += DEF_DOWN_MOD + return defense_value
        -
        [docs] def next_turn(self): +
        [docs] def get_damage(self, attacker, defender): """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - - newchar = self.db.fighters[self.db.turn] # Note the new character - - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn. - - # Count down condition timers. - for fighter in self.db.fighters: - condition_tickdown(fighter, newchar)
        - -
        [docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. + Returns a value for damage to be deducted from the defender's HP after abilities + successful hit. Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
        + attacker (obj): Character doing the attacking + defender (obj): Character being damaged -
        [docs] def join_fight(self, character): + Returns: + damage_value (int): Damage value, which is to be deducted from the defending + character's HP. + + Notes: + This is where conditions affecting damage are accounted for. Since attack items + roll their own damage in itemfunc_attack(), their damage is unaffected by any + conditions. + """ + # For this example, just generate a number between 15 and 25. + damage_value = randint(15, 25) + # Add to damage roll if attacker has the "Damage Up" condition. + if "Damage Up" in attacker.db.conditions: + damage_value += DMG_UP_MOD + # Subtract from the roll if the attacker has the "Damage Down" condition. + if "Damage Down" in attacker.db.conditions: + damage_value += DMG_DOWN_MOD + return damage_value
        + +
        [docs] def resolve_attack( + self, + attacker, + defender, + attack_value=None, + defense_value=None, + damage_value=None, + inflict_condition=[], + ): """ - Adds a new character to a fight already in progress. + Resolves an attack and outputs the result. Args: - character (obj): Character to be added to the fight. + attacker (obj): Character doing the attacking + defender (obj): Character being attacked + + Options: + attack_value (int): Override for attack roll + defense_value (int): Override for defense value + damage_value (int): Override for damage value + inflict_condition (list): Conditions to inflict upon hit, a + list of tuples formated as (condition(str), duration(int)) + + Notes: + This function is called by normal attacks as well as attacks + made with items. """ - # Inserts the fighter to the turn order, right behind whoever's turn it currently is. - self.db.fighters.insert(self.db.turn, character) - # Tick the turn counter forward one to compensate. - self.db.turn += 1 - # Initialize the character like you do at the start. - self.initialize_for_combat(character)
        - - -""" ----------------------------------------------------------------------------- -COMMANDS START HERE ----------------------------------------------------------------------------- -""" - - -
        [docs]class CmdFight(Command): - """ - Starts a fight with everyone in the same room as you. - - Usage: - fight - - When you start a fight, everyone in the room who is able to - fight is added to combat, and a turn order is randomly rolled. - When it's your turn, you can attack other characters. - """ - - key = "fight" - help_category = "combat" - -
        [docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.game_systems.turnbattle.tb_items.TBItemsTurnHandler")
        - # Remember you'll have to change the path to the script if you copy this code to your own modules! - - -
        [docs]class CmdAttack(Command): - """ - Attacks another character. - - Usage: - attack <target> - - When in a fight, you may attack another character. The attack has - a chance to hit, and if successful, will deal damage. - """ - - key = "attack" - help_category = "combat" - -
        [docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - if "Frightened" in self.caller.db.conditions: # Can't attack if frightened - self.caller.msg("You're too frightened to attack!") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender) - spend_action(self.caller, 1, action_name="attack") # Use up one action.
        - - -
        [docs]class CmdPass(Command): - """ - Passes on your turn. - - Usage: - pass - - When in a fight, you can use this command to end your turn early, even - if there are still any actions you can take. - """ - - key = "pass" - aliases = ["wait", "hold"] - help_category = "combat" - -
        [docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
        - - -
        [docs]class CmdDisengage(Command): - """ - Passes your turn and attempts to end combat. - - Usage: - disengage - - Ends your turn early and signals that you're trying to end - the fight. If all participants in a fight disengage, the - fight ends. - """ - - key = "disengage" - aliases = ["spare"] - help_category = "combat" - -
        [docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
        - - -
        [docs]class CmdRest(Command): - """ - Recovers damage. - - Usage: - rest - - Resting recovers your HP to its maximum, but you can only - rest if you're not in a fight. - """ - - key = "rest" - help_category = "combat" - -
        [docs] def func(self): - "This performs the actual command." - - if is_in_combat(self.caller): # If you're in combat - self.caller.msg("You can't rest while you're in combat.") - return - - self.caller.db.hp = self.caller.db.max_hp # Set current HP to maximum - self.caller.location.msg_contents("%s rests to recover HP." % self.caller) - """ - You'll probably want to replace this with your own system for recovering HP. - """
        - - -
        [docs]class CmdCombatHelp(CmdHelp): - """ - View help or a list of topics - - Usage: - help <topic or command> - help list - help all - - This will search for help on commands and other - topics related to the game. - """ - - # Just like the default help command, but will give quick - # tips on combat when used in a fight with no arguments. - -
        [docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack a target, attempting to deal damage.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - + "|wUse:|n Use an item you're carrying." - ) + # Get an attack roll from the attacker. + if not attack_value: + attack_value = self.get_attack(attacker, defender) + # Get a defense value from the defender. + if not defense_value: + defense_value = self.get_defense(attacker, defender) + # If the attack value is lower than the defense value, miss. Otherwise, hit. + if attack_value < defense_value: + attacker.location.msg_contents("%s's attack misses %s!" % (attacker, defender)) else: - super().func() # Call the default help command
        + if not damage_value: + damage_value = self.get_damage(attacker, defender) # Calculate damage value. + # Announce damage dealt and apply damage. + attacker.location.msg_contents( + "%s hits %s for %i damage!" % (attacker, defender, damage_value) + ) + self.apply_damage(defender, damage_value) + # Inflict conditions on hit, if any specified + for condition in inflict_condition: + self.add_condition(defender, attacker, condition[0], condition[1]) + # If defender HP is reduced to 0 or less, call at_defeat. + if defender.db.hp <= 0: + self.at_defeat(defender)
        - -
        [docs]class CmdUse(MuxCommand): - """ - Use an item. - - Usage: - use <item> [= target] - - An item can have various function - looking at the item may - provide information as to its effects. Some items can be used - to attack others, and as such can only be used in combat. - """ - - key = "use" - help_category = "combat" - -
        [docs] def func(self): +
        [docs] def spend_item_use(self, item, user): """ - This performs the actual command. + Spends one use on an item with limited uses. + + Args: + item (obj): Item being used + user (obj): Character using the item + + Notes: + If item.db.item_consumable is 'True', the item is destroyed if it + runs out of uses - if it's a string instead of 'True', it will also + spawn a new object as residue, using the value of item.db.item_consumable + as the name of the prototype to spawn. """ - # Search for item - item = self.caller.search(self.lhs, candidates=self.caller.contents) - if not item: + item.db.item_uses -= 1 # Spend one use + + if item.db.item_uses > 0: # Has uses remaining + # Inform the player + user.msg("%s has %i uses remaining." % (item.key.capitalize(), item.db.item_uses)) + + else: # All uses spent + + if not item.db.item_consumable: # Item isn't consumable + # Just inform the player that the uses are gone + user.msg("%s has no uses remaining." % item.key.capitalize()) + + else: # If item is consumable + # If the value is 'True', just destroy the item + if item.db.item_consumable: + user.msg("%s has been consumed." % item.key.capitalize()) + item.delete() # Delete the spent item + + else: # If a string, use value of item_consumable to spawn an object in its place + residue = spawn({"prototype": item.db.item_consumable})[0] # Spawn the residue + # Move the residue to the same place as the item + residue.location = item.location + user.msg("After using %s, you are left with %s." % (item, residue)) + item.delete() # Delete the spent item
        + +
        [docs] def use_item(self, user, item, target): + """ + Performs the action of using an item. + + Args: + user (obj): Character using the item + item (obj): Item being used + target (obj): Target of the item use + """ + # If item is self only and no target given, set target to self. + if item.db.item_selfonly and target is None: + target = user + + # If item is self only, abort use if used on others. + if item.db.item_selfonly and user != target: + user.msg("%s can only be used on yourself." % item) return - # Search for target, if any is given - target = None - if self.rhs: - target = self.caller.search(self.rhs) - if not target: - return + # Set kwargs to pass to item_func + kwargs = {} + if item.db.item_kwargs: + kwargs = item.db.item_kwargs - # If in combat, can only use items on your turn - if is_in_combat(self.caller): - if not is_turn(self.caller): - self.caller.msg("You can only use items on your turn.") - return - - if not item.db.item_func: # Object has no item_func, not usable - self.caller.msg("'%s' is not a usable item." % item.key.capitalize()) + # Match item_func string to function + try: + item_func = ITEMFUNCS[item.db.item_func] + except KeyError: # If item_func string doesn't match to a function in ITEMFUNCS + user.msg("ERROR: %s not defined in ITEMFUNCS" % item.db.item_func) return - if item.attributes.has("item_uses"): # Item has limited uses - if item.db.item_uses <= 0: # Limited uses are spent - self.caller.msg("'%s' has no uses remaining." % item.key.capitalize()) - return + # Call the item function - abort if it returns False, indicating an error. + # This performs the actual action of using the item. + # Regardless of what the function returns (if anything), it's still executed. + if not item_func(item, user, target, **kwargs): + return - # If everything checks out, call the use_item function - use_item(self.caller, item, target)
        + # If we haven't returned yet, we assume the item was used successfully. + # Spend one use if item has limited uses + if item.db.item_uses: + self.spend_item_use(item, user) + # Spend an action if in combat + if self.is_in_combat(user): + self.spend_action(user, 1, action_name="item")
        -
        [docs]class BattleCmdSet(default_cmds.CharacterCmdSet): - """ - This command set includes all the commmands used in the battle system. - """ - - key = "DefaultCharacter" - -
        [docs] def at_cmdset_creation(self): +
        [docs] def condition_tickdown(self, character, turnchar): """ - Populates the cmdset + Ticks down the duration of conditions on a character at the start of a given character's + turn. + + Args: + character (obj): Character to tick down the conditions of + turnchar (obj): Character whose turn it currently is + + Notes: + In combat, this is called on every fighter at the start of every character's turn. Out + of combat, it's instead called when a character's at_update() hook is called, which is + every 30 seconds by default. """ - self.add(CmdFight()) - self.add(CmdAttack()) - self.add(CmdRest()) - self.add(CmdPass()) - self.add(CmdDisengage()) - self.add(CmdCombatHelp()) - self.add(CmdUse())
        + + for key in character.db.conditions: + # The first value is the remaining turns - the second value is whose turn to count down + # on. + condition_duration = character.db.conditions[key][0] + condition_turnchar = character.db.conditions[key][1] + # If the duration is 'True', then the condition doesn't tick down - it lasts + # indefinitely. + if condition_duration is not True: + # Count down if the given turn character matches the condition's turn character. + if condition_turnchar == turnchar: + character.db.conditions[key][0] -= 1 + if character.db.conditions[key][0] <= 0: + # If the duration is brought down to 0, remove the condition and inform + # everyone. + character.location.msg_contents( + "%s no longer has the '%s' condition." % (str(character), str(key)) + ) + del character.db.conditions[key]
        + +
        [docs] def add_condition(self, character, turnchar, condition, duration): + """ + Adds a condition to a fighter. + + Args: + character (obj): Character to give the condition to + turnchar (obj): Character whose turn to tick down the condition on in combat + condition (str): Name of the condition + duration (int or True): Number of turns the condition lasts, or True for indefinite + """ + # The first value is the remaining turns - the second value is whose turn to count down on. + character.db.conditions.update({condition: [duration, turnchar]}) + # Tell everyone! + character.location.msg_contents("%s gains the '%s' condition." % (character, condition))
        + + # ---------------------------------------------------------------------------- + # ITEM FUNCTIONS START HERE + # ---------------------------------------------------------------------------- + + # These functions carry out the action of using an item - every item should + # contain a db entry "item_func", with its value being a string that is + # matched to one of these functions in the ITEMFUNCS dictionary below. + + # Every item function must take the following arguments: + # item (obj): The item being used + # user (obj): The character using the item + # target (obj): The target of the item use + + # Item functions must also accept **kwargs - these keyword arguments can be + # used to define how different items that use the same function can have + # different effects (for example, different attack items doing different + # amounts of damage). + + # Each function below contains a description of what kwargs the function will + # take and the effect they have on the result. + +
        [docs] def itemfunc_heal(self, item, user, target, **kwargs): + """ + Item function that heals HP. + + kwargs: + min_healing(int): Minimum amount of HP recovered + max_healing(int): Maximum amount of HP recovered + """ + if not target: + target = user # Target user if none specified + + if not target.attributes.has("max_hp"): # Has no HP to speak of + user.msg("You can't use %s on that." % item) + return False # Returning false aborts the item use + + if target.db.hp >= target.db.max_hp: + user.msg("%s is already at full health." % target) + return False + + min_healing = 20 + max_healing = 40 + + # Retrieve healing range from kwargs, if present + if "healing_range" in kwargs: + min_healing = kwargs["healing_range"][0] + max_healing = kwargs["healing_range"][1] + + to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp + if target.db.hp + to_heal > target.db.max_hp: + to_heal = target.db.max_hp - target.db.hp # Cap healing to max HP + target.db.hp += to_heal + + user.location.msg_contents("%s uses %s! %s regains %i HP!" % (user, item, target, to_heal))
        + +
        [docs] def itemfunc_add_condition(self, item, user, target, **kwargs): + """ + Item function that gives the target one or more conditions. + + kwargs: + conditions (list): Conditions added by the item + formatted as a list of tuples: (condition (str), duration (int or True)) + + Notes: + Should mostly be used for beneficial conditions - use itemfunc_attack + for an item that can give an enemy a harmful condition. + """ + conditions = [("Regeneration", 5)] + + if not target: + target = user # Target user if none specified + + if not target.attributes.has("max_hp"): # Is not a fighter + user.msg("You can't use %s on that." % item) + return False # Returning false aborts the item use + + # Retrieve condition / duration from kwargs, if present + if "conditions" in kwargs: + conditions = kwargs["conditions"] + + user.location.msg_contents("%s uses %s!" % (user, item)) + + # Add conditions to the target + for condition in conditions: + self.add_condition(target, user, condition[0], condition[1])
        + +
        [docs] def itemfunc_cure_condition(self, item, user, target, **kwargs): + """ + Item function that'll remove given conditions from a target. + + kwargs: + to_cure(list): List of conditions (str) that the item cures when used + """ + to_cure = ["Poisoned"] + + if not target: + target = user # Target user if none specified + + if not target.attributes.has("max_hp"): # Is not a fighter + user.msg("You can't use %s on that." % item) + return False # Returning false aborts the item use + + # Retrieve condition(s) to cure from kwargs, if present + if "to_cure" in kwargs: + to_cure = kwargs["to_cure"] + + item_msg = "%s uses %s! " % (user, item) + + for key in target.db.conditions: + if key in to_cure: + # If condition specified in to_cure, remove it. + item_msg += "%s no longer has the '%s' condition. " % (str(target), str(key)) + del target.db.conditions[key] + + user.location.msg_contents(item_msg)
        + +
        [docs] def itemfunc_attack(self, item, user, target, **kwargs): + """ + Item function that attacks a target. + + kwargs: + min_damage(int): Minimum damage dealt by the attack + max_damage(int): Maximum damage dealth by the attack + accuracy(int): Bonus / penalty to attack accuracy roll + inflict_condition(list): List of conditions inflicted on hit, + formatted as a (str, int) tuple containing condition name + and duration. + + Notes: + Calls resolve_attack at the end. + """ + if not self.is_in_combat(user): + user.msg("You can only use that in combat.") + return False # Returning false aborts the item use + + if not target: + user.msg("You have to specify a target to use %s! (use <item> = <target>)" % item) + return False + + if target == user: + user.msg("You can't attack yourself!") + return False + + if not target.db.hp: # Has no HP + user.msg("You can't use %s on that." % item) + return False + + min_damage = 20 + max_damage = 40 + accuracy = 0 + inflict_condition = [] + + # Retrieve values from kwargs, if present + if "damage_range" in kwargs: + min_damage = kwargs["damage_range"][0] + max_damage = kwargs["damage_range"][1] + if "accuracy" in kwargs: + accuracy = kwargs["accuracy"] + if "inflict_condition" in kwargs: + inflict_condition = kwargs["inflict_condition"] + + # Roll attack and damage + attack_value = randint(1, 100) + accuracy + damage_value = randint(min_damage, max_damage) + + # Account for "Accuracy Up" and "Accuracy Down" conditions + if "Accuracy Up" in user.db.conditions: + attack_value += 25 + if "Accuracy Down" in user.db.conditions: + attack_value -= 25 + + user.location.msg_contents("%s attacks %s with %s!" % (user, target, item)) + self.resolve_attack( + user, + target, + attack_value=attack_value, + damage_value=damage_value, + inflict_condition=inflict_condition, + )
        -""" ----------------------------------------------------------------------------- -ITEM FUNCTIONS START HERE ----------------------------------------------------------------------------- - -These functions carry out the action of using an item - every item should -contain a db entry "item_func", with its value being a string that is -matched to one of these functions in the ITEMFUNCS dictionary below. - -Every item function must take the following arguments: - item (obj): The item being used - user (obj): The character using the item - target (obj): The target of the item use - -Item functions must also accept **kwargs - these keyword arguments can be -used to define how different items that use the same function can have -different effects (for example, different attack items doing different -amounts of damage). - -Each function below contains a description of what kwargs the function will -take and the effect they have on the result. -""" - - -
        [docs]def itemfunc_heal(item, user, target, **kwargs): - """ - Item function that heals HP. - - kwargs: - min_healing(int): Minimum amount of HP recovered - max_healing(int): Maximum amount of HP recovered - """ - if not target: - target = user # Target user if none specified - - if not target.attributes.has("max_hp"): # Has no HP to speak of - user.msg("You can't use %s on that." % item) - return False # Returning false aborts the item use - - if target.db.hp >= target.db.max_hp: - user.msg("%s is already at full health." % target) - return False - - min_healing = 20 - max_healing = 40 - - # Retrieve healing range from kwargs, if present - if "healing_range" in kwargs: - min_healing = kwargs["healing_range"][0] - max_healing = kwargs["healing_range"][1] - - to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp - if target.db.hp + to_heal > target.db.max_hp: - to_heal = target.db.max_hp - target.db.hp # Cap healing to max HP - target.db.hp += to_heal - - user.location.msg_contents("%s uses %s! %s regains %i HP!" % (user, item, target, to_heal))
        - - -
        [docs]def itemfunc_add_condition(item, user, target, **kwargs): - """ - Item function that gives the target one or more conditions. - - kwargs: - conditions (list): Conditions added by the item - formatted as a list of tuples: (condition (str), duration (int or True)) - - Notes: - Should mostly be used for beneficial conditions - use itemfunc_attack - for an item that can give an enemy a harmful condition. - """ - conditions = [("Regeneration", 5)] - - if not target: - target = user # Target user if none specified - - if not target.attributes.has("max_hp"): # Is not a fighter - user.msg("You can't use %s on that." % item) - return False # Returning false aborts the item use - - # Retrieve condition / duration from kwargs, if present - if "conditions" in kwargs: - conditions = kwargs["conditions"] - - user.location.msg_contents("%s uses %s!" % (user, item)) - - # Add conditions to the target - for condition in conditions: - add_condition(target, user, condition[0], condition[1])
        - - -
        [docs]def itemfunc_cure_condition(item, user, target, **kwargs): - """ - Item function that'll remove given conditions from a target. - - kwargs: - to_cure(list): List of conditions (str) that the item cures when used - """ - to_cure = ["Poisoned"] - - if not target: - target = user # Target user if none specified - - if not target.attributes.has("max_hp"): # Is not a fighter - user.msg("You can't use %s on that." % item) - return False # Returning false aborts the item use - - # Retrieve condition(s) to cure from kwargs, if present - if "to_cure" in kwargs: - to_cure = kwargs["to_cure"] - - item_msg = "%s uses %s! " % (user, item) - - for key in target.db.conditions: - if key in to_cure: - # If condition specified in to_cure, remove it. - item_msg += "%s no longer has the '%s' condition. " % (str(target), str(key)) - del target.db.conditions[key] - - user.location.msg_contents(item_msg)
        - - -
        [docs]def itemfunc_attack(item, user, target, **kwargs): - """ - Item function that attacks a target. - - kwargs: - min_damage(int): Minimum damage dealt by the attack - max_damage(int): Maximum damage dealth by the attack - accuracy(int): Bonus / penalty to attack accuracy roll - inflict_condition(list): List of conditions inflicted on hit, - formatted as a (str, int) tuple containing condition name - and duration. - - Notes: - Calls resolve_attack at the end. - """ - if not is_in_combat(user): - user.msg("You can only use that in combat.") - return False # Returning false aborts the item use - - if not target: - user.msg("You have to specify a target to use %s! (use <item> = <target>)" % item) - return False - - if target == user: - user.msg("You can't attack yourself!") - return False - - if not target.db.hp: # Has no HP - user.msg("You can't use %s on that." % item) - return False - - min_damage = 20 - max_damage = 40 - accuracy = 0 - inflict_condition = [] - - # Retrieve values from kwargs, if present - if "damage_range" in kwargs: - min_damage = kwargs["damage_range"][0] - max_damage = kwargs["damage_range"][1] - if "accuracy" in kwargs: - accuracy = kwargs["accuracy"] - if "inflict_condition" in kwargs: - inflict_condition = kwargs["inflict_condition"] - - # Roll attack and damage - attack_value = randint(1, 100) + accuracy - damage_value = randint(min_damage, max_damage) - - # Account for "Accuracy Up" and "Accuracy Down" conditions - if "Accuracy Up" in user.db.conditions: - attack_value += 25 - if "Accuracy Down" in user.db.conditions: - attack_value -= 25 - - user.location.msg_contents("%s attacks %s with %s!" % (user, target, item)) - resolve_attack( - user, - target, - attack_value=attack_value, - damage_value=damage_value, - inflict_condition=inflict_condition, - )
        +COMBAT_RULES = ItemCombatRules() # Match strings to item functions here. We can't store callables on # prototypes, so we store a string instead, matching that string to # a callable in this dictionary. ITEMFUNCS = { - "heal": itemfunc_heal, - "attack": itemfunc_attack, - "add_condition": itemfunc_add_condition, - "cure_condition": itemfunc_cure_condition, + "heal": COMBAT_RULES.itemfunc_heal, + "attack": COMBAT_RULES.itemfunc_attack, + "add_condition": COMBAT_RULES.itemfunc_add_condition, + "cure_condition": COMBAT_RULES.itemfunc_cure_condition, } """ @@ -1494,11 +742,343 @@ AMULET_OF_WEAKNESS = { "key": "The Amulet of Weakness", - "desc": "The one who holds this amulet can call upon its power to gain great weakness. It's not a terribly useful artifact.", + "desc": "The one who holds this amulet can call upon its power to gain great weakness. " + "It's not a terribly useful artifact.", "item_func": "add_condition", "item_selfonly": True, "item_kwargs": {"conditions": [("Damage Down", 3), ("Accuracy Down", 3), ("Defense Down", 3)]}, } + + +""" +---------------------------------------------------------------------------- +CHARACTER TYPECLASS +---------------------------------------------------------------------------- +""" + + +
        [docs]class TBItemsCharacter(tb_basic.TBBasicCharacter): + """ + A character able to participate in turn-based combat. Has attributes for current + and maximum HP, and access to combat commands. + """ + + rules = ItemCombatRules() + +
        [docs] def at_object_creation(self): + """ + Called once, when this object is first created. This is the + normal hook to overload for most object types. + """ + self.db.max_hp = 100 # Set maximum HP to 100 + self.db.hp = self.db.max_hp # Set current HP to maximum + self.db.conditions = {} # Set empty dict for conditions + # Subscribe character to the ticker handler + tickerhandler.add(NONCOMBAT_TURN_TIME, self.at_update, idstring="update") + """ + Adds attributes for a character's current and maximum HP. + We're just going to set this value at '100' by default. + + An empty dictionary is created to store conditions later, + and the character is subscribed to the Ticker Handler, which + will call at_update() on the character, with the interval + specified by NONCOMBAT_TURN_TIME above. This is used to tick + down conditions out of combat. + + You may want to expand this to include various 'stats' that + can be changed at creation and factor into combat calculations. + """
        + +
        [docs] def at_turn_start(self): + """ + Hook called at the beginning of this character's turn in combat. + """ + # Prompt the character for their turn and give some information. + self.msg("|wIt's your turn! You have %i HP remaining.|n" % self.db.hp) + + # Apply conditions that fire at the start of each turn. + self.apply_turn_conditions()
        + +
        [docs] def apply_turn_conditions(self): + """ + Applies the effect of conditions that occur at the start of each + turn in combat, or every 30 seconds out of combat. + """ + # Regeneration: restores 4 to 8 HP at the start of character's turn + if "Regeneration" in self.db.conditions: + to_heal = randint(REGEN_RATE[0], REGEN_RATE[1]) # Restore HP + if self.db.hp + to_heal > self.db.max_hp: + to_heal = self.db.max_hp - self.db.hp # Cap healing to max HP + self.db.hp += to_heal + self.location.msg_contents("%s regains %i HP from Regeneration." % (self, to_heal)) + + # Poisoned: does 4 to 8 damage at the start of character's turn + if "Poisoned" in self.db.conditions: + to_hurt = randint(POISON_RATE[0], POISON_RATE[1]) # Deal damage + self.rules.apply_damage(self, to_hurt) + self.location.msg_contents("%s takes %i damage from being Poisoned." % (self, to_hurt)) + if self.db.hp <= 0: + # Call at_defeat if poison defeats the character + self.rules.at_defeat(self) + + # Haste: Gain an extra action in combat. + if self.rules.is_in_combat(self) and "Haste" in self.db.conditions: + self.db.combat_actionsleft += 1 + self.msg("You gain an extra action this turn from Haste!") + + # Paralyzed: Have no actions in combat. + if self.rules.is_in_combat(self) and "Paralyzed" in self.db.conditions: + self.db.combat_actionsleft = 0 + self.location.msg_contents("%s is Paralyzed, and can't act this turn!" % self) + self.db.combat_turnhandler.turn_end_check(self)
        + +
        [docs] def at_update(self): + """ + Fires every 30 seconds. + """ + if not self.rules.is_in_combat(self): # Not in combat + # Change all conditions to update on character's turn. + for key in self.db.conditions: + self.db.conditions[key][1] = self + # Apply conditions that fire every turn + self.apply_turn_conditions() + # Tick down condition durations + self.rules.condition_tickdown(self, self)
        + + +
        [docs]class TBItemsCharacterTest(TBItemsCharacter): + """ + Just like the TBItemsCharacter, but doesn't subscribe to the TickerHandler. + This makes it easier to run unit tests on. + """ + +
        [docs] def at_object_creation(self): + self.db.max_hp = 100 # Set maximum HP to 100 + self.db.hp = self.db.max_hp # Set current HP to maximum + self.db.conditions = {} # Set empty dict for conditions
        + + +""" +---------------------------------------------------------------------------- +SCRIPTS START HERE +---------------------------------------------------------------------------- +""" + + +
        [docs]class TBItemsTurnHandler(tb_basic.TBBasicTurnHandler): + """ + This is the script that handles the progression of combat through turns. + On creation (when a fight is started) it adds all combat-ready characters + to its roster and then sorts them into a turn order. There can only be one + fight going on in a single room at a time, so the script is assigned to a + room as its object. + + Fights persist until only one participant is left with any HP or all + remaining participants choose to end the combat with the 'disengage' command. + """ + + rules = COMBAT_RULES + +
        [docs] def next_turn(self): + """ + Advances to the next character in the turn order. + """ + super().next_turn() + + # Count down condition timers. + next_fighter = self.db.fighters[self.db.turn] + for fighter in self.db.fighters: + self.rules.condition_tickdown(fighter, next_fighter)
        + + +""" +---------------------------------------------------------------------------- +COMMANDS START HERE +---------------------------------------------------------------------------- +""" + + +
        [docs]class CmdFight(tb_basic.CmdFight): + """ + Starts a fight with everyone in the same room as you. + + Usage: + fight + + When you start a fight, everyone in the room who is able to + fight is added to combat, and a turn order is randomly rolled. + When it's your turn, you can attack other characters. + """ + + key = "fight" + help_category = "combat" + + rules = COMBAT_RULES + combat_handler_class = TBItemsTurnHandler
        + + +
        [docs]class CmdAttack(tb_basic.CmdAttack): + """ + Attacks another character. + + Usage: + attack <target> + + When in a fight, you may attack another character. The attack has + a chance to hit, and if successful, will deal damage. + """ + + key = "attack" + help_category = "combat" + rules = COMBAT_RULES
        + + +
        [docs]class CmdPass(tb_basic.CmdPass): + """ + Passes on your turn. + + Usage: + pass + + When in a fight, you can use this command to end your turn early, even + if there are still any actions you can take. + """ + + key = "pass" + aliases = ["wait", "hold"] + help_category = "combat" + rules = COMBAT_RULES
        + + +
        [docs]class CmdDisengage(tb_basic.CmdDisengage): + """ + Passes your turn and attempts to end combat. + + Usage: + disengage + + Ends your turn early and signals that you're trying to end + the fight. If all participants in a fight disengage, the + fight ends. + """ + + key = "disengage" + aliases = ["spare"] + help_category = "combat" + + rules = COMBAT_RULES
        + + +
        [docs]class CmdRest(tb_basic.CmdRest): + """ + Recovers damage. + + Usage: + rest + + Resting recovers your HP to its maximum, but you can only + rest if you're not in a fight. + """ + + key = "rest" + help_category = "combat" + + rules = COMBAT_RULES
        + + +
        [docs]class CmdCombatHelp(tb_basic.CmdCombatHelp): + """ + View help or a list of topics + + Usage: + help <topic or command> + help list + help all + + This will search for help on commands and other + topics related to the game. + """ + + rules = COMBAT_RULES + combat_help_text = ( + "Available combat commands:|/" + "|wAttack:|n Attack a target, attempting to deal damage.|/" + "|wPass:|n Pass your turn without further action.|/" + "|wDisengage:|n End your turn and attempt to end combat.|/" + "|wUse:|n Use an item you're carrying." + )
        + + +
        [docs]class CmdUse(MuxCommand): + """ + Use an item. + + Usage: + use <item> [= target] + + An item can have various function - looking at the item may + provide information as to its effects. Some items can be used + to attack others, and as such can only be used in combat. + """ + + key = "use" + help_category = "combat" + + rules = COMBAT_RULES + +
        [docs] def func(self): + """ + This performs the actual command. + """ + # Search for item + item = self.caller.search(self.lhs, candidates=self.caller.contents) + if not item: + return + + # Search for target, if any is given + target = None + if self.rhs: + target = self.caller.search(self.rhs) + if not target: + return + + # If in combat, can only use items on your turn + if self.rules.is_in_combat(self.caller): + if not self.rules.is_turn(self.caller): + self.caller.msg("You can only use items on your turn.") + return + + if not item.db.item_func: # Object has no item_func, not usable + self.caller.msg("'%s' is not a usable item." % item.key.capitalize()) + return + + if item.attributes.has("item_uses"): # Item has limited uses + if item.db.item_uses <= 0: # Limited uses are spent + self.caller.msg("'%s' has no uses remaining." % item.key.capitalize()) + return + + # If everything checks out, call the use_item function + self.rules.use_item(self.caller, item, target)
        + + +
        [docs]class BattleCmdSet(default_cmds.CharacterCmdSet): + """ + This command set includes all the commmands used in the battle system. + """ + + key = "DefaultCharacter" + +
        [docs] def at_cmdset_creation(self): + """ + Populates the cmdset + """ + self.add(CmdFight()) + self.add(CmdAttack()) + self.add(CmdRest()) + self.add(CmdPass()) + self.add(CmdDisengage()) + self.add(CmdCombatHelp()) + self.add(CmdUse())
        @@ -1533,7 +1113,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_magic.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_magic.html index 69dc4354d4..fb8dd156d7 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_magic.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_magic.html @@ -45,7 +45,7 @@ """ Simple turn-based combat system with spell casting -Contrib - Tim Ashley Jenkins 2017 +Contrib - Tim Ashley Jenkins 2017, Refactor by Griatch, 2022 This is a version of the 'turnbattle' contrib that includes a basic, expandable framework for a 'magic system', whereby players can spend @@ -111,9 +111,291 @@ """ from random import randint -from evennia import DefaultCharacter, Command, default_cmds, DefaultScript, create_object +from evennia.utils.logger import log_trace +from evennia import Command, default_cmds, DefaultScript, create_object from evennia.commands.default.muxcommand import MuxCommand -from evennia.commands.default.help import CmdHelp + +from . import tb_basic + +""" +---------------------------------------------------------------------------- +SPELL FUNCTIONS START HERE +---------------------------------------------------------------------------- + +These are the functions that are called by the 'cast' command to perform the +effects of various spells. Which spells execute which functions and what +parameters are passed to them are specified at the bottom of the module, in +the 'SPELLS' dictionary. + +All of these functions take the same arguments: + caster (obj): Character casting the spell + spell_name (str): Name of the spell being cast + targets (list): List of objects targeted by the spell + cost (int): MP cost of casting the spell + +These functions also all accept **kwargs, and how these are used is specified +in the docstring for each function. +""" + +
        [docs]class MagicCombatRules(tb_basic.BasicCombatRules): + +
        [docs] def spell_healing(self, caster, spell_name, targets, cost, **kwargs): + """ + Spell that restores HP to a target or targets. + + kwargs: + healing_range (tuple): Minimum and maximum amount healed to + each target. (20, 40) by default. + """ + spell_msg = "%s casts %s!" % (caster, spell_name) + + min_healing = 20 + max_healing = 40 + + # Retrieve healing range from kwargs, if present + if "healing_range" in kwargs: + min_healing = kwargs["healing_range"][0] + max_healing = kwargs["healing_range"][1] + + for character in targets: + to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp + if character.db.hp + to_heal > character.db.max_hp: + to_heal = character.db.max_hp - character.db.hp # Cap healing to max HP + character.db.hp += to_heal + spell_msg += " %s regains %i HP!" % (character, to_heal) + + caster.db.mp -= cost # Deduct MP cost + + caster.location.msg_contents(spell_msg) # Message the room with spell results + + if self.is_in_combat(caster): # Spend action if in combat + self.spend_action(caster, 1, action_name="cast")
        + +
        [docs] def spell_attack(self, caster, spell_name, targets, cost, **kwargs): + """ + Spell that deals damage in combat. Similar to resolve_attack. + + kwargs: + attack_name (tuple): Single and plural describing the sort of + attack or projectile that strikes each enemy. + damage_range (tuple): Minimum and maximum damage dealt by the + spell. (10, 20) by default. + accuracy (int): Modifier to the spell's attack roll, determining + an increased or decreased chance to hit. 0 by default. + attack_count (int): How many individual attacks are made as part + of the spell. If the number of attacks exceeds the number of + targets, the first target specified will be attacked more + than once. Just 1 by default - if the attack_count is less + than the number targets given, each target will only be + attacked once. + """ + spell_msg = "%s casts %s!" % (caster, spell_name) + + atkname_single = "The spell" + atkname_plural = "spells" + min_damage = 10 + max_damage = 20 + accuracy = 0 + attack_count = 1 + + # Retrieve some variables from kwargs, if present + if "attack_name" in kwargs: + atkname_single = kwargs["attack_name"][0] + atkname_plural = kwargs["attack_name"][1] + if "damage_range" in kwargs: + min_damage = kwargs["damage_range"][0] + max_damage = kwargs["damage_range"][1] + if "accuracy" in kwargs: + accuracy = kwargs["accuracy"] + if "attack_count" in kwargs: + attack_count = kwargs["attack_count"] + + to_attack = [] + # If there are more attacks than targets given, attack first target multiple times + if len(targets) < attack_count: + to_attack = to_attack + targets + extra_attacks = attack_count - len(targets) + for n in range(extra_attacks): + to_attack.insert(0, targets[0]) + else: + to_attack = to_attack + targets + + # Set up dictionaries to track number of hits and total damage + total_hits = {} + total_damage = {} + for fighter in targets: + total_hits.update({fighter: 0}) + total_damage.update({fighter: 0}) + + # Resolve attack for each target + for fighter in to_attack: + attack_value = randint(1, 100) + accuracy # Spell attack roll + defense_value = self.get_defense(caster, fighter) + if attack_value >= defense_value: + spell_dmg = randint(min_damage, max_damage) # Get spell damage + total_hits[fighter] += 1 + total_damage[fighter] += spell_dmg + + for fighter in targets: + # Construct combat message + if total_hits[fighter] == 0: + spell_msg += " The spell misses %s!" % fighter + elif total_hits[fighter] > 0: + attack_count_str = atkname_single + " hits" + if total_hits[fighter] > 1: + attack_count_str = "%i %s hit" % (total_hits[fighter], atkname_plural) + spell_msg += " %s %s for %i damage!" % ( + attack_count_str, + fighter, + total_damage[fighter], + ) + + caster.db.mp -= cost # Deduct MP cost + + caster.location.msg_contents(spell_msg) # Message the room with spell results + + for fighter in targets: + # Apply damage + self.apply_damage(fighter, total_damage[fighter]) + # If fighter HP is reduced to 0 or less, call at_defeat. + if fighter.db.hp <= 0: + self.at_defeat(fighter) + + if self.is_in_combat(caster): # Spend action if in combat + self.spend_action(caster, 1, action_name="cast")
        + +
        [docs] def spell_conjure(self, caster, spell_name, targets, cost, **kwargs): + """ + Spell that creates an object. + + kwargs: + obj_key (str): Key of the created object. + obj_desc (str): Desc of the created object. + obj_typeclass (str): Typeclass path of the object. + + If you want to make more use of this particular spell funciton, + you may want to modify it to use the spawner (in evennia.utils.spawner) + instead of creating objects directly. + """ + + obj_key = "a nondescript object" + obj_desc = "A perfectly generic object." + obj_typeclass = "evennia.objects.objects.DefaultObject" + + # Retrieve some variables from kwargs, if present + if "obj_key" in kwargs: + obj_key = kwargs["obj_key"] + if "obj_desc" in kwargs: + obj_desc = kwargs["obj_desc"] + if "obj_typeclass" in kwargs: + obj_typeclass = kwargs["obj_typeclass"] + + conjured_obj = create_object( + obj_typeclass, key=obj_key, location=caster.location + ) # Create object + conjured_obj.db.desc = obj_desc # Add object desc + + caster.db.mp -= cost # Deduct MP cost + + # Message the room to announce the creation of the object + caster.location.msg_contents( + "%s casts %s, and %s appears!" % (caster, spell_name, conjured_obj) + )
        + + +COMBAT_RULES = MagicCombatRules() + + +""" +---------------------------------------------------------------------------- +SPELL DEFINITIONS START HERE +---------------------------------------------------------------------------- +In this section, each spell is matched to a function, and given parameters +that determine its MP cost, valid type and number of targets, and what +function casting the spell executes. + +This data is given as a dictionary of dictionaries - the key of each entry +is the spell's name, and the value is a dictionary of various options and +parameters, some of which are required and others which are optional. + +Required values for spells: + + cost (int): MP cost of casting the spell + target (str): Valid targets for the spell. Can be any of: + "none" - No target needed + "self" - Self only + "any" - Any object + "anyobj" - Any object that isn't a character + "anychar" - Any character + "other" - Any object excluding the caster + "otherchar" - Any character excluding the caster + spellfunc (callable): Function that performs the action of the spell. + Must take the following arguments: caster (obj), spell_name (str), + targets (list), and cost (int), as well as **kwargs. + +Optional values for spells: + + combat_spell (bool): If the spell can be cast in combat. True by default. + noncombat_spell (bool): If the spell can be cast out of combat. True by default. + max_targets (int): Maximum number of objects that can be targeted by the spell. + 1 by default - unused if target is "none" or "self" + +Any other values specified besides the above will be passed as kwargs to 'spellfunc'. +You can use kwargs to effectively re-use the same function for different but similar +spells - for example, 'magic missile' and 'flame shot' use the same function, but +behave differently, as they have different damage ranges, accuracy, amount of attacks +made as part of the spell, and so forth. If you make your spell functions flexible +enough, you can make a wide variety of spells just by adding more entries to this +dictionary. +""" + +SPELLS = { + "magic missile": { + "spellfunc": COMBAT_RULES.spell_attack, + "target": "otherchar", + "cost": 3, + "noncombat_spell": False, + "max_targets": 3, + "attack_name": ("A bolt", "bolts"), + "damage_range": (4, 7), + "accuracy": 999, + "attack_count": 3, + }, + "flame shot": { + "spellfunc": COMBAT_RULES.spell_attack, + "target": "otherchar", + "cost": 3, + "noncombat_spell": False, + "attack_name": ("A jet of flame", "jets of flame"), + "damage_range": (25, 35), + }, + "cure wounds": { + "spellfunc": COMBAT_RULES.spell_healing, + "target": "anychar", + "cost": 5 + }, + "mass cure wounds": { + "spellfunc": COMBAT_RULES.spell_healing, + "target": "anychar", + "cost": 10, + "max_targets": 5, + }, + "full heal": { + "spellfunc": COMBAT_RULES.spell_healing, + "target": "anychar", + "cost": 12, + "healing_range": (100, 100), + }, + "cactus conjuration": { + "spellfunc": COMBAT_RULES.spell_conjure, + "target": "none", + "cost": 2, + "combat_spell": False, + "obj_key": "a cactus", + "obj_desc": "An ordinary green cactus with little spines.", + }, +} + """ ---------------------------------------------------------------------------- @@ -124,246 +406,6 @@ TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds ACTIONS_PER_TURN = 1 # Number of actions allowed per turn -""" ----------------------------------------------------------------------------- -COMBAT FUNCTIONS START HERE ----------------------------------------------------------------------------- -""" - - -
        [docs]def roll_init(character): - """ - Rolls a number between 1-1000 to determine initiative. - - Args: - character (obj): The character to determine initiative for - - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. - - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. - - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: - - return (randint(1,20)) + character.db.dexterity - - This way, characters with a higher dexterity will go first more often. - """ - return randint(1, 1000)
        - - -
        [docs]def get_attack(attacker, defender): - """ - Returns a value for an attack roll. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. - - Notes: - By default, returns a random integer from 1 to 100 without using any - properties from either the attacker or defender. - - This can easily be expanded to return a value based on characters stats, - equipment, and abilities. This is why the attacker and defender are passed - to this function, even though nothing from either one are used in this example. - """ - # For this example, just return a random integer up to 100. - attack_value = randint(1, 100) - return attack_value
        - - -
        [docs]def get_defense(attacker, defender): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. - - Notes: - By default, returns 50, not taking any properties of the defender or - attacker into account. - - As above, this can be expanded upon based on character stats and equipment. - """ - # For this example, just return 50, for about a 50/50 chance of hit. - defense_value = 50 - return defense_value
        - - -
        [docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - By default, returns a random integer from 15 to 25 without using any - properties from either the attacker or defender. - - Again, this can be expanded upon. - """ - # For this example, just generate a number between 15 and 25. - damage_value = randint(15, 25) - return damage_value
        - - -
        [docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. - - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
        - - -
        [docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
        - - -
        [docs]def resolve_attack(attacker, defender, attack_value=None, defense_value=None): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - - Notes: - Even though the attack and defense values are calculated - extremely simply, they are separated out into their own functions - so that they are easier to expand upon. - """ - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents("%s's attack misses %s!" % (attacker, defender)) - else: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - attacker.location.msg_contents( - "%s hits %s for %i damage!" % (attacker, defender, damage_value) - ) - apply_damage(defender, damage_value) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
        - - -
        [docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
        - - -
        [docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
        - - -
        [docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
        - - -
        [docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if not is_in_combat(character): - return - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
        - """ ---------------------------------------------------------------------------- @@ -372,11 +414,13 @@ """ -
        [docs]class TBMagicCharacter(DefaultCharacter): +
        [docs]class TBMagicCharacter(tb_basic.TBBasicCharacter): """ A character able to participate in turn-based combat. Has attributes for current - and maximum HP, and access to combat commands. + and maximum HP, access to combat commands and magic. + """ + rules = COMBAT_RULES
        [docs] def at_object_creation(self): """ @@ -393,32 +437,7 @@ self.db.hp = self.db.max_hp # Set current HP to maximum self.db.spells_known = [] # Set empty spells known list self.db.max_mp = 20 # Set maximum MP to 20 - self.db.mp = self.db.max_mp # Set current MP to maximum
        - -
        [docs] def at_pre_move(self, destination): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
        + self.db.mp = self.db.max_mp # Set current MP to maximum
        """ @@ -428,7 +447,7 @@ """ -
        [docs]class TBMagicTurnHandler(DefaultScript): +
        [docs]class TBMagicTurnHandler(tb_basic.TBBasicTurnHandler): """ This is the script that handles the progression of combat through turns. On creation (when a fight is started) it adds all combat-ready characters @@ -440,173 +459,7 @@ remaining participants choose to end the combat with the 'disengage' command. """ -
        [docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
        - -
        [docs] def at_stop(self): - """ - Called at script termination. - """ - for fighter in self.db.fighters: - combat_cleanup(fighter) # Clean up the combat attributes for every fighter. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
        - -
        [docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
        - -
        [docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. - - Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
        - -
        [docs] def start_turn(self, character): - """ - Readies a character for the start of their turn by replenishing their - available actions and notifying them that their turn has come up. - - Args: - character (obj): Character to be readied. - - Notes: - Here, you only get one action per turn, but you might want to allow more than - one per turn, or even grant a number of actions based on a character's - attributes. You can even add multiple different kinds of actions, I.E. actions - separated for movement, by adding "character.db.combat_movesleft = 3" or - something similar. - """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Prompt the character for their turn and give some information. - character.msg("|wIt's your turn! You have %i HP remaining.|n" % character.db.hp)
        - -
        [docs] def next_turn(self): - """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - newchar = self.db.fighters[self.db.turn] # Note the new character - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn.
        - -
        [docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. - - Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
        - -
        [docs] def join_fight(self, character): - """ - Adds a new character to a fight already in progress. - - Args: - character (obj): Character to be added to the fight. - """ - # Inserts the fighter to the turn order, right behind whoever's turn it currently is. - self.db.fighters.insert(self.db.turn, character) - # Tick the turn counter forward one to compensate. - self.db.turn += 1 - # Initialize the character like you do at the start. - self.initialize_for_combat(character)
        + rules = COMBAT_RULES
        """ @@ -616,7 +469,7 @@ """ -
        [docs]class CmdFight(Command): +
        [docs]class CmdFight(tb_basic.CmdFight): """ Starts a fight with everyone in the same room as you. @@ -631,36 +484,11 @@ key = "fight" help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.game_systems.turnbattle.tb_magic.TBMagicTurnHandler")
        - # Remember you'll have to change the path to the script if you copy this code to your own modules! + rules = COMBAT_RULES + combat_handler_class = TBMagicTurnHandler
        -
        [docs]class CmdAttack(Command): +
        [docs]class CmdAttack(tb_basic.CmdAttack): """ Attacks another character. @@ -674,42 +502,10 @@ key = "attack" help_category = "combat" -
        [docs] def func(self): - "This performs the actual command." - "Set the attacker to the caller and the defender to the target." - - if not is_in_combat(self.caller): # If not in combat, can't attack. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn, can't attack. - self.caller.msg("You can only do that on your turn.") - return - - if not self.caller.db.hp: # Can't attack if you have no HP. - self.caller.msg("You can't attack, you've been defeated.") - return - - attacker = self.caller - defender = self.caller.search(self.args) - - if not defender: # No valid target given. - return - - if not defender.db.hp: # Target object has no HP left or to begin with - self.caller.msg("You can't fight that!") - return - - if attacker == defender: # Target and attacker are the same - self.caller.msg("You can't attack yourself!") - return - - "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender) - spend_action(self.caller, 1, action_name="attack") # Use up one action.
        + rules = COMBAT_RULES
        -
        [docs]class CmdPass(Command): +
        [docs]class CmdPass(tb_basic.CmdPass): """ Passes on your turn. @@ -724,25 +520,10 @@ aliases = ["wait", "hold"] help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
        + rules = COMBAT_RULES
        -
        [docs]class CmdDisengage(Command): +
        [docs]class CmdDisengage(tb_basic.CmdDisengage): """ Passes your turn and attempts to end combat. @@ -758,24 +539,7 @@ aliases = ["spare"] help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
        + rules = COMBAT_RULES
        [docs]class CmdLearnSpell(Command): @@ -865,6 +629,8 @@ key = "cast" help_category = "magic" + rules = COMBAT_RULES +
        [docs] def func(self): """ This performs the actual command. @@ -891,7 +657,7 @@ caller.msg(spells_known_msg) # List the spells the player knows return - spellname = self.lhs.lower() + spellname = self.lhs.lower() # noqa - not used but potentially useful spell_to_cast = [] spell_targets = [] @@ -952,12 +718,12 @@ return # If in combat and the spell isn't a combat spell, give error message and return - if spelldata["combat_spell"] == False and is_in_combat(caller): + if spelldata["combat_spell"] is False and self.rules.is_in_combat(caller): caller.msg("You can't use the spell '%s' in combat." % spell_to_cast) return # If not in combat and the spell isn't a non-combat spell, error ms and return. - if spelldata["noncombat_spell"] == False and is_in_combat(caller) == False: + if spelldata["noncombat_spell"] is False and self.rules.is_in_combat(caller) is False: caller.msg("You can't use the spell '%s' outside of combat." % spell_to_cast) return @@ -1055,10 +821,12 @@ key = "rest" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): "This performs the actual command." - if is_in_combat(self.caller): # If you're in combat + if self.rules.is_in_combat(self.caller): # If you're in combat self.caller.msg("You can't rest while you're in combat.") return @@ -1099,7 +867,7 @@ )
        -
        [docs]class CmdCombatHelp(CmdHelp): +
        [docs]class CmdCombatHelp(tb_basic.CmdCombatHelp): """ View help or a list of topics @@ -1110,21 +878,7 @@ This will search for help on commands and other topics related to the game. - """ - - # Just like the default help command, but will give quick - # tips on combat when used in a fight with no arguments. - -
        [docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack a target, attempting to deal damage.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - ) - else: - super().func() # Call the default help command
        + """
        [docs]class BattleCmdSet(default_cmds.CharacterCmdSet): @@ -1147,280 +901,6 @@ self.add(CmdLearnSpell()) self.add(CmdCast()) self.add(CmdStatus())
        - - -""" ----------------------------------------------------------------------------- -SPELL FUNCTIONS START HERE ----------------------------------------------------------------------------- - -These are the functions that are called by the 'cast' command to perform the -effects of various spells. Which spells execute which functions and what -parameters are passed to them are specified at the bottom of the module, in -the 'SPELLS' dictionary. - -All of these functions take the same arguments: - caster (obj): Character casting the spell - spell_name (str): Name of the spell being cast - targets (list): List of objects targeted by the spell - cost (int): MP cost of casting the spell - -These functions also all accept **kwargs, and how these are used is specified -in the docstring for each function. -""" - - -
        [docs]def spell_healing(caster, spell_name, targets, cost, **kwargs): - """ - Spell that restores HP to a target or targets. - - kwargs: - healing_range (tuple): Minimum and maximum amount healed to - each target. (20, 40) by default. - """ - spell_msg = "%s casts %s!" % (caster, spell_name) - - min_healing = 20 - max_healing = 40 - - # Retrieve healing range from kwargs, if present - if "healing_range" in kwargs: - min_healing = kwargs["healing_range"][0] - max_healing = kwargs["healing_range"][1] - - for character in targets: - to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp - if character.db.hp + to_heal > character.db.max_hp: - to_heal = character.db.max_hp - character.db.hp # Cap healing to max HP - character.db.hp += to_heal - spell_msg += " %s regains %i HP!" % (character, to_heal) - - caster.db.mp -= cost # Deduct MP cost - - caster.location.msg_contents(spell_msg) # Message the room with spell results - - if is_in_combat(caster): # Spend action if in combat - spend_action(caster, 1, action_name="cast")
        - - -
        [docs]def spell_attack(caster, spell_name, targets, cost, **kwargs): - """ - Spell that deals damage in combat. Similar to resolve_attack. - - kwargs: - attack_name (tuple): Single and plural describing the sort of - attack or projectile that strikes each enemy. - damage_range (tuple): Minimum and maximum damage dealt by the - spell. (10, 20) by default. - accuracy (int): Modifier to the spell's attack roll, determining - an increased or decreased chance to hit. 0 by default. - attack_count (int): How many individual attacks are made as part - of the spell. If the number of attacks exceeds the number of - targets, the first target specified will be attacked more - than once. Just 1 by default - if the attack_count is less - than the number targets given, each target will only be - attacked once. - """ - spell_msg = "%s casts %s!" % (caster, spell_name) - - atkname_single = "The spell" - atkname_plural = "spells" - min_damage = 10 - max_damage = 20 - accuracy = 0 - attack_count = 1 - - # Retrieve some variables from kwargs, if present - if "attack_name" in kwargs: - atkname_single = kwargs["attack_name"][0] - atkname_plural = kwargs["attack_name"][1] - if "damage_range" in kwargs: - min_damage = kwargs["damage_range"][0] - max_damage = kwargs["damage_range"][1] - if "accuracy" in kwargs: - accuracy = kwargs["accuracy"] - if "attack_count" in kwargs: - attack_count = kwargs["attack_count"] - - to_attack = [] - # If there are more attacks than targets given, attack first target multiple times - if len(targets) < attack_count: - to_attack = to_attack + targets - extra_attacks = attack_count - len(targets) - for n in range(extra_attacks): - to_attack.insert(0, targets[0]) - else: - to_attack = to_attack + targets - - # Set up dictionaries to track number of hits and total damage - total_hits = {} - total_damage = {} - for fighter in targets: - total_hits.update({fighter: 0}) - total_damage.update({fighter: 0}) - - # Resolve attack for each target - for fighter in to_attack: - attack_value = randint(1, 100) + accuracy # Spell attack roll - defense_value = get_defense(caster, fighter) - if attack_value >= defense_value: - spell_dmg = randint(min_damage, max_damage) # Get spell damage - total_hits[fighter] += 1 - total_damage[fighter] += spell_dmg - - for fighter in targets: - # Construct combat message - if total_hits[fighter] == 0: - spell_msg += " The spell misses %s!" % fighter - elif total_hits[fighter] > 0: - attack_count_str = atkname_single + " hits" - if total_hits[fighter] > 1: - attack_count_str = "%i %s hit" % (total_hits[fighter], atkname_plural) - spell_msg += " %s %s for %i damage!" % ( - attack_count_str, - fighter, - total_damage[fighter], - ) - - caster.db.mp -= cost # Deduct MP cost - - caster.location.msg_contents(spell_msg) # Message the room with spell results - - for fighter in targets: - # Apply damage - apply_damage(fighter, total_damage[fighter]) - # If fighter HP is reduced to 0 or less, call at_defeat. - if fighter.db.hp <= 0: - at_defeat(fighter) - - if is_in_combat(caster): # Spend action if in combat - spend_action(caster, 1, action_name="cast")
        - - -
        [docs]def spell_conjure(caster, spell_name, targets, cost, **kwargs): - """ - Spell that creates an object. - - kwargs: - obj_key (str): Key of the created object. - obj_desc (str): Desc of the created object. - obj_typeclass (str): Typeclass path of the object. - - If you want to make more use of this particular spell funciton, - you may want to modify it to use the spawner (in evennia.utils.spawner) - instead of creating objects directly. - """ - - obj_key = "a nondescript object" - obj_desc = "A perfectly generic object." - obj_typeclass = "evennia.objects.objects.DefaultObject" - - # Retrieve some variables from kwargs, if present - if "obj_key" in kwargs: - obj_key = kwargs["obj_key"] - if "obj_desc" in kwargs: - obj_desc = kwargs["obj_desc"] - if "obj_typeclass" in kwargs: - obj_typeclass = kwargs["obj_typeclass"] - - conjured_obj = create_object( - obj_typeclass, key=obj_key, location=caster.location - ) # Create object - conjured_obj.db.desc = obj_desc # Add object desc - - caster.db.mp -= cost # Deduct MP cost - - # Message the room to announce the creation of the object - caster.location.msg_contents( - "%s casts %s, and %s appears!" % (caster, spell_name, conjured_obj) - )
        - - -""" ----------------------------------------------------------------------------- -SPELL DEFINITIONS START HERE ----------------------------------------------------------------------------- -In this section, each spell is matched to a function, and given parameters -that determine its MP cost, valid type and number of targets, and what -function casting the spell executes. - -This data is given as a dictionary of dictionaries - the key of each entry -is the spell's name, and the value is a dictionary of various options and -parameters, some of which are required and others which are optional. - -Required values for spells: - - cost (int): MP cost of casting the spell - target (str): Valid targets for the spell. Can be any of: - "none" - No target needed - "self" - Self only - "any" - Any object - "anyobj" - Any object that isn't a character - "anychar" - Any character - "other" - Any object excluding the caster - "otherchar" - Any character excluding the caster - spellfunc (callable): Function that performs the action of the spell. - Must take the following arguments: caster (obj), spell_name (str), - targets (list), and cost (int), as well as **kwargs. - -Optional values for spells: - - combat_spell (bool): If the spell can be cast in combat. True by default. - noncombat_spell (bool): If the spell can be cast out of combat. True by default. - max_targets (int): Maximum number of objects that can be targeted by the spell. - 1 by default - unused if target is "none" or "self" - -Any other values specified besides the above will be passed as kwargs to 'spellfunc'. -You can use kwargs to effectively re-use the same function for different but similar -spells - for example, 'magic missile' and 'flame shot' use the same function, but -behave differently, as they have different damage ranges, accuracy, amount of attacks -made as part of the spell, and so forth. If you make your spell functions flexible -enough, you can make a wide variety of spells just by adding more entries to this -dictionary. -""" - -SPELLS = { - "magic missile": { - "spellfunc": spell_attack, - "target": "otherchar", - "cost": 3, - "noncombat_spell": False, - "max_targets": 3, - "attack_name": ("A bolt", "bolts"), - "damage_range": (4, 7), - "accuracy": 999, - "attack_count": 3, - }, - "flame shot": { - "spellfunc": spell_attack, - "target": "otherchar", - "cost": 3, - "noncombat_spell": False, - "attack_name": ("A jet of flame", "jets of flame"), - "damage_range": (25, 35), - }, - "cure wounds": {"spellfunc": spell_healing, "target": "anychar", "cost": 5}, - "mass cure wounds": { - "spellfunc": spell_healing, - "target": "anychar", - "cost": 10, - "max_targets": 5, - }, - "full heal": { - "spellfunc": spell_healing, - "target": "anychar", - "cost": 12, - "healing_range": (100, 100), - }, - "cactus conjuration": { - "spellfunc": spell_conjure, - "target": "none", - "cost": 2, - "combat_spell": False, - "obj_key": "a cactus", - "obj_desc": "An ordinary green cactus with little spines.", - }, -}
        @@ -1455,7 +935,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_range.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_range.html index 051b35316f..0df11142d7 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_range.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tb_range.html @@ -145,8 +145,9 @@ """ from random import randint -from evennia import DefaultCharacter, DefaultObject, Command, default_cmds, DefaultScript +from evennia import DefaultObject, Command, default_cmds, DefaultScript from evennia.commands.default.help import CmdHelp +from . import tb_basic """ ---------------------------------------------------------------------------- @@ -164,238 +165,102 @@ """ -
        [docs]def roll_init(character): - """ - Rolls a number between 1-1000 to determine initiative. +
        [docs]class RangedCombatRules(tb_basic.BasicCombatRules): - Args: - character (obj): The character to determine initiative for +
        [docs] def get_attack(self, attacker, defender, attack_type): + """ + Returns a value for an attack roll. - Returns: - initiative (int): The character's place in initiative - higher - numbers go first. + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked + attack_type (str): Type of attack ('melee' or 'ranged') - Notes: - By default, does not reference the character and simply returns - a random integer from 1 to 1000. + Returns: + attack_value (int): Attack roll value, compared against a defense value + to determine whether an attack hits or misses. - Since the character is passed to this function, you can easily reference - a character's stats to determine an initiative roll - for example, if your - character has a 'dexterity' attribute, you can use it to give that character - an advantage in turn order, like so: + Notes: + By default, generates a random integer from 1 to 100 without using any + properties from either the attacker or defender, and modifies the result + based on whether it's for a melee or ranged attack. - return (randint(1,20)) + character.db.dexterity + This can easily be expanded to return a value based on characters stats, + equipment, and abilities. This is why the attacker and defender are passed + to this function, even though nothing from either one are used in this example. + """ + # For this example, just return a random integer up to 100. + attack_value = randint(1, 100) + # Make melee attacks more accurate, ranged attacks less accurate + if attack_type == "melee": + attack_value += 15 + if attack_type == "ranged": + attack_value -= 15 + return attack_value
        - This way, characters with a higher dexterity will go first more often. - """ - return randint(1, 1000)
        +
        [docs] def get_defense(self, attacker, defender, attack_type='melee'): + """ + Returns a value for defense, which an attack roll must equal or exceed in order + for an attack to hit. + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked + attack_type (str): Type of attack ('melee' or 'ranged') -
        [docs]def get_attack(attacker, defender, attack_type): - """ - Returns a value for an attack roll. + Returns: + defense_value (int): Defense value, compared against an attack roll + to determine whether an attack hits or misses. - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - attack_type (str): Type of attack ('melee' or 'ranged') + Notes: + By default, returns 50, not taking any properties of the defender or + attacker into account. - Returns: - attack_value (int): Attack roll value, compared against a defense value - to determine whether an attack hits or misses. + As above, this can be expanded upon based on character stats and equipment. + """ + # For this example, just return 50, for about a 50/50 chance of hit. + defense_value = 50 + return defense_value
        - Notes: - By default, generates a random integer from 1 to 100 without using any - properties from either the attacker or defender, and modifies the result - based on whether it's for a melee or ranged attack. +
        [docs] def get_range(self, obj1, obj2): + """ + Gets the combat range between two objects. - This can easily be expanded to return a value based on characters stats, - equipment, and abilities. This is why the attacker and defender are passed - to this function, even though nothing from either one are used in this example. - """ - # For this example, just return a random integer up to 100. - attack_value = randint(1, 100) - # Make melee attacks more accurate, ranged attacks less accurate - if attack_type == "melee": - attack_value += 15 - if attack_type == "ranged": - attack_value -= 15 - return attack_value
        + Args: + obj1 (obj): First object + obj2 (obj): Second object + Returns: + range (int or None): Distance between two objects or None if not applicable + """ + # Return None if not applicable. + if not obj1.db.combat_range: + return None + if not obj2.db.combat_range: + return None + if obj1 not in obj2.db.combat_range: + return None + if obj2 not in obj1.db.combat_range: + return None + # Return the range between the two objects. + return obj1.db.combat_range[obj2]
        -
        [docs]def get_defense(attacker, defender, attack_type): - """ - Returns a value for defense, which an attack roll must equal or exceed in order - for an attack to hit. +
        [docs] def distance_inc(self, mover, target): + """ + Function that increases distance in range field between mover and target. - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - attack_type (str): Type of attack ('melee' or 'ranged') + Args: + mover (obj): The object moving + target (obj): The object to be moved away from + """ + mover.db.combat_range[target] += 1 + target.db.combat_range[mover] = mover.db.combat_range[target] + # Set a cap of 2: + if self.get_range(mover, target) > 2: + target.db.combat_range[mover] = 2 + mover.db.combat_range[target] = 2
        - Returns: - defense_value (int): Defense value, compared against an attack roll - to determine whether an attack hits or misses. - - Notes: - By default, returns 50, not taking any properties of the defender or - attacker into account. - - As above, this can be expanded upon based on character stats and equipment. - """ - # For this example, just return 50, for about a 50/50 chance of hit. - defense_value = 50 - return defense_value
        - - -
        [docs]def get_damage(attacker, defender): - """ - Returns a value for damage to be deducted from the defender's HP after abilities - successful hit. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being damaged - - Returns: - damage_value (int): Damage value, which is to be deducted from the defending - character's HP. - - Notes: - By default, returns a random integer from 15 to 25 without using any - properties from either the attacker or defender. - - Again, this can be expanded upon. - """ - # For this example, just generate a number between 15 and 25. - damage_value = randint(15, 25) - return damage_value
        - - -
        [docs]def apply_damage(defender, damage): - """ - Applies damage to a target, reducing their HP by the damage amount to a - minimum of 0. - - Args: - defender (obj): Character taking damage - damage (int): Amount of damage being taken - """ - defender.db.hp -= damage # Reduce defender's HP by the damage dealt. - # If this reduces it to 0 or less, set HP to 0. - if defender.db.hp <= 0: - defender.db.hp = 0
        - - -
        [docs]def at_defeat(defeated): - """ - Announces the defeat of a fighter in combat. - - Args: - defeated (obj): Fighter that's been defeated. - - Notes: - All this does is announce a defeat message by default, but if you - want anything else to happen to defeated fighters (like putting them - into a dying state or something similar) then this is the place to - do it. - """ - defeated.location.msg_contents("%s has been defeated!" % defeated)
        - - -
        [docs]def resolve_attack(attacker, defender, attack_type, attack_value=None, defense_value=None): - """ - Resolves an attack and outputs the result. - - Args: - attacker (obj): Character doing the attacking - defender (obj): Character being attacked - attack_type (str): Type of attack (melee or ranged) - - Notes: - Even though the attack and defense values are calculated - extremely simply, they are separated out into their own functions - so that they are easier to expand upon. - """ - # Get an attack roll from the attacker. - if not attack_value: - attack_value = get_attack(attacker, defender, attack_type) - # Get a defense value from the defender. - if not defense_value: - defense_value = get_defense(attacker, defender, attack_type) - # If the attack value is lower than the defense value, miss. Otherwise, hit. - if attack_value < defense_value: - attacker.location.msg_contents( - "%s's %s attack misses %s!" % (attacker, attack_type, defender) - ) - else: - damage_value = get_damage(attacker, defender) # Calculate damage value. - # Announce damage dealt and apply damage. - attacker.location.msg_contents( - "%s hits %s with a %s attack for %i damage!" - % (attacker, defender, attack_type, damage_value) - ) - apply_damage(defender, damage_value) - # If defender HP is reduced to 0 or less, call at_defeat. - if defender.db.hp <= 0: - at_defeat(defender)
        - - -
        [docs]def get_range(obj1, obj2): - """ - Gets the combat range between two objects. - - Args: - obj1 (obj): First object - obj2 (obj): Second object - - Returns: - range (int or None): Distance between two objects or None if not applicable - """ - # Return None if not applicable. - if not obj1.db.combat_range: - return None - if not obj2.db.combat_range: - return None - if obj1 not in obj2.db.combat_range: - return None - if obj2 not in obj1.db.combat_range: - return None - # Return the range between the two objects. - return obj1.db.combat_range[obj2]
        - - -
        [docs]def distance_inc(mover, target): - """ - Function that increases distance in range field between mover and target. - - Args: - mover (obj): The object moving - target (obj): The object to be moved away from - """ - mover.db.combat_range[target] += 1 - target.db.combat_range[mover] = mover.db.combat_range[target] - # Set a cap of 2: - if get_range(mover, target) > 2: - target.db.combat_range[mover] = 2 - mover.db.combat_range[target] = 2
        - - -
        [docs]def approach(mover, target): - """ - Manages a character's whole approach, including changes in ranges to other characters. - - Args: - mover (obj): The object moving - target (obj): The object to be moved toward - - Notes: - The mover will also automatically move toward any objects that are closer to the - target than the mover is. The mover will also move away from anything they started - out close to. - """ - - def distance_dec(mover, target): +
        [docs] def distance_dec(self, mover, target): """ Helper function that decreases distance in range field between mover and target. @@ -406,167 +271,154 @@ mover.db.combat_range[target] -= 1 target.db.combat_range[mover] = mover.db.combat_range[target] # If this brings mover to range 0 (Engaged): - if get_range(mover, target) <= 0: + if self.get_range(mover, target) <= 0: # Reset range to each other to 0 and copy target's ranges to mover. target.db.combat_range[mover] = 0 mover.db.combat_range = target.db.combat_range - # Assure everything else has the same distance from the mover and target, now that they're together + # Assure everything else has the same distance from the mover and target, now that + # they're together for thing in mover.location.contents: if thing != mover and thing != target: - thing.db.combat_range[mover] = thing.db.combat_range[target] + thing.db.combat_range[mover] = thing.db.combat_range[target]
        - contents = mover.location.contents +
        [docs] def approach(self, mover, target): + """ + Manages a character's whole approach, including changes in ranges to other characters. - for thing in contents: - if thing != mover and thing != target: - # Move closer to each object closer to the target than you. - if get_range(mover, thing) > get_range(target, thing): - distance_dec(mover, thing) - # Move further from each object that's further from you than from the target. - if get_range(mover, thing) < get_range(target, thing): - distance_inc(mover, thing) - # Lastly, move closer to your target. - distance_dec(mover, target)
        + Args: + mover (obj): The object moving + target (obj): The object to be moved toward + + Notes: + The mover will also automatically move toward any objects that are closer to the + target than the mover is. The mover will also move away from anything they started + out close to. + """ + + contents = mover.location.contents + + for thing in contents: + if thing != mover and thing != target: + # Move closer to each object closer to the target than you. + if self.get_range(mover, thing) > self.get_range(target, thing): + self.distance_dec(mover, thing) + # Move further from each object that's further from you than from the target. + if self.get_range(mover, thing) < self.get_range(target, thing): + self.distance_inc(mover, thing) + # Lastly, move closer to your target. + self.distance_dec(mover, target)
        + +
        [docs] def withdraw(self, mover, target): + """ + Manages a character's whole withdrawal, including changes in ranges to other characters. + + Args: + mover (obj): The object moving + target (obj): The object to be moved away from + + Notes: + The mover will also automatically move away from objects that are close to the target + of their withdrawl. The mover will never inadvertently move toward anything else while + withdrawing - they can be considered to be moving to open space. + """ + + contents = mover.location.contents + + for thing in contents: + if thing != mover and thing != target: + # Move away from each object closer to the target than you, if it's also closer to + # you than you are to the target. + if (self.get_range(mover, thing) >= self.get_range(target, thing) + and self.get_range(mover, thing) < self.get_range(mover, target)): + self.distance_inc(mover, thing) + # Move away from anything your target is engaged with + if self.get_range(target, thing) == 0: + self.distance_inc(mover, thing) + # Move away from anything you're engaged with. + if self.get_range(mover, thing) == 0: + self.distance_inc(mover, thing) + # Then, move away from your target. + self.distance_inc(mover, target)
        + +
        [docs] def resolve_attack(self, attacker, defender, attack_value=None, defense_value=None, + attack_type='melee'): + """ + Resolves an attack and outputs the result. + + Args: + attacker (obj): Character doing the attacking + defender (obj): Character being attacked + attack_type (str): Type of attack (melee or ranged) + + Notes: + Even though the attack and defense values are calculated + extremely simply, they are separated out into their own functions + so that they are easier to expand upon. + + """ + # Get an attack roll from the attacker. + if not attack_value: + attack_value = self.get_attack(attacker, defender, attack_type) + # Get a defense value from the defender. + if not defense_value: + defense_value = self.get_defense(attacker, defender, attack_type) + # If the attack value is lower than the defense value, miss. Otherwise, hit. + if attack_value < defense_value: + attacker.location.msg_contents( + "%s's %s attack misses %s!" % (attacker, attack_type, defender) + ) + else: + damage_value = self.get_damage(attacker, defender) # Calculate damage value. + # Announce damage dealt and apply damage. + attacker.location.msg_contents( + "%s hits %s with a %s attack for %i damage!" + % (attacker, defender, attack_type, damage_value) + ) + self.apply_damage(defender, damage_value) + # If defender HP is reduced to 0 or less, call at_defeat. + if defender.db.hp <= 0: + self.at_defeat(defender)
        + +
        [docs] def combat_status_message(self, fighter): + """ + Sends a message to a player with their current HP and + distances to other fighters and objects. Called at turn + start and by the 'status' command. + """ + if not fighter.db.max_hp: + fighter.db.hp = 100 + fighter.db.max_hp = 100 + + status_msg = "HP Remaining: %i / %i" % (fighter.db.hp, fighter.db.max_hp) + + if not self.is_in_combat(fighter): + fighter.msg(status_msg) + return + + engaged_obj = [] + reach_obj = [] + range_obj = [] + + for thing in fighter.db.combat_range: + if thing != fighter: + if fighter.db.combat_range[thing] == 0: + engaged_obj.append(thing) + if fighter.db.combat_range[thing] == 1: + reach_obj.append(thing) + if fighter.db.combat_range[thing] > 1: + range_obj.append(thing) + + if engaged_obj: + status_msg += "|/Engaged targets: %s" % ", ".join(obj.key for obj in engaged_obj) + if reach_obj: + status_msg += "|/Reach targets: %s" % ", ".join(obj.key for obj in reach_obj) + if range_obj: + status_msg += "|/Ranged targets: %s" % ", ".join(obj.key for obj in range_obj) + + fighter.msg(status_msg)
        -
        [docs]def withdraw(mover, target): - """ - Manages a character's whole withdrawal, including changes in ranges to other characters. - - Args: - mover (obj): The object moving - target (obj): The object to be moved away from - - Notes: - The mover will also automatically move away from objects that are close to the target - of their withdrawl. The mover will never inadvertently move toward anything else while - withdrawing - they can be considered to be moving to open space. - """ - - contents = mover.location.contents - - for thing in contents: - if thing != mover and thing != target: - # Move away from each object closer to the target than you, if it's also closer to you than you are to the target. - if get_range(mover, thing) >= get_range(target, thing) and get_range( - mover, thing - ) < get_range(mover, target): - distance_inc(mover, thing) - # Move away from anything your target is engaged with - if get_range(target, thing) == 0: - distance_inc(mover, thing) - # Move away from anything you're engaged with. - if get_range(mover, thing) == 0: - distance_inc(mover, thing) - # Then, move away from your target. - distance_inc(mover, target)
        - - -
        [docs]def combat_cleanup(character): - """ - Cleans up all the temporary combat-related attributes on a character. - - Args: - character (obj): Character to have their combat attributes removed - - Notes: - Any attribute whose key begins with 'combat_' is temporary and no - longer needed once a fight ends. - """ - for attr in character.attributes.all(): - if attr.key[:7] == "combat_": # If the attribute name starts with 'combat_'... - character.attributes.remove(key=attr.key) # ...then delete it!
        - - -
        [docs]def is_in_combat(character): - """ - Returns true if the given character is in combat. - - Args: - character (obj): Character to determine if is in combat or not - - Returns: - (bool): True if in combat or False if not in combat - """ - return bool(character.db.combat_turnhandler)
        - - -
        [docs]def is_turn(character): - """ - Returns true if it's currently the given character's turn in combat. - - Args: - character (obj): Character to determine if it is their turn or not - - Returns: - (bool): True if it is their turn or False otherwise - """ - turnhandler = character.db.combat_turnhandler - currentchar = turnhandler.db.fighters[turnhandler.db.turn] - return bool(character == currentchar)
        - - -
        [docs]def spend_action(character, actions, action_name=None): - """ - Spends a character's available combat actions and checks for end of turn. - - Args: - character (obj): Character spending the action - actions (int) or 'all': Number of actions to spend, or 'all' to spend all actions - - Keyword Args: - action_name (str or None): If a string is given, sets character's last action in - combat to provided string - """ - if action_name: - character.db.combat_lastaction = action_name - if actions == "all": # If spending all actions - character.db.combat_actionsleft = 0 # Set actions to 0 - else: - character.db.combat_actionsleft -= actions # Use up actions. - if character.db.combat_actionsleft < 0: - character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions - character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
        - - -
        [docs]def combat_status_message(fighter): - """ - Sends a message to a player with their current HP and - distances to other fighters and objects. Called at turn - start and by the 'status' command. - """ - if not fighter.db.max_hp: - fighter.db.hp = 100 - fighter.db.max_hp = 100 - - status_msg = "HP Remaining: %i / %i" % (fighter.db.hp, fighter.db.max_hp) - - if not is_in_combat(fighter): - fighter.msg(status_msg) - return - - engaged_obj = [] - reach_obj = [] - range_obj = [] - - for thing in fighter.db.combat_range: - if thing != fighter: - if fighter.db.combat_range[thing] == 0: - engaged_obj.append(thing) - if fighter.db.combat_range[thing] == 1: - reach_obj.append(thing) - if fighter.db.combat_range[thing] > 1: - range_obj.append(thing) - - if engaged_obj: - status_msg += "|/Engaged targets: %s" % ", ".join(obj.key for obj in engaged_obj) - if reach_obj: - status_msg += "|/Reach targets: %s" % ", ".join(obj.key for obj in reach_obj) - if range_obj: - status_msg += "|/Ranged targets: %s" % ", ".join(obj.key for obj in range_obj) - - fighter.msg(status_msg)
        - +COMBAT_RULES = RangedCombatRules() """ ---------------------------------------------------------------------------- @@ -575,7 +427,7 @@ """ -
        [docs]class TBRangeTurnHandler(DefaultScript): +
        [docs]class TBRangeTurnHandler(tb_basic.TBBasicTurnHandler): """ This is the script that handles the progression of combat through turns. On creation (when a fight is started) it adds all combat-ready characters @@ -588,74 +440,7 @@ command. """ -
        [docs] def at_script_creation(self): - """ - Called once, when the script is created. - """ - self.key = "Combat Turn Handler" - self.interval = 5 # Once every 5 seconds - self.persistent = True - self.db.fighters = [] - - # Add all fighters in the room with at least 1 HP to the combat." - for thing in self.obj.contents: - if thing.db.hp: - self.db.fighters.append(thing) - - # Initialize each fighter for combat - for fighter in self.db.fighters: - self.initialize_for_combat(fighter) - - # Add a reference to this script to the room - self.obj.db.combat_turnhandler = self - - # Initialize range field for all objects in the room - for thing in self.obj.contents: - self.init_range(thing) - - # Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order. - # The initiative roll is determined by the roll_init function and can be customized easily. - ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True) - self.db.fighters = ordered_by_roll - - # Announce the turn order. - self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters)) - - # Start first fighter's turn. - self.start_turn(self.db.fighters[0]) - - # Set up the current turn and turn timeout delay. - self.db.turn = 0 - self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
        - -
        [docs] def at_stop(self): - """ - Called at script termination. - """ - for thing in self.obj.contents: - combat_cleanup(thing) # Clean up the combat attributes for every object in the room. - self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
        - -
        [docs] def at_repeat(self): - """ - Called once every self.interval seconds. - """ - currentchar = self.db.fighters[ - self.db.turn - ] # Note the current character in the turn order. - self.db.timer -= self.interval # Count down the timer. - - if self.db.timer <= 0: - # Force current character to disengage if timer runs out. - self.obj.msg_contents("%s's turn timed out!" % currentchar) - spend_action( - currentchar, "all", action_name="disengage" - ) # Spend all remaining actions. - return - elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left - # Warn the current character if they're about to time out. - currentchar.msg("WARNING: About to time out!") - self.db.timeout_warning_given = True
        + rules = COMBAT_RULES
        [docs] def init_range(self, to_init): """ @@ -707,23 +492,7 @@ to_init.db.combat_range.update({to_init: 0}) # Add additional distance from anchor object, if any. for n in range(add_distance): - withdraw(to_init, anchor_obj)
        - -
        [docs] def initialize_for_combat(self, character): - """ - Prepares a character for combat when starting or entering a fight. - - Args: - character (obj): Character to initialize for combat. - """ - combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. - character.db.combat_actionsleft = ( - 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 - ) - character.db.combat_turnhandler = ( - self # Add a reference to this turn handler script to the character - ) - character.db.combat_lastaction = "null" # Track last action taken in combat
        + self.rules.withdraw(to_init, anchor_obj)
        [docs] def start_turn(self, character): """ @@ -738,64 +507,8 @@ characters to both move and attack in the same turn (or, alternately, move twice or attack twice). """ - character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions - # Prompt the character for their turn and give some information. - character.msg("|wIt's your turn!|n") - combat_status_message(character)
        - -
        [docs] def next_turn(self): - """ - Advances to the next character in the turn order. - """ - - # Check to see if every character disengaged as their last action. If so, end combat. - disengage_check = True - for fighter in self.db.fighters: - if ( - fighter.db.combat_lastaction != "disengage" - ): # If a character has done anything but disengage - disengage_check = False - if disengage_check: # All characters have disengaged - self.obj.msg_contents("All fighters have disengaged! Combat is over!") - self.stop() # Stop this script and end combat. - return - - # Check to see if only one character is left standing. If so, end combat. - defeated_characters = 0 - for fighter in self.db.fighters: - if fighter.db.HP == 0: - defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated) - if defeated_characters == ( - len(self.db.fighters) - 1 - ): # If only one character isn't defeated - for fighter in self.db.fighters: - if fighter.db.HP != 0: - LastStanding = fighter # Pick the one fighter left with HP remaining - self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding) - self.stop() # Stop this script and end combat. - return - - # Cycle to the next turn. - currentchar = self.db.fighters[self.db.turn] - self.db.turn += 1 # Go to the next in the turn order. - if self.db.turn > len(self.db.fighters) - 1: - self.db.turn = 0 # Go back to the first in the turn order once you reach the end. - newchar = self.db.fighters[self.db.turn] # Note the new character - self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer. - self.db.timeout_warning_given = False # Reset the timeout warning. - self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar)) - self.start_turn(newchar) # Start the new character's turn.
        - -
        [docs] def turn_end_check(self, character): - """ - Tests to see if a character's turn is over, and cycles to the next turn if it is. - - Args: - character (obj): Character to test for end of turn - """ - if not character.db.combat_actionsleft: # Character has no actions remaining - self.next_turn() - return
        + super().start_turn(character) + character.db.combat_actionsleft = ACTIONS_PER_TURN
        [docs] def join_fight(self, character): """ @@ -822,51 +535,12 @@ """ -
        [docs]class TBRangeCharacter(DefaultCharacter): +
        [docs]class TBRangeCharacter(tb_basic.TBBasicCharacter): """ A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands. """ - -
        [docs] def at_object_creation(self): - """ - Called once, when this object is first created. This is the - normal hook to overload for most object types. - """ - self.db.max_hp = 100 # Set maximum HP to 100 - self.db.hp = self.db.max_hp # Set current HP to maximum - """ - Adds attributes for a character's current and maximum HP. - We're just going to set this value at '100' by default. - - You may want to expand this to include various 'stats' that - can be changed at creation and factor into combat calculations. - """
        - -
        [docs] def at_pre_move(self, destination): - """ - Called just before starting to move this object to - destination. - - Args: - destination (Object): The object we are moving to - - Returns: - shouldmove (bool): If we should move or not. - - Notes: - If this method returns False/None, the move is cancelled - before it is even started. - - """ - # Keep the character from moving if at 0 HP or in combat. - if is_in_combat(self): - self.msg("You can't exit a room while in combat!") - return False # Returning false keeps the character from moving. - if self.db.HP <= 0: - self.msg("You can't move, you've been defeated!") - return False - return True
        + rules = COMBAT_RULES
        [docs]class TBRangeObject(DefaultObject): @@ -896,7 +570,7 @@ """ # Can't drop something if in combat and it's not your turn - if is_in_combat(dropper) and not is_turn(dropper): + if self.rules.is_in_combat(dropper) and not self.rules.is_turn(dropper): dropper.msg("You can only drop things on your turn!") return False return True
        @@ -940,11 +614,11 @@ before it is even started. """ # Restrictions for getting in combat - if is_in_combat(getter): - if not is_turn(getter): # Not your turn + if self.rules.is_in_combat(getter): + if not self.rules.is_turn(getter): # Not your turn getter.msg("You can only get things on your turn!") return False - if get_range(self, getter) > 0: # Too far away + if self.rules.get_range(self, getter) > 0: # Too far away getter.msg("You aren't close enough to get that! (see: help approach)") return False return True
        @@ -973,8 +647,8 @@ if self in thing.db.combat_range: thing.db.combat_range.pop(self, None) # If in combat, getter spends an action - if is_in_combat(getter): - spend_action(getter, 1, action_name="get") # Use up one action. + if self.rules.is_in_combat(getter): + self.rules.spend_action(getter, 1, action_name="get") # Use up one action.
        [docs] def at_pre_give(self, giver, getter): """ @@ -996,11 +670,11 @@ """ # Restrictions for giving in combat - if is_in_combat(giver): - if not is_turn(giver): # Not your turn + if self.rules.is_in_combat(giver): + if not self.rules.is_turn(giver): # Not your turn giver.msg("You can only give things on your turn!") return False - if get_range(giver, getter) > 0: # Too far away from target + if self.rules.get_range(giver, getter) > 0: # Too far away from target giver.msg( "You aren't close enough to give things to %s! (see: help approach)" % getter ) @@ -1024,8 +698,8 @@ """ # Spend an action if in combat - if is_in_combat(giver): - spend_action(giver, 1, action_name="give") # Use up one action.
        + if self.rules.is_in_combat(giver): + self.rules.spend_action(giver, 1, action_name="give") # Use up one action. """ @@ -1035,7 +709,7 @@ """ -
        [docs]class CmdFight(Command): +
        [docs]class CmdFight(tb_basic.CmdFight): """ Starts a fight with everyone in the same room as you. @@ -1050,36 +724,11 @@ key = "fight" help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - here = self.caller.location - fighters = [] - - if not self.caller.db.hp: # If you don't have any hp - self.caller.msg("You can't start a fight if you've been defeated!") - return - if is_in_combat(self.caller): # Already in a fight - self.caller.msg("You're already in a fight!") - return - for thing in here.contents: # Test everything in the room to add it to the fight. - if thing.db.HP: # If the object has HP... - fighters.append(thing) # ...then add it to the fight. - if len(fighters) <= 1: # If you're the only able fighter in the room - self.caller.msg("There's nobody here to fight!") - return - if here.db.combat_turnhandler: # If there's already a fight going on... - here.msg_contents("%s joins the fight!" % self.caller) - here.db.combat_turnhandler.join_fight(self.caller) # Join the fight! - return - here.msg_contents("%s starts a fight!" % self.caller) - # Add a turn handler script to the room, which starts combat. - here.scripts.add("contrib.game_systems.turnbattle.tb_range.TBRangeTurnHandler")
        - # Remember you'll have to change the path to the script if you copy this code to your own modules! + rules = COMBAT_RULES + combat_handler_class = TBRangeTurnHandler
        -
        [docs]class CmdAttack(Command): +
        [docs]class CmdAttack(tb_basic.CmdAttack): """ Attacks another character in melee. @@ -1095,15 +744,17 @@ key = "attack" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): "This performs the actual command." "Set the attacker to the caller and the defender to the target." - if not is_in_combat(self.caller): # If not in combat, can't attack. + if not self.rules.is_in_combat(self.caller): # If not in combat, can't attack. self.caller.msg("You can only do that in combat. (see: help fight)") return - if not is_turn(self.caller): # If it's not your turn, can't attack. + if not self.rules.is_turn(self.caller): # If it's not your turn, can't attack. self.caller.msg("You can only do that on your turn.") return @@ -1125,7 +776,7 @@ self.caller.msg("You can't attack yourself!") return - if not get_range(attacker, defender) == 0: # Target isn't in melee + if not self.rules.get_range(attacker, defender) == 0: # Target isn't in melee self.caller.msg( "%s is too far away to attack - you need to get closer! (see: help approach)" % defender @@ -1133,8 +784,8 @@ return "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender, "melee") - spend_action(self.caller, 1, action_name="attack") # Use up one action.
        + self.rules.resolve_attack(attacker, defender, "melee") + self.rules.spend_action(self.caller, 1, action_name="attack") # Use up one action.
        [docs]class CmdShoot(Command): @@ -1154,15 +805,17 @@ key = "shoot" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): "This performs the actual command." "Set the attacker to the caller and the defender to the target." - if not is_in_combat(self.caller): # If not in combat, can't attack. + if not self.rules.is_in_combat(self.caller): # If not in combat, can't attack. self.caller.msg("You can only do that in combat. (see: help fight)") return - if not is_turn(self.caller): # If it's not your turn, can't attack. + if not self.rules.is_turn(self.caller): # If it's not your turn, can't attack. self.caller.msg("You can only do that on your turn.") return @@ -1188,19 +841,21 @@ in_melee = [] for target in attacker.db.combat_range: # Object is engaged and has HP - if get_range(attacker, defender) == 0 and target.db.hp and target != self.caller: + if (self.rules.get_range(attacker, defender) == 0 + and target.db.hp and target != self.caller): in_melee.append(target) # Add to list of targets in melee if len(in_melee) > 0: self.caller.msg( - "You can't shoot because there are fighters engaged with you (%s) - you need to retreat! (see: help withdraw)" + "You can't shoot because there are fighters engaged with you (%s) - you need " + "to retreat! (see: help withdraw)" % ", ".join(obj.key for obj in in_melee) ) return "If everything checks out, call the attack resolving function." - resolve_attack(attacker, defender, "ranged") - spend_action(self.caller, 1, action_name="attack") # Use up one action.
        + self.rules.resolve_attack(attacker, defender, "ranged") + self.rules.spend_action(self.caller, 1, action_name="attack") # Use up one action.
        [docs]class CmdApproach(Command): @@ -1217,14 +872,16 @@ key = "approach" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): "This performs the actual command." - if not is_in_combat(self.caller): # If not in combat, can't approach. + if not self.rules.is_in_combat(self.caller): # If not in combat, can't approach. self.caller.msg("You can only do that in combat. (see: help fight)") return - if not is_turn(self.caller): # If it's not your turn, can't approach. + if not self.rules.is_turn(self.caller): # If it's not your turn, can't approach. self.caller.msg("You can only do that on your turn.") return @@ -1246,14 +903,14 @@ self.caller.msg("You can't move toward yourself!") return - if get_range(mover, target) <= 0: # Already engaged with target + if self.rules.get_range(mover, target) <= 0: # Already engaged with target self.caller.msg("You're already next to that target!") return # If everything checks out, call the approach resolving function. - approach(mover, target) + self.rules.approach(mover, target) mover.location.msg_contents("%s moves toward %s." % (mover, target)) - spend_action(self.caller, 1, action_name="move") # Use up one action.
        + self.rules.spend_action(self.caller, 1, action_name="move") # Use up one action.
        [docs]class CmdWithdraw(Command): @@ -1269,14 +926,16 @@ key = "withdraw" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): "This performs the actual command." - if not is_in_combat(self.caller): # If not in combat, can't withdraw. + if not self.rules.is_in_combat(self.caller): # If not in combat, can't withdraw. self.caller.msg("You can only do that in combat. (see: help fight)") return - if not is_turn(self.caller): # If it's not your turn, can't withdraw. + if not self.rules.is_turn(self.caller): # If it's not your turn, can't withdraw. self.caller.msg("You can only do that on your turn.") return @@ -1303,12 +962,12 @@ return # If everything checks out, call the approach resolving function. - withdraw(mover, target) + self.rules.withdraw(mover, target) mover.location.msg_contents("%s moves away from %s." % (mover, target)) - spend_action(self.caller, 1, action_name="move") # Use up one action.
        + self.rules.spend_action(self.caller, 1, action_name="move") # Use up one action. -
        [docs]class CmdPass(Command): +
        [docs]class CmdPass(tb_basic.CmdPass): """ Passes on your turn. @@ -1323,25 +982,10 @@ aliases = ["wait", "hold"] help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # Can only pass a turn in combat. - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # Can only pass if it's your turn. - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents( - "%s takes no further action, passing the turn." % self.caller - ) - spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
        + rules = COMBAT_RULES
        -
        [docs]class CmdDisengage(Command): +
        [docs]class CmdDisengage(tb_basic.CmdDisengage): """ Passes your turn and attempts to end combat. @@ -1357,27 +1001,10 @@ aliases = ["spare"] help_category = "combat" -
        [docs] def func(self): - """ - This performs the actual command. - """ - if not is_in_combat(self.caller): # If you're not in combat - self.caller.msg("You can only do that in combat. (see: help fight)") - return - - if not is_turn(self.caller): # If it's not your turn - self.caller.msg("You can only do that on your turn.") - return - - self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller) - spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions. - """ - The action_name kwarg sets the character's last action to "disengage", which is checked by - the turn handler script to see if all fighters have disengaged. - """
        + rules = COMBAT_RULES
        -
        [docs]class CmdRest(Command): +
        [docs]class CmdRest(tb_basic.CmdRest): """ Recovers damage. @@ -1391,18 +1018,7 @@ key = "rest" help_category = "combat" -
        [docs] def func(self): - "This performs the actual command." - - if is_in_combat(self.caller): # If you're in combat - self.caller.msg("You can't rest while you're in combat.") - return - - self.caller.db.hp = self.caller.db.max_hp # Set current HP to maximum - self.caller.location.msg_contents("%s rests to recover HP." % self.caller) - """ - You'll probably want to replace this with your own system for recovering HP. - """
        + rules = COMBAT_RULES
        [docs]class CmdStatus(Command): @@ -1419,12 +1035,14 @@ key = "status" help_category = "combat" + rules = COMBAT_RULES +
        [docs] def func(self): "This performs the actual command." - combat_status_message(self.caller)
        + self.rules.combat_status_message(self.caller) -
        [docs]class CmdCombatHelp(CmdHelp): +
        [docs]class CmdCombatHelp(tb_basic.CmdCombatHelp): """ View help or a list of topics @@ -1439,21 +1057,17 @@ # Just like the default help command, but will give quick # tips on combat when used in a fight with no arguments. - -
        [docs] def func(self): - if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone - self.caller.msg( - "Available combat commands:|/" - + "|wAttack:|n Attack an engaged target, attempting to deal damage.|/" - + "|wShoot:|n Attack from a distance, if not engaged with other fighters.|/" - + "|wApproach:|n Move one step cloer to a target.|/" - + "|wWithdraw:|n Move one step away from a target.|/" - + "|wPass:|n Pass your turn without further action.|/" - + "|wStatus:|n View current HP and ranges to other targets.|/" - + "|wDisengage:|n End your turn and attempt to end combat.|/" - ) - else: - super().func() # Call the default help command
        + rules = COMBAT_RULES + combat_help_text = ( + "Available combat commands:|/" + "|wAttack:|n Attack an engaged target, attempting to deal damage.|/" + "|wShoot:|n Attack from a distance, if not engaged with other fighters.|/" + "|wApproach:|n Move one step cloer to a target.|/" + "|wWithdraw:|n Move one step away from a target.|/" + "|wPass:|n Pass your turn without further action.|/" + "|wStatus:|n View current HP and ranges to other targets.|/" + "|wDisengage:|n End your turn and attempt to end combat.|/" + )
        [docs]class BattleCmdSet(default_cmds.CharacterCmdSet): @@ -1511,7 +1125,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tests.html b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tests.html index fa34bdce93..c273e3a93e 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/game_systems/turnbattle/tests.html @@ -160,45 +160,46 @@ # Test combat functions
        [docs] def test_tbbasicfunc(self): # Initiative roll - initiative = tb_basic.roll_init(self.attacker) + initiative = tb_basic.COMBAT_RULES.roll_init(self.attacker) self.assertTrue(initiative >= 0 and initiative <= 1000) # Attack roll - attack_roll = tb_basic.get_attack(self.attacker, self.defender) + attack_roll = tb_basic.COMBAT_RULES.get_attack(self.attacker, self.defender) self.assertTrue(attack_roll >= 0 and attack_roll <= 100) # Defense roll - defense_roll = tb_basic.get_defense(self.attacker, self.defender) + defense_roll = tb_basic.COMBAT_RULES.get_defense(self.attacker, self.defender) self.assertTrue(defense_roll == 50) # Damage roll - damage_roll = tb_basic.get_damage(self.attacker, self.defender) + damage_roll = tb_basic.COMBAT_RULES.get_damage(self.attacker, self.defender) self.assertTrue(damage_roll >= 15 and damage_roll <= 25) # Apply damage self.defender.db.hp = 10 - tb_basic.apply_damage(self.defender, 3) + tb_basic.COMBAT_RULES.apply_damage(self.defender, 3) self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_basic.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10) + tb_basic.COMBAT_RULES.resolve_attack(self.attacker, self.defender, + attack_value=20, defense_value=10) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True - tb_basic.combat_cleanup(self.attacker) + tb_basic.COMBAT_RULES.combat_cleanup(self.attacker) self.assertFalse(self.attacker.db.combat_attribute) # Is in combat - self.assertFalse(tb_basic.is_in_combat(self.attacker)) + self.assertFalse(tb_basic.COMBAT_RULES.is_in_combat(self.attacker)) # Set up turn handler script for further tests self.attacker.location.scripts.add(tb_basic.TBBasicTurnHandler) - self.turnhandler = self.attacker.db.combat_TurnHandler - self.assertTrue(self.attacker.db.combat_TurnHandler) + self.turnhandler = self.attacker.db.combat_turnHandler + self.assertTrue(self.attacker.db.combat_turnHandler) # Set the turn handler's interval very high to keep it from repeating during tests. self.turnhandler.interval = 10000 # Force turn order self.turnhandler.db.fighters = [self.attacker, self.defender] self.turnhandler.db.turn = 0 # Test is turn - self.assertTrue(tb_basic.is_turn(self.attacker)) + self.assertTrue(tb_basic.COMBAT_RULES.is_turn(self.attacker)) # Spend actions self.attacker.db.Combat_ActionsLeft = 1 - tb_basic.spend_action(self.attacker, 1, action_name="Test") + tb_basic.COMBAT_RULES.spend_action(self.attacker, 1, action_name="Test") self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0) self.assertTrue(self.attacker.db.Combat_LastAction == "Test") # Initialize for combat @@ -253,45 +254,45 @@ # Test the combat functions in tb_equip too. They work mostly the same.
        [docs] def test_tbequipfunc(self): # Initiative roll - initiative = tb_equip.roll_init(self.attacker) + initiative = tb_equip.COMBAT_RULES.roll_init(self.attacker) self.assertTrue(initiative >= 0 and initiative <= 1000) # Attack roll - attack_roll = tb_equip.get_attack(self.attacker, self.defender) + attack_roll = tb_equip.COMBAT_RULES.get_attack(self.attacker, self.defender) self.assertTrue(attack_roll >= -50 and attack_roll <= 150) # Defense roll - defense_roll = tb_equip.get_defense(self.attacker, self.defender) + defense_roll = tb_equip.COMBAT_RULES.get_defense(self.attacker, self.defender) self.assertTrue(defense_roll == 50) # Damage roll - damage_roll = tb_equip.get_damage(self.attacker, self.defender) + damage_roll = tb_equip.COMBAT_RULES.get_damage(self.attacker, self.defender) self.assertTrue(damage_roll >= 0 and damage_roll <= 50) # Apply damage self.defender.db.hp = 10 - tb_equip.apply_damage(self.defender, 3) + tb_equip.COMBAT_RULES.apply_damage(self.defender, 3) self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_equip.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10) + tb_equip.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True - tb_equip.combat_cleanup(self.attacker) + tb_equip.COMBAT_RULES.combat_cleanup(self.attacker) self.assertFalse(self.attacker.db.combat_attribute) # Is in combat - self.assertFalse(tb_equip.is_in_combat(self.attacker)) + self.assertFalse(tb_equip.COMBAT_RULES.is_in_combat(self.attacker)) # Set up turn handler script for further tests self.attacker.location.scripts.add(tb_equip.TBEquipTurnHandler) - self.turnhandler = self.attacker.db.combat_TurnHandler - self.assertTrue(self.attacker.db.combat_TurnHandler) + self.turnhandler = self.attacker.db.combat_turnHandler + self.assertTrue(self.attacker.db.combat_turnHandler) # Set the turn handler's interval very high to keep it from repeating during tests. self.turnhandler.interval = 10000 # Force turn order self.turnhandler.db.fighters = [self.attacker, self.defender] self.turnhandler.db.turn = 0 # Test is turn - self.assertTrue(tb_equip.is_turn(self.attacker)) + self.assertTrue(tb_equip.COMBAT_RULES.is_turn(self.attacker)) # Spend actions self.attacker.db.Combat_ActionsLeft = 1 - tb_equip.spend_action(self.attacker, 1, action_name="Test") + tb_equip.COMBAT_RULES.spend_action(self.attacker, 1, action_name="Test") self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0) self.assertTrue(self.attacker.db.Combat_LastAction == "Test") # Initialize for combat @@ -345,47 +346,49 @@ # Test combat functions in tb_range too.
        [docs] def test_tbrangefunc(self): # Initiative roll - initiative = tb_range.roll_init(self.attacker) + initiative = tb_range.COMBAT_RULES.roll_init(self.attacker) self.assertTrue(initiative >= 0 and initiative <= 1000) # Attack roll - attack_roll = tb_range.get_attack(self.attacker, self.defender, "test") + attack_roll = tb_range.COMBAT_RULES.get_attack(self.attacker, self.defender, + attack_type="test") self.assertTrue(attack_roll >= 0 and attack_roll <= 100) # Defense roll - defense_roll = tb_range.get_defense(self.attacker, self.defender, "test") + defense_roll = tb_range.COMBAT_RULES.get_defense(self.attacker, self.defender, + attack_type="test") self.assertTrue(defense_roll == 50) # Damage roll - damage_roll = tb_range.get_damage(self.attacker, self.defender) + damage_roll = tb_range.COMBAT_RULES.get_damage(self.attacker, self.defender) self.assertTrue(damage_roll >= 15 and damage_roll <= 25) # Apply damage self.defender.db.hp = 10 - tb_range.apply_damage(self.defender, 3) + tb_range.COMBAT_RULES.apply_damage(self.defender, 3) self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_range.resolve_attack( - self.attacker, self.defender, "test", attack_value=20, defense_value=10 + tb_range.COMBAT_RULES.resolve_attack( + self.attacker, self.defender, attack_type="test", attack_value=20, defense_value=10 ) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True - tb_range.combat_cleanup(self.attacker) + tb_range.COMBAT_RULES.combat_cleanup(self.attacker) self.assertFalse(self.attacker.db.combat_attribute) # Is in combat - self.assertFalse(tb_range.is_in_combat(self.attacker)) + self.assertFalse(tb_range.COMBAT_RULES.is_in_combat(self.attacker)) # Set up turn handler script for further tests self.attacker.location.scripts.add(tb_range.TBRangeTurnHandler) - self.turnhandler = self.attacker.db.combat_TurnHandler - self.assertTrue(self.attacker.db.combat_TurnHandler) + self.turnhandler = self.attacker.db.combat_turnHandler + self.assertTrue(self.attacker.db.combat_turnHandler) # Set the turn handler's interval very high to keep it from repeating during tests. self.turnhandler.interval = 10000 # Force turn order self.turnhandler.db.fighters = [self.attacker, self.defender] self.turnhandler.db.turn = 0 # Test is turn - self.assertTrue(tb_range.is_turn(self.attacker)) + self.assertTrue(tb_range.COMBAT_RULES.is_turn(self.attacker)) # Spend actions self.attacker.db.Combat_ActionsLeft = 1 - tb_range.spend_action(self.attacker, 1, action_name="Test") + tb_range.COMBAT_RULES.spend_action(self.attacker, 1, action_name="Test") self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0) self.assertTrue(self.attacker.db.Combat_LastAction == "Test") # Initialize for combat @@ -403,7 +406,7 @@ # Start turn self.defender.db.Combat_ActionsLeft = 0 self.turnhandler.start_turn(self.defender) - self.assertTrue(self.defender.db.Combat_ActionsLeft == 2) + self.assertEqual(self.defender.db.Combat_ActionsLeft, 2) # Next turn self.turnhandler.db.fighters = [self.attacker, self.defender] self.turnhandler.db.turn = 0 @@ -422,13 +425,13 @@ self.assertTrue(self.turnhandler.db.turn == 1) self.assertTrue(self.turnhandler.db.fighters == [self.joiner, self.attacker, self.defender]) # Now, test for approach/withdraw functions - self.assertTrue(tb_range.get_range(self.attacker, self.defender) == 1) + self.assertTrue(tb_range.COMBAT_RULES.get_range(self.attacker, self.defender) == 1) # Approach - tb_range.approach(self.attacker, self.defender) - self.assertTrue(tb_range.get_range(self.attacker, self.defender) == 0) + tb_range.COMBAT_RULES.approach(self.attacker, self.defender) + self.assertTrue(tb_range.COMBAT_RULES.get_range(self.attacker, self.defender) == 0) # Withdraw - tb_range.withdraw(self.attacker, self.defender) - self.assertTrue(tb_range.get_range(self.attacker, self.defender) == 1)
        + tb_range.COMBAT_RULES.withdraw(self.attacker, self.defender) + self.assertTrue(tb_range.COMBAT_RULES.get_range(self.attacker, self.defender) == 1)
        [docs]class TestTurnBattleItemsFunc(BaseEvenniaTest): @@ -460,45 +463,46 @@ # Test functions in tb_items.
        [docs] def test_tbitemsfunc(self): # Initiative roll - initiative = tb_items.roll_init(self.attacker) + initiative = tb_items.COMBAT_RULES.roll_init(self.attacker) self.assertTrue(initiative >= 0 and initiative <= 1000) # Attack roll - attack_roll = tb_items.get_attack(self.attacker, self.defender) + attack_roll = tb_items.COMBAT_RULES.get_attack(self.attacker, self.defender) self.assertTrue(attack_roll >= 0 and attack_roll <= 100) # Defense roll - defense_roll = tb_items.get_defense(self.attacker, self.defender) + defense_roll = tb_items.COMBAT_RULES.get_defense(self.attacker, self.defender) self.assertTrue(defense_roll == 50) # Damage roll - damage_roll = tb_items.get_damage(self.attacker, self.defender) + damage_roll = tb_items.COMBAT_RULES.get_damage(self.attacker, self.defender) self.assertTrue(damage_roll >= 15 and damage_roll <= 25) # Apply damage self.defender.db.hp = 10 - tb_items.apply_damage(self.defender, 3) + tb_items.COMBAT_RULES.apply_damage(self.defender, 3) self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_items.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10) + tb_items.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20, + defense_value=10) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True - tb_items.combat_cleanup(self.attacker) + tb_items.COMBAT_RULES.combat_cleanup(self.attacker) self.assertFalse(self.attacker.db.combat_attribute) # Is in combat - self.assertFalse(tb_items.is_in_combat(self.attacker)) + self.assertFalse(tb_items.COMBAT_RULES.is_in_combat(self.attacker)) # Set up turn handler script for further tests self.attacker.location.scripts.add(tb_items.TBItemsTurnHandler) - self.turnhandler = self.attacker.db.combat_TurnHandler - self.assertTrue(self.attacker.db.combat_TurnHandler) + self.turnhandler = self.attacker.db.combat_turnHandler + self.assertTrue(self.attacker.db.combat_turnHandler) # Set the turn handler's interval very high to keep it from repeating during tests. self.turnhandler.interval = 10000 # Force turn order self.turnhandler.db.fighters = [self.attacker, self.defender] self.turnhandler.db.turn = 0 # Test is turn - self.assertTrue(tb_items.is_turn(self.attacker)) + self.assertTrue(tb_items.COMBAT_RULES.is_turn(self.attacker)) # Spend actions self.attacker.db.Combat_ActionsLeft = 1 - tb_items.spend_action(self.attacker, 1, action_name="Test") + tb_items.COMBAT_RULES.spend_action(self.attacker, 1, action_name="Test") self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0) self.assertTrue(self.attacker.db.Combat_LastAction == "Test") # Initialize for combat @@ -529,29 +533,29 @@ self.assertTrue(self.turnhandler.db.fighters == [self.joiner, self.attacker, self.defender]) # Now time to test item stuff. # Spend item use - tb_items.spend_item_use(self.test_healpotion, self.user) + tb_items.COMBAT_RULES.spend_item_use(self.test_healpotion, self.user) self.assertTrue(self.test_healpotion.db.item_uses == 2) # Use item self.user.db.hp = 2 - tb_items.use_item(self.user, self.test_healpotion, self.user) + tb_items.COMBAT_RULES.use_item(self.user, self.test_healpotion, self.user) self.assertTrue(self.user.db.hp > 2) # Add contition - tb_items.add_condition(self.user, self.user, "Test", 5) + tb_items.COMBAT_RULES.add_condition(self.user, self.user, "Test", 5) self.assertTrue(self.user.db.conditions == {"Test": [5, self.user]}) # Condition tickdown - tb_items.condition_tickdown(self.user, self.user) - self.assertTrue(self.user.db.conditions == {"Test": [4, self.user]}) + tb_items.COMBAT_RULES.condition_tickdown(self.user, self.user) + self.assertEqual(self.user.db.conditions, {"Test": [4, self.user]}) # Test item functions now! # Item heal self.user.db.hp = 2 - tb_items.itemfunc_heal(self.test_healpotion, self.user, self.user) + tb_items.COMBAT_RULES.itemfunc_heal(self.test_healpotion, self.user, self.user) # Item add condition self.user.db.conditions = {} - tb_items.itemfunc_add_condition(self.test_healpotion, self.user, self.user) + tb_items.COMBAT_RULES.itemfunc_add_condition(self.test_healpotion, self.user, self.user) self.assertTrue(self.user.db.conditions == {"Regeneration": [5, self.user]}) # Item cure condition self.user.db.conditions = {"Poisoned": [5, self.user]} - tb_items.itemfunc_cure_condition(self.test_healpotion, self.user, self.user) + tb_items.COMBAT_RULES.itemfunc_cure_condition(self.test_healpotion, self.user, self.user) self.assertTrue(self.user.db.conditions == {})
        @@ -578,45 +582,46 @@ # Test combat functions in tb_magic.
        [docs] def test_tbbasicfunc(self): # Initiative roll - initiative = tb_magic.roll_init(self.attacker) + initiative = tb_magic.COMBAT_RULES.roll_init(self.attacker) self.assertTrue(initiative >= 0 and initiative <= 1000) # Attack roll - attack_roll = tb_magic.get_attack(self.attacker, self.defender) + attack_roll = tb_magic.COMBAT_RULES.get_attack(self.attacker, self.defender) self.assertTrue(attack_roll >= 0 and attack_roll <= 100) # Defense roll - defense_roll = tb_magic.get_defense(self.attacker, self.defender) + defense_roll = tb_magic.COMBAT_RULES.get_defense(self.attacker, self.defender) self.assertTrue(defense_roll == 50) # Damage roll - damage_roll = tb_magic.get_damage(self.attacker, self.defender) + damage_roll = tb_magic.COMBAT_RULES.get_damage(self.attacker, self.defender) self.assertTrue(damage_roll >= 15 and damage_roll <= 25) # Apply damage self.defender.db.hp = 10 - tb_magic.apply_damage(self.defender, 3) + tb_magic.COMBAT_RULES.apply_damage(self.defender, 3) self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_magic.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10) + tb_magic.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20, + defense_value=10) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True - tb_magic.combat_cleanup(self.attacker) + tb_magic.COMBAT_RULES.combat_cleanup(self.attacker) self.assertFalse(self.attacker.db.combat_attribute) # Is in combat - self.assertFalse(tb_magic.is_in_combat(self.attacker)) + self.assertFalse(tb_magic.COMBAT_RULES.is_in_combat(self.attacker)) # Set up turn handler script for further tests self.attacker.location.scripts.add(tb_magic.TBMagicTurnHandler) - self.turnhandler = self.attacker.db.combat_TurnHandler - self.assertTrue(self.attacker.db.combat_TurnHandler) + self.turnhandler = self.attacker.db.combat_turnHandler + self.assertTrue(self.attacker.db.combat_turnHandler) # Set the turn handler's interval very high to keep it from repeating during tests. self.turnhandler.interval = 10000 # Force turn order self.turnhandler.db.fighters = [self.attacker, self.defender] self.turnhandler.db.turn = 0 # Test is turn - self.assertTrue(tb_magic.is_turn(self.attacker)) + self.assertTrue(tb_magic.COMBAT_RULES.is_turn(self.attacker)) # Spend actions self.attacker.db.Combat_ActionsLeft = 1 - tb_magic.spend_action(self.attacker, 1, action_name="Test") + tb_magic.COMBAT_RULES.spend_action(self.attacker, 1, action_name="Test") self.assertTrue(self.attacker.db.Combat_ActionsLeft == 0) self.assertTrue(self.attacker.db.Combat_LastAction == "Test") # Initialize for combat @@ -679,7 +684,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/extended_room/extended_room.html b/docs/1.0-dev/_modules/evennia/contrib/grid/extended_room/extended_room.html index 9556dc8d9e..371006af37 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/extended_room/extended_room.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/extended_room/extended_room.html @@ -669,7 +669,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/extended_room/tests.html b/docs/1.0-dev/_modules/evennia/contrib/grid/extended_room/tests.html index 775f6ffb77..d2d42e8286 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/extended_room/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/extended_room/tests.html @@ -179,7 +179,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/simpledoor/simpledoor.html b/docs/1.0-dev/_modules/evennia/contrib/grid/simpledoor/simpledoor.html index 9dbb5a5cf3..1f8c85f9df 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/simpledoor/simpledoor.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/simpledoor/simpledoor.html @@ -270,7 +270,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/simpledoor/tests.html b/docs/1.0-dev/_modules/evennia/contrib/grid/simpledoor/tests.html index 708692d61e..15a2593b78 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/simpledoor/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/simpledoor/tests.html @@ -105,7 +105,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/slow_exit/slow_exit.html b/docs/1.0-dev/_modules/evennia/contrib/grid/slow_exit/slow_exit.html index 90d5e62a0c..fd3eac76ac 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/slow_exit/slow_exit.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/slow_exit/slow_exit.html @@ -250,7 +250,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/slow_exit/tests.html b/docs/1.0-dev/_modules/evennia/contrib/grid/slow_exit/tests.html index 57420e9c42..9f80d4aed6 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/slow_exit/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/slow_exit/tests.html @@ -103,7 +103,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/wilderness/tests.html b/docs/1.0-dev/_modules/evennia/contrib/grid/wilderness/tests.html index dd78d33059..6d1c62b8a0 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/wilderness/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/wilderness/tests.html @@ -213,7 +213,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/wilderness/wilderness.html b/docs/1.0-dev/_modules/evennia/contrib/grid/wilderness/wilderness.html index 7b66703dd2..4f8f4cabd4 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/wilderness/wilderness.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/wilderness/wilderness.html @@ -850,7 +850,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/commands.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/commands.html index d28abed562..5feed4371e 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/commands.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/commands.html @@ -550,7 +550,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/example.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/example.html index c0b0e0aa17..b96521fbf3 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/example.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/example.html @@ -367,7 +367,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/launchcmd.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/launchcmd.html index 6a97070dc6..74e945dda7 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/launchcmd.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/launchcmd.html @@ -500,7 +500,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/tests.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/tests.html index 9e0e48d7fd..6f4a037755 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/tests.html @@ -1374,7 +1374,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/utils.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/utils.html index 6a55d55a13..02db2ed08d 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/utils.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/utils.html @@ -131,7 +131,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xymap.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xymap.html index dd18e5907e..596806bb77 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xymap.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xymap.html @@ -1021,7 +1021,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xymap_legend.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xymap_legend.html index cffe1ab4a7..8098b04e87 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xymap_legend.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xymap_legend.html @@ -1371,7 +1371,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xyzgrid.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xyzgrid.html index 85e00b5d74..5f6d9edd35 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xyzgrid.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xyzgrid.html @@ -384,7 +384,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xyzroom.html b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xyzroom.html index f9d82f1983..7c01c33810 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xyzroom.html +++ b/docs/1.0-dev/_modules/evennia/contrib/grid/xyzgrid/xyzroom.html @@ -657,7 +657,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/dice/dice.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/dice/dice.html index bc8ba58732..4ebfca7d4a 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/dice/dice.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/dice/dice.html @@ -366,7 +366,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/dice/tests.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/dice/tests.html index 77e0f94ea9..89ff39f5e0 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/dice/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/dice/tests.html @@ -99,7 +99,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/health_bar/health_bar.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/health_bar/health_bar.html index a58b88e6c6..3cb114e142 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/health_bar/health_bar.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/health_bar/health_bar.html @@ -211,7 +211,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/health_bar/tests.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/health_bar/tests.html index 53e4c25ad2..a55724c6c9 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/health_bar/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/health_bar/tests.html @@ -122,7 +122,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/rplanguage.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/rplanguage.html index 029966a3ac..bc9f484612 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/rplanguage.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/rplanguage.html @@ -661,7 +661,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/rpsystem.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/rpsystem.html index 1d18c0021a..cdc289a63e 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/rpsystem.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/rpsystem.html @@ -1838,7 +1838,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/tests.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/tests.html index 0a165e3f65..710869d579 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/rpsystem/tests.html @@ -397,7 +397,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/traits/tests.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/traits/tests.html index 15d78eb360..5f96c77e55 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/traits/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/traits/tests.html @@ -1024,7 +1024,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/rpg/traits/traits.html b/docs/1.0-dev/_modules/evennia/contrib/rpg/traits/traits.html index 73678079ef..49bef4400a 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/rpg/traits/traits.html +++ b/docs/1.0-dev/_modules/evennia/contrib/rpg/traits/traits.html @@ -1711,7 +1711,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/bodyfunctions/bodyfunctions.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/bodyfunctions/bodyfunctions.html index ae6a945d56..1285f8167e 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/bodyfunctions/bodyfunctions.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/bodyfunctions/bodyfunctions.html @@ -147,7 +147,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/bodyfunctions/tests.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/bodyfunctions/tests.html index f0efe269f2..2cfd2ad573 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/bodyfunctions/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/bodyfunctions/tests.html @@ -149,7 +149,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/mirror/mirror.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/mirror/mirror.html index 43ae51d8a4..81a924e1ba 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/mirror/mirror.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/mirror/mirror.html @@ -138,7 +138,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/red_button/red_button.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/red_button/red_button.html index 1189ef072d..9a4381b08f 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/red_button/red_button.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/red_button/red_button.html @@ -656,7 +656,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/talking_npc/talking_npc.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/talking_npc/talking_npc.html index f2f9546f4e..b93d7bf192 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/talking_npc/talking_npc.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/talking_npc/talking_npc.html @@ -215,7 +215,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/talking_npc/tests.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/talking_npc/tests.html index 1f66f43a03..9f20d7df18 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/talking_npc/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/talking_npc/tests.html @@ -90,7 +90,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/intro_menu.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/intro_menu.html index e7e884c0ee..cf0b3fbcc0 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/intro_menu.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/intro_menu.html @@ -100,7 +100,8 @@
        [docs] def at_cmdset_creation(self): from evennia import default_cmds - self.add(default_cmds.CmdHelp())
        + self.add(default_cmds.CmdHelp()) + self.add(default_cmds.CmdChannel())
        [docs]def goto_command_demo_help(caller, raw_string, **kwargs): @@ -168,11 +169,11 @@ of their name (like '|ylook cozy|n') or use the number in the list to pick the one you want, like this: - |ylook 2-small|n + |ylook small-2|n As long as what you write is uniquely identifying you can be lazy and not write the full name of the thing you want to look at. Try '|ylook bo|n', - '|yl co|n' or '|yl 1-sm|n'! + '|yl co|n' or '|yl sm-1|n'! ... Oh, and if you see database-ids like (#1245) by the name of objects, it's because you are playing with Builder-privileges or higher. Regular @@ -401,7 +402,7 @@ experience altogether. MUDs are |wdifferent|n from Interactive Fiction (IF) in that they are multiplayer -and usually has a consistent game world with many stories and protagonists +and usually have a consistent game world with many stories and protagonists acting at the same time. Like there are many different styles of graphical MMOs, there are |wmany @@ -543,8 +544,8 @@ the help command. For your game you could add help about your game, lore, rules etc as well. -At the moment you only have |whelp|n and some |wChannel Names|n (the '<menu commands>' -is just a placeholder to indicate you are using this menu). +At the moment you probably only have |whelp|n and a |wchannel|n command +(the '<menu commands>' is just a placeholder to indicate you are using this menu). We'll add more commands as we get to them in this tutorial - but we'll only cover a small handful. Once you exit you'll find a lot more! Now let's try @@ -573,9 +574,7 @@ channel can see it. If someone else is on your server, you may get a reply! Evennia can link its in-game channels to external chat networks. This allows -you to talk with people not actually logged into the game. For -example, the online Evennia-demo links its |wpublic|n channel to the #evennia -IRC support channel. +you to talk with people not actually logged into the game. ## OPTIONS @@ -622,19 +621,13 @@ |g** Paging people **|n Halfway between talking on a |wChannel|n and chatting in your current location -with |wsay|n and |wpose|n, you can also |wpage|n people. This is like a private -message only they can see. +with |wsay|n and |wpose|n, you can also send private messages with |wpage|n: - |ypage <name> = Hello there! - page <name1>, <name2> = Hello both of you!|n + |ypage <name> Hello there!|n -If you are alone on the server, put your own name as |w<name>|n to test it and -page yourself. Write just |ypage|n to see your latest pages. This will also show -you if anyone paged you while you were offline. - -(By the way - depending on which games you are used to, you may think that the -use of |y=|n above is strange. This is a MUSH/MUX-style of syntax. For your own -game you can change the |wpose|n command to work however you prefer). +Put your own name as |y<name>|n to page yourself as a test. Write just |ypage|n +to see your latest pages. This will also show you if anyone paged you while you +were offline. ## OPTIONS @@ -719,8 +712,8 @@ Evennia. With this you should be able to continue exploring and also find help if you get stuck! -Write |ynext|n to end this wizard and continue to the tutorial-world quest! -If you want there is also some |wextra|n info for where to go beyond that. +Write |ynext|n to end this wizard. If you want there is also some |wextra|n info +for where to go beyond that. ## OPTIONS @@ -748,12 +741,12 @@ - The tutorial-world may or may not be your cup of tea, but it does show off several |wuseful tools|n of Evennia. You may want to check out how it works: - |yhttps://www.evennia.com/docs/latest/Tutorial-World-Introduction|n + |yhttps://www.evennia.com/docs/latest/Howto/Starting/Part1/Tutorial-World|n - You can then continue looking through the |wTutorials|n and pick one that fits your level of understanding. - |yhttps://www.evennia.com/docs/latest/Tutorials|n + |yhttps://www.evennia.com/docs/latest/Howto/Howto-Overview|n - Make sure to |wjoin our forum|n and connect to our |wsupport chat|n! The Evennia community is very active and friendly and no question is too simple. @@ -794,7 +787,10 @@ self.caller.cmdset.remove(DemoCommandSetRoom) self.caller.cmdset.remove(DemoCommandSetComms) _maintain_demo_room(self.caller, delete=True) - super().close_menu()
        + super().close_menu() + if self.caller.account: + self.caller.msg("Restoring permissions ...") + self.caller.account.execute_cmd("unquell")
        [docs] def options_formatter(self, optionslist): @@ -858,7 +854,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/mob.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/mob.html index 5f75e94945..7fdb0be66d 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/mob.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/mob.html @@ -512,7 +512,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/objects.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/objects.html index 2d8934d71b..fe4338db17 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/objects.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/objects.html @@ -1260,7 +1260,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/rooms.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/rooms.html index 2de2f53b3f..dbfa23b51a 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/rooms.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/rooms.html @@ -56,7 +56,7 @@ import random from evennia import TICKER_HANDLER -from evennia import CmdSet, Command, DefaultRoom +from evennia import CmdSet, Command, DefaultRoom, DefaultExit from evennia import utils, create_object, search_object from evennia import syscmdkeys, default_cmds from .objects import LightSource @@ -348,6 +348,18 @@ self.db.details = {detailkey.lower(): description} +
        [docs]class TutorialStartExit(DefaultExit): + """ + This is like a normal exit except it makes the `intro` command available + on itself. We put it on the exit in order to provide this command to the + Limbo room without modifying Limbo itself - deleting the tutorial exit + will also clean up the intro command. + + """ +
        [docs] def at_object_creation(self): + self.cmdset.add(CmdSetEvenniaIntro, persistent=True)
        + + # ------------------------------------------------------------- # # Weather room - room with a ticker @@ -443,8 +455,8 @@ from .intro_menu import init_menu # quell also superusers if self.caller.account: + self.caller.msg("Auto-quelling permissions while in intro ...") self.caller.account.execute_cmd("quell") - self.caller.msg("(Auto-quelling)") init_menu(self.caller) @@ -472,8 +484,7 @@ "The first room of the tutorial. " "This assigns the health Attribute to " "the account." - ) - self.cmdset.add(CmdSetEvenniaIntro, persistent=True) + )
        [docs] def at_object_receive(self, character, source_location): """ @@ -1244,7 +1255,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/tests.html b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/tests.html index 319a752e76..bcbb18a8f6 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/tutorials/tutorial_world/tests.html @@ -267,7 +267,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/outputs.html b/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/outputs.html index 90eef76222..1a1a554779 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/outputs.html +++ b/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/outputs.html @@ -136,7 +136,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/server.html b/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/server.html index f547f56707..bf3414468f 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/server.html +++ b/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/server.html @@ -325,7 +325,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/tests.html b/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/tests.html index d549f4ec37..2316da220b 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/utils/auditing/tests.html @@ -208,7 +208,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/utils/fieldfill/fieldfill.html b/docs/1.0-dev/_modules/evennia/contrib/utils/fieldfill/fieldfill.html index 6f041922e1..637703259a 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/utils/fieldfill/fieldfill.html +++ b/docs/1.0-dev/_modules/evennia/contrib/utils/fieldfill/fieldfill.html @@ -792,7 +792,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/utils/random_string_generator/random_string_generator.html b/docs/1.0-dev/_modules/evennia/contrib/utils/random_string_generator/random_string_generator.html index 501ac1cbaa..7cf21ca101 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/utils/random_string_generator/random_string_generator.html +++ b/docs/1.0-dev/_modules/evennia/contrib/utils/random_string_generator/random_string_generator.html @@ -431,7 +431,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/utils/random_string_generator/tests.html b/docs/1.0-dev/_modules/evennia/contrib/utils/random_string_generator/tests.html index 240074e2e8..721b9fb327 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/utils/random_string_generator/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/utils/random_string_generator/tests.html @@ -101,7 +101,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/utils/tree_select/tests.html b/docs/1.0-dev/_modules/evennia/contrib/utils/tree_select/tests.html index f15b2bd68e..b11819adab 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/utils/tree_select/tests.html +++ b/docs/1.0-dev/_modules/evennia/contrib/utils/tree_select/tests.html @@ -138,7 +138,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/contrib/utils/tree_select/tree_select.html b/docs/1.0-dev/_modules/evennia/contrib/utils/tree_select/tree_select.html index ca1c8c4968..7f5a0915c2 100644 --- a/docs/1.0-dev/_modules/evennia/contrib/utils/tree_select/tree_select.html +++ b/docs/1.0-dev/_modules/evennia/contrib/utils/tree_select/tree_select.html @@ -654,7 +654,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/help/filehelp.html b/docs/1.0-dev/_modules/evennia/help/filehelp.html index 11110b28ee..780cb82b76 100644 --- a/docs/1.0-dev/_modules/evennia/help/filehelp.html +++ b/docs/1.0-dev/_modules/evennia/help/filehelp.html @@ -335,7 +335,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/help/manager.html b/docs/1.0-dev/_modules/evennia/help/manager.html index 1adf4584e6..d639c09e81 100644 --- a/docs/1.0-dev/_modules/evennia/help/manager.html +++ b/docs/1.0-dev/_modules/evennia/help/manager.html @@ -276,7 +276,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/help/models.html b/docs/1.0-dev/_modules/evennia/help/models.html index 6b0b09efa7..808d3e22a3 100644 --- a/docs/1.0-dev/_modules/evennia/help/models.html +++ b/docs/1.0-dev/_modules/evennia/help/models.html @@ -384,7 +384,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/help/utils.html b/docs/1.0-dev/_modules/evennia/help/utils.html index 08d618491b..72498d1c62 100644 --- a/docs/1.0-dev/_modules/evennia/help/utils.html +++ b/docs/1.0-dev/_modules/evennia/help/utils.html @@ -308,7 +308,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/locks/lockfuncs.html b/docs/1.0-dev/_modules/evennia/locks/lockfuncs.html index 932d91b4fa..6229679f7e 100644 --- a/docs/1.0-dev/_modules/evennia/locks/lockfuncs.html +++ b/docs/1.0-dev/_modules/evennia/locks/lockfuncs.html @@ -706,7 +706,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/locks/lockhandler.html b/docs/1.0-dev/_modules/evennia/locks/lockhandler.html index 63ae75995c..805185af17 100644 --- a/docs/1.0-dev/_modules/evennia/locks/lockhandler.html +++ b/docs/1.0-dev/_modules/evennia/locks/lockhandler.html @@ -857,7 +857,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/objects/manager.html b/docs/1.0-dev/_modules/evennia/objects/manager.html index 108f9fbe12..41ddd307bf 100644 --- a/docs/1.0-dev/_modules/evennia/objects/manager.html +++ b/docs/1.0-dev/_modules/evennia/objects/manager.html @@ -810,7 +810,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/objects/models.html b/docs/1.0-dev/_modules/evennia/objects/models.html index cb76d0ea39..33255ce7d3 100644 --- a/docs/1.0-dev/_modules/evennia/objects/models.html +++ b/docs/1.0-dev/_modules/evennia/objects/models.html @@ -468,7 +468,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/objects/objects.html b/docs/1.0-dev/_modules/evennia/objects/objects.html index 2eaa3f7622..27e3f70b12 100644 --- a/docs/1.0-dev/_modules/evennia/objects/objects.html +++ b/docs/1.0-dev/_modules/evennia/objects/objects.html @@ -2938,7 +2938,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/prototypes/menus.html b/docs/1.0-dev/_modules/evennia/prototypes/menus.html index f19eaf0ddf..4bd645a71e 100644 --- a/docs/1.0-dev/_modules/evennia/prototypes/menus.html +++ b/docs/1.0-dev/_modules/evennia/prototypes/menus.html @@ -2834,7 +2834,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/prototypes/protfuncs.html b/docs/1.0-dev/_modules/evennia/prototypes/protfuncs.html index 3a85758cfb..b440bdbb55 100644 --- a/docs/1.0-dev/_modules/evennia/prototypes/protfuncs.html +++ b/docs/1.0-dev/_modules/evennia/prototypes/protfuncs.html @@ -150,7 +150,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/prototypes/prototypes.html b/docs/1.0-dev/_modules/evennia/prototypes/prototypes.html index 0f2c8ef974..b270f003a3 100644 --- a/docs/1.0-dev/_modules/evennia/prototypes/prototypes.html +++ b/docs/1.0-dev/_modules/evennia/prototypes/prototypes.html @@ -1212,7 +1212,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/prototypes/spawner.html b/docs/1.0-dev/_modules/evennia/prototypes/spawner.html index 72d08920f8..e691bac09e 100644 --- a/docs/1.0-dev/_modules/evennia/prototypes/spawner.html +++ b/docs/1.0-dev/_modules/evennia/prototypes/spawner.html @@ -1112,7 +1112,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/scripts/manager.html b/docs/1.0-dev/_modules/evennia/scripts/manager.html index 179bb19662..99d7223ac7 100644 --- a/docs/1.0-dev/_modules/evennia/scripts/manager.html +++ b/docs/1.0-dev/_modules/evennia/scripts/manager.html @@ -403,7 +403,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/scripts/models.html b/docs/1.0-dev/_modules/evennia/scripts/models.html index a56c16f42e..2df02d11ca 100644 --- a/docs/1.0-dev/_modules/evennia/scripts/models.html +++ b/docs/1.0-dev/_modules/evennia/scripts/models.html @@ -257,7 +257,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/scripts/monitorhandler.html b/docs/1.0-dev/_modules/evennia/scripts/monitorhandler.html index bed903070d..8055ad4561 100644 --- a/docs/1.0-dev/_modules/evennia/scripts/monitorhandler.html +++ b/docs/1.0-dev/_modules/evennia/scripts/monitorhandler.html @@ -301,7 +301,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/scripts/scripthandler.html b/docs/1.0-dev/_modules/evennia/scripts/scripthandler.html index 3bd5cd319f..89aa7ef658 100644 --- a/docs/1.0-dev/_modules/evennia/scripts/scripthandler.html +++ b/docs/1.0-dev/_modules/evennia/scripts/scripthandler.html @@ -231,7 +231,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/scripts/scripts.html b/docs/1.0-dev/_modules/evennia/scripts/scripts.html index 0034d09d00..4eb603b341 100644 --- a/docs/1.0-dev/_modules/evennia/scripts/scripts.html +++ b/docs/1.0-dev/_modules/evennia/scripts/scripts.html @@ -886,7 +886,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/scripts/taskhandler.html b/docs/1.0-dev/_modules/evennia/scripts/taskhandler.html index f61fbb68b0..ebc82ef9f0 100644 --- a/docs/1.0-dev/_modules/evennia/scripts/taskhandler.html +++ b/docs/1.0-dev/_modules/evennia/scripts/taskhandler.html @@ -686,7 +686,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/scripts/tickerhandler.html b/docs/1.0-dev/_modules/evennia/scripts/tickerhandler.html index f4eda6f134..90387eafc0 100644 --- a/docs/1.0-dev/_modules/evennia/scripts/tickerhandler.html +++ b/docs/1.0-dev/_modules/evennia/scripts/tickerhandler.html @@ -719,7 +719,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/amp_client.html b/docs/1.0-dev/_modules/evennia/server/amp_client.html index 13ab1b4070..7d7114ba44 100644 --- a/docs/1.0-dev/_modules/evennia/server/amp_client.html +++ b/docs/1.0-dev/_modules/evennia/server/amp_client.html @@ -328,7 +328,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/connection_wizard.html b/docs/1.0-dev/_modules/evennia/server/connection_wizard.html index 57bbbb0cb9..ee9e0f629d 100644 --- a/docs/1.0-dev/_modules/evennia/server/connection_wizard.html +++ b/docs/1.0-dev/_modules/evennia/server/connection_wizard.html @@ -597,7 +597,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/deprecations.html b/docs/1.0-dev/_modules/evennia/server/deprecations.html index cd2f41fecc..484730358b 100644 --- a/docs/1.0-dev/_modules/evennia/server/deprecations.html +++ b/docs/1.0-dev/_modules/evennia/server/deprecations.html @@ -239,7 +239,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/evennia_launcher.html b/docs/1.0-dev/_modules/evennia/server/evennia_launcher.html index d35d055a17..e0783b067f 100644 --- a/docs/1.0-dev/_modules/evennia/server/evennia_launcher.html +++ b/docs/1.0-dev/_modules/evennia/server/evennia_launcher.html @@ -135,10 +135,10 @@ SRESET = chr(19) # shutdown server in reset mode # requirements -PYTHON_MIN = "3.7" +PYTHON_MIN = "3.9" TWISTED_MIN = "20.3.0" -DJANGO_MIN = "3.2.0" -DJANGO_LT = "4.0" +DJANGO_MIN = "4.0.2" +DJANGO_LT = "4.1" try: sys.path[1] = EVENNIA_ROOT @@ -248,7 +248,7 @@ ) ERROR_INITSETTINGS = """ - ERROR: 'evennia --initsettings' must be called from the root of +u ERROR: 'evennia --initsettings' must be called from the root of your game directory, since it tries to (re)create the new settings.py file in a subfolder server/conf/. """ @@ -419,7 +419,7 @@ ERROR_DJANGO_MIN = """ ERROR: Django {dversion} found. Evennia requires at least version {django_min} (but - no higher than {django_lt}). + below version {django_lt}). If you are using a virtualenv, use the command `pip install --upgrade -e evennia` where `evennia` is the folder to where you cloned the Evennia library. If not @@ -2367,6 +2367,7 @@ global TEST_MODE TEST_MODE = True + # init the db/game dir, if needed init_game_directory(CURRENT_DIR, check_db=check_db, need_gamedir=need_gamedir) if option == "migrate": @@ -2378,12 +2379,12 @@ if run_custom_commands(option, *unknown_args): # run any custom commands sys.exit() - - # pass on to the core django manager - re-parse the entire input line - # but keep 'evennia' as the name instead of django-admin. This is - # an exit condition. - sys.argv[0] = re.sub(r"(-script\.pyw?|\.exe)?$", "", sys.argv[0]) - sys.exit(execute_from_command_line(sys.argv)) + else: + # pass on to the core django manager - re-parse the entire input line + # but keep 'evennia' as the name instead of django-admin. This is + # an exit condition. + sys.argv[0] = re.sub(r"(-script\.pyw?|\.exe)?$", "", sys.argv[0]) + sys.exit(execute_from_command_line(sys.argv)) elif not args.tail_log: # no input; print evennia info (don't pring if we're tailing log) @@ -2430,7 +2431,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/game_index_client/client.html b/docs/1.0-dev/_modules/evennia/server/game_index_client/client.html index 6c18e350c2..82d3217a74 100644 --- a/docs/1.0-dev/_modules/evennia/server/game_index_client/client.html +++ b/docs/1.0-dev/_modules/evennia/server/game_index_client/client.html @@ -255,7 +255,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/game_index_client/service.html b/docs/1.0-dev/_modules/evennia/server/game_index_client/service.html index 4a5bc2975e..72b2bfb65e 100644 --- a/docs/1.0-dev/_modules/evennia/server/game_index_client/service.html +++ b/docs/1.0-dev/_modules/evennia/server/game_index_client/service.html @@ -134,7 +134,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/initial_setup.html b/docs/1.0-dev/_modules/evennia/server/initial_setup.html index 99b4234e24..08d66e7458 100644 --- a/docs/1.0-dev/_modules/evennia/server/initial_setup.html +++ b/docs/1.0-dev/_modules/evennia/server/initial_setup.html @@ -72,7 +72,10 @@ LIMBO_DESC = _(""" Welcome to your new |wEvennia|n-based game! Visit https://www.evennia.com if you need help, want to contribute, report issues or just join the community. -As Account #1 you can create a demo/tutorial area with '|wbatchcommand tutorial_world.build|n'. + +As a privileged user, write |wbatchcommand tutorial_world.build|n to build +tutorial content. Once built, try |wintro|n for starting help and |wtutorial|n to +play the demo game. """) @@ -299,7 +302,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/inputfuncs.html b/docs/1.0-dev/_modules/evennia/server/inputfuncs.html index 78cd507c5e..430d9a10ed 100644 --- a/docs/1.0-dev/_modules/evennia/server/inputfuncs.html +++ b/docs/1.0-dev/_modules/evennia/server/inputfuncs.html @@ -730,7 +730,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/manager.html b/docs/1.0-dev/_modules/evennia/server/manager.html index 0da405361a..3cd74fcba4 100644 --- a/docs/1.0-dev/_modules/evennia/server/manager.html +++ b/docs/1.0-dev/_modules/evennia/server/manager.html @@ -129,7 +129,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/models.html b/docs/1.0-dev/_modules/evennia/server/models.html index 391e8ef4f1..06732c7edd 100644 --- a/docs/1.0-dev/_modules/evennia/server/models.html +++ b/docs/1.0-dev/_modules/evennia/server/models.html @@ -209,7 +209,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/amp.html b/docs/1.0-dev/_modules/evennia/server/portal/amp.html index b8330cf3bf..b3975babed 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/amp.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/amp.html @@ -624,7 +624,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/amp_server.html b/docs/1.0-dev/_modules/evennia/server/portal/amp_server.html index 549f8fb73f..33cc8b5c8a 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/amp_server.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/amp_server.html @@ -561,7 +561,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/grapevine.html b/docs/1.0-dev/_modules/evennia/server/portal/grapevine.html index c473c19df6..5f216972f9 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/grapevine.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/grapevine.html @@ -435,7 +435,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/irc.html b/docs/1.0-dev/_modules/evennia/server/portal/irc.html index 083a29e861..a56cdc7cad 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/irc.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/irc.html @@ -554,7 +554,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/mccp.html b/docs/1.0-dev/_modules/evennia/server/portal/mccp.html index 57e55e0a40..cad30bc41b 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/mccp.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/mccp.html @@ -165,7 +165,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/mssp.html b/docs/1.0-dev/_modules/evennia/server/portal/mssp.html index bded14f3a9..280594f4e4 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/mssp.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/mssp.html @@ -210,7 +210,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/mxp.html b/docs/1.0-dev/_modules/evennia/server/portal/mxp.html index 93802b541d..ab7b4aa1ec 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/mxp.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/mxp.html @@ -167,7 +167,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/naws.html b/docs/1.0-dev/_modules/evennia/server/portal/naws.html index f7cb382cd5..d7093d5f5c 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/naws.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/naws.html @@ -160,7 +160,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/portal.html b/docs/1.0-dev/_modules/evennia/server/portal/portal.html index b6c5a1e7ff..4f6a368187 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/portal.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/portal.html @@ -521,7 +521,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/portalsessionhandler.html b/docs/1.0-dev/_modules/evennia/server/portal/portalsessionhandler.html index 3921d35f80..cf4c98d769 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/portalsessionhandler.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/portalsessionhandler.html @@ -563,7 +563,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/rss.html b/docs/1.0-dev/_modules/evennia/server/portal/rss.html index 5c6ab46fc9..9eb3087705 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/rss.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/rss.html @@ -240,7 +240,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/ssh.html b/docs/1.0-dev/_modules/evennia/server/portal/ssh.html index f1800d1a69..cc3d220dc1 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/ssh.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/ssh.html @@ -604,7 +604,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/ssl.html b/docs/1.0-dev/_modules/evennia/server/portal/ssl.html index 8b46735b19..12ec2da824 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/ssl.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/ssl.html @@ -196,7 +196,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/suppress_ga.html b/docs/1.0-dev/_modules/evennia/server/portal/suppress_ga.html index 2ef3988296..43c65841e7 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/suppress_ga.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/suppress_ga.html @@ -144,7 +144,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/telnet.html b/docs/1.0-dev/_modules/evennia/server/portal/telnet.html index 8c5bb88104..d2dc7dee45 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/telnet.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/telnet.html @@ -586,7 +586,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/telnet_oob.html b/docs/1.0-dev/_modules/evennia/server/portal/telnet_oob.html index e0331b0f74..6104bf5ea8 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/telnet_oob.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/telnet_oob.html @@ -523,7 +523,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/telnet_ssl.html b/docs/1.0-dev/_modules/evennia/server/portal/telnet_ssl.html index 6d4a1f98c4..b78e385744 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/telnet_ssl.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/telnet_ssl.html @@ -228,7 +228,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/tests.html b/docs/1.0-dev/_modules/evennia/server/portal/tests.html index 056b79e769..9c08f4a8e4 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/tests.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/tests.html @@ -397,7 +397,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/ttype.html b/docs/1.0-dev/_modules/evennia/server/portal/ttype.html index aa2d7a229f..5129e8d66e 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/ttype.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/ttype.html @@ -263,7 +263,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/webclient.html b/docs/1.0-dev/_modules/evennia/server/portal/webclient.html index fa56393ee3..cf4d8fc11a 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/webclient.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/webclient.html @@ -392,7 +392,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/portal/webclient_ajax.html b/docs/1.0-dev/_modules/evennia/server/portal/webclient_ajax.html index 607e3b7969..27aa29432f 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/webclient_ajax.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/webclient_ajax.html @@ -567,7 +567,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner.html b/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner.html index 8e5d5a4cde..cf5c5083c4 100644 --- a/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner.html +++ b/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner.html @@ -702,7 +702,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner_settings.html b/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner_settings.html index 2388d4c0e0..c370a3c701 100644 --- a/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner_settings.html +++ b/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner_settings.html @@ -413,7 +413,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/profiling/memplot.html b/docs/1.0-dev/_modules/evennia/server/profiling/memplot.html index 1c861b060e..8c3b0c6767 100644 --- a/docs/1.0-dev/_modules/evennia/server/profiling/memplot.html +++ b/docs/1.0-dev/_modules/evennia/server/profiling/memplot.html @@ -192,7 +192,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/profiling/test_queries.html b/docs/1.0-dev/_modules/evennia/server/profiling/test_queries.html index 566b1f3c88..81ea7cbe34 100644 --- a/docs/1.0-dev/_modules/evennia/server/profiling/test_queries.html +++ b/docs/1.0-dev/_modules/evennia/server/profiling/test_queries.html @@ -119,7 +119,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/profiling/tests.html b/docs/1.0-dev/_modules/evennia/server/profiling/tests.html index 5919a101f5..4ef5df60fd 100644 --- a/docs/1.0-dev/_modules/evennia/server/profiling/tests.html +++ b/docs/1.0-dev/_modules/evennia/server/profiling/tests.html @@ -235,7 +235,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/profiling/timetrace.html b/docs/1.0-dev/_modules/evennia/server/profiling/timetrace.html index 2302150ed8..af58c37864 100644 --- a/docs/1.0-dev/_modules/evennia/server/profiling/timetrace.html +++ b/docs/1.0-dev/_modules/evennia/server/profiling/timetrace.html @@ -116,7 +116,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/server.html b/docs/1.0-dev/_modules/evennia/server/server.html index 587ffb7e7d..18afd88c05 100644 --- a/docs/1.0-dev/_modules/evennia/server/server.html +++ b/docs/1.0-dev/_modules/evennia/server/server.html @@ -828,7 +828,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/serversession.html b/docs/1.0-dev/_modules/evennia/server/serversession.html index e4289d0c86..f9d35b2f42 100644 --- a/docs/1.0-dev/_modules/evennia/server/serversession.html +++ b/docs/1.0-dev/_modules/evennia/server/serversession.html @@ -513,7 +513,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/session.html b/docs/1.0-dev/_modules/evennia/server/session.html index 0af25e407c..526142f687 100644 --- a/docs/1.0-dev/_modules/evennia/server/session.html +++ b/docs/1.0-dev/_modules/evennia/server/session.html @@ -251,7 +251,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/sessionhandler.html b/docs/1.0-dev/_modules/evennia/server/sessionhandler.html index ad816c1406..b16acfecee 100644 --- a/docs/1.0-dev/_modules/evennia/server/sessionhandler.html +++ b/docs/1.0-dev/_modules/evennia/server/sessionhandler.html @@ -944,7 +944,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/throttle.html b/docs/1.0-dev/_modules/evennia/server/throttle.html index 8a6b0dbea2..e4fb0744cf 100644 --- a/docs/1.0-dev/_modules/evennia/server/throttle.html +++ b/docs/1.0-dev/_modules/evennia/server/throttle.html @@ -299,7 +299,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/validators.html b/docs/1.0-dev/_modules/evennia/server/validators.html index e0717dd709..00d21bf4ef 100644 --- a/docs/1.0-dev/_modules/evennia/server/validators.html +++ b/docs/1.0-dev/_modules/evennia/server/validators.html @@ -166,7 +166,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/server/webserver.html b/docs/1.0-dev/_modules/evennia/server/webserver.html index 3df4b77b2c..212b0a6bbb 100644 --- a/docs/1.0-dev/_modules/evennia/server/webserver.html +++ b/docs/1.0-dev/_modules/evennia/server/webserver.html @@ -376,7 +376,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/typeclasses/attributes.html b/docs/1.0-dev/_modules/evennia/typeclasses/attributes.html index 5e4155cfd2..acdcd56c9b 100644 --- a/docs/1.0-dev/_modules/evennia/typeclasses/attributes.html +++ b/docs/1.0-dev/_modules/evennia/typeclasses/attributes.html @@ -1759,7 +1759,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/typeclasses/managers.html b/docs/1.0-dev/_modules/evennia/typeclasses/managers.html index 213c2f7084..e5323a7bb9 100644 --- a/docs/1.0-dev/_modules/evennia/typeclasses/managers.html +++ b/docs/1.0-dev/_modules/evennia/typeclasses/managers.html @@ -935,7 +935,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/typeclasses/models.html b/docs/1.0-dev/_modules/evennia/typeclasses/models.html index ae8b119747..9f2acbf03a 100644 --- a/docs/1.0-dev/_modules/evennia/typeclasses/models.html +++ b/docs/1.0-dev/_modules/evennia/typeclasses/models.html @@ -1141,7 +1141,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/typeclasses/tags.html b/docs/1.0-dev/_modules/evennia/typeclasses/tags.html index 15f0271980..60b783b31d 100644 --- a/docs/1.0-dev/_modules/evennia/typeclasses/tags.html +++ b/docs/1.0-dev/_modules/evennia/typeclasses/tags.html @@ -659,7 +659,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/ansi.html b/docs/1.0-dev/_modules/evennia/utils/ansi.html index 18985bfbe9..01436a3c26 100644 --- a/docs/1.0-dev/_modules/evennia/utils/ansi.html +++ b/docs/1.0-dev/_modules/evennia/utils/ansi.html @@ -1579,7 +1579,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/batchprocessors.html b/docs/1.0-dev/_modules/evennia/utils/batchprocessors.html index b420662581..0d0180c6f9 100644 --- a/docs/1.0-dev/_modules/evennia/utils/batchprocessors.html +++ b/docs/1.0-dev/_modules/evennia/utils/batchprocessors.html @@ -515,7 +515,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/containers.html b/docs/1.0-dev/_modules/evennia/utils/containers.html index 797418eb7f..ff5cb2a551 100644 --- a/docs/1.0-dev/_modules/evennia/utils/containers.html +++ b/docs/1.0-dev/_modules/evennia/utils/containers.html @@ -322,7 +322,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/dbserialize.html b/docs/1.0-dev/_modules/evennia/utils/dbserialize.html index a5af0733d3..c7dfa684f3 100644 --- a/docs/1.0-dev/_modules/evennia/utils/dbserialize.html +++ b/docs/1.0-dev/_modules/evennia/utils/dbserialize.html @@ -836,7 +836,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/eveditor.html b/docs/1.0-dev/_modules/evennia/utils/eveditor.html index e4b8b3426f..e8f8698c3b 100644 --- a/docs/1.0-dev/_modules/evennia/utils/eveditor.html +++ b/docs/1.0-dev/_modules/evennia/utils/eveditor.html @@ -1219,7 +1219,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/evform.html b/docs/1.0-dev/_modules/evennia/utils/evform.html index 635ea72a66..9155ad1c43 100644 --- a/docs/1.0-dev/_modules/evennia/utils/evform.html +++ b/docs/1.0-dev/_modules/evennia/utils/evform.html @@ -544,7 +544,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/evmenu.html b/docs/1.0-dev/_modules/evennia/utils/evmenu.html index 8cf9101c50..50ff32c1da 100644 --- a/docs/1.0-dev/_modules/evennia/utils/evmenu.html +++ b/docs/1.0-dev/_modules/evennia/utils/evmenu.html @@ -2201,7 +2201,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/evmore.html b/docs/1.0-dev/_modules/evennia/utils/evmore.html index 8bf596b3ff..753e3c0738 100644 --- a/docs/1.0-dev/_modules/evennia/utils/evmore.html +++ b/docs/1.0-dev/_modules/evennia/utils/evmore.html @@ -629,7 +629,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/evtable.html b/docs/1.0-dev/_modules/evennia/utils/evtable.html index 87755d61ee..56b2006100 100644 --- a/docs/1.0-dev/_modules/evennia/utils/evtable.html +++ b/docs/1.0-dev/_modules/evennia/utils/evtable.html @@ -1825,7 +1825,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/funcparser.html b/docs/1.0-dev/_modules/evennia/utils/funcparser.html index 82a32a82bc..7b0df23f6d 100644 --- a/docs/1.0-dev/_modules/evennia/utils/funcparser.html +++ b/docs/1.0-dev/_modules/evennia/utils/funcparser.html @@ -1404,7 +1404,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/gametime.html b/docs/1.0-dev/_modules/evennia/utils/gametime.html index 4014341dfb..ed32c53405 100644 --- a/docs/1.0-dev/_modules/evennia/utils/gametime.html +++ b/docs/1.0-dev/_modules/evennia/utils/gametime.html @@ -361,7 +361,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/idmapper/manager.html b/docs/1.0-dev/_modules/evennia/utils/idmapper/manager.html index a828712ad7..a50e0e1111 100644 --- a/docs/1.0-dev/_modules/evennia/utils/idmapper/manager.html +++ b/docs/1.0-dev/_modules/evennia/utils/idmapper/manager.html @@ -109,7 +109,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/idmapper/models.html b/docs/1.0-dev/_modules/evennia/utils/idmapper/models.html index 407dd60a6e..3f59623592 100644 --- a/docs/1.0-dev/_modules/evennia/utils/idmapper/models.html +++ b/docs/1.0-dev/_modules/evennia/utils/idmapper/models.html @@ -753,7 +753,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/idmapper/tests.html b/docs/1.0-dev/_modules/evennia/utils/idmapper/tests.html index 5b34a7b3b0..4bc30fa831 100644 --- a/docs/1.0-dev/_modules/evennia/utils/idmapper/tests.html +++ b/docs/1.0-dev/_modules/evennia/utils/idmapper/tests.html @@ -154,7 +154,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/logger.html b/docs/1.0-dev/_modules/evennia/utils/logger.html index 7830039086..3e49b72e2d 100644 --- a/docs/1.0-dev/_modules/evennia/utils/logger.html +++ b/docs/1.0-dev/_modules/evennia/utils/logger.html @@ -672,7 +672,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/optionclasses.html b/docs/1.0-dev/_modules/evennia/utils/optionclasses.html index eb7eef4399..bca2f103af 100644 --- a/docs/1.0-dev/_modules/evennia/utils/optionclasses.html +++ b/docs/1.0-dev/_modules/evennia/utils/optionclasses.html @@ -399,7 +399,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/optionhandler.html b/docs/1.0-dev/_modules/evennia/utils/optionhandler.html index 8ccc4bca0d..8379eb7f70 100644 --- a/docs/1.0-dev/_modules/evennia/utils/optionhandler.html +++ b/docs/1.0-dev/_modules/evennia/utils/optionhandler.html @@ -260,7 +260,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/picklefield.html b/docs/1.0-dev/_modules/evennia/utils/picklefield.html index 2b7e6c6963..c88aa323d7 100644 --- a/docs/1.0-dev/_modules/evennia/utils/picklefield.html +++ b/docs/1.0-dev/_modules/evennia/utils/picklefield.html @@ -378,7 +378,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/search.html b/docs/1.0-dev/_modules/evennia/utils/search.html index f9c5a2f982..15357a12e1 100644 --- a/docs/1.0-dev/_modules/evennia/utils/search.html +++ b/docs/1.0-dev/_modules/evennia/utils/search.html @@ -440,7 +440,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/test_resources.html b/docs/1.0-dev/_modules/evennia/utils/test_resources.html index cca0ce77de..c60865413b 100644 --- a/docs/1.0-dev/_modules/evennia/utils/test_resources.html +++ b/docs/1.0-dev/_modules/evennia/utils/test_resources.html @@ -664,7 +664,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/text2html.html b/docs/1.0-dev/_modules/evennia/utils/text2html.html index 7a5131de08..3c938e8b94 100644 --- a/docs/1.0-dev/_modules/evennia/utils/text2html.html +++ b/docs/1.0-dev/_modules/evennia/utils/text2html.html @@ -461,7 +461,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/utils.html b/docs/1.0-dev/_modules/evennia/utils/utils.html index 9259dffabe..bd47fb9a21 100644 --- a/docs/1.0-dev/_modules/evennia/utils/utils.html +++ b/docs/1.0-dev/_modules/evennia/utils/utils.html @@ -2756,7 +2756,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/validatorfuncs.html b/docs/1.0-dev/_modules/evennia/utils/validatorfuncs.html index 994c0996f1..2b879da6f6 100644 --- a/docs/1.0-dev/_modules/evennia/utils/validatorfuncs.html +++ b/docs/1.0-dev/_modules/evennia/utils/validatorfuncs.html @@ -334,7 +334,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/conjugate.html b/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/conjugate.html index c810f62ad4..5ed7ad511d 100644 --- a/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/conjugate.html +++ b/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/conjugate.html @@ -463,7 +463,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/pronouns.html b/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/pronouns.html index 8d9b0545ca..a4ce798791 100644 --- a/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/pronouns.html +++ b/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/pronouns.html @@ -636,7 +636,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/tests.html b/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/tests.html index 0f11ae8760..7347010b33 100644 --- a/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/tests.html +++ b/docs/1.0-dev/_modules/evennia/utils/verb_conjugation/tests.html @@ -363,7 +363,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/accounts.html b/docs/1.0-dev/_modules/evennia/web/admin/accounts.html index 3b0dc112d2..68188641ac 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/accounts.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/accounts.html @@ -322,7 +322,7 @@ return str(dbserialize.pack_dbobj(obj)) serialized_string.help_text = ( - "Copy & paste this string into an Attribute's `value` field to store it there." + "Copy & paste this string into an Attribute's `value` field to store this account there." )
        [docs] def puppeted_objects(self, obj): @@ -489,7 +489,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/attributes.html b/docs/1.0-dev/_modules/evennia/web/admin/attributes.html index 2ba696a825..bffd320d4c 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/attributes.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/attributes.html @@ -283,7 +283,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/comms.html b/docs/1.0-dev/_modules/evennia/web/admin/comms.html index 5d43006d30..6e107a99f9 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/comms.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/comms.html @@ -173,7 +173,8 @@ return str(dbserialize.pack_dbobj(obj)) serialized_string.help_text = ( - "Copy & paste this string into an Attribute's `value` field to store it there." + "Copy & paste this string into an Attribute's `value` field to store " + "this message-object there." )
        [docs] def get_form(self, request, obj=None, **kwargs): @@ -290,7 +291,7 @@ return str(dbserialize.pack_dbobj(obj))
        serialized_string.help_text = ( - "Copy & paste this string into an Attribute's `value` field to store it there." + "Copy & paste this string into an Attribute's `value` field to store this channel there." )
        [docs] def get_form(self, request, obj=None, **kwargs): @@ -360,7 +361,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/frontpage.html b/docs/1.0-dev/_modules/evennia/web/admin/frontpage.html index 0a67319f82..91c0f36ae6 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/frontpage.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/frontpage.html @@ -103,7 +103,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/help.html b/docs/1.0-dev/_modules/evennia/web/admin/help.html index 574adec407..b99f7e47e3 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/help.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/help.html @@ -139,7 +139,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/objects.html b/docs/1.0-dev/_modules/evennia/web/admin/objects.html index b1f266c64f..4dda8d66dd 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/objects.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/objects.html @@ -48,10 +48,9 @@ # from django.conf import settings from django import forms -from django.urls import reverse +from django.urls import reverse, path from django.http import HttpResponseRedirect from django.conf import settings -from django.conf.urls import url from django.contrib import admin, messages from django.contrib.admin.utils import flatten_fieldsets from django.contrib.admin.widgets import ForeignKeyRawIdWidget @@ -271,7 +270,7 @@ return str(dbserialize.pack_dbobj(obj)) serialized_string.help_text = ( - "Copy & paste this string into an Attribute's `value` field to store it there." + "Copy & paste this string into an Attribute's `value` field to store this object there." )
        [docs] def get_fieldsets(self, request, obj=None): @@ -310,8 +309,8 @@
        [docs] def get_urls(self): urls = super().get_urls() custom_urls = [ - url( - r"^account-object-link/(?P<object_id>.+)/$", + path( + "account-object-link/<int:pk>", self.admin_site.admin_view(self.link_object_to_account), name="object-account-link" ) @@ -427,7 +426,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/scripts.html b/docs/1.0-dev/_modules/evennia/web/admin/scripts.html index 0d3d7a0d31..03e4723f75 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/scripts.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/scripts.html @@ -169,7 +169,7 @@ return str(dbserialize.pack_dbobj(obj))
        serialized_string.help_text = ( - "Copy & paste this string into an Attribute's `value` field to store it there." + "Copy & paste this string into an Attribute's `value` field to store this script there." ) @@ -233,7 +233,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/server.html b/docs/1.0-dev/_modules/evennia/web/admin/server.html index 1a32f836da..e47c0b1d77 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/server.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/server.html @@ -101,7 +101,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/tags.html b/docs/1.0-dev/_modules/evennia/web/admin/tags.html index 77384b5024..0b1097bb76 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/tags.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/tags.html @@ -310,7 +310,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/admin/utils.html b/docs/1.0-dev/_modules/evennia/web/admin/utils.html index d3d272e3f6..dd7d757e27 100644 --- a/docs/1.0-dev/_modules/evennia/web/admin/utils.html +++ b/docs/1.0-dev/_modules/evennia/web/admin/utils.html @@ -155,7 +155,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/api/filters.html b/docs/1.0-dev/_modules/evennia/web/api/filters.html index 57ef597a54..167766adea 100644 --- a/docs/1.0-dev/_modules/evennia/web/api/filters.html +++ b/docs/1.0-dev/_modules/evennia/web/api/filters.html @@ -223,7 +223,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/api/permissions.html b/docs/1.0-dev/_modules/evennia/web/api/permissions.html index a05e90bb32..85de05541f 100644 --- a/docs/1.0-dev/_modules/evennia/web/api/permissions.html +++ b/docs/1.0-dev/_modules/evennia/web/api/permissions.html @@ -169,7 +169,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/api/root.html b/docs/1.0-dev/_modules/evennia/web/api/root.html index ed45989d92..09088fb7ce 100644 --- a/docs/1.0-dev/_modules/evennia/web/api/root.html +++ b/docs/1.0-dev/_modules/evennia/web/api/root.html @@ -93,7 +93,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/api/serializers.html b/docs/1.0-dev/_modules/evennia/web/api/serializers.html index 6d0a372456..de553b7426 100644 --- a/docs/1.0-dev/_modules/evennia/web/api/serializers.html +++ b/docs/1.0-dev/_modules/evennia/web/api/serializers.html @@ -412,7 +412,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/api/tests.html b/docs/1.0-dev/_modules/evennia/web/api/tests.html index df16394496..c39ffe1d38 100644 --- a/docs/1.0-dev/_modules/evennia/web/api/tests.html +++ b/docs/1.0-dev/_modules/evennia/web/api/tests.html @@ -49,15 +49,14 @@ from evennia.utils.test_resources import BaseEvenniaTest from evennia.web.api import serializers from rest_framework.test import APIClient -from django.urls import reverse +from django.urls import reverse, path, include from django.test import override_settings from collections import namedtuple -from django.conf.urls import url, include from django.core.exceptions import ObjectDoesNotExist urlpatterns = [ - url(r"^", include("evennia.web.website.urls")), - url(r"^api/", include("evennia.web.api.urls", namespace="api")), + path(r"^", include("evennia.web.website.urls")), + path(r"^api/", include("evennia.web.api.urls", namespace="api")), ] @@ -255,7 +254,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/api/views.html b/docs/1.0-dev/_modules/evennia/web/api/views.html index 0c42b6a42b..c0be8ae8a7 100644 --- a/docs/1.0-dev/_modules/evennia/web/api/views.html +++ b/docs/1.0-dev/_modules/evennia/web/api/views.html @@ -249,7 +249,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/templatetags/addclass.html b/docs/1.0-dev/_modules/evennia/web/templatetags/addclass.html index 71bacff198..259bf7e4a1 100644 --- a/docs/1.0-dev/_modules/evennia/web/templatetags/addclass.html +++ b/docs/1.0-dev/_modules/evennia/web/templatetags/addclass.html @@ -93,7 +93,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/utils/adminsite.html b/docs/1.0-dev/_modules/evennia/web/utils/adminsite.html index 61dd3394ea..266864ed14 100644 --- a/docs/1.0-dev/_modules/evennia/web/utils/adminsite.html +++ b/docs/1.0-dev/_modules/evennia/web/utils/adminsite.html @@ -113,7 +113,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/utils/backends.html b/docs/1.0-dev/_modules/evennia/web/utils/backends.html index 9a6be23304..ca9f158bf7 100644 --- a/docs/1.0-dev/_modules/evennia/web/utils/backends.html +++ b/docs/1.0-dev/_modules/evennia/web/utils/backends.html @@ -119,7 +119,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/utils/general_context.html b/docs/1.0-dev/_modules/evennia/web/utils/general_context.html index 325b551718..38ab5f21a3 100644 --- a/docs/1.0-dev/_modules/evennia/web/utils/general_context.html +++ b/docs/1.0-dev/_modules/evennia/web/utils/general_context.html @@ -207,7 +207,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/utils/middleware.html b/docs/1.0-dev/_modules/evennia/web/utils/middleware.html index 49539c1d3b..0a030c16ca 100644 --- a/docs/1.0-dev/_modules/evennia/web/utils/middleware.html +++ b/docs/1.0-dev/_modules/evennia/web/utils/middleware.html @@ -148,7 +148,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/utils/tests.html b/docs/1.0-dev/_modules/evennia/web/utils/tests.html index 9aee470306..12c102b810 100644 --- a/docs/1.0-dev/_modules/evennia/web/utils/tests.html +++ b/docs/1.0-dev/_modules/evennia/web/utils/tests.html @@ -126,7 +126,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/webclient/views.html b/docs/1.0-dev/_modules/evennia/web/webclient/views.html index b9a790ed3a..13cd4c95fe 100644 --- a/docs/1.0-dev/_modules/evennia/web/webclient/views.html +++ b/docs/1.0-dev/_modules/evennia/web/webclient/views.html @@ -106,7 +106,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/forms.html b/docs/1.0-dev/_modules/evennia/web/website/forms.html index 27bec760a7..765fab16e5 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/forms.html +++ b/docs/1.0-dev/_modules/evennia/web/website/forms.html @@ -251,7 +251,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/tests.html b/docs/1.0-dev/_modules/evennia/web/website/tests.html index 37b9ae3b8b..cdfcfc2c2d 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/tests.html +++ b/docs/1.0-dev/_modules/evennia/web/website/tests.html @@ -443,7 +443,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/views/accounts.html b/docs/1.0-dev/_modules/evennia/web/website/views/accounts.html index 5e0e78416e..20ec4bea6f 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/views/accounts.html +++ b/docs/1.0-dev/_modules/evennia/web/website/views/accounts.html @@ -152,7 +152,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/views/channels.html b/docs/1.0-dev/_modules/evennia/web/website/views/channels.html index 0b4eb11a25..e65a01dd87 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/views/channels.html +++ b/docs/1.0-dev/_modules/evennia/web/website/views/channels.html @@ -255,7 +255,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/views/characters.html b/docs/1.0-dev/_modules/evennia/web/website/views/characters.html index a17497d6dc..96623472d2 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/views/characters.html +++ b/docs/1.0-dev/_modules/evennia/web/website/views/characters.html @@ -243,8 +243,8 @@ ObjectDeleteView) can delete a character they own. """ - - pass + # using the character form fails there + form_class = forms.EvenniaForm
        [docs]class CharacterCreateView(CharacterMixin, ObjectCreateView): @@ -330,7 +330,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/views/errors.html b/docs/1.0-dev/_modules/evennia/web/website/views/errors.html index 55192b1db8..237f17c345 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/views/errors.html +++ b/docs/1.0-dev/_modules/evennia/web/website/views/errors.html @@ -92,7 +92,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/views/help.html b/docs/1.0-dev/_modules/evennia/web/website/views/help.html index 405c0785f6..24abeed4c7 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/views/help.html +++ b/docs/1.0-dev/_modules/evennia/web/website/views/help.html @@ -409,7 +409,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/views/index.html b/docs/1.0-dev/_modules/evennia/web/website/views/index.html index a5a8b4143a..7bdd9704be 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/views/index.html +++ b/docs/1.0-dev/_modules/evennia/web/website/views/index.html @@ -190,7 +190,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/views/mixins.html b/docs/1.0-dev/_modules/evennia/web/website/views/mixins.html index 959345011a..e4e9bac756 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/views/mixins.html +++ b/docs/1.0-dev/_modules/evennia/web/website/views/mixins.html @@ -131,8 +131,6 @@ def page_title(self): # Makes sure the page has a sensible title. return "Delete %s" % self.typeclass._meta.verbose_name.title() - -
        @@ -167,7 +165,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/evennia/web/website/views/objects.html b/docs/1.0-dev/_modules/evennia/web/website/views/objects.html index 9fb9ea2229..001b0849e6 100644 --- a/docs/1.0-dev/_modules/evennia/web/website/views/objects.html +++ b/docs/1.0-dev/_modules/evennia/web/website/views/objects.html @@ -204,28 +204,7 @@ template_name = "website/object_confirm_delete.html" # -- Evennia constructs -- - access_type = "delete" - -
        [docs] def delete(self, request, *args, **kwargs): - """ - Calls the delete() method on the fetched object and then - redirects to the success URL. - - We extend this so we can capture the name for the sake of confirmation. - - """ - # Get the object in question. ObjectDetailView.get_object() will also - # check to make sure the current user (authenticated or not) has - # permission to delete it! - obj = str(self.get_object()) - - # Perform the actual deletion (the parent class handles this, which will - # in turn call the delete() method on the object) - response = super().delete(request, *args, **kwargs) - - # Notify the user of the deletion - messages.success(request, "Successfully deleted '%s'." % obj) - return response
        + access_type = "delete"
        [docs]class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView): @@ -344,7 +323,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/functools.html b/docs/1.0-dev/_modules/functools.html index beac999bb7..c74fecc566 100644 --- a/docs/1.0-dev/_modules/functools.html +++ b/docs/1.0-dev/_modules/functools.html @@ -53,18 +53,16 @@ # See C source code for _functools credits/copyright __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', - 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial', - 'partialmethod', 'singledispatch'] + 'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce', + 'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod', + 'cached_property'] -try: - from _functools import reduce -except ImportError: - pass from abc import get_cache_token from collections import namedtuple # import types, weakref # Deferred to single_dispatch() from reprlib import recursive_repr from _thread import RLock +from types import GenericAlias ################################################################################ @@ -133,80 +131,84 @@ def _gt_from_lt(self, other, NotImplemented=NotImplemented): 'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).' - op_result = self.__lt__(other) + op_result = type(self).__lt__(self, other) if op_result is NotImplemented: return op_result return not op_result and self != other def _le_from_lt(self, other, NotImplemented=NotImplemented): 'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).' - op_result = self.__lt__(other) + op_result = type(self).__lt__(self, other) + if op_result is NotImplemented: + return op_result return op_result or self == other def _ge_from_lt(self, other, NotImplemented=NotImplemented): 'Return a >= b. Computed by @total_ordering from (not a < b).' - op_result = self.__lt__(other) + op_result = type(self).__lt__(self, other) if op_result is NotImplemented: return op_result return not op_result def _ge_from_le(self, other, NotImplemented=NotImplemented): 'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).' - op_result = self.__le__(other) + op_result = type(self).__le__(self, other) if op_result is NotImplemented: return op_result return not op_result or self == other def _lt_from_le(self, other, NotImplemented=NotImplemented): 'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).' - op_result = self.__le__(other) + op_result = type(self).__le__(self, other) if op_result is NotImplemented: return op_result return op_result and self != other def _gt_from_le(self, other, NotImplemented=NotImplemented): 'Return a > b. Computed by @total_ordering from (not a <= b).' - op_result = self.__le__(other) + op_result = type(self).__le__(self, other) if op_result is NotImplemented: return op_result return not op_result def _lt_from_gt(self, other, NotImplemented=NotImplemented): 'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).' - op_result = self.__gt__(other) + op_result = type(self).__gt__(self, other) if op_result is NotImplemented: return op_result return not op_result and self != other def _ge_from_gt(self, other, NotImplemented=NotImplemented): 'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).' - op_result = self.__gt__(other) + op_result = type(self).__gt__(self, other) + if op_result is NotImplemented: + return op_result return op_result or self == other def _le_from_gt(self, other, NotImplemented=NotImplemented): 'Return a <= b. Computed by @total_ordering from (not a > b).' - op_result = self.__gt__(other) + op_result = type(self).__gt__(self, other) if op_result is NotImplemented: return op_result return not op_result def _le_from_ge(self, other, NotImplemented=NotImplemented): 'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).' - op_result = self.__ge__(other) + op_result = type(self).__ge__(self, other) if op_result is NotImplemented: return op_result return not op_result or self == other def _gt_from_ge(self, other, NotImplemented=NotImplemented): 'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).' - op_result = self.__ge__(other) + op_result = type(self).__ge__(self, other) if op_result is NotImplemented: return op_result return op_result and self != other def _lt_from_ge(self, other, NotImplemented=NotImplemented): 'Return a < b. Computed by @total_ordering from (not a >= b).' - op_result = self.__ge__(other) + op_result = type(self).__ge__(self, other) if op_result is NotImplemented: return op_result return not op_result @@ -269,6 +271,46 @@ pass +################################################################################ +### reduce() sequence to a single item +################################################################################ + +_initial_missing = object() + +def reduce(function, sequence, initial=_initial_missing): + """ + reduce(function, iterable[, initial]) -> value + + Apply a function of two arguments cumulatively to the items of a sequence + or iterable, from left to right, so as to reduce the iterable to a single + value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates + ((((1+2)+3)+4)+5). If initial is present, it is placed before the items + of the iterable in the calculation, and serves as a default when the + iterable is empty. + """ + + it = iter(sequence) + + if initial is _initial_missing: + try: + value = next(it) + except StopIteration: + raise TypeError( + "reduce() of empty iterable with no initial value") from None + else: + value = initial + + for element in it: + value = function(value, element) + + return value + +try: + from _functools import reduce +except ImportError: + pass + + ################################################################################ ### partial() argument application ################################################################################ @@ -281,22 +323,13 @@ __slots__ = "func", "args", "keywords", "__dict__", "__weakref__" - def __new__(*args, **keywords): - if not args: - raise TypeError("descriptor '__new__' of partial needs an argument") - if len(args) < 2: - raise TypeError("type 'partial' takes at least one argument") - cls, func, *args = args + def __new__(cls, func, /, *args, **keywords): if not callable(func): raise TypeError("the first argument must be callable") - args = tuple(args) if hasattr(func, "func"): args = func.args + args - tmpkw = func.keywords.copy() - tmpkw.update(keywords) - keywords = tmpkw - del tmpkw + keywords = {**func.keywords, **keywords} func = func.func self = super(partial, cls).__new__(cls) @@ -306,13 +339,9 @@ self.keywords = keywords return self - def __call__(*args, **keywords): - if not args: - raise TypeError("descriptor '__call__' of partial needs an argument") - self, *args = args - newkeywords = self.keywords.copy() - newkeywords.update(keywords) - return self.func(*self.args, *args, **newkeywords) + def __call__(self, /, *args, **keywords): + keywords = {**self.keywords, **keywords} + return self.func(*self.args, *args, **keywords) @recursive_repr() def __repr__(self): @@ -366,20 +395,7 @@ callables as instance methods. """ - def __init__(*args, **keywords): - if len(args) >= 2: - self, func, *args = args - elif not args: - raise TypeError("descriptor '__init__' of partialmethod " - "needs an argument") - elif 'func' in keywords: - func = keywords.pop('func') - self, *args = args - else: - raise TypeError("type 'partialmethod' takes at least one argument, " - "got %d" % (len(args)-1)) - args = tuple(args) - + def __init__(self, func, /, *args, **keywords): if not callable(func) and not hasattr(func, "__get__"): raise TypeError("{!r} is not callable or a descriptor" .format(func)) @@ -392,8 +408,7 @@ # it's also more efficient since only one function will be called self.func = func.func self.args = func.args + args - self.keywords = func.keywords.copy() - self.keywords.update(keywords) + self.keywords = {**func.keywords, **keywords} else: self.func = func self.args = args @@ -411,17 +426,14 @@ keywords=keywords) def _make_unbound_method(self): - def _method(*args, **keywords): - call_keywords = self.keywords.copy() - call_keywords.update(keywords) - cls_or_self, *rest = args - call_args = (cls_or_self,) + self.args + tuple(rest) - return self.func(*call_args, **call_keywords) + def _method(cls_or_self, /, *args, **keywords): + keywords = {**self.keywords, **keywords} + return self.func(cls_or_self, *self.args, *args, **keywords) _method.__isabstractmethod__ = self.__isabstractmethod__ _method._partialmethod = self return _method - def __get__(self, obj, cls): + def __get__(self, obj, cls=None): get = getattr(self.func, "__get__", None) result = None if get is not None: @@ -444,6 +456,15 @@ def __isabstractmethod__(self): return getattr(self.func, "__isabstractmethod__", False) + __class_getitem__ = classmethod(GenericAlias) + + +# Helper functions + +def _unwrap_partial(func): + while isinstance(func, partial): + func = func.func + return func ################################################################################ ### LRU Cache function decorator @@ -514,7 +535,7 @@ with f.cache_info(). Clear the cache and statistics with f.cache_clear(). Access the underlying function with f.__wrapped__. - See: http://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU) + See: https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU) """ @@ -523,17 +544,23 @@ # The internals of the lru_cache are encapsulated for thread safety and # to allow the implementation to change (including a possible C version). - # Early detection of an erroneous call to @lru_cache without any arguments - # resulting in the inner function being passed to maxsize instead of an - # integer or None. Negative maxsize is treated as 0. if isinstance(maxsize, int): + # Negative maxsize is treated as 0 if maxsize < 0: maxsize = 0 + elif callable(maxsize) and isinstance(typed, bool): + # The user_function was passed in directly via the maxsize argument + user_function, maxsize = maxsize, 128 + wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) + wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed} + return update_wrapper(wrapper, user_function) elif maxsize is not None: - raise TypeError('Expected maxsize to be an integer or None') + raise TypeError( + 'Expected first argument to be an integer, a callable, or None') def decorating_function(user_function): wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) + wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed} return update_wrapper(wrapper, user_function) return decorating_function @@ -660,6 +687,15 @@ pass +################################################################################ +### cache -- simplified access to the infinity cache +################################################################################ + +def cache(user_function, /): + 'Simple lightweight unbounded cache. Sometimes called "memoize".' + return lru_cache(maxsize=None)(user_function) + + ################################################################################ ### singledispatch() - single-dispatch generic function decorator ################################################################################ @@ -667,7 +703,7 @@ def _c3_merge(sequences): """Merges MROs in *sequences* to a single MRO using the C3 algorithm. - Adapted from http://www.python.org/download/releases/2.3/mro/. + Adapted from https://www.python.org/download/releases/2.3/mro/. """ result = [] @@ -747,6 +783,7 @@ # Remove entries which are already present in the __mro__ or unrelated. def is_related(typ): return (typ not in bases and hasattr(typ, '__mro__') + and not isinstance(typ, GenericAlias) and issubclass(cls, typ)) types = [n for n in types if is_related(n)] # Remove entries which are strict bases of other entries (they will end up @@ -844,6 +881,9 @@ dispatch_cache[cls] = impl return impl + def _is_valid_dispatch_type(cls): + return isinstance(cls, type) and not isinstance(cls, GenericAlias) + def register(cls, func=None): """generic_func.register(cls, func) -> func @@ -851,9 +891,15 @@ """ nonlocal cache_token - if func is None: - if isinstance(cls, type): + if _is_valid_dispatch_type(cls): + if func is None: return lambda f: register(cls, f) + else: + if func is not None: + raise TypeError( + f"Invalid first argument to `register()`. " + f"{cls!r} is not a class." + ) ann = getattr(cls, '__annotations__', {}) if not ann: raise TypeError( @@ -866,9 +912,12 @@ # only import typing if annotation parsing is necessary from typing import get_type_hints argname, cls = next(iter(get_type_hints(func).items())) - assert isinstance(cls, type), ( - f"Invalid annotation for {argname!r}. {cls!r} is not a class." - ) + if not _is_valid_dispatch_type(cls): + raise TypeError( + f"Invalid annotation for {argname!r}. " + f"{cls!r} is not a class." + ) + registry[cls] = func if cache_token is None and hasattr(cls, '__abstractmethods__'): cache_token = get_cache_token() @@ -890,6 +939,100 @@ wrapper._clear_cache = dispatch_cache.clear update_wrapper(wrapper, func) return wrapper + + +# Descriptor version +class singledispatchmethod: + """Single-dispatch generic method descriptor. + + Supports wrapping existing descriptors and handles non-descriptor + callables as instance methods. + """ + + def __init__(self, func): + if not callable(func) and not hasattr(func, "__get__"): + raise TypeError(f"{func!r} is not callable or a descriptor") + + self.dispatcher = singledispatch(func) + self.func = func + + def register(self, cls, method=None): + """generic_method.register(cls, func) -> func + + Registers a new implementation for the given *cls* on a *generic_method*. + """ + return self.dispatcher.register(cls, func=method) + + def __get__(self, obj, cls=None): + def _method(*args, **kwargs): + method = self.dispatcher.dispatch(args[0].__class__) + return method.__get__(obj, cls)(*args, **kwargs) + + _method.__isabstractmethod__ = self.__isabstractmethod__ + _method.register = self.register + update_wrapper(_method, self.func) + return _method + + @property + def __isabstractmethod__(self): + return getattr(self.func, '__isabstractmethod__', False) + + +################################################################################ +### cached_property() - computed once per instance, cached as attribute +################################################################################ + +_NOT_FOUND = object() + + +class cached_property: + def __init__(self, func): + self.func = func + self.attrname = None + self.__doc__ = func.__doc__ + self.lock = RLock() + + def __set_name__(self, owner, name): + if self.attrname is None: + self.attrname = name + elif name != self.attrname: + raise TypeError( + "Cannot assign the same cached_property to two different names " + f"({self.attrname!r} and {name!r})." + ) + + def __get__(self, instance, owner=None): + if instance is None: + return self + if self.attrname is None: + raise TypeError( + "Cannot use cached_property instance without calling __set_name__ on it.") + try: + cache = instance.__dict__ + except AttributeError: # not all objects have __dict__ (e.g. class defines slots) + msg = ( + f"No '__dict__' attribute on {type(instance).__name__!r} " + f"instance to cache {self.attrname!r} property." + ) + raise TypeError(msg) from None + val = cache.get(self.attrname, _NOT_FOUND) + if val is _NOT_FOUND: + with self.lock: + # check if another thread filled cache while we awaited lock + val = cache.get(self.attrname, _NOT_FOUND) + if val is _NOT_FOUND: + val = self.func(instance) + try: + cache[self.attrname] = val + except TypeError: + msg = ( + f"The '__dict__' attribute on {type(instance).__name__!r} instance " + f"does not support item assignment for caching {self.attrname!r} property." + ) + raise TypeError(msg) from None + return val + + __class_getitem__ = classmethod(GenericAlias)
        @@ -924,7 +1067,6 @@

        Versions

        diff --git a/docs/1.0-dev/_modules/index.html b/docs/1.0-dev/_modules/index.html index 620136804d..3a8b27a710 100644 --- a/docs/1.0-dev/_modules/index.html +++ b/docs/1.0-dev/_modules/index.html @@ -45,7 +45,6 @@
      • django.db.models.manager
      • django.db.models.query
      • django.db.models.query_utils
      • -
      • django.utils.deconstruct
      • django.utils.functional
      • evennia
      • functools
      • +
      • re
      • rest_framework.test
      @@ -334,7 +333,6 @@

      Versions

      diff --git a/docs/1.0-dev/_modules/re.html b/docs/1.0-dev/_modules/re.html new file mode 100644 index 0000000000..30b2c0f01e --- /dev/null +++ b/docs/1.0-dev/_modules/re.html @@ -0,0 +1,487 @@ + + + + + + + + re — Evennia 1.0-dev documentation + + + + + + + + + + + + + + + +
      +
      +
      +
      + +

      Source code for re

      +#
      +# Secret Labs' Regular Expression Engine
      +#
      +# re-compatible interface for the sre matching engine
      +#
      +# Copyright (c) 1998-2001 by Secret Labs AB.  All rights reserved.
      +#
      +# This version of the SRE library can be redistributed under CNRI's
      +# Python 1.6 license.  For any other use, please contact Secret Labs
      +# AB (info@pythonware.com).
      +#
      +# Portions of this engine have been developed in cooperation with
      +# CNRI.  Hewlett-Packard provided funding for 1.6 integration and
      +# other compatibility work.
      +#
      +
      +r"""Support for regular expressions (RE).
      +
      +This module provides regular expression matching operations similar to
      +those found in Perl.  It supports both 8-bit and Unicode strings; both
      +the pattern and the strings being processed can contain null bytes and
      +characters outside the US ASCII range.
      +
      +Regular expressions can contain both special and ordinary characters.
      +Most ordinary characters, like "A", "a", or "0", are the simplest
      +regular expressions; they simply match themselves.  You can
      +concatenate ordinary characters, so last matches the string 'last'.
      +
      +The special characters are:
      +    "."      Matches any character except a newline.
      +    "^"      Matches the start of the string.
      +    "$"      Matches the end of the string or just before the newline at
      +             the end of the string.
      +    "*"      Matches 0 or more (greedy) repetitions of the preceding RE.
      +             Greedy means that it will match as many repetitions as possible.
      +    "+"      Matches 1 or more (greedy) repetitions of the preceding RE.
      +    "?"      Matches 0 or 1 (greedy) of the preceding RE.
      +    *?,+?,?? Non-greedy versions of the previous three special characters.
      +    {m,n}    Matches from m to n repetitions of the preceding RE.
      +    {m,n}?   Non-greedy version of the above.
      +    "\\"     Either escapes special characters or signals a special sequence.
      +    []       Indicates a set of characters.
      +             A "^" as the first character indicates a complementing set.
      +    "|"      A|B, creates an RE that will match either A or B.
      +    (...)    Matches the RE inside the parentheses.
      +             The contents can be retrieved or matched later in the string.
      +    (?aiLmsux) The letters set the corresponding flags defined below.
      +    (?:...)  Non-grouping version of regular parentheses.
      +    (?P<name>...) The substring matched by the group is accessible by name.
      +    (?P=name)     Matches the text matched earlier by the group named name.
      +    (?#...)  A comment; ignored.
      +    (?=...)  Matches if ... matches next, but doesn't consume the string.
      +    (?!...)  Matches if ... doesn't match next.
      +    (?<=...) Matches if preceded by ... (must be fixed length).
      +    (?<!...) Matches if not preceded by ... (must be fixed length).
      +    (?(id/name)yes|no) Matches yes pattern if the group with id/name matched,
      +                       the (optional) no pattern otherwise.
      +
      +The special sequences consist of "\\" and a character from the list
      +below.  If the ordinary character is not on the list, then the
      +resulting RE will match the second character.
      +    \number  Matches the contents of the group of the same number.
      +    \A       Matches only at the start of the string.
      +    \Z       Matches only at the end of the string.
      +    \b       Matches the empty string, but only at the start or end of a word.
      +    \B       Matches the empty string, but not at the start or end of a word.
      +    \d       Matches any decimal digit; equivalent to the set [0-9] in
      +             bytes patterns or string patterns with the ASCII flag.
      +             In string patterns without the ASCII flag, it will match the whole
      +             range of Unicode digits.
      +    \D       Matches any non-digit character; equivalent to [^\d].
      +    \s       Matches any whitespace character; equivalent to [ \t\n\r\f\v] in
      +             bytes patterns or string patterns with the ASCII flag.
      +             In string patterns without the ASCII flag, it will match the whole
      +             range of Unicode whitespace characters.
      +    \S       Matches any non-whitespace character; equivalent to [^\s].
      +    \w       Matches any alphanumeric character; equivalent to [a-zA-Z0-9_]
      +             in bytes patterns or string patterns with the ASCII flag.
      +             In string patterns without the ASCII flag, it will match the
      +             range of Unicode alphanumeric characters (letters plus digits
      +             plus underscore).
      +             With LOCALE, it will match the set [0-9_] plus characters defined
      +             as letters for the current locale.
      +    \W       Matches the complement of \w.
      +    \\       Matches a literal backslash.
      +
      +This module exports the following functions:
      +    match     Match a regular expression pattern to the beginning of a string.
      +    fullmatch Match a regular expression pattern to all of a string.
      +    search    Search a string for the presence of a pattern.
      +    sub       Substitute occurrences of a pattern found in a string.
      +    subn      Same as sub, but also return the number of substitutions made.
      +    split     Split a string by the occurrences of a pattern.
      +    findall   Find all occurrences of a pattern in a string.
      +    finditer  Return an iterator yielding a Match object for each match.
      +    compile   Compile a pattern into a Pattern object.
      +    purge     Clear the regular expression cache.
      +    escape    Backslash all non-alphanumerics in a string.
      +
      +Each function other than purge and escape can take an optional 'flags' argument
      +consisting of one or more of the following module constants, joined by "|".
      +A, L, and U are mutually exclusive.
      +    A  ASCII       For string patterns, make \w, \W, \b, \B, \d, \D
      +                   match the corresponding ASCII character categories
      +                   (rather than the whole Unicode categories, which is the
      +                   default).
      +                   For bytes patterns, this flag is the only available
      +                   behaviour and needn't be specified.
      +    I  IGNORECASE  Perform case-insensitive matching.
      +    L  LOCALE      Make \w, \W, \b, \B, dependent on the current locale.
      +    M  MULTILINE   "^" matches the beginning of lines (after a newline)
      +                   as well as the string.
      +                   "$" matches the end of lines (before a newline) as well
      +                   as the end of the string.
      +    S  DOTALL      "." matches any character at all, including the newline.
      +    X  VERBOSE     Ignore whitespace and comments for nicer looking RE's.
      +    U  UNICODE     For compatibility only. Ignored for string patterns (it
      +                   is the default), and forbidden for bytes patterns.
      +
      +This module also defines an exception 'error'.
      +
      +"""
      +
      +import enum
      +import sre_compile
      +import sre_parse
      +import functools
      +try:
      +    import _locale
      +except ImportError:
      +    _locale = None
      +
      +
      +# public symbols
      +__all__ = [
      +    "match", "fullmatch", "search", "sub", "subn", "split",
      +    "findall", "finditer", "compile", "purge", "template", "escape",
      +    "error", "Pattern", "Match", "A", "I", "L", "M", "S", "X", "U",
      +    "ASCII", "IGNORECASE", "LOCALE", "MULTILINE", "DOTALL", "VERBOSE",
      +    "UNICODE",
      +]
      +
      +__version__ = "2.2.1"
      +
      +class RegexFlag(enum.IntFlag):
      +    ASCII = A = sre_compile.SRE_FLAG_ASCII # assume ascii "locale"
      +    IGNORECASE = I = sre_compile.SRE_FLAG_IGNORECASE # ignore case
      +    LOCALE = L = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale
      +    UNICODE = U = sre_compile.SRE_FLAG_UNICODE # assume unicode "locale"
      +    MULTILINE = M = sre_compile.SRE_FLAG_MULTILINE # make anchors look for newline
      +    DOTALL = S = sre_compile.SRE_FLAG_DOTALL # make dot match newline
      +    VERBOSE = X = sre_compile.SRE_FLAG_VERBOSE # ignore whitespace and comments
      +    # sre extensions (experimental, don't rely on these)
      +    TEMPLATE = T = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking
      +    DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation
      +
      +    def __repr__(self):
      +        if self._name_ is not None:
      +            return f're.{self._name_}'
      +        value = self._value_
      +        members = []
      +        negative = value < 0
      +        if negative:
      +            value = ~value
      +        for m in self.__class__:
      +            if value & m._value_:
      +                value &= ~m._value_
      +                members.append(f're.{m._name_}')
      +        if value:
      +            members.append(hex(value))
      +        res = '|'.join(members)
      +        if negative:
      +            if len(members) > 1:
      +                res = f'~({res})'
      +            else:
      +                res = f'~{res}'
      +        return res
      +    __str__ = object.__str__
      +globals().update(RegexFlag.__members__)
      +
      +# sre exception
      +error = sre_compile.error
      +
      +# --------------------------------------------------------------------
      +# public interface
      +
      +def match(pattern, string, flags=0):
      +    """Try to apply the pattern at the start of the string, returning
      +    a Match object, or None if no match was found."""
      +    return _compile(pattern, flags).match(string)
      +
      +def fullmatch(pattern, string, flags=0):
      +    """Try to apply the pattern to all of the string, returning
      +    a Match object, or None if no match was found."""
      +    return _compile(pattern, flags).fullmatch(string)
      +
      +def search(pattern, string, flags=0):
      +    """Scan through string looking for a match to the pattern, returning
      +    a Match object, or None if no match was found."""
      +    return _compile(pattern, flags).search(string)
      +
      +def sub(pattern, repl, string, count=0, flags=0):
      +    """Return the string obtained by replacing the leftmost
      +    non-overlapping occurrences of the pattern in string by the
      +    replacement repl.  repl can be either a string or a callable;
      +    if a string, backslash escapes in it are processed.  If it is
      +    a callable, it's passed the Match object and must return
      +    a replacement string to be used."""
      +    return _compile(pattern, flags).sub(repl, string, count)
      +
      +def subn(pattern, repl, string, count=0, flags=0):
      +    """Return a 2-tuple containing (new_string, number).
      +    new_string is the string obtained by replacing the leftmost
      +    non-overlapping occurrences of the pattern in the source
      +    string by the replacement repl.  number is the number of
      +    substitutions that were made. repl can be either a string or a
      +    callable; if a string, backslash escapes in it are processed.
      +    If it is a callable, it's passed the Match object and must
      +    return a replacement string to be used."""
      +    return _compile(pattern, flags).subn(repl, string, count)
      +
      +def split(pattern, string, maxsplit=0, flags=0):
      +    """Split the source string by the occurrences of the pattern,
      +    returning a list containing the resulting substrings.  If
      +    capturing parentheses are used in pattern, then the text of all
      +    groups in the pattern are also returned as part of the resulting
      +    list.  If maxsplit is nonzero, at most maxsplit splits occur,
      +    and the remainder of the string is returned as the final element
      +    of the list."""
      +    return _compile(pattern, flags).split(string, maxsplit)
      +
      +def findall(pattern, string, flags=0):
      +    """Return a list of all non-overlapping matches in the string.
      +
      +    If one or more capturing groups are present in the pattern, return
      +    a list of groups; this will be a list of tuples if the pattern
      +    has more than one group.
      +
      +    Empty matches are included in the result."""
      +    return _compile(pattern, flags).findall(string)
      +
      +def finditer(pattern, string, flags=0):
      +    """Return an iterator over all non-overlapping matches in the
      +    string.  For each match, the iterator returns a Match object.
      +
      +    Empty matches are included in the result."""
      +    return _compile(pattern, flags).finditer(string)
      +
      +def compile(pattern, flags=0):
      +    "Compile a regular expression pattern, returning a Pattern object."
      +    return _compile(pattern, flags)
      +
      +def purge():
      +    "Clear the regular expression caches"
      +    _cache.clear()
      +    _compile_repl.cache_clear()
      +
      +def template(pattern, flags=0):
      +    "Compile a template pattern, returning a Pattern object"
      +    return _compile(pattern, flags|T)
      +
      +# SPECIAL_CHARS
      +# closing ')', '}' and ']'
      +# '-' (a range in character set)
      +# '&', '~', (extended character set operations)
      +# '#' (comment) and WHITESPACE (ignored) in verbose mode
      +_special_chars_map = {i: '\\' + chr(i) for i in b'()[]{}?*+-|^$\\.&~# \t\n\r\v\f'}
      +
      +def escape(pattern):
      +    """
      +    Escape special characters in a string.
      +    """
      +    if isinstance(pattern, str):
      +        return pattern.translate(_special_chars_map)
      +    else:
      +        pattern = str(pattern, 'latin1')
      +        return pattern.translate(_special_chars_map).encode('latin1')
      +
      +Pattern = type(sre_compile.compile('', 0))
      +Match = type(sre_compile.compile('', 0).match(''))
      +
      +# --------------------------------------------------------------------
      +# internals
      +
      +_cache = {}  # ordered!
      +
      +_MAXCACHE = 512
      +def _compile(pattern, flags):
      +    # internal: compile pattern
      +    if isinstance(flags, RegexFlag):
      +        flags = flags.value
      +    try:
      +        return _cache[type(pattern), pattern, flags]
      +    except KeyError:
      +        pass
      +    if isinstance(pattern, Pattern):
      +        if flags:
      +            raise ValueError(
      +                "cannot process flags argument with a compiled pattern")
      +        return pattern
      +    if not sre_compile.isstring(pattern):
      +        raise TypeError("first argument must be string or compiled pattern")
      +    p = sre_compile.compile(pattern, flags)
      +    if not (flags & DEBUG):
      +        if len(_cache) >= _MAXCACHE:
      +            # Drop the oldest item
      +            try:
      +                del _cache[next(iter(_cache))]
      +            except (StopIteration, RuntimeError, KeyError):
      +                pass
      +        _cache[type(pattern), pattern, flags] = p
      +    return p
      +
      +@functools.lru_cache(_MAXCACHE)
      +def _compile_repl(repl, pattern):
      +    # internal: compile replacement pattern
      +    return sre_parse.parse_template(repl, pattern)
      +
      +def _expand(pattern, match, template):
      +    # internal: Match.expand implementation hook
      +    template = sre_parse.parse_template(template, pattern)
      +    return sre_parse.expand_template(template, match)
      +
      +def _subx(pattern, template):
      +    # internal: Pattern.sub/subn implementation helper
      +    template = _compile_repl(template, pattern)
      +    if not template[0] and len(template[1]) == 1:
      +        # literal replacement
      +        return template[1][0]
      +    def filter(match, template=template):
      +        return sre_parse.expand_template(template, match)
      +    return filter
      +
      +# register myself for pickling
      +
      +import copyreg
      +
      +def _pickle(p):
      +    return _compile, (p.pattern, p.flags)
      +
      +copyreg.pickle(Pattern, _pickle, _compile)
      +
      +# --------------------------------------------------------------------
      +# experimental stuff (see python-dev discussions for details)
      +
      +class Scanner:
      +    def __init__(self, lexicon, flags=0):
      +        from sre_constants import BRANCH, SUBPATTERN
      +        if isinstance(flags, RegexFlag):
      +            flags = flags.value
      +        self.lexicon = lexicon
      +        # combine phrases into a compound pattern
      +        p = []
      +        s = sre_parse.State()
      +        s.flags = flags
      +        for phrase, action in lexicon:
      +            gid = s.opengroup()
      +            p.append(sre_parse.SubPattern(s, [
      +                (SUBPATTERN, (gid, 0, 0, sre_parse.parse(phrase, flags))),
      +                ]))
      +            s.closegroup(gid, p[-1])
      +        p = sre_parse.SubPattern(s, [(BRANCH, (None, p))])
      +        self.scanner = sre_compile.compile(p)
      +    def scan(self, string):
      +        result = []
      +        append = result.append
      +        match = self.scanner.scanner(string).match
      +        i = 0
      +        while True:
      +            m = match()
      +            if not m:
      +                break
      +            j = m.end()
      +            if i == j:
      +                break
      +            action = self.lexicon[m.lastindex-1][1]
      +            if callable(action):
      +                self.match = m
      +                action = action(self, m.group())
      +            if action is not None:
      +                append(action)
      +            i = j
      +        return result, string[i:]
      +
      + +
      +
      +
      +
      + +
      +
      + + + + \ No newline at end of file diff --git a/docs/1.0-dev/_modules/rest_framework/test.html b/docs/1.0-dev/_modules/rest_framework/test.html index 57e41e42f9..29375d0dda 100644 --- a/docs/1.0-dev/_modules/rest_framework/test.html +++ b/docs/1.0-dev/_modules/rest_framework/test.html @@ -46,6 +46,7 @@ import io from importlib import import_module +import django from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.handlers.wsgi import WSGIHandler @@ -122,7 +123,7 @@ """ raw_kwargs = {} - def start_response(wsgi_status, wsgi_headers): + def start_response(wsgi_status, wsgi_headers, exc_info=None): status, _, reason = wsgi_status.partition(' ') raw_kwargs['status'] = int(status) raw_kwargs['reason'] = reason @@ -167,7 +168,7 @@ def __init__(self, *args, **kwargs): self._session = RequestsClient() kwargs['transports'] = [coreapi.transports.HTTPTransport(session=self.session)] - return super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @property def session(self): @@ -222,9 +223,11 @@ ret = renderer.render(data) # Determine the content-type header from the renderer - content_type = "{}; charset={}".format( - renderer.media_type, renderer.charset - ) + content_type = renderer.media_type + if renderer.charset: + content_type = "{}; charset={}".format( + content_type, renderer.charset + ) # Coerce text to bytes if required. if isinstance(ret, str): @@ -398,6 +401,13 @@ client_class = APIClient +def cleanup_url_patterns(cls): + if hasattr(cls, '_module_urlpatterns'): + cls._module.urlpatterns = cls._module_urlpatterns + else: + del cls._module.urlpatterns + + class URLPatternsTestCase(testcases.SimpleTestCase): """ Isolate URL patterns on a per-TestCase basis. For example, @@ -426,17 +436,23 @@ cls._module.urlpatterns = cls.urlpatterns cls._override.enable() + + if django.VERSION > (4, 0): + cls.addClassCleanup(cls._override.disable) + cls.addClassCleanup(cleanup_url_patterns, cls) + super().setUpClass() - @classmethod - def tearDownClass(cls): - super().tearDownClass() - cls._override.disable() + if django.VERSION < (4, 0): + @classmethod + def tearDownClass(cls): + super().tearDownClass() + cls._override.disable() - if hasattr(cls, '_module_urlpatterns'): - cls._module.urlpatterns = cls._module_urlpatterns - else: - del cls._module.urlpatterns + if hasattr(cls, '_module_urlpatterns'): + cls._module.urlpatterns = cls._module_urlpatterns + else: + del cls._module.urlpatterns
      @@ -471,7 +487,6 @@

      Versions

      diff --git a/docs/1.0-dev/_sources/Coding/Quirks.md.txt b/docs/1.0-dev/_sources/Coding/Quirks.md.txt index 0e2274a588..f0275289e9 100644 --- a/docs/1.0-dev/_sources/Coding/Quirks.md.txt +++ b/docs/1.0-dev/_sources/Coding/Quirks.md.txt @@ -110,7 +110,7 @@ Try to avoid doing so. distributions (notably Ubuntu 16.04 LTS). Zope is a dependency of Twisted. The error manifests in the server not starting with an error that `zope.interface` is not found even though `pip list` shows it's installed. The reason is a missing empty `__init__.py` file at the root of the zope -package. If the virtualenv is named "evenv" as suggested in the [Setup Quickstart](../Setup/Setup-Quickstart.md) +package. If the virtualenv is named "evenv" as suggested in the [Setup Quickstart](../Setup/Installation.md) instructions, use the following command to fix it: ```shell diff --git a/docs/1.0-dev/_sources/Coding/Updating-Your-Game.md.txt b/docs/1.0-dev/_sources/Coding/Updating-Your-Game.md.txt index 617d482ac0..6805a1ab77 100644 --- a/docs/1.0-dev/_sources/Coding/Updating-Your-Game.md.txt +++ b/docs/1.0-dev/_sources/Coding/Updating-Your-Game.md.txt @@ -2,7 +2,7 @@ Fortunately, it's extremely easy to keep your Evennia server up-to-date. If you haven't already, see -the [Getting Started guide](../Setup/Setup-Quickstart.md) and get everything running. +the [Getting Started guide](../Setup/Installation.md) and get everything running. ## Updating with the latest Evennia code changes diff --git a/docs/1.0-dev/_sources/Concepts/Internationalization.md.txt b/docs/1.0-dev/_sources/Concepts/Internationalization.md.txt index 994729158e..1db1753dd2 100644 --- a/docs/1.0-dev/_sources/Concepts/Internationalization.md.txt +++ b/docs/1.0-dev/_sources/Concepts/Internationalization.md.txt @@ -88,7 +88,7 @@ translation bad ... You are welcome to help improve the situation! To start a new translation you need to first have cloned the Evennia repositry with GIT and activated a python virtualenv as described on the -[Setup Quickstart](../Setup/Setup-Quickstart.md) page. +[Setup Quickstart](../Setup/Installation.md) page. Go to `evennia/evennia/` - that is, not your game dir, but inside the `evennia/` repo itself. If you see the `locale/` folder you are in the right place. Make diff --git a/docs/1.0-dev/_sources/Contribs/Arxcode-installing-help.md.txt b/docs/1.0-dev/_sources/Contribs/Arxcode-installing-help.md.txt index a49f01bf67..fb90bb6aa6 100644 --- a/docs/1.0-dev/_sources/Contribs/Arxcode-installing-help.md.txt +++ b/docs/1.0-dev/_sources/Contribs/Arxcode-installing-help.md.txt @@ -23,7 +23,7 @@ better match with the vanilla Evennia install. Firstly, set aside a folder/directory on your drive for everything to follow. You need to start by installing [Evennia](https://www.evennia.com) by following most of the -[Getting Started Instructions](../Setup/Setup-Quickstart.md) for your OS. The difference is that you need to `git clone +[Getting Started Instructions](../Setup/Installation.md) for your OS. The difference is that you need to `git clone https://github.com/TehomCD/evennia.git` instead of Evennia's repo because Arx uses TehomCD's older Evennia 0.8 [fork](https://github.com/TehomCD/evennia), notably still using Python2. This detail is important if referring to newer Evennia documentation. @@ -31,7 +31,7 @@ important if referring to newer Evennia documentation. If you are new to Evennia it's *highly* recommended that you run through the instructions in full - including initializing and starting a new empty game and connecting to it. That way you can be sure Evennia works correctly as a base line. If you have trouble, make sure to -read the [Troubleshooting instructions](../Setup/Extended-Installation.md#troubleshooting) for your +read the [Troubleshooting instructions](../Setup/Installation-Git.md#troubleshooting) for your operating system. You can also drop into our [forums](https://groups.google.com/forum/#%21forum/evennia), join `#evennia` on `irc.freenode.net` or chat from the linked [Discord Server](https://discord.gg/NecFePw). diff --git a/docs/1.0-dev/_sources/Contributing-Docs.md.txt b/docs/1.0-dev/_sources/Contributing-Docs.md.txt index 3b29e37d19..fea633d96c 100644 --- a/docs/1.0-dev/_sources/Contributing-Docs.md.txt +++ b/docs/1.0-dev/_sources/Contributing-Docs.md.txt @@ -720,7 +720,7 @@ available at https://evennia.github.io/evennia/latest/. [commonmark-help]: https://commonmark.org/help/ [sphinx-autodoc]: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc [sphinx-napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html -[getting-started]: Setup/Setup-Quickstart +[getting-started]: Setup/Installation [contributing]: ./Contributing [ReST]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html [ReST-tables]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#tables diff --git a/docs/1.0-dev/_sources/Evennia-Introduction.md.txt b/docs/1.0-dev/_sources/Evennia-Introduction.md.txt index 7ecceb7f5d..76d02a5acf 100644 --- a/docs/1.0-dev/_sources/Evennia-Introduction.md.txt +++ b/docs/1.0-dev/_sources/Evennia-Introduction.md.txt @@ -49,11 +49,11 @@ connect to the demo via your telnet client you can do so at `demo.evennia.com`, Once you installed Evennia yourself it comes with its own tutorial - this shows off some of the possibilities _and_ gives you a small single-player quest to play. The tutorial takes only one -single in-game command to install as explained [here](Howto/Starting/Part1/Tutorial-World-Introduction.md). +single in-game command to install as explained [here](Howto/Starting/Part1/Tutorial-World.md). ## What you need to know to work with Evennia -Assuming you have Evennia working (see the [quick start instructions](Setup/Setup-Quickstart.md)) and have +Assuming you have Evennia working (see the [quick start instructions](Setup/Installation.md)) and have gotten as far as to start the server and connect to it with the client of your choice, here's what you need to know depending on your skills and needs. diff --git a/docs/1.0-dev/_sources/Glossary.md.txt b/docs/1.0-dev/_sources/Glossary.md.txt index cfdfaf4ff6..b53008d649 100644 --- a/docs/1.0-dev/_sources/Glossary.md.txt +++ b/docs/1.0-dev/_sources/Glossary.md.txt @@ -340,12 +340,12 @@ something like "the command 'evennia' is not available" - it's probably because not 'active' yet (see below). Usage: -- `virtualenv ` - initialize a new virtualenv `` in a new folder `` in the current -location. Called `evenv` in these docs. -- `virtualenv -p path/to/alternate/python_executable ` - create a virtualenv using another -Python version than default. -- `source /bin/activate`(linux/mac) - activate the virtualenv in ``. -- `\Scripts\activate` (windows) + +- `python3.10 -m venv evenv` - initialize a new virtualenv-folder `evenv` in the current +location. You can call this whatever you like. The Python-version you use for this call will be the one used +for everything inside the virtualenv. +- `source evenv/bin/activate` (linux/mac) or `evenv\Scripts\activate`(windows) - this activates the + virtualenv. - `deactivate` - turn off the currently activated virtualenv. A virtualenv is 'activated' only for the console/terminal it was started in, but it's safe to @@ -377,6 +377,6 @@ never have to actually `cd` into the `evenv` folder. You can activate it from an still be considered "in" the virtualenv wherever you go until you `deactivate` or close the console/terminal. -So, when do I *need* to activate my virtualenv? If the virtualenv is not active, none of the Python +So, when do you *need* to activate my virtualenv? If the virtualenv is not active, none of the Python packages/programs you installed in it will be available to you. So at a minimum, *it needs to be activated whenever you want to use the `evennia` command* for any reason. diff --git a/docs/1.0-dev/_sources/Howto/Evennia-for-MUSH-Users.md.txt b/docs/1.0-dev/_sources/Howto/Evennia-for-MUSH-Users.md.txt index e5dd840db1..752047ecb4 100644 --- a/docs/1.0-dev/_sources/Howto/Evennia-for-MUSH-Users.md.txt +++ b/docs/1.0-dev/_sources/Howto/Evennia-for-MUSH-Users.md.txt @@ -88,7 +88,7 @@ based inheritance of MUSH. There are other differences for sure, but that should give some feel for things. Enough with the theory. Let's get down to more practical matters next. To install, see the -[Getting Started instructions](../Setup/Setup-Quickstart.md). +[Getting Started instructions](../Setup/Installation.md). ## A first step making things more familiar @@ -211,7 +211,7 @@ for-roleplaying-sessions) that can be of interest. An important aspect of making things more familiar for *Players* is adding new and tweaking existing commands. How this is done is covered by the [Tutorial on adding new commands](Adding-Command- Tutorial). You may also find it useful to shop through the `evennia/contrib/` folder. The -[Tutorial world](Starting/Part1/Tutorial-World-Introduction.md) is a small single-player quest you can try (it’s not very MUSH- +[Tutorial world](Starting/Part1/Tutorial-World.md) is a small single-player quest you can try (it’s not very MUSH- like but it does show many Evennia concepts in action). Beyond that there are [many more tutorials](./Howto-Overview.md) to try out. If you feel you want a more visual overview you can also look at [Evennia in pictures](https://evennia.blogspot.se/2016/05/evennia-in-pictures.html). diff --git a/docs/1.0-dev/_sources/Howto/Evennia-for-roleplaying-sessions.md.txt b/docs/1.0-dev/_sources/Howto/Evennia-for-roleplaying-sessions.md.txt index 59dbc1ccf4..7e68968cf3 100644 --- a/docs/1.0-dev/_sources/Howto/Evennia-for-roleplaying-sessions.md.txt +++ b/docs/1.0-dev/_sources/Howto/Evennia-for-roleplaying-sessions.md.txt @@ -26,7 +26,7 @@ defaults for our particular use-case. Below we will flesh out these components f ## Starting out -We will assume you start from scratch. You need Evennia installed, as per the [Setup Quickstart](../Setup/Setup-Quickstart.md) +We will assume you start from scratch. You need Evennia installed, as per the [Setup Quickstart](../Setup/Installation.md) instructions. Initialize a new game directory with `evennia init `. In this tutorial we assume your game dir is simply named `mygame`. You can use the default database and keep all other settings to default for now. Familiarize yourself with the diff --git a/docs/1.0-dev/_sources/Howto/Howto-Overview.md.txt b/docs/1.0-dev/_sources/Howto/Howto-Overview.md.txt index 3d07ddbe6e..a944ca0b78 100644 --- a/docs/1.0-dev/_sources/Howto/Howto-Overview.md.txt +++ b/docs/1.0-dev/_sources/Howto/Howto-Overview.md.txt @@ -16,7 +16,7 @@ in mind for your own game, this will give you a good start. 1. [Introduction & Overview](Starting/Part1/Starting-Part1.md) 1. [Building stuff](Starting/Part1/Building-Quickstart.md) -1. [The Tutorial World](Starting/Part1/Tutorial-World-Introduction.md) +1. [The Tutorial World](Starting/Part1/Tutorial-World.md) 1. [Python basics](Starting/Part1/Python-basic-introduction.md) 1. [Game dir overview](Starting/Part1/Gamedir-Overview.md) 1. [Python classes and objects](Starting/Part1/Python-classes-and-objects.md) diff --git a/docs/1.0-dev/_sources/Howto/Starting/Part1/Evennia-Library-Overview.md.txt b/docs/1.0-dev/_sources/Howto/Starting/Part1/Evennia-Library-Overview.md.txt index 70c66ce882..3a2dd11252 100644 --- a/docs/1.0-dev/_sources/Howto/Starting/Part1/Evennia-Library-Overview.md.txt +++ b/docs/1.0-dev/_sources/Howto/Starting/Part1/Evennia-Library-Overview.md.txt @@ -16,7 +16,7 @@ what you can download from us. The github repo is also searchable. Finally, you can clone the evennia repo to your own computer and read the sources locally. This is necessary if you want to help with Evennia's development itself. See the - [extended install instructions](../../../Setup/Extended-Installation.md) if you want to do this. + [extended install instructions](../../../Setup/Installation-Git.md) if you want to do this. ## Where is it? diff --git a/docs/1.0-dev/_sources/Howto/Starting/Part1/Gamedir-Overview.md.txt b/docs/1.0-dev/_sources/Howto/Starting/Part1/Gamedir-Overview.md.txt index 5c55ef9029..834fa3f546 100644 --- a/docs/1.0-dev/_sources/Howto/Starting/Part1/Gamedir-Overview.md.txt +++ b/docs/1.0-dev/_sources/Howto/Starting/Part1/Gamedir-Overview.md.txt @@ -200,7 +200,7 @@ people change and re-structure this in various ways to better fit their ideas. - [batch_cmds.ev](github:evennia/game_template/world/batch_cmds.ev) - This is an `.ev` file, which is essentially just a list of Evennia commands to execute in sequence. This one is empty and ready to expand on. The - [Tutorial World](./Tutorial-World-Introduction.md) was built with such a batch-file. + [Tutorial World](./Tutorial-World.md) was built with such a batch-file. - [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Prototypes.md) is a way to easily vary objects without changing their base typeclass. For example, one could use prototypes to tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different diff --git a/docs/1.0-dev/_sources/Howto/Starting/Part1/More-on-Commands.md.txt b/docs/1.0-dev/_sources/Howto/Starting/Part1/More-on-Commands.md.txt index 00911c05ba..6997d5993c 100644 --- a/docs/1.0-dev/_sources/Howto/Starting/Part1/More-on-Commands.md.txt +++ b/docs/1.0-dev/_sources/Howto/Starting/Part1/More-on-Commands.md.txt @@ -144,7 +144,7 @@ change (no code changed, only stuff in the database). The commands of a cmdset attached to an object with `obj.cmdset.add()` will by default be made available to that object but _also to those in the same location as that object_. If you did the [Building introduction](./Building-Quickstart.md) -you've seen an example of this with the "Red Button" object. The [Tutorial world](./Tutorial-World-Introduction.md) +you've seen an example of this with the "Red Button" object. The [Tutorial world](./Tutorial-World.md) also has many examples of objects with commands on them. To show how this could work, let's put our 'hit' Command on our simple `sword` object from the previous section. diff --git a/docs/1.0-dev/_sources/Howto/Starting/Part1/Starting-Part1.md.txt b/docs/1.0-dev/_sources/Howto/Starting/Part1/Starting-Part1.md.txt index dbe6881f37..4d434091c8 100644 --- a/docs/1.0-dev/_sources/Howto/Starting/Part1/Starting-Part1.md.txt +++ b/docs/1.0-dev/_sources/Howto/Starting/Part1/Starting-Part1.md.txt @@ -25,7 +25,7 @@ and share with others! 1. Introduction (you are here) 1. [Building stuff](./Building-Quickstart.md) -1. [The Tutorial World](./Tutorial-World-Introduction.md) +1. [The Tutorial World](./Tutorial-World.md) 1. [Python basics](./Python-basic-introduction.md) 1. [Game dir overview](./Gamedir-Overview.md) 1. [Python classes and objects](./Python-classes-and-objects.md) @@ -79,7 +79,7 @@ things don't change much from year to year. Popular choices for Python are PyCha ### Set up a game dir for the tutorial -Next you should make sure you have [installed Evennia](../../../Setup/Setup-Quickstart.md). If you followed the instructions +Next you should make sure you have [installed Evennia](../../../Setup/Installation.md). If you followed the instructions you will already have created a game-dir. You could use that for this tutorial or you may want to do the tutorial in its own, isolated game dir; it's up to you. diff --git a/docs/1.0-dev/_sources/Howto/Starting/Part1/Tutorial-World-Introduction.md.txt b/docs/1.0-dev/_sources/Howto/Starting/Part1/Tutorial-World.md.txt similarity index 100% rename from docs/1.0-dev/_sources/Howto/Starting/Part1/Tutorial-World-Introduction.md.txt rename to docs/1.0-dev/_sources/Howto/Starting/Part1/Tutorial-World.md.txt diff --git a/docs/1.0-dev/_sources/Howto/Starting/Part3/Tutorial-for-basic-MUSH-like-game.md.txt b/docs/1.0-dev/_sources/Howto/Starting/Part3/Tutorial-for-basic-MUSH-like-game.md.txt index b9c345f88c..f57c6ac330 100644 --- a/docs/1.0-dev/_sources/Howto/Starting/Part3/Tutorial-for-basic-MUSH-like-game.md.txt +++ b/docs/1.0-dev/_sources/Howto/Starting/Part3/Tutorial-for-basic-MUSH-like-game.md.txt @@ -660,6 +660,6 @@ The simple "Power" game mechanic should be easily expandable to something more f useful, same is true for the combat score principle. The `+attack` could be made to target a specific player (or npc) and automatically compare their relevant attributes to determine a result. -To continue from here, you can take a look at the [Tutorial World](../Part1/Tutorial-World-Introduction.md). For +To continue from here, you can take a look at the [Tutorial World](../Part1/Tutorial-World.md). For more specific ideas, see the [other tutorials and hints](../../Howto-Overview.md) as well as the [Evennia Component overview](../../../Components/Components-Overview.md). diff --git a/docs/1.0-dev/_sources/Setup/Changelog.md.txt b/docs/1.0-dev/_sources/Setup/Changelog.md.txt new file mode 100644 index 0000000000..d3b02f135e --- /dev/null +++ b/docs/1.0-dev/_sources/Setup/Changelog.md.txt @@ -0,0 +1,701 @@ +# Changelog + +### Evennia 1.0 + +> Not released yet +> 2019-2022 develop branch (WIP) + +Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10 + +- New `drop:holds()` lock default to limit dropping nonsensical things. Access check + defaults to True for backwards-compatibility in 0.9, will be False in 1.0 +- REST API allows you external access to db objects through HTTP requests (Tehom) +- `Object.normalize_name` and `.validate_name` added to (by default) enforce latinify + on character name and avoid potential exploits using clever Unicode chars (trhr) +- New `utils.format_grid` for easily displaying long lists of items in a block. +- 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 + replaceable via `settings.py` for modding enthusiasts. (volund) +- 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 multi-match 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. +- Add `tags.has()` method for checking if an object has a tag or tags (PR by ChrisLR) +- Make IP throttle use Django-based cache system for optional persistence (PR by strikaco) +- Renamed Tutorial classes "Weapon" and "WeaponRack" to "TutorialWeapon" and + "TutorialWeaponRack" to prevent collisions with classes in mygame +- New `crafting` contrib, adding a full crafting subsystem (Griatch 2020) +- The `rplanguage` contrib now auto-capitalizes sentences and retains ellipsis (...). This + change means that proper nouns at the start of sentences will not be treated as nouns. +- Make MuxCommand `lhs/rhslist` always be lists, also if empty (used to be the empty string) +- Fix typo in UnixCommand contrib, where `help` was given as `--hel`. +- Latin (la) i18n translation (jamalainm) +- Made the `evennia` dir possible to use without gamedir for purpose of doc generation. +- Make Scripts' timer component independent from script object deletion; can now start/stop + timer without deleting Script. The `.persistent` flag now only controls if timer survives + reload - Script has to be removed with `.delete()` like other typeclassed entities. +- Add `utils.repeat` and `utils.unrepeat` as shortcuts to TickerHandler add/remove, similar + to how `utils.delay` is a shortcut for TaskHandler add. +- Refactor the classic `red_button` example to use `utils.delay/repeat` and modern recommended + code style and paradigms instead of relying on `Scripts` for everything. +- Expand `CommandTest` with ability to check multiple message-receivers; inspired by PR by + user davewiththenicehat. Also add new doc string. +- Add central `FuncParser` as a much more powerful replacement for the old `parse_inlinefunc` + function. +- Add `evennia/utils/verb_conjugation` for automatic verb conjugation (English only). This + is useful for implementing actor-stance emoting for sending a string to different targets. +- New version of Italian translation (rpolve) +- `utils.evmenu.ask_yes_no` is a helper function that makes it easy to ask a yes/no question + to the user and respond to their input. This complements the existing `get_input` helper. +- Allow sending messages with `page/tell` without a `=` if target name contains no spaces. +- New FileHelpStorage system allows adding help entries via external files. +- `sethelp` command now warns if shadowing other help-types when creating a new + entry. +- Help command now uses `view` lock to determine if cmd/entry shows in index and + `read` lock to determine if it can be read. It used to be `view` in the role + of the latter. Migration swaps these around. +- In modules given by `settings.PROTOTYPE_MODULES`, spawner will now first look for a global + list `PROTOTYPE_LIST` of dicts before loading all dicts in the module as prototypes. +- New Channel-System using the `channel` command and nicks. Removed the `ChannelHandler` and the + concept of a dynamically created `ChannelCmdSet`. +- Add `Msg.db_receiver_external` field to allowe external, string-id message-receivers. +- Renamed `app.css` to `website.css` for consistency. Removed old prosimii-css files. +- Remove `mygame/web/static_overrides` and -`template_overrides`, reorganize website/admin/client/api + into a more consistent structure for overriding. Expanded webpage documentation considerably. +- REST API list-view was shortened (#2401). New CSS/HTML. Add ReDoc for API autodoc page. +- Update and fix dummyrunner with cleaner code and setup. +- Made `iter_to_str` format prettier strings, using Oxford comma. +- Added an MXP anchor tag to also support clickable web links. +- New `tasks` command for managing tasks started with `utils.delay` (PR by davewiththenicehat) +- Make `help` index output clickable for webclient/clients with MXP (PR by davewiththenicehat) +- Custom `evennia` launcher commands (e.g. `evennia mycmd foo bar`). Add new commands as callables + accepting `*args`, as `settings.EXTRA_LAUNCHER_COMMANDS = {'mycmd': 'path.to.callable', ...}`. +- New `XYZGrid` contrib, adding x,y,z grid coordinates with in-game map and + pathfinding. Controlled outside of the game via custom evennia launcher command. +- `Script.delete` has new kwarg `stop_task=True`, that can be used to avoid + infinite recursion when wanting to set up Script to delete-on-stop. +- Command executions now done on copies to make sure `yield` don't cause crossovers. Add + `Command.retain_instance` flag for reusing the same command instance. +- The `typeclass` command will now correctly search the correct database-table for the target + obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc). +- Merged `script` and `scripts` commands into one, for both managing global- and + on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`. +- Keep GMCP function case if outputfunc starts with capital letter (so `cmd_name` -> `Cmd.Name` + but `Cmd_nAmE` -> `Cmd.nAmE`). This helps e.g Mudlet's legacy `Client_GUI` implementation) +- Prototypes now allow setting `prototype_parent` directly to a prototype-dict. + This makes it easier when dynamically building in-module prototypes. +- `RPSystem contrib` was expanded to support case, so /tall becomes 'tall man' + while /Tall becomes 'Tall man'. One can turn this off if wanting the old style. +- Change `EvTable` fixed-height rebalance algorithm to fill with empty lines at end of + column instead of inserting rows based on cell-size (could be mistaken for a bug). +- Split `return_appearance` hook with helper methods and have it use a template + string in order to make it easier to override. +- Add validation question to default account creation. +- Add `LOCALECHO` client option to add server-side echo for clients that does + not support this (useful for getting a complete log). +- Make `@lazy_property` decorator create read/delete-protected properties. This is + because it's used for handlers, and e.g. self.locks=[] is a common beginner mistake. +- Add `$pron()` inlinefunc for pronoun parsing in actor-stance strings using + `msg_contents`. +- Update defauklt website to show Telnet/SSL/SSH connect info. Added new + `SERVER_HOSTNAME` setting for use in the server:port stanza. +- Changed all `at_before/after_*` hooks to `at_pre/post_*` for consistency + across Evennia (the old names still work but are deprecated) +- Change `settings.COMMAND_DEFAULT_ARG_REGEX` default from `None` to a regex meaning that + a space or `/` must separate the cmdname and args. This better fits common expectations. +- Add confirmation question to `ban`/`unban` commands. +- Check new `teleport` and `teleport_here` lock-types in `teleport` command to optionally + allow to limit teleportation of an object or to a specific destination. +- Add `settings.MXP_ENABLED=True` and `settings.MXP_OUTGOING_ONLY=True` as sane defaults, + to avoid known security issues with players entering MXP links. +- Add browser name to webclient `CLIENT_NAME` in `session.protocol_flags`, e.g. + `"Evennia webclient (websocket:firefox)"` or `"evennia webclient (ajax:chrome)"`. +- `TagHandler.add/has(tag=...)` kwarg changed to `add/has(key=...)` for consistency + with other handlers. +- Make `DefaultScript.delete`, `DefaultChannel.delete` and `DefaultAccount.delete` return + bool True/False if deletion was successful (like `DefaultObject.delete` before them) +- `contrib.custom_gametime` days/weeks/months now always starts from 1 (to match + the standard calendar form ... there is no month 0 every year after all). +- `AttributeProperty`/`NAttributeProperty` to allow managing Attributes/NAttributes + on typeclasses in the same way as Django fields. +- Give build/system commands a `@name` to fall back to if the non-@ name is used + by another command (like `open` and `@open`. If no duplicate, @ is optional. +- Move legacy channel-management commands (`ccreate`, `addcom` etc) to a contrib + since their work is now fully handled by the single `channel` command. +- Expand `examine` command's code to much more extensible and modular. Show + attribute categories and value types (when not strings). +- `AttributeHandler.remove(key, return_exception=False, category=None, ...)` changed + to `.remove(key, category=None, return_exception=False, ...)` for consistency. +- New `command cooldown` contrib for making it easier to manage commands using + dynamic cooldowns between uses (owllex) +- Restructured `contrib/` folder, placing all contribs as separate packages under + subfolders. All imports will need to be updated. +- Made `MonitorHandler.add/remove` support `category` for monitoring Attributes + with a category (before only key was used, ignoring category entirely). +- Move `create_*` functions into db managers, leaving `utils.create` only being + wrapper functions (consistent with `utils.search`). No change of api otherwise. +- Add support for `$dbref()` and `$search` when assigning an Attribute value + with the `set` command. This allows assigning real objects from in-game. +- Add ability to examine `/script` and `/channel` entities with `examine` command. +- Homogenize manager search methods to return querysets and not lists. +- Restructure unit tests to always honor default settings; make new parents in + on location for easy use in game dir. + + +## Evennia 0.9.5 + +> 2019-2020 +> Released 2020-11-14. +> Transitional release, including new doc system. + +Backported from develop: Python 3.8, 3.9 support. Django 3.2+ support, Twisted 21+ support. + +- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False +- `py` command now reroutes stdout to output results in-game client. `py` +without arguments starts a full interactive Python console. +- Webclient default to a single input pane instead of two. Now defaults to no help-popup. +- Webclient fix of prompt display +- Webclient multimedia support for relaying images, video and sounds via + `.msg(image=URL)`, `.msg(video=URL)` + and `.msg(audio=URL)` +- Add Spanish translation (fermuch) +- Expand `GLOBAL_SCRIPTS` container to always start scripts and to include all + global scripts regardless of how they were created. +- Change settings to always use lists instead of tuples, to make mutable + settings easier to add to. (#1912) +- Make new `CHANNEL_MUDINFO` setting for specifying the mudinfo channel +- Make `CHANNEL_CONNECTINFO` take full channel definition +- Make `DEFAULT_CHANNELS` list auto-create channels missing at reload +- Webclient `ANSI->HTML` parser updated. Webclient line width changed from 1.6em to 1.1em + to better make ANSI graphics look the same as for third-party clients +- `AttributeHandler.get(return_list=True)` will return `[]` if there are no + Attributes instead of `[None]`. +- Remove `pillow` requirement (install especially if using imagefield) +- Add Simplified Korean translation (aceamro) +- Show warning on `start -l` if settings contains values unsafe for production. +- Make code auto-formatted with Black. +- Make default `set` command able to edit nested structures (PR by Aaron McMillan) +- Allow running Evennia test suite from core repo with `make test`. +- Return `store_key` from `TickerHandler.add` and add `store_key` as a kwarg to + the `TickerHandler.remove` method. This makes it easier to manage tickers. +- EvMore auto-justify now defaults to False since this works better with all types + of texts (such as tables). New `justify` bool. Old `justify_kwargs` remains + but is now only used to pass extra kwargs into the justify function. +- EvMore `text` argument can now also be a list or a queryset. Querysets will be + sliced to only return the required data per page. +- Improve performance of `find` and `objects` commands on large data sets (strikaco) +- New `CHANNEL_HANDLER_CLASS` setting allows for replacing the ChannelHandler entirely. +- Made `py` interactive mode support regular quit() and more verbose. +- Made `Account.options.get` accept `default=None` kwarg to mimic other uses of get. Set + the new `raise_exception` boolean if ranting to raise KeyError on a missing key. +- Moved behavior of unmodified `Command` and `MuxCommand` `.func()` to new + `.get_command_info()` method for easier overloading and access. (Volund) +- Removed unused `CYCLE_LOGFILES` setting. Added `SERVER_LOG_DAY_ROTATION` + and `SERVER_LOG_MAX_SIZE` (and equivalent for PORTAL) to control log rotation. +- Addded `inside_rec` lockfunc - if room is locked, the normal `inside()` lockfunc will + fail e.g. for your inventory objs (since their loc is you), whereas this will pass. +- RPSystem contrib's CmdRecog will now list all recogs if no arg is given. Also multiple + bugfixes. +- Remove `dummy@example.com` as a default account email when unset, a string is no longer + required by Django. +- Fixes to `spawn`, make updating an existing prototype/object work better. Add `/raw` switch + to `spawn` command to extract the raw prototype dict for manual editing. +- `list_to_string` is now `iter_to_string` (but old name still works as legacy alias). It will + now accept any input, including generators and single values. +- EvTable should now correctly handle columns with wider asian-characters in them. +- Update Twisted requirement to >=2.3.0 to close security vulnerability +- Add `$random` inlinefunc, supports minval,maxval arguments that can be ints and floats. +- Add `evennia.utils.inlinefuncs.raw()` as a helper to escape inlinefuncs in a string. +- Make CmdGet/Drop/Give give proper error if `obj.move_to` returns `False`. +- Make `Object/Room/Exit.create`'s `account` argument optional. If not given, will set perms + to that of the object itself (along with normal Admin/Dev permission). +- Make `INLINEFUNC_STACK_MAXSIZE` default visible in `settings_default.py`. +- Change how `ic` finds puppets; non-priveleged users will use `_playable_characters` list as + candidates, Builders+ will use list, local search and only global search if no match found. +- Make `cmd.at_post_cmd()` always run after `cmd.func()`, even when the latter uses delays + with yield. +- `EvMore` support for db queries and django paginators as well as easier to override for custom + pagination (e.g. to create EvTables for every page instead of splittine one table) +- Using `EvMore pagination`, dramatically improves performance of `spawn/list` and `scripts` listings + (100x speed increase for displaying 1000+ prototypes/scripts). +- `EvMenu` now uses the more logically named `.ndb._evmenu` instead of `.ndb._menutree` to store itself. + Both still work for backward compatibility, but `_menutree` is deprecated. +- `EvMenu.msg(txt)` added as a central place to send text to the user, makes it easier to override. + Default `EvMenu.msg` sends with OOB type="menu" for use with OOB and webclient pane-redirects. +- New EvMenu templating system for quickly building simpler EvMenus without as much code. +- Add `Command.client_height()` method to match existing `.client_width` (stricako) +- Include more Web-client info in `session.protocol_flags`. +- Fixes in multi-match situations - don't allow finding/listing multimatches for 3-box when + only two boxes in location. +- Fix for TaskHandler with proper deferred returns/ability to cancel etc (PR by davewiththenicehat) +- Add `PermissionHandler.check` method for straight string perm-checks without needing lockstrings. +- Add `evennia.utils.utils.strip_unsafe_input` for removing html/newlines/tags from user input. The + `INPUT_CLEANUP_BYPASS_PERMISSIONS` is a list of perms that bypass this safety stripping. +- Make default `set` and `examine` commands aware of Attribute categories. + +## Evennia 0.9 + +> 2018-2019 +> Released Oct 2019 + +### Distribution + +- New requirement: Python 3.7 (py2.7 support removed) +- Django 2.1 +- Twisted 19.2.1 +- Autobahn websockets (removed old tmwx) +- Docker image updated + +### Commands + +- Remove `@`-prefix from all default commands (prefixes still work, optional) +- Removed default `@delaccount` command, incorporating as `@account/delete` instead. Added confirmation + question. +- Add new `@force` command to have another object perform a command. +- Add the Portal uptime to the `@time` command. +- Make the `@link` command first make a local search before a global search. +- Have the default Unloggedin-look command look for optional `connection_screen()` callable in + `mygame/server/conf/connection_screen.py`. This allows for more flexible welcome screens + that are calculated on the fly. +- `@py` command now defaults to escaping html tags in its output when viewing in the webclient. + Use new `/clientraw` switch to get old behavior (issue #1369). +- Shorter and more informative, dynamic, listing of on-command vars if not + setting func() in child command class. +- New Command helper methods + - `.client_width()` returns client width of the session running the command. + - `.styled_table(*args, **kwargs)` returns a formatted evtable styled by user's options + - `.style_header(*args, **kwargs)` creates styled header entry + - `.style_separator(*args, **kwargs)` " separator + - `.style_footer(*args, **kwargs)` " footer + +### Web + +- Change webclient from old txws version to use more supported/feature-rich Autobahn websocket library + +#### Evennia game index + +- Made Evennia game index client a part of core - now configured from settings file (old configs + need to be moved) +- The `evennia connections` command starts a wizard that helps you connect your game to the game index. +- The game index now accepts games with no public telnet/webclient info (for early prototypes). + +#### New golden-layout based Webclient UI (@friarzen) +- Features + - Much slicker behavior and more professional look + - Allows tabbing as well as click and drag of panes in any grid position + - Renaming tabs, assignments of data tags and output types are simple per-pane menus now + - Any number of input panes, with separate histories + - Button UI (disabled in JS by default) + +#### Web/Django standard initiative (@strikaco) +- Features + - Adds a series of web-based forms and generic class-based views + - Accounts + - Register - Enhances registration; allows optional collection of email address + - Form - Adds a generic Django form for creating Accounts from the web + - Characters + - Create - Authenticated users can create new characters from the website (requires associated form) + - Detail - Authenticated and authorized users can view select details about characters + - List - Authenticated and authorized users can browse a list of all characters + - Manage - Authenticated users can edit or delete owned characters from the web + - Form - Adds a generic Django form for creating characters from the web + - Channels + - Detail - Authorized users can view channel logs from the web + - List - Authorized users can browse a list of all channels + - Help Entries + - Detail - Authorized users can view help entries from the web + - List - Authorized users can browse a list of all help entries from the web + - Navbar changes + - Characters - Link to character list + - Channels - Link to channel list + - Help - Link to help entry list + - Puppeting + - Users can puppet their own characters within the context of the website + - Dropdown + - Link to create characters + - Link to manage characters + - Link to quick-select puppets + - Link to password change workflow +- Functions + - Updates Bootstrap to v4 stable + - Enables use of Django Messages framework to communicate with users in browser + - Implements webclient/website `_shared_login` functionality as Django middleware + - 'account' and 'puppet' are added to all request contexts for authenticated users + - Adds unit tests for all web views +- Cosmetic + - Prettifies Django 'forgot password' workflow (requires SMTP to actually function) + - Prettifies Django 'change password' workflow +- Bugfixes + - Fixes bug on login page where error messages were not being displayed + - Remove strvalue field from admin; it made no sense to have here, being an optimization field + for internal use. + +### Prototypes + +- `evennia.prototypes.save_prototype` now takes the prototype as a normal + argument (`prototype`) instead of having to give it as `**prototype`. +- `evennia.prototypes.search_prototype` has a new kwarg `require_single=False` that + raises a KeyError exception if query gave 0 or >1 results. +- `evennia.prototypes.spawner` can now spawn by passing a `prototype_key` + +### Typeclasses + +- Add new methods on all typeclasses, useful specifically for object handling from the website/admin: + + `web_get_admin_url()`: Returns the path to the object detail page in the Admin backend. + + `web_get_create_url()`: Returns the path to the typeclass' creation page on the website, if implemented. + + `web_get_absolute_url()`: Returns the path to the object's detail page on the website, if implemented. + + `web_get_update_url()`: Returns the path to the object's update page on the website, if implemented. + + `web_get_delete_url()`: Returns the path to the object's delete page on the website, if implemented. +- All typeclasses have new helper class method `create`, which encompasses useful functionality + that used to be embedded for example in the respective `@create` or `@connect` commands. +- DefaultAccount now has new class methods implementing many things that used to be in unloggedin + commands (these can now be customized on the class instead): + + `is_banned()`: Checks if a given username or IP is banned. + + `get_username_validators`: Return list of validators for username validation (see + `settings.AUTH_USERNAME_VALIDATORS`) + + `authenticate`: Method to check given username/password. + + `normalize_username`: Normalizes names so (for Unicode environments) users cannot mimic existing usernames by replacing select characters with visually-similar Unicode chars. + + `validate_username`: Mechanism for validating a username based on predefined Django validators. + + `validate_password`: Mechanism for validating a password based on predefined Django validators. + + `set_password`: Apply password to account, using validation checks. +- `AttributeHandler.remove` and `TagHandler.remove` can now be used to delete by-category. If neither + key nor category is given, they now work the same as .clear(). + +### Protocols + +- Support for `Grapevine` MUD-chat network ("channels" supported) + +### Server + +- Convert ServerConf model to store its values as a Picklefield (same as + Attributes) instead of using a custom solution. +- OOB: Add support for MSDP LIST, REPORT, UNREPORT commands (re-mapped to `msdp_list`, + `msdp_report`, `msdp_unreport`, inlinefuncs) +- Added `evennia.ANSIString` to flat API. +- Server/Portal log files now cycle to names on the form `server_.log_19_03_08_` instead of `server.log___19.3.8`, retaining + unix file sorting order. +- Django signals fire for important events: Puppet/Unpuppet, Object create/rename, Login, + Logout, Login fail Disconnect, Account create/rename + +### Settings + +- `GLOBAL_SCRIPTS` - dict defining typeclasses of global scripts to store on the new + `evennia.GLOBAL_SCRIPTS` container. These will auto-start when Evennia start and will always + exist. +- `OPTIONS_ACCOUNTS_DEFAULT` - option dict with option defaults and Option classes +- `OPTION_CLASS_MODULES` - classes representing an on-Account Option, on special form +- `VALIDATOR_FUNC_MODULES` - (general) text validator functions, for verifying an input + is on a specific form. + +### Utils + +- `evennia` launcher now fully handles all django-admin commands, like running tests in parallel. +- `evennia.utils.create.account` now also takes `tags` and `attrs` keywords. +- `evennia.utils.interactive` decorator can now allow you to use yield(secs) to pause operation + in any function, not just in Command.func. Likewise, response = yield(question) will work + if the decorated function has an argument or kwarg `caller`. +- Added many more unit tests. +- Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')` + since the size is more likely to be changed on the command line. +- `utils.to_str(text, session=None)` now acts as the old `utils.to_unicode` (which was removed). + This converts to the str() type (not to a byte-string as in Evennia 0.8), trying different + encodings. This function will also force-convert any object passed to it into a string (so + `force_string` flag was removed and assumed always set). +- `utils.to_bytes(text, session=None)` replaces the old `utils.to_str()` functionality and converts + str to bytes. +- `evennia.MONITOR_HANDLER.all` now takes keyword argument `obj` to only retrieve monitors from that specific + Object (rather than all monitors in the entire handler). +- Support adding `\f` in command doc strings to force where EvMore puts page breaks. +- Validation Functions now added with standard API to homogenize user input validation. +- Option Classes added to make storing user-options easier and smoother. +- `evennia.VALIDATOR_CONTAINER` and `evennia.OPTION_CONTAINER` added to load these. + +### Contribs + +- Evscaperoom - a full puzzle engine for making multiplayer escape rooms in Evennia. Used to make + the entry for the MUD-Coder's Guild's 2019 Game Jam with the theme "One Room", where it ranked #1. +- Evennia game-index client no longer a contrib - moved into server core and configured with new + setting `GAME_INDEX_ENABLED`. +- The `extended_room` contrib saw some backwards-incompatible refactoring: + + All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now + it's `CmdExtendedRoomLook` etc. + + The `detail` command was broken out of the `desc` command and is now a new, stand-alone command + `CmdExtendedRoomDetail`. This was done to make things easier to extend and to mimic how the detail + command works in the tutorial-world. + + The `detail` command now also supports deleting details (like the tutorial-world version). + + The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way + to install the extended-room contrib. +- Reworked `menu_login` contrib to use latest EvMenu standards. Now also supports guest logins. +- Mail contrib was refactored to have optional Command classes `CmdMail` for OOC+IC mail (added + to the CharacterCmdSet and `CmdMailCharacter` for IC-only mailing between chars (added to CharacterCmdSet) + +### Translations + +- Simplified chinese, courtesy of user MaxAlex. + + +## Evennia 0.8 + +> 2017-2018 +> Released Nov 2018 + +### Requirements + +- Up requirements to Django 1.11.x, Twisted 18 and pillow 5.2.0 +- Add `inflect` dependency for automatic pluralization of object names. + +### Server/Portal + +- Removed `evennia_runner`, completely refactor `evennia_launcher.py` (the 'evennia' program) + with different functionality). +- Both Portal/Server are now stand-alone processes (easy to run as daemon) +- Made Portal the AMP Server for starting/restarting the Server (the AMP client) +- Dynamic logging now happens using `evennia -l` rather than by interactive mode. +- Made AMP secure against erroneous HTTP requests on the wrong port (return error messages). +- The `evennia istart` option will start/switch the Server in foreground (interactive) mode, where it logs + to terminal and can be stopped with Ctrl-C. Using `evennia reload`, or reloading in-game, will + return Server to normal daemon operation. +- For validating passwords, use safe Django password-validation backend instead of custom Evennia one. +- Alias `evennia restart` to mean the same as `evennia reload`. + +### Prototype changes + +- New OLC started from `olc` command for loading/saving/manipulating prototypes in a menu. +- Moved evennia/utils/spawner.py into the new evennia/prototypes/ along with all new + functionality around prototypes. +- A new form of prototype - database-stored prototypes, editable from in-game, was added. The old, + module-created prototypes remain as read-only prototypes. +- All prototypes must have a key `prototype_key` identifying the prototype in listings. This is + checked to be server-unique. Prototypes created in a module will use the global variable name they + are assigned to if no `prototype_key` is given. +- Prototype field `prototype` was renamed to `prototype_parent` to avoid mixing terms. +- All prototypes must either have `typeclass` or `prototype_parent` defined. If using + `prototype_parent`, `typeclass` must be defined somewhere in the inheritance chain. This is a + change from Evennia 0.7 which allowed 'mixin' prototypes without `typeclass`/`prototype_key`. To + make a mixin now, give it a default typeclass, like `evennia.objects.objects.DefaultObject` and just + override in the child as needed. +- Spawning an object using a prototype will automatically assign a new tag to it, named the same as + the `prototype_key` and with the category `from_prototype`. +- The spawn command was extended to accept a full prototype on one line. +- The spawn command got the /save switch to save the defined prototype and its key +- The command spawn/menu will now start an OLC (OnLine Creation) menu to load/save/edit/spawn prototypes. + +### EvMenu + +- Added `EvMenu.helptext_formatter(helptext)` to allow custom formatting of per-node help. +- Added `evennia.utils.evmenu.list_node` decorator for turning an EvMenu node into a multi-page listing. +- A `goto` option callable returning None (rather than the name of the next node) will now rerun the + current node instead of failing. +- Better error handling of in-node syntax errors. +- Improve dedent of default text/helptext formatter. Right-strip whitespace. +- Add `debug` option when creating menu - this turns off persistence and makes the `menudebug` + command available for examining the current menu state. + + +### Webclient + +- Webclient now uses a plugin system to inject new components from the html file. +- Split-windows - divide input field into any number of horizontal/vertical panes and + assign different types of server messages to them. +- Lots of cleanup and bug fixes. +- Hot buttons plugin (friarzen) (disabled by default). + +### Locks + +- New function `evennia.locks.lockhandler.check_lockstring`. This allows for checking an object + against an arbitrary lockstring without needing the lock to be stored on an object first. +- New function `evennia.locks.lockhandler.validate_lockstring` allows for stand-alone validation + of a lockstring. +- New function `evennia.locks.lockhandler.get_all_lockfuncs` gives a dict {"name": lockfunc} for + all available lock funcs. This is useful for dynamic listings. + + +### Utils + +- Added new `columnize` function for easily splitting text into multiple columns. At this point it + is not working too well with ansi-colored text however. +- Extend the `dedent` function with a new `baseline_index` kwarg. This allows to force all lines to + the indentation given by the given line regardless of if other lines were already a 0 indentation. + This removes a problem with the original `textwrap.dedent` which will only dedent to the least + indented part of a text. +- Added `exit_cmd` to EvMore pager, to allow for calling a command (e.g. 'look') when leaving the pager. +- `get_all_typeclasses` will return dict `{"path": typeclass, ...}` for all typeclasses available + in the system. This is used by the new `@typeclass/list` subcommand (useful for builders etc). +- `evennia.utils.dbserialize.deserialize(obj)` is a new helper function to *completely* disconnect + a mutable recovered from an Attribute from the database. This will convert all nested `_Saver*` + classes to their plain-Python counterparts. + +### General + +- Start structuring the `CHANGELOG` to list features in more detail. +- Docker image `evennia/evennia:develop` is now auto-built, tracking the develop branch. +- Inflection and grouping of multiple objects in default room (an box, three boxes) +- `evennia.set_trace()` is now a shortcut for launching pdb/pudb on a line in the Evennia event loop. +- Removed the enforcing of `MAX_NR_CHARACTERS=1` for `MULTISESSION_MODE` `0` and `1` by default. +- Add `evennia.utils.logger.log_sec` for logging security-related messages (marked SS in log). + +### Contribs + +- `Auditing` (Johnny): Log and filter server input/output for security purposes +- `Build Menu` (vincent-lg): New @edit command to edit object properties in a menu. +- `Field Fill` (Tim Ashley Jenkins): Wraps EvMenu for creating submittable forms. +- `Health Bar` (Tim Ashley Jenkins): Easily create colorful bars/meters. +- `Tree select` (Fluttersprite): Wrap EvMenu to create a common type of menu from a string. +- `Turnbattle suite` (Tim Ashley Jenkins)- the old `turnbattle.py` was moved into its own + `turnbattle/` package and reworked with many different flavors of combat systems: + - `tb_basic` - The basic turnbattle system, with initiative/turn order attack/defense/damage. + - `tb_equip` - Adds weapon and armor, wielding, accuracy modifiers. + - `tb_items` - Extends `tb_equip` with item use with conditions/status effects. + - `tb_magic` - Extends `tb_equip` with spellcasting. + - `tb_range` - Adds system for abstract positioning and movement. + - The `extended_room` contrib saw some backwards-incompatible refactoring: + - All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now + it's `CmdExtendedRoomLook` etc. + - The `detail` command was broken out of the `desc` command and is now a new, stand-alone command + `CmdExtendedRoomDetail`. This was done to make things easier to extend and to mimic how the detail + command works in the tutorial-world. + - The `detail` command now also supports deleting details (like the tutorial-world version). + - The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way + to install the extended-room contrib. +- Updates and some cleanup of existing contribs. + + +### Internationalization + +- Polish translation by user ogotai + +# Overview-Changelogs + +> These are changelogs from a time before we used formal version numbers. + +## Sept 2017: +Release of Evennia 0.7; upgrade to Django 1.11, change 'Player' to +'Account', rework the website template and a slew of other updates. +Info on what changed and how to migrate is found here: +https://groups.google.com/forum/#!msg/evennia/0JYYNGY-NfE/cDFaIwmPBAAJ + +## Feb 2017: +New devel branch created, to lead up to Evennia 0.7. + +## Dec 2016: +Lots of bugfixes and considerable uptick in contributors. Unittest coverage +and PEP8 adoption and refactoring. + +## May 2016: +Evennia 0.6 with completely reworked Out-of-band system, making +the message path completely flexible and built around input/outputfuncs. +A completely new webclient, split into the evennia.js library and a +gui library, making it easier to customize. + +## Feb 2016: +Added the new EvMenu and EvMore utilities, updated EvEdit and cleaned up +a lot of the batchcommand functionality. Started work on new Devel branch. + +## Sept 2015: +Evennia 0.5. Merged devel branch, full library format implemented. + +## Feb 2015: +Development currently in devel/ branch. Moved typeclasses to use +django's proxy functionality. Changed the Evennia folder layout to a +library format with a stand-alone launcher, in preparation for making +an 'evennia' pypy package and using versioning. The version we will +merge with will likely be 0.5. There is also work with an expanded +testing structure and the use of threading for saves. We also now +use Travis for automatic build checking. + +## Sept 2014: +Updated to Django 1.7+ which means South dependency was dropped and +minimum Python version upped to 2.7. MULTISESSION_MODE=3 was added +and the web customization system was overhauled using the latest +functionality of django. Otherwise, mostly bug-fixes and +implementation of various smaller feature requests as we got used +to github. Many new users have appeared. + +## Jan 2014: +Moved Evennia project from Google Code to github.com/evennia/evennia. + +## Nov 2013: +Moved the internal webserver into the Server and added support for +out-of-band protocols (MSDP initially). This large development push +also meant fixes and cleanups of the way attributes were handled. +Tags were added, along with proper handlers for permissions, nicks +and aliases. + +## May 2013: +Made players able to control more than one Character at the same +time, through the MULTISESSION_MODE=2 addition. This lead to a lot +of internal changes for the server. + +## Oct 2012: +Changed Evennia from the Modified Artistic 1.0 license to the more +standard and permissive BSD license. Lots of updates and bug fixes as +more people start to use it in new ways. Lots of new caching and +speed-ups. + +## March 2012: +Evennia's API has changed and simplified slightly in that the +base-modules where removed from game/gamesrc. Instead admins are +encouraged to explicitly create new modules under game/gamesrc/ when +they want to implement their game - gamesrc/ is empty by default +except for the example folders that contain template files to use for +this purpose. We also added the ev.py file, implementing a new, flat +API. Work is ongoing to add support for mud-specific telnet +extensions, notably the MSDP and GMCP out-of-band extensions. On the +community side, evennia's dev blog was started and linked on planet +Mud-dev aggregator. + +## Nov 2011: +After creating several different proof-of-concept game systems (in +contrib and privately) as well testing lots of things to make sure the +implementation is basically sound, we are declaring Evennia out of +Alpha. This can mean as much or as little as you want, admittedly - +development is still heavy but the issue list is at an all-time low +and the server is slowly stabilizing as people try different things +with it. So Beta it is! + +## Aug 2011: +Split Evennia into two processes: Portal and Server. After a lot of +work trying to get in-memory code-reloading to work, it's clear this +is not Python's forte - it's impossible to catch all exceptions, +especially in asynchronous code like this. Trying to do so results in +hackish, flakey and unstable code. With the Portal-Server split, the +Server can simply be rebooted while players connected to the Portal +remain connected. The two communicates over twisted's AMP protocol. + +## May 2011: +The new version of Evennia, originally hitting trunk in Aug2010, is +maturing. All commands from the pre-Aug version, including IRC/IMC2 +support works again. An ajax web-client was added earlier in the year, +including moving Evennia to be its own webserver (no more need for +Apache or django-testserver). Contrib-folder added. + +## Aug 2010: +Evennia-griatch-branch is ready for merging with trunk. This marks a +rather big change in the inner workings of the server, such as the +introduction of TypeClasses and Scripts (as compared to the old +ScriptParents and Events) but should hopefully bring everything +together into one consistent package as code development continues. + +## May 2010: +Evennia is currently being heavily revised and cleaned from +the years of gradual piecemeal development. It is thus in a very +'Alpha' stage at the moment. This means that old code snippets +will not be backwards compatabile. Changes touch almost all +parts of Evennia's innards, from the way Objects are handled +to Events, Commands and Permissions. + +## April 2010: +Griatch takes over Maintainership of the Evennia project from +the original creator Greg Taylor. + +# Older + +Earlier revisions, with previous maintainer, used SVN on Google Code +and have no changelogs. + +First commit (Evennia's birthday) was November 20, 2006. diff --git a/docs/1.0-dev/_sources/Setup/Extended-Installation.md.txt b/docs/1.0-dev/_sources/Setup/Extended-Installation.md.txt deleted file mode 100644 index 9b7bcbe566..0000000000 --- a/docs/1.0-dev/_sources/Setup/Extended-Installation.md.txt +++ /dev/null @@ -1,557 +0,0 @@ -# Getting Started - - -This will help you download, install and start Evennia for the first time. - -> Note: You don't need to make anything visible to the 'net in order to run and -> test out Evennia. Apart from downloading and updating you don't even need an -> internet connection until you feel ready to share your game with the world. - -- [Quick Start](#quick-start) -- [Requirements](#requirements) -- [Linux Install](#linux-install) -- [Mac Install](#mac-install) -- [Windows Install](#windows-install) -- [Running in Docker](./Running-Evennia-in-Docker.md) -- [Where to Go Next](#where-to-go-next) -- [Troubleshooting](#troubleshooting) -- [Glossary of terms](../Glossary.md) - -## Quick Start - -For the impatient. If you have trouble with a step, you should jump on to the -more detailed instructions for your platform. - -1. Install Python, GIT and python-virtualenv. Start a Console/Terminal. -2. `cd` to some place you want to do your development (like a folder - `/home/anna/muddev/` on Linux or a folder in your personal user directory on Windows). -3. `git clone https://github.com/evennia/evennia.git` (a new folder `evennia` is created) -4. `python -m venv evenv` (a new folder `evenv` is created) -5. `source evenv/bin/activate` (Linux, Mac), `evenv\Scripts\activate` (Windows) -6. `pip install -e evennia` -7. `evennia --init mygame` -8. `cd mygame` -9. `evennia migrate` -10. `evennia start` (make sure to make a superuser when asked) -Evennia should now be running and you can connect to it by pointing a web browser to -`http://localhost:4001` or a MUD telnet client to `localhost:4000` (use `127.0.0.1` if your OS does -not recognize `localhost`). - -We also release [Docker images](./Running-Evennia-in-Docker.md) -based on `master` and `develop` branches. - -## Requirements - -Any system that supports Python3.7+ should work. We'll describe how to install -everything in the following sections. -- Linux/Unix -- Windows (Vista, Win7, Win8, Win10) -- Mac OSX (>=10.5 recommended) - -- [Python](https://www.python.org) (v3.7, 3.8 and 3.9 are tested) - - [virtualenv](https://pypi.python.org/pypi/virtualenv) for making isolated - Python environments. Installed with `pip install virtualenv`. - -- [GIT](https://git-scm.com/) - version control software for getting and -updating Evennia itself - Mac users can use the -[git-osx-installer](https://code.google.com/p/git-osx-installer/) or the -[MacPorts version](https://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac). -- [Twisted](https://twistedmatrix.com) (v19.0+) - - [ZopeInterface](https://www.zope.org/Products/ZopeInterface) (v3.0+) - usually included in -Twisted packages - - Linux/Mac users may need the `gcc` and `python-dev` packages or equivalent. - - Windows users need [MS Visual C++](https://aka.ms/vs/16/release/vs_buildtools.exe) and *maybe* -[pypiwin32](https://pypi.python.org/pypi/pypiwin32). -- [Django](https://www.djangoproject.com) (v2.2.x), be warned that latest dev - version is usually untested with Evennia) - -## Linux Install - -If you run into any issues during the installation and first start, please -check out [Linux Troubleshooting](#linux-troubleshooting). - -For Debian-derived systems (like Ubuntu, Mint etc), start a terminal and -install the [dependencies](#requirements): - -``` -sudo apt-get update -sudo apt-get install python3 python3-pip python3-dev python3-setuptools python3-git -python3-virtualenv gcc - -# If you are using an Ubuntu version that defaults to Python3, like 18.04+, use this instead: -sudo apt-get update -sudo apt-get install python3.7 python3-pip python3.7-dev python3-setuptools virtualenv gcc - -``` -Note that, the default Python version for your distribution may still not be Python3.7 after this. -This is ok - we'll specify exactly which Python to use later. -You should make sure to *not* be `root` after this step, running as `root` is a -security risk. Now create a folder where you want to do all your Evennia -development: - -``` -mkdir muddev -cd muddev -``` - -Next we fetch Evennia itself: - -``` -git clone https://github.com/evennia/evennia.git -``` -A new folder `evennia` will appear containing the Evennia library. This only -contains the source code though, it is not *installed* yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a _virtualenv_. If you are unsure about what a -virtualenv is and why it's useful, see the [Glossary entry on -virtualenv](../Glossary.md#virtualenv). - -Run `python -V` to see which version of Python your system defaults to. - -``` -# If your Linux defaults to Python3.7+: -virtualenv evenv - -# If your Linux defaults to Python2 or an older version -# of Python3, you must instead point to Python3.7+ explicitly: -virtualenv -p /usr/bin/python3.7 evenv -``` - -A new folder `evenv` will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system (or the Linux distro lagging behind -on Python package versions). It will also always use the right version of Python. -Activate the virtualenv: - -``` -source evenv/bin/activate -``` - -The text `(evenv)` should appear next to your prompt to show that the virtual -environment is active. - -> Remember that you need to activate the virtualenv like this *every time* you -> start a new terminal to get access to the Python packages (notably the -> important `evennia` program) we are about to install. - -Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the `evennia/` and `evenv/` -folders) and run - -``` -pip install -e evennia -``` - -For more info about `pip`, see the [Glossary entry on pip](../Glossary.md#pip). If -install failed with any issues, see [Linux Troubleshooting](#linux-troubleshooting). - -Next we'll start our new game, here called "mygame". This will create yet -another new folder where you will be creating your new game: - -``` -evennia --init mygame -``` - -Your final folder structure should look like this: -``` -./muddev - evenv/ - evennia/ - mygame/ -``` - -You can [configure Evennia](./Server-Conf.md#settings-file) extensively, for example -to use a [different database](./Choosing-An-SQL-Server.md). For now we'll just stick -to the defaults though. - -``` -cd mygame -evennia migrate # (this creates the database) -evennia start # (create a superuser when asked. Email is optional.) -``` - -> Server logs are found in `mygame/server/logs/`. To easily view server logs -> live in the terminal, use `evennia -l` (exit the log-view with Ctrl-C). - -Your game should now be running! Open a web browser at `http://localhost:4001` -or point a telnet client to `localhost:4000` and log in with the user you -created. Check out [where to go next](#where-to-go-next). - - -## Mac Install - -The Evennia server is a terminal program. Open the terminal e.g. from -*Applications->Utilities->Terminal*. [Here is an introduction to the Mac -terminal](https://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) -if you are unsure how it works. If you run into any issues during the -installation, please check out [Mac Troubleshooting](#mac-troubleshooting). - -* Python should already be installed but you must make sure it's a high enough version. -([This](https://docs.python-guide.org/en/latest/starting/install/osx/) discusses - how you may upgrade it). Remember that you need Python3.7, not Python2.7! -* GIT can be obtained with -[git-osx-installer](https://code.google.com/p/git-osx-installer/) or via -MacPorts [as described -here](https://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac). -* If you run into issues with installing `Twisted` later you may need to -install gcc and the Python headers. - -After this point you should not need `sudo` or any higher privileges to install anything. - -Now create a folder where you want to do all your Evennia development: - -``` -mkdir muddev -cd muddev -``` - -Next we fetch Evennia itself: - -``` -git clone https://github.com/evennia/evennia.git -``` - -A new folder `evennia` will appear containing the Evennia library. This only -contains the source code though, it is not *installed* yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a _virtualenv_. If you are unsure about what a -virtualenv is and why it's useful, see the [Glossary entry on virtualenv](../Glossary.md#virtualenv). - -Run `python -V` to check which Python your system defaults to. - - -``` -# If your Mac defaults to Python3: -virtualenv evenv - -# If your Mac defaults to Python2 you need to specify the Python3.7 binary explicitly: -virtualenv -p /path/to/your/python3.7 evenv -``` - -A new folder `evenv` will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system. Activate the virtualenv: - -``` -source evenv/bin/activate -``` - -The text `(evenv)` should appear next to your prompt to show the virtual -environment is active. - -> Remember that you need to activate the virtualenv like this *every time* you -> start a new terminal to get access to the Python packages (notably the -> important `evennia` program) we are about to install. - -Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the `evennia/` and `evenv/` -folders) and run - -``` -pip install --upgrade pip # Old pip versions may be an issue on Mac. -pip install --upgrade setuptools # Ditto concerning Mac issues. -pip install -e evennia -``` - -For more info about `pip`, see the [Glossary entry on pip](../Glossary.md#pip). If -install failed with any issues, see [Mac Troubleshooting](#mac-troubleshooting). - -Next we'll start our new game. We'll call it "mygame" here. This creates a new -folder where you will be creating your new game: - -``` -evennia --init mygame -``` - -Your final folder structure should look like this: - -``` -./muddev - evenv/ - evennia/ - mygame/ -``` - -You can [configure Evennia](./Server-Conf.md#settings-file) extensively, for example -to use a [different database](./Choosing-An-SQL-Server.md). We'll go with the -defaults here. - -``` -cd mygame -evennia migrate # (this creates the database) -evennia start # (create a superuser when asked. Email is optional.) -``` - -> Server logs are found in `mygame/server/logs/`. To easily view server logs -> live in the terminal, use `evennia -l` (exit the log-view with Ctrl-C). - -Your game should now be running! Open a web browser at `http://localhost:4001` -or point a telnet client to `localhost:4000` and log in with the user you -created. Check out [where to go next](#where-to-go-next). - - -## Windows Install - -If you run into any issues during the installation, please check out -[Windows Troubleshooting](#windows-troubleshooting). - -> If you are running Windows10, consider using the Windows Subsystem for Linux -> ([WSL](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)) instead. -> You should then follow the Linux install instructions above. - -The Evennia server itself is a command line program. In the Windows launch -menu, start *All Programs -> Accessories -> command prompt* and you will get -the Windows command line interface. Here is [one of many tutorials on using the Windows command -line](https://www.bleepingcomputer.com/tutorials/windows-command-prompt-introduction/) -if you are unfamiliar with it. - -* Install Python [from the Python homepage](https://www.python.org/downloads/windows/). You will -need to be a -Windows Administrator to install packages. You want Python version **3.7.0** (latest verified -version), usually -the 64-bit version (although it doesn't matter too much). **When installing, make sure -to check-mark *all* install options, especially the one about making Python -available on the path (you may have to scroll to see it)**. This allows you to -just write `python` in any console without first finding where the `python` -program actually sits on your hard drive. -* You need to also get [GIT](https://git-scm.com/downloads) and install it. You -can use the default install options but when you get asked to "Adjust your PATH -environment", you should select the second option "Use Git from the Windows -Command Prompt", which gives you more freedom as to where you can use the -program. -* Finally you must install the [Microsoft Visual C++ compiler for -Python](https://aka.ms/vs/16/release/vs_buildtools.exe). Download and run the linked installer and -install the C++ tools. Keep all the defaults. Allow the install of the "Win10 SDK", even if you are -on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due -to a failure to build the "Twisted wheels", this is where you are missing things. -* You *may* need the [pypiwin32](https://pypi.python.org/pypi/pypiwin32) Python headers. Install -these only if you have issues. - -You can install Evennia wherever you want. `cd` to that location and create a -new folder for all your Evennia development (let's call it `muddev`). - -``` -mkdir muddev -cd muddev -``` - -> Hint: If `cd` isn't working you can use `pushd` instead to force the -> directory change. - -Next we fetch Evennia itself: - -``` -git clone https://github.com/evennia/evennia.git -``` - -A new folder `evennia` will appear containing the Evennia library. This only -contains the source code though, it is not *installed* yet. To isolate the -Evennia install and its dependencies from the rest of the system, it is good -Python practice to install into a _virtualenv_. If you are unsure about what a -virtualenv is and why it's useful, see the [Glossary entry on virtualenv](../Glossary.md#virtualenv). - -In your console, try `python -V` to see which version of Python your system -defaults to. - - -``` -pip install virtualenv - -# If your setup defaults to Python3.7: -virtualenv evenv - -# If your setup defaults to Python2, specify path to python3.exe explicitly: -virtualenv -p C:\Python37\python.exe evenv - -# If you get an infinite spooling response, press CTRL + C to interrupt and try using: -python -m venv evenv - -``` -A new folder `evenv` will appear (we could have called it anything). This -folder will hold a self-contained setup of Python packages without interfering -with default Python packages on your system. Activate the virtualenv: - -``` -# If you are using a standard command prompt, you can use the following: -evenv\scripts\activate.bat - -# If you are using a PS Shell, Git Bash, or other, you can use the following: -.\evenv\scripts\activate - -``` -The text `(evenv)` should appear next to your prompt to show the virtual -environment is active. - -> Remember that you need to activate the virtualenv like this *every time* you -> start a new console window if you want to get access to the Python packages -> (notably the important `evennia` program) we are about to install. - -Next, install Evennia into your active virtualenv. Make sure you are standing -at the top of your mud directory tree (so you see the `evennia` and `evenv` -folders when you use the `dir` command) and run - -``` -pip install -e evennia -``` -For more info about `pip`, see the [Glossary entry on pip](../Glossary.md#pip). If -the install failed with any issues, see [Windows Troubleshooting](#windows- -troubleshooting). -Next we'll start our new game, we'll call it "mygame" here. This creates a new folder where you will -be -creating your new game: - -``` -evennia --init mygame -``` - -Your final folder structure should look like this: - -``` -path\to\muddev - evenv\ - evennia\ - mygame\ -``` - -You can [configure Evennia](./Server-Conf.md#settings-file) extensively, for example -to use a [different database](./Choosing-An-SQL-Server.md). We'll go with the -defaults here. - -``` -cd mygame -evennia migrate # (this creates the database) -evennia start # (create a superuser when asked. Email is optional.) -``` - -> Server logs are found in `mygame/server/logs/`. To easily view server logs -> live in the terminal, use `evennia -l` (exit the log-view with Ctrl-C). - -Your game should now be running! Open a web browser at `http://localhost:4001` -or point a telnet client to `localhost:4000` and log in with the user you -created. Check out [where to go next](#where-to-go-next). - -## Non-interactive setup - -When you first run `evennia start` after having created the database, you will be asked -to interactively insert the superuser username, email and password. If you need to do -this in an automated faction (such as in an automated build flow), you can supply those -values as environment variables, `EVENNIA_SUPERUSER_USERNAME`, `EVENNIA_SUPERUSER_EMAIL` and -`EVENNIA_SUPERUSER_PASSWORD`. The email can be left out and will then be set to be the -empty string. - -Use this to start Evennia (the envvars will be ignored on subsequent starts): - -``` -EVENNIA_SUPERUSER_USERNAME=Foo EVENNIA_SUPERUSER_PASSWORD=MygreatPwd evennia start - -``` - -## Where to Go Next - -Welcome to Evennia! Your new game is fully functioning, but empty. If you just -logged in, stand in the `Limbo` room and run - - @batchcommand tutorial_world.build - -to build [Evennia's tutorial world](../Howto/Starting/Part1/Tutorial-World-Introduction.md) - it's a small solo quest to -explore. Only run the instructed `@batchcommand` once. You'll get a lot of text scrolling by as the -tutorial is built. Once done, the `tutorial` exit will have appeared out of Limbo - just write -`tutorial` to enter it. - -Once you get back to `Limbo` from the tutorial (if you get stuck in the tutorial quest you can do -`@tel #2` to jump to Limbo), a good idea is to learn how to [start, stop and reload](Start-Stop- -Reload) the Evennia server. You may also want to familiarize yourself with some -[commonly used terms in our Glossary](../Glossary.md). After that, why not experiment with -[creating some new items and build some new rooms](../Howto/Starting/Part1/Building-Quickstart.md) out from Limbo. - -From here on, you could move on to do one of our [introductory tutorials](../Howto/Howto-Overview.md) or simply dive -headlong into Evennia's comprehensive [manual](https://github.com/evennia/evennia/wiki). While -Evennia has no major game systems out of the box, we do supply a range of optional *contribs* that -you can use or borrow from. They range from dice rolling and alternative color schemes to barter and -combat systems. You can find the [growing list of contribs -here](https://github.com/evennia/evennia/blob/master/evennia/contrib/README.md). - -If you have any questions, you can always ask in [the developer -chat](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) -`#evennia` on `irc.freenode.net` or by posting to the [Evennia -forums](https://groups.google.com/forum/#%21forum/evennia). You can also join the [Discord -Server](https://discord.gg/NecFePw). - -Finally, if you are itching to help out or support Evennia (awesome!) have an -issue to report or a feature to request, [see here](../Contributing.md). - -Enjoy your stay! - - -## Troubleshooting - -If you have issues with installing or starting Evennia for the first time, -check the section for your operating system below. If you have an issue not -covered here, [please report it](https://github.com/evennia/evennia/issues) -so it can be fixed or a workaround found! - -Remember, the server logs are in `mygame/server/logs/`. To easily view server logs in the terminal, -you can run `evennia -l`, or (in the future) start the server with `evennia start -l`. - -### Linux Troubleshooting - -- If you get an error when installing Evennia (especially with lines mentioning - failing to include `Python.h`) then try `sudo apt-get install python3-setuptools python3-dev`. - Once installed, run `pip install -e evennia` again. -- Under some not-updated Linux distributions you may run into errors with a - too-old `setuptools` or missing `functools`. If so, update your environment - with `pip install --upgrade pip wheel setuptools`. Then try `pip install -e evennia` again. -- If you get an `setup.py not found` error message while trying to `pip install`, make sure you are - in the right directory. You should be at the same level of the `evenv` directory, and the - `evennia` git repository. Note that there is an `evennia` directory inside of the repository too. -- One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; `Command -"python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/` with errors -like `distutils.errors.DistutilsError: Could not find suitable distribution for -Requirement.parse('incremental>=16.10.1')`. This appears possible to solve by simply updating Ubuntu -with `sudo apt-get update && sudo apt-get dist-upgrade`. -- Users of Fedora (notably Fedora 24) has reported a `gcc` error saying the directory -`/usr/lib/rpm/redhat/redhat-hardened-cc1` is missing, despite `gcc` itself being installed. [The -confirmed work-around](https://gist.github.com/yograterol/99c8e123afecc828cb8c) seems to be to -install the `redhat-rpm-config` package with e.g. `sudo dnf install redhat-rpm-config`. -- Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues -with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to -yourself?) - -### Mac Troubleshooting - -- Mac users have reported a critical `MemoryError` when trying to start Evennia on Mac with a Python -version below `2.7.12`. If you get this error, update to the latest XCode and Python2 version. -- Some Mac users have reported not being able to connect to `localhost` (i.e. your own computer). If -so, try to connect to `127.0.0.1` instead, which is the same thing. Use port 4000 from mud clients -and port 4001 from the web browser as usual. - -### Windows Troubleshooting - -- If you installed Python but the `python` command is not available (even in a new console), then -you might have missed installing Python on the path. In the Windows Python installer you get a list -of options for what to install. Most or all options are pre-checked except this one, and you may -even have to scroll down to see it. Reinstall Python and make sure it's checked. -- If your MUD client cannot connect to `localhost:4000`, try the equivalent `127.0.0.1:4000` -instead. Some MUD clients on Windows does not appear to understand the alias `localhost`. -- If you run `virtualenv evenv` and get a `'virtualenv' is not recognized as an internal or external -command, -operable program or batch file.` error, you can `mkdir evenv`, `cd evenv` and then `python -m -virtualenv .` as a workaround. -- Some Windows users get an error installing the Twisted 'wheel'. A wheel is a pre-compiled binary -package for Python. A common reason for this error is that you are using a 32-bit version of Python, -but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a -slightly older Twisted version. So if, say, version `18.1` failed, install `18.0` manually with `pip -install twisted==18.0`. Alternatively you could try to get a 64-bit version of Python (uninstall the -32bit one). If so, you must then `deactivate` the virtualenv, delete the `evenv` folder and recreate -it anew (it will then use the new Python executable). -- If your server won't start, with no error messages (and no log files at all when starting from -scratch), try to start with `evennia ipstart` instead. If you then see an error about `system cannot -find the path specified`, it may be that the file `evennia/evennia/server/twistd.bat` has the wrong -path to the `twistd` executable. This file is auto-generated, so try to delete it and then run -`evennia start` to rebuild it and see if it works. If it still doesn't work you need to open it in a -text editor like Notepad. It's just one line containing the path to the `twistd.exe` executable as -determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and -you should update the line to the real location. -- Some users have reported issues with Windows WSL and anti-virus software during Evennia -development. Timeout errors and the inability to run `evennia connections` may be due to your anti- -virus software interfering. Try disabling or changing your anti-virus software settings. diff --git a/docs/1.0-dev/_sources/Setup/Installing-on-Android.md.txt b/docs/1.0-dev/_sources/Setup/Installation-Android.md.txt similarity index 92% rename from docs/1.0-dev/_sources/Setup/Installing-on-Android.md.txt rename to docs/1.0-dev/_sources/Setup/Installation-Android.md.txt index f7ba8b04ea..d2f2892d64 100644 --- a/docs/1.0-dev/_sources/Setup/Installing-on-Android.md.txt +++ b/docs/1.0-dev/_sources/Setup/Installation-Android.md.txt @@ -5,6 +5,11 @@ This page describes how to install and run the Evennia server on an Android phon installing a slew of third-party programs from the Google Play store, so make sure you are okay with this before starting. +```warning +Android installation is experimental and not tested with later versions of Android. +Report your findings. +``` + ## Install Termux The first thing to do is install a terminal emulator that allows a "full" version of linux to be @@ -80,7 +85,7 @@ Install the latest Evennia in a way that lets you edit the source This step will possibly take quite a while - we are downloading Evennia and are then installing it, building all of the requirements for Evennia to run. If you run into trouble on this step, please -see [Troubleshooting](./Installing-on-Android.md#troubleshooting). +see [Troubleshooting](./Installation-Android.md#troubleshooting). You can go to the dir where Evennia is installed with `cd $VIRTUAL_ENV/src/evennia`. `git grep (something)` can be handy, as can `git diff` @@ -88,7 +93,7 @@ You can go to the dir where Evennia is installed with `cd $VIRTUAL_ENV/src/evenn ### Final steps At this point, Evennia is installed on your phone! You can now continue with the original -[Setup Quickstart](./Setup-Quickstart.md) instruction, we repeat them here for clarity. +[Setup Quickstart](./Installation.md) instruction, we repeat them here for clarity. To start a new game: @@ -120,7 +125,7 @@ $ cd ~ && source evenv/bin/activate (evenv) $ evennia start ``` -You may wish to look at the [Linux Instructions](./Extended-Installation.md#linux-install) for more. +You may wish to look at the [Linux Instructions](./Installation-Git.md#linux-install) for more. ## Caveats diff --git a/docs/1.0-dev/_sources/Setup/Running-Evennia-in-Docker.md.txt b/docs/1.0-dev/_sources/Setup/Installation-Docker.md.txt similarity index 99% rename from docs/1.0-dev/_sources/Setup/Running-Evennia-in-Docker.md.txt rename to docs/1.0-dev/_sources/Setup/Installation-Docker.md.txt index 31d45cb0e8..b07953838f 100644 --- a/docs/1.0-dev/_sources/Setup/Running-Evennia-in-Docker.md.txt +++ b/docs/1.0-dev/_sources/Setup/Installation-Docker.md.txt @@ -34,7 +34,7 @@ evennia|docker /usr/src/game $ This is a normal shell prompt. We are in the `/usr/src/game` location inside the docker container. If you had anything in the folder you started from, you should see it here (with `ls`) since we mounted the current directory to `usr/src/game` (with `-v` above). You have the `evennia` command -available and can now proceed to create a new game as per the [Setup Quickstart](./Setup-Quickstart.md) +available and can now proceed to create a new game as per the [Setup Quickstart](./Installation.md) instructions (you can skip the virtualenv and install 'globally' in the container though). You can run Evennia from inside this container if you want to, it's like you are root in a little diff --git a/docs/1.0-dev/_sources/Setup/Installation-Git.md.txt b/docs/1.0-dev/_sources/Setup/Installation-Git.md.txt new file mode 100644 index 0000000000..139267204b --- /dev/null +++ b/docs/1.0-dev/_sources/Setup/Installation-Git.md.txt @@ -0,0 +1,302 @@ +# Installing with GIT + +This installs and runs Evennia from its sources. This is required if you want to contribute to Evennia +itself or have an easier time exploring the code. See the basic [Installation](./Installation.md) for +a quick installation of the library. See the [troubleshooting](./Installation-Troubleshooting.md) if you run +into trouble. + +- [Quick Start](#quick-start) +- [Linux Install](#linux-install) +- [Mac Install](#mac-install) +- [Windows Install](#windows-install) +- [Running in Docker](./Installation-Docker.md) +- [Where to Go Next](#where-to-go-next) +- [Troubleshooting](#troubleshooting) +- [Glossary of terms](../Glossary.md) + +## Summary + +For the impatient. If you have trouble with a step, you should jump on to the +more detailed instructions for your platform. + +1. Install Python, GIT and python-virtualenv. Start a Console/Terminal. +2. `cd` to some place you want to do your development (like a folder + `/home/anna/muddev/` on Linux or a folder in your personal user directory on Windows). +3. `git clone https://github.com/evennia/evennia.git` (a new folder `evennia` is created) +4. `python3.10 -m venv evenv` (a new folder `evenv` is created) +5. `source evenv/bin/activate` (Linux, Mac), `evenv\Scripts\activate` (Windows) +6. `pip install -e evennia` +7. `evennia --init mygame` +8. `cd mygame` +9. `evennia migrate` +10. `evennia start` (make sure to make a superuser when asked) +Evennia should now be running and you can connect to it by pointing a web browser to +`http://localhost:4001` or a MUD telnet client to `localhost:4000` (use `127.0.0.1` if your OS does +not recognize `localhost`). + +## Linux Install + +If you run into any issues during the installation and first start, please +check out [Linux Troubleshooting](#linux-troubleshooting). + +For Debian-derived systems (like Ubuntu, Mint etc), start a terminal and +install the [dependencies](#requirements): + +``` +sudo apt-get update +sudo apt-get install python3.10 python3.10-venv python3.10-dev gcc + +``` +You should make sure to *not* be `root` after this step, running as `root` is a +security risk. Now create a folder where you want to do all your Evennia +development: + +``` +mkdir muddev +cd muddev +``` + +Next we fetch Evennia itself: + +``` +git clone https://github.com/evennia/evennia.git +``` +A new folder `evennia` will appear containing the Evennia library. This only +contains the source code though, it is not *installed* yet. To isolate the +Evennia install and its dependencies from the rest of the system, it is good +Python practice to install into a _virtualenv_. If you are unsure about what a +virtualenv is and why it's useful, see the [Glossary entry on +virtualenv](../Glossary.md#virtualenv). + +``` +python3.10 -m venv evenv +``` + +A new folder `evenv` will appear (we could have called it anything). This +folder will hold a self-contained setup of Python packages without interfering +with default Python packages on your system (or the Linux distro lagging behind +on Python package versions). It will also always use the right version of Python. +Activate the virtualenv: + +``` +source evenv/bin/activate +``` + +The text `(evenv)` should appear next to your prompt to show that the virtual +environment is active. + +> Remember that you need to activate the virtualenv like this *every time* you +> start a new terminal to get access to the Python packages (notably the +> important `evennia` program) we are about to install. + +Next, install Evennia into your active virtualenv. Make sure you are standing +at the top of your mud directory tree (so you see the `evennia/` and `evenv/` +folders) and run + +``` +pip install -e evennia +``` + +Test that you can run the `evennia` command everywhere while your virtualenv (evenv) is active. + +Next you can continue initializing your game from the regular [Installation instructions](./Installation.md). + + +## Mac Install + +The Evennia server is a terminal program. Open the terminal e.g. from +*Applications->Utilities->Terminal*. [Here is an introduction to the Mac +terminal](https://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) +if you are unsure how it works. If you run into any issues during the +installation, please check out [Mac Troubleshooting](#mac-troubleshooting). + +* Python should already be installed but you must make sure it's a high enough version - go for + 3.10. +([This](https://docs.python-guide.org/en/latest/starting/install/osx/) discusses + how you may upgrade it). +* GIT can be obtained with +[git-osx-installer](https://code.google.com/p/git-osx-installer/) or via MacPorts [as described +here](https://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac). +* If you run into issues with installing `Twisted` later you may need to +install gcc and the Python headers. + +After this point you should not need `sudo` or any higher privileges to install anything. + +Now create a folder where you want to do all your Evennia development: + +``` +mkdir muddev +cd muddev +``` + +Next we fetch Evennia itself: + +``` +git clone https://github.com/evennia/evennia.git +``` + +A new folder `evennia` will appear containing the Evennia library. This only +contains the source code though, it is not *installed* yet. To isolate the +Evennia install and its dependencies from the rest of the system, it is good +Python practice to install into a _virtualenv_. If you are unsure about what a +virtualenv is and why it's useful, see the [Glossary entry on virtualenv](../Glossary.md#virtualenv). + +``` +python3.10 -m venv evenv + +``` +A new folder `evenv` will appear (we could have called it anything). This +folder will hold a self-contained setup of Python packages without interfering +with default Python packages on your system. Activate the virtualenv: + +``` +source evenv/bin/activate +``` + +The text `(evenv)` should appear next to your prompt to show the virtual +environment is active. + +> Remember that you need to activate the virtualenv like this *every time* you +> start a new terminal to get access to the Python packages (notably the +> important `evennia` program) we are about to install. + +Next, install Evennia into your active virtualenv. Make sure you are standing +at the top of your mud directory tree (so you see the `evennia/` and `evenv/` +folders) and run + +``` +pip install --upgrade pip # Old pip versions may be an issue on Mac. +pip install --upgrade setuptools # Ditto concerning Mac issues. +pip install -e evennia +``` + +Test that you can run the `evennia` command everywhere while your virtualenv (evenv) is active. + +Next you can continue initializing your game from the regular [Installation instructions](./Installation.md). + +## Windows Install + +> If you are running Windows10, consider using the _Windows Subsystem for Linux_ +> ([WSL](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)) instead. +> Just set up WSL with an Ubuntu image and follow the Linux install instructions above. + +The Evennia server itself is a command line program. In the Windows launch +menu, start *All Programs -> Accessories -> command prompt* and you will get +the Windows command line interface. Here is [one of many tutorials on using the Windows command +line](https://www.bleepingcomputer.com/tutorials/windows-command-prompt-introduction/) +if you are unfamiliar with it. + +* Install Python [from the Python homepage](https://www.python.org/downloads/windows/). You will +need to be a +Windows Administrator to install packages. Get Python any version of Python **3.10**, usually +the 64-bit version (although it doesn't matter too much). **When installing, make sure +to check-mark *all* install options, especially the one about making Python +available on the path (you may have to scroll to see it)**. This allows you to +just write `python` in any console without first finding where the `python` +program actually sits on your hard drive. +* You need to also get [GIT](https://git-scm.com/downloads) and install it. You +can use the default install options but when you get asked to "Adjust your PATH +environment", you should select the second option "Use Git from the Windows +Command Prompt", which gives you more freedom as to where you can use the +program. +* Finally you must install the [Microsoft Visual C++ compiler for +Python](https://aka.ms/vs/16/release/vs_buildtools.exe). Download and run the linked installer and +install the C++ tools. Keep all the defaults. Allow the install of the "Win10 SDK", even if you are +on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due +to a failure to build the "Twisted wheels", this is where you are missing things. +* You *may* need the [pypiwin32](https://pypi.python.org/pypi/pypiwin32) Python headers. Install +these only if you have issues. + +You can install Evennia wherever you want. `cd` to that location and create a +new folder for all your Evennia development (let's call it `muddev`). + +``` +mkdir muddev +cd muddev +``` + +> Hint: If `cd` isn't working you can use `pushd` instead to force the +> directory change. + +Next we fetch Evennia itself: + +``` +git clone https://github.com/evennia/evennia.git +``` + +A new folder `evennia` will appear containing the Evennia library. This only +contains the source code though, it is not *installed* yet. To isolate the +Evennia install and its dependencies from the rest of the system, it is good +Python practice to install into a _virtualenv_. If you are unsure about what a +virtualenv is and why it's useful, see the [Glossary entry on virtualenv](../Glossary.md#virtualenv). + +``` +python3.10 -m venv evenv + +``` +A new folder `evenv` will appear (we could have called it anything). This +folder will hold a self-contained setup of Python packages without interfering +with default Python packages on your system. Activate the virtualenv: + +``` +# If you are using a standard command prompt, you can use the following: +evenv\scripts\activate.bat + +# If you are using a PS Shell, Git Bash, or other, you can use the following: +.\evenv\scripts\activate + +``` +The text `(evenv)` should appear next to your prompt to show the virtual +environment is active. + +> Remember that you need to activate the virtualenv like this *every time* you +> start a new console window if you want to get access to the Python packages +> (notably the important `evennia` program) we are about to install. + +Next, install Evennia into your active virtualenv. Make sure you are standing +at the top of your mud directory tree (so you see the `evennia` and `evenv` +folders when you use the `dir` command) and run + +``` +pip install -e evennia +``` + +Test that you can run the `evennia` command everywhere while your virtualenv (evenv) is active. + +Next you can continue initializing your game from the regular [Installation instructions](./Installation.md). + +## Where to Go Next + +Welcome to Evennia! Your new game is fully functioning, but empty. If you just +logged in, stand in the `Limbo` room and run + + @batchcommand tutorial_world.build + +to build [Evennia's tutorial world](../Howto/Starting/Part1/Tutorial-World.md) - it's a small solo quest to +explore. Only run the instructed `@batchcommand` once. You'll get a lot of text scrolling by as the +tutorial is built. Once done, the `tutorial` exit will have appeared out of Limbo - just write +`tutorial` to enter it. + +Once you get back to `Limbo` from the tutorial (if you get stuck in the tutorial quest you can do +`@tel #2` to jump to Limbo), a good idea is to learn how to [start, stop and reload](Start-Stop- +Reload) the Evennia server. You may also want to familiarize yourself with some +[commonly used terms in our Glossary](../Glossary.md). After that, why not experiment with +[creating some new items and build some new rooms](../Howto/Starting/Part1/Building-Quickstart.md) out from Limbo. + +From here on, you could move on to do one of our [introductory tutorials](../Howto/Howto-Overview.md) or simply dive +headlong into Evennia's comprehensive [manual](https://github.com/evennia/evennia/wiki). While +Evennia has no major game systems out of the box, we do supply a range of optional *contribs* that +you can use or borrow from. They range from dice rolling and alternative color schemes to barter and +combat systems. You can find the [growing list of contribs +here](https://github.com/evennia/evennia/blob/master/evennia/contrib/README.md). + +If you have any questions, you can always ask in [the developer +chat](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) +`#evennia` on `irc.freenode.net` or by posting to the [Evennia +forums](https://groups.google.com/forum/#%21forum/evennia). You can also join the [Discord +Server](https://discord.gg/NecFePw). + +Finally, if you are itching to help out or support Evennia (awesome!) have an +issue to report or a feature to request, [see here](../Contributing.md). + +Enjoy your stay! \ No newline at end of file diff --git a/docs/1.0-dev/_sources/Setup/Installation-Non-Interactive.md.txt b/docs/1.0-dev/_sources/Setup/Installation-Non-Interactive.md.txt new file mode 100644 index 0000000000..7095260830 --- /dev/null +++ b/docs/1.0-dev/_sources/Setup/Installation-Non-Interactive.md.txt @@ -0,0 +1,19 @@ +# Non-interactive setup + +The first ime you run `evennia start` (just after having created the database), you will be asked +to interactively insert the superuser username, email and password. If you are deploying Evennia +as part of an automatic build script, you don't want to enter this information manually. + +You can have the superuser be created automatically by passing environment variables to your +build script: + +- `EVENNIA_SUPERUSER_USERNAME` +- `EVENNIA_SUPERUSER_PASSWORD` +- `EVENNIA_SUPERUSER_EMAIL` is optional. If not given, empty string is used. + +These envvars will only be used on the _very first_ server start and then ignored. For example: + +``` +EVENNIA_SUPERUSER_USERNAME=myname EVENNIA_SUPERUSER_PASSWORD=mypwd evennia start + +``` \ No newline at end of file diff --git a/docs/1.0-dev/_sources/Setup/Installation-Troubleshooting.md.txt b/docs/1.0-dev/_sources/Setup/Installation-Troubleshooting.md.txt new file mode 100644 index 0000000000..76ce5e5c18 --- /dev/null +++ b/docs/1.0-dev/_sources/Setup/Installation-Troubleshooting.md.txt @@ -0,0 +1,115 @@ +# Installation Troubleshooting + +If you have an issue not covered here, [please report it](https://github.com/evennia/evennia/issues/new/choose) +so it can be fixed or a workaround found! + +The server logs are in `mygame/server/logs/`. To easily view server logs in the terminal, +you can run `evennia -l`, or (in the future) start the server with `evennia start -l`. + +## Check your Requirements + +Any system that supports Python3.9+ should work. We'll describe how to install +everything in the following sections. +- Linux/Unix +- Windows (Win7, Win8, Win10, Win11) +- Mac OSX (>10.5 recommended) + +- [Python](https://www.python.org) (v3.9 and 3.10 are tested) + - [virtualenv](https://pypi.python.org/pypi/virtualenv) for making isolated + Python environments. Installed with `pip install virtualenv`. +- [Twisted](https://twistedmatrix.com) (v22.0+) + - [ZopeInterface](https://www.zope.org/Products/ZopeInterface) (v3.0+) - usually included in + Twisted packages + - Linux/Mac users may need the `gcc` and `python-dev` packages or equivalent. + - Windows users need [MS Visual C++](https://aka.ms/vs/16/release/vs_buildtools.exe) and *maybe* + [pypiwin32](https://pypi.python.org/pypi/pypiwin32). +- [Django](https://www.djangoproject.com) (v4.0.1+), be warned that latest dev + version is usually untested with Evennia. +- [GIT](https://git-scm.com/) - version control software used if you want to install the sources + (but also useful to track your own code) - Mac users can use the + [git-osx-installer](https://code.google.com/p/git-osx-installer/) or the + [MacPorts version](https://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac). + +## Confusion of location (GIT installation) + +It's common to be confused and install Evennia in the wrong location. After following the +[git install](./Installation-Git.md) instructions, the folder structure should look like this: + +``` +muddev/ + evenv/ + evennia/ + mygame/ +``` + +The evennia code itself is found inside `evennia/evennia/` (so two levels down). Your settings file +is `mygame/server/conf/settings.py` and the _parent_ setting file is `evennia/evennia/settings_default.py`. + +## Localhost not found + +If `localhost` doesn't work when trying to connect to your local game, try `127.0.0.1`, which is the same thing. + +## Linux Troubleshooting + +- If you get an error when installing Evennia (especially with lines mentioning + failing to include `Python.h`) then try `sudo apt-get install python3-setuptools python3-dev`. + Once installed, run `pip install -e evennia` again. +- When doing a [git install](./Installation-Git.md), some not-updated Linux distributions may give errors + about a too-old `setuptools` or missing `functools`. If so, update your environment + with `pip install --upgrade pip wheel setuptools`. Then try `pip install -e evennia` again. +- One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; `Command + "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/` with errors + like `distutils.errors.DistutilsError: Could not find suitable distribution for + Requirement.parse('incremental>=16.10.1')`. This appears possible to solve by simply updating Ubuntu + with `sudo apt-get update && sudo apt-get dist-upgrade`. +- Users of Fedora (notably Fedora 24) has reported a `gcc` error saying the directory + `/usr/lib/rpm/redhat/redhat-hardened-cc1` is missing, despite `gcc` itself being installed. [The + confirmed work-around](https://gist.github.com/yograterol/99c8e123afecc828cb8c) seems to be to + install the `redhat-rpm-config` package with e.g. `sudo dnf install redhat-rpm-config`. +- Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues + with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to + yourself?) + +## Mac Troubleshooting + +- Mac users have reported a critical `MemoryError` when trying to start Evennia on Mac with a Python + version below `2.7.12`. If you get this error, update to the latest XCode and Python2 version. +- Some Mac users have reported not being able to connect to `localhost` (i.e. your own computer). If + so, try to connect to `127.0.0.1` instead, which is the same thing. Use port 4000 from mud clients + and port 4001 from the web browser as usual. + +## Windows Troubleshooting + +- Install Python [from the Python homepage](https://www.python.org/downloads/windows/). You will + need to be a Windows Administrator to install packages. +- When installing Python, make sure to check-mark *all* install options, especially the one about making Python + available on the path (you may have to scroll to see it). This allows you to + just write `python` in any console without first finding where the `python` + program actually sits on your hard drive. +- If you get a `command not found` when trying to run the `evennia` program after installation, try closing the + Console and starting it again (remember to re-activate the virtualenv!). Sometimes Windows is not updating + its environment properly. +- If you installed Python but the `python` command is not available (even in a new console), then + you might have missed installing Python on the path. In the Windows Python installer you get a list + of options for what to install. Most or all options are pre-checked except this one, and you may + even have to scroll down to see it. Reinstall Python and make sure it's checked. +- If your MUD client cannot connect to `localhost:4000`, try the equivalent `127.0.0.1:4000` + instead. Some MUD clients on Windows does not appear to understand the alias `localhost`. +- Some Windows users get an error installing the Twisted 'wheel'. A wheel is a pre-compiled binary + package for Python. A common reason for this error is that you are using a 32-bit version of Python, + but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a + slightly older Twisted version. So if, say, version `22.1` failed, install `22.0` manually with `pip + install twisted==22.0`. Alternatively you could check that you are using the 64-bit version of Python + and uninstall any 32bit one. If so, you must then `deactivate` the virtualenv, delete the `evenv` folder + and recreate it anew with your new Python. +- If your server won't start, with no error messages (and no log files at all when starting from + scratch), try to start with `evennia ipstart` instead. If you then see an error about `system cannot + find the path specified`, it may be that the file `evennia\evennia\server\twistd.bat` has the wrong + path to the `twistd` executable. This file is auto-generated, so try to delete it and then run + `evennia start` to rebuild it and see if it works. If it still doesn't work you need to open it in a + text editor like Notepad. It's just one line containing the path to the `twistd.exe` executable as + determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and + you should update the line to the real location. +- Some users have reported issues with Windows WSL and anti-virus software during Evennia + development. Timeout errors and the inability to run `evennia connections` may be due to your anti- + virus software interfering. Try disabling or changing your anti-virus software settings. diff --git a/docs/1.0-dev/_sources/Setup/Installation-Upgrade.md.txt b/docs/1.0-dev/_sources/Setup/Installation-Upgrade.md.txt new file mode 100644 index 0000000000..ff583a9039 --- /dev/null +++ b/docs/1.0-dev/_sources/Setup/Installation-Upgrade.md.txt @@ -0,0 +1,44 @@ +# Upgrading an existing installation + +These are steps to take when you have an _exiting game dir that you want to keep_. +If you don't, you can always just delete your old game dir and initialize a fresh one using +the normal [install instructions](./Installation.md). + +## Evennia v0.9.5 to 1.0 + +Prior to 1.0, all Evennia installs were [Git-installs](./Installation-Git.md). These instructions +assume that you have a cloned `evennia` repo and use a virtualenv (best practices). + +- Make sure to stop Evennia 0.9.5 entirely with `evennia stop`. +- `deactivate` to leave your active virtualenv. +- Make a _backup_ of your entire `mygame` folder, just to be sure! +- Delete the old `evenv` folder, or rename it (in case you want to keep using 0.9.5 for a while). + + +- Install Python 3.10 (recommended) or 3.9. Follow the [Git-installation](./Installation-Git.md) for your OS if needed. +- If using virtualenv, make a _new_ one with `python3.10 -m venv evenv`, then activate with `source evenv/bin/activate` + (linux/mac) or `\evenv\Script\activate` (windows) +- `cd` into your `evennia/` folder (you want to see the `docs/`, `bin/` directories as well as a nested `evennia/` folder) +- **Prior to 1.0 release only** - do `git checkout develop` to switch to the develop branch. After release, this will + be found on the default master branch. +- `git pull` +- `pip install -e .` +- If you want the optional extra libs, do `pip install -r requirements_extra.txt`. +- Test that you can run the `evennia` command. + + +- Next, `cd` to your game folder (like `mygame`.) +- If you have changed `mygame/web`, _rename_ the folder to `web_0.9.5`. If you didn't change anything (or don't have +anything you want to keep), you can _delete_ it entirely. +- Copy `evennia/evennia/game_template/web` to `mygame/` (e.g. using `cp -Rf` or a file manager). This new `web` folder +replaces the old one and has a very different structure. +- `evennia migrate` +- `evennia start` + +If you made extensive work in your game dir, you may well find that you need to do some (hopefully minor) +changes to your code before it will start with Evennia 1.0. Some important points: + +- The `evennia/contrib/` folder changed structure - there are now categorized sub-folders, so you have to update +your imports. +- Any `web` changes need to be moved back from your backup into the new structure of `web/` manually. +- See the [Evennia 1.0 Changelog](./Changelog.md) for all changes. \ No newline at end of file diff --git a/docs/1.0-dev/_sources/Setup/Installation.md.txt b/docs/1.0-dev/_sources/Setup/Installation.md.txt new file mode 100644 index 0000000000..ddd8a27a8e --- /dev/null +++ b/docs/1.0-dev/_sources/Setup/Installation.md.txt @@ -0,0 +1,110 @@ +# Installation + +The Evennia server is installed, run and maintained from the terminal (console/CMD on Windows). Starting the server +doesn't make anything visible online. Once you download everything you can in fact develop your game in complete +isolation if you want, without needing any access to the internet. + +Evennia requires [Python](https://www.python.org/downloads/) 3.9 or 3.10. +Using a [Python virtualenv](../Glossary.md#virtualenv) is highly recommended in order to keep your +Evennia installation independent from the system libraries. Don't install Evennia as +administrator or superuser. + +```{warning} +pip install evennia is not yet available in develop branch. Use the [git installation](./Installation-Git.md). +``` +```{warning} +If you are converting an existing game from a previous version, [see here](./Installation-Upgrade.md). +``` + + pip install evennia + +Once installed, make sure the `evennia` command works. Use `evennia -h` for usage help. If you are using a +virtualenv, make sure it's active whenever you need to use the `evennia` command. + +Alternatively, you can [install Evennia from github](./Installation-Git.md) or use [docker](./Installation-Docker.md). +Check out [installation troubleshooting](./Installation-Troubleshootin.md) if you run into problems. Some +users have also experimented with [installing Evennia on Android](./Installation-Android.md). + +## Initialize a new game + +Use `cd` to enter a folder where you want to do your game development. Here (and in +the rest of the Evennia documentation) we call this folder `mygame`, but you should of course +name your game whatever you like. + + evennia --init mygame + +This will create a new folder `mygame` (or whatever you chose) in your current location. This +contains empty templates and all the default settings needed to start the server. + + +## Start the new game + + cd mygame + evennia migrate + +This will create the default database (Sqlite3). The database file ends up as `mygame/server/evennia.db3`. If you +ever want to start from a fresh database, just delete this file and re-run `evennia migrate` again. + + evennia start + +Set your user-name and password when prompted. This will be the "god user" or "superuser" in-game. +The email is optional. + +> You can also [automate](./Installation-Non-Interactive.md) the creation of the super user. + +If all went well, the server is now up and running. Point a legacy MUD/telnet client to `localhost:4000` or +a web browser at [http://localhost:4001](http://localhost:4001) to play your new (if empty) game! + +Log in as a new account or use the superuser you just created. + + +## Restarting and stopping + + +You can restart the server without disconnecting players: + + evennia restart + +To do a full stop and restart (will disconnect players): + + evennia reboot + +Full stop of the server (use `evennia start` to restart): + + evennia stop + +## See server logs + +Log files are in `mygame/server/logs`. You can tail them live with + + evennia --log + +or + + evennia -l + + +You can start viewing the log immediately when running `evennia` commands, such as + + + evennia start -l + +To exit the log tailing, enter `Ctrl-C` (`Cmd-C` for Mac). This will not affect the server. + +## Server configuration + +The server configuration file is `mygame/server/settings.py`. It's empty by default. Copy and change +only the settings you want from the [default settings file](./Settings-Default.md). + + +## The Next steps + +You are good to go! + +Evennia comes with a small [Tutorial World](../Howto/Starting/Part1/Tutorial-World.md) to experiment and learn from. After logging +in, you can create it by running + + batchcommand tutorial_world.build + +Next, why not head into the [Starting Tutorial](../Howto/Starting/Part1/Starting-Part1.md) +to learn how to start making your new game! \ No newline at end of file diff --git a/docs/1.0-dev/_sources/Setup/Online-Setup.md.txt b/docs/1.0-dev/_sources/Setup/Online-Setup.md.txt index 5307021542..6107365717 100644 --- a/docs/1.0-dev/_sources/Setup/Online-Setup.md.txt +++ b/docs/1.0-dev/_sources/Setup/Online-Setup.md.txt @@ -344,7 +344,7 @@ game stays online. Many services guarantee a certain level of up-time and also d for you. Make sure to check, some offer lower rates in exchange for you yourself being fully responsible for your data/backups. - Usually offers a fixed domain name, so no need to mess with IP addresses. -- May have the ability to easily deploy [docker](./Running-Evennia-in-Docker.md) versions of evennia +- May have the ability to easily deploy [docker](./Installation-Docker.md) versions of evennia and/or your game. **Disadvantages** @@ -362,7 +362,7 @@ Docker) to deploy your game to the remote server; it will likely ease installati Docker images may be a little confusing if you are completely new to them though. If not using docker, and assuming you know how to connect to your account over ssh/PuTTy, you should -be able to follow the [Setup Quickstart](./Setup-Quickstart.md) instructions normally. You only need Python +be able to follow the [Setup Quickstart](./Installation.md) instructions normally. You only need Python and GIT pre-installed; these should both be available on any servers (if not you should be able to easily ask for them to be installed). On a VPS or Cloud service you can install them yourself as needed. diff --git a/docs/1.0-dev/_sources/Setup/Server-Conf.md.txt b/docs/1.0-dev/_sources/Setup/Server-Conf.md.txt index 88b717eab4..8c3265ca6c 100644 --- a/docs/1.0-dev/_sources/Setup/Server-Conf.md.txt +++ b/docs/1.0-dev/_sources/Setup/Server-Conf.md.txt @@ -8,7 +8,7 @@ ways to customize the server and expand it with your own plugins. The "Settings" file referenced throughout the documentation is the file `mygame/server/conf/settings.py`. This is automatically created on the first run of `evennia --init` -(see the [Setup Quickstart](./Setup-Quickstart.md) page). +(see the [Setup Quickstart](./Installation.md) page). Your new `settings.py` is relatively bare out of the box. Evennia's core settings file is actually [evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py) diff --git a/docs/1.0-dev/_sources/Setup/Settings-Default.md.txt b/docs/1.0-dev/_sources/Setup/Settings-Default.md.txt new file mode 100644 index 0000000000..9fed95fd15 --- /dev/null +++ b/docs/1.0-dev/_sources/Setup/Settings-Default.md.txt @@ -0,0 +1,1202 @@ + +# Evennia Default settings file + +Master file is located at `evennia/evennia/settings_default.py`. Read +its comments to see what each setting does and copy only what you want +to change into `mygame/server/conf/settings.py`. + +Example of accessing settings: + +``` +from django.conf import settings + +if settings.SERVERNAME == "Evennia": + print("Yay!") +``` + +---- + +```python +""" +Master configuration file for Evennia. + +NOTE: NO MODIFICATIONS SHOULD BE MADE TO THIS FILE! + +All settings changes should be done by copy-pasting the variable and +its value to /server/conf/settings.py. + +Hint: Don't copy&paste over more from this file than you actually want +to change. Anything you don't copy&paste will thus retain its default +value - which may change as Evennia is developed. This way you can +always be sure of what you have changed and what is default behaviour. + +""" +from django.contrib.messages import constants as messages +from django.urls import reverse_lazy + +import os +import sys + +###################################################################### +# Evennia base server config +###################################################################### + +# This is the name of your game. Make it catchy! +SERVERNAME = "Evennia" +# Short one-sentence blurb describing your game. Shown under the title +# on the website and could be used in online listings of your game etc. +GAME_SLOGAN = "The Python MUD/MU* creation system" +# The url address to your server, like mymudgame.com. This should be the publicly +# visible location. This is used e.g. on the web site to show how you connect to the +# game over telnet. Default is localhost (only on your machine). +SERVER_HOSTNAME = "localhost" +# Lockdown mode will cut off the game from any external connections +# and only allow connections from localhost. Requires a cold reboot. +LOCKDOWN_MODE = False +# Activate telnet service +TELNET_ENABLED = True +# A list of ports the Evennia telnet server listens on Can be one or many. +TELNET_PORTS = [4000] +# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6. +TELNET_INTERFACES = ["0.0.0.0"] +# Activate Telnet+SSL protocol (SecureSocketLibrary) for supporting clients +SSL_ENABLED = False +# Ports to use for Telnet+SSL +SSL_PORTS = [4003] +# Telnet+SSL Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6. +SSL_INTERFACES = ["0.0.0.0"] +# OOB (out-of-band) telnet communication allows Evennia to communicate +# special commands and data with enabled Telnet clients. This is used +# to create custom client interfaces over a telnet connection. To make +# full use of OOB, you need to prepare functions to handle the data +# server-side (see INPUT_FUNC_MODULES). TELNET_ENABLED is required for this +# to work. +TELNET_OOB_ENABLED = False +# Activate SSH protocol communication (SecureShell) +SSH_ENABLED = False +# Ports to use for SSH +SSH_PORTS = [4004] +# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6. +SSH_INTERFACES = ["0.0.0.0"] +# Start the evennia django+twisted webserver so you can +# browse the evennia website and the admin interface +# (Obs - further web configuration can be found below +# in the section 'Config for Django web features') +WEBSERVER_ENABLED = True +# This is a security setting protecting against host poisoning +# attacks. It defaults to allowing all. In production, make +# sure to change this to your actual host addresses/IPs. +ALLOWED_HOSTS = ["*"] +# The webserver sits behind a Portal proxy. This is a list +# of tuples (proxyport,serverport) used. The proxyports are what +# the Portal proxy presents to the world. The serverports are +# the internal ports the proxy uses to forward data to the Server-side +# webserver (these should not be publicly open) +WEBSERVER_PORTS = [(4001, 4005)] +# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6. +WEBSERVER_INTERFACES = ["0.0.0.0"] +# IP addresses that may talk to the server in a reverse proxy configuration, +# like NginX. +UPSTREAM_IPS = ["127.0.0.1"] +# The webserver uses threadpool for handling requests. This will scale +# with server load. Set the minimum and maximum number of threads it +# may use as (min, max) (must be > 0) +WEBSERVER_THREADPOOL_LIMITS = (1, 20) +# Start the evennia webclient. This requires the webserver to be running and +# offers the fallback ajax-based webclient backbone for browsers not supporting +# the websocket one. +WEBCLIENT_ENABLED = True +# Activate Websocket support for modern browsers. If this is on, the +# default webclient will use this and only use the ajax version if the browser +# is too old to support websockets. Requires WEBCLIENT_ENABLED. +WEBSOCKET_CLIENT_ENABLED = True +# Server-side websocket port to open for the webclient. Note that this value will +# be dynamically encoded in the webclient html page to allow the webclient to call +# home. If the external encoded value needs to be different than this, due to +# working through a proxy or docker port-remapping, the environment variable +# WEBCLIENT_CLIENT_PROXY_PORT can be used to override this port only for the +# front-facing client's sake. +WEBSOCKET_CLIENT_PORT = 4002 +# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6. +WEBSOCKET_CLIENT_INTERFACE = "0.0.0.0" +# Actual URL for webclient component to reach the websocket. You only need +# to set this if you know you need it, like using some sort of proxy setup. +# If given it must be on the form "ws[s]://hostname[:port]". If left at None, +# the client will itself figure out this url based on the server's hostname. +# e.g. ws://external.example.com or wss://external.example.com:443 +WEBSOCKET_CLIENT_URL = None +# This determine's whether Evennia's custom admin page is used, or if the +# standard Django admin is used. +EVENNIA_ADMIN = True +# The Server opens an AMP port so that the portal can +# communicate with it. This is an internal functionality of Evennia, usually +# operating between two processes on the same machine. You usually don't need to +# change this unless you cannot use the default AMP port/host for +# whatever reason. +AMP_HOST = "localhost" +AMP_PORT = 4006 +AMP_INTERFACE = "127.0.0.1" + + +# Path to the lib directory containing the bulk of the codebase's code. +EVENNIA_DIR = os.path.dirname(os.path.abspath(__file__)) +# Path to the game directory (containing the server/conf/settings.py file) +# This is dynamically created- there is generally no need to change this! +if EVENNIA_DIR.lower() == os.getcwd().lower() or ( + sys.argv[1] == "test" if len(sys.argv) > 1 else False +): + # unittesting mode + GAME_DIR = os.getcwd() +else: + # Fallback location (will be replaced by the actual game dir at runtime) + GAME_DIR = os.path.join(EVENNIA_DIR, "game_template") + for i in range(10): + gpath = os.getcwd() + if "server" in os.listdir(gpath): + if os.path.isfile(os.path.join("server", "conf", "settings.py")): + GAME_DIR = gpath + break + os.chdir(os.pardir) +# Place to put log files, how often to rotate the log and how big each log file +# may become before rotating. +LOG_DIR = os.path.join(GAME_DIR, "server", "logs") +SERVER_LOG_FILE = os.path.join(LOG_DIR, "server.log") +SERVER_LOG_DAY_ROTATION = 7 +SERVER_LOG_MAX_SIZE = 1000000 +PORTAL_LOG_FILE = os.path.join(LOG_DIR, "portal.log") +PORTAL_LOG_DAY_ROTATION = 7 +PORTAL_LOG_MAX_SIZE = 1000000 +# The http log is usually only for debugging since it's very spammy +HTTP_LOG_FILE = os.path.join(LOG_DIR, "http_requests.log") +# if this is set to the empty string, lockwarnings will be turned off. +LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log") +# Number of lines to append to rotating channel logs when they rotate +CHANNEL_LOG_NUM_TAIL_LINES = 20 +# Max size (in bytes) of channel log files before they rotate +CHANNEL_LOG_ROTATE_SIZE = 1000000 +# Unused by default, but used by e.g. the MapSystem contrib. A place for storing +# semi-permanent data and avoid it being rebuilt over and over. It is created +# on-demand only. +CACHE_DIR = os.path.join(GAME_DIR, "server", ".cache") +# Local time zone for this installation. All choices can be found here: +# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +TIME_ZONE = "UTC" +# Activate time zone in datetimes +USE_TZ = True +# Authentication backends. This is the code used to authenticate a user. +AUTHENTICATION_BACKENDS = ["evennia.web.utils.backends.CaseInsensitiveModelBackend"] +# Language code for this installation. All choices can be found here: +# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes +LANGUAGE_CODE = "en-us" +# How long time (in seconds) a user may idle before being logged +# out. This can be set as big as desired. A user may avoid being +# thrown off by sending the empty system command 'idle' to the server +# at regular intervals. Set <=0 to deactivate idle timeout completely. +IDLE_TIMEOUT = -1 +# The idle command can be sent to keep your session active without actually +# having to spam normal commands regularly. It gives no feedback, only updates +# the idle timer. Note that "idle" will *always* work, even if a different +# command-name is given here; this is because the webclient needs a default +# to send to avoid proxy timeouts. +IDLE_COMMAND = "idle" +# The set of encodings tried. An Account object may set an attribute "encoding" on +# itself to match the client used. If not set, or wrong encoding is +# given, this list is tried, in order, aborting on the first match. +# Add sets for languages/regions your accounts are likely to use. +# (see http://en.wikipedia.org/wiki/Character_encoding) +# Telnet default encoding, unless specified by the client, will be ENCODINGS[0]. +ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"] +# Regular expression applied to all output to a given session in order +# to strip away characters (usually various forms of decorations) for the benefit +# of users with screen readers. Note that ANSI/MXP doesn't need to +# be stripped this way, that is handled automatically. +SCREENREADER_REGEX_STRIP = r"\+-+|\+$|\+~|--+|~~+|==+" +# MXP support means the ability to show clickable links in the client. Clicking +# the link will execute a game command. It's a way to add mouse input to the game. +MXP_ENABLED = True +# If this is set, MXP can only be sent by the server and not added from the +# client side. Disabling this is a potential security risk because it could +# allow malevolent players to lure others to execute commands they did not +# intend to. +MXP_OUTGOING_ONLY = True +# Database objects are cached in what is known as the idmapper. The idmapper +# caching results in a massive speedup of the server (since it dramatically +# limits the number of database accesses needed) and also allows for +# storing temporary data on objects. It is however also the main memory +# consumer of Evennia. With this setting the cache can be capped and +# flushed when it reaches a certain size. Minimum is 50 MB but it is +# not recommended to set this to less than 100 MB for a distribution +# system. +# Empirically, N_objects_in_cache ~ ((RMEM - 35) / 0.0157): +# mem(MB) | objs in cache || mem(MB) | objs in cache +# 50 | ~1000 || 800 | ~49 000 +# 100 | ~4000 || 1200 | ~75 000 +# 200 | ~10 000 || 1600 | ~100 000 +# 500 | ~30 000 || 2000 | ~125 000 +# Note that the estimated memory usage is not exact (and the cap is only +# checked every 5 minutes), so err on the side of caution if +# running on a server with limited memory. Also note that Python +# will not necessarily return the memory to the OS when the idmapper +# flashes (the memory will be freed and made available to the Python +# process only). How many objects need to be in memory at any given +# time depends very much on your game so some experimentation may +# be necessary (use @server to see how many objects are in the idmapper +# cache at any time). Setting this to None disables the cache cap. +IDMAPPER_CACHE_MAXSIZE = 200 # (MB) +# This determines how many connections per second the Portal should +# accept, as a DoS countermeasure. If the rate exceeds this number, incoming +# connections will be queued to this rate, so none will be lost. +# Must be set to a value > 0. +MAX_CONNECTION_RATE = 2 +# Determine how many commands per second a given Session is allowed +# to send to the Portal via a connected protocol. Too high rate will +# drop the command and echo a warning. Note that this will also cap +# OOB messages so don't set it too low if you expect a lot of events +# from the client! To turn the limiter off, set to <= 0. +MAX_COMMAND_RATE = 80 +# The warning to echo back to users if they send commands too fast +COMMAND_RATE_WARNING = "You entered commands too fast. Wait a moment and try again." +# custom, extra commands to add to the `evennia` launcher. This is a dict +# of {'cmdname': 'path.to.callable', ...}, where the callable will be passed +# any extra args given on the command line. For example `evennia cmdname foo bar`. +EXTRA_LAUNCHER_COMMANDS = {} + +# Determine how large of a string can be sent to the server in number +# of characters. If they attempt to enter a string over this character +# limit, we stop them and send a message. To make unlimited, set to +# 0 or less. +MAX_CHAR_LIMIT = 6000 +# The warning to echo back to users if they enter a very large string +MAX_CHAR_LIMIT_WARNING = ( + "You entered a string that was too long. " "Please break it up into multiple parts." +) +# If this is true, errors and tracebacks from the engine will be +# echoed as text in-game as well as to the log. This can speed up +# debugging. OBS: Showing full tracebacks to regular users could be a +# security problem -turn this off in a production game! +IN_GAME_ERRORS = True +# Broadcast "Server restart"-like messages to all sessions. +BROADCAST_SERVER_RESTART_MESSAGES = True + +###################################################################### +# Evennia Database config +###################################################################### + +# Database config syntax: +# ENGINE - path to the the database backend. Possible choices are: +# 'django.db.backends.sqlite3', (default) +# 'django.db.backends.mysql', +# 'django.db.backends.postgresql', +# 'django.db.backends.oracle' (untested). +# NAME - database name, or path to the db file for sqlite3 +# USER - db admin (unused in sqlite3) +# PASSWORD - db admin password (unused in sqlite3) +# HOST - empty string is localhost (unused in sqlite3) +# PORT - empty string defaults to localhost (unused in sqlite3) +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.getenv("TEST_DB_PATH", os.path.join(GAME_DIR, "server", "evennia.db3")), + "USER": "", + "PASSWORD": "", + "HOST": "", + "PORT": "", + } +} +# How long the django-database connection should be kept open, in seconds. +# If you get errors about the database having gone away after long idle +# periods, shorten this value (e.g. MySQL defaults to a timeout of 8 hrs) +CONN_MAX_AGE = 3600 * 7 +# When removing or renaming models, such models stored in Attributes may +# become orphaned and will return as None. If the change is a rename (that +# is, there is a 1:1 pk mapping between the old and the new), the unserializer +# can convert old to new when retrieving them. This is a list of tuples +# (old_natural_key, new_natural_key). Note that Django ContentTypes' +# natural_keys are themselves tuples (appname, modelname). Creation-dates will +# not be checked for models specified here. If new_natural_key does not exist, +# `None` will be returned and stored back as if no replacement was set. +ATTRIBUTE_STORED_MODEL_RENAME = [ + (("players", "playerdb"), ("accounts", "accountdb")), + (("typeclasses", "defaultplayer"), ("typeclasses", "defaultaccount")), +] +# Default type of autofield (required by Django) +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' + +###################################################################### +# Evennia webclient options +###################################################################### + +# default webclient options (without user changing it) +WEBCLIENT_OPTIONS = { + # Gags prompts in output window and puts them on the input bar + "gagprompt": True, + # Shows help files in a new popup window instead of in-pane + "helppopup": False, + # Shows notifications of new messages as popup windows + "notification_popup": False, + # Plays a sound for notifications of new messages + "notification_sound": False +} + +###################################################################### +# Evennia pluggable modules +###################################################################### +# Plugin modules extend Evennia in various ways. In the cases with no +# existing default, there are examples of many of these modules +# in contrib/examples. + +# The command parser module to use. See the default module for which +# functions it must implement +COMMAND_PARSER = "evennia.commands.cmdparser.cmdparser" +# On a multi-match when search objects or commands, the user has the +# ability to search again with an index marker that differentiates +# the results. If multiple "box" objects +# are found, they can by default be separated as 1-box, 2-box. Below you +# can change the regular expression used. The regex must have one +# have two capturing groups (?P...) and (?P...) - the default +# parser expects this. It should also involve a number starting from 1. +# When changing this you must also update SEARCH_MULTIMATCH_TEMPLATE +# to properly describe the syntax. +SEARCH_MULTIMATCH_REGEX = r"(?P[^-]*)-(?P[0-9]+)(?P.*)" +# To display multimatch errors in various listings we must display +# the syntax in a way that matches what SEARCH_MULTIMATCH_REGEX understand. +# The template will be populated with data and expects the following markup: +# {number} - the order of the multimatch, starting from 1; {name} - the +# name (key) of the multimatched entity; {aliases} - eventual +# aliases for the entity; {info} - extra info like #dbrefs for staff. Don't +# forget a line break if you want one match per line. +SEARCH_MULTIMATCH_TEMPLATE = " {name}-{number}{aliases}{info}\n" +# The handler that outputs errors when using any API-level search +# (not manager methods). This function should correctly report errors +# both for command- and object-searches. This allows full control +# over the error output (it uses SEARCH_MULTIMATCH_TEMPLATE by default). +SEARCH_AT_RESULT = "evennia.utils.utils.at_search_result" +# Single characters to ignore at the beginning of a command. When set, e.g. +# cmd, @cmd and +cmd will all find a command "cmd" or one named "@cmd" etc. If +# you have defined two different commands cmd and @cmd you can still enter +# @cmd to exactly target the second one. Single-character commands consisting +# of only a prefix character will not be stripped. Set to the empty +# string ("") to turn off prefix ignore. +CMD_IGNORE_PREFIXES = "@&/+" +# The module holding text strings for the connection screen. +# This module should contain one or more variables +# with strings defining the look of the screen. +CONNECTION_SCREEN_MODULE = "server.conf.connection_screens" +# Delay to use before sending the evennia.syscmdkeys.CMD_LOGINSTART Command +# when a new session connects (this defaults the unloggedin-look for showing +# the connection screen). The delay is useful mainly for telnet, to allow +# client/server to establish client capabilities like color/mxp etc before +# sending any text. A value of 0.3 should be enough. While a good idea, it may +# cause issues with menu-logins and autoconnects since the menu will not have +# started when the autoconnects starts sending menu commands. +DELAY_CMD_LOGINSTART = 0.3 +# A module that must exist - this holds the instructions Evennia will use to +# first prepare the database for use (create user #1 and Limbo etc). Only override if +# you really know what # you are doing. If replacing, it must contain a function +# handle_setup(stepname=None). The function will start being called with no argument +# and is expected to maintain a named sequence of steps. Once each step is completed, it +# should be saved with ServerConfig.objects.conf('last_initial_setup_step', stepname) +# on a crash, the system will continue by calling handle_setup with the last completed +# step. The last step in the sequence must be named 'done'. Once this key is saved, +# initialization will not run again. +INITIAL_SETUP_MODULE = "evennia.server.initial_setup" +# An optional module that, if existing, must hold a function +# named at_initial_setup(). This hook method can be used to customize +# the server's initial setup sequence (the very first startup of the system). +# The check will fail quietly if module doesn't exist or fails to load. +AT_INITIAL_SETUP_HOOK_MODULE = "server.conf.at_initial_setup" +# Module containing your custom at_server_start(), at_server_reload() and +# at_server_stop() methods. These methods will be called every time +# the server starts, reloads and resets/stops respectively. +AT_SERVER_STARTSTOP_MODULE = "server.conf.at_server_startstop" +# List of one or more module paths to modules containing a function start_ +# plugin_services(application). This module will be called with the main +# Evennia Server application when the Server is initiated. +# It will be called last in the startup sequence. +SERVER_SERVICES_PLUGIN_MODULES = ["server.conf.server_services_plugins"] +# List of one or more module paths to modules containing a function +# start_plugin_services(application). This module will be called with the +# main Evennia Portal application when the Portal is initiated. +# It will be called last in the startup sequence. +PORTAL_SERVICES_PLUGIN_MODULES = ["server.conf.portal_services_plugins"] +# Module holding MSSP meta data. This is used by MUD-crawlers to determine +# what type of game you are running, how many accounts you have etc. +MSSP_META_MODULE = "server.conf.mssp" +# Module for web plugins. +WEB_PLUGINS_MODULE = "server.conf.web_plugins" +# Tuple of modules implementing lock functions. All callable functions +# inside these modules will be available as lock functions. +LOCK_FUNC_MODULES = ("evennia.locks.lockfuncs", "server.conf.lockfuncs") +# Module holding handlers for managing incoming data from the client. These +# will be loaded in order, meaning functions in later modules may overload +# previous ones if having the same name. +INPUT_FUNC_MODULES = ["evennia.server.inputfuncs", "server.conf.inputfuncs"] +# Modules that contain prototypes for use with the spawner mechanism. +PROTOTYPE_MODULES = ["world.prototypes"] +# Modules containining Prototype functions able to be embedded in prototype +# definitions from in-game. +PROT_FUNC_MODULES = ["evennia.prototypes.protfuncs"] +# Module holding settings/actions for the dummyrunner program (see the +# dummyrunner for more information) +DUMMYRUNNER_SETTINGS_MODULE = "evennia.server.profiling.dummyrunner_settings" +# Mapping to extend Evennia's normal ANSI color tags. The mapping is a list of +# tuples mapping the exact tag (not a regex!) to the ANSI convertion, like +# `(r"%c%r", ansi.ANSI_RED)` (the evennia.utils.ansi module contains all +# ANSI escape sequences). Default is to use `|` and `|[` -prefixes. +COLOR_ANSI_EXTRA_MAP = [] +# Extend the available regexes for adding XTERM256 colors in-game. This is given +# as a list of regexes, where each regex must contain three anonymous groups for +# holding integers 0-5 for the red, green and blue components Default is +# is r'\|([0-5])([0-5])([0-5])', which allows e.g. |500 for red. +# XTERM256 foreground color replacement +COLOR_XTERM256_EXTRA_FG = [] +# XTERM256 background color replacement. Default is \|\[([0-5])([0-5])([0-5])' +COLOR_XTERM256_EXTRA_BG = [] +# Extend the available regexes for adding XTERM256 grayscale values in-game. Given +# as a list of regexes, where each regex must contain one anonymous group containing +# a single letter a-z to mark the level from white to black. Default is r'\|=([a-z])', +# which allows e.g. |=k for a medium gray. +# XTERM256 grayscale foreground +COLOR_XTERM256_EXTRA_GFG = [] +# XTERM256 grayscale background. Default is \|\[=([a-z])' +COLOR_XTERM256_EXTRA_GBG = [] +# ANSI does not support bright backgrounds, so Evennia fakes this by mapping it to +# XTERM256 backgrounds where supported. This is a list of tuples that maps the wanted +# ansi tag (not a regex!) to a valid XTERM256 background tag, such as `(r'{[r', r'{[500')`. +COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [] +# If set True, the above color settings *replace* the default |-style color markdown +# rather than extend it. +COLOR_NO_DEFAULT = False + + +###################################################################### +# Default command sets and commands +###################################################################### + +# Command set used on session before account has logged in +CMDSET_UNLOGGEDIN = "commands.default_cmdsets.UnloggedinCmdSet" +# (Note that changing these three following cmdset paths will only affect NEW +# created characters/objects, not those already in play. So if you want to +# change this and have it apply to every object, it's recommended you do it +# before having created a lot of objects (or simply reset the database after +# the change for simplicity)). +# Command set used on the logged-in session +CMDSET_SESSION = "commands.default_cmdsets.SessionCmdSet" +# Default set for logged in account with characters (fallback) +CMDSET_CHARACTER = "commands.default_cmdsets.CharacterCmdSet" +# Command set for accounts without a character (ooc) +CMDSET_ACCOUNT = "commands.default_cmdsets.AccountCmdSet" + +# Location to search for cmdsets if full path not given +CMDSET_PATHS = ["commands", "evennia", "evennia.contrib"] +# Fallbacks for cmdset paths that fail to load. Note that if you change the path for your +# default cmdsets, you will also need to copy CMDSET_FALLBACKS after your change in your +# settings file for it to detect the change. +CMDSET_FALLBACKS = { + CMDSET_CHARACTER: "evennia.commands.default.cmdset_character.CharacterCmdSet", + CMDSET_ACCOUNT: "evennia.commands.default.cmdset_account.AccountCmdSet", + CMDSET_SESSION: "evennia.commands.default.cmdset_session.SessionCmdSet", + CMDSET_UNLOGGEDIN: "evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet", +} +# Parent class for all default commands. Changing this class will +# modify all default commands, so do so carefully. +COMMAND_DEFAULT_CLASS = "evennia.commands.default.muxcommand.MuxCommand" +# Command.arg_regex is a regular expression desribing how the arguments +# to the command must be structured for the command to match a given user +# input. By default the command-name should end with a space or / (since the +# default commands uses MuxCommand and /switches). Note that the extra \n +# is necessary for use with batchprocessor. +COMMAND_DEFAULT_ARG_REGEX = r'^[ /]|\n|$' +# By default, Command.msg will only send data to the Session calling +# the Command in the first place. If set, Command.msg will instead return +# data to all Sessions connected to the Account/Character associated with +# calling the Command. This may be more intuitive for users in certain +# multisession modes. +COMMAND_DEFAULT_MSG_ALL_SESSIONS = False +# The default lockstring of a command. +COMMAND_DEFAULT_LOCKS = "" + +###################################################################### +# Typeclasses and other paths +###################################################################### + +# These are paths that will be prefixed to the paths given if the +# immediately entered path fail to find a typeclass. It allows for +# shorter input strings. They must either base off the game directory +# or start from the evennia library. +TYPECLASS_PATHS = [ + "typeclasses", + "evennia", + "evennia.contrib", + "evennia.contrib.game_systems", + "evennia.contrib.base_systems", + "evennia.contrib.full_systems", + "evennia.contrib.tutorials", + "evennia.contrib.utils", +] + +# Typeclass for account objects (linked to a character) (fallback) +BASE_ACCOUNT_TYPECLASS = "typeclasses.accounts.Account" +# Typeclass and base for all objects (fallback) +BASE_OBJECT_TYPECLASS = "typeclasses.objects.Object" +# Typeclass for character objects linked to an account (fallback) +BASE_CHARACTER_TYPECLASS = "typeclasses.characters.Character" +# Typeclass for rooms (fallback) +BASE_ROOM_TYPECLASS = "typeclasses.rooms.Room" +# Typeclass for Exit objects (fallback). +BASE_EXIT_TYPECLASS = "typeclasses.exits.Exit" +# Typeclass for Channel (fallback). +BASE_CHANNEL_TYPECLASS = "typeclasses.channels.Channel" +# Typeclass for Scripts (fallback). You usually don't need to change this +# but create custom variations of scripts on a per-case basis instead. +BASE_SCRIPT_TYPECLASS = "typeclasses.scripts.Script" +# The default home location used for all objects. This is used as a +# fallback if an object's normal home location is deleted. Default +# is Limbo (#2). +DEFAULT_HOME = "#2" +# The start position for new characters. Default is Limbo (#2). +# MULTISESSION_MODE = 0, 1 - used by default unloggedin create command +# MULTISESSION_MODE = 2, 3 - used by default character_create command +START_LOCATION = "#2" +# Lookups of Attributes, Tags, Nicks, Aliases can be aggressively +# cached to avoid repeated database hits. This often gives noticeable +# performance gains since they are called so often. Drawback is that +# if you are accessing the database from multiple processes (such as +# from a website -not- running Evennia's own webserver) data may go +# out of sync between the processes. Keep on unless you face such +# issues. +TYPECLASS_AGGRESSIVE_CACHE = True +# These are fallbacks for BASE typeclasses failing to load. Usually needed only +# during doc building. The system expects these to *always* load correctly, so +# only modify if you are making fundamental changes to how objects/accounts +# work and know what you are doing +FALLBACK_ACCOUNT_TYPECLASS = "evennia.accounts.accounts.DefaultAccount" +FALLBACK_OBJECT_TYPECLASS = "evennia.objects.objects.DefaultObject" +FALLBACK_CHARACTER_TYPECLASS = "evennia.objects.objects.DefaultCharacter" +FALLBACK_ROOM_TYPECLASS = "evennia.objects.objects.DefaultRoom" +FALLBACK_EXIT_TYPECLASS = "evennia.objects.objects.DefaultExit" +FALLBACK_CHANNEL_TYPECLASS = "evennia.comms.comms.DefaultChannel" +FALLBACK_SCRIPT_TYPECLASS = "evennia.scripts.scripts.DefaultScript" + + +###################################################################### +# Options and validators +###################################################################### + +# Options available on Accounts. Each such option is described by a +# class available from evennia.OPTION_CLASSES, in turn making use +# of validators from evennia.VALIDATOR_FUNCS to validate input when +# the user changes an option. The options are accessed through the +# `Account.options` handler. + +# ("Description", 'Option Class name in evennia.OPTION_CLASS_MODULES', 'Default Value') + +OPTIONS_ACCOUNT_DEFAULT = { + "border_color": ("Headers, footers, table borders, etc.", "Color", "n"), + "header_star_color": ("* inside Header lines.", "Color", "n"), + "header_text_color": ("Text inside Header lines.", "Color", "w"), + "header_fill": ("Fill for Header lines.", "Text", "="), + "separator_star_color": ("* inside Separator lines.", "Color", "n"), + "separator_text_color": ("Text inside Separator lines.", "Color", "w"), + "separator_fill": ("Fill for Separator Lines.", "Text", "-"), + "footer_star_color": ("* inside Footer lines.", "Color", "n"), + "footer_text_color": ("Text inside Footer Lines.", "Color", "n"), + "footer_fill": ("Fill for Footer Lines.", "Text", "="), + "column_names_color": ("Table column header text.", "Color", "w"), + "help_category_color": ("Help category names.", "Color", "n"), + "help_entry_color": ("Help entry names.", "Color", "n"), + "timezone": ("Timezone for dates. @tz for a list.", "Timezone", "UTC"), +} +# Modules holding Option classes, responsible for serializing the option and +# calling validator functions on it. Same-named functions in modules added +# later in this list will override those added earlier. +OPTION_CLASS_MODULES = ["evennia.utils.optionclasses"] +# Module holding validator functions. These are used as a resource for +# validating options, but can also be used as input validators in general. +# Same-named functions in modules added later in this list will override those +# added earlier. +VALIDATOR_FUNC_MODULES = ["evennia.utils.validatorfuncs"] + +###################################################################### +# Batch processors +###################################################################### + +# Python path to a directory to be searched for batch scripts +# for the batch processors (.ev and/or .py files). +BASE_BATCHPROCESS_PATHS = [ + "world", + "evennia.contrib", + "evennia.contrib.tutorials", +] + +###################################################################### +# Game Time setup +###################################################################### + +# You don't actually have to use this, but it affects the routines in +# evennia.utils.gametime.py and allows for a convenient measure to +# determine the current in-game time. You can of course interpret +# "week", "month" etc as your own in-game time units as desired. + +# The time factor dictates if the game world runs faster (timefactor>1) +# or slower (timefactor<1) than the real world. +TIME_FACTOR = 2.0 +# The starting point of your game time (the epoch), in seconds. +# In Python a value of 0 means Jan 1 1970 (use negatives for earlier +# start date). This will affect the returns from the utils.gametime +# module. If None, the server's first start-time is used as the epoch. +TIME_GAME_EPOCH = None +# Normally, game time will only increase when the server runs. If this is True, +# game time will not pause when the server reloads or goes offline. This setting +# together with a time factor of 1 should keep the game in sync with +# the real time (add a different epoch to shift time) +TIME_IGNORE_DOWNTIMES = False + +###################################################################### +# Help system +###################################################################### +# Help output from CmdHelp are wrapped in an EvMore call +# (excluding webclient with separate help popups). If continuous scroll +# is preferred, change 'HELP_MORE' to False. EvMORE uses CLIENT_DEFAULT_HEIGHT +HELP_MORE_ENABLED = True +# The help category of a command if not specified. +COMMAND_DEFAULT_HELP_CATEGORY = "general" +# The help category of a db or file-based help entry if not specified +DEFAULT_HELP_CATEGORY = "general" +# File-based help entries. These are modules containing dicts defining help +# entries. They can be used together with in-database entries created in-game. +FILE_HELP_ENTRY_MODULES = ["world.help_entries"] +# if topics listed in help should be clickable +# clickable links only work on clients that support MXP. +HELP_CLICKABLE_TOPICS = True + +###################################################################### +# FuncParser +# +# Strings parsed with the FuncParser can contain 'callables' on the +# form $funcname(args,kwargs), which will lead to actual Python functions +# being executed. +###################################################################### +# This changes the start-symbol for the funcparser callable. Note that +# this will make a lot of documentation invalid and there may also be +# other unexpected side effects, so change with caution. +FUNCPARSER_START_CHAR = '$' +# The symbol to use to escape Func +FUNCPARSER_ESCAPE_CHAR = '\\' +# This is the global max nesting-level for nesting functions in +# the funcparser. This protects against infinite loops. +FUNCPARSER_MAX_NESTING = 20 +# Activate funcparser for all outgoing strings. The current Session +# will be passed into the parser (used to be called inlinefuncs) +FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = False +# Only functions defined globally (and not starting with '_') in +# these modules will be considered valid inlinefuncs. The list +# is loaded from left-to-right, same-named functions will overload +FUNCPARSER_OUTGOING_MESSAGES_MODULES = ["evennia.utils.funcparser", "server.conf.inlinefuncs"] +# Prototype values are also parsed with FuncParser. These modules +# define which $func callables are available to use in prototypes. +FUNCPARSER_PROTOTYPE_PARSING_MODULES = ["evennia.prototypes.protfuncs", + "server.conf.prototypefuncs"] + +###################################################################### +# Global Scripts +###################################################################### + +# Global scripts started here will be available through +# 'evennia.GLOBAL_SCRIPTS.key'. The scripts will survive a reload and be +# recreated automatically if deleted. Each entry must have the script keys, +# whereas all other fields in the specification are optional. If 'typeclass' is +# not given, BASE_SCRIPT_TYPECLASS will be assumed. Note that if you change +# typeclass for the same key, a new Script will replace the old one on +# `evennia.GLOBAL_SCRIPTS`. +GLOBAL_SCRIPTS = { + # 'key': {'typeclass': 'typeclass.path.here', + # 'repeats': -1, 'interval': 50, 'desc': 'Example script'}, +} + +###################################################################### +# Default Account setup and access +###################################################################### + +# Different Multisession modes allow a player (=account) to connect to the +# game simultaneously with multiple clients (=sessions). In modes 0,1 there is +# only one character created to the same name as the account at first login. +# In modes 2,3 no default character will be created and the MAX_NR_CHARACTERS +# value (below) defines how many characters the default char_create command +# allow per account. +# 0 - single session, one account, one character, when a new session is +# connected, the old one is disconnected +# 1 - multiple sessions, one account, one character, each session getting +# the same data +# 2 - multiple sessions, one account, many characters, one session per +# character (disconnects multiplets) +# 3 - like mode 2, except multiple sessions can puppet one character, each +# session getting the same data. +MULTISESSION_MODE = 0 +# The maximum number of characters allowed by the default ooc char-creation command +MAX_NR_CHARACTERS = 1 +# The access hierarchy, in climbing order. A higher permission in the +# hierarchy includes access of all levels below it. Used by the perm()/pperm() +# lock functions, which accepts both plural and singular (Admin & Admins) +PERMISSION_HIERARCHY = [ + "Guest", # note-only used if GUEST_ENABLED=True + "Player", + "Helper", + "Builder", + "Admin", + "Developer", +] +# The default permission given to all new accounts +PERMISSION_ACCOUNT_DEFAULT = "Player" +# Default sizes for client window (in number of characters), if client +# is not supplying this on its own +CLIENT_DEFAULT_WIDTH = 78 +# telnet standard height is 24; does anyone use such low-res displays anymore? +CLIENT_DEFAULT_HEIGHT = 45 +# Set rate limits per-IP on account creations and login attempts. Set limits +# to None to disable. +CREATION_THROTTLE_LIMIT = 2 +CREATION_THROTTLE_TIMEOUT = 10 * 60 +LOGIN_THROTTLE_LIMIT = 5 +LOGIN_THROTTLE_TIMEOUT = 5 * 60 +# Certain characters, like html tags, line breaks and tabs are stripped +# from user input for commands using the `evennia.utils.strip_unsafe_input` helper +# since they can be exploitative. This list defines Account-level permissions +# (and higher) that bypass this stripping. It is used as a fallback if a +# specific list of perms are not given to the helper function. +INPUT_CLEANUP_BYPASS_PERMISSIONS = ['Builder'] + + +###################################################################### +# Guest accounts +###################################################################### + +# This enables guest logins, by default via "connect guest". Note that +# you need to edit your login screen to inform about this possibility. +GUEST_ENABLED = False +# Typeclass for guest account objects (linked to a character) +BASE_GUEST_TYPECLASS = "typeclasses.accounts.Guest" +# The permission given to guests +PERMISSION_GUEST_DEFAULT = "Guests" +# The default home location used for guests. +GUEST_HOME = DEFAULT_HOME +# The start position used for guest characters. +GUEST_START_LOCATION = START_LOCATION +# The naming convention used for creating new guest +# accounts/characters. The size of this list also determines how many +# guests may be on the game at once. The default is a maximum of nine +# guests, named Guest1 through Guest9. +GUEST_LIST = ["Guest" + str(s + 1) for s in range(9)] + +###################################################################### +# In-game Channels created from server start +###################################################################### + +# The mudinfo channel is a read-only channel used by Evennia to replay status +# messages, connection info etc to staff. The superuser will automatically be +# subscribed to this channel. If set to None, the channel is disabled and +# status messages will only be logged (not recommended). +CHANNEL_MUDINFO = { + "key": "MudInfo", + "aliases": "", + "desc": "Connection log", + "locks": "control:perm(Developer);listen:perm(Admin);send:false()", +} +# Optional channel (same form as CHANNEL_MUDINFO) that will receive connection +# messages like (" has (dis)connected"). While the MudInfo channel +# will also receieve this info, this channel is meant for non-staffers. If +# None, this information will only be logged. +CHANNEL_CONNECTINFO = None +# New accounts will auto-sub to the default channels given below (but they can +# unsub at any time). Traditionally, at least 'public' should exist. Entries +# will be (re)created on the next reload, but removing or updating a same-key +# channel from this list will NOT automatically change/remove it in the game, +# that needs to be done manually. Note: To create other, non-auto-subbed +# channels, create them manually in server/conf/at_initial_setup.py. +DEFAULT_CHANNELS = [ + { + "key": "Public", + "aliases": ("pub",), + "desc": "Public discussion", + "locks": "control:perm(Admin);listen:all();send:all()", + "typeclass": BASE_CHANNEL_TYPECLASS, + } +] + +###################################################################### +# External Connections +###################################################################### + +# Note: You do *not* have to make your MUD open to +# the public to use the external connections, they +# operate as long as you have an internet connection, +# just like stand-alone chat clients. + +# The Evennia Game Index is a dynamic listing of Evennia games. You can add your game +# to this list also if it is in closed pre-alpha development. +GAME_INDEX_ENABLED = False +# This dict +GAME_INDEX_LISTING = { + "game_name": "Mygame", # usually SERVERNAME + "game_status": "pre-alpha", # pre-alpha, alpha, beta or launched + "short_description": "", # could be GAME_SLOGAN + "long_description": "", + "listing_contact": "", # email + "telnet_hostname": "", # mygame.com + "telnet_port": "", # 1234 + "game_website": "", # http://mygame.com + "web_client_url": "", # http://mygame.com/webclient +} +# Evennia can connect to external IRC channels and +# echo what is said on the channel to IRC and vice +# versa. Obs - make sure the IRC network allows bots. +# When enabled, command @irc2chan will be available in-game +# IRC requires that you have twisted.words installed. +IRC_ENABLED = False +# RSS allows to connect RSS feeds (from forum updates, blogs etc) to +# an in-game channel. The channel will be updated when the rss feed +# updates. Use @rss2chan in game to connect if this setting is +# active. OBS: RSS support requires the python-feedparser package to +# be installed (through package manager or from the website +# http://code.google.com/p/feedparser/) +RSS_ENABLED = False +RSS_UPDATE_INTERVAL = 60 * 10 # 10 minutes +# Grapevine (grapevine.haus) is a network for listing MUDs as well as allow +# users of said MUDs to communicate with each other on shared channels. To use, +# your game must first be registered by logging in and creating a game entry at +# https://grapevine.haus. Evennia links grapevine channels to in-game channels +# with the @grapevine2chan command, available once this flag is set +# Grapevine requires installing the pyopenssl library (pip install pyopenssl) +GRAPEVINE_ENABLED = False +# Grapevine channels to allow connection to. See https://grapevine.haus/chat +# for the available channels. Only channels in this list can be linked to in-game +# channels later. +GRAPEVINE_CHANNELS = ["gossip", "testing"] +# Grapevine authentication. Register your game at https://grapevine.haus to get +# them. These are secret and should thus be overridden in secret_settings file +GRAPEVINE_CLIENT_ID = "" +GRAPEVINE_CLIENT_SECRET = "" + +###################################################################### +# Django web features +###################################################################### + +# While DEBUG is False, show a regular server error page on the web +# stuff, email the traceback to the people in the ADMINS tuple +# below. If True, show a detailed traceback for the web +# browser to display. Note however that this will leak memory when +# active, so make sure to turn it off for a production server! +DEBUG = False +# Emails are sent to these people if the above DEBUG value is False. If you'd +# rather prefer nobody receives emails, leave this commented out or empty. +ADMINS = () # 'Your Name', 'your_email@domain.com'),) +# These guys get broken link notifications when SEND_BROKEN_LINK_EMAILS is True. +MANAGERS = ADMINS +# This is a public point of contact for players or the public to contact +# a staff member or administrator of the site. It is publicly posted. +STAFF_CONTACT_EMAIL = None +# If using Sites/Pages from the web admin, this value must be set to the +# database-id of the Site (domain) we want to use with this game's Pages. +SITE_ID = 1 +# The age for sessions. +# Default: 1209600 (2 weeks, in seconds) +SESSION_COOKIE_AGE = 1209600 +# Session cookie domain +# Default: None +SESSION_COOKIE_DOMAIN = None +# The name of the cookie to use for sessions. +# Default: 'sessionid' +SESSION_COOKIE_NAME = "sessionid" +# Should the session expire when the browser closes? +# Default: False +SESSION_EXPIRE_AT_BROWSER_CLOSE = False +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = False + +# Where to find locales (no need to change this, most likely) +LOCALE_PATHS = [os.path.join(EVENNIA_DIR, "locale/")] +# How to display time stamps in e.g. the admin +SHORT_DATETIME_FORMAT = 'Y-m-d H:i:s.u' +DATETIME_FORMAT = 'Y-m-d H:i:s' # ISO 8601 but without T and timezone +# This should be turned off unless you want to do tests with Django's +# development webserver (normally Evennia runs its own server) +SERVE_MEDIA = False +# The master urlconf file that contains all of the sub-branches to the +# applications. Change this to add your own URLs to the website. +ROOT_URLCONF = "web.urls" +# Where users are redirected after logging in via contrib.auth.login. +LOGIN_REDIRECT_URL = "/" +# Where to redirect users when using the @login_required decorator. +LOGIN_URL = reverse_lazy("login") +# Where to redirect users who wish to logout. +LOGOUT_URL = reverse_lazy("logout") +# URL that handles the media served from MEDIA_ROOT. +# Example: "http://media.lawrence.com" +MEDIA_URL = "/media/" +# Absolute path to the directory that holds file uploads from web apps. +MEDIA_ROOT = os.path.join(GAME_DIR, "server", ".media") +# URL prefix for admin media -- CSS, JavaScript and images. Make sure +# to use a trailing slash. Admin-related files are searched under STATIC_URL/admin. +STATIC_URL = "/static/" +# Absolute path to directory where the static data will be gathered into to be +# served by webserver. +STATIC_ROOT = os.path.join(GAME_DIR, "server", ".static") +# Location of static data to overload the defaults from +# evennia/web/static. +STATICFILES_DIRS = [os.path.join(GAME_DIR, "web", "static")] +# Patterns of files in the static directories. Used here to make sure that +# its readme file is preserved but unused. +STATICFILES_IGNORE_PATTERNS = ["README.md"] +# The name of the currently selected web template. This corresponds to the +# directory names shown in the templates directory. +WEBSITE_TEMPLATE = "website" +WEBCLIENT_TEMPLATE = "webclient" +# We setup the location of the website template as well as the admin site. +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(GAME_DIR, "web", "templates"), + os.path.join(GAME_DIR, "web", "templates", WEBSITE_TEMPLATE), + os.path.join(GAME_DIR, "web", "templates", WEBCLIENT_TEMPLATE), + os.path.join(EVENNIA_DIR, "web", "templates"), + os.path.join(EVENNIA_DIR, "web", "templates", WEBSITE_TEMPLATE), + os.path.join(EVENNIA_DIR, "web", "templates", WEBCLIENT_TEMPLATE), + ], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.i18n", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.media", + "django.template.context_processors.debug", + "django.contrib.messages.context_processors.messages", + "sekizai.context_processors.sekizai", + "evennia.web.utils.general_context.general_context", + ], + # While true, show "pretty" error messages for template syntax errors. + "debug": DEBUG, + }, + } +] +# Django cache settings +# https://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + }, + 'throttle': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'TIMEOUT': 60 * 5, + 'OPTIONS': { + 'MAX_ENTRIES': 2000 + } + } +} +# MiddleWare are semi-transparent extensions to Django's functionality. +# see http://www.djangoproject.com/documentation/middleware/ for a more detailed +# explanation. +MIDDLEWARE = [ + "django.middleware.common.CommonMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", # 1.4? + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.admindocs.middleware.XViewMiddleware", + "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware", + "evennia.web.utils.middleware.SharedLoginMiddleware", +] + +###################################################################### +# Evennia components +###################################################################### + +# Global and Evennia-specific apps. This ties everything together so we can +# refer to app models and perform DB syncs. +INSTALLED_APPS = [ + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.admindocs", + "django.contrib.flatpages", + "django.contrib.sites", + "django.contrib.staticfiles", + "evennia.web.utils.adminsite.EvenniaAdminApp", # replaces django.contrib.admin + "django.contrib.messages", + "rest_framework", + "django_filters", + "sekizai", + "evennia.utils.idmapper", + "evennia.server", + "evennia.typeclasses", + "evennia.accounts", + "evennia.objects", + "evennia.comms", + "evennia.help", + "evennia.scripts", + "evennia.web", +] +# The user profile extends the User object with more functionality; +# This should usually not be changed. +AUTH_USER_MODEL = "accounts.AccountDB" + +# Password validation plugins +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": {"min_length": 8}, + }, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, + {"NAME": "evennia.server.validators.EvenniaPasswordValidator"}, +] + +# Username validation plugins +AUTH_USERNAME_VALIDATORS = [ + {"NAME": "django.contrib.auth.validators.ASCIIUsernameValidator"}, + {"NAME": "django.core.validators.MinLengthValidator", "OPTIONS": {"limit_value": 3},}, + {"NAME": "django.core.validators.MaxLengthValidator", "OPTIONS": {"limit_value": 30},}, + {"NAME": "evennia.server.validators.EvenniaUsernameAvailabilityValidator"}, +] + +# Use a custom test runner that just tests Evennia-specific apps. +TEST_RUNNER = "evennia.server.tests.testrunner.EvenniaTestSuiteRunner" + +# Messages and Bootstrap don't classify events the same way; this setting maps +# messages.error() to Bootstrap 'danger' classes. +MESSAGE_TAGS = {messages.ERROR: "danger"} + +# Django REST Framework settings +REST_FRAMEWORK = { + # django_filters allows you to specify search fields for models in an API View + "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",), + # whether to paginate results and how many per page + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", + "PAGE_SIZE": 25, + # require logged in users to call API so that access checks can work on them + "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated",], + # These are the different ways people can authenticate for API requests - via + # session or with user/password. Other ways are possible, such as via tokens + # or oauth, but require additional dependencies. + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.BasicAuthentication", + "rest_framework.authentication.SessionAuthentication", + ], + # default permission checks used by the EvenniaPermission class + "DEFAULT_CREATE_PERMISSION": "builder", + "DEFAULT_LIST_PERMISSION": "builder", + "DEFAULT_VIEW_LOCKS": ["examine"], + "DEFAULT_DESTROY_LOCKS": ["delete"], + "DEFAULT_UPDATE_LOCKS": ["control", "edit"], + # No throttle class set by default. Setting one also requires a cache backend to be specified. +} + +# To enable the REST api, turn this to True +REST_API_ENABLED = False + +###################################################################### +# Networking Replaceables +###################################################################### +# This allows for replacing the very core of the infrastructure holding Evennia +# together with your own variations. You should usually never have to touch +# this, and if so, you really need to know what you are doing. + +# The Base Session Class is used as a parent class for all Protocols such as +# Telnet and SSH.) Changing this could be really dangerous. It will cascade +# to tons of classes. You generally shouldn't need to touch protocols. +BASE_SESSION_CLASS = "evennia.server.session.Session" + +# Telnet Protocol inherits from whatever above BASE_SESSION_CLASS is specified. +# It is used for all telnet connections, and is also inherited by the SSL Protocol +# (which is just TLS + Telnet). +TELNET_PROTOCOL_CLASS = "evennia.server.portal.telnet.TelnetProtocol" +SSL_PROTOCOL_CLASS = "evennia.server.portal.ssl.SSLProtocol" + +# Websocket Client Protocol. This inherits from BASE_SESSION_CLASS. It is used +# for all webclient connections. +WEBSOCKET_PROTOCOL_CLASS = "evennia.server.portal.webclient.WebSocketClient" + +# Protocol for the SSH interface. This inherits from BASE_SESSION_CLASS. +SSH_PROTOCOL_CLASS = "evennia.server.portal.ssh.SshProtocol" + +# Server-side session class used. This will inherit from BASE_SESSION_CLASS. +# This one isn't as dangerous to replace. +SERVER_SESSION_CLASS = "evennia.server.serversession.ServerSession" + +# The Server SessionHandler manages all ServerSessions, handling logins, +# ensuring the login process happens smoothly, handling expected and +# unexpected disconnects. You shouldn't need to touch it, but you can. +# Replace it to implement altered game logic. +SERVER_SESSION_HANDLER_CLASS = "evennia.server.sessionhandler.ServerSessionHandler" + +# The Portal SessionHandler manages all incoming connections regardless of +# the protocol in use. It is responsible for keeping them going and informing +# the Server Session Handler of the connections and synchronizing them across the +# AMP connection. You shouldn't ever need to change this. But you can. +PORTAL_SESSION_HANDLER_CLASS = "evennia.server.portal.portalsessionhandler.PortalSessionHandler" + + +# These are members / properties / attributes kept on both Server and +# Portal Sessions. They are sync'd at various points, such as logins and +# reloads. If you add to this, you may need to adjust the class __init__ +# so the additions have somewhere to go. These must be simple things that +# can be pickled - stuff you could serialize to JSON is best. +SESSION_SYNC_ATTRS = ( + "protocol_key", + "address", + "suid", + "sessid", + "uid", + "csessid", + "uname", + "logged_in", + "puid", + "conn_time", + "cmd_last", + "cmd_last_visible", + "cmd_total", + "protocol_flags", + "server_data", + "cmdset_storage_string", +) + +# The following are used for the communications between the Portal and Server. +# Very dragons territory. +AMP_SERVER_PROTOCOL_CLASS = "evennia.server.portal.amp_server.AMPServerProtocol" +AMP_CLIENT_PROTOCOL_CLASS = "evennia.server.amp_client.AMPServerClientProtocol" + +# don't change this manually, it can be checked from code to know if +# being run from a unit test (set by the evennia.utils.test_resources.BaseEvenniaTest +# and BaseEvenniaTestCase unit testing parents) +_TEST_ENVIRONMENT = False + +###################################################################### +# Django extensions +###################################################################### + +# Django extesions are useful third-party tools that are not +# always included in the default django distro. +try: + import django_extensions # noqa + + INSTALLED_APPS += ["django_extensions"] +except ImportError: + # Django extensions are not installed in all distros. + pass + +####################################################################### +# SECRET_KEY +####################################################################### +# This is the signing key for the cookies generated by Evennia's +# web interface. +# +# It is a fallback for the SECRET_KEY setting in settings.py, which +# is randomly seeded when settings.py is first created. If copying +# from here, make sure to change it! +SECRET_KEY = "changeme!(*#&*($&*(#*(&SDFKJJKLS*(@#KJAS" + +``` diff --git a/docs/1.0-dev/_sources/Setup/Settings-File.md.txt b/docs/1.0-dev/_sources/Setup/Settings-File.md.txt deleted file mode 100644 index a035a66873..0000000000 --- a/docs/1.0-dev/_sources/Setup/Settings-File.md.txt +++ /dev/null @@ -1,3 +0,0 @@ -# The Evennia Default Settings file - -TODO \ No newline at end of file diff --git a/docs/1.0-dev/_sources/Setup/Setup-Overview.md.txt b/docs/1.0-dev/_sources/Setup/Setup-Overview.md.txt index 6c49ae743d..456c3ddf3d 100644 --- a/docs/1.0-dev/_sources/Setup/Setup-Overview.md.txt +++ b/docs/1.0-dev/_sources/Setup/Setup-Overview.md.txt @@ -4,10 +4,10 @@ This documentation covers how to setup and maintain the server, from first insta ## Installation & running -- [Installation & Setup quick-start](./Setup-Quickstart.md) - one page to quickly get you going -- [Extended Install instructions](./Extended-Installation.md) - if you have trouble or want to contribute to Evennia itself -- [Running through Docker](./Running-Evennia-in-Docker.md) - alternative install method, useful for quick deployment on remote servers -- [Installing Evennia on Android](./Installing-on-Android.md) - for those craving a mobile life +- [Installation & Setup quick-start](./Installation.md) - one page to quickly get you going +- [Extended Install instructions](./Installation-Git.md) - if you have trouble or want to contribute to Evennia itself +- [Running through Docker](./Installation-Docker.md) - alternative install method, useful for quick deployment on remote servers +- [Installing Evennia on Android](./Installation-Android.md) - for those craving a mobile life - [Controlling the server](./Start-Stop-Reload.md) - an extended view on how to start/stop/update the server ## Installing custom game dirs @@ -16,7 +16,7 @@ This documentation covers how to setup and maintain the server, from first insta ## Configuring -- [The settings file](./Settings-File.md) - how and where to change the main settings of the server +- [The settings file](./Settings-Default.md) - how and where to change the main settings of the server - [Change database engine](./Choosing-An-SQL-Server.md) - if you want to use something other than SQLite3 - [Evennia game index](./Evennia-Game-Index.md) - register your upcoming game with the index to start the hype going diff --git a/docs/1.0-dev/_sources/Setup/Setup-Quickstart.md.txt b/docs/1.0-dev/_sources/Setup/Setup-Quickstart.md.txt deleted file mode 100644 index b64350b72b..0000000000 --- a/docs/1.0-dev/_sources/Setup/Setup-Quickstart.md.txt +++ /dev/null @@ -1,99 +0,0 @@ -# Setup quickstart - -The Evennia server is installed, run and maintained from the terminal (console/CMD on Windows). Starting the -server doesn't make anything visible online. Once you download everything you can in fact develop your game -in complete isolation if you want, without needing any access to the internet. - -## Installation - -Evennia supports Python 3.7 to 3.9. As with most Python packages, using a -[virtualenv](../Glossary.md#virtualenv) is recommended in order to keep your -installation independent from the system libraries. It's _not_ recommended -to install Evennia as superuser. - -```{warning} - - This is not yet available. Switch to the 0.9.5 version of these docs to install Evennia. -``` - - pip install evennia - -Make sure the `evennia` command works. Use `evennia -h` for usage help (or read on). - -If you are having trouble, want to install in some other way (like with Docker) -or want to contribute to Evennia itself, check out the [Extended Installation -instructions](./Extended-Installation.md). It also has a [troubleshooting -section](./Extended-Installation.md#troubleshooting) for different operating -systems. - - -## Initialize a new game - -Use `cd` to enter a folder where you want to do your game development. Here (and in -the rest of the Evennia documentation) we call this folder `mygame`, but you should of course -name your game whatever you like: - - evennia --init mygame - -This will create a new folder `mygame` (or whatever you chose) in your current location. This -contains empty templates and all the default settings needed to start the server. - - -## Start the new game - -`cd` into your game folder (`mygame` in our case). Next, run - - evennia migrate - -This will create the default database (Sqlite3). The database file ends up as `mygame/server/evennia.db3`. If you -ever want to start from a fresh database, just delete this file and re-run `evennia migrate` again. - - evennia start - -Set your user-name and password when prompted. This will be the "god user" or "superuser" in-game. The email is optional. - -If all went well, the server is now up and running. Point a legacy MUD/telnet client to `localhost:4000` or -a web browser at [http://localhost:4001](http://localhost:4001) to play your new (if empty) game! - -> If `localhost` doesn't work on your computer, use `127.0.0.1`, which is the same thing. - - -## See server logs - -This will echol the server logs to the terminal as they come in: - - evennia --log - -or - - evennia -l - - -You can also start logging immediately when running `evennia` commands, such as - - - evennia start -l - - -To exit the log view, enter `Ctrl-C` (`Cmd-C` for Mac). This will not affect the server. - - -## Restarting and stopping - - -You can restart the server without disconnecting any connected players: - - evennia restart - -To do a full stop and restart (will disconnect everyone): - - evennia reboot - -Full stop of the server (will need to use `start` to activate it again): - - evennia stop - - -## The Next step - -Why not head into the [Starting Tutorial](../Howto/Starting/Part1/Starting-Part1.md) to learn how to start making your new game! diff --git a/docs/1.0-dev/_sources/Setup/Start-Stop-Reload.md.txt b/docs/1.0-dev/_sources/Setup/Start-Stop-Reload.md.txt index 32fc11189d..80cb76cf3e 100644 --- a/docs/1.0-dev/_sources/Setup/Start-Stop-Reload.md.txt +++ b/docs/1.0-dev/_sources/Setup/Start-Stop-Reload.md.txt @@ -3,7 +3,7 @@ You control Evennia from your game folder (we refer to it as `mygame/` here), using the `evennia` program. If the `evennia` program is not available on the command line you must first install -Evennia as described in the [Setup Quickstart](./Setup-Quickstart.md) page. +Evennia as described in the [Setup Quickstart](./Installation.md) page. > Hint: If you ever try the `evennia` command and get an error complaining that the command is not available, make sure your [virtualenv](../Glossary.md#virtualenv) is active. diff --git a/docs/1.0-dev/_sources/index.md.txt b/docs/1.0-dev/_sources/index.md.txt index f7dae8eef5..c9430d5754 100644 --- a/docs/1.0-dev/_sources/index.md.txt +++ b/docs/1.0-dev/_sources/index.md.txt @@ -17,7 +17,7 @@ This is the manual of [Evennia](https://www.evennia.com), the open source Python `MU*` creation system. - [Evennia Introduction](./Evennia-Introduction.md) -- [Install & Setup Quickstart](Setup/Setup-Quickstart.md) +- [Install & Setup Quickstart](Setup/Installation.md) - [Starting tutorial](Howto/Starting/Part1/Starting-Part1.md) - [How to contribute and get help](./Contributing.md) diff --git a/docs/1.0-dev/_sources/toc.md.txt b/docs/1.0-dev/_sources/toc.md.txt index 61581c5989..e5556895fe 100644 --- a/docs/1.0-dev/_sources/toc.md.txt +++ b/docs/1.0-dev/_sources/toc.md.txt @@ -155,7 +155,7 @@ Howto/Starting/Part1/Python-basic-introduction Howto/Starting/Part1/Python-classes-and-objects Howto/Starting/Part1/Searching-Things Howto/Starting/Part1/Starting-Part1 -Howto/Starting/Part1/Tutorial-World-Introduction +Howto/Starting/Part1/Tutorial-World Howto/Starting/Part2/Game-Planning Howto/Starting/Part2/Planning-Some-Useful-Contribs Howto/Starting/Part2/Planning-The-Tutorial-Game @@ -182,23 +182,27 @@ Howto/Web-Character-View-Tutorial Licensing Links Setup/Apache-Config +Setup/Changelog Setup/Choosing-An-SQL-Server Setup/Client-Support-Grid Setup/Evennia-Game-Index -Setup/Extended-Installation Setup/Grapevine Setup/HAProxy-Config Setup/How-to-connect-Evennia-to-Twitter Setup/IRC -Setup/Installing-on-Android +Setup/Installation +Setup/Installation-Android +Setup/Installation-Docker +Setup/Installation-Git +Setup/Installation-Non-Interactive +Setup/Installation-Troubleshooting +Setup/Installation-Upgrade Setup/Online-Setup Setup/RSS -Setup/Running-Evennia-in-Docker Setup/Security Setup/Server-Conf -Setup/Settings-File +Setup/Settings-Default Setup/Setup-Overview -Setup/Setup-Quickstart Setup/Start-Stop-Reload Unimplemented api/evennia diff --git a/docs/1.0-dev/api/evennia-api.html b/docs/1.0-dev/api/evennia-api.html index 95115fec09..d603de8217 100644 --- a/docs/1.0-dev/api/evennia-api.html +++ b/docs/1.0-dev/api/evennia-api.html @@ -633,7 +633,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.accounts.accounts.html b/docs/1.0-dev/api/evennia.accounts.accounts.html index ef3959edd4..73f3b153bf 100644 --- a/docs/1.0-dev/api/evennia.accounts.accounts.html +++ b/docs/1.0-dev/api/evennia.accounts.accounts.html @@ -1179,7 +1179,6 @@ overriding the call (unused by default).

      Versions

      diff --git a/docs/1.0-dev/api/evennia.accounts.bots.html b/docs/1.0-dev/api/evennia.accounts.bots.html index dd7292ce17..e220c27c53 100644 --- a/docs/1.0-dev/api/evennia.accounts.bots.html +++ b/docs/1.0-dev/api/evennia.accounts.bots.html @@ -494,7 +494,6 @@ triggered by the bot_data_in Inputfunc.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.accounts.html b/docs/1.0-dev/api/evennia.accounts.html index 74ca76e693..d6cafdd5b9 100644 --- a/docs/1.0-dev/api/evennia.accounts.html +++ b/docs/1.0-dev/api/evennia.accounts.html @@ -95,7 +95,6 @@ more Objects depending on settings. An Account has no in-game existence.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.accounts.manager.html b/docs/1.0-dev/api/evennia.accounts.manager.html index c301110e07..e9429b654c 100644 --- a/docs/1.0-dev/api/evennia.accounts.manager.html +++ b/docs/1.0-dev/api/evennia.accounts.manager.html @@ -304,7 +304,6 @@ accounts of this typeclass.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.accounts.models.html b/docs/1.0-dev/api/evennia.accounts.models.html index f03371ad5f..d47d537908 100644 --- a/docs/1.0-dev/api/evennia.accounts.models.html +++ b/docs/1.0-dev/api/evennia.accounts.models.html @@ -162,6 +162,13 @@ instances.

      class built by **create_forward_many_to_many_manager()** defined below.

      +
      +
      +date_joined
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      db_attributes
      @@ -178,6 +185,27 @@ instances.

      class built by **create_forward_many_to_many_manager()** defined below.

      +
      +
      +db_date_created
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +db_key
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +db_lock_storage
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      db_tags
      @@ -194,6 +222,27 @@ instances.

      class built by **create_forward_many_to_many_manager()** defined below.

      +
      +
      +db_typeclass_path
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +email
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +first_name
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      get_next_by_date_joined(*, field=<django.db.models.fields.DateTimeField: date_joined>, is_next=True, **kwargs)
      @@ -253,6 +302,13 @@ class built by **create_forward_many_to_many_manager()** define object the first time, the query is executed.

      +
      +
      +is_active
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      property is_bot
      @@ -265,6 +321,34 @@ object the first time, the query is executed.

      A wrapper for getting database field db_is_connected.

      +
      +
      +is_staff
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +is_superuser
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +last_login
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +last_name
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      logentry_set
      @@ -295,6 +379,13 @@ many-to-one relation.

      class built by **create_forward_many_to_many_manager()** defined below.

      +
      +
      +password
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      path = 'evennia.accounts.models.AccountDB'
      @@ -368,6 +459,13 @@ instances.

      class built by **create_forward_many_to_many_manager()** defined below.

      +
      +
      +username
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      @@ -412,7 +510,6 @@ class built by **create_forward_many_to_many_manager()** define

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.cmdhandler.html b/docs/1.0-dev/api/evennia.commands.cmdhandler.html index 6079cda897..8c9e78e4ec 100644 --- a/docs/1.0-dev/api/evennia.commands.cmdhandler.html +++ b/docs/1.0-dev/api/evennia.commands.cmdhandler.html @@ -170,7 +170,6 @@ default Evennia.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.cmdparser.html b/docs/1.0-dev/api/evennia.commands.cmdparser.html index 0a19fd716c..e4cf79cd2a 100644 --- a/docs/1.0-dev/api/evennia.commands.cmdparser.html +++ b/docs/1.0-dev/api/evennia.commands.cmdparser.html @@ -206,7 +206,6 @@ the remaining arguments, and the matched cmdobject from the cmdset.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.cmdset.html b/docs/1.0-dev/api/evennia.commands.cmdset.html index 253138fa9c..53ceae0995 100644 --- a/docs/1.0-dev/api/evennia.commands.cmdset.html +++ b/docs/1.0-dev/api/evennia.commands.cmdset.html @@ -423,7 +423,6 @@ self.add().

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.cmdsethandler.html b/docs/1.0-dev/api/evennia.commands.cmdsethandler.html index f6be176707..2726ff0533 100644 --- a/docs/1.0-dev/api/evennia.commands.cmdsethandler.html +++ b/docs/1.0-dev/api/evennia.commands.cmdsethandler.html @@ -391,7 +391,6 @@ handled automatically by @reload).

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.command.html b/docs/1.0-dev/api/evennia.commands.command.html index a40c9cc40b..21db65735c 100644 --- a/docs/1.0-dev/api/evennia.commands.command.html +++ b/docs/1.0-dev/api/evennia.commands.command.html @@ -150,7 +150,7 @@ replace this without disabling auto_help.

      -arg_regex = re.compile('^[ /]+.*$|$', re.IGNORECASE)
      +arg_regex = re.compile('^[ /]|\\n|$', re.IGNORECASE)
      @@ -529,7 +529,6 @@ detailing the contents of the table.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.account.html b/docs/1.0-dev/api/evennia.commands.default.account.html index 9a07462030..e2c4a48a01 100644 --- a/docs/1.0-dev/api/evennia.commands.default.account.html +++ b/docs/1.0-dev/api/evennia.commands.default.account.html @@ -859,7 +859,6 @@ to all the variables defined therein.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.admin.html b/docs/1.0-dev/api/evennia.commands.default.admin.html index 4537a0850e..f5aac9e474 100644 --- a/docs/1.0-dev/api/evennia.commands.default.admin.html +++ b/docs/1.0-dev/api/evennia.commands.default.admin.html @@ -257,7 +257,7 @@ to accounts respectively.

      -aliases = ['remit', 'pemit']
      +aliases = ['pemit', 'remit']
      @@ -288,7 +288,7 @@ to accounts respectively.

      -search_index_entry = {'aliases': 'remit pemit', 'category': 'admin', 'key': 'emit', 'no_prefix': ' remit pemit', 'tags': '', 'text': '\n admin command for emitting message to multiple objects\n\n Usage:\n emit[/switches] [<obj>, <obj>, ... =] <message>\n remit [<obj>, <obj>, ... =] <message>\n pemit [<obj>, <obj>, ... =] <message>\n\n Switches:\n room - limit emits to rooms only (default)\n accounts - limit emits to accounts only\n contents - send to the contents of matched objects too\n\n Emits a message to the selected objects or to\n your immediate surroundings. If the object is a room,\n send to its contents. remit and pemit are just\n limited forms of emit, for sending to rooms and\n to accounts respectively.\n '}
      +search_index_entry = {'aliases': 'pemit remit', 'category': 'admin', 'key': 'emit', 'no_prefix': ' pemit remit', 'tags': '', 'text': '\n admin command for emitting message to multiple objects\n\n Usage:\n emit[/switches] [<obj>, <obj>, ... =] <message>\n remit [<obj>, <obj>, ... =] <message>\n pemit [<obj>, <obj>, ... =] <message>\n\n Switches:\n room - limit emits to rooms only (default)\n accounts - limit emits to accounts only\n contents - send to the contents of matched objects too\n\n Emits a message to the selected objects or to\n your immediate surroundings. If the object is a room,\n send to its contents. remit and pemit are just\n limited forms of emit, for sending to rooms and\n to accounts respectively.\n '}
      @@ -544,7 +544,6 @@ including all currently unlogged in.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.batchprocess.html b/docs/1.0-dev/api/evennia.commands.default.batchprocess.html index 4c14a254c7..ef4662f632 100644 --- a/docs/1.0-dev/api/evennia.commands.default.batchprocess.html +++ b/docs/1.0-dev/api/evennia.commands.default.batchprocess.html @@ -78,7 +78,7 @@ skipping, reloading etc.

      -aliases = ['batchcommand', 'batchcmd']
      +aliases = ['batchcmd', 'batchcommand']
      @@ -109,7 +109,7 @@ skipping, reloading etc.

      -search_index_entry = {'aliases': 'batchcommand batchcmd', 'category': 'building', 'key': 'batchcommands', 'no_prefix': ' batchcommand batchcmd', 'tags': '', 'text': '\n build from batch-command file\n\n Usage:\n batchcommands[/interactive] <python.path.to.file>\n\n Switch:\n interactive - this mode will offer more control when\n executing the batch file, like stepping,\n skipping, reloading etc.\n\n Runs batches of commands from a batch-cmd text file (*.ev).\n\n '}
      +search_index_entry = {'aliases': 'batchcmd batchcommand', 'category': 'building', 'key': 'batchcommands', 'no_prefix': ' batchcmd batchcommand', 'tags': '', 'text': '\n build from batch-command file\n\n Usage:\n batchcommands[/interactive] <python.path.to.file>\n\n Switch:\n interactive - this mode will offer more control when\n executing the batch file, like stepping,\n skipping, reloading etc.\n\n Runs batches of commands from a batch-cmd text file (*.ev).\n\n '}
      @@ -219,7 +219,6 @@ object copies behind when testing out the script.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.building.html b/docs/1.0-dev/api/evennia.commands.default.building.html index 8dcc91ef3c..1c2623af43 100644 --- a/docs/1.0-dev/api/evennia.commands.default.building.html +++ b/docs/1.0-dev/api/evennia.commands.default.building.html @@ -1285,7 +1285,7 @@ server settings.

      -aliases = ['@type', '@swap', '@typeclasses', '@parent', '@update']
      +aliases = ['@update', '@parent', '@swap', '@typeclasses', '@type']
      @@ -1316,7 +1316,7 @@ server settings.

      -search_index_entry = {'aliases': '@type @swap @typeclasses @parent @update', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass type swap typeclasses parent update', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclasses or typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object. This will also\n reset cmdsets!\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}
      +search_index_entry = {'aliases': '@update @parent @swap @typeclasses @type', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass update parent swap typeclasses type', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclasses or typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object. This will also\n reset cmdsets!\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}
      @@ -1471,7 +1471,7 @@ If object is not specified, the current location is examined.

      -aliases = ['@exam', '@ex']
      +aliases = ['@ex', '@exam']
      @@ -1739,7 +1739,7 @@ the cases, see the module doc.

      -search_index_entry = {'aliases': '@exam @ex', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine exam ex', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n script - examine a Script\n channel - examine a Channel\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}
      +search_index_entry = {'aliases': '@ex @exam', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine ex exam', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n script - examine a Script\n channel - examine a Channel\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}
      @@ -2261,7 +2261,6 @@ displays a list of available prototypes.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.cmdset_account.html b/docs/1.0-dev/api/evennia.commands.default.cmdset_account.html index 442c86fada..53ff551c87 100644 --- a/docs/1.0-dev/api/evennia.commands.default.cmdset_account.html +++ b/docs/1.0-dev/api/evennia.commands.default.cmdset_account.html @@ -118,7 +118,6 @@ command method rather than caller.msg().

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.cmdset_character.html b/docs/1.0-dev/api/evennia.commands.default.cmdset_character.html index e2af49b1f3..06bf4c160e 100644 --- a/docs/1.0-dev/api/evennia.commands.default.cmdset_character.html +++ b/docs/1.0-dev/api/evennia.commands.default.cmdset_character.html @@ -116,7 +116,6 @@ Account cmdset. Account commands remain available also to Characters.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.cmdset_session.html b/docs/1.0-dev/api/evennia.commands.default.cmdset_session.html index e4e76025cb..e59179d459 100644 --- a/docs/1.0-dev/api/evennia.commands.default.cmdset_session.html +++ b/docs/1.0-dev/api/evennia.commands.default.cmdset_session.html @@ -113,7 +113,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.cmdset_unloggedin.html b/docs/1.0-dev/api/evennia.commands.default.cmdset_unloggedin.html index ce09ee2efa..d0379b45f0 100644 --- a/docs/1.0-dev/api/evennia.commands.default.cmdset_unloggedin.html +++ b/docs/1.0-dev/api/evennia.commands.default.cmdset_unloggedin.html @@ -115,7 +115,6 @@ of the state instance in this module.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.comms.html b/docs/1.0-dev/api/evennia.commands.default.comms.html index f37e10c816..397cffba6f 100644 --- a/docs/1.0-dev/api/evennia.commands.default.comms.html +++ b/docs/1.0-dev/api/evennia.commands.default.comms.html @@ -196,7 +196,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.

      -aliases = ['@channels', '@chan']
      +aliases = ['@chan', '@channels']
      @@ -722,7 +722,7 @@ don’t actually sub to yet.

      -search_index_entry = {'aliases': '@channels @chan', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel channels chan', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}
      +search_index_entry = {'aliases': '@chan @channels', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel chan channels', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}
      @@ -875,7 +875,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.

      -aliases = ['@channels', '@chan']
      +aliases = ['@chan', '@channels']
      @@ -895,7 +895,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.

      -search_index_entry = {'aliases': '@channels @chan', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel channels chan', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}
      +search_index_entry = {'aliases': '@chan @channels', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel chan channels', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}
      @@ -1301,7 +1301,6 @@ must be added to game settings.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.general.html b/docs/1.0-dev/api/evennia.commands.default.general.html index 4e3ce98bf5..0dd3a54472 100644 --- a/docs/1.0-dev/api/evennia.commands.default.general.html +++ b/docs/1.0-dev/api/evennia.commands.default.general.html @@ -649,7 +649,7 @@ automatically begin with your name.

      -aliases = [':', 'emote']
      +aliases = ['emote', ':']
      @@ -690,7 +690,7 @@ space.

      -search_index_entry = {'aliases': ': emote', 'category': 'general', 'key': 'pose', 'no_prefix': ' : emote', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}
      +search_index_entry = {'aliases': 'emote :', 'category': 'general', 'key': 'pose', 'no_prefix': ' emote :', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}
      @@ -791,7 +791,6 @@ which permission groups you are a member of.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.help.html b/docs/1.0-dev/api/evennia.commands.default.help.html index dd6ca6e489..d8e7a657dc 100644 --- a/docs/1.0-dev/api/evennia.commands.default.help.html +++ b/docs/1.0-dev/api/evennia.commands.default.help.html @@ -487,7 +487,6 @@ the user will be able to enter a partial match to access it.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.html b/docs/1.0-dev/api/evennia.commands.default.html index 4d35acea40..b1a3130db4 100644 --- a/docs/1.0-dev/api/evennia.commands.default.html +++ b/docs/1.0-dev/api/evennia.commands.default.html @@ -105,7 +105,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.muxcommand.html b/docs/1.0-dev/api/evennia.commands.default.muxcommand.html index 6ca2730607..482729d345 100644 --- a/docs/1.0-dev/api/evennia.commands.default.muxcommand.html +++ b/docs/1.0-dev/api/evennia.commands.default.muxcommand.html @@ -281,7 +281,6 @@ character is actually attached to this Account and Session.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.syscommands.html b/docs/1.0-dev/api/evennia.commands.default.syscommands.html index fdde5f653d..bda483e51d 100644 --- a/docs/1.0-dev/api/evennia.commands.default.syscommands.html +++ b/docs/1.0-dev/api/evennia.commands.default.syscommands.html @@ -237,7 +237,6 @@ the raw_cmdname is the cmdname unmodified by eventual prefix-st

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.system.html b/docs/1.0-dev/api/evennia.commands.default.system.html index 4234cd3b6a..eac00b77ab 100644 --- a/docs/1.0-dev/api/evennia.commands.default.system.html +++ b/docs/1.0-dev/api/evennia.commands.default.system.html @@ -623,7 +623,7 @@ See |luhttps://ww
      -aliases = ['@task', '@delays']
      +aliases = ['@delays', '@task']
      @@ -669,7 +669,7 @@ to all the variables defined therein.

      -search_index_entry = {'aliases': '@task @delays', 'category': 'system', 'key': '@tasks', 'no_prefix': 'tasks task delays', 'tags': '', 'text': "\n Display or terminate active tasks (delays).\n\n Usage:\n tasks[/switch] [task_id or function_name]\n\n Switches:\n pause - Pause the callback of a task.\n unpause - Process all callbacks made since pause() was called.\n do_task - Execute the task (call its callback).\n call - Call the callback of this task.\n remove - Remove a task without executing it.\n cancel - Stop a task from automatically executing.\n\n Notes:\n A task is a single use method of delaying the call of a function. Calls are created\n in code, using `evennia.utils.delay`.\n See |luhttps://www.evennia.com/docs/latest/Command-Duration.html|ltthe docs|le for help.\n\n By default, tasks that are canceled and never called are cleaned up after one minute.\n\n Examples:\n - `tasks/cancel move_callback` - Cancels all movement delays from the slow_exit contrib.\n In this example slow exits creates it's tasks with\n `utils.delay(move_delay, move_callback)`\n - `tasks/cancel 2` - Cancel task id 2.\n\n "}
      +search_index_entry = {'aliases': '@delays @task', 'category': 'system', 'key': '@tasks', 'no_prefix': 'tasks delays task', 'tags': '', 'text': "\n Display or terminate active tasks (delays).\n\n Usage:\n tasks[/switch] [task_id or function_name]\n\n Switches:\n pause - Pause the callback of a task.\n unpause - Process all callbacks made since pause() was called.\n do_task - Execute the task (call its callback).\n call - Call the callback of this task.\n remove - Remove a task without executing it.\n cancel - Stop a task from automatically executing.\n\n Notes:\n A task is a single use method of delaying the call of a function. Calls are created\n in code, using `evennia.utils.delay`.\n See |luhttps://www.evennia.com/docs/latest/Command-Duration.html|ltthe docs|le for help.\n\n By default, tasks that are canceled and never called are cleaned up after one minute.\n\n Examples:\n - `tasks/cancel move_callback` - Cancels all movement delays from the slow_exit contrib.\n In this example slow exits creates it's tasks with\n `utils.delay(move_delay, move_callback)`\n - `tasks/cancel 2` - Cancel task id 2.\n\n "}
      @@ -768,7 +768,6 @@ to all the variables defined therein.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.tests.html b/docs/1.0-dev/api/evennia.commands.default.tests.html index 64e4011cbb..cdcea6ba6e 100644 --- a/docs/1.0-dev/api/evennia.commands.default.tests.html +++ b/docs/1.0-dev/api/evennia.commands.default.tests.html @@ -736,7 +736,7 @@ main test suite started with

      Test the batch processor.

      -red_button = <module 'evennia.contrib.tutorials.red_button.red_button' from '/tmp/tmpjnn5el2k/fb702c3d6aa12338560aabd8b4c8401ba5c07e98/evennia/contrib/tutorials/red_button/red_button.py'>
      +red_button = <module 'evennia.contrib.tutorials.red_button.red_button' from '/tmp/tmpbwd5029o/0a0aeaa2c49833e38cbfa193b894ff3e176d1c35/evennia/contrib/tutorials/red_button/red_button.py'>
      @@ -928,7 +928,6 @@ set in self.parse())

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.default.unloggedin.html b/docs/1.0-dev/api/evennia.commands.default.unloggedin.html index 5b38d039d9..71d18dd72c 100644 --- a/docs/1.0-dev/api/evennia.commands.default.unloggedin.html +++ b/docs/1.0-dev/api/evennia.commands.default.unloggedin.html @@ -62,7 +62,7 @@ connect “account name” “pass word”

      -aliases = ['co', 'con', 'conn']
      +aliases = ['conn', 'con', 'co']
      @@ -97,7 +97,7 @@ there is no object yet before the account has logged in)

      -search_index_entry = {'aliases': 'co con conn', 'category': 'general', 'key': 'connect', 'no_prefix': ' co con conn', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}
      +search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'no_prefix': ' conn con co', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}
      @@ -176,7 +176,7 @@ version is a bit more complicated.

      -aliases = ['q', 'qu']
      +aliases = ['qu', 'q']
      @@ -202,7 +202,7 @@ version is a bit more complicated.

      -search_index_entry = {'aliases': 'q qu', 'category': 'general', 'key': 'quit', 'no_prefix': ' q qu', 'tags': '', 'text': '\n quit when in unlogged-in state\n\n Usage:\n quit\n\n We maintain a different version of the quit command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}
      +search_index_entry = {'aliases': 'qu q', 'category': 'general', 'key': 'quit', 'no_prefix': ' qu q', 'tags': '', 'text': '\n quit when in unlogged-in state\n\n Usage:\n quit\n\n We maintain a different version of the quit command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}
      @@ -226,7 +226,7 @@ All it does is display the connect screen.

      -aliases = ['l', 'look']
      +aliases = ['look', 'l']
      @@ -252,7 +252,7 @@ All it does is display the connect screen.

      -search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' l look', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}
      +search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' look l', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}
      @@ -275,7 +275,7 @@ for simplicity. It shows a pane of info.

      -aliases = ['?', 'h']
      +aliases = ['h', '?']
      @@ -301,7 +301,7 @@ for simplicity. It shows a pane of info.

      -search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'no_prefix': ' ? h', 'tags': '', 'text': '\n get help when in unconnected-in state\n\n Usage:\n help\n\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}
      +search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'no_prefix': ' h ?', 'tags': '', 'text': '\n get help when in unconnected-in state\n\n Usage:\n help\n\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}
      @@ -498,7 +498,6 @@ logged in, use option screenreader on).

      Versions

      diff --git a/docs/1.0-dev/api/evennia.commands.html b/docs/1.0-dev/api/evennia.commands.html index 7f938f9a22..d91b892daf 100644 --- a/docs/1.0-dev/api/evennia.commands.html +++ b/docs/1.0-dev/api/evennia.commands.html @@ -121,7 +121,6 @@ Evennia.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.comms.comms.html b/docs/1.0-dev/api/evennia.comms.comms.html index a69153b4e5..f4c297e6d4 100644 --- a/docs/1.0-dev/api/evennia.comms.comms.html +++ b/docs/1.0-dev/api/evennia.comms.comms.html @@ -862,7 +862,6 @@ responsibility.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.comms.html b/docs/1.0-dev/api/evennia.comms.html index c25a4f45c8..04724d9c9b 100644 --- a/docs/1.0-dev/api/evennia.comms.html +++ b/docs/1.0-dev/api/evennia.comms.html @@ -94,7 +94,6 @@ as code related to external communication like IRC or RSS.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.comms.managers.html b/docs/1.0-dev/api/evennia.comms.managers.html index 7fdf04e19b..cdd1ea7a91 100644 --- a/docs/1.0-dev/api/evennia.comms.managers.html +++ b/docs/1.0-dev/api/evennia.comms.managers.html @@ -421,7 +421,6 @@ case sensitive) match.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.comms.models.html b/docs/1.0-dev/api/evennia.comms.models.html index e4f6886eed..89aa116ee5 100644 --- a/docs/1.0-dev/api/evennia.comms.models.html +++ b/docs/1.0-dev/api/evennia.comms.models.html @@ -573,6 +573,27 @@ instances.

      class built by **create_forward_many_to_many_manager()** defined below.

      +
      +
      +db_date_created
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +db_key
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      + +
      +
      +db_lock_storage
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      db_tags
      @@ -589,6 +610,13 @@ instances.

      class built by **create_forward_many_to_many_manager()** defined below.

      +
      +
      +db_typeclass_path
      +

      A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

      +
      +
      get_next_by_db_date_created(*, field=<django.db.models.fields.DateTimeField: db_date_created>, is_next=True, **kwargs)
      @@ -788,7 +816,6 @@ entities to un-subscribe from the channel.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.aws_s3_cdn.html b/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.aws_s3_cdn.html index 5495b19b3b..a389a20203 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.aws_s3_cdn.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.aws_s3_cdn.html @@ -40,471 +40,8 @@
      -
      -

      evennia.contrib.base_systems.awsstorage.aws_s3_cdn

      -

      AWS Storage System -The Right Honourable Reverend (trhr) 2020

      -

      ABOUT THIS PLUGIN:

      -

      This plugin migrates the Web-based portion of Evennia, namely images, -javascript, and other items located inside staticfiles into Amazon AWS (S3) for hosting.

      -

      Files hosted on S3 are “in the cloud,” and while your personal -server may be sufficient for serving multimedia to a minimal number of users, -the perfect use case for this plugin would be:

      -
        -
      1. Servers supporting heavy web-based traffic (webclient, etc)

      2. -
      3. With a sizable number of users

      4. -
      5. Where the users are globally distributed

      6. -
      7. Where multimedia files are served to users as a part of gameplay

      8. -
      -

      Bottom line - if you’re sending an image to a player every time they traverse a -map, the bandwidth reduction will be substantial. If not, probably skip -this one.

      -

      Note that storing and serving files via S3 is not technically free outside of -Amazon’s “free tier” offering, which you may or may not be eligible for; -evennia’s base install currently requires 1.5MB of storage space on S3, -making the current total cost to install this plugin ~$0.0005 per year. If -you have substantial media assets and intend to serve them to many users, -caveat emptor on a total cost of ownership - check AWS’s pricing structure.

      -

      See the ./README.md file for details and install instructions.

      -
      -
      -evennia.contrib.base_systems.awsstorage.aws_s3_cdn.setting(name, default=None)[source]
      -

      Helper function to get a Django setting by name. If setting doesn’t exist -it will return a default.

      -
      -
      Parameters
      -

      name (str) – A Django setting name

      -
      -
      Returns
      -

      The value of the setting variable by that name

      -
      -
      -
      - -
      -
      -evennia.contrib.base_systems.awsstorage.aws_s3_cdn.safe_join(base, *paths)[source]
      -

      Helper function, a version of django.utils._os.safe_join for S3 paths. -Joins one or more path components to the base path component -intelligently. Returns a normalized version of the final path. -The final path must be located inside of the base path component -(otherwise a ValueError is raised). Paths outside the base path -indicate a possible security sensitive operation.

      -
      -
      Parameters
      -
        -
      • base (str) – A path string to the base of the staticfiles

      • -
      • *paths (list) – A list of paths as referenced from the base path

      • -
      -
      -
      Returns
      -

      final_path (str) – A joined path, base + filepath

      -
      -
      -
      - -
      -
      -evennia.contrib.base_systems.awsstorage.aws_s3_cdn.check_location(storage)[source]
      -

      Helper function to make sure that the storage location is configured correctly.

      -
      -
      Parameters
      -

      storage (Storage) – A Storage object (Django)

      -
      -
      Raises
      -

      ImproperlyConfigured – If the storage location is not configured correctly, -this is raised.

      -
      -
      -
      - -
      -
      -evennia.contrib.base_systems.awsstorage.aws_s3_cdn.lookup_env(names)[source]
      -

      Helper function for looking up names in env vars. Returns the first element found.

      -
      -
      Parameters
      -

      names (str) – A list of environment variables

      -
      -
      Returns
      -

      value (str) – The value of the found environment variable.

      -
      -
      -
      - -
      -
      -evennia.contrib.base_systems.awsstorage.aws_s3_cdn.get_available_overwrite_name(name, max_length)[source]
      -

      Helper function indicating files that will be overwritten during trunc.

      -
      -
      Parameters
      -
        -
      • name (str) – The name of the file

      • -
      • max_length (int) – The maximum length of a filename

      • -
      -
      -
      Returns
      -

      joined (path) – A joined path including directory, file, and extension

      -
      -
      -
      - -
      -
      -class evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3StorageFile(*args, **kwargs)[source]
      -

      Bases: django.core.files.base.File

      -

      The default file object used by the S3Boto3Storage backend. -This file implements file streaming using boto’s multipart -uploading functionality. The file can be opened in read or -write mode. -This class extends Django’s File class. However, the contained -data is only the data contained in the current buffer. So you -should not access the contained file object directly. You should -access the data via this class. -Warning: This file must be closed using the close() method in -order to properly write the file to S3. Be sure to close the file -in your application.

      -
      -
      -__init__(name, mode, storage, buffer_size=None)[source]
      -

      Initializes the File object.

      -
      -
      Parameters
      -
        -
      • name (str) – The name of the file

      • -
      • mode (str) – The access mode (‘r’ or ‘w’)

      • -
      • storage (Storage) – The Django Storage object

      • -
      • buffer_size (int) – The buffer size, for multipart uploads

      • -
      -
      -
      -
      - -
      -
      -buffer_size = 5242880
      -
      - -
      -
      -property size
      -

      Helper property to return filesize

      -
      - -
      -
      -property file
      -

      Helper function to manage zipping and temporary files

      -
      - -
      -
      -read(*args, **kwargs)[source]
      -

      Checks if file is in read mode; then continues to boto3 operation

      -
      - -
      -
      -readline(*args, **kwargs)[source]
      -

      Checks if file is in read mode; then continues to boto3 operation

      -
      - -
      -
      -write(content)[source]
      -

      Checks if file is in write mode or needs multipart handling, -then continues to boto3 operation.

      -
      - -
      -
      -close()[source]
      -

      Manages file closing after multipart uploads

      -
      - -
      -
      -deconstruct()
      -

      Return a 3-tuple of class import path, positional arguments, -and keyword arguments.

      -
      - -
      - -
      -
      -class evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage(*args, **kwargs)[source]
      -

      Bases: django.core.files.storage.Storage

      -

      Amazon Simple Storage Service using Boto3 -This storage backend supports opening files in read or write -mode and supports streaming(buffering) data in chunks to S3 -when writing.

      -
      -
      -default_content_type = 'application/octet-stream'
      -
      - -
      -
      -access_key_names = ['AWS_S3_ACCESS_KEY_ID', 'AWS_ACCESS_KEY_ID']
      -
      - -
      -
      -secret_key_names = ['AWS_S3_SECRET_ACCESS_KEY', 'AWS_SECRET_ACCESS_KEY']
      -
      - -
      -
      -security_token_names = ['AWS_SESSION_TOKEN', 'AWS_SECURITY_TOKEN']
      -
      - -
      -
      -file_overwrite = True
      -
      - -
      -
      -object_parameters = {}
      -
      - -
      -
      -bucket_name = None
      -
      - -
      -
      -auto_create_bucket = False
      -
      - -
      -
      -default_acl = 'public-read'
      -
      - -
      -
      -bucket_acl = 'public-read'
      -
      - -
      -
      -querystring_auth = True
      -
      - -
      -
      -querystring_expire = 3600
      -
      - -
      -
      -signature_version = None
      -
      - -
      -
      -reduced_redundancy = False
      -
      - -
      -
      -location = ''
      -
      - -
      -
      -encryption = False
      -
      - -
      -
      -custom_domain = None
      -
      - -
      -
      -addressing_style = None
      -
      - -
      -
      -secure_urls = True
      -
      - -
      -
      -file_name_charset = 'utf-8'
      -
      - -
      -
      -gzip = False
      -
      - -
      -
      -preload_metadata = False
      -
      - -
      -
      -gzip_content_types = ('text/css', 'text/javascript', 'application/javascript', 'application/x-javascript', 'image/svg+xml')
      -
      - -
      -
      -endpoint_url = None
      -
      - -
      -
      -proxies = None
      -
      - -
      -
      -region_name = None
      -
      - -
      -
      -use_ssl = True
      -
      - -
      -
      -verify = None
      -
      - -
      -
      -max_memory_size = 0
      -
      - -
      -
      -__init__(acl=None, bucket=None, **settings)[source]
      -

      Check if some of the settings we’ve provided as class attributes -need to be overwritten with values passed in here.

      -
      - -
      -
      -url_protocol = 'http:'
      -
      - -
      -
      -access_key = ''
      -
      - -
      -
      -secret_key = ''
      -
      - -
      -
      -security_token = None
      -
      - -
      -
      -config = None
      -
      - -
      -
      -property connection
      -

      Creates the actual connection to S3

      -
      - -
      -
      -property bucket
      -

      Get the current bucket. If there is no current bucket object -create it.

      -
      - -
      -
      -property entries
      -

      Get the locally cached files for the bucket.

      -
      - -
      -
      -delete(name)[source]
      -

      Deletes a file from S3.

      -
      - -
      -
      -exists(name)[source]
      -

      Checks if file exists.

      -
      - -
      -
      -listdir(name)[source]
      -

      Translational function to go from S3 file paths to the format -Django’s listdir expects.

      -
      - -
      -
      -size(name)[source]
      -

      Gets the filesize of a remote file.

      -
      - -
      -
      -deconstruct()
      -

      Return a 3-tuple of class import path, positional arguments, -and keyword arguments.

      -
      - -
      -
      -get_object_parameters(name)[source]
      -

      Returns a dictionary that is passed to file upload. Override this -method to adjust this on a per-object basis to set e.g ContentDisposition. -By default, returns the value of AWS_S3_OBJECT_PARAMETERS. -Setting ContentEncoding will prevent objects from being automatically gzipped.

      -
      - -
      -
      -get_modified_time(name)[source]
      -

      Returns an (aware) datetime object containing the last modified time if -USE_TZ is True, otherwise returns a naive datetime in the local timezone.

      -
      - -
      -
      -modified_time(name)[source]
      -

      Returns a naive datetime object containing the last modified time. -If USE_TZ=False then get_modified_time will return a naive datetime -so we just return that, else we have to localize and strip the tz

      -
      - -
      -
      -url(name, parameters=None, expire=None)[source]
      -

      Returns the URL of a remotely-hosted file

      -
      - -
      -
      -get_available_name(name, max_length=None)[source]
      -

      Overwrite existing file with the same name.

      -
      - -
      - +
      +

      evennia.contrib.base_systems.awsstorage.aws_s3_cdn

      @@ -547,7 +84,6 @@ so we just return that, else we have to localize and strip the tz

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.html b/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.html index 5531a6b457..b22e18a2c0 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.tests.html index 1ef696988f..0fa98c9633 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.awsstorage.tests.html @@ -301,7 +301,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.building_menu.html b/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.building_menu.html index 296f396e82..48da2f14a6 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.building_menu.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.building_menu.html @@ -924,7 +924,6 @@ set in self.parse())

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.html b/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.html index 25aa11ce9f..168727f9f8 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.tests.html index 33144f9b5d..5fa57c2a1e 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.building_menu.tests.html @@ -155,7 +155,6 @@ Use add_choice and its variants to create menu choices.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.color_markups.html b/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.color_markups.html index fd2966889e..a646c84083 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.color_markups.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.color_markups.html @@ -126,7 +126,6 @@ COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BGS_EXTR

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.html b/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.html index 5761000d51..b7649c95b9 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.tests.html index b21218d0d0..c422a98757 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.color_markups.tests.html @@ -106,7 +106,6 @@ settings to test it causes issues with unrelated tests.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.custom_gametime.html b/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.custom_gametime.html index aee1473b39..fa18ca00e5 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.custom_gametime.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.custom_gametime.html @@ -323,7 +323,6 @@ The time is given in units as keyword arguments.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.html b/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.html index babb4d5160..05730c6c0f 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.tests.html index 009101e394..1821cf503b 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.custom_gametime.tests.html @@ -127,7 +127,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.connection_screens.html b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.connection_screens.html index e7de846c06..f4ae96e0da 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.connection_screens.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.connection_screens.html @@ -99,7 +99,6 @@ of the screen is done by the unlogged-in “look” command.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html index 0130b96d01..65ff58328c 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html @@ -78,7 +78,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE.

      -aliases = ['co', 'con', 'conn']
      +aliases = ['conn', 'con', 'co']
      @@ -108,7 +108,7 @@ there is no object yet before the account has logged in)

      -search_index_entry = {'aliases': 'co con conn', 'category': 'general', 'key': 'connect', 'no_prefix': ' co con conn', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}
      +search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'no_prefix': ' conn con co', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}
      @@ -185,7 +185,7 @@ version is a bit more complicated.

      -aliases = ['q', 'qu']
      +aliases = ['qu', 'q']
      @@ -211,7 +211,7 @@ version is a bit more complicated.

      -search_index_entry = {'aliases': 'q qu', 'category': 'general', 'key': 'quit', 'no_prefix': ' q qu', 'tags': '', 'text': '\n We maintain a different version of the `quit` command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}
      +search_index_entry = {'aliases': 'qu q', 'category': 'general', 'key': 'quit', 'no_prefix': ' qu q', 'tags': '', 'text': '\n We maintain a different version of the `quit` command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}
      @@ -230,7 +230,7 @@ All it does is display the connect screen.

      -aliases = ['l', 'look']
      +aliases = ['look', 'l']
      @@ -256,7 +256,7 @@ All it does is display the connect screen.

      -search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' l look', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}
      +search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' look l', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}
      @@ -274,7 +274,7 @@ for simplicity. It shows a pane of info.

      -aliases = ['?', 'h']
      +aliases = ['h', '?']
      @@ -300,7 +300,7 @@ for simplicity. It shows a pane of info.

      -search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'no_prefix': ' ? h', 'tags': '', 'text': '\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}
      +search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'no_prefix': ' h ?', 'tags': '', 'text': '\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}
      @@ -347,7 +347,6 @@ for simplicity. It shows a pane of info.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.html b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.html index 8f297cc447..4f9def5480 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.html @@ -92,7 +92,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.tests.html index 12baffc657..f7277c157e 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.tests.html @@ -111,7 +111,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.html b/docs/1.0-dev/api/evennia.contrib.base_systems.html index 4ff487b29c..3784cc28a5 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.html @@ -140,7 +140,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.callbackhandler.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.callbackhandler.html index fbe24869b2..9906777e36 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.callbackhandler.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.callbackhandler.html @@ -215,63 +215,63 @@ the expected fields for a callback (code, author, valid…).

      class evennia.contrib.base_systems.ingame_python.callbackhandler.Callback(obj, name, number, code, author, valid, parameters, created_on, updated_by, updated_on)

      Bases: tuple

      -
      +
      -property author
      +author

      Alias for field number 4

      -
      +
      -property code
      +code

      Alias for field number 3

      -
      +
      -property created_on
      +created_on

      Alias for field number 7

      -
      +
      -property name
      +name

      Alias for field number 1

      -
      +
      -property number
      +number

      Alias for field number 2

      -
      +
      -property obj
      +obj

      Alias for field number 0

      -
      +
      -property parameters
      +parameters

      Alias for field number 6

      -
      +
      -property updated_by
      +updated_by

      Alias for field number 8

      -
      +
      -property updated_on
      +updated_on

      Alias for field number 9

      -
      +
      -property valid
      +valid

      Alias for field number 5

      @@ -319,7 +319,6 @@ the expected fields for a callback (code, author, valid…).

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.commands.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.commands.html index 4633e2b849..c79637ea61 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.commands.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.commands.html @@ -55,7 +55,7 @@
      -aliases = ['@callback', '@callbacks', '@calls']
      +aliases = ['@callback', '@calls', '@callbacks']
      @@ -136,7 +136,7 @@ on user permission.

      -search_index_entry = {'aliases': '@callback @callbacks @calls', 'category': 'building', 'key': '@call', 'no_prefix': 'call callback callbacks calls', 'tags': '', 'text': '\n Command to edit callbacks.\n '}
      +search_index_entry = {'aliases': '@callback @calls @callbacks', 'category': 'building', 'key': '@call', 'no_prefix': 'call callback calls callbacks', 'tags': '', 'text': '\n Command to edit callbacks.\n '}
      @@ -183,7 +183,6 @@ on user permission.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.eventfuncs.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.eventfuncs.html index 01080f2ef1..df441b9ca1 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.eventfuncs.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.eventfuncs.html @@ -151,7 +151,6 @@ to be called from inside another event.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.html index 46d462d6db..1a767a1ee1 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.html @@ -95,7 +95,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.scripts.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.scripts.html index f7d8d4f363..3242b23e69 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.scripts.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.scripts.html @@ -440,7 +440,6 @@ restart only twice.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.tests.html index 8cde7eb763..ef4e4eaf8a 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.tests.html @@ -226,7 +226,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.typeclasses.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.typeclasses.html index 886d842841..5992a43edd 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.typeclasses.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.typeclasses.html @@ -84,7 +84,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.utils.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.utils.html index 24d8f47312..6d0dd97a02 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.utils.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.utils.html @@ -204,7 +204,6 @@ either “yes” or “okay” (maybe ‘say I don’t like it, but okay’).

      Versions diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.connection_screens.html b/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.connection_screens.html index 69d7ff7538..b608a4fa4e 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.connection_screens.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.connection_screens.html @@ -84,7 +84,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.html b/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.html index 51edd02d6e..e77c02c9f3 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.menu_login.html b/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.menu_login.html index 5fe6a0bab7..a7d3bbc635 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.menu_login.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.menu_login.html @@ -84,7 +84,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.tests.html index 74d47601ad..7a25276a1c 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.menu_login.tests.html @@ -84,7 +84,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.html b/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.html index 34f09a8fca..5b1b90e4c7 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.mux_comms_cmds.html b/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.mux_comms_cmds.html index 58c6104bbf..175032d9e7 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.mux_comms_cmds.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.mux_comms_cmds.html @@ -633,7 +633,6 @@ channel lists.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.tests.html index 698230f720..87b675518d 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.mux_comms_cmds.tests.html @@ -133,7 +133,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.html b/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.html index 37ced182a8..8fd99589f8 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.tests.html b/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.tests.html index a1f336edba..b22929a3f3 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.tests.html @@ -147,7 +147,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.unixcommand.html b/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.unixcommand.html index 02e628c7cb..4e357b06c1 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.unixcommand.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.unixcommand.unixcommand.html @@ -373,7 +373,6 @@ use its add_argument method.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html index ed06406372..6f443c6b33 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html @@ -150,7 +150,7 @@ the operation will be general or on the room.

      -aliases = ['abort', 'quit', 'q', 'chicken out']
      +aliases = ['chicken out', 'q', 'quit', 'abort']
      @@ -174,7 +174,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'abort quit q chicken out', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' abort quit q chicken out', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}
      +search_index_entry = {'aliases': 'chicken out q quit abort', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' chicken out q quit abort', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}
      @@ -310,7 +310,7 @@ shout

      -aliases = ['shout', ';', 'whisper']
      +aliases = [';', 'whisper', 'shout']
      @@ -339,7 +339,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'shout ; whisper', 'category': 'general', 'key': 'say', 'no_prefix': ' shout ; whisper', 'tags': '', 'text': '\n Perform an communication action.\n\n Usage:\n say <text>\n whisper\n shout\n\n '}
      +search_index_entry = {'aliases': '; whisper shout', 'category': 'general', 'key': 'say', 'no_prefix': ' ; whisper shout', 'tags': '', 'text': '\n Perform an communication action.\n\n Usage:\n say <text>\n whisper\n shout\n\n '}
      @@ -367,7 +367,7 @@ emote /me points to /box and /lever.

      -aliases = [':', 'pose']
      +aliases = ['pose', ':']
      @@ -406,7 +406,7 @@ set in self.parse())

      -search_index_entry = {'aliases': ': pose', 'category': 'general', 'key': 'emote', 'no_prefix': ' : pose', 'tags': '', 'text': '\n Perform a free-form emote. Use /me to\n include yourself in the emote and /name\n to include other objects or characters.\n Use "..." to enact speech.\n\n Usage:\n emote <emote>\n :<emote\n\n Example:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n '}
      +search_index_entry = {'aliases': 'pose :', 'category': 'general', 'key': 'emote', 'no_prefix': ' pose :', 'tags': '', 'text': '\n Perform a free-form emote. Use /me to\n include yourself in the emote and /name\n to include other objects or characters.\n Use "..." to enact speech.\n\n Usage:\n emote <emote>\n :<emote\n\n Example:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n '}
      @@ -429,7 +429,7 @@ looks and what actions is available.

      -aliases = ['e', 'unfocus', 'ex', 'examine']
      +aliases = ['ex', 'unfocus', 'examine', 'e']
      @@ -458,7 +458,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'e unfocus ex examine', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' e unfocus ex examine', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}
      +search_index_entry = {'aliases': 'ex unfocus examine e', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' ex unfocus examine e', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}
      @@ -520,7 +520,7 @@ set in self.parse())

      -aliases = ['inventory', 'give', 'inv', 'i']
      +aliases = ['inv', 'inventory', 'i', 'give']
      @@ -544,7 +544,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'inventory give inv i', 'category': 'evscaperoom', 'key': 'get', 'no_prefix': ' inventory give inv i', 'tags': '', 'text': '\n Use focus / examine instead.\n\n '}
      +search_index_entry = {'aliases': 'inv inventory i give', 'category': 'evscaperoom', 'key': 'get', 'no_prefix': ' inv inventory i give', 'tags': '', 'text': '\n Use focus / examine instead.\n\n '}
      @@ -565,7 +565,7 @@ set in self.parse())

      -aliases = ['@dig', '@open']
      +aliases = ['@open', '@dig']
      @@ -588,7 +588,7 @@ to all the variables defined therein.

      -search_index_entry = {'aliases': '@dig @open', 'category': 'general', 'key': 'open', 'no_prefix': ' dig open', 'tags': '', 'text': '\n Interact with an object in focus.\n\n Usage:\n <action> [arg]\n\n '}
      +search_index_entry = {'aliases': '@open @dig', 'category': 'general', 'key': 'open', 'no_prefix': ' open dig', 'tags': '', 'text': '\n Interact with an object in focus.\n\n Usage:\n <action> [arg]\n\n '}
      @@ -1037,7 +1037,6 @@ self.add().

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.html index beaff965c5..9b3c616df8 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.html @@ -97,7 +97,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.menu.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.menu.html index d40f2f6845..68822af970 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.menu.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.menu.html @@ -194,7 +194,6 @@ option related to this node.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.objects.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.objects.html index 5b6c9ea68c..1504b00a7e 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.objects.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.objects.html @@ -1814,7 +1814,6 @@ inject the list of callsigns.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.room.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.room.html index 79ed32d65d..f7a8d53f9f 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.room.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.room.html @@ -273,7 +273,6 @@ contents of the object by default.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.scripts.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.scripts.html index 0e3049ccd8..60b12944a3 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.scripts.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.scripts.html @@ -134,7 +134,6 @@ overriding the call (unused by default).

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.state.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.state.html index 02060ab3cb..45d5b61d40 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.state.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.state.html @@ -270,7 +270,6 @@ happens just before room.character_cleanup()

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.tests.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.tests.html index 6d55e2b786..2481ff938a 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.tests.html @@ -210,7 +210,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.utils.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.utils.html index 2721209dc9..8f92a8607b 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.utils.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.utils.html @@ -202,7 +202,6 @@ surrounded by borders.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.html b/docs/1.0-dev/api/evennia.contrib.full_systems.html index 65053a8abc..e338efd9b8 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.html @@ -100,7 +100,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.barter.barter.html b/docs/1.0-dev/api/evennia.contrib.game_systems.barter.barter.html index 5ce84d7646..993058f8db 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.barter.barter.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.barter.barter.html @@ -885,7 +885,6 @@ info to your choice.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.barter.html b/docs/1.0-dev/api/evennia.contrib.game_systems.barter.html index b41259c4b9..bc2258b90e 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.barter.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.barter.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.barter.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.barter.tests.html index 8716842c16..026dfa3ec7 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.barter.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.barter.tests.html @@ -122,7 +122,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.clothing.html b/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.clothing.html index 5975b30364..2a05b13542 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.clothing.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.clothing.html @@ -735,7 +735,6 @@ items.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.html b/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.html index e41875d271..53ee8bc961 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.tests.html index 32095187db..9446584557 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.tests.html @@ -107,7 +107,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.cooldowns.html b/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.cooldowns.html index aff48ec092..ba73f08951 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.cooldowns.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.cooldowns.html @@ -318,7 +318,6 @@ requirements small.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.html b/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.html index fdfaa0168e..2cb2c21d68 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.tests.html index a025483bff..f01a42ff96 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.cooldowns.tests.html @@ -177,7 +177,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.crafting.html b/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.crafting.html index b2bfd030a8..3fce2249dd 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.crafting.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.crafting.html @@ -867,7 +867,6 @@ the crafting_tool_err_msg if available.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.example_recipes.html b/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.example_recipes.html index 7b79fb4682..1af6e80a01 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.example_recipes.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.example_recipes.html @@ -577,7 +577,6 @@ set in self.parse())

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.html b/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.html index d8405ba133..c0fda73671 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.html @@ -102,7 +102,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.tests.html index fbee692699..c62e15b8a4 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.crafting.tests.html @@ -297,7 +297,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.gendersub.html b/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.gendersub.html index 2c49cff2bf..2bda131fa4 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.gendersub.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.gendersub.html @@ -224,7 +224,6 @@ All extra kwargs will be passed on to the protocol.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.html b/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.html index 1af4ada0e7..6a999253a2 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.tests.html index 5bdcb7e88a..69fb942448 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.gendersub.tests.html @@ -101,7 +101,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.html b/docs/1.0-dev/api/evennia.contrib.game_systems.html index 05d81407b8..491e5bcb75 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.html @@ -148,7 +148,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.mail.html b/docs/1.0-dev/api/evennia.contrib.game_systems.mail.html index dc9c7d5226..f07f1512ef 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.mail.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.mail.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.mail.mail.html b/docs/1.0-dev/api/evennia.contrib.game_systems.mail.mail.html index 524cccc0c0..467926b1ec 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.mail.mail.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.mail.mail.html @@ -343,7 +343,6 @@ reply - Replies to a received message, appending the original message to the b

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.mail.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.mail.tests.html index 728b26bc5e..f6095905e3 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.mail.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.mail.tests.html @@ -96,7 +96,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.html b/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.html index dcc9b8be78..b8fc0fe673 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.multidescer.html b/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.multidescer.html index bcfabba762..09b880cd59 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.multidescer.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.multidescer.html @@ -168,7 +168,6 @@ description in use and db.multidesc to store all descriptions.<

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.tests.html index 72dba6c5cb..df74c5f9a1 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.multidescer.tests.html @@ -96,7 +96,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.html b/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.html index 5b8de1f41a..3b25a1ea42 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.html @@ -91,7 +91,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.puzzles.html b/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.puzzles.html index a5c9e083ec..3cf20de622 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.puzzles.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.puzzles.html @@ -550,7 +550,6 @@ self.add().

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.tests.html index a051d4f8d3..0246c335c4 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.puzzles.tests.html @@ -147,7 +147,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.html b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.html index e5f941beff..1e98dda8ae 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.html @@ -95,7 +95,6 @@

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_basic.html b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_basic.html index 721195c01f..88597c208d 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_basic.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_basic.html @@ -43,7 +43,7 @@

      evennia.contrib.game_systems.turnbattle.tb_basic

      Simple turn-based combat system

      -

      Contrib - Tim Ashley Jenkins 2017

      +

      Contrib - Tim Ashley Jenkins 2017, Refactor by Griatch 2022

      This is a framework for a simple turn-based combat system, similar to those used in D&D-style tabletop role playing games. It allows any character to start a fight in a room, at which point initiative @@ -84,9 +84,14 @@ in your game and using it as-is.

      evennia.contrib.game_systems.turnbattle.tb_basic.ACTIONS_PER_TURN = 1
      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.roll_init(character)[source]
      +
      +
      +class evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules[source]
      +

      Bases: object

      +

      Stores all combat rules and helper methods.

      +
      +
      +roll_init(character)[source]

      Rolls a number between 1-1000 to determine initiative.

      Parameters
      @@ -108,9 +113,9 @@ an advantage in turn order, like so:

      This way, characters with a higher dexterity will go first more often.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.get_attack(attacker, defender)[source]
      +
      +
      +get_attack(attacker, defender)[source]

      Returns a value for an attack roll.

      Parameters
      @@ -136,9 +141,9 @@ equipment, and abilities. This is why the attacker and defender are passed to this function, even though nothing from either one are used in this example.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.get_defense(attacker, defender)[source]
      +
      +
      +get_defense(attacker, defender)[source]

      Returns a value for defense, which an attack roll must equal or exceed in order for an attack to hit.

      @@ -163,9 +168,9 @@ attacker into account.

      As above, this can be expanded upon based on character stats and equipment.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.get_damage(attacker, defender)[source]
      +
      +
      +get_damage(attacker, defender)[source]

      Returns a value for damage to be deducted from the defender’s HP after abilities successful hit.

      @@ -190,9 +195,9 @@ properties from either the attacker or defender.

      Again, this can be expanded upon.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.apply_damage(defender, damage)[source]
      +
      +
      +apply_damage(defender, damage)[source]

      Applies damage to a target, reducing their HP by the damage amount to a minimum of 0.

      @@ -205,9 +210,9 @@ minimum of 0.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.at_defeat(defeated)[source]
      +
      +
      +at_defeat(defeated)[source]

      Announces the defeat of a fighter in combat.

      Parameters
      @@ -221,9 +226,9 @@ into a dying state or something similar) then this is the place to do it.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.resolve_attack(attacker, defender, attack_value=None, defense_value=None)[source]
      +
      +
      +resolve_attack(attacker, defender, attack_value=None, defense_value=None)[source]

      Resolves an attack and outputs the result.

      Parameters
      @@ -239,9 +244,9 @@ extremely simply, they are separated out into their own functions so that they are easier to expand upon.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.combat_cleanup(character)[source]
      +
      +
      +combat_cleanup(character)[source]

      Cleans up all the temporary combat-related attributes on a character.

      Parameters
      @@ -253,9 +258,9 @@ so that they are easier to expand upon.

      longer needed once a fight ends.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.is_in_combat(character)[source]
      +
      +
      +is_in_combat(character)[source]

      Returns true if the given character is in combat.

      Parameters
      @@ -267,9 +272,9 @@ longer needed once a fight ends.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.is_turn(character)[source]
      +
      +
      +is_turn(character)[source]

      Returns true if it’s currently the given character’s turn in combat.

      Parameters
      @@ -281,9 +286,9 @@ longer needed once a fight ends.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_basic.spend_action(character, actions, action_name=None)[source]
      +
      +
      +spend_action(character, actions, action_name=None)[source]

      Spends a character’s available combat actions and checks for end of turn.

      Parameters
      @@ -301,12 +306,24 @@ longer needed once a fight ends.

      +
      + +
      +
      +evennia.contrib.game_systems.turnbattle.tb_basic.COMBAT_RULES = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      +
      class evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter(*args, **kwargs)[source]

      Bases: evennia.objects.objects.DefaultCharacter

      A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      +
      at_object_creation()[source]
      @@ -367,6 +384,11 @@ fight going on in a single room at a time, so the script is assigned to a room as its object.

      Fights persist until only one participant is left with any HP or all remaining participants choose to end the combat with the ‘disengage’ command.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      +
      at_script_creation()[source]
      @@ -488,6 +510,17 @@ When it’s your turn, you can attack other characters.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      + +
      +
      +combat_handler_class
      +

      alias of TBBasicTurnHandler

      +
      +
      func()[source]
      @@ -532,6 +565,11 @@ a chance to hit, and if successful, will deal damage.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      +
      func()[source]
      @@ -573,7 +611,7 @@ if there are still any actions you can take.

      -aliases = ['wait', 'hold']
      +aliases = ['hold', 'wait']
      @@ -581,6 +619,11 @@ if there are still any actions you can take.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      +
      func()[source]
      @@ -594,7 +637,7 @@ if there are still any actions you can take.

      -search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      +search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      @@ -626,6 +669,11 @@ fight ends.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      +
      func()[source]
      @@ -665,6 +713,11 @@ rest if you’re not in a fight.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      +
      func()[source]
      @@ -701,6 +754,16 @@ help all

      This will search for help on commands and other topics related to the game.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules object>
      +
      + +
      +
      +combat_help_text = 'Available combat commands:|/|wAttack:|n Attack a target, attempting to deal damage.|/|wPass:|n Pass your turn without further action.|/|wDisengage:|n End your turn and attempt to end combat.|/'
      +
      +
      func()[source]
      @@ -744,17 +807,17 @@ topics related to the game.

      key = 'DefaultCharacter'
      +
      +
      +path = 'evennia.contrib.game_systems.turnbattle.tb_basic.BattleCmdSet'
      +
      +
      at_cmdset_creation()[source]

      Populates the cmdset

      -
      -
      -path = 'evennia.contrib.game_systems.turnbattle.tb_basic.BattleCmdSet'
      -
      -
      @@ -799,7 +862,6 @@ topics related to the game.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_equip.html b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_equip.html index 7ba44b24fa..92487c6825 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_equip.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_equip.html @@ -43,7 +43,7 @@

      evennia.contrib.game_systems.turnbattle.tb_equip

      Simple turn-based combat system with equipment

      -

      Contrib - Tim Ashley Jenkins 2017

      +

      Contrib - Tim Ashley Jenkins 2017, Refactor by Griatch 2022

      This is a version of the ‘turnbattle’ contrib with a basic system for weapons and armor implemented. Weapons can have unique damage ranges and accuracy modifiers, while armor can reduce incoming damage and @@ -94,33 +94,14 @@ in your game and using it as-is.

      evennia.contrib.game_systems.turnbattle.tb_equip.ACTIONS_PER_TURN = 1
      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.roll_init(character)[source]
      -

      Rolls a number between 1-1000 to determine initiative.

      -
      -
      Parameters
      -

      character (obj) – The character to determine initiative for

      -
      -
      Returns
      -

      initiative (int) – The character’s place in initiative - higher -numbers go first.

      -
      -
      -

      Notes

      -

      By default, does not reference the character and simply returns -a random integer from 1 to 1000.

      -

      Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

      -

      return (randint(1,20)) + character.db.dexterity

      -

      This way, characters with a higher dexterity will go first more often.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.get_attack(attacker, defender)[source]
      +
      +
      +class evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules[source]
      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules

      +

      Has all the methods of the basic combat, with the addition of equipment.

      +
      +
      +get_attack(attacker, defender)[source]

      Returns a value for an attack roll.

      Parameters
      @@ -145,9 +126,9 @@ weapons are less accurate but deal more damage. Of course, you can change this paradigm completely in your own game.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.get_defense(attacker, defender)[source]
      +
      +
      +get_defense(attacker, defender)[source]

      Returns a value for defense, which an attack roll must equal or exceed in order for an attack to hit.

      @@ -172,9 +153,9 @@ modified up or down by armor. In this example, wearing armor actually makes you a little easier to hit, but reduces incoming damage.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.get_damage(attacker, defender)[source]
      +
      +
      +get_damage(attacker, defender)[source]

      Returns a value for damage to be deducted from the defender’s HP after abilities successful hit.

      @@ -199,40 +180,9 @@ unarmed damage range if no weapon is wielded. Incoming damage is reduced by the defender’s armor.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.apply_damage(defender, damage)[source]
      -

      Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

      -
      -
      Parameters
      -
        -
      • defender (obj) – Character taking damage

      • -
      • damage (int) – Amount of damage being taken

      • -
      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.at_defeat(defeated)[source]
      -

      Announces the defeat of a fighter in combat.

      -
      -
      Parameters
      -

      defeated (obj) – Fighter that’s been defeated.

      -
      -
      -

      Notes

      -

      All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.resolve_attack(attacker, defender, attack_value=None, defense_value=None)[source]
      +
      +
      +resolve_attack(attacker, defender, attack_value=None, defense_value=None)[source]

      Resolves an attack and outputs the result.

      Parameters
      @@ -248,72 +198,17 @@ extremely simply, they are separated out into their own functions so that they are easier to expand upon.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.combat_cleanup(character)[source]
      -

      Cleans up all the temporary combat-related attributes on a character.

      -
      -
      Parameters
      -

      character (obj) – Character to have their combat attributes removed

      -
      -
      -

      Notes

      -

      Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.is_in_combat(character)[source]
      -

      Returns true if the given character is in combat.

      -
      -
      Parameters
      -

      character (obj) – Character to determine if is in combat or not

      -
      -
      Returns
      -

      (bool) – True if in combat or False if not in combat

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.is_turn(character)[source]
      -

      Returns true if it’s currently the given character’s turn in combat.

      -
      -
      Parameters
      -

      character (obj) – Character to determine if it is their turn or not

      -
      -
      Returns
      -

      (bool) – True if it is their turn or False otherwise

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_equip.spend_action(character, actions, action_name=None)[source]
      -

      Spends a character’s available combat actions and checks for end of turn.

      -
      -
      Parameters
      -
        -
      • character (obj) – Character spending the action

      • -
      • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

      • -
      -
      -
      Keyword Arguments
      -
        -
      • action_name (str or None) – If a string is given, sets character’s last action in

      • -
      • to provided string (combat) –

      • -
      -
      -
      -
      +
      +
      +evennia.contrib.game_systems.turnbattle.tb_equip.COMBAT_RULES = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      class evennia.contrib.game_systems.turnbattle.tb_equip.TBEquipTurnHandler(*args, **kwargs)[source]
      -

      Bases: evennia.scripts.scripts.DefaultScript

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler

      This is the script that handles the progression of combat through turns. On creation (when a fight is started) it adds all combat-ready characters to its roster and then sorts them into a turn order. There can only be one @@ -321,91 +216,21 @@ fight going on in a single room at a time, so the script is assigned to a room as its object.

      Fights persist until only one participant is left with any HP or all remaining participants choose to end the combat with the ‘disengage’ command.

      -
      -
      -at_script_creation()[source]
      -

      Called once, when the script is created.

      -
      - -
      -
      -at_stop()[source]
      -

      Called at script termination.

      -
      - -
      -
      -at_repeat()[source]
      -

      Called once every self.interval seconds.

      -
      - -
      -
      -initialize_for_combat(character)[source]
      -

      Prepares a character for combat when starting or entering a fight.

      -
      -
      Parameters
      -

      character (obj) – Character to initialize for combat.

      -
      -
      -
      - -
      -
      -start_turn(character)[source]
      -

      Readies a character for the start of their turn by replenishing their -available actions and notifying them that their turn has come up.

      -
      -
      Parameters
      -

      character (obj) – Character to be readied.

      -
      -
      -

      Notes

      -

      Here, you only get one action per turn, but you might want to allow more than -one per turn, or even grant a number of actions based on a character’s -attributes. You can even add multiple different kinds of actions, I.E. actions -separated for movement, by adding “character.db.combat_movesleft = 3” or -something similar.

      -
      - -
      -
      -next_turn()[source]
      -

      Advances to the next character in the turn order.

      -
      - -
      -
      -turn_end_check(character)[source]
      -

      Tests to see if a character’s turn is over, and cycles to the next turn if it is.

      -
      -
      Parameters
      -

      character (obj) – Character to test for end of turn

      -
      -
      -
      - -
      -
      -join_fight(character)[source]
      -

      Adds a new character to a fight already in progress.

      -
      -
      Parameters
      -

      character (obj) – Character to be added to the fight.

      -
      -
      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      exception DoesNotExist
      -

      Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler.DoesNotExist

      exception MultipleObjectsReturned
      -

      Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler.MultipleObjectsReturned

      @@ -425,6 +250,11 @@ something similar.

      class evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon(*args, **kwargs)[source]

      Bases: evennia.objects.objects.DefaultObject

      A weapon which can be wielded in combat with the ‘wield’ command.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      +
      at_object_creation()[source]
      @@ -531,7 +361,7 @@ normal hook to overload for most object types.

      class evennia.contrib.game_systems.turnbattle.tb_equip.TBEquipCharacter(*args, **kwargs)[source]
      -

      Bases: evennia.objects.objects.DefaultCharacter

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter

      A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands.

      @@ -541,34 +371,16 @@ and maximum HP, and access to combat commands.

      normal hook to overload for most object types.

      -
      -
      -at_pre_move(destination)[source]
      -

      Called just before starting to move this object to -destination.

      -
      -
      Parameters
      -

      destination (Object) – The object we are moving to

      -
      -
      Returns
      -

      shouldmove (bool) – If we should move or not.

      -
      -
      -

      Notes

      -

      If this method returns False/None, the move is cancelled -before it is even started.

      -
      -
      exception DoesNotExist
      -

      Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter.DoesNotExist

      exception MultipleObjectsReturned
      -

      Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter.MultipleObjectsReturned

      @@ -586,7 +398,7 @@ before it is even started.

      class evennia.contrib.game_systems.turnbattle.tb_equip.CmdFight(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdFight

      Starts a fight with everyone in the same room as you.

      Usage:

      fight

      @@ -605,10 +417,15 @@ When it’s your turn, you can attack other characters.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      + +
      +
      +command_handler_class
      +

      alias of TBEquipTurnHandler

      @@ -631,7 +448,7 @@ When it’s your turn, you can attack other characters.

      class evennia.contrib.game_systems.turnbattle.tb_equip.CmdAttack(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdAttack

      Attacks another character.

      Usage:

      attack <target>

      @@ -649,11 +466,10 @@ a chance to hit, and if successful, will deal damage.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      @@ -675,7 +491,7 @@ a chance to hit, and if successful, will deal damage.

      class evennia.contrib.game_systems.turnbattle.tb_equip.CmdPass(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdPass

      Passes on your turn.

      Usage:

      pass

      @@ -690,7 +506,7 @@ if there are still any actions you can take.

      -aliases = ['wait', 'hold']
      +aliases = ['hold', 'wait']
      @@ -698,11 +514,10 @@ if there are still any actions you can take.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      @@ -711,7 +526,7 @@ if there are still any actions you can take.

      -search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      +search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      @@ -719,7 +534,7 @@ if there are still any actions you can take.

      class evennia.contrib.game_systems.turnbattle.tb_equip.CmdDisengage(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdDisengage

      Passes your turn and attempts to end combat.

      Usage:

      disengage

      @@ -743,11 +558,10 @@ fight ends.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      @@ -764,7 +578,7 @@ fight ends.

      class evennia.contrib.game_systems.turnbattle.tb_equip.CmdRest(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdRest

      Recovers damage.

      Usage:

      rest

      @@ -782,11 +596,10 @@ rest if you’re not in a fight.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      @@ -808,7 +621,7 @@ rest if you’re not in a fight.

      class evennia.contrib.game_systems.turnbattle.tb_equip.CmdCombatHelp(**kwargs)[source]
      -

      Bases: evennia.commands.default.help.CmdHelp

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdCombatHelp

      View help or a list of topics

      Usage:

      help <topic or command> @@ -818,11 +631,10 @@ help all

      This will search for help on commands and other topics related to the game.

      -
      -
      -func()[source]
      -

      Run the dynamic help entry creator.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      @@ -876,6 +688,11 @@ currently wielding.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      +
      func()[source]
      @@ -920,6 +737,11 @@ weapon you are currently wielding and become unarmed.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      +
      func()[source]
      @@ -965,6 +787,11 @@ command to remove any armor you are wearing.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      +
      func()[source]
      @@ -1010,6 +837,11 @@ You can’t use this command in combat.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_equip.EquipmentCombatRules object>
      +
      +
      func()[source]
      @@ -1098,7 +930,6 @@ You can’t use this command in combat.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_items.html b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_items.html index 099fb1788d..691998f985 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_items.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_items.html @@ -106,33 +106,13 @@ in your game and using it as-is.

      evennia.contrib.game_systems.turnbattle.tb_items.DEF_DOWN_MOD = -15
      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.roll_init(character)[source]
      -

      Rolls a number between 1-1000 to determine initiative.

      -
      -
      Parameters
      -

      character (obj) – The character to determine initiative for

      -
      -
      Returns
      -

      initiative (int) – The character’s place in initiative - higher -numbers go first.

      -
      -
      -

      Notes

      -

      By default, does not reference the character and simply returns -a random integer from 1 to 1000.

      -

      Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

      -

      return (randint(1,20)) + character.db.dexterity

      -

      This way, characters with a higher dexterity will go first more often.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.get_attack(attacker, defender)[source]
      +
      +
      +class evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules[source]
      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules

      +
      +
      +get_attack(attacker, defender)[source]

      Returns a value for an attack roll.

      Parameters
      @@ -156,9 +136,9 @@ Accuracy Up and Accuracy Down are also accounted for in itemfunc_attack(), so that attack items’ accuracy is affected as well.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.get_defense(attacker, defender)[source]
      +
      +
      +get_defense(attacker, defender)[source]

      Returns a value for defense, which an attack roll must equal or exceed in order for an attack to hit.

      @@ -181,9 +161,9 @@ for an attack to hit.

      This is where conditions affecting defense are accounted for.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.get_damage(attacker, defender)[source]
      +
      +
      +get_damage(attacker, defender)[source]

      Returns a value for damage to be deducted from the defender’s HP after abilities successful hit.

      @@ -208,40 +188,9 @@ roll their own damage in itemfunc_attack(), their damage is unaffected by any conditions.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.apply_damage(defender, damage)[source]
      -

      Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

      -
      -
      Parameters
      -
        -
      • defender (obj) – Character taking damage

      • -
      • damage (int) – Amount of damage being taken

      • -
      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.at_defeat(defeated)[source]
      -

      Announces the defeat of a fighter in combat.

      -
      -
      Parameters
      -

      defeated (obj) – Fighter that’s been defeated.

      -
      -
      -

      Notes

      -

      All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.resolve_attack(attacker, defender, attack_value=None, defense_value=None, damage_value=None, inflict_condition=[])[source]
      +
      +
      +resolve_attack(attacker, defender, attack_value=None, defense_value=None, damage_value=None, inflict_condition=[])[source]

      Resolves an attack and outputs the result.

      Parameters
      @@ -266,71 +215,9 @@ inflict_condition (list): Conditions to inflict upon hit, a

      made with items.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.combat_cleanup(character)[source]
      -

      Cleans up all the temporary combat-related attributes on a character.

      -
      -
      Parameters
      -

      character (obj) – Character to have their combat attributes removed

      -
      -
      -

      Notes

      -

      Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.is_in_combat(character)[source]
      -

      Returns true if the given character is in combat.

      -
      -
      Parameters
      -

      character (obj) – Character to determine if is in combat or not

      -
      -
      Returns
      -

      (bool) – True if in combat or False if not in combat

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.is_turn(character)[source]
      -

      Returns true if it’s currently the given character’s turn in combat.

      -
      -
      Parameters
      -

      character (obj) – Character to determine if it is their turn or not

      -
      -
      Returns
      -

      (bool) – True if it is their turn or False otherwise

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.spend_action(character, actions, action_name=None)[source]
      -

      Spends a character’s available combat actions and checks for end of turn.

      -
      -
      Parameters
      -
        -
      • character (obj) – Character spending the action

      • -
      • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

      • -
      -
      -
      Keyword Arguments
      -
        -
      • action_name (str or None) – If a string is given, sets character’s last action in

      • -
      • to provided string (combat) –

      • -
      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.spend_item_use(item, user)[source]
      +
      +
      +spend_item_use(item, user)[source]

      Spends one use on an item with limited uses.

      Parameters
      @@ -347,9 +234,9 @@ spawn a new object as residue, using the value of item.db.item_consumable as the name of the prototype to spawn.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.use_item(user, item, target)[source]
      +
      +
      +use_item(user, item, target)[source]

      Performs the action of using an item.

      Parameters
      @@ -362,10 +249,11 @@ as the name of the prototype to spawn.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.condition_tickdown(character, turnchar)[source]
      -

      Ticks down the duration of conditions on a character at the start of a given character’s turn.

      +
      +
      +condition_tickdown(character, turnchar)[source]
      +

      Ticks down the duration of conditions on a character at the start of a given character’s +turn.

      Parameters
        @@ -375,14 +263,14 @@ as the name of the prototype to spawn.

      Notes

      -

      In combat, this is called on every fighter at the start of every character’s turn. Out of -combat, it’s instead called when a character’s at_update() hook is called, which is every -30 seconds by default.

      +

      In combat, this is called on every fighter at the start of every character’s turn. Out +of combat, it’s instead called when a character’s at_update() hook is called, which is +every 30 seconds by default.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.add_condition(character, turnchar, condition, duration)[source]
      +
      +
      +add_condition(character, turnchar, condition, duration)[source]

      Adds a condition to a fighter.

      Parameters
      @@ -396,12 +284,107 @@ combat, it’s instead called when a character’s at_update() hook is called, w
      +
      +
      +itemfunc_heal(item, user, target, **kwargs)[source]
      +

      Item function that heals HP.

      +
      +
      kwargs:

      min_healing(int): Minimum amount of HP recovered +max_healing(int): Maximum amount of HP recovered

      +
      +
      +
      + +
      +
      +itemfunc_add_condition(item, user, target, **kwargs)[source]
      +

      Item function that gives the target one or more conditions.

      +
      +
      kwargs:
      +
      conditions (list): Conditions added by the item

      formatted as a list of tuples: (condition (str), duration (int or True))

      +
      +
      +
      +
      +

      Notes

      +

      Should mostly be used for beneficial conditions - use itemfunc_attack +for an item that can give an enemy a harmful condition.

      +
      + +
      +
      +itemfunc_cure_condition(item, user, target, **kwargs)[source]
      +

      Item function that’ll remove given conditions from a target.

      +
      +
      kwargs:

      to_cure(list): List of conditions (str) that the item cures when used

      +
      +
      +
      + +
      +
      +itemfunc_attack(item, user, target, **kwargs)[source]
      +

      Item function that attacks a target.

      +
      +
      kwargs:

      min_damage(int): Minimum damage dealt by the attack +max_damage(int): Maximum damage dealth by the attack +accuracy(int): Bonus / penalty to attack accuracy roll +inflict_condition(list): List of conditions inflicted on hit,

      +
      +

      formatted as a (str, int) tuple containing condition name +and duration.

      +
      +
      +
      +

      Notes

      +

      Calls resolve_attack at the end.

      +
      + +
      + +
      +
      +evennia.contrib.game_systems.turnbattle.tb_items.ITEMFUNCS = {'add_condition': <bound method ItemCombatRules.itemfunc_add_condition of <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>>, 'attack': <bound method ItemCombatRules.itemfunc_attack of <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>>, 'cure_condition': <bound method ItemCombatRules.itemfunc_cure_condition of <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>>, 'heal': <bound method ItemCombatRules.itemfunc_heal of <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>>}
      +

      You can paste these prototypes into your game’s prototypes.py module in your +/world/ folder, and use the spawner to create them - they serve as examples +of items you can make and a handy way to demonstrate the system for +conditions as well.

      +

      Items don’t have any particular typeclass - any object with a db entry +“item_func” that references one of the functions given above can be used as +an item with the ‘use’ command.

      +

      Only “item_func” is required, but item behavior can be further modified by +specifying any of the following:

      +
      +

      item_uses (int): If defined, item has a limited number of uses

      +

      item_selfonly (bool): If True, user can only use the item on themself

      +
      +
      item_consumable(True or str): If True, item is destroyed when it runs

      out of uses. If a string is given, the item will spawn a new +object as it’s destroyed, with the string specifying what prototype +to spawn.

      +
      +
      item_kwargs (dict): Keyword arguments to pass to the function defined in

      item_func. Unique to each function, and can be used to make multiple +items using the same function work differently.

      +
      +
      +
      +
      + +
      +
      +evennia.contrib.game_systems.turnbattle.tb_items.AMULET_OF_WEAKNESS = {'desc': "The one who holds this amulet can call upon its power to gain great weakness. It's not a terribly useful artifact.", 'item_func': 'add_condition', 'item_kwargs': {'conditions': [('Damage Down', 3), ('Accuracy Down', 3), ('Defense Down', 3)]}, 'item_selfonly': True, 'key': 'The Amulet of Weakness'}
      +
      +
      class evennia.contrib.game_systems.turnbattle.tb_items.TBItemsCharacter(*args, **kwargs)[source]
      -

      Bases: evennia.objects.objects.DefaultCharacter

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter

      A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      +
      at_object_creation()[source]
      @@ -409,24 +392,6 @@ and maximum HP, and access to combat commands.

      normal hook to overload for most object types.

      -
      -
      -at_pre_move(destination)[source]
      -

      Called just before starting to move this object to -destination.

      -
      -
      Parameters
      -

      destination (Object) – The object we are moving to

      -
      -
      Returns
      -

      shouldmove (bool) – If we should move or not.

      -
      -
      -

      Notes

      -

      If this method returns False/None, the move is cancelled -before it is even started.

      -
      -
      at_turn_start()[source]
      @@ -449,13 +414,13 @@ turn in combat, or every 30 seconds out of combat.

      exception DoesNotExist
      -

      Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter.DoesNotExist

      exception MultipleObjectsReturned
      -

      Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter.MultipleObjectsReturned

      @@ -510,7 +475,7 @@ normal hook to overload for most object types.

      class evennia.contrib.game_systems.turnbattle.tb_items.TBItemsTurnHandler(*args, **kwargs)[source]
      -

      Bases: evennia.scripts.scripts.DefaultScript

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler

      This is the script that handles the progression of combat through turns. On creation (when a fight is started) it adds all combat-ready characters to its roster and then sorts them into a turn order. There can only be one @@ -518,52 +483,10 @@ fight going on in a single room at a time, so the script is assigned to a room as its object.

      Fights persist until only one participant is left with any HP or all remaining participants choose to end the combat with the ‘disengage’ command.

      -
      -
      -at_script_creation()[source]
      -

      Called once, when the script is created.

      -
      - -
      -
      -at_stop()[source]
      -

      Called at script termination.

      -
      - -
      -
      -at_repeat()[source]
      -

      Called once every self.interval seconds.

      -
      - -
      -
      -initialize_for_combat(character)[source]
      -

      Prepares a character for combat when starting or entering a fight.

      -
      -
      Parameters
      -

      character (obj) – Character to initialize for combat.

      -
      -
      -
      - -
      -
      -start_turn(character)[source]
      -

      Readies a character for the start of their turn by replenishing their -available actions and notifying them that their turn has come up.

      -
      -
      Parameters
      -

      character (obj) – Character to be readied.

      -
      -
      -

      Notes

      -

      Here, you only get one action per turn, but you might want to allow more than -one per turn, or even grant a number of actions based on a character’s -attributes. You can even add multiple different kinds of actions, I.E. actions -separated for movement, by adding “character.db.combat_movesleft = 3” or -something similar.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      @@ -571,38 +494,16 @@ something similar.

      Advances to the next character in the turn order.

      -
      -
      -turn_end_check(character)[source]
      -

      Tests to see if a character’s turn is over, and cycles to the next turn if it is.

      -
      -
      Parameters
      -

      character (obj) – Character to test for end of turn

      -
      -
      -
      - -
      -
      -join_fight(character)[source]
      -

      Adds a new character to a fight already in progress.

      -
      -
      Parameters
      -

      character (obj) – Character to be added to the fight.

      -
      -
      -
      -
      exception DoesNotExist
      -

      Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler.DoesNotExist

      exception MultipleObjectsReturned
      -

      Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler.MultipleObjectsReturned

      @@ -620,7 +521,7 @@ something similar.

      class evennia.contrib.game_systems.turnbattle.tb_items.CmdFight(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdFight

      Starts a fight with everyone in the same room as you.

      Usage:

      fight

      @@ -639,10 +540,15 @@ When it’s your turn, you can attack other characters.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      + +
      +
      +combat_handler_class
      +

      alias of TBItemsTurnHandler

      @@ -665,7 +571,7 @@ When it’s your turn, you can attack other characters.

      class evennia.contrib.game_systems.turnbattle.tb_items.CmdAttack(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdAttack

      Attacks another character.

      Usage:

      attack <target>

      @@ -683,11 +589,10 @@ a chance to hit, and if successful, will deal damage.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      @@ -709,7 +614,7 @@ a chance to hit, and if successful, will deal damage.

      class evennia.contrib.game_systems.turnbattle.tb_items.CmdPass(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdPass

      Passes on your turn.

      Usage:

      pass

      @@ -724,7 +629,7 @@ if there are still any actions you can take.

      -aliases = ['wait', 'hold']
      +aliases = ['hold', 'wait']
      @@ -732,11 +637,10 @@ if there are still any actions you can take.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      @@ -745,7 +649,7 @@ if there are still any actions you can take.

      -search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      +search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      @@ -753,7 +657,7 @@ if there are still any actions you can take.

      class evennia.contrib.game_systems.turnbattle.tb_items.CmdDisengage(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdDisengage

      Passes your turn and attempts to end combat.

      Usage:

      disengage

      @@ -777,11 +681,10 @@ fight ends.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      @@ -798,7 +701,7 @@ fight ends.

      class evennia.contrib.game_systems.turnbattle.tb_items.CmdRest(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdRest

      Recovers damage.

      Usage:

      rest

      @@ -816,11 +719,10 @@ rest if you’re not in a fight.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      @@ -842,7 +744,7 @@ rest if you’re not in a fight.

      class evennia.contrib.game_systems.turnbattle.tb_items.CmdCombatHelp(**kwargs)[source]
      -

      Bases: evennia.commands.default.help.CmdHelp

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdCombatHelp

      View help or a list of topics

      Usage:

      help <topic or command> @@ -852,11 +754,15 @@ help all

      This will search for help on commands and other topics related to the game.

      -
      -
      -func()[source]
      -

      Run the dynamic help entry creator.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      + +
      +
      +combat_help_text = "Available combat commands:|/|wAttack:|n Attack a target, attempting to deal damage.|/|wPass:|n Pass your turn without further action.|/|wDisengage:|n End your turn and attempt to end combat.|/|wUse:|n Use an item you're carrying."
      +
      @@ -907,6 +813,11 @@ to attack others, and as such can only be used in combat.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules object>
      +
      +
      func()[source]
      @@ -935,6 +846,11 @@ to attack others, and as such can only be used in combat.

      class evennia.contrib.game_systems.turnbattle.tb_items.BattleCmdSet(cmdsetobj=None, key=None)[source]

      Bases: evennia.commands.default.cmdset_character.CharacterCmdSet

      This command set includes all the commmands used in the battle system.

      +
      +
      +path = 'evennia.contrib.game_systems.turnbattle.tb_items.BattleCmdSet'
      +
      +
      key = 'DefaultCharacter'
      @@ -946,94 +862,6 @@ to attack others, and as such can only be used in combat.

      Populates the cmdset

      -
      -
      -path = 'evennia.contrib.game_systems.turnbattle.tb_items.BattleCmdSet'
      -
      - -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.itemfunc_heal(item, user, target, **kwargs)[source]
      -

      Item function that heals HP.

      -
      -
      kwargs:

      min_healing(int): Minimum amount of HP recovered -max_healing(int): Maximum amount of HP recovered

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.itemfunc_add_condition(item, user, target, **kwargs)[source]
      -

      Item function that gives the target one or more conditions.

      -
      -
      kwargs:
      -
      conditions (list): Conditions added by the item

      formatted as a list of tuples: (condition (str), duration (int or True))

      -
      -
      -
      -
      -

      Notes

      -

      Should mostly be used for beneficial conditions - use itemfunc_attack -for an item that can give an enemy a harmful condition.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.itemfunc_cure_condition(item, user, target, **kwargs)[source]
      -

      Item function that’ll remove given conditions from a target.

      -
      -
      kwargs:

      to_cure(list): List of conditions (str) that the item cures when used

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.itemfunc_attack(item, user, target, **kwargs)[source]
      -

      Item function that attacks a target.

      -
      -
      kwargs:

      min_damage(int): Minimum damage dealt by the attack -max_damage(int): Maximum damage dealth by the attack -accuracy(int): Bonus / penalty to attack accuracy roll -inflict_condition(list): List of conditions inflicted on hit,

      -
      -

      formatted as a (str, int) tuple containing condition name -and duration.

      -
      -
      -
      -

      Notes

      -

      Calls resolve_attack at the end.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_items.ITEMFUNCS = {'add_condition': <function itemfunc_add_condition>, 'attack': <function itemfunc_attack>, 'cure_condition': <function itemfunc_cure_condition>, 'heal': <function itemfunc_heal>}
      -

      You can paste these prototypes into your game’s prototypes.py module in your -/world/ folder, and use the spawner to create them - they serve as examples -of items you can make and a handy way to demonstrate the system for -conditions as well.

      -

      Items don’t have any particular typeclass - any object with a db entry -“item_func” that references one of the functions given above can be used as -an item with the ‘use’ command.

      -

      Only “item_func” is required, but item behavior can be further modified by -specifying any of the following:

      -
      -

      item_uses (int): If defined, item has a limited number of uses

      -

      item_selfonly (bool): If True, user can only use the item on themself

      -
      -
      item_consumable(True or str): If True, item is destroyed when it runs

      out of uses. If a string is given, the item will spawn a new -object as it’s destroyed, with the string specifying what prototype -to spawn.

      -
      -
      item_kwargs (dict): Keyword arguments to pass to the function defined in

      item_func. Unique to each function, and can be used to make multiple -items using the same function work differently.

      -
      -
      -
      @@ -1078,7 +906,6 @@ items using the same function work differently.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_magic.html b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_magic.html index 01164e7484..7fed227a6f 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_magic.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_magic.html @@ -43,7 +43,7 @@

      evennia.contrib.game_systems.turnbattle.tb_magic

      Simple turn-based combat system with spell casting

      -

      Contrib - Tim Ashley Jenkins 2017

      +

      Contrib - Tim Ashley Jenkins 2017, Refactor by Griatch, 2022

      This is a version of the ‘turnbattle’ contrib that includes a basic, expandable framework for a ‘magic system’, whereby players can spend a limited resource (MP) to achieve a wide variety of effects, both in @@ -98,234 +98,130 @@ self.add(tb_magic.BattleCmdSet())

      This module is meant to be heavily expanded on, so you may want to copy it to your game’s ‘world’ folder and modify it there rather than importing it in your game and using it as-is.

      +
      +
      +class evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules[source]
      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules

      +
      +
      +spell_healing(caster, spell_name, targets, cost, **kwargs)[source]
      +

      Spell that restores HP to a target or targets.

      +
      +
      kwargs:
      +
      healing_range (tuple): Minimum and maximum amount healed to

      each target. (20, 40) by default.

      +
      +
      +
      +
      +
      + +
      +
      +spell_attack(caster, spell_name, targets, cost, **kwargs)[source]
      +

      Spell that deals damage in combat. Similar to resolve_attack.

      +
      +
      kwargs:
      +
      attack_name (tuple): Single and plural describing the sort of

      attack or projectile that strikes each enemy.

      +
      +
      damage_range (tuple): Minimum and maximum damage dealt by the

      spell. (10, 20) by default.

      +
      +
      accuracy (int): Modifier to the spell’s attack roll, determining

      an increased or decreased chance to hit. 0 by default.

      +
      +
      attack_count (int): How many individual attacks are made as part

      of the spell. If the number of attacks exceeds the number of +targets, the first target specified will be attacked more +than once. Just 1 by default - if the attack_count is less +than the number targets given, each target will only be +attacked once.

      +
      +
      +
      +
      +
      + +
      +
      +spell_conjure(caster, spell_name, targets, cost, **kwargs)[source]
      +

      Spell that creates an object.

      +
      +
      kwargs:

      obj_key (str): Key of the created object. +obj_desc (str): Desc of the created object. +obj_typeclass (str): Typeclass path of the object.

      +
      +
      +

      If you want to make more use of this particular spell funciton, +you may want to modify it to use the spawner (in evennia.utils.spawner) +instead of creating objects directly.

      +
      + +
      + +
      +
      +evennia.contrib.game_systems.turnbattle.tb_magic.COMBAT_RULES = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +

      In this section, each spell is matched to a function, and given parameters +that determine its MP cost, valid type and number of targets, and what +function casting the spell executes.

      +

      This data is given as a dictionary of dictionaries - the key of each entry +is the spell’s name, and the value is a dictionary of various options and +parameters, some of which are required and others which are optional.

      +

      Required values for spells:

      +
      +

      cost (int): MP cost of casting the spell +target (str): Valid targets for the spell. Can be any of:

      +
      +

      “none” - No target needed +“self” - Self only +“any” - Any object +“anyobj” - Any object that isn’t a character +“anychar” - Any character +“other” - Any object excluding the caster +“otherchar” - Any character excluding the caster

      +
      +
      +
      spellfunc (callable): Function that performs the action of the spell.

      Must take the following arguments: caster (obj), spell_name (str), +targets (list), and cost (int), as well as **kwargs.

      +
      +
      +
      +

      Optional values for spells:

      +
      +

      combat_spell (bool): If the spell can be cast in combat. True by default. +noncombat_spell (bool): If the spell can be cast out of combat. True by default. +max_targets (int): Maximum number of objects that can be targeted by the spell.

      +
      +

      1 by default - unused if target is “none” or “self”

      +
      +
      +

      Any other values specified besides the above will be passed as kwargs to ‘spellfunc’. +You can use kwargs to effectively re-use the same function for different but similar +spells - for example, ‘magic missile’ and ‘flame shot’ use the same function, but +behave differently, as they have different damage ranges, accuracy, amount of attacks +made as part of the spell, and so forth. If you make your spell functions flexible +enough, you can make a wide variety of spells just by adding more entries to this +dictionary.

      +
      + +
      +
      +evennia.contrib.game_systems.turnbattle.tb_magic.SPELLS = {'cactus conjuration': {'combat_spell': False, 'cost': 2, 'obj_desc': 'An ordinary green cactus with little spines.', 'obj_key': 'a cactus', 'spellfunc': <bound method MagicCombatRules.spell_conjure of <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>>, 'target': 'none'}, 'cure wounds': {'cost': 5, 'spellfunc': <bound method MagicCombatRules.spell_healing of <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>>, 'target': 'anychar'}, 'flame shot': {'attack_name': ('A jet of flame', 'jets of flame'), 'cost': 3, 'damage_range': (25, 35), 'noncombat_spell': False, 'spellfunc': <bound method MagicCombatRules.spell_attack of <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>>, 'target': 'otherchar'}, 'full heal': {'cost': 12, 'healing_range': (100, 100), 'spellfunc': <bound method MagicCombatRules.spell_healing of <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>>, 'target': 'anychar'}, 'magic missile': {'accuracy': 999, 'attack_count': 3, 'attack_name': ('A bolt', 'bolts'), 'cost': 3, 'damage_range': (4, 7), 'max_targets': 3, 'noncombat_spell': False, 'spellfunc': <bound method MagicCombatRules.spell_attack of <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>>, 'target': 'otherchar'}, 'mass cure wounds': {'cost': 10, 'max_targets': 5, 'spellfunc': <bound method MagicCombatRules.spell_healing of <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>>, 'target': 'anychar'}}
      +
      +
      evennia.contrib.game_systems.turnbattle.tb_magic.ACTIONS_PER_TURN = 1
      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.roll_init(character)[source]
      -

      Rolls a number between 1-1000 to determine initiative.

      -
      -
      Parameters
      -

      character (obj) – The character to determine initiative for

      -
      -
      Returns
      -

      initiative (int) – The character’s place in initiative - higher -numbers go first.

      -
      -
      -

      Notes

      -

      By default, does not reference the character and simply returns -a random integer from 1 to 1000.

      -

      Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

      -

      return (randint(1,20)) + character.db.dexterity

      -

      This way, characters with a higher dexterity will go first more often.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.get_attack(attacker, defender)[source]
      -

      Returns a value for an attack roll.

      -
      -
      Parameters
      -
        -
      • attacker (obj) – Character doing the attacking

      • -
      • defender (obj) – Character being attacked

      • -
      -
      -
      Returns
      -

      attack_value (int)

      -
      -
      Attack roll value, compared against a defense value

      to determine whether an attack hits or misses.

      -
      -
      -

      -
      -
      -

      Notes

      -

      By default, returns a random integer from 1 to 100 without using any -properties from either the attacker or defender.

      -

      This can easily be expanded to return a value based on characters stats, -equipment, and abilities. This is why the attacker and defender are passed -to this function, even though nothing from either one are used in this example.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.get_defense(attacker, defender)[source]
      -

      Returns a value for defense, which an attack roll must equal or exceed in order -for an attack to hit.

      -
      -
      Parameters
      -
        -
      • attacker (obj) – Character doing the attacking

      • -
      • defender (obj) – Character being attacked

      • -
      -
      -
      Returns
      -

      defense_value (int)

      -
      -
      Defense value, compared against an attack roll

      to determine whether an attack hits or misses.

      -
      -
      -

      -
      -
      -

      Notes

      -

      By default, returns 50, not taking any properties of the defender or -attacker into account.

      -

      As above, this can be expanded upon based on character stats and equipment.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.get_damage(attacker, defender)[source]
      -

      Returns a value for damage to be deducted from the defender’s HP after abilities -successful hit.

      -
      -
      Parameters
      -
        -
      • attacker (obj) – Character doing the attacking

      • -
      • defender (obj) – Character being damaged

      • -
      -
      -
      Returns
      -

      damage_value (int)

      -
      -
      Damage value, which is to be deducted from the defending

      character’s HP.

      -
      -
      -

      -
      -
      -

      Notes

      -

      By default, returns a random integer from 15 to 25 without using any -properties from either the attacker or defender.

      -

      Again, this can be expanded upon.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.apply_damage(defender, damage)[source]
      -

      Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

      -
      -
      Parameters
      -
        -
      • defender (obj) – Character taking damage

      • -
      • damage (int) – Amount of damage being taken

      • -
      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.at_defeat(defeated)[source]
      -

      Announces the defeat of a fighter in combat.

      -
      -
      Parameters
      -

      defeated (obj) – Fighter that’s been defeated.

      -
      -
      -

      Notes

      -

      All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.resolve_attack(attacker, defender, attack_value=None, defense_value=None)[source]
      -

      Resolves an attack and outputs the result.

      -
      -
      Parameters
      -
        -
      • attacker (obj) – Character doing the attacking

      • -
      • defender (obj) – Character being attacked

      • -
      -
      -
      -

      Notes

      -

      Even though the attack and defense values are calculated -extremely simply, they are separated out into their own functions -so that they are easier to expand upon.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.combat_cleanup(character)[source]
      -

      Cleans up all the temporary combat-related attributes on a character.

      -
      -
      Parameters
      -

      character (obj) – Character to have their combat attributes removed

      -
      -
      -

      Notes

      -

      Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.is_in_combat(character)[source]
      -

      Returns true if the given character is in combat.

      -
      -
      Parameters
      -

      character (obj) – Character to determine if is in combat or not

      -
      -
      Returns
      -

      (bool) – True if in combat or False if not in combat

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.is_turn(character)[source]
      -

      Returns true if it’s currently the given character’s turn in combat.

      -
      -
      Parameters
      -

      character (obj) – Character to determine if it is their turn or not

      -
      -
      Returns
      -

      (bool) – True if it is their turn or False otherwise

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.spend_action(character, actions, action_name=None)[source]
      -

      Spends a character’s available combat actions and checks for end of turn.

      -
      -
      Parameters
      -
        -
      • character (obj) – Character spending the action

      • -
      • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

      • -
      -
      -
      Keyword Arguments
      -
        -
      • action_name (str or None) – If a string is given, sets character’s last action in

      • -
      • to provided string (combat) –

      • -
      -
      -
      -
      -
      class evennia.contrib.game_systems.turnbattle.tb_magic.TBMagicCharacter(*args, **kwargs)[source]
      -

      Bases: evennia.objects.objects.DefaultCharacter

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter

      A character able to participate in turn-based combat. Has attributes for current -and maximum HP, and access to combat commands.

      +and maximum HP, access to combat commands and magic.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +
      +
      at_object_creation()[source]
      @@ -337,34 +233,16 @@ We’re just going to set this value at ‘100’ by default.

      can be changed at creation and factor into combat calculations.

      -
      -
      -at_pre_move(destination)[source]
      -

      Called just before starting to move this object to -destination.

      -
      -
      Parameters
      -

      destination (Object) – The object we are moving to

      -
      -
      Returns
      -

      shouldmove (bool) – If we should move or not.

      -
      -
      -

      Notes

      -

      If this method returns False/None, the move is cancelled -before it is even started.

      -
      -
      exception DoesNotExist
      -

      Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter.DoesNotExist

      exception MultipleObjectsReturned
      -

      Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter.MultipleObjectsReturned

      @@ -382,7 +260,7 @@ before it is even started.

      class evennia.contrib.game_systems.turnbattle.tb_magic.TBMagicTurnHandler(*args, **kwargs)[source]
      -

      Bases: evennia.scripts.scripts.DefaultScript

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler

      This is the script that handles the progression of combat through turns. On creation (when a fight is started) it adds all combat-ready characters to its roster and then sorts them into a turn order. There can only be one @@ -390,91 +268,21 @@ fight going on in a single room at a time, so the script is assigned to a room as its object.

      Fights persist until only one participant is left with any HP or all remaining participants choose to end the combat with the ‘disengage’ command.

      -
      -
      -at_script_creation()[source]
      -

      Called once, when the script is created.

      -
      - -
      -
      -at_stop()[source]
      -

      Called at script termination.

      -
      - -
      -
      -at_repeat()[source]
      -

      Called once every self.interval seconds.

      -
      - -
      -
      -initialize_for_combat(character)[source]
      -

      Prepares a character for combat when starting or entering a fight.

      -
      -
      Parameters
      -

      character (obj) – Character to initialize for combat.

      -
      -
      -
      - -
      -
      -start_turn(character)[source]
      -

      Readies a character for the start of their turn by replenishing their -available actions and notifying them that their turn has come up.

      -
      -
      Parameters
      -

      character (obj) – Character to be readied.

      -
      -
      -

      Notes

      -

      Here, you only get one action per turn, but you might want to allow more than -one per turn, or even grant a number of actions based on a character’s -attributes. You can even add multiple different kinds of actions, I.E. actions -separated for movement, by adding “character.db.combat_movesleft = 3” or -something similar.

      -
      - -
      -
      -next_turn()[source]
      -

      Advances to the next character in the turn order.

      -
      - -
      -
      -turn_end_check(character)[source]
      -

      Tests to see if a character’s turn is over, and cycles to the next turn if it is.

      -
      -
      Parameters
      -

      character (obj) – Character to test for end of turn

      -
      -
      -
      - -
      -
      -join_fight(character)[source]
      -

      Adds a new character to a fight already in progress.

      -
      -
      Parameters
      -

      character (obj) – Character to be added to the fight.

      -
      -
      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +
      exception DoesNotExist
      -

      Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler.DoesNotExist

      exception MultipleObjectsReturned
      -

      Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler.MultipleObjectsReturned

      @@ -492,7 +300,7 @@ something similar.

      class evennia.contrib.game_systems.turnbattle.tb_magic.CmdFight(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdFight

      Starts a fight with everyone in the same room as you.

      Usage:

      fight

      @@ -511,10 +319,15 @@ When it’s your turn, you can attack other characters.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +
      + +
      +
      +combat_handler_class
      +

      alias of TBMagicTurnHandler

      @@ -537,7 +350,7 @@ When it’s your turn, you can attack other characters.

      class evennia.contrib.game_systems.turnbattle.tb_magic.CmdAttack(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdAttack

      Attacks another character.

      Usage:

      attack <target>

      @@ -555,11 +368,10 @@ a chance to hit, and if successful, will deal damage.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +
      @@ -581,7 +393,7 @@ a chance to hit, and if successful, will deal damage.

      class evennia.contrib.game_systems.turnbattle.tb_magic.CmdPass(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdPass

      Passes on your turn.

      Usage:

      pass

      @@ -596,7 +408,7 @@ if there are still any actions you can take.

      -aliases = ['wait', 'hold']
      +aliases = ['hold', 'wait']
      @@ -604,11 +416,10 @@ if there are still any actions you can take.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +
      @@ -617,7 +428,7 @@ if there are still any actions you can take.

      -search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      +search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      @@ -625,7 +436,7 @@ if there are still any actions you can take.

      class evennia.contrib.game_systems.turnbattle.tb_magic.CmdDisengage(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdDisengage

      Passes your turn and attempts to end combat.

      Usage:

      disengage

      @@ -649,11 +460,10 @@ fight ends.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +
      @@ -680,17 +490,17 @@ fight ends.

      The following spells are provided as examples:

      -
      |wmagic missile|n (3 MP): Fires three missiles that never miss. Can target

      up to three different enemies.

      +
      |wmagic missile|n (3 MP): Fires three missiles that never miss. Can target

      up to three different enemies.

      -

      |wflame shot|n (3 MP): Shoots a high-damage jet of flame at one target.

      -

      |wcure wounds|n (5 MP): Heals damage on one target.

      +

      |wflame shot|n (3 MP): Shoots a high-damage jet of flame at one target.

      +

      |wcure wounds|n (5 MP): Heals damage on one target.

      -
      |wmass cure wounds|n (10 MP): Like ‘cure wounds’, but can heal up to 5

      targets at once.

      +
      |wmass cure wounds|n (10 MP): Like ‘cure wounds’, but can heal up to 5

      targets at once.

      -

      |wfull heal|n (12 MP): Heals one target back to full HP.

      -

      |wcactus conjuration|n (2 MP): Creates a cactus.

      +

      |wfull heal|n (12 MP): Heals one target back to full HP.

      +

      |wcactus conjuration|n (2 MP): Creates a cactus.

      @@ -748,6 +558,11 @@ Typing ‘cast’ by itself will give you a list of spells you know.

      help_category = 'magic'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +
      +
      func()[source]
      @@ -798,6 +613,11 @@ only rest if you’re not in a fight.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_magic.MagicCombatRules object>
      +
      +
      func()[source]
      @@ -868,7 +688,7 @@ other targets in combat.

      class evennia.contrib.game_systems.turnbattle.tb_magic.CmdCombatHelp(**kwargs)[source]
      -

      Bases: evennia.commands.default.help.CmdHelp

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdCombatHelp

      View help or a list of topics

      Usage:

      help <topic or command> @@ -878,12 +698,6 @@ help all

      This will search for help on commands and other topics related to the game.

      -
      -
      -func()[source]
      -

      Run the dynamic help entry creator.

      -
      -
      aliases = ['?']
      @@ -916,6 +730,11 @@ topics related to the game.

      class evennia.contrib.game_systems.turnbattle.tb_magic.BattleCmdSet(cmdsetobj=None, key=None)[source]

      Bases: evennia.commands.default.cmdset_character.CharacterCmdSet

      This command set includes all the commmands used in the battle system.

      +
      +
      +path = 'evennia.contrib.game_systems.turnbattle.tb_magic.BattleCmdSet'
      +
      +
      key = 'DefaultCharacter'
      @@ -927,62 +746,6 @@ topics related to the game.

      Populates the cmdset

      -
      -
      -path = 'evennia.contrib.game_systems.turnbattle.tb_magic.BattleCmdSet'
      -
      - -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.spell_healing(caster, spell_name, targets, cost, **kwargs)[source]
      -

      Spell that restores HP to a target or targets.

      -
      -
      kwargs:
      -
      healing_range (tuple): Minimum and maximum amount healed to

      each target. (20, 40) by default.

      -
      -
      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.spell_attack(caster, spell_name, targets, cost, **kwargs)[source]
      -

      Spell that deals damage in combat. Similar to resolve_attack.

      -
      -
      kwargs:
      -
      attack_name (tuple): Single and plural describing the sort of

      attack or projectile that strikes each enemy.

      -
      -
      damage_range (tuple): Minimum and maximum damage dealt by the

      spell. (10, 20) by default.

      -
      -
      accuracy (int): Modifier to the spell’s attack roll, determining

      an increased or decreased chance to hit. 0 by default.

      -
      -
      attack_count (int): How many individual attacks are made as part

      of the spell. If the number of attacks exceeds the number of -targets, the first target specified will be attacked more -than once. Just 1 by default - if the attack_count is less -than the number targets given, each target will only be -attacked once.

      -
      -
      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_magic.spell_conjure(caster, spell_name, targets, cost, **kwargs)[source]
      -

      Spell that creates an object.

      -
      -
      kwargs:

      obj_key (str): Key of the created object. -obj_desc (str): Desc of the created object. -obj_typeclass (str): Typeclass path of the object.

      -
      -
      -

      If you want to make more use of this particular spell funciton, -you may want to modify it to use the spawner (in evennia.utils.spawner) -instead of creating objects directly.

      @@ -1027,7 +790,6 @@ instead of creating objects directly.

      Versions

      diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_range.html b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_range.html index e215874ae8..9afe7eb48a 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_range.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tb_range.html @@ -135,33 +135,13 @@ in your game and using it as-is.

      evennia.contrib.game_systems.turnbattle.tb_range.ACTIONS_PER_TURN = 2
      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.roll_init(character)[source]
      -

      Rolls a number between 1-1000 to determine initiative.

      -
      -
      Parameters
      -

      character (obj) – The character to determine initiative for

      -
      -
      Returns
      -

      initiative (int) – The character’s place in initiative - higher -numbers go first.

      -
      -
      -

      Notes

      -

      By default, does not reference the character and simply returns -a random integer from 1 to 1000.

      -

      Since the character is passed to this function, you can easily reference -a character’s stats to determine an initiative roll - for example, if your -character has a ‘dexterity’ attribute, you can use it to give that character -an advantage in turn order, like so:

      -

      return (randint(1,20)) + character.db.dexterity

      -

      This way, characters with a higher dexterity will go first more often.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.get_attack(attacker, defender, attack_type)[source]
      +
      +
      +class evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules[source]
      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules

      +
      +
      +get_attack(attacker, defender, attack_type)[source]

      Returns a value for an attack roll.

      Parameters
      @@ -189,9 +169,9 @@ equipment, and abilities. This is why the attacker and defender are passed to this function, even though nothing from either one are used in this example.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.get_defense(attacker, defender, attack_type)[source]
      +
      +
      +get_defense(attacker, defender, attack_type='melee')[source]

      Returns a value for defense, which an attack roll must equal or exceed in order for an attack to hit.

      @@ -217,67 +197,90 @@ attacker into account.

      As above, this can be expanded upon based on character stats and equipment.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.get_damage(attacker, defender)[source]
      -

      Returns a value for damage to be deducted from the defender’s HP after abilities -successful hit.

      +
      +
      +get_range(obj1, obj2)[source]
      +

      Gets the combat range between two objects.

      Parameters
        -
      • attacker (obj) – Character doing the attacking

      • -
      • defender (obj) – Character being damaged

      • +
      • obj1 (obj) – First object

      • +
      • obj2 (obj) – Second object

      Returns
      -

      damage_value (int)

      -
      -
      Damage value, which is to be deducted from the defending

      character’s HP.

      +

      range (int or None) – Distance between two objects or None if not applicable

      -

      -
      -
      -

      Notes

      -

      By default, returns a random integer from 15 to 25 without using any -properties from either the attacker or defender.

      -

      Again, this can be expanded upon.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.apply_damage(defender, damage)[source]
      -

      Applies damage to a target, reducing their HP by the damage amount to a -minimum of 0.

      +
      +
      +distance_inc(mover, target)[source]
      +

      Function that increases distance in range field between mover and target.

      Parameters
        -
      • defender (obj) – Character taking damage

      • -
      • damage (int) – Amount of damage being taken

      • +
      • mover (obj) – The object moving

      • +
      • target (obj) – The object to be moved away from

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.at_defeat(defeated)[source]
      -

      Announces the defeat of a fighter in combat.

      +
      +
      +distance_dec(mover, target)[source]
      +

      Helper function that decreases distance in range field between mover and target.

      Parameters
      -

      defeated (obj) – Fighter that’s been defeated.

      +
        +
      • mover (obj) – The object moving

      • +
      • target (obj) – The object to be moved toward

      • +
      +
      +
      +
      + +
      +
      +approach(mover, target)[source]
      +

      Manages a character’s whole approach, including changes in ranges to other characters.

      +
      +
      Parameters
      +
        +
      • mover (obj) – The object moving

      • +
      • target (obj) – The object to be moved toward

      • +

      Notes

      -

      All this does is announce a defeat message by default, but if you -want anything else to happen to defeated fighters (like putting them -into a dying state or something similar) then this is the place to -do it.

      +

      The mover will also automatically move toward any objects that are closer to the +target than the mover is. The mover will also move away from anything they started +out close to.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.resolve_attack(attacker, defender, attack_type, attack_value=None, defense_value=None)[source]
      +
      +
      +withdraw(mover, target)[source]
      +

      Manages a character’s whole withdrawal, including changes in ranges to other characters.

      +
      +
      Parameters
      +
        +
      • mover (obj) – The object moving

      • +
      • target (obj) – The object to be moved away from

      • +
      +
      +
      +

      Notes

      +

      The mover will also automatically move away from objects that are close to the target +of their withdrawl. The mover will never inadvertently move toward anything else while +withdrawing - they can be considered to be moving to open space.

      +
      + +
      +
      +resolve_attack(attacker, defender, attack_value=None, defense_value=None, attack_type='melee')[source]

      Resolves an attack and outputs the result.

      Parameters
      @@ -294,147 +297,25 @@ extremely simply, they are separated out into their own functions so that they are easier to expand upon.

      -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.get_range(obj1, obj2)[source]
      -

      Gets the combat range between two objects.

      -
      -
      Parameters
      -
        -
      • obj1 (obj) – First object

      • -
      • obj2 (obj) – Second object

      • -
      -
      -
      Returns
      -

      range (int or None) – Distance between two objects or None if not applicable

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.distance_inc(mover, target)[source]
      -

      Function that increases distance in range field between mover and target.

      -
      -
      Parameters
      -
        -
      • mover (obj) – The object moving

      • -
      • target (obj) – The object to be moved away from

      • -
      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.approach(mover, target)[source]
      -

      Manages a character’s whole approach, including changes in ranges to other characters.

      -
      -
      Parameters
      -
        -
      • mover (obj) – The object moving

      • -
      • target (obj) – The object to be moved toward

      • -
      -
      -
      -

      Notes

      -

      The mover will also automatically move toward any objects that are closer to the -target than the mover is. The mover will also move away from anything they started -out close to.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.withdraw(mover, target)[source]
      -

      Manages a character’s whole withdrawal, including changes in ranges to other characters.

      -
      -
      Parameters
      -
        -
      • mover (obj) – The object moving

      • -
      • target (obj) – The object to be moved away from

      • -
      -
      -
      -

      Notes

      -

      The mover will also automatically move away from objects that are close to the target -of their withdrawl. The mover will never inadvertently move toward anything else while -withdrawing - they can be considered to be moving to open space.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.combat_cleanup(character)[source]
      -

      Cleans up all the temporary combat-related attributes on a character.

      -
      -
      Parameters
      -

      character (obj) – Character to have their combat attributes removed

      -
      -
      -

      Notes

      -

      Any attribute whose key begins with ‘combat_’ is temporary and no -longer needed once a fight ends.

      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.is_in_combat(character)[source]
      -

      Returns true if the given character is in combat.

      -
      -
      Parameters
      -

      character (obj) – Character to determine if is in combat or not

      -
      -
      Returns
      -

      (bool) – True if in combat or False if not in combat

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.is_turn(character)[source]
      -

      Returns true if it’s currently the given character’s turn in combat.

      -
      -
      Parameters
      -

      character (obj) – Character to determine if it is their turn or not

      -
      -
      Returns
      -

      (bool) – True if it is their turn or False otherwise

      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.spend_action(character, actions, action_name=None)[source]
      -

      Spends a character’s available combat actions and checks for end of turn.

      -
      -
      Parameters
      -
        -
      • character (obj) – Character spending the action

      • -
      • actions (int) – Number of actions to spend, or ‘all’ to spend all actions

      • -
      -
      -
      Keyword Arguments
      -
        -
      • action_name (str or None) – If a string is given, sets character’s last action in

      • -
      • to provided string (combat) –

      • -
      -
      -
      -
      - -
      -
      -evennia.contrib.game_systems.turnbattle.tb_range.combat_status_message(fighter)[source]
      +
      +
      +combat_status_message(fighter)[source]

      Sends a message to a player with their current HP and distances to other fighters and objects. Called at turn start and by the ‘status’ command.

      +
      + +
      +
      +evennia.contrib.game_systems.turnbattle.tb_range.COMBAT_RULES = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      +
      class evennia.contrib.game_systems.turnbattle.tb_range.TBRangeTurnHandler(*args, **kwargs)[source]
      -

      Bases: evennia.scripts.scripts.DefaultScript

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler

      This is the script that handles the progression of combat through turns. On creation (when a fight is started) it adds all combat-ready characters to its roster and then sorts them into a turn order. There can only be one @@ -443,23 +324,10 @@ room as its object.

      Fights persist until only one participant is left with any HP or all remaining participants choose to end the combat with the ‘disengage’ command.

      -
      -
      -at_script_creation()[source]
      -

      Called once, when the script is created.

      -
      - -
      -
      -at_stop()[source]
      -

      Called at script termination.

      -
      - -
      -
      -at_repeat()[source]
      -

      Called once every self.interval seconds.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      @@ -489,17 +357,6 @@ command.

      -
      -
      -initialize_for_combat(character)[source]
      -

      Prepares a character for combat when starting or entering a fight.

      -
      -
      Parameters
      -

      character (obj) – Character to initialize for combat.

      -
      -
      -
      -
      start_turn(character)[source]
      @@ -516,23 +373,6 @@ characters to both move and attack in the same turn (or, alternately, move twice or attack twice).

      -
      -
      -next_turn()[source]
      -

      Advances to the next character in the turn order.

      -
      - -
      -
      -turn_end_check(character)[source]
      -

      Tests to see if a character’s turn is over, and cycles to the next turn if it is.

      -
      -
      Parameters
      -

      character (obj) – Character to test for end of turn

      -
      -
      -
      -
      join_fight(character)[source]
      @@ -547,13 +387,13 @@ move twice or attack twice).

      exception DoesNotExist
      -

      Bases: evennia.scripts.scripts.DefaultScript.DoesNotExist

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler.DoesNotExist

      exception MultipleObjectsReturned
      -

      Bases: evennia.scripts.scripts.DefaultScript.MultipleObjectsReturned

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler.MultipleObjectsReturned

      @@ -571,44 +411,24 @@ move twice or attack twice).

      class evennia.contrib.game_systems.turnbattle.tb_range.TBRangeCharacter(*args, **kwargs)[source]
      -

      Bases: evennia.objects.objects.DefaultCharacter

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter

      A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands.

      -
      -
      -at_object_creation()[source]
      -

      Called once, when this object is first created. This is the -normal hook to overload for most object types.

      -
      - -
      -
      -at_pre_move(destination)[source]
      -

      Called just before starting to move this object to -destination.

      -
      -
      Parameters
      -

      destination (Object) – The object we are moving to

      -
      -
      Returns
      -

      shouldmove (bool) – If we should move or not.

      -
      -
      -

      Notes

      -

      If this method returns False/None, the move is cancelled -before it is even started.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      exception DoesNotExist
      -

      Bases: evennia.objects.objects.DefaultCharacter.DoesNotExist

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter.DoesNotExist

      exception MultipleObjectsReturned
      -

      Bases: evennia.objects.objects.DefaultCharacter.MultipleObjectsReturned

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter.MultipleObjectsReturned

      @@ -783,7 +603,7 @@ permissions or the at_pre_give() hook for that.

      class evennia.contrib.game_systems.turnbattle.tb_range.CmdFight(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdFight

      Starts a fight with everyone in the same room as you.

      Usage:

      fight

      @@ -802,10 +622,15 @@ When it’s your turn, you can attack other characters.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      + +
      +
      +combat_handler_class
      +

      alias of TBRangeTurnHandler

      @@ -828,7 +653,7 @@ When it’s your turn, you can attack other characters.

      class evennia.contrib.game_systems.turnbattle.tb_range.CmdAttack(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdAttack

      Attacks another character in melee.

      Usage:

      attack <target>

      @@ -848,6 +673,11 @@ you. Use the ‘approach’ command to get closer to a target.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      +
      func()[source]
      @@ -895,6 +725,11 @@ nearby enemies.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      +
      func()[source]
      @@ -939,6 +774,11 @@ characters you are 0 spaces away from.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      +
      func()[source]
      @@ -982,6 +822,11 @@ characters you are 0 spaces away from.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      +
      func()[source]
      @@ -1008,7 +853,7 @@ characters you are 0 spaces away from.

      class evennia.contrib.game_systems.turnbattle.tb_range.CmdPass(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdPass

      Passes on your turn.

      Usage:

      pass

      @@ -1023,7 +868,7 @@ if there are still any actions you can take.

      -aliases = ['wait', 'hold']
      +aliases = ['hold', 'wait']
      @@ -1031,11 +876,10 @@ if there are still any actions you can take.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      @@ -1044,7 +888,7 @@ if there are still any actions you can take.

      -search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      +search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}
      @@ -1052,7 +896,7 @@ if there are still any actions you can take.

      class evennia.contrib.game_systems.turnbattle.tb_range.CmdDisengage(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdDisengage

      Passes your turn and attempts to end combat.

      Usage:

      disengage

      @@ -1076,11 +920,10 @@ fight ends.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      @@ -1097,7 +940,7 @@ fight ends.

      class evennia.contrib.game_systems.turnbattle.tb_range.CmdRest(**kwargs)[source]
      -

      Bases: evennia.commands.command.Command

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdRest

      Recovers damage.

      Usage:

      rest

      @@ -1115,11 +958,10 @@ rest if you’re not in a fight.

      help_category = 'combat'
      -
      -
      -func()[source]
      -

      This performs the actual command.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      @@ -1159,6 +1001,11 @@ other targets in combat.

      help_category = 'combat'
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      +
      func()[source]
      @@ -1185,7 +1032,7 @@ other targets in combat.

      class evennia.contrib.game_systems.turnbattle.tb_range.CmdCombatHelp(**kwargs)[source]
      -

      Bases: evennia.commands.default.help.CmdHelp

      +

      Bases: evennia.contrib.game_systems.turnbattle.tb_basic.CmdCombatHelp

      View help or a list of topics

      Usage:

      help <topic or command> @@ -1195,11 +1042,15 @@ help all

      This will search for help on commands and other topics related to the game.

      -
      -
      -func()[source]
      -

      Run the dynamic help entry creator.

      -
      +
      +
      +rules = <evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules object>
      +
      + +
      +
      +combat_help_text = 'Available combat commands:|/|wAttack:|n Attack an engaged target, attempting to deal damage.|/|wShoot:|n Attack from a distance, if not engaged with other fighters.|/|wApproach:|n Move one step cloer to a target.|/|wWithdraw:|n Move one step away from a target.|/|wPass:|n Pass your turn without further action.|/|wStatus:|n View current HP and ranges to other targets.|/|wDisengage:|n End your turn and attempt to end combat.|/'
      +
      @@ -1238,17 +1089,17 @@ topics related to the game.

      key = 'DefaultCharacter'
      +
      +
      +path = 'evennia.contrib.game_systems.turnbattle.tb_range.BattleCmdSet'
      +
      +
      at_cmdset_creation()[source]

      Populates the cmdset

      -
      -
      -path = 'evennia.contrib.game_systems.turnbattle.tb_range.BattleCmdSet'
      -
      -
    @@ -1293,7 +1144,6 @@ topics related to the game.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tests.html b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tests.html index 8617ea8a83..6baafa63db 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.turnbattle.tests.html @@ -267,7 +267,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.extended_room.extended_room.html b/docs/1.0-dev/api/evennia.contrib.grid.extended_room.extended_room.html index 7eb533864a..e051830f22 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.extended_room.extended_room.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.extended_room.extended_room.html @@ -545,7 +545,6 @@ self.add().

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.extended_room.html b/docs/1.0-dev/api/evennia.contrib.grid.extended_room.html index b3e07e5536..0ecfa6399e 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.extended_room.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.extended_room.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.extended_room.tests.html b/docs/1.0-dev/api/evennia.contrib.grid.extended_room.tests.html index 2844566b1d..4ac6bb5282 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.extended_room.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.extended_room.tests.html @@ -156,7 +156,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.html b/docs/1.0-dev/api/evennia.contrib.grid.html index d5dd077f00..b39e5b76b7 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.html @@ -146,7 +146,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.html b/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.html index 33ad0a655e..6c894e9d58 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.html @@ -90,7 +90,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.mapbuilder.html b/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.mapbuilder.html index 24fd3591d7..6926312e8b 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.mapbuilder.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.mapbuilder.html @@ -84,7 +84,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.tests.html b/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.tests.html index 0ac1415e54..ebae6a65db 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.mapbuilder.tests.html @@ -84,7 +84,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.html b/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.html index e2d440cfb9..717e7ef38e 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.simpledoor.html b/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.simpledoor.html index 6311fc1647..71cdc328f9 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.simpledoor.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.simpledoor.html @@ -308,7 +308,6 @@ self.add().

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.tests.html b/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.tests.html index 90f91edd7c..fddee8b898 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.simpledoor.tests.html @@ -96,7 +96,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.html b/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.html index 2cadc32c5f..8a7a617f08 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.html @@ -98,7 +98,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.slow_exit.html b/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.slow_exit.html index 871135a59b..2a889e6b5c 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.slow_exit.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.slow_exit.html @@ -282,7 +282,6 @@ self.add().

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.tests.html b/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.tests.html index 24c18b46a2..408bb57b83 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.slow_exit.tests.html @@ -96,7 +96,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.wilderness.html b/docs/1.0-dev/api/evennia.contrib.grid.wilderness.html index 6b59922478..c2d321f10d 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.wilderness.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.wilderness.html @@ -97,7 +97,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.wilderness.tests.html b/docs/1.0-dev/api/evennia.contrib.grid.wilderness.tests.html index fe008aaa5d..39a0b27cc3 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.wilderness.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.wilderness.tests.html @@ -142,7 +142,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.wilderness.wilderness.html b/docs/1.0-dev/api/evennia.contrib.grid.wilderness.wilderness.html index 3f75b138e9..ac983a8e66 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.wilderness.wilderness.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.wilderness.wilderness.html @@ -718,7 +718,6 @@ coordinate.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.commands.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.commands.html index 82022ef449..92ceb9de12 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.commands.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.commands.html @@ -49,33 +49,33 @@ the commands with XYZ-aware equivalents.

    class evennia.contrib.grid.xyzgrid.commands.PathData(target, xymap, directions, step_sequence, task)

    Bases: tuple

    -
    +
    -property directions
    +directions

    Alias for field number 2

    -
    +
    -property step_sequence
    +step_sequence

    Alias for field number 3

    -
    +
    -property target
    +target

    Alias for field number 0

    -
    +
    -property task
    +task

    Alias for field number 4

    -
    +
    -property xymap
    +xymap

    Alias for field number 1

    @@ -384,7 +384,6 @@ self.add().

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.example.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.example.html index 394e0c8450..dfa19a1028 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.example.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.example.html @@ -132,7 +132,6 @@ into a room by only acts as a target for finding the exit’s destination.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.html index 6412d88020..7c09912794 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.html @@ -105,7 +105,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.launchcmd.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.launchcmd.html index 99eb088dab..d0c108ba47 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.launchcmd.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.launchcmd.html @@ -102,7 +102,6 @@ once added to settings.EXTRA_LAUNCHER_COMMANDS.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.prototypes.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.prototypes.html index 67a660eeea..aadf08494d 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.prototypes.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.prototypes.html @@ -97,7 +97,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.tests.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.tests.html index a6497da6fb..880f73248d 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.tests.html @@ -1362,7 +1362,6 @@ different visibility distances.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.utils.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.utils.html index 92aa67371a..61026a3b0a 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.utils.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.utils.html @@ -111,7 +111,6 @@ leads to another map.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xymap.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xymap.html index 8950158c48..8ff70b229e 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xymap.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xymap.html @@ -523,7 +523,6 @@ weights and may also show nodes not actually reachable at the moment:

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xymap_legend.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xymap_legend.html index f5a1116e9f..5ecfd45150 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xymap_legend.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xymap_legend.html @@ -1273,7 +1273,6 @@ one-way link out of the teleporter on one side.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xyzgrid.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xyzgrid.html index 7479f232d1..13de2f9ecb 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xyzgrid.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xyzgrid.html @@ -316,7 +316,6 @@ previously exist.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xyzroom.html b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xyzroom.html index a8519d571e..84696f1536 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xyzroom.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.xyzgrid.xyzroom.html @@ -515,7 +515,6 @@ be any room (including non-XYRooms) and is not checked for XYZ coordinates.

    <

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.html b/docs/1.0-dev/api/evennia.contrib.html index 89d0fec8a1..75476b4ecc 100644 --- a/docs/1.0-dev/api/evennia.contrib.html +++ b/docs/1.0-dev/api/evennia.contrib.html @@ -392,7 +392,6 @@ useful but are deemed too game-specific to go into the core library.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html b/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html index d7751b4d96..4789101ad1 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html @@ -234,7 +234,7 @@ everyone but the person rolling.

    -aliases = ['roll', '@dice']
    +aliases = ['@dice', 'roll']
    @@ -260,7 +260,7 @@ everyone but the person rolling.

    -search_index_entry = {'aliases': 'roll @dice', 'category': 'general', 'key': 'dice', 'no_prefix': ' roll dice', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}
    +search_index_entry = {'aliases': '@dice roll', 'category': 'general', 'key': 'dice', 'no_prefix': ' dice roll', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}
    @@ -337,7 +337,6 @@ Add with @py self.cmdset.add(“contrib.dice.DiceCmdSet”)

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.dice.html b/docs/1.0-dev/api/evennia.contrib.rpg.dice.html index bd399e91e2..a2d6a3f539 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.dice.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.dice.html @@ -96,7 +96,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.dice.tests.html b/docs/1.0-dev/api/evennia.contrib.rpg.dice.tests.html index cf2c39afd1..20c48b0352 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.dice.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.dice.tests.html @@ -101,7 +101,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.health_bar.html b/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.health_bar.html index 0b765b2aab..244cd1c8df 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.health_bar.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.health_bar.html @@ -149,7 +149,6 @@ readers will be unable to read the graphical aspect of the bar.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.html b/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.html index a22ac2585a..1055df0703 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.tests.html b/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.tests.html index 17310ae681..ac072de188 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.health_bar.tests.html @@ -96,7 +96,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.html b/docs/1.0-dev/api/evennia.contrib.rpg.html index dbe504fe34..71fd1522d2 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.html @@ -135,7 +135,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.html b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.html index 4e6f15090f..0f05c65bad 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.html @@ -92,7 +92,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rplanguage.html b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rplanguage.html index b060f51799..8f9e318550 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rplanguage.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rplanguage.html @@ -411,7 +411,6 @@ means fully obscured.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rpsystem.html b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rpsystem.html index 20ab26036c..dfdafc5d23 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rpsystem.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rpsystem.html @@ -1375,7 +1375,6 @@ the evennia.contrib.rpg.rplanguage module.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.tests.html b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.tests.html index d19a757c9b..d1118a3e0b 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.tests.html @@ -203,7 +203,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.traits.html b/docs/1.0-dev/api/evennia.contrib.rpg.traits.html index 28ef7c3cbb..8a5ad08ae3 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.traits.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.traits.html @@ -112,7 +112,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.traits.tests.html b/docs/1.0-dev/api/evennia.contrib.rpg.traits.tests.html index 7c0de83a9f..63c7ade77f 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.traits.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.traits.tests.html @@ -578,7 +578,6 @@ under the hood.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.traits.traits.html b/docs/1.0-dev/api/evennia.contrib.rpg.traits.traits.html index c26cb535fa..741c22a34a 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.traits.traits.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.traits.traits.html @@ -1058,7 +1058,6 @@ returned.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.batchprocessor.example_batch_code.html b/docs/1.0-dev/api/evennia.contrib.tutorials.batchprocessor.example_batch_code.html index 1e8a05b3ae..86abe44939 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.batchprocessor.example_batch_code.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.batchprocessor.example_batch_code.html @@ -84,7 +84,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.batchprocessor.html b/docs/1.0-dev/api/evennia.contrib.tutorials.batchprocessor.html index d4f8cd5d7e..089f86d3d2 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.batchprocessor.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.batchprocessor.html @@ -90,7 +90,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.bodyfunctions.html b/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.bodyfunctions.html index 2d0217635a..c499fc0d96 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.bodyfunctions.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.bodyfunctions.html @@ -141,7 +141,6 @@ a random check here so as to only return 33% of the time.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.html b/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.html index f6f4d93383..a3991ff1da 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.tests.html b/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.tests.html index 7fd106f7d6..ede8f66b94 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.bodyfunctions.tests.html @@ -121,7 +121,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.html b/docs/1.0-dev/api/evennia.contrib.tutorials.html index 0855f3a366..51239365f9 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.html @@ -122,7 +122,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.mirror.html b/docs/1.0-dev/api/evennia.contrib.tutorials.mirror.html index 414e2de456..fd1662f954 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.mirror.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.mirror.html @@ -90,7 +90,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.mirror.mirror.html b/docs/1.0-dev/api/evennia.contrib.tutorials.mirror.mirror.html index 7a541d07b8..a6baacb6c8 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.mirror.mirror.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.mirror.mirror.html @@ -155,7 +155,6 @@ on all entities in it.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.html b/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.html index 51f7581c06..8e6c6c0daa 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.html @@ -93,7 +93,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html b/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html index 3926eb0381..4380cb6624 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html @@ -84,7 +84,7 @@ such as when closing the lid and un-blinding a character.

    -aliases = ['press button', 'push', 'press']
    +aliases = ['push', 'press button', 'press']
    @@ -113,7 +113,7 @@ check if the lid is open or closed.

    -search_index_entry = {'aliases': 'press button push press', 'category': 'general', 'key': 'push button', 'no_prefix': ' press button push press', 'tags': '', 'text': '\n Push the red button (lid closed)\n\n Usage:\n push button\n\n '}
    +search_index_entry = {'aliases': 'push press button press', 'category': 'general', 'key': 'push button', 'no_prefix': ' push press button press', 'tags': '', 'text': '\n Push the red button (lid closed)\n\n Usage:\n push button\n\n '}
    @@ -183,7 +183,7 @@ check if the lid is open or closed.

    -aliases = ['smash', 'smash lid', 'break lid']
    +aliases = ['break lid', 'smash lid', 'smash']
    @@ -210,7 +210,7 @@ break.

    -search_index_entry = {'aliases': 'smash smash lid break lid', 'category': 'general', 'key': 'smash glass', 'no_prefix': ' smash smash lid break lid', 'tags': '', 'text': '\n Smash the protective glass.\n\n Usage:\n smash glass\n\n Try to smash the glass of the button.\n\n '}
    +search_index_entry = {'aliases': 'break lid smash lid smash', 'category': 'general', 'key': 'smash glass', 'no_prefix': ' break lid smash lid smash', 'tags': '', 'text': '\n Smash the protective glass.\n\n Usage:\n smash glass\n\n Try to smash the glass of the button.\n\n '}
    @@ -310,7 +310,7 @@ be mutually exclusive.

    -aliases = ['press button', 'push', 'press']
    +aliases = ['push', 'press button', 'press']
    @@ -339,7 +339,7 @@ set in self.parse())

    -search_index_entry = {'aliases': 'press button push press', 'category': 'general', 'key': 'push button', 'no_prefix': ' press button push press', 'tags': '', 'text': '\n Push the red button\n\n Usage:\n push button\n\n '}
    +search_index_entry = {'aliases': 'push press button press', 'category': 'general', 'key': 'push button', 'no_prefix': ' push press button press', 'tags': '', 'text': '\n Push the red button\n\n Usage:\n push button\n\n '}
    @@ -437,7 +437,7 @@ be mutually exclusive.

    -aliases = ['l', 'ex', 'examine', 'get', 'listen', 'feel']
    +aliases = ['get', 'examine', 'feel', 'listen', 'l', 'ex']
    @@ -463,7 +463,7 @@ be mutually exclusive.

    -search_index_entry = {'aliases': 'l ex examine get listen feel', 'category': 'general', 'key': 'look', 'no_prefix': ' l ex examine get listen feel', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}
    +search_index_entry = {'aliases': 'get examine feel listen l ex', 'category': 'general', 'key': 'look', 'no_prefix': ' get examine feel listen l ex', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}
    @@ -747,7 +747,6 @@ temporarily.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.html b/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.html index 43b6854d6a..e9f19ee574 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.talking_npc.html b/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.talking_npc.html index 5471719588..7b55b0bed0 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.talking_npc.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.talking_npc.html @@ -233,7 +233,6 @@ the conversation defined above.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.tests.html b/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.tests.html index 6f47835157..3c4215c29c 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.talking_npc.tests.html @@ -96,7 +96,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.html index 61202b9ff4..8e605a4992 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.html @@ -94,7 +94,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.intro_menu.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.intro_menu.html index b58eeb2afc..c982921e0a 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.intro_menu.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.intro_menu.html @@ -275,7 +275,6 @@ option related to this node.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.mob.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.mob.html index 8b04a81b9a..e0633d38d8 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.mob.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.mob.html @@ -324,7 +324,6 @@ right away, also when patrolling on a very slow ticker.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html index 9e68179201..7a73ba3fac 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html @@ -364,7 +364,7 @@ of the object. We overload it with our own version.

    -aliases = ['light', 'burn']
    +aliases = ['burn', 'light']
    @@ -391,7 +391,7 @@ to sit on a “lightable” object, we operate only on self.obj.

    -search_index_entry = {'aliases': 'light burn', 'category': 'tutorialworld', 'key': 'on', 'no_prefix': ' light burn', 'tags': '', 'text': '\n Creates light where there was none. Something to burn.\n '}
    +search_index_entry = {'aliases': 'burn light', 'category': 'tutorialworld', 'key': 'on', 'no_prefix': ' burn light', 'tags': '', 'text': '\n Creates light where there was none. Something to burn.\n '}
    @@ -495,7 +495,7 @@ shift green root up/down

    -aliases = ['pull', 'shiftroot', 'push', 'move']
    +aliases = ['pull', 'push', 'move', 'shiftroot']
    @@ -531,7 +531,7 @@ yellow/green - horizontal roots

    -search_index_entry = {'aliases': 'pull shiftroot push move', 'category': 'tutorialworld', 'key': 'shift', 'no_prefix': ' pull shiftroot push move', 'tags': '', 'text': '\n Shifts roots around.\n\n Usage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n '}
    +search_index_entry = {'aliases': 'pull push move shiftroot', 'category': 'tutorialworld', 'key': 'shift', 'no_prefix': ' pull push move shiftroot', 'tags': '', 'text': '\n Shifts roots around.\n\n Usage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n '}
    @@ -548,7 +548,7 @@ yellow/green - horizontal roots

    -aliases = ['push button', 'button', 'press button']
    +aliases = ['push button', 'press button', 'button']
    @@ -574,7 +574,7 @@ yellow/green - horizontal roots

    -search_index_entry = {'aliases': 'push button button press button', 'category': 'tutorialworld', 'key': 'press', 'no_prefix': ' push button button press button', 'tags': '', 'text': '\n Presses a button.\n '}
    +search_index_entry = {'aliases': 'push button press button button', 'category': 'tutorialworld', 'key': 'press', 'no_prefix': ' push button press button button', 'tags': '', 'text': '\n Presses a button.\n '}
    @@ -718,7 +718,7 @@ parry - forgoes your attack but will make you harder to hit on next

    -aliases = ['thrust', 'bash', 'stab', 'defend', 'fight', 'hit', 'chop', 'slash', 'pierce', 'kill', 'parry']
    +aliases = ['defend', 'chop', 'slash', 'hit', 'thrust', 'stab', 'fight', 'kill', 'parry', 'bash', 'pierce']
    @@ -744,7 +744,7 @@ parry - forgoes your attack but will make you harder to hit on next

    -search_index_entry = {'aliases': 'thrust bash stab defend fight hit chop slash pierce kill parry', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' thrust bash stab defend fight hit chop slash pierce kill parry', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}
    +search_index_entry = {'aliases': 'defend chop slash hit thrust stab fight kill parry bash pierce', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' defend chop slash hit thrust stab fight kill parry bash pierce', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}
    @@ -990,7 +990,6 @@ pulling weapons from it indefinitely.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html index dbfc1fc10e..09e4f2678b 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html @@ -361,6 +361,45 @@ at the given detailkey.

    +
    +
    +class evennia.contrib.tutorials.tutorial_world.rooms.TutorialStartExit(*args, **kwargs)[source]
    +

    Bases: evennia.objects.objects.DefaultExit

    +

    This is like a normal exit except it makes the intro command available +on itself. We put it on the exit in order to provide this command to the +Limbo room without modifying Limbo itself - deleting the tutorial exit +will also clean up the intro command.

    +
    +
    +at_object_creation()[source]
    +

    Called once, when this object is first created. This is the +normal hook to overload for most object types.

    +
    + +
    +
    +exception DoesNotExist
    +

    Bases: evennia.objects.objects.DefaultExit.DoesNotExist

    +
    + +
    +
    +exception MultipleObjectsReturned
    +

    Bases: evennia.objects.objects.DefaultExit.MultipleObjectsReturned

    +
    + +
    +
    +path = 'evennia.contrib.tutorials.tutorial_world.rooms.TutorialStartExit'
    +
    + +
    +
    +typename = 'TutorialStartExit'
    +
    + +
    +
    class evennia.contrib.tutorials.tutorial_world.rooms.WeatherRoom(*args, **kwargs)[source]
    @@ -716,7 +755,7 @@ if they fall off the bridge.

    -aliases = ['?', 'h']
    +aliases = ['h', '?']
    @@ -742,7 +781,7 @@ if they fall off the bridge.

    -search_index_entry = {'aliases': '? h', 'category': 'tutorial world', 'key': 'help', 'no_prefix': ' ? h', 'tags': '', 'text': '\n Overwritten help command while on the bridge.\n '}
    +search_index_entry = {'aliases': 'h ?', 'category': 'tutorial world', 'key': 'help', 'no_prefix': ' h ?', 'tags': '', 'text': '\n Overwritten help command while on the bridge.\n '}
    @@ -868,7 +907,7 @@ to find something.

    -aliases = ['l', 'search', 'fiddle', 'feel', 'feel around']
    +aliases = ['feel around', 'fiddle', 'search', 'feel', 'l']
    @@ -896,7 +935,7 @@ random chance of eventually finding a light source.

    -search_index_entry = {'aliases': 'l search fiddle feel feel around', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' l search fiddle feel feel around', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}
    +search_index_entry = {'aliases': 'feel around fiddle search feel l', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' feel around fiddle search feel l', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}
    @@ -1257,7 +1296,6 @@ overriding the call (unused by default).

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.tests.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.tests.html index 0ac34d8615..c78100e5df 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.tests.html @@ -183,7 +183,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.auditing.html b/docs/1.0-dev/api/evennia.contrib.utils.auditing.html index 41fa77d5e0..83209aff3b 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.auditing.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.auditing.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.auditing.outputs.html b/docs/1.0-dev/api/evennia.contrib.utils.auditing.outputs.html index c4f634c50f..9ec52fa95e 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.auditing.outputs.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.auditing.outputs.html @@ -126,7 +126,6 @@ compromised or taken down, losing your logs along with it is no help!).

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.auditing.server.html b/docs/1.0-dev/api/evennia.contrib.utils.auditing.server.html index a4657ee96b..afc7063efe 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.auditing.server.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.auditing.server.html @@ -165,7 +165,6 @@ writing to log. Recording cleartext password attempts is bad policy.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.auditing.tests.html b/docs/1.0-dev/api/evennia.contrib.utils.auditing.tests.html index 1b257294ac..d6d502832b 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.auditing.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.auditing.tests.html @@ -111,7 +111,6 @@ parsed from the Session object.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.fieldfill.fieldfill.html b/docs/1.0-dev/api/evennia.contrib.utils.fieldfill.fieldfill.html index 1297593a50..566e9e923b 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.fieldfill.fieldfill.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.fieldfill.fieldfill.html @@ -444,7 +444,6 @@ send

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.fieldfill.html b/docs/1.0-dev/api/evennia.contrib.utils.fieldfill.html index 668d35bec4..f7a98df857 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.fieldfill.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.fieldfill.html @@ -90,7 +90,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.html b/docs/1.0-dev/api/evennia.contrib.utils.html index 0a628262c4..a27aa1e4ff 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.html @@ -108,7 +108,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.html b/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.html index ac798ab4f6..3b43387ac5 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.random_string_generator.html b/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.random_string_generator.html index a281b1bbb1..9ec85aab93 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.random_string_generator.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.random_string_generator.html @@ -299,7 +299,6 @@ calling the get method.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.tests.html b/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.tests.html index 20e129577b..b3430611da 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.random_string_generator.tests.html @@ -97,7 +97,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.tree_select.html b/docs/1.0-dev/api/evennia.contrib.utils.tree_select.html index 8eaf9dbffc..cf414658df 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.tree_select.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.tree_select.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.tree_select.tests.html b/docs/1.0-dev/api/evennia.contrib.utils.tree_select.tests.html index cc85380fc7..1b6b022820 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.tree_select.tests.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.tree_select.tests.html @@ -107,7 +107,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.contrib.utils.tree_select.tree_select.html b/docs/1.0-dev/api/evennia.contrib.utils.tree_select.tree_select.html index c15c680644..4002148217 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.tree_select.tree_select.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.tree_select.tree_select.html @@ -439,7 +439,6 @@ to determine the color the player chose.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.help.filehelp.html b/docs/1.0-dev/api/evennia.help.filehelp.html index b24e7c86b9..fd505fecc9 100644 --- a/docs/1.0-dev/api/evennia.help.filehelp.html +++ b/docs/1.0-dev/api/evennia.help.filehelp.html @@ -282,7 +282,6 @@ return a list of **FileHelpEntry.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.help.html b/docs/1.0-dev/api/evennia.help.html index b26422a030..aaaf73694e 100644 --- a/docs/1.0-dev/api/evennia.help.html +++ b/docs/1.0-dev/api/evennia.help.html @@ -96,7 +96,6 @@ itself.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.help.manager.html b/docs/1.0-dev/api/evennia.help.manager.html index 4193b68ee8..9da1530f80 100644 --- a/docs/1.0-dev/api/evennia.help.manager.html +++ b/docs/1.0-dev/api/evennia.help.manager.html @@ -250,7 +250,6 @@ in-game setting information and so on.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.help.models.html b/docs/1.0-dev/api/evennia.help.models.html index 38878949fd..ea29ec64af 100644 --- a/docs/1.0-dev/api/evennia.help.models.html +++ b/docs/1.0-dev/api/evennia.help.models.html @@ -413,7 +413,6 @@ object the first time, the query is executed.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.help.utils.html b/docs/1.0-dev/api/evennia.help.utils.html index d253e9a15a..7c6aca3b79 100644 --- a/docs/1.0-dev/api/evennia.help.utils.html +++ b/docs/1.0-dev/api/evennia.help.utils.html @@ -186,7 +186,6 @@ followed by any sub-sub-categories down to a max-depth of 5.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.html b/docs/1.0-dev/api/evennia.html index f862d400e6..0bf185b055 100644 --- a/docs/1.0-dev/api/evennia.html +++ b/docs/1.0-dev/api/evennia.html @@ -739,7 +739,6 @@ with ‘q’, remove the break line and restart server when finished.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.locks.html b/docs/1.0-dev/api/evennia.locks.html index e98ebb6ffa..e400777f91 100644 --- a/docs/1.0-dev/api/evennia.locks.html +++ b/docs/1.0-dev/api/evennia.locks.html @@ -93,7 +93,6 @@ also contains the default lock functions used in lock definitions.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.locks.lockfuncs.html b/docs/1.0-dev/api/evennia.locks.lockfuncs.html index fd02df55e5..73de9e1cad 100644 --- a/docs/1.0-dev/api/evennia.locks.lockfuncs.html +++ b/docs/1.0-dev/api/evennia.locks.lockfuncs.html @@ -437,7 +437,6 @@ unpacked to their real value. We only support basic properties.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.locks.lockhandler.html b/docs/1.0-dev/api/evennia.locks.lockhandler.html index 0177bde535..9027206385 100644 --- a/docs/1.0-dev/api/evennia.locks.lockhandler.html +++ b/docs/1.0-dev/api/evennia.locks.lockhandler.html @@ -456,7 +456,6 @@ among the locks defined by lockstring.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.objects.html b/docs/1.0-dev/api/evennia.objects.html index b2fb0bd7cc..24879f8b72 100644 --- a/docs/1.0-dev/api/evennia.objects.html +++ b/docs/1.0-dev/api/evennia.objects.html @@ -93,7 +93,6 @@ objects inherit from classes in this package.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.objects.manager.html b/docs/1.0-dev/api/evennia.objects.manager.html index e473f5981d..f894acaeac 100644 --- a/docs/1.0-dev/api/evennia.objects.manager.html +++ b/docs/1.0-dev/api/evennia.objects.manager.html @@ -492,7 +492,6 @@ adding this rarely makes sense since this data will not survive a reload.

    Versions diff --git a/docs/1.0-dev/api/evennia.objects.models.html b/docs/1.0-dev/api/evennia.objects.models.html index ecc8d23fc2..51ca471214 100644 --- a/docs/1.0-dev/api/evennia.objects.models.html +++ b/docs/1.0-dev/api/evennia.objects.models.html @@ -320,6 +320,13 @@ instances.

    class built by **create_forward_many_to_many_manager()** defined below.

    +
    +
    +db_date_created
    +

    A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

    +
    +
    db_destination_id
    @@ -330,11 +337,25 @@ class built by **create_forward_many_to_many_manager()** define db_home_id
    +
    +
    +db_key
    +

    A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

    +
    +
    db_location_id
    +
    +
    +db_lock_storage
    +

    A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

    +
    +
    db_tags
    @@ -351,6 +372,13 @@ instances.

    class built by **create_forward_many_to_many_manager()** defined below.

    +
    +
    +db_typeclass_path
    +

    A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

    +
    +
    property destination
    @@ -564,7 +592,6 @@ class built by **create_forward_many_to_many_manager()** define

    Versions

    diff --git a/docs/1.0-dev/api/evennia.objects.objects.html b/docs/1.0-dev/api/evennia.objects.objects.html index a8231fd02c..7b7a279754 100644 --- a/docs/1.0-dev/api/evennia.objects.objects.html +++ b/docs/1.0-dev/api/evennia.objects.objects.html @@ -2243,7 +2243,6 @@ read for an error string instead.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.prototypes.html b/docs/1.0-dev/api/evennia.prototypes.html index 93259aee17..bf9d4797b1 100644 --- a/docs/1.0-dev/api/evennia.prototypes.html +++ b/docs/1.0-dev/api/evennia.prototypes.html @@ -92,7 +92,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.prototypes.menus.html b/docs/1.0-dev/api/evennia.prototypes.menus.html index 4d4ddd99b0..e45e7acd98 100644 --- a/docs/1.0-dev/api/evennia.prototypes.menus.html +++ b/docs/1.0-dev/api/evennia.prototypes.menus.html @@ -197,7 +197,6 @@ prototype rather than creating a new one.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.prototypes.protfuncs.html b/docs/1.0-dev/api/evennia.prototypes.protfuncs.html index 987f5a5e78..3adcb57df7 100644 --- a/docs/1.0-dev/api/evennia.prototypes.protfuncs.html +++ b/docs/1.0-dev/api/evennia.prototypes.protfuncs.html @@ -121,7 +121,6 @@ Returns the value of another key in this prototoype. Will raise an error if

    Versions

    diff --git a/docs/1.0-dev/api/evennia.prototypes.prototypes.html b/docs/1.0-dev/api/evennia.prototypes.prototypes.html index b32aaaa090..895f124cb7 100644 --- a/docs/1.0-dev/api/evennia.prototypes.prototypes.html +++ b/docs/1.0-dev/api/evennia.prototypes.prototypes.html @@ -518,7 +518,6 @@ validator (callable, optional): If given, this will be called with the value to<

    Versions

    diff --git a/docs/1.0-dev/api/evennia.prototypes.spawner.html b/docs/1.0-dev/api/evennia.prototypes.spawner.html index 1ec79e01e6..be8946c91f 100644 --- a/docs/1.0-dev/api/evennia.prototypes.spawner.html +++ b/docs/1.0-dev/api/evennia.prototypes.spawner.html @@ -522,7 +522,6 @@ custom prototype_parents are given to this function.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.scripts.html b/docs/1.0-dev/api/evennia.scripts.html index e1da70204d..2e5b47ccd3 100644 --- a/docs/1.0-dev/api/evennia.scripts.html +++ b/docs/1.0-dev/api/evennia.scripts.html @@ -99,7 +99,6 @@ timed effects.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.scripts.manager.html b/docs/1.0-dev/api/evennia.scripts.manager.html index bccc0b2a67..99df9f918f 100644 --- a/docs/1.0-dev/api/evennia.scripts.manager.html +++ b/docs/1.0-dev/api/evennia.scripts.manager.html @@ -281,7 +281,6 @@ scripts in the database.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.scripts.models.html b/docs/1.0-dev/api/evennia.scripts.models.html index c7309767fe..b5d1d5e82b 100644 --- a/docs/1.0-dev/api/evennia.scripts.models.html +++ b/docs/1.0-dev/api/evennia.scripts.models.html @@ -218,6 +218,27 @@ instances.

    class built by **create_forward_many_to_many_manager()** defined below.

    +
    +
    +db_date_created
    +

    A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

    +
    + +
    +
    +db_key
    +

    A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

    +
    + +
    +
    +db_lock_storage
    +

    A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

    +
    +
    db_obj_id
    @@ -239,6 +260,13 @@ instances.

    class built by **create_forward_many_to_many_manager()** defined below.

    +
    +
    +db_typeclass_path
    +

    A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

    +
    +
    property desc
    @@ -378,7 +406,6 @@ class built by **create_forward_many_to_many_manager()** define

    Versions

    diff --git a/docs/1.0-dev/api/evennia.scripts.monitorhandler.html b/docs/1.0-dev/api/evennia.scripts.monitorhandler.html index fc597733a1..cdb6cd1796 100644 --- a/docs/1.0-dev/api/evennia.scripts.monitorhandler.html +++ b/docs/1.0-dev/api/evennia.scripts.monitorhandler.html @@ -203,7 +203,6 @@ all kwargs must be possible to pickle!

    Versions

    diff --git a/docs/1.0-dev/api/evennia.scripts.scripthandler.html b/docs/1.0-dev/api/evennia.scripts.scripthandler.html index def23f6382..5a5f2a77bf 100644 --- a/docs/1.0-dev/api/evennia.scripts.scripthandler.html +++ b/docs/1.0-dev/api/evennia.scripts.scripthandler.html @@ -185,7 +185,6 @@ If no key is given, delete all scripts on the object!

    Versions

    diff --git a/docs/1.0-dev/api/evennia.scripts.scripts.html b/docs/1.0-dev/api/evennia.scripts.scripts.html index a2e229d370..f96e4dfdff 100644 --- a/docs/1.0-dev/api/evennia.scripts.scripts.html +++ b/docs/1.0-dev/api/evennia.scripts.scripts.html @@ -305,7 +305,6 @@ could be used).

    Versions

    diff --git a/docs/1.0-dev/api/evennia.scripts.taskhandler.html b/docs/1.0-dev/api/evennia.scripts.taskhandler.html index d6c1c8548e..c764290a4a 100644 --- a/docs/1.0-dev/api/evennia.scripts.taskhandler.html +++ b/docs/1.0-dev/api/evennia.scripts.taskhandler.html @@ -563,7 +563,6 @@ This method should be automatically called when Evennia starts.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.scripts.tickerhandler.html b/docs/1.0-dev/api/evennia.scripts.tickerhandler.html index 26ffab0a37..5cd1770913 100644 --- a/docs/1.0-dev/api/evennia.scripts.tickerhandler.html +++ b/docs/1.0-dev/api/evennia.scripts.tickerhandler.html @@ -428,7 +428,6 @@ non-db objects.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.amp_client.html b/docs/1.0-dev/api/evennia.server.amp_client.html index 70a8f900ef..c2da2f2027 100644 --- a/docs/1.0-dev/api/evennia.server.amp_client.html +++ b/docs/1.0-dev/api/evennia.server.amp_client.html @@ -266,7 +266,6 @@ operation, as defined by the global variables in

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.connection_wizard.html b/docs/1.0-dev/api/evennia.server.connection_wizard.html index 2bb2cf7e87..4e4595d722 100644 --- a/docs/1.0-dev/api/evennia.server.connection_wizard.html +++ b/docs/1.0-dev/api/evennia.server.connection_wizard.html @@ -205,7 +205,6 @@ fails (and is expected to echo why if so).

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.deprecations.html b/docs/1.0-dev/api/evennia.server.deprecations.html index 804ca65925..52a3839aef 100644 --- a/docs/1.0-dev/api/evennia.server.deprecations.html +++ b/docs/1.0-dev/api/evennia.server.deprecations.html @@ -109,7 +109,6 @@ does not stop launch.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.evennia_launcher.html b/docs/1.0-dev/api/evennia.server.evennia_launcher.html index b5b57ed549..f7dfcbb766 100644 --- a/docs/1.0-dev/api/evennia.server.evennia_launcher.html +++ b/docs/1.0-dev/api/evennia.server.evennia_launcher.html @@ -612,7 +612,6 @@ in the terminal/console, for example:

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.game_index_client.client.html b/docs/1.0-dev/api/evennia.server.game_index_client.client.html index 412d5ff9d1..3342b10276 100644 --- a/docs/1.0-dev/api/evennia.server.game_index_client.client.html +++ b/docs/1.0-dev/api/evennia.server.game_index_client.client.html @@ -195,7 +195,6 @@ to this Protocol. The connection has been closed.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.game_index_client.html b/docs/1.0-dev/api/evennia.server.game_index_client.html index c7fe84f4b3..5c71cdf37a 100644 --- a/docs/1.0-dev/api/evennia.server.game_index_client.html +++ b/docs/1.0-dev/api/evennia.server.game_index_client.html @@ -90,7 +90,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.game_index_client.service.html b/docs/1.0-dev/api/evennia.server.game_index_client.service.html index 3cafcb129a..9198f6d60a 100644 --- a/docs/1.0-dev/api/evennia.server.game_index_client.service.html +++ b/docs/1.0-dev/api/evennia.server.game_index_client.service.html @@ -114,7 +114,6 @@ to the Evennia Game Index.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.html b/docs/1.0-dev/api/evennia.server.html index 05b6e06258..91db58c7ed 100644 --- a/docs/1.0-dev/api/evennia.server.html +++ b/docs/1.0-dev/api/evennia.server.html @@ -151,7 +151,6 @@ to connect to the game.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.initial_setup.html b/docs/1.0-dev/api/evennia.server.initial_setup.html index dd46b2a89c..05b4a50e26 100644 --- a/docs/1.0-dev/api/evennia.server.initial_setup.html +++ b/docs/1.0-dev/api/evennia.server.initial_setup.html @@ -132,7 +132,6 @@ the function will exit immediately.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.inputfuncs.html b/docs/1.0-dev/api/evennia.server.inputfuncs.html index 3b59e621b7..8f2ab85a98 100644 --- a/docs/1.0-dev/api/evennia.server.inputfuncs.html +++ b/docs/1.0-dev/api/evennia.server.inputfuncs.html @@ -393,7 +393,6 @@ common clients.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.manager.html b/docs/1.0-dev/api/evennia.server.manager.html index d24a9a8406..51282da189 100644 --- a/docs/1.0-dev/api/evennia.server.manager.html +++ b/docs/1.0-dev/api/evennia.server.manager.html @@ -122,7 +122,6 @@ value (str): If key was given, this is the stored value, or

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.models.html b/docs/1.0-dev/api/evennia.server.models.html index cd0cf7790c..cd7f9a99ce 100644 --- a/docs/1.0-dev/api/evennia.server.models.html +++ b/docs/1.0-dev/api/evennia.server.models.html @@ -176,7 +176,6 @@ object the first time, the query is executed.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.amp.html b/docs/1.0-dev/api/evennia.server.portal.amp.html index 2c5346078a..9e7c34c064 100644 --- a/docs/1.0-dev/api/evennia.server.portal.amp.html +++ b/docs/1.0-dev/api/evennia.server.portal.amp.html @@ -559,7 +559,6 @@ function call

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.amp_server.html b/docs/1.0-dev/api/evennia.server.portal.amp_server.html index 28dff9aa19..782671cfbd 100644 --- a/docs/1.0-dev/api/evennia.server.portal.amp_server.html +++ b/docs/1.0-dev/api/evennia.server.portal.amp_server.html @@ -307,7 +307,6 @@ global variables in evennia/server/amp.py.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.grapevine.html b/docs/1.0-dev/api/evennia.server.portal.grapevine.html index 12036b52af..e64bb6cae4 100644 --- a/docs/1.0-dev/api/evennia.server.portal.grapevine.html +++ b/docs/1.0-dev/api/evennia.server.portal.grapevine.html @@ -304,7 +304,6 @@ disconnect this protocol.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.html b/docs/1.0-dev/api/evennia.server.portal.html index 928f616e77..dc8e1a9dfd 100644 --- a/docs/1.0-dev/api/evennia.server.portal.html +++ b/docs/1.0-dev/api/evennia.server.portal.html @@ -109,7 +109,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.irc.html b/docs/1.0-dev/api/evennia.server.portal.irc.html index f2b887a8c6..3f6806e4f5 100644 --- a/docs/1.0-dev/api/evennia.server.portal.irc.html +++ b/docs/1.0-dev/api/evennia.server.portal.irc.html @@ -96,7 +96,7 @@ as sends text to it when prompted

    -factory: Optional[twisted.internet.protocol.Factory] = None
    +factory: Optional[twisted.internet.protocol.Factory, None] = None
    @@ -420,7 +420,6 @@ sessions.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.mccp.html b/docs/1.0-dev/api/evennia.server.portal.mccp.html index 2c2705eac2..d915bc2b11 100644 --- a/docs/1.0-dev/api/evennia.server.portal.mccp.html +++ b/docs/1.0-dev/api/evennia.server.portal.mccp.html @@ -154,7 +154,6 @@ creating a zlib compression stream.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.mssp.html b/docs/1.0-dev/api/evennia.server.portal.mssp.html index 4e91adffe3..317fcf5aa8 100644 --- a/docs/1.0-dev/api/evennia.server.portal.mssp.html +++ b/docs/1.0-dev/api/evennia.server.portal.mssp.html @@ -155,7 +155,6 @@ operation.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.mxp.html b/docs/1.0-dev/api/evennia.server.portal.mxp.html index 929d38ce12..9d53ad6311 100644 --- a/docs/1.0-dev/api/evennia.server.portal.mxp.html +++ b/docs/1.0-dev/api/evennia.server.portal.mxp.html @@ -147,7 +147,6 @@ that supports it (mudlet, zmud, mushclient are a few)

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.naws.html b/docs/1.0-dev/api/evennia.server.portal.naws.html index 17993df03f..78b8b12045 100644 --- a/docs/1.0-dev/api/evennia.server.portal.naws.html +++ b/docs/1.0-dev/api/evennia.server.portal.naws.html @@ -143,7 +143,6 @@ operation.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.portal.html b/docs/1.0-dev/api/evennia.server.portal.portal.html index 81d1fa745f..dd2d3071fe 100644 --- a/docs/1.0-dev/api/evennia.server.portal.portal.html +++ b/docs/1.0-dev/api/evennia.server.portal.portal.html @@ -142,7 +142,6 @@ case it always needs to be restarted manually.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.portalsessionhandler.html b/docs/1.0-dev/api/evennia.server.portal.portalsessionhandler.html index a1eb7dec3e..2a052ea7da 100644 --- a/docs/1.0-dev/api/evennia.server.portal.portalsessionhandler.html +++ b/docs/1.0-dev/api/evennia.server.portal.portalsessionhandler.html @@ -343,7 +343,6 @@ method exixts, it sends the data to a method send_default.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.rss.html b/docs/1.0-dev/api/evennia.server.portal.rss.html index c6d96c0d63..52d3d08eb8 100644 --- a/docs/1.0-dev/api/evennia.server.portal.rss.html +++ b/docs/1.0-dev/api/evennia.server.portal.rss.html @@ -184,7 +184,6 @@ on slow connections.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.ssh.html b/docs/1.0-dev/api/evennia.server.portal.ssh.html index f51eb68403..23533233a9 100644 --- a/docs/1.0-dev/api/evennia.server.portal.ssh.html +++ b/docs/1.0-dev/api/evennia.server.portal.ssh.html @@ -405,7 +405,6 @@ do not exist, the keypair is created.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.ssl.html b/docs/1.0-dev/api/evennia.server.portal.ssl.html index 2fcbb4f9cf..e529914b27 100644 --- a/docs/1.0-dev/api/evennia.server.portal.ssl.html +++ b/docs/1.0-dev/api/evennia.server.portal.ssl.html @@ -125,7 +125,6 @@ server-side.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.suppress_ga.html b/docs/1.0-dev/api/evennia.server.portal.suppress_ga.html index 2261b6f4b4..e822b15092 100644 --- a/docs/1.0-dev/api/evennia.server.portal.suppress_ga.html +++ b/docs/1.0-dev/api/evennia.server.portal.suppress_ga.html @@ -133,7 +133,6 @@ protocol to set it up.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.telnet.html b/docs/1.0-dev/api/evennia.server.portal.telnet.html index df922088a1..a27816553e 100644 --- a/docs/1.0-dev/api/evennia.server.portal.telnet.html +++ b/docs/1.0-dev/api/evennia.server.portal.telnet.html @@ -326,7 +326,6 @@ Note that it must be actively turned back on again!

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.telnet_oob.html b/docs/1.0-dev/api/evennia.server.portal.telnet_oob.html index 79aeab6c44..64193fb0bb 100644 --- a/docs/1.0-dev/api/evennia.server.portal.telnet_oob.html +++ b/docs/1.0-dev/api/evennia.server.portal.telnet_oob.html @@ -299,7 +299,6 @@ We assume the structure is valid JSON.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.telnet_ssl.html b/docs/1.0-dev/api/evennia.server.portal.telnet_ssl.html index 39c3d4d1cf..b3081e2cd9 100644 --- a/docs/1.0-dev/api/evennia.server.portal.telnet_ssl.html +++ b/docs/1.0-dev/api/evennia.server.portal.telnet_ssl.html @@ -136,7 +136,6 @@ server-side.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.tests.html b/docs/1.0-dev/api/evennia.server.portal.tests.html index 85633838e7..40a651f959 100644 --- a/docs/1.0-dev/api/evennia.server.portal.tests.html +++ b/docs/1.0-dev/api/evennia.server.portal.tests.html @@ -192,7 +192,6 @@ its inverse gives the correct string.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.ttype.html b/docs/1.0-dev/api/evennia.server.portal.ttype.html index ffae6d421a..07277ca8fc 100644 --- a/docs/1.0-dev/api/evennia.server.portal.ttype.html +++ b/docs/1.0-dev/api/evennia.server.portal.ttype.html @@ -142,7 +142,6 @@ stored on protocol.protocol_flags under the TTYPE key.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.webclient.html b/docs/1.0-dev/api/evennia.server.portal.webclient.html index 16009ca089..2d6bad84ef 100644 --- a/docs/1.0-dev/api/evennia.server.portal.webclient.html +++ b/docs/1.0-dev/api/evennia.server.portal.webclient.html @@ -259,7 +259,6 @@ client instead.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.portal.webclient_ajax.html b/docs/1.0-dev/api/evennia.server.portal.webclient_ajax.html index cbab24baab..619861cc79 100644 --- a/docs/1.0-dev/api/evennia.server.portal.webclient_ajax.html +++ b/docs/1.0-dev/api/evennia.server.portal.webclient_ajax.html @@ -403,7 +403,6 @@ client instead.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.profiling.dummyrunner.html b/docs/1.0-dev/api/evennia.server.profiling.dummyrunner.html index c2fc8ca5fd..53416c7511 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.dummyrunner.html +++ b/docs/1.0-dev/api/evennia.server.profiling.dummyrunner.html @@ -341,7 +341,6 @@ all “intelligence” of the dummy client.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.profiling.dummyrunner_settings.html b/docs/1.0-dev/api/evennia.server.profiling.dummyrunner_settings.html index 3dbc75fe9b..b309a88943 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.dummyrunner_settings.html +++ b/docs/1.0-dev/api/evennia.server.profiling.dummyrunner_settings.html @@ -224,7 +224,6 @@ dummyrunner output about just how fast commands are being processed.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.profiling.html b/docs/1.0-dev/api/evennia.server.profiling.html index 375f50f9ec..0621f44a54 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.html +++ b/docs/1.0-dev/api/evennia.server.profiling.html @@ -95,7 +95,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.profiling.memplot.html b/docs/1.0-dev/api/evennia.server.profiling.memplot.html index 858c33dd25..705591cd88 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.memplot.html +++ b/docs/1.0-dev/api/evennia.server.profiling.memplot.html @@ -129,7 +129,6 @@ the script will append to this file if it already exists.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.profiling.settings_mixin.html b/docs/1.0-dev/api/evennia.server.profiling.settings_mixin.html index ef7b473112..cd287797d3 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.settings_mixin.html +++ b/docs/1.0-dev/api/evennia.server.profiling.settings_mixin.html @@ -91,7 +91,6 @@ servers!

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.profiling.test_queries.html b/docs/1.0-dev/api/evennia.server.profiling.test_queries.html index 9f6dc35e82..0116a89396 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.test_queries.html +++ b/docs/1.0-dev/api/evennia.server.profiling.test_queries.html @@ -93,7 +93,6 @@ to setup the environment to test.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.profiling.tests.html b/docs/1.0-dev/api/evennia.server.profiling.tests.html index d18d9a7f09..929a85eb85 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.tests.html +++ b/docs/1.0-dev/api/evennia.server.profiling.tests.html @@ -187,7 +187,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.profiling.timetrace.html b/docs/1.0-dev/api/evennia.server.profiling.timetrace.html index ab5f9fab50..30db5ba60a 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.timetrace.html +++ b/docs/1.0-dev/api/evennia.server.profiling.timetrace.html @@ -102,7 +102,6 @@ This message will get attached time stamp.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.server.html b/docs/1.0-dev/api/evennia.server.server.html index e6a1929980..51299e5586 100644 --- a/docs/1.0-dev/api/evennia.server.server.html +++ b/docs/1.0-dev/api/evennia.server.server.html @@ -227,7 +227,6 @@ shutdown or a reset.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.serversession.html b/docs/1.0-dev/api/evennia.server.serversession.html index 9b0be59454..dcb474b88f 100644 --- a/docs/1.0-dev/api/evennia.server.serversession.html +++ b/docs/1.0-dev/api/evennia.server.serversession.html @@ -392,7 +392,6 @@ property, e.g. obj.ndb.attr = value etc.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.session.html b/docs/1.0-dev/api/evennia.server.session.html index 392ea8fff1..66e13949d8 100644 --- a/docs/1.0-dev/api/evennia.server.session.html +++ b/docs/1.0-dev/api/evennia.server.session.html @@ -197,7 +197,6 @@ should overload this to format/handle the outgoing data as needed.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.sessionhandler.html b/docs/1.0-dev/api/evennia.server.sessionhandler.html index 8720cad1ad..59e9fbd024 100644 --- a/docs/1.0-dev/api/evennia.server.sessionhandler.html +++ b/docs/1.0-dev/api/evennia.server.sessionhandler.html @@ -604,7 +604,6 @@ on the form commandname=((args), {kwargs}).

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.signals.html b/docs/1.0-dev/api/evennia.server.signals.html index 38a75535f0..d8d50ae572 100644 --- a/docs/1.0-dev/api/evennia.server.signals.html +++ b/docs/1.0-dev/api/evennia.server.signals.html @@ -98,7 +98,6 @@ without necessitating a full takeover of hooks that may be in high demand.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.throttle.html b/docs/1.0-dev/api/evennia.server.throttle.html index 51f32a7431..0e7c0c8177 100644 --- a/docs/1.0-dev/api/evennia.server.throttle.html +++ b/docs/1.0-dev/api/evennia.server.throttle.html @@ -237,7 +237,6 @@ fails recently.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.validators.html b/docs/1.0-dev/api/evennia.server.validators.html index 4498a920d6..83a708c0aa 100644 --- a/docs/1.0-dev/api/evennia.server.validators.html +++ b/docs/1.0-dev/api/evennia.server.validators.html @@ -146,7 +146,6 @@ by this validator.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.server.webserver.html b/docs/1.0-dev/api/evennia.server.webserver.html index 845511d6d3..78a860f89a 100644 --- a/docs/1.0-dev/api/evennia.server.webserver.html +++ b/docs/1.0-dev/api/evennia.server.webserver.html @@ -304,7 +304,6 @@ directory this path represents.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.settings_default.html b/docs/1.0-dev/api/evennia.settings_default.html index f7bc82f257..f34a719d2c 100644 --- a/docs/1.0-dev/api/evennia.settings_default.html +++ b/docs/1.0-dev/api/evennia.settings_default.html @@ -92,7 +92,6 @@ always be sure of what you have changed and what is default behaviour.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.typeclasses.attributes.html b/docs/1.0-dev/api/evennia.typeclasses.attributes.html index c003d5e1ed..52061c0f02 100644 --- a/docs/1.0-dev/api/evennia.typeclasses.attributes.html +++ b/docs/1.0-dev/api/evennia.typeclasses.attributes.html @@ -1514,7 +1514,6 @@ with nicks stored on the Account level.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.typeclasses.html b/docs/1.0-dev/api/evennia.typeclasses.html index a2fd6a266b..ecb350f99e 100644 --- a/docs/1.0-dev/api/evennia.typeclasses.html +++ b/docs/1.0-dev/api/evennia.typeclasses.html @@ -98,7 +98,6 @@ Attribute and Tag models are defined along with their handlers.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.typeclasses.managers.html b/docs/1.0-dev/api/evennia.typeclasses.managers.html index 9ed3787bde..3498ecb006 100644 --- a/docs/1.0-dev/api/evennia.typeclasses.managers.html +++ b/docs/1.0-dev/api/evennia.typeclasses.managers.html @@ -491,7 +491,6 @@ Mutually exclusive to include_children.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.typeclasses.models.html b/docs/1.0-dev/api/evennia.typeclasses.models.html index c767a60ee1..7bd32ed34b 100644 --- a/docs/1.0-dev/api/evennia.typeclasses.models.html +++ b/docs/1.0-dev/api/evennia.typeclasses.models.html @@ -770,7 +770,6 @@ developer’s responsibility.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.typeclasses.tags.html b/docs/1.0-dev/api/evennia.typeclasses.tags.html index 3cbbef98f5..b96096ae1d 100644 --- a/docs/1.0-dev/api/evennia.typeclasses.tags.html +++ b/docs/1.0-dev/api/evennia.typeclasses.tags.html @@ -509,7 +509,6 @@ replicated with a lock check against the lockstring

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.ansi.html b/docs/1.0-dev/api/evennia.utils.ansi.html index ba908a142f..26eed0fc5f 100644 --- a/docs/1.0-dev/api/evennia.utils.ansi.html +++ b/docs/1.0-dev/api/evennia.utils.ansi.html @@ -974,7 +974,6 @@ with.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.batchprocessors.html b/docs/1.0-dev/api/evennia.utils.batchprocessors.html index 8c8ff2cd46..edcb329d9f 100644 --- a/docs/1.0-dev/api/evennia.utils.batchprocessors.html +++ b/docs/1.0-dev/api/evennia.utils.batchprocessors.html @@ -396,7 +396,6 @@ namespace.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.containers.html b/docs/1.0-dev/api/evennia.utils.containers.html index 9f4d80e6bb..611e24549a 100644 --- a/docs/1.0-dev/api/evennia.utils.containers.html +++ b/docs/1.0-dev/api/evennia.utils.containers.html @@ -231,7 +231,6 @@ scripts defined in settings.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.create.html b/docs/1.0-dev/api/evennia.utils.create.html index 633baab511..df14d9a8ec 100644 --- a/docs/1.0-dev/api/evennia.utils.create.html +++ b/docs/1.0-dev/api/evennia.utils.create.html @@ -300,7 +300,6 @@ operations and is thus not suitable for play-testing the game.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.dbserialize.html b/docs/1.0-dev/api/evennia.utils.dbserialize.html index cd78c71f8c..7a84a2cb9c 100644 --- a/docs/1.0-dev/api/evennia.utils.dbserialize.html +++ b/docs/1.0-dev/api/evennia.utils.dbserialize.html @@ -166,7 +166,6 @@ _SaverList, _SaverDict and _SaverSet counterparts.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.eveditor.html b/docs/1.0-dev/api/evennia.utils.eveditor.html index 34a7fd11ae..e2d9c9e100 100644 --- a/docs/1.0-dev/api/evennia.utils.eveditor.html +++ b/docs/1.0-dev/api/evennia.utils.eveditor.html @@ -277,7 +277,7 @@ indentation.

    -aliases = [':fi', ':s', ':!', ':<', ':DD', ':r', ':p', ':>', ':fd', ':j', '::', ':y', ':i', ':I', ':dd', ':f', ':u', ':wq', ':h', ':x', ':A', ':S', ':UU', ':::', ':q', ':w', ':q!', ':uu', ':=', ':dw', ':', ':echo']
    +aliases = [':y', ':u', ':r', ':::', ':q!', ':i', ':I', ':=', '::', ':p', ':s', ':f', ':fd', ':!', ':wq', ':echo', ':w', ':DD', ':fi', ':j', ':q', ':dw', ':S', ':>', ':', ':x', ':dd', ':A', ':<', ':h', ':uu', ':UU']
    @@ -305,7 +305,7 @@ efficient presentation.

    -search_index_entry = {'aliases': ':fi :s :! :< :DD :r :p :> :fd :j :: :y :i :I :dd :f :u :wq :h :x :A :S :UU ::: :q :w :q! :uu := :dw : :echo', 'category': 'general', 'key': ':editor_command_group', 'no_prefix': ' :fi :s :! :< :DD :r :p :> :fd :j :: :y :i :I :dd :f :u :wq :h :x :A :S :UU ::: :q :w :q! :uu := :dw : :echo', 'tags': '', 'text': '\n Commands for the editor\n '}
    +search_index_entry = {'aliases': ':y :u :r ::: :q! :i :I := :: :p :s :f :fd :! :wq :echo :w :DD :fi :j :q :dw :S :> : :x :dd :A :< :h :uu :UU', 'category': 'general', 'key': ':editor_command_group', 'no_prefix': ' :y :u :r ::: :q! :i :I := :: :p :s :f :fd :! :wq :echo :w :DD :fi :j :q :dw :S :> : :x :dd :A :< :h :uu :UU', 'tags': '', 'text': '\n Commands for the editor\n '}
    @@ -533,7 +533,6 @@ formatting information.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.evform.html b/docs/1.0-dev/api/evennia.utils.evform.html index 2e45e33cc9..9f3d2d6973 100644 --- a/docs/1.0-dev/api/evennia.utils.evform.html +++ b/docs/1.0-dev/api/evennia.utils.evform.html @@ -262,7 +262,6 @@ If this is given, filename is not read.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.evmenu.html b/docs/1.0-dev/api/evennia.utils.evmenu.html index 33dea72f75..2a6a71a687 100644 --- a/docs/1.0-dev/api/evennia.utils.evmenu.html +++ b/docs/1.0-dev/api/evennia.utils.evmenu.html @@ -947,7 +947,7 @@ single question.

    -aliases = ['yes', 'abort', 'no', 'y', 'n', 'a', '__nomatch_command']
    +aliases = ['__nomatch_command', 'yes', 'n', 'abort', 'y', 'a', 'no']
    @@ -973,7 +973,7 @@ single question.

    -search_index_entry = {'aliases': 'yes abort no y n a __nomatch_command', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' yes abort no y n a __nomatch_command', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}
    +search_index_entry = {'aliases': '__nomatch_command yes n abort y a no', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' __nomatch_command yes n abort y a no', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}
    @@ -1172,7 +1172,6 @@ Must be on the form callable(caller, raw_string, **kwargs).

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.evmore.html b/docs/1.0-dev/api/evennia.utils.evmore.html index 932c7b8a1a..d5f5128ce0 100644 --- a/docs/1.0-dev/api/evennia.utils.evmore.html +++ b/docs/1.0-dev/api/evennia.utils.evmore.html @@ -78,7 +78,7 @@ the caller.msg() construct every time the page is updated.

    -aliases = ['quit', 'p', 'previous', 'end', 'e', 't', 'next', 'a', 'abort', 'n', 'q', 'top']
    +aliases = ['abort', 'n', 'previous', 'top', 'a', 't', 'end', 'p', 'quit', 'e', 'next', 'q']
    @@ -104,7 +104,7 @@ the caller.msg() construct every time the page is updated.

    -search_index_entry = {'aliases': 'quit p previous end e t next a abort n q top', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' quit p previous end e t next a abort n q top', 'tags': '', 'text': '\n Manipulate the text paging. Catch no-input with aliases.\n '}
    +search_index_entry = {'aliases': 'abort n previous top a t end p quit e next q', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' abort n previous top a t end p quit e next q', 'tags': '', 'text': '\n Manipulate the text paging. Catch no-input with aliases.\n '}
    @@ -543,7 +543,6 @@ the evmore commands will be available when this is run).

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.evtable.html b/docs/1.0-dev/api/evennia.utils.evtable.html index 85669906fa..3ef6b91501 100644 --- a/docs/1.0-dev/api/evennia.utils.evtable.html +++ b/docs/1.0-dev/api/evennia.utils.evtable.html @@ -652,7 +652,6 @@ given from 0 to Ncolumns-1.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.funcparser.html b/docs/1.0-dev/api/evennia.utils.funcparser.html index 834d6b5e9e..0f77e0e5be 100644 --- a/docs/1.0-dev/api/evennia.utils.funcparser.html +++ b/docs/1.0-dev/api/evennia.utils.funcparser.html @@ -906,7 +906,6 @@ and the mapping can always be auto-detected.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.gametime.html b/docs/1.0-dev/api/evennia.utils.gametime.html index 3f46d732ed..8538311d00 100644 --- a/docs/1.0-dev/api/evennia.utils.gametime.html +++ b/docs/1.0-dev/api/evennia.utils.gametime.html @@ -281,7 +281,6 @@ the epoch set by settings.TIME_GAME_EPOCH will still apply.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.html b/docs/1.0-dev/api/evennia.utils.html index fbc15bd7b7..3fa53bca8b 100644 --- a/docs/1.0-dev/api/evennia.utils.html +++ b/docs/1.0-dev/api/evennia.utils.html @@ -144,7 +144,6 @@ functionality.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.idmapper.html b/docs/1.0-dev/api/evennia.utils.idmapper.html index 5a0459a35c..091b6c4e9a 100644 --- a/docs/1.0-dev/api/evennia.utils.idmapper.html +++ b/docs/1.0-dev/api/evennia.utils.idmapper.html @@ -92,7 +92,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.idmapper.manager.html b/docs/1.0-dev/api/evennia.utils.idmapper.manager.html index 0bc33b91c7..746caaa85f 100644 --- a/docs/1.0-dev/api/evennia.utils.idmapper.manager.html +++ b/docs/1.0-dev/api/evennia.utils.idmapper.manager.html @@ -97,7 +97,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.idmapper.models.html b/docs/1.0-dev/api/evennia.utils.idmapper.models.html index 9f9bc04582..b4a71e002b 100644 --- a/docs/1.0-dev/api/evennia.utils.idmapper.models.html +++ b/docs/1.0-dev/api/evennia.utils.idmapper.models.html @@ -309,7 +309,6 @@ catch in an easy way here. Ideas are appreciated. /Griatch

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.idmapper.tests.html b/docs/1.0-dev/api/evennia.utils.idmapper.tests.html index 7c692f528a..7479cabec0 100644 --- a/docs/1.0-dev/api/evennia.utils.idmapper.tests.html +++ b/docs/1.0-dev/api/evennia.utils.idmapper.tests.html @@ -406,7 +406,6 @@ object the first time, the query is executed.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.logger.html b/docs/1.0-dev/api/evennia.utils.logger.html index b07200e3e9..2f844b8bbb 100644 --- a/docs/1.0-dev/api/evennia.utils.logger.html +++ b/docs/1.0-dev/api/evennia.utils.logger.html @@ -127,7 +127,7 @@ server.log.2020_01_29__2

    Reformat logging

    -timeFormat: Optional[str] = None
    +timeFormat: Optional[str, None] = None
    @@ -480,7 +480,6 @@ all if the file is shorter than nlines.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.optionclasses.html b/docs/1.0-dev/api/evennia.utils.optionclasses.html index cc9654d0ca..453b60ec53 100644 --- a/docs/1.0-dev/api/evennia.utils.optionclasses.html +++ b/docs/1.0-dev/api/evennia.utils.optionclasses.html @@ -899,7 +899,6 @@ entries are processed.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.optionhandler.html b/docs/1.0-dev/api/evennia.utils.optionhandler.html index cd1dcab068..f99ecfb474 100644 --- a/docs/1.0-dev/api/evennia.utils.optionhandler.html +++ b/docs/1.0-dev/api/evennia.utils.optionhandler.html @@ -207,7 +207,6 @@ than their values.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.picklefield.html b/docs/1.0-dev/api/evennia.utils.picklefield.html index 41cc932cec..10e0f557b3 100644 --- a/docs/1.0-dev/api/evennia.utils.picklefield.html +++ b/docs/1.0-dev/api/evennia.utils.picklefield.html @@ -251,7 +251,6 @@ This is used by the serialization framework.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.search.html b/docs/1.0-dev/api/evennia.utils.search.html index 3c9fd0f702..573408dee2 100644 --- a/docs/1.0-dev/api/evennia.utils.search.html +++ b/docs/1.0-dev/api/evennia.utils.search.html @@ -370,7 +370,6 @@ matches were found.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.test_resources.html b/docs/1.0-dev/api/evennia.utils.test_resources.html index 40b1493c70..fc6543eb5a 100644 --- a/docs/1.0-dev/api/evennia.utils.test_resources.html +++ b/docs/1.0-dev/api/evennia.utils.test_resources.html @@ -423,7 +423,6 @@ classes and settings in mygame.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.text2html.html b/docs/1.0-dev/api/evennia.utils.text2html.html index b1db39c729..00b64b4efb 100644 --- a/docs/1.0-dev/api/evennia.utils.text2html.html +++ b/docs/1.0-dev/api/evennia.utils.text2html.html @@ -460,7 +460,6 @@ into html statements.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.utils.html b/docs/1.0-dev/api/evennia.utils.utils.html index 7ccc1a64eb..eeb84f6ad8 100644 --- a/docs/1.0-dev/api/evennia.utils.utils.html +++ b/docs/1.0-dev/api/evennia.utils.utils.html @@ -1728,7 +1728,6 @@ is longer than base_word, the excess will retain its original c

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.validatorfuncs.html b/docs/1.0-dev/api/evennia.utils.validatorfuncs.html index 5f29e9b29a..935c70eaa6 100644 --- a/docs/1.0-dev/api/evennia.utils.validatorfuncs.html +++ b/docs/1.0-dev/api/evennia.utils.validatorfuncs.html @@ -207,7 +207,6 @@ ignored.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.verb_conjugation.conjugate.html b/docs/1.0-dev/api/evennia.utils.verb_conjugation.conjugate.html index e0b13e37c6..ea2255f200 100644 --- a/docs/1.0-dev/api/evennia.utils.verb_conjugation.conjugate.html +++ b/docs/1.0-dev/api/evennia.utils.verb_conjugation.conjugate.html @@ -338,7 +338,6 @@ need, dare, ought.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.verb_conjugation.html b/docs/1.0-dev/api/evennia.utils.verb_conjugation.html index 816ce37eeb..833571a5bf 100644 --- a/docs/1.0-dev/api/evennia.utils.verb_conjugation.html +++ b/docs/1.0-dev/api/evennia.utils.verb_conjugation.html @@ -91,7 +91,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.verb_conjugation.pronouns.html b/docs/1.0-dev/api/evennia.utils.verb_conjugation.pronouns.html index 2b81f29f5d..58b85e2934 100644 --- a/docs/1.0-dev/api/evennia.utils.verb_conjugation.pronouns.html +++ b/docs/1.0-dev/api/evennia.utils.verb_conjugation.pronouns.html @@ -226,7 +226,6 @@ string and others respectively. If pronoun is invalid, the word is returned verb

    Versions

    diff --git a/docs/1.0-dev/api/evennia.utils.verb_conjugation.tests.html b/docs/1.0-dev/api/evennia.utils.verb_conjugation.tests.html index f75bcd347c..3271c2e68e 100644 --- a/docs/1.0-dev/api/evennia.utils.verb_conjugation.tests.html +++ b/docs/1.0-dev/api/evennia.utils.verb_conjugation.tests.html @@ -937,7 +937,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.accounts.html b/docs/1.0-dev/api/evennia.web.admin.accounts.html index ecf0cb1b9e..9619bf6747 100644 --- a/docs/1.0-dev/api/evennia.web.admin.accounts.html +++ b/docs/1.0-dev/api/evennia.web.admin.accounts.html @@ -468,7 +468,6 @@ has a slightly different workflow.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.attributes.html b/docs/1.0-dev/api/evennia.web.admin.attributes.html index f8c9430a18..6091c1003c 100644 --- a/docs/1.0-dev/api/evennia.web.admin.attributes.html +++ b/docs/1.0-dev/api/evennia.web.admin.attributes.html @@ -227,7 +227,6 @@ people used the admin at the same time

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.comms.html b/docs/1.0-dev/api/evennia.web.admin.comms.html index 44651f6ac5..414cf9ae68 100644 --- a/docs/1.0-dev/api/evennia.web.admin.comms.html +++ b/docs/1.0-dev/api/evennia.web.admin.comms.html @@ -468,7 +468,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.frontpage.html b/docs/1.0-dev/api/evennia.web.admin.frontpage.html index b7058861db..348c591124 100644 --- a/docs/1.0-dev/api/evennia.web.admin.frontpage.html +++ b/docs/1.0-dev/api/evennia.web.admin.frontpage.html @@ -97,7 +97,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.help.html b/docs/1.0-dev/api/evennia.web.admin.help.html index 01a007f44a..d3ddeb62a5 100644 --- a/docs/1.0-dev/api/evennia.web.admin.help.html +++ b/docs/1.0-dev/api/evennia.web.admin.help.html @@ -220,7 +220,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.html b/docs/1.0-dev/api/evennia.web.admin.html index efbdb1fcb0..102fd535dd 100644 --- a/docs/1.0-dev/api/evennia.web.admin.html +++ b/docs/1.0-dev/api/evennia.web.admin.html @@ -100,7 +100,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.objects.html b/docs/1.0-dev/api/evennia.web.admin.objects.html index 6cde81134a..994eb18e12 100644 --- a/docs/1.0-dev/api/evennia.web.admin.objects.html +++ b/docs/1.0-dev/api/evennia.web.admin.objects.html @@ -388,7 +388,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.scripts.html b/docs/1.0-dev/api/evennia.web.admin.scripts.html index 2100249572..131a6fe0d1 100644 --- a/docs/1.0-dev/api/evennia.web.admin.scripts.html +++ b/docs/1.0-dev/api/evennia.web.admin.scripts.html @@ -258,7 +258,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.server.html b/docs/1.0-dev/api/evennia.web.admin.server.html index 61fdf26c89..b6b6515e5e 100644 --- a/docs/1.0-dev/api/evennia.web.admin.server.html +++ b/docs/1.0-dev/api/evennia.web.admin.server.html @@ -133,7 +133,6 @@ in the web admin interface.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.tags.html b/docs/1.0-dev/api/evennia.web.admin.tags.html index f5c9084629..83e5f88ddc 100644 --- a/docs/1.0-dev/api/evennia.web.admin.tags.html +++ b/docs/1.0-dev/api/evennia.web.admin.tags.html @@ -312,7 +312,6 @@ people used the admin at the same time

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.urls.html b/docs/1.0-dev/api/evennia.web.admin.urls.html index ef3cc61d0d..23525cfab2 100644 --- a/docs/1.0-dev/api/evennia.web.admin.urls.html +++ b/docs/1.0-dev/api/evennia.web.admin.urls.html @@ -86,7 +86,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.admin.utils.html b/docs/1.0-dev/api/evennia.web.admin.utils.html index 09019ba7e0..601b8882dc 100644 --- a/docs/1.0-dev/api/evennia.web.admin.utils.html +++ b/docs/1.0-dev/api/evennia.web.admin.utils.html @@ -125,7 +125,6 @@ admin process. This is intended to be used with forms.ChoiceField.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.api.filters.html b/docs/1.0-dev/api/evennia.web.api.filters.html index 3833c7c479..f340a18e8a 100644 --- a/docs/1.0-dev/api/evennia.web.api.filters.html +++ b/docs/1.0-dev/api/evennia.web.api.filters.html @@ -49,7 +49,7 @@ documentation specifically regarding DRF integration.

    https://django-filter.readthedocs.io/en/latest/guide/rest_framework.html

    -evennia.web.api.filters.get_tag_query(tag_type: Optional[str], key: str) → django.db.models.query_utils.Q[source]
    +evennia.web.api.filters.get_tag_query(tag_type: Optional[str, None], key: str) → django.db.models.query_utils.Q[source]

    Returns a Q object for searching by tag names for typeclasses :param tag_type: The type of tag (None, ‘alias’, etc) :type tag_type: str or None @@ -295,7 +295,6 @@ documentation specifically regarding DRF integration.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.api.html b/docs/1.0-dev/api/evennia.web.api.html index a5c4e5ac75..c53bd2b60e 100644 --- a/docs/1.0-dev/api/evennia.web.api.html +++ b/docs/1.0-dev/api/evennia.web.api.html @@ -95,7 +95,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.api.permissions.html b/docs/1.0-dev/api/evennia.web.api.permissions.html index c87c627bc9..d6a93924c3 100644 --- a/docs/1.0-dev/api/evennia.web.api.permissions.html +++ b/docs/1.0-dev/api/evennia.web.api.permissions.html @@ -180,7 +180,6 @@ complete the action.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.api.root.html b/docs/1.0-dev/api/evennia.web.api.root.html index 0de6680217..cec63543f8 100644 --- a/docs/1.0-dev/api/evennia.web.api.root.html +++ b/docs/1.0-dev/api/evennia.web.api.root.html @@ -104,7 +104,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.api.serializers.html b/docs/1.0-dev/api/evennia.web.api.serializers.html index 038a41f978..1dd571f0a3 100644 --- a/docs/1.0-dev/api/evennia.web.api.serializers.html +++ b/docs/1.0-dev/api/evennia.web.api.serializers.html @@ -534,7 +534,6 @@ explicitly to not have them render PK-related fields.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.api.tests.html b/docs/1.0-dev/api/evennia.web.api.tests.html index df403fbf0e..a8d2858b55 100644 --- a/docs/1.0-dev/api/evennia.web.api.tests.html +++ b/docs/1.0-dev/api/evennia.web.api.tests.html @@ -150,7 +150,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.api.urls.html b/docs/1.0-dev/api/evennia.web.api.urls.html index bcb4137d8f..ed00b0f1dc 100644 --- a/docs/1.0-dev/api/evennia.web.api.urls.html +++ b/docs/1.0-dev/api/evennia.web.api.urls.html @@ -98,7 +98,6 @@ set attribute: action: POST, url: /objects/<:pk>/set-attribute, view nam

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.api.views.html b/docs/1.0-dev/api/evennia.web.api.views.html index 6ef72fa4b9..8bf5b8794e 100644 --- a/docs/1.0-dev/api/evennia.web.api.views.html +++ b/docs/1.0-dev/api/evennia.web.api.views.html @@ -485,7 +485,6 @@ Note that command auto-help and file-based help entries are not accessible this

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.html b/docs/1.0-dev/api/evennia.web.html index 1f91e9960f..bcd59828da 100644 --- a/docs/1.0-dev/api/evennia.web.html +++ b/docs/1.0-dev/api/evennia.web.html @@ -153,7 +153,6 @@ relate the database contents to web pages.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.templatetags.addclass.html b/docs/1.0-dev/api/evennia.web.templatetags.addclass.html index 86ee513bd1..8db0515ac8 100644 --- a/docs/1.0-dev/api/evennia.web.templatetags.addclass.html +++ b/docs/1.0-dev/api/evennia.web.templatetags.addclass.html @@ -89,7 +89,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.templatetags.html b/docs/1.0-dev/api/evennia.web.templatetags.html index 928ba26c56..3480c434c1 100644 --- a/docs/1.0-dev/api/evennia.web.templatetags.html +++ b/docs/1.0-dev/api/evennia.web.templatetags.html @@ -89,7 +89,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.urls.html b/docs/1.0-dev/api/evennia.web.urls.html index 6c8ca095ef..dd87c9eddf 100644 --- a/docs/1.0-dev/api/evennia.web.urls.html +++ b/docs/1.0-dev/api/evennia.web.urls.html @@ -99,7 +99,6 @@ dynamic content as appropriate.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.utils.adminsite.html b/docs/1.0-dev/api/evennia.web.utils.adminsite.html index 6d95107977..189318bbf7 100644 --- a/docs/1.0-dev/api/evennia.web.utils.adminsite.html +++ b/docs/1.0-dev/api/evennia.web.utils.adminsite.html @@ -126,7 +126,6 @@ registered in this site.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.utils.backends.html b/docs/1.0-dev/api/evennia.web.utils.backends.html index 8b75835f8e..0638caf6b8 100644 --- a/docs/1.0-dev/api/evennia.web.utils.backends.html +++ b/docs/1.0-dev/api/evennia.web.utils.backends.html @@ -110,7 +110,6 @@ an already authenticated account and bypass authentication.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.utils.general_context.html b/docs/1.0-dev/api/evennia.web.utils.general_context.html index 90b0be2533..10cdef698c 100644 --- a/docs/1.0-dev/api/evennia.web.utils.general_context.html +++ b/docs/1.0-dev/api/evennia.web.utils.general_context.html @@ -101,7 +101,6 @@ to context of all views.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.utils.html b/docs/1.0-dev/api/evennia.web.utils.html index 6eac238e1e..b0fb655d51 100644 --- a/docs/1.0-dev/api/evennia.web.utils.html +++ b/docs/1.0-dev/api/evennia.web.utils.html @@ -93,7 +93,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.utils.middleware.html b/docs/1.0-dev/api/evennia.web.utils.middleware.html index c18f5c5553..5a3c0566e5 100644 --- a/docs/1.0-dev/api/evennia.web.utils.middleware.html +++ b/docs/1.0-dev/api/evennia.web.utils.middleware.html @@ -102,7 +102,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.utils.tests.html b/docs/1.0-dev/api/evennia.web.utils.tests.html index ca342d8063..ee27b3f101 100644 --- a/docs/1.0-dev/api/evennia.web.utils.tests.html +++ b/docs/1.0-dev/api/evennia.web.utils.tests.html @@ -100,7 +100,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.webclient.html b/docs/1.0-dev/api/evennia.web.webclient.html index f5e1205a90..758d97781d 100644 --- a/docs/1.0-dev/api/evennia.web.webclient.html +++ b/docs/1.0-dev/api/evennia.web.webclient.html @@ -90,7 +90,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.webclient.urls.html b/docs/1.0-dev/api/evennia.web.webclient.urls.html index c418bf927b..1e78f2900d 100644 --- a/docs/1.0-dev/api/evennia.web.webclient.urls.html +++ b/docs/1.0-dev/api/evennia.web.webclient.urls.html @@ -85,7 +85,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.webclient.views.html b/docs/1.0-dev/api/evennia.web.webclient.views.html index 3cbb3616d8..15541a88a7 100644 --- a/docs/1.0-dev/api/evennia.web.webclient.views.html +++ b/docs/1.0-dev/api/evennia.web.webclient.views.html @@ -92,7 +92,6 @@ page and serve it eventual static content.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.forms.html b/docs/1.0-dev/api/evennia.web.website.forms.html index d8c57ce42d..c5c2f70fca 100644 --- a/docs/1.0-dev/api/evennia.web.website.forms.html +++ b/docs/1.0-dev/api/evennia.web.website.forms.html @@ -322,7 +322,6 @@ wish to allow.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.html b/docs/1.0-dev/api/evennia.web.website.html index 4e0f9b6e3c..bfd04a2001 100644 --- a/docs/1.0-dev/api/evennia.web.website.html +++ b/docs/1.0-dev/api/evennia.web.website.html @@ -106,7 +106,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.tests.html b/docs/1.0-dev/api/evennia.web.website.tests.html index 1ef3cd06cb..39ae30df6a 100644 --- a/docs/1.0-dev/api/evennia.web.website.tests.html +++ b/docs/1.0-dev/api/evennia.web.website.tests.html @@ -530,7 +530,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.urls.html b/docs/1.0-dev/api/evennia.web.website.urls.html index b9f8597532..2c9b4ddd5f 100644 --- a/docs/1.0-dev/api/evennia.web.website.urls.html +++ b/docs/1.0-dev/api/evennia.web.website.urls.html @@ -85,7 +85,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.accounts.html b/docs/1.0-dev/api/evennia.web.website.views.accounts.html index 4032924325..c8b7c647ec 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.accounts.html +++ b/docs/1.0-dev/api/evennia.web.website.views.accounts.html @@ -132,7 +132,6 @@ proceeds with creating the Account object.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.channels.html b/docs/1.0-dev/api/evennia.web.website.views.channels.html index 97e45c30d2..15f52b44f2 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.channels.html +++ b/docs/1.0-dev/api/evennia.web.website.views.channels.html @@ -207,7 +207,6 @@ name.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.characters.html b/docs/1.0-dev/api/evennia.web.website.views.characters.html index 3a7c710e2f..c964fdda6b 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.characters.html +++ b/docs/1.0-dev/api/evennia.web.website.views.characters.html @@ -228,10 +228,16 @@ list of all characters the user may access.

    -class evennia.web.website.views.characters.CharacterDeleteView(**kwargs)[source]
    +class evennia.web.website.views.characters.CharacterDeleteView(*args, **kwargs)[source]

    Bases: evennia.web.website.views.characters.CharacterMixin, evennia.web.website.views.objects.ObjectDeleteView

    This view provides a mechanism by which a logged-in player (enforced by ObjectDeleteView) can delete a character they own.

    +
    +
    +form_class
    +

    alias of evennia.web.website.forms.EvenniaForm

    +
    +
    @@ -298,7 +304,6 @@ proceeds with creating the Character object.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.errors.html b/docs/1.0-dev/api/evennia.web.website.views.errors.html index c9b33c6a4e..34161e1e10 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.errors.html +++ b/docs/1.0-dev/api/evennia.web.website.views.errors.html @@ -92,7 +92,6 @@ implemented yet.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.help.html b/docs/1.0-dev/api/evennia.web.website.views.help.html index 5fad6e15d3..b6927e18aa 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.help.html +++ b/docs/1.0-dev/api/evennia.web.website.views.help.html @@ -268,7 +268,6 @@ instead of pk and slug.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.html b/docs/1.0-dev/api/evennia.web.website.views.html index 5a23089db3..1e90a43316 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.html +++ b/docs/1.0-dev/api/evennia.web.website.views.html @@ -97,7 +97,6 @@

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.index.html b/docs/1.0-dev/api/evennia.web.website.views.index.html index f9408b055e..09fde79c62 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.index.html +++ b/docs/1.0-dev/api/evennia.web.website.views.index.html @@ -133,7 +133,6 @@ of this method.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.mixins.html b/docs/1.0-dev/api/evennia.web.website.views.mixins.html index 818f03873c..91299eb469 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.mixins.html +++ b/docs/1.0-dev/api/evennia.web.website.views.mixins.html @@ -105,7 +105,7 @@ otherwise.

    -class evennia.web.website.views.mixins.EvenniaDeleteView(**kwargs)[source]
    +class evennia.web.website.views.mixins.EvenniaDeleteView(*args, **kwargs)[source]

    Bases: django.views.generic.edit.DeleteView, evennia.web.website.views.mixins.TypeclassMixin

    This view extends Django’s default DeleteView.

    DeleteView is used for deleting objects, be they Accounts, Characters or @@ -159,7 +159,6 @@ otherwise.

    Versions

    diff --git a/docs/1.0-dev/api/evennia.web.website.views.objects.html b/docs/1.0-dev/api/evennia.web.website.views.objects.html index 631462f30f..3eb3ad5ee7 100644 --- a/docs/1.0-dev/api/evennia.web.website.views.objects.html +++ b/docs/1.0-dev/api/evennia.web.website.views.objects.html @@ -119,7 +119,7 @@ default title for the page.

    -class evennia.web.website.views.objects.ObjectDeleteView(**kwargs)[source]
    +class evennia.web.website.views.objects.ObjectDeleteView(*args, **kwargs)[source]

    Bases: django.contrib.auth.mixins.LoginRequiredMixin, evennia.web.website.views.objects.ObjectDetailView, evennia.web.website.views.mixins.EvenniaDeleteView

    This is an important view for obvious reasons!

    Any view you write that deals with deleting a specific object will want to @@ -142,14 +142,6 @@ permissions to delete the requested object.

    access_type = 'delete'
    -
    -
    -delete(request, *args, **kwargs)[source]
    -

    Calls the delete() method on the fetched object and then -redirects to the success URL.

    -

    We extend this so we can capture the name for the sake of confirmation.

    -
    -
    @@ -257,7 +249,6 @@ validated and sanitized.

    Versions

    diff --git a/docs/1.0-dev/genindex.html b/docs/1.0-dev/genindex.html index 219ef5b610..7eafebe663 100644 --- a/docs/1.0-dev/genindex.html +++ b/docs/1.0-dev/genindex.html @@ -87,10 +87,6 @@
  • (evennia.comms.models.SubscriptionHandler method)
  • (evennia.comms.models.TempMsg method) -
  • -
  • (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage method) -
  • -
  • (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3StorageFile method)
  • (evennia.contrib.base_systems.building_menu.building_menu.BuildingMenu method)
  • @@ -369,10 +365,6 @@
  • (evennia.typeclasses.models.TypedObject method)
  • -
  • access_key (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute) -
  • -
  • access_key_names (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute) -
  • access_type (evennia.web.website.views.channels.ChannelMixin attribute) + -
  • at_db_location_postsave() (evennia.objects.models.ObjectDB method)
  • -
  • at_defeat() (in module evennia.contrib.game_systems.turnbattle.tb_basic) - -
  • at_desc() (evennia.objects.objects.DefaultObject method)
  • at_disconnect() (evennia.accounts.accounts.DefaultAccount method) @@ -1899,8 +1871,6 @@
  • (evennia.contrib.game_systems.turnbattle.tb_items.TBItemsCharacterTest method)
  • (evennia.contrib.game_systems.turnbattle.tb_magic.TBMagicCharacter method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_range.TBRangeCharacter method)
  • (evennia.contrib.grid.extended_room.extended_room.ExtendedRoom method)
  • @@ -1943,6 +1913,8 @@
  • (evennia.contrib.tutorials.tutorial_world.rooms.TeleportRoom method)
  • (evennia.contrib.tutorials.tutorial_world.rooms.TutorialRoom method) +
  • +
  • (evennia.contrib.tutorials.tutorial_world.rooms.TutorialStartExit method)
  • (evennia.contrib.tutorials.tutorial_world.rooms.WeatherRoom method)
  • @@ -2080,14 +2052,6 @@
  • at_pre_move() (evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicCharacter method)
  • @@ -2131,14 +2095,6 @@
  • (evennia.contrib.game_systems.barter.barter.TradeTimeout method)
  • (evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_equip.TBEquipTurnHandler method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_items.TBItemsTurnHandler method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_magic.TBMagicTurnHandler method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_range.TBRangeTurnHandler method)
  • (evennia.contrib.tutorials.bodyfunctions.bodyfunctions.BodyFunctions method)
  • @@ -2169,14 +2125,6 @@
  • (evennia.contrib.game_systems.barter.barter.TradeTimeout method)
  • (evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_equip.TBEquipTurnHandler method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_items.TBItemsTurnHandler method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_magic.TBMagicTurnHandler method) -
  • -
  • (evennia.contrib.game_systems.turnbattle.tb_range.TBRangeTurnHandler method)
  • (evennia.contrib.grid.wilderness.wilderness.WildernessScript method)
  • @@ -2262,14 +2210,6 @@
  • at_stop() (evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler method)
  • @@ -2379,11 +2319,9 @@
  • authenticated_response (evennia.web.website.tests.EvenniaWebTest attribute)
  • -
  • author() (evennia.contrib.base_systems.ingame_python.callbackhandler.Callback property) +
  • author (evennia.contrib.base_systems.ingame_python.callbackhandler.Callback attribute)
  • auto_close_msg (evennia.contrib.tutorials.red_button.red_button.RedButton attribute) -
  • -
  • auto_create_bucket (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute)
  • auto_help (evennia.commands.command.Command attribute) @@ -2532,6 +2470,8 @@
  • +
  • date_joined (evennia.accounts.models.AccountDB attribute) +
  • Datetime (class in evennia.utils.optionclasses)
  • datetime() (in module evennia.utils.validatorfuncs) @@ -4247,10 +4191,18 @@
  • db_data (evennia.typeclasses.tags.Tag attribute)
  • -
  • db_date_created (evennia.comms.models.Msg attribute) +
  • db_date_created (evennia.accounts.models.AccountDB attribute)
  • - -
    • evennia.contrib.utils.tree_select.tests @@ -6316,6 +6271,8 @@
    • module
    + +
    • evennia.contrib.utils.tree_select.tree_select @@ -7544,11 +7501,9 @@
  • ExhaustedGenerator
  • -
  • exists() (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage method) +
  • exists() (evennia.scripts.taskhandler.TaskHandler method)
  • @@ -7720,12 +7675,6 @@
  • (evennia.web.admin.tags.TagAdmin attribute)
  • -
  • file() (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3StorageFile property) -
  • -
  • file_name_charset (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute) -
  • -
  • file_overwrite (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute) -
  • FileHelpEntry (class in evennia.help.filehelp)
  • FileHelpStorageHandler (class in evennia.help.filehelp) @@ -7769,6 +7718,8 @@
  • finish() (evennia.contrib.game_systems.barter.barter.TradeHandler method)
  • FireballRecipe (class in evennia.contrib.game_systems.crafting.example_recipes) +
  • +
  • first_name (evennia.accounts.models.AccountDB attribute)
  • flatten_diff() (in module evennia.prototypes.spawner)
  • @@ -7817,6 +7768,8 @@
  • form_class (evennia.web.website.views.accounts.AccountMixin attribute)
  • -
  • get_damage() (in module evennia.contrib.game_systems.turnbattle.tb_basic) +
  • get_damage() (evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules method)
  • get_db_prep_lookup() (evennia.utils.picklefield.PickledObjectField method) @@ -8806,16 +8705,14 @@
  • get_default() (evennia.utils.picklefield.PickledObjectField method)
  • -
  • get_defense() (in module evennia.contrib.game_systems.turnbattle.tb_basic) +
  • get_defense() (evennia.contrib.game_systems.turnbattle.tb_basic.BasicCombatRules method)
  • get_deferred() (evennia.scripts.taskhandler.TaskHandler method) @@ -8985,8 +8882,6 @@
  • get_min_height() (evennia.utils.evtable.EvCell method)
  • get_min_width() (evennia.utils.evtable.EvCell method) -
  • -
  • get_modified_time() (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage method)
  • get_new() (evennia.server.portal.rss.RSSReader method)
  • @@ -9040,8 +8935,6 @@
  • (evennia.web.website.views.objects.ObjectDetailView method)
  • -
  • get_object_parameters() (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage method) -
  • get_object_with_account() (evennia.objects.manager.ObjectDBManager method)
  • get_objs_at_coordinates() (evennia.contrib.grid.wilderness.wilderness.WildernessScript method) @@ -9110,7 +9003,7 @@
  • (evennia.web.website.views.help.HelpMixin method)
  • -
  • get_range() (in module evennia.contrib.game_systems.turnbattle.tb_range) +
  • get_range() (evennia.contrib.game_systems.turnbattle.tb_range.RangedCombatRules method)
  • get_recently_connected_accounts() (evennia.accounts.manager.AccountDBManager method)
  • @@ -9247,10 +9140,6 @@
  • grid() (evennia.contrib.grid.xyzgrid.xyzgrid.XYZGrid property)
  • groups (evennia.accounts.models.AccountDB attribute) -
  • -
  • gzip (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute) -
  • -
  • gzip_content_types (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute)
  • @@ -10123,17 +10012,7 @@
  • initialize_for_combat() (evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler method) - -
  • initialize_nick_templates() (in module evennia.typeclasses.attributes)
  • inlines (evennia.web.admin.accounts.AccountAdmin attribute) @@ -10221,6 +10100,8 @@
  • IRCBot.MultipleObjectsReturned
  • IRCBotFactory (class in evennia.server.portal.irc) +
  • +
  • is_active (evennia.accounts.models.AccountDB attribute)
  • is_active() (evennia.scripts.models.ScriptDB property)
  • @@ -10238,34 +10119,18 @@
  • is_exit (evennia.commands.command.Command attribute)
  • -
  • is_in_combat() (in module evennia.contrib.game_systems.turnbattle.tb_basic) - -
  • is_iter() (in module evennia.utils.utils) +
  • +
  • is_staff (evennia.accounts.models.AccountDB attribute) +
  • +
  • is_superuser (evennia.accounts.models.AccountDB attribute)
  • is_superuser() (evennia.objects.objects.DefaultObject property)
  • -
  • is_turn() (in module evennia.contrib.game_systems.turnbattle.tb_basic) - -
  • is_typeclass() (evennia.typeclasses.models.TypedObject method)
  • is_valid() (evennia.contrib.game_systems.barter.barter.TradeTimeout method) @@ -10295,16 +10160,18 @@
  • istitle() (evennia.utils.ansi.ANSIString method)
  • isupper() (evennia.utils.ansi.ANSIString method) +
  • +
  • ItemCombatRules (class in evennia.contrib.game_systems.turnbattle.tb_items)
  • itemcoordinates() (evennia.contrib.grid.wilderness.wilderness.WildernessScript property)
  • -
  • itemfunc_add_condition() (in module evennia.contrib.game_systems.turnbattle.tb_items) +
  • itemfunc_add_condition() (evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules method)
  • -
  • itemfunc_attack() (in module evennia.contrib.game_systems.turnbattle.tb_items) +
  • itemfunc_attack() (evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules method)
  • -
  • itemfunc_cure_condition() (in module evennia.contrib.game_systems.turnbattle.tb_items) +
  • itemfunc_cure_condition() (evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules method)
  • -
  • itemfunc_heal() (in module evennia.contrib.game_systems.turnbattle.tb_items) +
  • itemfunc_heal() (evennia.contrib.game_systems.turnbattle.tb_items.ItemCombatRules method)
  • ITEMFUNCS (in module evennia.contrib.game_systems.turnbattle.tb_items)
  • @@ -10327,12 +10194,6 @@
  • join_fight() (evennia.contrib.game_systems.turnbattle.tb_basic.TBBasicTurnHandler method)
  • @@ -11033,6 +10894,10 @@
  • LanguageHandler.DoesNotExist
  • LanguageHandler.MultipleObjectsReturned +
  • +
  • last_login (evennia.accounts.models.AccountDB attribute) +
  • +
  • last_name (evennia.accounts.models.AccountDB attribute)
  • latinify() (in module evennia.utils.utils)
  • @@ -11169,8 +11034,6 @@
  • list_tasks() (evennia.contrib.base_systems.ingame_python.commands.CmdCallback method)
  • list_to_string() (in module evennia.utils.utils) -
  • -
  • listdir() (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage method)
  • Listenable (class in evennia.contrib.full_systems.evscaperoom.objects)
  • @@ -11207,8 +11070,6 @@
  • load_sync_data() (evennia.server.session.Session method)
  • loads() (in module evennia.server.portal.amp) -
  • -
  • location (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute)
  • location() (evennia.objects.models.ObjectDB property)
  • @@ -12222,8 +12083,6 @@
  • (evennia.server.webserver.Website method)
  • -
  • lookup_env() (in module evennia.contrib.base_systems.awsstorage.aws_s3_cdn) -
  • lower() (evennia.utils.ansi.ANSIString method)
  • lstrip() (evennia.utils.ansi.ANSIString method) @@ -12235,6 +12094,8 @@ - +
    -
  • max_memory_size (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute) -
  • max_num_lines (evennia.web.website.views.channels.ChannelDetailView attribute)
  • max_pathfinding_length (evennia.contrib.grid.xyzgrid.xymap.XYMap attribute) @@ -12665,8 +12524,6 @@
  • ModelAttributeBackend (class in evennia.typeclasses.attributes)
  • models (evennia.web.admin.comms.MsgForm.Meta attribute) -
  • -
  • modified_time() (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage method)
  • module @@ -12741,8 +12598,6 @@
  • evennia.contrib.base_systems
  • evennia.contrib.base_systems.awsstorage -
  • -
  • evennia.contrib.base_systems.awsstorage.aws_s3_cdn
  • evennia.contrib.base_systems.awsstorage.tests
  • @@ -13489,9 +13344,11 @@

    N

    @@ -13821,18 +13670,16 @@
  • obfuscate_whisper() (in module evennia.contrib.rpg.rpsystem.rplanguage)
  • -
  • obj (evennia.contrib.game_systems.cooldowns.cooldowns.CooldownHandler attribute) +
  • obj (evennia.contrib.base_systems.ingame_python.callbackhandler.Callback attribute)
  • -
  • obj() (evennia.contrib.base_systems.ingame_python.callbackhandler.Callback property) - -
  • obj1_search (evennia.contrib.full_systems.evscaperoom.commands.CmdCreateObj attribute)
  • -
  • preload_metadata (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute) -
  • print_debug_info() (evennia.utils.evmenu.EvMenu method)
  • print_help() (evennia.contrib.base_systems.unixcommand.unixcommand.UnixCommandParser method) @@ -14908,8 +14755,6 @@
  • prototype_to_str() (in module evennia.prototypes.prototypes)
  • PrototypeEvMore (class in evennia.prototypes.prototypes) -
  • -
  • proxies (evennia.contrib.base_systems.awsstorage.aws_s3_cdn.S3Boto3Storage attribute)
  • puppet() (evennia.accounts.accounts.DefaultAccount property)
  • @@ -14961,10 +14806,10 @@
  • (evennia.typeclasses.attributes.ModelAttributeBackend method)
  • -
  • query_status() (in module evennia.server.evennia_launcher) -
  • -
  • replace_data() (evennia.utils.evtable.EvCell method)
  • +